index : flyspray | |
Archlinux32 customized Flyspray installation | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | includes/class.user.php | 555 |
diff --git a/includes/class.user.php b/includes/class.user.php new file mode 100644 index 0000000..f79ad04 --- /dev/null +++ b/includes/class.user.php @@ -0,0 +1,555 @@ +<?php + +class User +{ + public $id = -1; + public $perms = array(); + public $infos = array(); + public $searches = array(); + public $search_keys = array('project', 'string', 'type', 'sev', 'pri', 'due', 'dev', 'cat', 'status', 'percent', 'changedfrom', 'closedfrom', + 'opened', 'closed', 'search_in_comments', 'search_in_details', 'search_for_all', 'reported', 'only_primary', 'only_watched', 'closedto', + 'changedto', 'duedatefrom', 'duedateto', 'openedfrom', 'openedto', 'has_attachment'); + + public function __construct($uid = 0) + { + global $db; + + if ($uid > 0) { + $sql = $db->query('SELECT *, g.group_id AS global_group, uig.record_id AS global_record_id + FROM {users} u, {users_in_groups} uig, {groups} g + WHERE u.user_id = ? AND uig.user_id = ? AND g.project_id = 0 + AND uig.group_id = g.group_id', + array($uid, $uid)); + } + + if ($uid > 0 && $db->countRows($sql) == 1) { + $this->infos = $db->fetchRow($sql); + $this->id = intval($uid); + } else { + $this->infos['real_name'] = L('anonuser'); + $this->infos['user_name'] = ''; + } + + $this->get_perms(); + } + + /** + * save_search + * + * @param string $do + * @access public + * @return void + * @notes FIXME: must return something, should not merge _GET and _REQUEST with other stuff. + */ + public function save_search($do = 'index') + { + global $db; + + if($this->isAnon()) { + return; + } + // Only logged in users get to use the 'last search' functionality + + if ($do == 'index') { + if (Post::val('search_name')) { + $arr = array(); + foreach ($this->search_keys as $key) { + $arr[$key] = Post::val($key, ($key == 'status') ? 'open' : null); + } + foreach (array('order', 'sort', 'order2', 'sort2') as $key) { + if (Post::val($key)) { + $arr[$key] = Post::val($key); + } + } + + $fields = array( + 'search_string'=> serialize($arr), + 'time'=> time(), + 'user_id'=> $this->id , + 'name'=> Post::val('search_name') + ); + $keys = array('name','user_id'); + $db->replace('{searches}', $fields, $keys); + } + } + + $sql = $db->query('SELECT * FROM {searches} WHERE user_id = ? ORDER BY name ASC', array($this->id)); + $this->searches = $db->fetchAllArray($sql); + } + + public function perms($name, $project = null) { + if (is_null($project)) { + global $proj; + $project = $proj->id; + } + + if (isset($this->perms[$project][$name])) { + return $this->perms[$project][$name]; + } else { + return 0; + } + } + + public function get_perms() + { + global $db, $fs; + + $fields = array('is_admin', 'manage_project', 'view_tasks', 'edit_own_comments', + 'open_new_tasks', 'modify_own_tasks', 'modify_all_tasks', + 'view_comments', 'add_comments', 'edit_comments', 'edit_assignments', + 'delete_comments', 'create_attachments', + 'delete_attachments', 'view_history', 'close_own_tasks', + 'close_other_tasks', 'assign_to_self', 'assign_others_to_self', + 'add_to_assignees', 'view_reports', 'add_votes', 'group_open','view_estimated_effort', + 'track_effort', 'view_current_effort_done', 'add_multiple_tasks', 'view_roadmap', + 'view_own_tasks', 'view_groups_tasks'); + + $this->perms = array(0 => array()); + // Get project settings which are important for permissions + # php7.2 compatible variant without create_function(), instead use a SQL UNION to fill a fake global-project with project_id=0 + $sql = $db->query(' + SELECT project_id, others_view, project_is_active, anon_open, comment_closed + FROM {projects} + UNION + SELECT 0,1,1,1,1'); + while ($row = $db->fetchRow($sql)) { + $this->perms[$row['project_id']] = $row; + } + + if (!$this->isAnon()) { + // Get the global group permissions for the current user + $sql = $db->query("SELECT ".join(', ', $fields).", g.project_id, uig.record_id, + g.group_open, g.group_id AS project_group + FROM {groups} g + LEFT JOIN {users_in_groups} uig ON g.group_id = uig.group_id + LEFT JOIN {projects} p ON g.project_id = p.project_id + WHERE uig.user_id = ? + ORDER BY g.project_id, g.group_id ASC", + array($this->id)); + + while ($row = $db->fetchRow($sql)) { + if (!isset($this->perms[$row['project_id']])) { + // should not happen, so clean up the DB + $db->query('DELETE FROM {users_in_groups} WHERE record_id = ?', array($row['record_id'])); + continue; + } + + $this->perms[$row['project_id']] = array_merge($this->perms[$row['project_id']], $row); + } + + // Set missing permissions and attachments + foreach ($this->perms as $proj_id => $value) { + foreach ($fields as $key) { + if ($key == 'project_group') { + continue; + } + + $this->perms[$proj_id][$key] = max($this->perms[0]['is_admin'], @$this->perms[$proj_id][$key], $this->perms[0][$key]); + if ($proj_id && $key != 'is_admin') { + $this->perms[$proj_id][$key] = max(@$this->perms[$proj_id]['manage_project'], $this->perms[$proj_id][$key]); + } + } + + // nobody can upload files if uploads are disabled at the system level.. + if (!$fs->max_file_size || !is_writable(BASEDIR .'/attachments')) { + $this->perms[$proj_id]['create_attachments'] = 0; + } + } + } + } + + public function check_account_ok() + { + global $conf, $baseurl; + // Anon users are always OK + if ($this->isAnon()) { + return; + } + $saltedpass = crypt($this->infos['user_pass'], $conf['general']['cookiesalt']); + + if (Cookie::val('flyspray_passhash') !== $saltedpass || !$this->infos['account_enabled'] + || !$this->perms('group_open', 0)) + { + $this->logout(); + Flyspray::redirect($baseurl); + } + } + + public function isAnon() + { + return $this->id < 0; + } + + /* }}} */ + /* permission related {{{ */ + + public function can_edit_comment($comment) + { + return $this->perms('edit_comments') + || ($comment['user_id'] == $this->id && $this->perms('edit_own_comments')); + } + + public function can_view_project($proj) + { + if (is_array($proj) && isset($proj['project_id'])) { + $proj = $proj['project_id']; + } + + return ($this->perms('view_tasks', $proj) || $this->perms('view_groups_tasks', $proj) || $this->perms('view_own_tasks', $proj)) + || ($this->perms('project_is_active', $proj) + && ($this->perms('others_view', $proj) || $this->perms('project_group', $proj))); + } + + /* can_select_project() is similiar to can_view_project(), but + * allows anonymous users/guests to select this project if the project allows anon task creation, + * but all other stuff is restricted. + */ + public function can_select_project($proj) + { + if (is_array($proj) && isset($proj['project_id'])) { + $proj = $proj['project_id']; + } + + return ( + $this->perms('view_tasks', $proj) + || $this->perms('view_groups_tasks', $proj) + || $this->perms('view_own_tasks', $proj) + ) + || + ( + $this->perms('project_is_active', $proj) + && ( + $this->perms('others_view', $proj) + || $this->perms('project_group', $proj) + || $this->perms('anon_open', $proj) + ) + ); + } + + public function can_view_task($task) + { + if ($task['task_token'] && Get::val('task_token') == $task['task_token']) { + return true; + } + + // Split into several separate tests so I can keep track on whats happening. + + // Project managers and admins allowed always. + if ($this->perms('manage_project', $task['project_id']) + || $this->perms('is_admin', $task['project_id'])) { + return true; + } + + // Allow if "allow anyone to view this project" is checked + // and task is not private. + if ($this->perms('others_view', $task['project_id']) && !$task['mark_private']) { + return true; + } + + if ($this->isAnon()) { + // Following checks need identified user. + return false; + } + + // Non-private task + if (!$task['mark_private']) { + // Can view tasks, always allow + if ($this->perms('view_tasks', $task['project_id'])) { + return true; + } + // User can view only own tasks + if ($this->perms('view_own_tasks', $task['project_id']) + && !$this->perms('view_groups_tasks', $task['project_id'])) { + if ($task['opened_by'] == $this->id) { + return true; + } + if (in_array($this->id, Flyspray::getAssignees($task['task_id']))) { + return true; + } + // No use to continue further. + return false; + } + // Ok, user *must* have view_groups_tasks permission, + // but do the check anyway just in case... there might + // appear more in the future. + if ($this->perms('view_groups_tasks', $task['project_id'])) { + // Two first checks the same as with view_own_tasks permission. + if ($task['opened_by'] == $this->id) { + return true; + } + // Fetch only once, could be needed three times. + $assignees = Flyspray::getAssignees($task['task_id']); + if (in_array($this->id, $assignees)) { + return true; + } + + // Must fetch other persons in the group now. Find out + // how to detect the right group for project and the + // other persons in it. Funny, found it in $perms. + $group = $this->perms('project_group', $task['project_id']); + $others = Project::listUsersIn($group); + + foreach ($others as $other) { + if ($other['user_id'] == $task['opened_by']) { + return true; + } + if (in_array($other['user_id'], $assignees)) { + return true; + } + } + + // Check the global group next. Note that for users in that group to be included, + // the has to be specified at global group level. So even if our permission system + // works by OR'ing the permissions together, who is actually considered to be in + // in the same group now depends on whether this permission has been given on global + // or project level. + if ($this->perms('view_groups_tasks', 0)) { + $group = $this->perms('project_group', 0); + $others = Project::listUsersIn($group); + + foreach ($others as $other) { + if ($other['user_id'] == $task['opened_by']) { + return true; + } + if (in_array($other['user_id'], $assignees)) { + return true; + } + } + } + + // No use to continue further. + return false; + } + } + + // Private task, user must be either assigned to the task + // or have opened it. + if ($task['mark_private']) { + if ($task['opened_by'] == $this->id) { + return true; + } + if (in_array($this->id, Flyspray::getAssignees($task['task_id']))) { + return true; + } + // No use to continue further. + return false; + } + + // Could not find any permission for viewing the task. + return false; + } + + public function can_edit_task($task) + { + return !$task['is_closed'] && ( + $this->perms('modify_all_tasks', $task['project_id']) || + ($this->id == $task['opened_by'] && $this->perms('modify_own_tasks', $task['project_id'])) || + in_array($this->id, Flyspray::getAssignees($task['task_id'])) + ); + } + + public function can_take_ownership($task) + { + $assignees = Flyspray::getAssignees($task['task_id']); + + return ($this->perms('assign_to_self', $task['project_id']) && empty($assignees)) + || ($this->perms('assign_others_to_self', $task['project_id']) && !in_array($this->id, $assignees)); + } + + public function can_add_to_assignees($task) + { + return ($this->perms('add_to_assignees', $task['project_id']) && !in_array($this->id, Flyspray::getAssignees($task['task_id']))); + } + + public function can_close_task($task) + { + return ($this->perms('close_own_tasks', $task['project_id']) && in_array($this->id, $task['assigned_to'])) + || $this->perms('close_other_tasks', $task['project_id']); + } + + public function can_set_task_parent($task) + { + return !$task['is_closed'] && ( + $this->perms('modify_all_tasks', $task['project_id']) || + in_array($this->id, Flyspray::getAssignees($task['task_id'])) + ); + } + + public function can_associate_task($task) + { + return !$task['is_closed'] && ( + $this->perms('modify_all_tasks', $task['project_id']) || + in_array($this->id, Flyspray::getAssignees($task['task_id'])) + ); + } + + public function can_add_task_dependency($task) + { + return !$task['is_closed'] && ( + $this->perms('modify_all_tasks', $task['project_id']) || + in_array($this->id, Flyspray::getAssignees($task['task_id'])) + ); + } + +//admin approve user registration + public function need_admin_approval() + { + global $fs; + return $this->isAnon() && $fs->prefs['need_approval'] && $fs->prefs['anon_reg']; + } + + public function get_group_id() + { + } + + /** + * tests if current configuration allows a guest user to register - without email verification code + */ + public function can_self_register() + { + global $fs; + return $this->isAnon() && $fs->prefs['anon_reg'] && !$fs->prefs['only_oauth_reg'] && !$fs->prefs['spam_proof'] ; + } + + /** + * tests if current configuration allows a guest user to register - with email verification code + */ + public function can_register() + { + global $fs; + return $this->isAnon() && $fs->prefs['anon_reg'] && !$fs->prefs['only_oauth_reg'] && $fs->prefs['spam_proof'] && !$fs->prefs['need_approval'] ; + } + + public function can_open_task($proj) + { + return $proj->id && ($this->perms('manage_project') || + $this->perms('project_is_active', $proj->id) && ($this->perms('open_new_tasks') || $this->perms('anon_open', $proj->id))); + } + + public function can_change_private($task) + { + return !$task['is_closed'] && ($this->perms('manage_project', $task['project_id']) || in_array($this->id, Flyspray::getAssignees($task['task_id']))); + } + + public function can_vote($task) + { + global $db, $fs; + + if (!$this->perms('add_votes', $task['project_id'])) { + return -1; + } + + // Check that the user hasn't already voted this task + $check = $db->query('SELECT vote_id + FROM {votes} + WHERE user_id = ? AND task_id = ?', + array($this->id, $task['task_id'])); + if ($db->countRows($check)) { + return -2; + } + + /* FS 1.0alpha daily vote limit + // Check that the user hasn't voted more than allowed today + $check = $db->query('SELECT vote_id + FROM {votes} + WHERE user_id = ? AND date_time > ?', + array($this->id, time() - 86400)); + if ($db->countRows($check) >= $fs->prefs['max_vote_per_day']) { + return -3; + } + */ + + /* FS 1.0beta2 max votes per user per project limit */ + $check = $db->query(' + SELECT COUNT(v.vote_id) + FROM {votes} v + JOIN {tasks} t ON t.task_id=v.task_id + WHERE user_id = ? + AND t.project_id = ? + AND t.is_closed <>1', + array($this->id, $task['project_id']) + ); + if ($db->countRows($check) >= $fs->prefs['votes_per_project']) { + return -4; + } + + + return 1; + } + + public function logout() + { + // Set cookie expiry time to the past, thus removing them + Flyspray::setcookie('flyspray_userid', '', time()-60); + Flyspray::setcookie('flyspray_passhash', '', time()-60); + Flyspray::setcookie('flyspray_project', '', time()-60); + if (Cookie::has(session_name())) { + Flyspray::setcookie(session_name(), '', time()-60); + } + + // Unset all of the session variables. + $_SESSION = array(); + session_destroy(); + + return !$this->isAnon(); + } + + /** + * Returns the activity by between dates for a project and user. + * @param date $startdate + * @param date $enddate + * @param integer $project_id + * @param integer $userid + * @return array used to get the count + * @access public + */ + static function getActivityUserCount($startdate, $enddate, $project_id, $userid) { + global $db; + $result = $db->query('SELECT count(event_date) as val + FROM {history} h left join {tasks} t on t.task_id = h.task_id + WHERE t.project_id = ? AND h.user_id = ? AND event_date BETWEEN ? AND ?', + array($project_id, $userid, $startdate, $enddate)); + $result = $db->fetchCol($result); + return $result[0]; + } + + /** + * Returns the day activity by the date for a project and user. + * @param date $date + * @param integer $project_id + * @param integer $userid + * @return array used to get the count + * @access public + */ + static function getDayActivityByUser($date_start, $date_end, $project_id, $userid) { + global $db; + //NOTE: from_unixtime() on mysql, to_timestamp() on PostreSQL + $func = ('mysql' == $db->dblink->dataProvider) ? 'from_unixtime' : 'to_timestamp'; + + $result = $db->query("SELECT count(date({$func}(event_date))) as val, MIN(event_date) as event_date + FROM {history} h left join {tasks} t on t.task_id = h.task_id + WHERE t.project_id = ? AND h.user_id = ? AND event_date BETWEEN ? AND ? + GROUP BY date({$func}(event_date)) ORDER BY event_date DESC", + array($project_id, $userid, $date_start, $date_end)); + + $date1 = new \DateTime("@$date_start"); + $date2 = new \DateTime("@$date_end"); + $days = $date1->diff($date2); + $days = $days->format('%a'); + $results = array(); + + for ($i = $days; $i > 0; $i--) { + $event_date = (string) strtotime("-{$i} day", $date_end); + $results[date('Y-m-d', $event_date)] = 0; + } + + while ($row = $result->fetchRow()) { + $event_date = date('Y-m-d', $row['event_date']); + $results[$event_date] = (integer) $row['val']; + } + + return array_values($results); + } + + /* }}} */ +} |