PageRenderTime 55ms CodeModel.GetById 14ms app.highlight 32ms RepoModel.GetById 2ms app.codeStats 0ms

/includes/class.flyspray.php

https://bitbucket.org/djl/flyspray-mirror
PHP | 1164 lines | 656 code | 124 blank | 384 comment | 124 complexity | 0e2c015b2c45bde79f0e4cb7a86d3d4e MD5 | raw file
   1<?php
   2/**
   3 * Flyspray
   4 *
   5 * Flyspray class
   6 *
   7 * This script contains all the functions we use often in
   8 * Flyspray to do miscellaneous things.
   9 *
  10 * @license http://opensource.org/licenses/lgpl-license.php Lesser GNU Public License
  11 * @package flyspray
  12 * @author Tony Collins
  13 * @author Florian Schmitz
  14 * @author Cristian Rodriguez
  15 */
  16
  17class Flyspray
  18{
  19
  20    /**
  21     * Current Flyspray version. Change this for each release.  Don't forget!
  22     * @access public
  23     * @var string
  24     */
  25    var $version = '1.0.0 dev';
  26
  27    /**
  28     * Flyspray preferences
  29     * @access public
  30     * @var array
  31     */
  32    var $prefs   = array();
  33
  34    /**
  35     * Max. file size for file uploads. 0 = no uploads allowed
  36     * @access public
  37     * @var integer
  38     */
  39    var $max_file_size = 0;
  40
  41    /**
  42     * List of projects the user is allowed to view
  43     * @access public
  44     * @var array
  45     */
  46    var $projects = array();
  47
  48    /**
  49     * List of severities. Loaded in i18n.inc.php
  50     * @access public
  51     * @var array
  52     */
  53    var $severities = array();
  54
  55    /**
  56     * List of all permissions to be used at various places
  57     * @access public
  58     * @var array
  59     */
  60    var $perms = array('manage_project', 'view_private', 'edit_private',
  61                       'view_tasks', 'open_new_tasks', 'modify_own_tasks',
  62                       'modify_all_tasks', 'view_history', 'close_own_tasks', 'close_other_tasks',
  63                       'edit_assignments', 'add_to_assignees', 'assign_to_self', 'assign_others_to_self', 'show_as_assignees',
  64                       'view_comments', 'add_comments', 'edit_own_comments', 'edit_comments', 'delete_comments',
  65                       'create_attachments', 'delete_attachments', 'view_userlist', 'view_reports',
  66                       'add_votes', 'view_svn');
  67
  68    /**
  69     * Sort permissions into "groups". Only for the visuals.
  70     * @access public
  71     * @var array
  72     */
  73    var $permgroups = array( array(0, 2, 'specialperms'), array(3, 9, 'taskperms'),
  74                             array(10, 14, 'assignmentperms'), array(15, 19, 'commentperms'),
  75                             array(20, 21, 'attachmentperms'), array(22, 25, 'variousperms'));
  76
  77    // Application-wide preferences {{{
  78    /**
  79     * Constructor, starts session, loads settings
  80     * @access private
  81     * @return void
  82     * @version 1.0
  83     */
  84    function Flyspray()
  85    {
  86        global $db;
  87
  88        $this->startSession();
  89
  90        $res = $db->query('SELECT pref_name, pref_value FROM {prefs}');
  91
  92        while ($row = $res->fetchRow()) {
  93            $this->prefs[$row['pref_name']] = $row['pref_value'];
  94        }
  95
  96        $sizes = array();
  97        foreach (array(ini_get('memory_limit'), ini_get('post_max_size'), ini_get('upload_max_filesize')) as $val) {
  98            if (!$val) {
  99                continue;
 100            }
 101
 102            $val = trim($val);
 103            $last = strtolower($val{strlen($val)-1});
 104            switch ($last) {
 105                // The 'G' modifier is available since PHP 5.1.0
 106                case 'g':
 107                    $val *= 1024;
 108                case 'm':
 109                    $val *= 1024;
 110                case 'k':
 111                    $val *= 1024;
 112            }
 113
 114            $sizes[] = $val;
 115        }
 116
 117        clearstatcache();
 118        $func = create_function('$x', 'return @is_file($x . "/index.html") && is_writable($x);');
 119        $this->max_file_size = ((bool) ini_get('file_uploads') && $func(BASEDIR . '/attachments')) ? round((min($sizes)/1024/1024), 1) : 0;
 120    } // }}}
 121
 122    function base_version($version)
 123    {
 124        if (strpos($version, ' ') === false) {
 125            return $version;
 126        }
 127        return substr($version, 0, strpos($version, ' '));
 128    }
 129
 130    function get_config_path($basedir = BASEDIR)
 131    {
 132        $cfile = $basedir . '/flyspray.conf.php';
 133        if (is_readable($hostconfig = sprintf('%s/%s.conf.php', $basedir, $_SERVER['SERVER_NAME']))) {
 134            $cfile = $hostconfig;
 135        }
 136        return $cfile;
 137    }
 138
 139    // {{{ Redirect to $url
 140    /**
 141     * Redirects the browser to the page in $url
 142     * This function is based on PEAR HTTP class
 143     * @param string $url
 144     * @param bool $exit
 145     * @param bool $rfc2616
 146     * @license BSD
 147     * @access public static
 148     * @return bool
 149     * @version 1.0
 150     */
 151    function Redirect($url, $exit = true, $rfc2616 = true)
 152    {
 153
 154        @ob_clean();
 155
 156        if (isset($_SESSION) && count($_SESSION)) {
 157            session_write_close();
 158        }
 159
 160        if (headers_sent()) {
 161            die('Headers are already sent, this should not have happened. Please inform Flyspray developers.');
 162        }
 163
 164        $url = FlySpray::absoluteURI($url);
 165
 166
 167        header('Location: '. $url);
 168
 169        if ($rfc2616 && isset($_SERVER['REQUEST_METHOD']) &&
 170            $_SERVER['REQUEST_METHOD'] != 'HEAD') {
 171            $url = Filters::noXSS($url);
 172            printf('%s to: <a href="%s">%s</a>.', eL('Redirect'), $url, $url);
 173        }
 174        if ($exit) {
 175            exit;
 176        }
 177
 178        return true;
 179    } // }}}
 180
 181    /**
 182     * Absolute URI (This function is part of PEAR::HTTP licensed under the BSD) {{{
 183     *
 184     * This function returns the absolute URI for the partial URL passed.
 185     * The current scheme (HTTP/HTTPS), host server, port, current script
 186     * location are used if necessary to resolve any relative URLs.
 187     *
 188     * Offsets potentially created by PATH_INFO are taken care of to resolve
 189     * relative URLs to the current script.
 190     *
 191     * You can choose a new protocol while resolving the URI.  This is
 192     * particularly useful when redirecting a web browser using relative URIs
 193     * and to switch from HTTP to HTTPS, or vice-versa, at the same time.
 194     *
 195     * @author  Philippe Jausions <Philippe.Jausions@11abacus.com>
 196     * @static
 197     * @access  public
 198     * @return  string  The absolute URI.
 199     * @param   string  $url Absolute or relative URI the redirect should go to.
 200     * @param   string  $protocol Protocol to use when redirecting URIs.
 201     * @param   integer $port A new port number.
 202     */
 203    function absoluteURI($url = null, $protocol = null, $port = null)
 204    {
 205        // filter CR/LF
 206        $url = str_replace(array("\r", "\n"), ' ', $url);
 207
 208        // Mess around with already absolute URIs
 209        if (preg_match('!^([a-z0-9]+)://!i', $url)) {
 210            if (empty($protocol) && empty($port)) {
 211                return $url;
 212            }
 213            if (!empty($protocol)) {
 214                $url = $protocol .':'. end($array = explode(':', $url, 2));
 215            }
 216            if (!empty($port)) {
 217                $url = preg_replace('!^(([a-z0-9]+)://[^/:]+)(:[\d]+)?!i',
 218                    '\1:'. $port, $url);
 219            }
 220            return $url;
 221        }
 222
 223        $host = 'localhost';
 224        if (!empty($_SERVER['HTTP_HOST'])) {
 225            list($host) = explode(':', $_SERVER['HTTP_HOST']);
 226        } elseif (!empty($_SERVER['SERVER_NAME'])) {
 227            list($host) = explode(':', $_SERVER['SERVER_NAME']);
 228        }
 229
 230        if (empty($protocol)) {
 231            if (isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'], 'on')) {
 232                $protocol = 'https';
 233            } else {
 234                $protocol = 'http';
 235            }
 236            if (!isset($port) || $port != intval($port)) {
 237                $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
 238            }
 239        }
 240
 241        if ($protocol == 'http' && $port == 80) {
 242            unset($port);
 243        }
 244        if ($protocol == 'https' && $port == 443) {
 245            unset($port);
 246        }
 247
 248        $server = $protocol .'://'. $host . (isset($port) ? ':'. $port : '');
 249
 250
 251        if (!strlen($url) || $url{0} == '?' || $url{0} == '#') {
 252            $uri = isset($_SERVER['REQUEST_URI']) ?
 253                $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF'];
 254            if ($url && $url{0} == '?' && false !== ($q = strpos($uri, '?'))) {
 255                $url = substr($uri, 0, $q) . $url;
 256            } else {
 257                $url = $uri . $url;
 258            }
 259        }
 260
 261        if ($url{0} == '/') {
 262            return $server . $url;
 263        }
 264
 265        // Check for PATH_INFO
 266        if (isset($_SERVER['PATH_INFO']) && strlen($_SERVER['PATH_INFO']) &&
 267                $_SERVER['PHP_SELF'] != $_SERVER['PATH_INFO']) {
 268            $path = dirname(substr($_SERVER['PHP_SELF'], 0, -strlen($_SERVER['PATH_INFO'])));
 269        } else {
 270            $path = dirname($_SERVER['PHP_SELF']);
 271        }
 272
 273        if (substr($path = strtr($path, '\\', '/'), -1) != '/') {
 274            $path .= '/';
 275        }
 276
 277        return $server . $path . $url;
 278    } // }}}
 279
 280    // Duplicate submission check {{{
 281    /**
 282     * Test to see if user resubmitted a form.
 283     * Checks only newtask and addcomment actions.
 284     * @return bool true if user has submitted the same action within less than 6 hours, false otherwise
 285     * @access public static
 286     * @version 1.0
 287     */
 288    function requestDuplicated()
 289    {
 290        // garbage collection -- clean entries older than 6 hrs
 291        $now = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
 292        if (!empty($_SESSION['requests_hash'])) {
 293            foreach ($_SESSION['requests_hash'] as $key => $val) {
 294                if ($val < $now-6*60*60) {
 295                    unset($_SESSION['requests_hash'][$key]);
 296                }
 297            }
 298        }
 299
 300      if (count($_POST)) {
 301
 302        if (preg_match('/^newtask|addcomment$/', Post::val('action', '')))
 303        {
 304            $currentrequest = md5(serialize($_POST));
 305            if (!empty($_SESSION['requests_hash'][$currentrequest])) {
 306                return true;
 307            }
 308            $_SESSION['requests_hash'][$currentrequest] = time();
 309        }
 310      }
 311        return false;
 312    } // }}}
 313
 314   /**
 315     * Gets the global ID of a task
 316     * @param string $task_id eg. FS#123, PR#12, 543 = FS#123, ...
 317     * @access public static
 318     * @return integer 0 on failure
 319     */
 320    function GetTaskId($task_id)
 321    {
 322        global $db;
 323
 324        if (is_numeric($task_id)) {
 325            // can only be global
 326            return $task_id;
 327        }
 328
 329        @list($prefix, $task) = explode( (strpos($task_id, '#') !== false) ? '#' : ' ', $task_id);
 330        if (!$task) {
 331            // certainly no existing task
 332            return 0;
 333        }
 334
 335        if ($prefix == 'FS' || trim($prefix) == 'bug') {
 336            // global as well
 337            return $task;
 338        }
 339
 340        // now try to get the global ID based on project level id
 341        return (int) $db->x->GetOne('SELECT task_id
 342                                    FROM {tasks} t
 343                               LEFT JOIN {projects} p ON t.project_id = p.project_id
 344                                   WHERE prefix_id = ? AND project_prefix = ?',
 345                                    null, array($task, $prefix));
 346    }
 347
 348    // Retrieve task details {{{
 349    /**
 350     * Gets all information about a task (and caches information if wanted)
 351     * @param integer $task_id
 352     * @param bool $cache_enabled
 353     * @access public static
 354     * @return mixed an array with all taskdetails or false on failure
 355     */
 356    function GetTaskDetails($task_id, $cache_enabled = false, $prefix = null)
 357    {
 358        global $db, $fs, $proj;
 359
 360        static $cache = array();
 361        $task_id = intval($task_id);
 362
 363        if (isset($cache[$task_id . (string) $prefix]) && $cache_enabled) {
 364            return $cache[$task_id . (string) $prefix];
 365        }
 366
 367        if (!is_null($prefix) && $prefix != 'FS' && trim($prefix) != 'bug') {
 368            $where = 't.prefix_id = ? AND project_prefix = ?';
 369            $params = array($task_id, trim($prefix));
 370        } else {
 371            $where = 't.task_id = ?';
 372            $params = array($task_id);
 373        }
 374
 375        $task = $db->x->getRow('SELECT  t.*, p.project_prefix, p.project_title,
 376                                      r.item_name   AS resolution_name,
 377                                      uo.real_name  AS opened_by_name,
 378                                      ue.real_name  AS last_edited_by_name,
 379                                      uc.real_name  AS closed_by_name
 380                                FROM  {tasks}       t
 381                           LEFT JOIN  {list_items}  r  ON t.resolution_reason = r.list_item_id
 382                           LEFT JOIN  {users}       uo ON t.opened_by = uo.user_id
 383                           LEFT JOIN  {users}       ue ON t.last_edited_by = ue.user_id
 384                           LEFT JOIN  {users}       uc ON t.closed_by = uc.user_id
 385                           LEFT JOIN  {projects}    p  ON t.project_id = p.project_id
 386                               WHERE  ' . $where, null, $params);
 387
 388        if (!$task) {
 389            return false;
 390        }
 391
 392        // Now add custom fields
 393        $sql = $db->x->getAll('SELECT field_value, field_name, f.field_id, li.item_name, lc.category_name, user_name
 394                               FROM {field_values} fv
 395                          LEFT JOIN {fields} f ON f.field_id = fv.field_id
 396                          LEFT JOIN {list_items} li ON (f.list_id = li.list_id AND field_value = li.list_item_id)
 397                          LEFT JOIN {list_category} lc ON (f.list_id = lc.list_id AND field_value = lc.category_id)
 398                          LEFT JOIN {users} u ON (field_type = ? AND field_value = u.user_id)
 399                              WHERE task_id = ?', null, array(FIELD_USER, $task['task_id']));
 400        foreach ($sql as $row) {
 401            $task['field' . $row['field_id']] = $row['field_value'];
 402            $task['field' . $row['field_id'] . '_name'] = ($row['user_name'] ? $row['user_name'] : ($row['item_name'] ? $row['item_name'] : $row['category_name']));
 403        }
 404
 405        $task['assigned_to'] = $task['assigned_to_name'] =  $task['assigned_to_uname'] = array();
 406        if ($assignees = Flyspray::GetAssignees($task_id, true)) {
 407            $task['assigned_to'] = $assignees[0];
 408            $task['assigned_to_name'] = $assignees[1];
 409            $task['assigned_to_uname'] = $assignees[2];
 410        }
 411
 412        $cache[$task_id . (string) $prefix] = $task;
 413
 414        return $task;
 415    } // }}}
 416    // List projects {{{
 417    /**
 418     * Returns a list of all projects
 419     * @access public static
 420     * @return array
 421     * @version 1.0
 422     */
 423    function listProjects()
 424    {
 425        global $db;
 426
 427        return $db->x->getAll('SELECT  project_id, project_title FROM {projects}');
 428    } // }}}
 429    // List themes {{{
 430    /**
 431     * Returns a list of all themes
 432     * @access public static
 433     * @return array
 434     * @version 1.0
 435     */
 436    function listThemes()
 437    {
 438        if ($handle = opendir(BASEDIR . '/themes/')) {
 439            $theme_array = array();
 440            while (false !== ($file = readdir($handle))) {
 441                if ($file != '.' && $file != '..' && is_file(BASEDIR . "/themes/$file/theme.css")) {
 442                    $theme_array[] = $file;
 443                }
 444            }
 445            closedir($handle);
 446        }
 447
 448        sort($theme_array);
 449        return $theme_array;
 450    } // }}}
 451    // List a project's group {{{
 452    /**
 453     * Returns a list of a project's groups
 454     * @param integer $proj_id
 455     * @access public static
 456     * @return array
 457     * @version 1.0
 458     */
 459    function listGroups($proj_id = 0)
 460    {
 461        global $db;
 462        return $db->x->getAll('SELECT  *
 463                             FROM  {groups}
 464                            WHERE  project_id = ?
 465                         ORDER BY  group_id ASC', null, array($proj_id));
 466    }
 467    /**
 468     * Returns a list of all groups, sorted by project
 469     * @param integer $user_id restrict to groups the user is member of
 470     * @access public static
 471     * @return array
 472     * @version 1.0
 473     */
 474    function listallGroups($user_id = null)
 475    {
 476        global $db, $fs;
 477
 478        $group_list = array(L('global') => null);
 479        $params = array();
 480
 481        $query = 'SELECT g.group_id, group_name, group_desc, g.project_id, project_title
 482                    FROM {groups} g
 483               LEFT JOIN {projects} p ON p.project_id = g.project_id';
 484        // Limit to groups a specific user is in
 485        if (!is_null($user_id)) {
 486            $query .= ' LEFT JOIN {users_in_groups} uig ON uig.group_id = g.group_id
 487                            WHERE uig.user_id = ? ';
 488            $params[] = $user_id;
 489        }
 490        $sql = $db->getAll($query, null, $params);
 491
 492        foreach ($sql as $row) {
 493            // make sure that the user only sees projects he is allowed to
 494            if ($row['project_id'] != '0' && Flyspray::array_find('project_id', $row['project_id'], $fs->projects) === false) {
 495                continue;
 496            }
 497            $group_list[$row['project_title']][] = $row;
 498        }
 499        $group_list[L('global')] = $group_list[''];
 500        unset($group_list['']);
 501
 502        return $group_list;
 503    }
 504    // }}}
 505    // List languages {{{
 506    /**
 507     * Returns a list of installed languages
 508     * @access public static
 509     * @return array
 510     * @version 1.0
 511     */
 512    function listLangs()
 513    {
 514        return str_replace('.php', '', array_map('basename', glob_compat(BASEDIR ."/lang/[a-zA-Z]*.php")));
 515
 516    } // }}}
 517    // Log events to the history table {{{
 518    /**
 519     * Saves an event to the database
 520     * @param integer $task_id
 521     * @param integer $type
 522     * @param string $newvalue
 523     * @param string $oldvalue
 524     * @param string $field
 525     * @param integer $time for synchronisation with other functions
 526     * @access public static
 527     * @return void
 528     * @version 1.0
 529     */
 530    function logEvent($task_id, $type, $newvalue = '', $oldvalue = '', $field = '', $time = null)
 531    {
 532        global $db, $user;
 533
 534        // This function creates entries in the history table.  These are the event types:
 535        //  0: Fields changed in a task
 536        //  1: New task created
 537        //  2: Task closed
 538        //  3: Task edited (for backwards compatibility with events prior to the history system)
 539        //  4: Comment added
 540        //  5: Comment edited
 541        //  6: Comment deleted
 542        //  7: Attachment added
 543        //  8: Attachment deleted
 544        //  9: User added to notification list
 545        // 10: User removed from notification list
 546        // 11: Related task added to this task
 547        // 12: Related task removed from this task
 548        // 13: Task re-opened
 549        // 14: Task assigned to user / re-assigned to different user / Unassigned
 550        // 15: This task was added to another task's related list
 551        // 16: This task was removed from another task's related list
 552        // 17: Reminder added
 553        // 18: Reminder deleted
 554        // 19: User took ownership
 555        // 20: Closure request made
 556        // 21: Re-opening request made
 557        // 22: Adding a new dependency
 558        // 23: This task added as a dependency of another task
 559        // 24: Removing a dependency
 560        // 25: This task removed from another task's dependency list
 561        // 26: Task was made private
 562        // 27: Task was made public
 563        // 28: PM request denied
 564        // 29: User added to the list of assignees
 565        // 30: New user registration
 566        // 31: User deletion
 567
 568        $query_params = array('task_id'=> intval($task_id), 
 569                              'user_id'=> intval($user->id),
 570                              'event_date'=> ((!is_numeric($time)) ? time() : $time),
 571                              'event_type'=> $type, 
 572                              'field_changed'=> $field, 
 573                              'old_value'=> (string) $oldvalue, 
 574                              'new_value'=> $newvalue);
 575
 576        if (!Pear::isError($db->x->autoExecute('{history}', $query_params))) {
 577            return true;
 578         }
 579
 580        return false;
 581    } // }}}
 582    // Log a request for an admin/project manager to do something {{{
 583    /**
 584     * Adds an admin request to the database
 585     * @param integer $type 1: Task close, 2: Task re-open
 586     * @param integer $project_id
 587     * @param integer $task_id
 588     * @param integer $submitter
 589     * @param string $reason
 590     * @access public static
 591     * @return void
 592     * @version 1.0
 593     */
 594    function AdminRequest($type, $project_id, $task_id, $submitter, $reason)
 595    {
 596        global $db;
 597        $db->x->autoExecute('{admin_requests}', array('project_id'=> $project_id, 
 598                                                      'task_id'=> $task_id, 
 599                                                      'submitted_by'=> $submitter, 
 600                                                      'request_type'=> $type, 
 601                                                      'reason_given'=> $reason, 
 602                                                      'time_submitted'=> time()));
 603    } // }}}
 604    // Check for an existing admin request for a task and event type {{{;
 605    /**
 606     * Checks whether or not there is an admin request for a task
 607     * @param integer $type 1: Task close, 2: Task re-open
 608     * @param integer $task_id
 609     * @access public static
 610     * @return bool
 611     * @version 1.0
 612     */
 613    function AdminRequestCheck($type, $task_id)
 614    {
 615        global $db;
 616
 617        $check = $db->x->getOne('SELECT request_id
 618                                 FROM {admin_requests}
 619                                WHERE request_type = ? AND task_id = ? AND resolved_by = 0',
 620                               null, array($type, $task_id));
 621        return (bool) $check;
 622    } // }}}
 623    // Get the current user's details {{{
 624    /**
 625     * Gets all user details of a user
 626     * @param integer $user_id
 627     * @access public static
 628     * @return array
 629     * @version 1.0
 630     */
 631    function getUserDetails($user_id)
 632    {
 633        global $db;
 634
 635        return $db->x->getRow('SELECT * FROM {users} WHERE user_id = ?', null, intval($user_id));
 636    } // }}}
 637    // Get group details {{{
 638    /**
 639     * Gets all information about a group
 640     * @param integer $group_id
 641     * @access public static
 642     * @return array
 643     * @version 1.0
 644     */
 645    function getGroupDetails($group_id)
 646    {
 647        global $db;
 648        return $db->x->getRow('SELECT *, count(uig.user_id) AS num_users
 649                             FROM {groups} g
 650                        LEFT JOIN {users_in_groups} uig ON uig.group_id = g.group_id
 651                            WHERE g.group_id = ?
 652                         GROUP BY g.group_id',
 653                          null, $group_id);
 654    } // }}}
 655    //  {{{
 656    /**
 657     * Crypt a password with md5
 658     * @param string $password
 659     * @access public static
 660     * @return string
 661     * @version 1.0
 662     */
 663    function cryptPassword($password, $salt = null)
 664    {
 665            return is_null($salt) ? md5($password) : hash_hmac('md5', $password, $salt);
 666    } // }}}
 667    // Set cookie {{{
 668    /**
 669     * Sets a cookie, automatically setting the URL
 670     * @param string $name
 671     * @param string $val
 672     * @param integer $time
 673     * @access public static
 674     * @return bool
 675     * @version 1.0
 676     */
 677    function setCookie($name, $val, $time = null)
 678    {
 679        $url = parse_url($GLOBALS['baseurl']);
 680        if (!is_int($time)) {
 681            $time = time()+60*60*24*30;
 682        }
 683        if ((strlen($name) + strlen($val)) > 4096) {
 684            //violation of the protocol
 685            trigger_error("Flyspray sent a too big cookie, browsers will not handle it");
 686            return false;
 687        }
 688
 689        return setcookie($name, $val, $time, $url['path']);
 690    } // }}}
 691    // Reminder daemon {{{
 692    /**
 693     * Starts the reminder daemon
 694     * @access public static
 695     * @return void
 696     * @version 1.0
 697     */
 698    function startReminderDaemon()
 699    {
 700        global $baseurl;
 701
 702        $runfile = Flyspray::get_tmp_dir() . '/flysprayreminders.run';
 703        $timeout = 600;
 704
 705        if (!is_file($runfile) or filemtime($runfile) < time() - ($timeout * 2)) {
 706
 707            $include = 'schedule.php';
 708            /* "localhost" is on **purpose** not a mistake �?�?
 709             * almost any server accepts requests to itself in localhost ;)
 710             * firewalls will not block it.
 711             * the "Host" http header will tell the webserver where flyspray is running.
 712             */
 713            Flyspray::remote_request($baseurl . $include, !GET_CONTENTS, $_SERVER['SERVER_PORT'], 'localhost', $_SERVER['HTTP_HOST']);
 714        }
 715    }
 716            // Start the session {{{
 717    /**
 718     * Starts the session
 719     * @access public static
 720     * @return void
 721     * @version 1.0
 722     * @notes smile intented
 723     */
 724    function startSession()
 725    {
 726        if (isset($_SESSION['SESSNAME'])) {
 727            return;
 728        }
 729
 730        $names = array( 'GetFirefox',
 731                        'UseLinux',
 732                        'NoMicrosoft',
 733                        'ThinkB4Replying',
 734                        'FreeSoftware',
 735                        'ReadTheFAQ',
 736                        'RTFM',
 737                        'VisitAU',
 738                        'SubliminalAdvertising',
 739                      );
 740
 741        foreach ($names as $val)
 742        {
 743            session_name($val);
 744            session_start();
 745
 746            if (isset($_SESSION['SESSNAME']))
 747            {
 748                $sessname = $_SESSION['SESSNAME'];
 749                break;
 750            }
 751
 752            $_SESSION = array();
 753            session_destroy();
 754            setcookie(session_name(), '', time()-60, '/');
 755        }
 756
 757        if (empty($sessname))
 758        {
 759            $rand_key = array_rand($names);
 760            $sessname = $names[$rand_key];
 761            session_name($sessname);
 762            session_start();
 763            $_SESSION['SESSNAME'] = $sessname;
 764        }
 765    }  // }}}
 766
 767    // Compare tasks {{{
 768    /**
 769     * Compares two tasks and returns an array of differences
 770     * @param array $old
 771     * @param array $new
 772     * @access public static
 773     * @return array array('field', 'old', 'new')
 774     * @version 1.0
 775     */
 776    function compare_tasks($old, $new)
 777    {
 778        global $proj;
 779        $comp = array('assigned_to_name', 'percent_complete',
 780                      'item_summary', 'detailed_desc', 'mark_private');
 781        $translation = array('assigned_to_name' => L('assignedto'),
 782                             'percent_complete' => L('percentcomplete'),
 783                             'mark_private' => L('visibility'),
 784                             'item_summary' => L('summary'),
 785                             'detailed_desc' => L('taskedited'));
 786        $changes = array();
 787        foreach ($comp as $db => $key)
 788        {
 789            $replace_new = $new[$key];
 790            $replace_old = $old[$key];
 791
 792            if ($old[$key] != $new[$key]) {
 793                switch ($key)
 794                {
 795                    case 'percent_complete':
 796                        $replace_new = $new[$key] . '%';
 797                        $replace_old = $old[$key] . '%';
 798                        break;
 799
 800                    case 'mark_private':
 801                        $replace_new = $new[$key] ? L('private') : L('public');
 802                        $replace_old = $old[$key] ? L('private') : L('public');
 803                        break;
 804                }
 805                $raw = (is_numeric($db)) ? $key : $db;
 806                $changes[] = array($key, $replace_old, $replace_new, $translation[$key], $raw, $old[$raw], $new[$raw]);
 807            }
 808        }
 809
 810        foreach ($proj->fields as $field) {
 811            $key = 'field'. $field->id;
 812            $old[$key] = isset($old[$key]) ? $old[$key] : '';
 813            $new[$key] = isset($new[$key]) ? $new[$key] : '';
 814            if ($old[$key] != $new[$key]) {
 815                if ($field->prefs['field_type'] == FIELD_DATE) {
 816                    $changes[] = array($key, formatDate($old[$key]), formatDate($new[$key]), $field->prefs['field_name'], $field->id, $old[$key], $new[$key]);
 817                } else {
 818                    $old[$key . '_name'] = isset($old[$key . '_name']) ? $old[$key . '_name'] : $old[$key];
 819                    $new[$key . '_name'] = isset($new[$key . '_name']) ? $new[$key . '_name'] : $new[$key];
 820                    $changes[] = array($key, $old[$key . '_name'], $new[$key . '_name'], $field->prefs['field_name'], $field->id, $old[$key], $new[$key]);
 821                }
 822            }
 823        }
 824
 825        return $changes;
 826    } // }}}
 827    // {{{
 828    /**
 829     * Get a list of assignees for a task
 830     * @param integer $task_id
 831     * @param bool $name whether or not names of the assignees should be returned as well
 832     * @access public static
 833     * @return array
 834     * @version 1.0
 835     */
 836    function GetAssignees($task_id, $names = false)
 837    {
 838        global $db;
 839
 840        $sql = $db->x->getAll('SELECT u.real_name, u.user_id, u.user_name
 841                             FROM {users} u, {assigned} a
 842                            WHERE task_id = ? AND u.user_id = a.user_id',
 843                              null, $task_id);
 844
 845        $assignees = array();
 846        foreach ($sql as $row) {
 847            if ($names) {
 848                $assignees[0][] = $row['user_id'];
 849                $assignees[1][] = $row['real_name'];
 850                $assignees[2][] = $row['user_name'];
 851            } else {
 852                $assignees[] = $row['user_id'];
 853            }
 854        }
 855
 856        return $assignees;
 857    } /// }}}
 858
 859    /**
 860     * Checks if a function is disabled
 861     * @param string $func_name
 862     * @access public static
 863     * @return bool
 864     * @version 1.0
 865     */
 866    function function_disabled($func_name)
 867    {
 868        $disabled_functions = explode(',', ini_get('disable_functions'));
 869        return in_array($func_name, $disabled_functions);
 870    }
 871
 872    /**
 873     * Returns the key number of an array which contains an array like array($key => $value)
 874     * For use with SQL result arrays
 875     * @param string $key
 876     * @param string $value
 877     * @param array $array
 878     * @access public static
 879     * @return mixed false if not found
 880     * @version 1.0
 881     */
 882    function array_find($key, $value, $array)
 883    {
 884        foreach ($array as $num => $part) {
 885            if (isset($part[$key]) && $part[$key] == $value) {
 886                return $num;
 887            }
 888        }
 889        return false;
 890    }
 891
 892    /**
 893     * Returns the user ID if valid, 0 otherwise
 894     * @param int $id
 895     * @access public static
 896     * @return integer 0 if the user does not exist
 897     * @version 1.0
 898     */
 899    function ValidUserId($id)
 900    {
 901        global $db;
 902
 903        $val = $db->x->GetOne('SELECT user_id FROM {users} WHERE user_id = ?', null, intval($id));
 904
 905        return intval($val);
 906    }
 907
 908    /**
 909     * Tries to determine a user ID from a user name. If the
 910     * user name does not exist, it assumes an user ID as input.     
 911     * @param mixed $user (string or int)
 912     * @access public static
 913     * @return integer 0 if the user does not exist
 914     * @version 1.0
 915     */
 916    function UserNameOrId($user)
 917    {
 918        $val = Flyspray::UserNameToId($user);
 919        return ($val) ? $val : Flyspray::ValidUserId($user);
 920    }
 921    
 922    /**
 923     * Returns the ID of a user with $name
 924     * @param string $name
 925     * @access public static
 926     * @return integer 0 if the user does not exist
 927     * @version 1.0
 928     */
 929    function UserNameToId($name)
 930    {
 931        global $db;
 932
 933        $val = $db->x->GetOne('SELECT user_id FROM {users} WHERE user_name = ?', null, trim($name));
 934
 935        return intval($val);
 936    }
 937
 938    /**
 939     * check_email
 940     *  checks if an email is valid
 941     * @param string $email
 942     * @access public
 943     * @return bool
 944     */
 945    function check_email($email)
 946    {
 947        include_once 'Validate.php';
 948
 949        return Validate::email($email);
 950    }
 951
 952    /**
 953     * get_tmp_dir
 954     * Based on PEAR System::tmpdir() by Tomas V.V.Cox.
 955     * @access public
 956     * @return void
 957     */
 958    function get_tmp_dir()
 959    {
 960        $return = '';
 961        if (function_exists('sys_get_temp_dir')) {
 962            $return = sys_get_temp_dir();
 963        } elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
 964            if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
 965                $return = $var;
 966            } else
 967            if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
 968                $return = $var;
 969            } else
 970            if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
 971                $return = $var;
 972            } else {
 973                $return = getenv('SystemRoot') . '\temp';
 974            }
 975
 976        } elseif ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
 977             $return = $var;
 978        } else {
 979            $return = '/tmp';
 980        }
 981        // Now, the final check
 982        if (@is_dir($return) && is_writable($return)) {
 983            return rtrim($return, DIRECTORY_SEPARATOR);
 984        // we have a problem at this stage.
 985        } elseif(is_writable(ini_get('upload_tmp_dir'))) {
 986            $return = ini_get('upload_tmp_dir');
 987        } elseif(is_writable(ini_get('session.save_path'))) {
 988            $return = ini_get('session.save_path');
 989        }
 990        return rtrim($return, DIRECTORY_SEPARATOR);
 991    }
 992
 993    /**
 994     * check_mime_type
 995     *
 996     * @param string $fname path to filename
 997     * @access public
 998     * @return string the mime type of the offended file.
 999     * @notes DO NOT use this function for any security related
1000     * task (i.e limiting file uploads by type)
1001     * it wasn't designed for that purpose but to UI related tasks.
1002     */
1003    function check_mime_type($fname) {
1004
1005        $type = '';
1006
1007        if (extension_loaded('fileinfo') && class_exists('finfo')) {
1008
1009            $info = new finfo(FILEINFO_MIME);
1010            $type = $info->file($fname);
1011
1012        } elseif(function_exists('mime_content_type')) {
1013
1014            $type = @mime_content_type($fname);
1015        // I hope we don't have to...
1016        } elseif(!FlySpray::function_disabled('exec') && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN'
1017            && php_uname('s') !== 'SunOS') {
1018                
1019                include_once 'class.commandexecution.php';
1020
1021                $file =& new CommandExecution();
1022                $file->setCmd('file');
1023                $file->bi = $fname;
1024                
1025                $type = $file->getCmdResult();
1026        }
1027        // if wasn't possible to determine , return empty string so
1028        // we can use the browser reported mime-type (probably fake)
1029        return trim($type);
1030    }
1031
1032    /**
1033     * Works like strtotime, but it considers the user's timezone
1034     * @access public
1035     * @param string $time
1036     * @return integer
1037     */
1038    function strtotime($time)
1039    {
1040        global $user;
1041
1042        $time = strtotime($time);
1043
1044        if (!$user->isAnon()) {
1045            $st = date('Z'); // server timezone offset
1046            // Example: User is UTC+3, Server UTC-2.
1047            // User enters 7:00. For the server it must be converted to 2:00 (done below)
1048            $time += ($st - $user->infos['time_zone']);
1049            // later it adds 5 hours to 2:00 for the user when the date is displayed.
1050        }
1051        //strtotime()  may return false, making this method to return bool instead of int.
1052        return $time ? $time : 0;
1053    }
1054
1055    /**
1056     * file_get_contents replacement for remote files
1057     * @access public
1058     * @param string $url
1059     * @param bool $get_contents whether or not to return file contents, use GET_CONTENTS for true
1060     * @param integer $port
1061     * @param string $connect manually choose server for connection
1062     * @return string an empty string is not necessarily a failure
1063     */
1064    function remote_request($url, $get_contents = false, $port = 80, $connect = '', $host = null)
1065    {
1066        $url = parse_url($url);
1067        if (!$connect) {
1068            $connect = $url['host'];
1069        }
1070
1071        if ($host) {
1072            $url['host'] = $host;
1073        }
1074
1075        $data = '';
1076
1077        if ($conn = @fsockopen($connect, $port, $errno, $errstr, 10)) {
1078            $out =  "GET {$url['path']} HTTP/1.0\r\n";
1079            $out .= "Host: {$url['host']}\r\n\r\n";
1080            $out .= "Connection: Close\r\n\r\n";
1081
1082            stream_set_timeout($conn, 5);
1083            fwrite($conn, $out);
1084
1085            if ($get_contents) {
1086                while (!feof($conn)) {
1087                    $data .= fgets($conn, 128);
1088                }
1089
1090                $pos = strpos($data, "\r\n\r\n");
1091
1092                if ($pos !== false) {
1093                   //strip the http headers.
1094                    $data = substr($data, $pos + 2 * strlen("\r\n"));
1095                }
1096            }
1097                fclose($conn);
1098        }
1099
1100        return $data;
1101    }
1102    
1103        
1104    /**
1105     * Returns an array containing all notification options the user is
1106     * allowed to use.
1107     * @access public
1108     * @return array
1109     */
1110    function GetNotificationOptions($noneAllowed = true)
1111    {
1112        switch ($this->prefs['user_notify']) 
1113        {
1114            case 0:
1115                return array(0             => L('none'));
1116            case 2:
1117                return array(NOTIFY_EMAIL  => L('email'));
1118            case 3:
1119                return array(NOTIFY_JABBER => L('jabber'));
1120                
1121        }
1122        
1123        $return = array(0             => L('none'),
1124                        NOTIFY_EMAIL  => L('email'),
1125                        NOTIFY_JABBER => L('jabber'),
1126                        NOTIFY_BOTH   => L('both'));
1127        if (!$noneAllowed) {
1128            unset($return[0]);
1129        }
1130        
1131        return $return;
1132    }
1133
1134    /**
1135     * getSvnRev
1136     *  For internal use
1137     * @access public
1138     * @return string
1139     */
1140    function getSvnRev()
1141    {
1142        if(is_file(BASEDIR. '/REVISION') && is_dir(BASEDIR . '/.svn')) {
1143
1144            return sprintf('r%d',file_get_contents(BASEDIR .'/REVISION'));
1145        }
1146
1147        return '';
1148    }
1149    
1150    function GetColorCssClass($task, $alternative = null)
1151    {
1152        $field = 'field' . $this->prefs['color_field'];
1153        if (!isset($task[$field])) {
1154            if (is_array($alternative) && isset($alternative[$field])) {
1155                $task = $alternative;
1156            } else {
1157                return '';
1158            }
1159        }
1160        return 'colorfield' . $task[$field];
1161    }
1162
1163}
1164?>