summaryrefslogtreecommitdiff
path: root/includes/modify.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/modify.inc.php')
-rw-r--r--includes/modify.inc.php3053
1 files changed, 3053 insertions, 0 deletions
diff --git a/includes/modify.inc.php b/includes/modify.inc.php
new file mode 100644
index 0000000..004a5a8
--- /dev/null
+++ b/includes/modify.inc.php
@@ -0,0 +1,3053 @@
+<?php
+
+/**
+ * Database Modifications
+ * @version $Id$
+ */
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+$notify = new Notifications;
+
+$lt = Post::isAlnum('list_type') ? Post::val('list_type') : '';
+$list_table_name = null;
+$list_column_name = null;
+$list_id = null;
+
+if (strlen($lt)) {
+ $list_table_name = '{list_'.$lt .'}';
+ $list_column_name = $lt . '_name';
+ $list_id = $lt . '_id';
+}
+
+function Post_to0($key) { return Post::val($key, 0); }
+
+function resizeImage($file, $max_x, $max_y, $forcePng = false)
+{
+ if ($max_x <= 0 || $max_y <= 0) {
+ $max_x = 5;
+ $max_y = 5;
+ }
+
+ $src = BASEDIR.'/avatars/'.$file;
+
+ list($width, $height, $type) = getImageSize($src);
+
+ $scale = min($max_x / $width, $max_y / $height);
+ $newWidth = $width * $scale;
+ $newHeight = $height * $scale;
+
+ $img = imagecreatefromstring(file_get_contents($src));
+ $black = imagecolorallocate($img, 0, 0, 0);
+ $resizedImage = imageCreateTrueColor($newWidth, $newHeight);
+ imagecolortransparent($resizedImage, $black);
+ imageCopyResampled($resizedImage, $img, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+ imageDestroy($img);
+ unlink($src);
+
+ if (!$forcePng) {
+ switch ($type) {
+ case IMAGETYPE_JPEG:
+ imageJpeg($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ case IMAGETYPE_GIF:
+ imageGif($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ case IMAGETYPE_PNG:
+ imagePng($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ default:
+ imagePng($resizedImage, BASEDIR.'/avatars/'.$file);
+ break;
+ }
+ }
+ else {
+ imagePng($resizedImage, BASEDIR.'/avatars/'.$file.'.png');
+ }
+
+ return;
+}
+
+if (Req::num('task_id')) {
+ $task = Flyspray::getTaskDetails(Req::num('task_id'));
+}
+
+if(isset($_SESSION)) {
+ unset($_SESSION['SUCCESS'], $_SESSION['ERROR'], $_SESSION['ERRORS']);
+}
+
+switch ($action = Req::val('action'))
+{
+ // ##################
+ // Adding a new task
+ // ##################
+ case 'newtask.newtask':
+
+ $newtaskerrors=array();
+
+ if (!Post::val('item_summary') || trim(Post::val('item_summary')) == '') { // description not required anymore
+ $newtaskerrors['summaryrequired']=1;
+ }
+
+ if ($user->isAnon() && !filter_var(Post::val('anon_email'), FILTER_VALIDATE_EMAIL)) {
+ $newtaskerrors['invalidemail']=1;
+ }
+
+ if (count($newtaskerrors)>0){
+ $_SESSION['ERRORS']=$newtaskerrors;
+ $_SESSION['ERROR']=L('invalidnewtask');
+ break;
+ }
+
+ list($task_id, $token) = Backend::create_task($_POST);
+ // Status and redirect
+ if ($task_id) {
+ $_SESSION['SUCCESS'] = L('newtaskadded');
+
+ if ($user->isAnon()) {
+ Flyspray::redirect(createURL('details', $task_id, null, array('task_token' => $token)));
+ } else {
+ Flyspray::redirect(createURL('details', $task_id));
+ }
+ } else {
+ Flyspray::show_error(L('databasemodfailed'));
+ break;
+ }
+ break;
+
+ // ##################
+ // Adding multiple new tasks
+ // ##################
+ case 'newmultitasks.newmultitasks':
+ if(!isset($_POST['item_summary'])) {
+ #Flyspray::show_error(L('summaryanddetails'));
+ Flyspray::show_error(L('summaryrequired'));
+ break;
+ }
+ $flag = true;
+ foreach($_POST['item_summary'] as $summary) {
+ if(!$summary || trim($summary) == "") {
+ $flag = false;
+ break;
+ }
+ }
+ $i = 0;
+ foreach($_POST['detailed_desc'] as $detail) {
+ if($detail){
+ # only for ckeditor/html, not for dokuwiki (or other syntax plugins in future)
+ if ($conf['general']['syntax_plugin'] != 'dokuwiki') {
+ $_POST['detailed_desc'][$i] = "<p>" . $detail . "</p>";
+ }
+ }
+ $i++;
+ }
+ if(!$flag) {
+ #Flyspray::show_error(L('summaryanddetails'));
+ Flyspray::show_error(L('summaryrequired'));
+ break;
+ }
+
+ $flag = true;
+ $length = count($_POST['detailed_desc']);
+ for($i = 0; $i < $length; $i++) {
+ $ticket = array();
+ foreach($_POST as $key => $value) {
+ if($key == "assigned_to") {
+ $sql = $db->query("SELECT user_id FROM {users} WHERE user_name = ? or real_name = ?", array($value[$i], $value[$i]));
+ $ticket["rassigned_to"] = array(intval($db->fetchOne($sql)));
+ continue;
+ }
+ if(is_array($value))
+ $ticket[$key] = $value[$i];
+ else
+ $ticket[$key] = $value;
+ }
+ list($task_id, $token) = Backend::create_task($ticket);
+ if (!$task_id) {
+ $flag = false;
+ break;
+ }
+ }
+
+ if(!$flag) {
+ Flyspray::show_error(L('databasemodfailed'));
+ break;
+ }
+
+ $_SESSION['SUCCESS'] = L('newtaskadded');
+ Flyspray::redirect(createURL('index', $proj->id));
+ break;
+
+ // ##################
+ // Modifying an existing task
+ // ##################
+ case 'details.update':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission')); # TODO create a better error message
+ break;
+ }
+
+ $errors=array();
+
+ # TODO add checks who should be able to move a task, modify_all_tasks perm should not be enough and the target project perms are required too.
+ # - User has project manager permission in source project AND in target project: Allowed to move task
+ # - User has project manager permission in source project, but NOT in target project: Can send request to PUSH task to target project. A user with project manager permission of target project can accept the PUSH request.
+ # - User has NO project manager permission in source project, but in target project: Can send request to PULL task to target project. A user with project manager permission of source project can accept the PULL request.
+ # - User has calculated can_edit_task permission in source project AND (at least) newtask perm in target project: Can send a request to move task (similiar to 'close task please'-request) with the target project id, sure.
+
+ $move=0;
+ if($task['project_id'] != Post::val('project_id')) {
+ $toproject=new Project(Post::val('project_id'));
+ if($user->perms('modify_all_tasks', $toproject->id)){
+ $move=1;
+ } else{
+ $errors['invalidtargetproject']=1;
+ }
+ }
+
+ if($move==1){
+ # Check that a task is not moved to a different project than its
+ # possible parent or subtasks. Note that even closed tasks are
+ # included in the result, a task can be always reopened later.
+ $result = $db->query('
+ SELECT parent.task_id, parent.project_id FROM {tasks} p
+ JOIN {tasks} parent ON parent.task_id = p.supertask_id
+ WHERE p.task_id = ?
+ AND parent.project_id <> ?',
+ array( $task['task_id'], Post::val('project_id') )
+ );
+ $parentcheck = $db->fetchRow($result);
+ if ($parentcheck && $parentcheck['task_id']) {
+ if ($parentcheck['project_id'] != Post::val('project_id')) {
+ $errors['denymovehasparent']=L('denymovehasparent');
+ }
+ }
+
+ $result = $db->query('
+ SELECT sub.task_id, sub.project_id FROM {tasks} p
+ JOIN {tasks} sub ON p.task_id = sub.supertask_id
+ WHERE p.task_id = ?
+ AND sub.project_id <> ?',
+ array( $task['task_id'], Post::val('project_id') )
+ );
+ $subcheck = $db->fetchRow($result);
+
+ # if there are any subtasks, check that the project is not changed
+ if ($subcheck && $subcheck['task_id']) {
+ $errors['denymovehassub']=L('denymovehassub');
+ }
+ }
+
+ # summary form input fields, so user get notified what needs to be done right to be accepted
+ if (!Post::val('item_summary')) {
+ # description can be empty now
+ #Flyspray::show_error(L('summaryanddetails'));
+ #Flyspray::show_error(L('summaryrequired'));
+ $errors['summaryrequired']=L('summaryrequired');
+ }
+
+ # ids of severity and priority are (probably!) intentional fixed in Flyspray.
+ if( isset($_POST['task_severity']) && (!is_numeric(Post::val('task_severity')) || Post::val('task_severity')>5 || Post::val('task_severity')<0 ) ){
+ $errors['invalidseverity']=1;
+ }
+
+ # peterdd:temp fix to allow priority 6 again
+ # But I think about 1-5 valid (and 0 for unset) only in future to harmonize
+ # with other trackers/taskplaner software and for severity-priority graphs like
+ # https://en.wikipedia.org/wiki/Time_management#The_Eisenhower_Method
+ if( isset($_POST['task_priority']) && (!is_numeric(Post::val('task_priority')) || Post::val('task_priority')>6 || Post::val('task_priority')<0 ) ){
+ $errors['invalidpriority']=1;
+ }
+
+ if( isset($_POST['percent_complete']) && (!is_numeric(Post::val('percent_complete')) || Post::val('percent_complete')>100 || Post::val('percent_complete')<0 ) ){
+ $errors['invalidprogress']=1;
+ }
+
+ # Description for the following list values here when moving a task to a different project:
+ # - Do we use the old invalid values? (current behavior until 1.0-beta2, invalid id-values in database can be set, can result in php-'notices' or values arent shown on pages)
+ # - Or set to default value of the new project? And inform the user to adjust the task properties in the new project?
+ # - Or create a new tasktype for the new project, but:
+ # - Has the user the permission to create a new tasktype for the new project?
+ # - similiar named tasktypes exists?
+ #
+ # Maybe let's go with 2 steps when in this situation:
+ # When user want move task to other project, a second page shows the form again but:
+ # dropdown list forms show - maybe divided as optiongroups - :
+ # -global list values ()
+ # -current project list values
+ # -target project list values
+ # -option to create a new option based on current project value (if the user has the permission for the target project!)
+ # -option to set to default value in target project or unset value
+ # Also consider that not all list dropdown field may be shown to the user because of project settings (visible_fields)!
+
+
+ # which $proj should we use here? $proj object is set in header.php by a request param before modify.inc.php is loaded, so it can differ from $task['project_id']!
+ if($move==1){
+ $statusarray=$toproject->listTaskStatuses();
+ } else{
+ $statusarray=$proj->listTaskStatuses();
+ }
+
+ # FIXME what if we move to different project, but the status_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and item_status selection is deactivated/not shown in edit task page?
+ if( isset($_POST['item_status']) && (!is_numeric(Post::val('item_status')) || false===Flyspray::array_find('status_id', Post::val('item_status'), $statusarray) ) ){
+ $errors['invalidstatus']=1;
+ }
+
+ if($move==1){
+ $typearray=$toproject->listTaskTypes();
+ } else{
+ $typearray=$proj->listTaskTypes();
+ }
+
+ # FIXME what if we move to different project, but tasktype_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and task_type selection is deactiveated/not shown in edit task page?
+ if( isset($_POST['task_type']) && (!is_numeric(Post::val('task_type')) || false===Flyspray::array_find('tasktype_id', Post::val('task_type'), $typearray) ) ){
+ $errors['invalidtasktype']=1;
+ }
+
+ # FIXME what if we move to different project and reportedver selection is deactivated/not shown in edit task page?
+ # FIXME what if we move to different project and reportedver is deactivated/not shown in edit task page?
+ # FIXME what if we move to different project and closedby_version selection is deactivated/not shown in edit task page?
+ # FIXME what if we move to different project and closedby_version is deactivated/not shown in edit task page?
+ if($move==1){
+ $versionarray=$toproject->listVersions();
+ } else{
+ $versionarray=$proj->listVersions();
+ }
+ if( isset($_POST['reportedver']) && (!is_numeric(Post::val('reportedver')) || ( $_POST['reportedver']!=='0' && false===Flyspray::array_find('version_id', Post::val('reportedver'), $versionarray)) ) ){
+ $errors['invalidreportedversion']=1;
+ }
+ if( isset($_POST['closedby_version']) && (!is_numeric(Post::val('closedby_version')) || ( $_POST['closedby_version']!=='0' && false===Flyspray::array_find('version_id', Post::val('closedby_version'), $versionarray)) ) ){
+ $errors['invaliddueversion']=1;
+ }
+
+ # FIXME what if we move to different project, but category_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and category selection is deactivated/not shown in edit task page?
+ if($move==1){
+ $catarray=$toproject->listCategories();
+ } else{
+ $catarray=$proj->listCategories();
+ }
+ if( isset($_POST['product_category']) && (!is_numeric(Post::val('product_category')) || false===Flyspray::array_find('category_id', Post::val('product_category'), $catarray) ) ){
+ $errors['invalidcategory']=1;
+ }
+
+ # FIXME what if we move to different project, but os_id is defined for the old project only (not global)?
+ # FIXME what if we move to different project and operating_system selection is deactivated/not shown in edit task page?
+ if($move==1){
+ $osarray=$toproject->listOs();
+ } else{
+ $osarray=$proj->listOs();
+ }
+ if( isset($_POST['operating_system']) && (!is_numeric(Post::val('operating_system')) || ( $_POST['operating_system']!=='0' && false===Flyspray::array_find('os_id', Post::val('operating_system'), $osarray)) ) ){
+ $errors['invalidos']=1;
+ }
+
+ if ($due_date = Post::val('due_date', 0)) {
+ $due_date = Flyspray::strtotime(Post::val('due_date'));
+ }
+
+ $estimated_effort = 0;
+ if (($estimated_effort = effort::editStringToSeconds(Post::val('estimated_effort'), $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format'])) === FALSE) {
+ $errors['invalideffort']=1;
+ }
+
+ $time = time();
+
+ $result = $db->query('SELECT * from {tasks} WHERE task_id = ?', array($task['task_id']));
+ $defaults = $db->fetchRow($result);
+
+ if (!Post::has('due_date')) {
+ $due_date = $defaults['due_date'];
+ }
+
+ if (!Post::has('estimated_effort')) {
+ $estimated_effort = $defaults['estimated_effort'];
+ }
+
+
+ if(count($errors)>0){
+ # some invalid input by the user. Do not save the input and in the details-edit-template show the user where in the form the invalid values are.
+ $_SESSION['ERRORS']=$errors; # $_SESSION['ERROR'] is very limited, holds only one string and often just overwritten
+ $_SESSION['ERROR']=L('invalidinput');
+ # pro and contra http 303 redirect here:
+ # - good: browser back button works, browser history.
+ # - bad: form inputs of user not preserved (at the moment). Annoying if user wrote a long description and then the form submit gets denied because of other reasons.
+ #Flyspray::redirect(createURL('edittask', $task['task_id']));
+ break;
+ }
+
+ # FIXME/TODO: If a user has only 'edit own task edit' permission and task remains in the same project,
+ # but there are not all fields visible/editable so the browser do not send that values with the form,
+ # the sql update query should not touch that fields. And it should not overwrite the field with the default value in this case.
+ # So this update query should be build dynamic (And for the future: when 'custom fields' are implemented ..)
+ # Alternative: Read task field values before update query.
+ # And we should check too what task fields the 'edit own task only'-user is allowed to change.
+ # (E.g ignore form fields the user is not allowed to change. Currently hardcoded in template..)
+
+/*
+ # Dynamic creation of the UPDATE query required
+ # First step: Settings which task fields can be changed by 'permission level': Based on situation found in FS 1.0-rc1 'status quo' in backend::create_task() and CleanFS/templates/template details.edit.tpl
+ #$basicfields[]=array('item_summary','detailed_desc', 'task_type', 'product_category', 'operating_system', 'task_severity', 'percent_complete', 'product_version', 'estimated_effort'); # modify_own_tasks, anon_open
+ $basicfields=$proj->prefs['basic_fields'];
+
+ # peterdd: just saved a bit work in progress for future dynamic sql update string
+ $sqlup='';
+ foreach($basicfields as $bf){
+ $sqlup.=' '.$bf.' = ?,';
+ $sqlparam[]= Post::val($bf, $oldvals[$bf]);
+ }
+ $sqlup.=' last_edited_by = ?,';
+ $sqlparam[]= $user->id;
+ $sqlup.=' last_edited_time = ?,';
+ $sqlparam[]= $time;
+
+ $devfields[]=array('task_priority', 'due_date', 'item_status', 'closedby_version'); # modify_all_tasks
+ $managerfields[]=array('project_id','mark_private'); # manage_project
+ #$customfields[]=array(); # Flyspray 1.? future: perms depend of each custom field setting in a project..
+
+ $sqlparam[]=$task['task_id'];
+ $sqlupdate='UPDATE {tasks} SET '.$sqlup.' WHERE task_id = ?';
+
+ echo '<pre>';print_r($sqlupdate);print_r($sqlparam);die();
+ $db->query($sqlupdate, $sqlparam);
+*/
+
+ $detailed_desc = Post::val('detailed_desc', $defaults['detailed_desc']);
+
+ # dokuwiki syntax plugin filters on output
+ if($conf['general']['syntax_plugin'] != 'dokuwiki'){
+ $purifierconfig = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($purifierconfig);
+ $detailed_desc = $purifier->purify($detailed_desc);
+ }
+
+ $db->query('UPDATE {tasks}
+ SET
+ project_id = ?,
+ task_type = ?,
+ item_summary = ?,
+ detailed_desc = ?,
+ item_status = ?,
+ mark_private = ?,
+ product_category = ?,
+ closedby_version = ?,
+ operating_system = ?,
+ task_severity = ?,
+ task_priority = ?,
+ last_edited_by = ?,
+ last_edited_time = ?,
+ due_date = ?,
+ percent_complete = ?,
+ product_version = ?,
+ estimated_effort = ?
+ WHERE task_id = ?',
+ array(
+ Post::val('project_id', $defaults['project_id']),
+ Post::val('task_type', $defaults['task_type']),
+ Post::val('item_summary', $defaults['item_summary']),
+ $detailed_desc,
+ Post::val('item_status', $defaults['item_status']),
+ intval($user->can_change_private($task) && Post::val('mark_private', $defaults['mark_private'])),
+ Post::val('product_category', $defaults['product_category']),
+ Post::val('closedby_version', $defaults['closedby_version']),
+ Post::val('operating_system', $defaults['operating_system']),
+ Post::val('task_severity', $defaults['task_severity']),
+ Post::val('task_priority', $defaults['task_priority']),
+ intval($user->id), $time, intval($due_date),
+ Post::val('percent_complete', $defaults['percent_complete']),
+ Post::val('reportedver', $defaults['product_version']),
+ intval($estimated_effort),
+ $task['task_id']
+ )
+ );
+
+ // Update the list of users assigned this task
+ $assignees = (array) Post::val('rassigned_to');
+ $assignees_changed = count(array_diff($task['assigned_to'], $assignees)) + count(array_diff($assignees, $task['assigned_to']));
+ if ($user->perms('edit_assignments') && $assignees_changed) {
+
+ // Delete the current assignees for this task
+ $db->query('DELETE FROM {assigned}
+ WHERE task_id = ?',
+ array($task['task_id']));
+
+ // Convert assigned_to and store them in the 'assigned' table
+ foreach ((array) Post::val('rassigned_to') as $key => $val)
+ {
+ $db->replace('{assigned}', array('user_id'=> $val, 'task_id'=> $task['task_id']), array('user_id','task_id'));
+ }
+ }
+
+ # FIXME what if we move to different project, but tag(s) is/are defined for the old project only (not global)?
+ # FIXME what if we move to different project and tag input field is deactivated/not shown in edit task page?
+ # - Create new tag(s) in target project if user has permission to create new tags but what with the users who have not the permission?
+ # update tags
+ $tagList = explode(';', Post::val('tags'));
+ $tagList = array_map('strip_tags', $tagList);
+ $tagList = array_map('trim', $tagList);
+ $tagList = array_unique($tagList); # avoid duplicates for inputs like: "tag1;tag1" or "tag1; tag1<p></p>"
+ $storedtags=array();
+ foreach($task['tags'] as $temptag){
+ $storedtags[]=$temptag['tag'];
+ }
+ $tags_changed = count(array_diff($storedtags, $tagList)) + count(array_diff($tagList, $storedtags));
+
+ if($tags_changed){
+ // Delete the current assigned tags for this task
+ $db->query('DELETE FROM {task_tag} WHERE task_id = ?', array($task['task_id']));
+ foreach ($tagList as $tag){
+ if ($tag == ''){
+ continue;
+ }
+ # size of {list_tag}.tag_name, see flyspray-install.xml
+ if(mb_strlen($tag) > 40){
+ # report that softerror
+ $errors['tagtoolong']=1;
+ continue;
+ }
+
+ $res=$db->query("SELECT tag_id FROM {list_tag} WHERE (project_id=0 OR project_id=?) AND tag_name LIKE ? ORDER BY project_id", array($proj->id,$tag) );
+ if($t=$db->fetchRow($res)){
+ $tag_id=$t['tag_id'];
+ } else{
+ if( $proj->prefs['freetagging']==1){
+ # add to taglist of the project
+ $db->query("INSERT INTO {list_tag} (project_id,tag_name) VALUES (?,?)", array($proj->id,$tag));
+ $tag_id=$db->insert_ID();
+ } else{
+ continue;
+ }
+ };
+ $db->query("INSERT INTO {task_tag}(task_id,tag_id) VALUES(?,?)", array($task['task_id'], $tag_id) );
+ }
+ }
+
+ // Get the details of the task we just updated
+ // To generate the changed-task message
+ $new_details_full = Flyspray::getTaskDetails($task['task_id']);
+ // Not very nice...maybe combine compare_tasks() and logEvent() ?
+ $result = $db->query("SELECT * FROM {tasks} WHERE task_id = ?",
+ array($task['task_id']));
+ $new_details = $db->fetchRow($result);
+
+ foreach ($new_details as $key => $val) {
+ if (strstr($key, 'last_edited_') || $key == 'assigned_to' || is_numeric($key)) {
+ continue;
+ }
+
+ if ($val != $task[$key]) {
+ // Log the changed fields in the task history
+ Flyspray::logEvent($task['task_id'], 3, $val, $task[$key], $key, $time);
+ }
+ }
+
+ $changes = Flyspray::compare_tasks($task, $new_details_full);
+ if (count($changes) > 0) {
+ $notify->create(NOTIFY_TASK_CHANGED, $task['task_id'], $changes, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+ if ($assignees_changed) {
+ // Log to task history
+ Flyspray::logEvent($task['task_id'], 14, implode(' ', $assignees), implode(' ', $task['assigned_to']), '', $time);
+
+ // Notify the new assignees what happened. This obviously won't happen if the task is now assigned to no-one.
+ if (count($assignees)) {
+ $new_assignees = array_diff($task['assigned_to'], $assignees);
+ // Remove current user from notification list
+ if (!$user->infos['notify_own']) {
+ $new_assignees = array_filter($new_assignees, function($u) use($user) { return $user->id != $u; } );
+ }
+ if(count($new_assignees)) {
+ $notify->create(NOTIFY_NEW_ASSIGNEE, $task['task_id'], null, $notify->specificAddresses($new_assignees), NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+ }
+ }
+
+ Backend::add_comment($task, Post::val('comment_text'), $time);
+ Backend::delete_files(Post::val('delete_att'));
+ Backend::upload_files($task['task_id'], '0', 'usertaskfile');
+ Backend::delete_links(Post::val('delete_link'));
+ Backend::upload_links($task['task_id'], '0', 'userlink');
+
+ $_SESSION['SUCCESS'] = L('taskupdated');
+ # report minor/soft errors too that does not hindered saving task
+ if(count($errors)>0){
+ $_SESSION['ERRORS']=$errors;
+ }
+ Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ // ##################
+ // closing a task
+ // ##################
+ case 'details.close':
+ if (!$user->can_close_task($task)) {
+ break;
+ }
+
+ if ($task['is_closed']) {
+ break;
+ }
+
+ if (!Post::val('resolution_reason')) {
+ Flyspray::show_error(L('noclosereason'));
+ break;
+ }
+
+ Backend::close_task($task['task_id'], Post::val('resolution_reason'), Post::val('closure_comment', ''), Post::val('mark100', false));
+
+ $_SESSION['SUCCESS'] = L('taskclosedmsg');
+ # FIXME there are several pages using this form, details and pendingreq at least
+ #Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ case 'details.associatesubtask':
+ if ( $task['task_id'] == Post::num('associate_subtask_id')) {
+ Flyspray::show_error(L('selfsupertasknotallowed'));
+ break;
+ }
+ $sql = $db->query('SELECT supertask_id, project_id FROM {tasks} WHERE task_id = ?',
+ array(Post::num('associate_subtask_id')));
+
+ $suptask = $db->fetchRow($sql);
+
+ // check to see if the subtask exists.
+ if (!$suptask) {
+ Flyspray::show_error(L('subtasknotexist'));
+ break;
+ }
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::num('associate_subtask_id'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('subtasknotexist'));
+ break;
+ }
+ }
+
+ // check to see if associated subtask is already the parent of this task
+ if ($suptask['supertask_id'] == Post::num('associate_subtask_id')) {
+ Flyspray::show_error(L('subtaskisparent'));
+ break;
+ }
+
+ // check to see if associated subtask already has a parent task
+ if ($suptask['supertask_id']) {
+ Flyspray::show_error(L('subtaskalreadyhasparent'));
+ break;
+ }
+
+ // check to see that both tasks belong to the same project
+ if ($task['project_id'] != $suptask['project_id']) {
+ Flyspray::show_error(L('musthavesameproject'));
+ break;
+ }
+
+ //associate the subtask
+ $db->query('UPDATE {tasks} SET supertask_id=? WHERE task_id=?',array( $task['task_id'], Post::num('associate_subtask_id')));
+ Flyspray::logEvent($task['task_id'], 32, Post::num('associate_subtask_id'));
+ Flyspray::logEvent(Post::num('associate_subtask_id'), 34, $task['task_id']);
+
+ $_SESSION['SUCCESS'] = sprintf( L('associatedsubtask'), Post::num('associate_subtask_id') );
+ break;
+
+
+ case 'reopen':
+ // ##################
+ // re-opening an task
+ // ##################
+ if (!$user->can_close_task($task)) {
+ break;
+ }
+
+ // Get last %
+ $old_percent = $db->query("SELECT old_value, new_value
+ FROM {history}
+ WHERE field_changed = 'percent_complete'
+ AND task_id = ? AND old_value != '100'
+ ORDER BY event_date DESC
+ LIMIT 1",
+ array($task['task_id']));
+ $old_percent = $db->fetchRow($old_percent);
+
+ $db->query("UPDATE {tasks}
+ SET resolution_reason = 0, closure_comment = '', date_closed = 0,
+ last_edited_time = ?, last_edited_by = ?, is_closed = 0, percent_complete = ?
+ WHERE task_id = ?",
+ array(time(), $user->id, intval($old_percent['old_value']), $task['task_id']));
+
+ Flyspray::logEvent($task['task_id'], 3, $old_percent['old_value'], $old_percent['new_value'], 'percent_complete');
+
+ $notify->create(NOTIFY_TASK_REOPENED, $task['task_id'], null, null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ // add comment of PM request to comment page if accepted
+ $sql = $db->query('SELECT * FROM {admin_requests} WHERE task_id = ? AND request_type = ? AND resolved_by = 0',
+ array($task['task_id'], 2));
+ $request = $db->fetchRow($sql);
+ if ($request) {
+ $db->query('INSERT INTO {comments}
+ (task_id, date_added, last_edited_time, user_id, comment_text)
+ VALUES ( ?, ?, ?, ?, ? )',
+ array($task['task_id'], time(), time(), $request['submitted_by'], $request['reason_given']));
+ // delete existing PM request
+ $db->query('UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?
+ WHERE request_id = ?',
+ array($user->id, time(), $request['request_id']));
+ }
+
+ Flyspray::logEvent($task['task_id'], 13);
+
+ $_SESSION['SUCCESS'] = L('taskreopenedmsg');
+ # FIXME there are several pages using this form, details and pendingreq at least
+ #Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ // ##################
+ // adding a comment
+ // ##################
+ case 'details.addcomment':
+ if (!Backend::add_comment($task, Post::val('comment_text'))) {
+ Flyspray::show_error(L('nocommententered'));
+ break;
+ }
+
+ if (Post::val('notifyme') == '1') {
+ // If the user wanted to watch this task for changes
+ Backend::add_notification($user->id, $task['task_id']);
+ }
+
+ $_SESSION['SUCCESS'] = L('commentaddedmsg');
+ Flyspray::redirect(createURL('details', $task['task_id']));
+ break;
+
+ // ##################
+ // Tracking
+ // ##################
+ case 'details.efforttracking':
+
+ require_once BASEDIR . '/includes/class.effort.php';
+ $effort = new effort($task['task_id'],$user->id);
+
+
+ if(Post::val('start_tracking')){
+ if($effort->startTracking())
+ {
+ $_SESSION['SUCCESS'] = L('efforttrackingstarted');
+ }
+ else
+ {
+ $_SESSION['ERROR'] = L('efforttrackingnotstarted');
+ }
+ }
+
+ if(Post::val('stop_tracking')){
+ $effort->stopTracking();
+ $_SESSION['SUCCESS'] = L('efforttrackingstopped');
+ }
+
+ if(Post::val('cancel_tracking')){
+ $effort->cancelTracking();
+ $_SESSION['SUCCESS'] = L('efforttrackingcancelled');
+ }
+
+ if(Post::val('manual_effort')){
+ if($effort->addEffort(Post::val('effort_to_add'), $proj)){
+ $_SESSION['SUCCESS'] = L('efforttrackingadded');
+ }
+ }
+
+ Flyspray::redirect(createURL('details', $task['task_id']).'#effort');
+ break;
+
+ // ##################
+ // sending a new user a confirmation code
+ // ##################
+ case 'register.sendcode':
+ if (!$user->can_register()) {
+ break;
+ }
+
+ $captchaerrors=array();
+ if($fs->prefs['captcha_securimage']){
+ $image = new Securimage();
+ if( !Post::isAlnum('captcha_code') || !$image->check(Post::val('captcha_code'))) {
+ $captchaerrors['invalidsecurimage']=1;
+ }
+ }
+
+ if($fs->prefs['captcha_recaptcha']){
+ require_once('class.recaptcha.php');
+ if( !recaptcha::verify()) {
+ $captchaerrors['invalidrecaptcha']=1;
+ }
+ }
+
+ if(count($captchaerrors)){
+ $_SESSION['ERRORS']=$captchaerrors;
+ Flyspray::show_error(L('captchaerror'));
+ break;
+ }
+
+ if (!Post::val('user_name') || !Post::val('real_name')
+ || !Post::val('email_address'))
+ {
+ // If the form wasn't filled out correctly, show an error
+ Flyspray::show_error(L('registererror'));
+ break;
+ }
+
+ if ($fs->prefs['repeat_emailaddress'] && Post::val('email_address') != Post::val('verify_email_address'))
+ {
+ Flyspray::show_error(L('emailverificationwrong'));
+ break;
+ }
+
+ $email = strtolower(Post::val('email_address'));
+ $jabber_id = strtolower(Post::val('jabber_id'));
+
+ //email is mandatory
+ if (!$email || !Flyspray::check_email($email)) {
+ Flyspray::show_error(L('novalidemail'));
+ break;
+ }
+ //jabber_id is optional
+ if ($jabber_id && !Jabber::check_jid($jabber_id)) {
+ Flyspray::show_error(L('novalidjabber'));
+ break;
+ }
+
+ $user_name = Backend::clean_username(Post::val('user_name'));
+
+ // Limit length
+ $real_name = substr(trim(Post::val('real_name')), 0, 100);
+ // Remove doubled up spaces and control chars
+ $real_name = preg_replace('![\x00-\x1f\s]+!u', ' ', $real_name);
+
+ if (!$user_name || empty($user_name) || !$real_name) {
+ Flyspray::show_error(L('entervalidusername'));
+ break;
+ }
+
+ // Delete registration codes older than 24 hours
+ $yesterday = time() - 86400;
+ $db->query('DELETE FROM {registrations} WHERE reg_time < ?', array($yesterday));
+
+ $sql = $db->query('SELECT COUNT(*) FROM {users} u, {registrations} r
+ WHERE u.user_name = ? OR r.user_name = ?',
+ array($user_name, $user_name));
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('usernametaken'));
+ break;
+ }
+
+ $sql = $db->query("SELECT COUNT(*) FROM {users} WHERE
+ jabber_id = ? AND jabber_id != ''
+ OR email_address = ? AND email_address != ''",
+ array($jabber_id, $email));
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('emailtaken'));
+ break;
+ }
+
+ // Generate a random bunch of numbers for the confirmation code and the confirmation url
+
+ foreach(array('randval','magic_url') as $genrandom) {
+
+ $$genrandom = md5(function_exists('openssl_random_pseudo_bytes') ?
+ openssl_random_pseudo_bytes(32) :
+ uniqid(mt_rand(), true));
+ }
+
+ $confirm_code = substr($randval, 0, 20);
+
+ // echo "<pre>Am I here?</pre>";
+ // send the email first
+ $userconfirmation = array();
+ $userconfirmation[$email] = array('recipient' => $email, 'lang' => $fs->prefs['lang_code']);
+ $recipients = array($userconfirmation);
+ if($notify->create(NOTIFY_CONFIRMATION, null, array($baseurl, $magic_url, $user_name, $confirm_code),
+ $recipients,
+ NOTIFY_EMAIL)) {
+
+ //email sent succefully, now update the database.
+ $reg_values = array(time(), $confirm_code, $user_name, $real_name,
+ $email, $jabber_id,
+ Post::num('notify_type'), $magic_url, Post::num('time_zone'));
+ // Insert everything into the database
+ $query = $db->query("INSERT INTO {registrations}
+ ( reg_time, confirm_code, user_name, real_name,
+ email_address, jabber_id, notify_type,
+ magic_url, time_zone )
+ VALUES ( " . $db->fill_placeholders($reg_values) . ' )', $reg_values);
+
+ if ($query) {
+ $_SESSION['SUCCESS'] = L('codesent');
+ Flyspray::redirect($baseurl);
+ }
+
+ } else {
+ Flyspray::show_error(L('codenotsent'));
+ break;
+ }
+
+ break;
+
+ // ##################
+ // new user self-registration with a confirmation code
+ // ##################
+ case 'register.registeruser':
+ if (!$user->can_register()) {
+ break;
+ }
+
+ if (!Post::val('user_pass') || !Post::val('confirmation_code')) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ if (strlen(Post::val('user_pass')) < MIN_PW_LENGTH) {
+ Flyspray::show_error(L('passwordtoosmall'));
+ break;
+ }
+
+ if ( $fs->prefs['repeat_password'] && Post::val('user_pass') != Post::val('user_pass2')) {
+ Flyspray::show_error(L('nomatchpass'));
+ break;
+ }
+
+ // Check that the user entered the right confirmation code
+ $sql = $db->query("SELECT * FROM {registrations} WHERE magic_url = ?",
+ array(Post::val('magic_url')));
+ $reg_details = $db->fetchRow($sql);
+
+ if ($reg_details['confirm_code'] != trim(Post::val('confirmation_code'))) {
+ Flyspray::show_error(L('confirmwrong'));
+ break;
+ }
+
+ $profile_image = 'profile_image';
+ $image_path = '';
+
+ if(isset($_FILES[$profile_image])) {
+ if(!empty($_FILES[$profile_image]['name'])) {
+ $allowed = array('jpg', 'jpeg', 'gif', 'png');
+
+ $image_name = $_FILES[$profile_image]['name'];
+ $explode = explode('.', $image_name);
+ $image_extn = strtolower(end($explode));
+ $image_temp = $_FILES[$profile_image]['tmp_name'];
+
+ if(in_array($image_extn, $allowed)) {
+ $avatar_name = substr(md5(time()), 0, 10).'.'.$image_extn;
+ $image_path = BASEDIR.'/avatars/'.$avatar_name;
+ move_uploaded_file($image_temp, $image_path);
+ resizeImage($avatar_name, $fs->prefs['max_avatar_size'], $fs->prefs['max_avatar_size']);
+ } else {
+ Flyspray::show_error(L('incorrectfiletype'));
+ break;
+ }
+ }
+ }
+
+ $enabled = 1;
+ if (!Backend::create_user($reg_details['user_name'],
+ Post::val('user_pass'),
+ $reg_details['real_name'],
+ $reg_details['jabber_id'],
+ $reg_details['email_address'],
+ $reg_details['notify_type'], $reg_details['time_zone'], $fs->prefs['anon_group'], $enabled ,'', '', $image_path)) {
+ Flyspray::show_error(L('usernametaken'));
+ break;
+ }
+
+ $db->query('DELETE FROM {registrations} WHERE magic_url = ? AND confirm_code = ?',
+ array(Post::val('magic_url'), Post::val('confirmation_code')));
+
+
+ $_SESSION['SUCCESS'] = L('accountcreated');
+ // If everything is ok, add here a notify to both administrators and the user.
+ // Otherwise, explain what wen wrong.
+
+ define('NO_DO', true);
+ break;
+
+ // ##################
+ // new user self-registration without a confirmation code
+ // ##################
+ case 'register.newuser':
+ case 'admin.newuser':
+ if (!($user->perms('is_admin') || $user->can_self_register())) {
+ break;
+ }
+
+ $captchaerrors=array();
+ if( !($user->perms('is_admin')) && $fs->prefs['captcha_securimage']) {
+ $image = new Securimage();
+ if( !Post::isAlnum('captcha_code') || !$image->check(Post::val('captcha_code'))) {
+ $captchaerrors['invalidsecurimage']=1;
+ }
+ }
+
+ if( !($user->perms('is_admin')) && $fs->prefs['captcha_recaptcha']){
+ require_once('class.recaptcha.php');
+ if( !recaptcha::verify()) {
+ $captchaerrors['invalidrecaptcha']=1;
+ }
+ }
+
+ # if both captchatypes are configured, maybe show the user which one or both failed.
+ if(count($captchaerrors)){
+ $_SESSION['ERRORS']=$captchaerrors;
+ Flyspray::show_error(L('captchaerror'));
+ break;
+ }
+
+ if (!Post::val('user_name') || !Post::val('real_name') || !Post::val('email_address'))
+ {
+ // If the form wasn't filled out correctly, show an error
+ Flyspray::show_error(L('registererror'));
+ break;
+ }
+
+ // Check email format
+ if (!Post::val('email_address') || !Flyspray::check_email(Post::val('email_address')))
+ {
+ Flyspray::show_error(L('novalidemail'));
+ break;
+ }
+
+ if ( $fs->prefs['repeat_emailaddress'] && Post::val('email_address') != Post::val('verify_email_address'))
+ {
+ Flyspray::show_error(L('emailverificationwrong'));
+ break;
+ }
+
+ if (strlen(Post::val('user_pass')) && (strlen(Post::val('user_pass')) < MIN_PW_LENGTH)) {
+ Flyspray::show_error(L('passwordtoosmall'));
+ break;
+ }
+
+ if ( $fs->prefs['repeat_password'] && Post::val('user_pass') != Post::val('user_pass2')) {
+ Flyspray::show_error(L('nomatchpass'));
+ break;
+ }
+
+ if ($user->perms('is_admin')) {
+ $group_in = Post::val('group_in');
+ } else {
+ $group_in = $fs->prefs['anon_group'];
+ }
+
+ if(!$user->perms('is_admin')) {
+
+ $sql = $db->query("SELECT COUNT(*) FROM {users} WHERE
+ jabber_id = ? AND jabber_id != ''
+ OR email_address = ? AND email_address != ''",
+ array(Post::val('jabber_id'), Post::val('email_address')));
+
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('emailtaken'));
+ break;
+ }
+ }
+
+ $enabled = 1;
+ if($user->need_admin_approval()) $enabled = 0;
+
+ $profile_image = 'profile_image';
+ $image_path = '';
+
+ if(isset($_FILES[$profile_image])) {
+ if(!empty($_FILES[$profile_image]['name'])) {
+ $allowed = array('jpg', 'jpeg', 'gif', 'png');
+
+ $image_name = $_FILES[$profile_image]['name'];
+ $explode = explode('.', $image_name);
+ $image_extn = strtolower(end($explode));
+ $image_temp = $_FILES[$profile_image]['tmp_name'];
+
+ if(in_array($image_extn, $allowed)) {
+ $avatar_name = substr(md5(time()), 0, 10).'.'.$image_extn;
+ $image_path = BASEDIR.'/avatars/'.$avatar_name;
+ move_uploaded_file($image_temp, $image_path);
+ resizeImage($avatar_name, $fs->prefs['max_avatar_size'], $fs->prefs['max_avatar_size']);
+ } else {
+ Flyspray::show_error(L('incorrectfiletype'));
+ break;
+ }
+ }
+ }
+
+ if (!Backend::create_user(Post::val('user_name'), Post::val('user_pass'),
+ Post::val('real_name'), Post::val('jabber_id'),
+ Post::val('email_address'), Post::num('notify_type'),
+ Post::num('time_zone'), $group_in, $enabled, '', '', $image_path)) {
+ Flyspray::show_error(L('usernametaken'));
+ break;
+ }
+
+ $_SESSION['SUCCESS'] = L('newusercreated');
+
+ if (!$user->perms('is_admin')) {
+ define('NO_DO', true);
+ }
+ break;
+
+
+ // ##################
+ // Admin based bulk registration of users
+ // ##################
+ case 'register.newuserbulk':
+ case 'admin.newuserbulk':
+ if (!($user->perms('is_admin')))
+ break;
+
+ $group_in = Post::val('group_in');
+ $error = '';
+ $success = '';
+ $noUsers = true;
+
+ // For each user in post, add them
+ for ($i = 0 ; $i < 10 ; $i++)
+ {
+ $user_name = Post::val('user_name' . $i);
+ $real_name = Post::val('real_name' . $i);
+ $email_address = Post::val('email_address' . $i);
+
+
+ if( $user_name == '' || $real_name == '' || $email_address == '')
+ continue;
+ else
+ $noUsers = false;
+
+ $enabled = 1;
+
+ // Avoid dups
+ $sql = $db->query("SELECT COUNT(*) FROM {users} WHERE email_address = ?",
+ array($email_address));
+
+ if ($db->fetchOne($sql))
+ {
+ $error .= "\n" . L('emailtakenbulk') . ": $email_address\n";
+ continue;
+ }
+
+ if (!Backend::create_user($user_name, Post::val('user_pass'),
+ $real_name, '',
+ $email_address,
+ Post::num('notify_type'),
+ Post::num('time_zone'), $group_in, $enabled, '', '', ''))
+ {
+ $error .= "\n" . L('usernametakenbulk') .": $user_name\n";
+ continue;
+ }
+ else
+ $success .= ' '.$user_name.' ';
+ }
+
+ if ($error != '')
+ Flyspray::show_error($error);
+ else if ( $noUsers == true)
+ Flyspray::show_error(L('nouserstoadd'));
+ else
+ {
+ $_SESSION['SUCCESS'] = L('created').$success;
+ if (!$user->perms('is_admin')) {
+ define('NO_DO', true);
+ }
+ }
+ break;
+
+
+ // ##################
+ // Bulk User Edit Form
+ // ##################
+ case 'admin.editallusers':
+
+ if (!($user->perms('is_admin'))) {
+ break;
+ }
+
+ $userids = Post::val('checkedUsers');
+
+ if(!is_array($userids)){
+ break;
+ }
+
+ $users=array();
+
+ foreach ($userids as $uid) {
+ if( ctype_digit($uid) ) {
+ if( $user->id == $uid ){
+ Flyspray::show_error(L('nosuicide'));
+ } else{
+ $users[]=$uid;
+ }
+ } else{
+ Flyspray::show_error(L('invalidinput'));
+ break 2;
+ }
+ }
+
+ if (count($users) == 0){
+ Flyspray::show_error(L('nouserselected'));
+ break;
+ }
+
+ // Make array of users to modify
+ $ids = "(" . $users[0];
+ for ($i = 1 ; $i < count($users) ; $i++)
+ {
+ $ids .= ", " . $users[$i];
+ }
+ $ids .= ")";
+
+ // Grab the action
+ if (isset($_POST['enable']))
+ {
+ $sql = $db->query("UPDATE {users} SET account_enabled = 1 WHERE user_id IN $ids");
+ }
+ else if (isset($_POST['disable']))
+ {
+ $sql = $db->query("UPDATE {users} SET account_enabled = 0 WHERE user_id IN $ids");
+ }
+ else if (isset($_POST['delete']))
+ {
+ //$sql = $db->query("DELETE FROM {users} WHERE user_id IN $ids");
+ foreach ($users as $uid) {
+ Backend::delete_user($uid);
+ }
+ }
+
+ // Show success message and exit
+ $_SESSION['SUCCESS'] = L('usersupdated');
+ break;
+
+
+
+ // ##################
+ // adding a new group
+ // ##################
+ case 'pm.newgroup':
+ case 'admin.newgroup':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (!Post::val('group_name')) {
+ Flyspray::show_error(L('groupanddesc'));
+ break;
+ } else {
+ // Check to see if the group name is available
+ $sql = $db->query("SELECT COUNT(*)
+ FROM {groups}
+ WHERE group_name = ? AND project_id = ?",
+ array(Post::val('group_name'), $proj->id));
+
+ if ($db->fetchOne($sql)) {
+ Flyspray::show_error(L('groupnametaken'));
+ break;
+ } else {
+ $cols = array('group_name', 'group_desc', 'manage_project', 'edit_own_comments',
+ 'view_tasks', 'open_new_tasks', 'modify_own_tasks', 'add_votes',
+ 'modify_all_tasks', 'view_comments', 'add_comments', 'edit_assignments',
+ 'edit_comments', 'delete_comments', 'create_attachments',
+ 'delete_attachments', 'view_history', 'close_own_tasks',
+ 'close_other_tasks', 'assign_to_self', 'show_as_assignees',
+ 'assign_others_to_self', 'add_to_assignees', 'view_reports', 'group_open',
+ 'view_estimated_effort', 'track_effort', 'view_current_effort_done',
+ 'add_multiple_tasks', 'view_roadmap', 'view_own_tasks', 'view_groups_tasks');
+
+ $params = array_map('Post_to0',$cols);
+ array_unshift($params, $proj->id);
+
+ $db->query("INSERT INTO {groups} (project_id, ". join(',', $cols).")
+ VALUES (". $db->fill_placeholders($cols, 1) . ')', $params);
+
+ $_SESSION['SUCCESS'] = L('newgroupadded');
+ }
+ }
+
+ break;
+
+ // ##################
+ // Update the global application preferences
+ // ##################
+ case 'globaloptions':
+ if (!$user->perms('is_admin')) {
+ break;
+ }
+
+ $errors=array();
+
+ $settings = array('jabber_server', 'jabber_port', 'jabber_username', 'notify_registration',
+ 'jabber_password', 'anon_group', 'user_notify', 'admin_email', 'email_ssl', 'email_tls',
+ 'lang_code', 'gravatars', 'hide_emails', 'spam_proof', 'default_project', 'default_entry',
+ 'dateformat','dateformat_extended',
+ 'jabber_ssl', 'anon_reg', 'global_theme', 'smtp_server', 'page_title',
+ 'smtp_user', 'smtp_pass', 'funky_urls', 'reminder_daemon','cache_feeds', 'intro_message',
+ 'disable_lostpw','disable_changepw','days_before_alert', 'emailNoHTML', 'need_approval', 'pages_welcome_msg',
+ 'active_oauths', 'only_oauth_reg', 'enable_avatars', 'max_avatar_size', 'default_order_by',
+ 'max_vote_per_day', 'votes_per_project', 'url_rewriting',
+ 'custom_style', 'general_integration', 'footer_integration',
+ 'repeat_password', 'repeat_emailaddress', 'massops');
+
+ if(!isset($fs->prefs['massops'])){
+ $db->query("INSERT INTO {prefs} (pref_name,pref_value) VALUES('massops',0)");
+ }
+
+ # candid for a plugin, so separate them for the future.
+ $settings[]='captcha_securimage';
+ if(!isset($fs->prefs['captcha_securimage'])){
+ $db->query("INSERT INTO {prefs} (pref_name,pref_value) VALUES('captcha_securimage',0)");
+ }
+
+ # candid for a plugin
+ $settings[]='captcha_recaptcha';
+ $settings[]='captcha_recaptcha_sitekey';
+ $settings[]='captcha_recaptcha_secret';
+ if(!isset($fs->prefs['captcha_recaptcha'])){
+ $db->query("INSERT INTO {prefs} (pref_name,pref_value) VALUES('captcha_recaptcha',0),('captcha_recaptcha_sitekey',''),('captcha_recaptcha_secret','')");
+ }
+
+ if(Post::val('need_approval') == '1' && Post::val('spam_proof')){
+ unset($_POST['spam_proof']); // if self register request admin to approve, disable spam_proof
+ // if you think different, modify functions in class.user.php directing different regiser tpl
+ }
+ if (Post::val('url_rewriting') == '1' && !$fs->prefs['url_rewriting']) {
+ # Setenv can't be used to set the env variable in .htaccess, because apache module setenv is often disabled on hostings and brings server error 500.
+ # First check if htaccess is turned on
+ #if (!array_key_exists('HTTP_HTACCESS_ENABLED', $_SERVER)) {
+ # Flyspray::show_error(L('enablehtaccess'));
+ # break;
+ #}
+
+ # Make sure mod_rewrite is enabled by checking a env var defined as HTTP_MOD_REWRITE in the .htaccess .
+ # It is possible to be converted to REDIRECT_HTTP_MOD_REWRITE . It's sound weired, but that's the case here.
+ if ( !array_key_exists('HTTP_MOD_REWRITE', $_SERVER) && !array_key_exists('REDIRECT_HTTP_MOD_REWRITE' , $_SERVER) ) {
+ #print_r($_SERVER);die();
+ Flyspray::show_error(L('nomodrewrite'));
+ break;
+ }
+ }
+
+ if( substr(Post::val('custom_style'), -4) != '.css'){
+ $_POST['custom_style']='';
+ }
+
+ # TODO validation
+ if( Post::val('default_order_by2') !='' && Post::val('default_order_by2') !='n'){
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'].', '.$_POST['default_order_by2'].' '.$_POST['default_order_by_dir2'];
+ } else{
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'];
+ }
+
+
+ foreach ($settings as $setting) {
+ $db->query('UPDATE {prefs} SET pref_value = ? WHERE pref_name = ?',
+ array(Post::val($setting, 0), $setting));
+ // Update prefs for following scripts
+ $fs->prefs[$setting] = Post::val($setting, 0);
+ }
+
+ // Process the list of groups into a format we can store
+ $viscols = trim(Post::val('visible_columns'));
+ $db->query("UPDATE {prefs} SET pref_value = ?
+ WHERE pref_name = 'visible_columns'",
+ array($viscols));
+ $fs->prefs['visible_columns'] = $viscols;
+
+ $visfields = trim(Post::val('visible_fields'));
+ $db->query("UPDATE {prefs} SET pref_value = ?
+ WHERE pref_name = 'visible_fields'",
+ array($visfields));
+ $fs->prefs['visible_fields'] = $visfields;
+
+ //save logo
+ if($_FILES['logo']['error'] == 0){
+ if( in_array(exif_imagetype($_FILES['logo']['tmp_name']), array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG)) ) {
+ $logofilename=strtolower(basename($_FILES['logo']['name']));
+ $logoexplode = explode('.', $logofilename);
+ $logoextension = strtolower(end($logoexplode));
+ $allowedextensions = array('gif', 'jpg', 'jpeg', 'png');
+
+ if(in_array($logoextension, $allowedextensions)){
+ move_uploaded_file($_FILES['logo']['tmp_name'], './' . $logofilename);
+ $sql = $db->query("SELECT * FROM {prefs} WHERE pref_name='logo'");
+ if(!$db->fetchOne($sql)){
+ $db->query("INSERT INTO {prefs} (pref_name) VALUES('logo')");
+ }
+ $db->query("UPDATE {prefs} SET pref_value = ? WHERE pref_name='logo'", $logofilename);
+ } else{
+ $errors['invalidfileextension']=1;
+ }
+ }
+ }
+ //saved logo
+
+ $_SESSION['SUCCESS'] = L('optionssaved');
+ if(count($errors)>0){
+ $_SESSION['ERRORS']=$errors;
+ }
+
+ break;
+
+ // ##################
+ // adding a new project
+ // ##################
+ case 'admin.newproject':
+ if (!$user->perms('is_admin')) {
+ break;
+ }
+
+ if (!Post::val('project_title')) {
+ Flyspray::show_error(L('emptytitle'));
+ break;
+ }
+
+ $viscols = $fs->prefs['visible_columns']
+ ? $fs->prefs['visible_columns']
+ : 'id tasktype priority severity summary status dueversion progress';
+
+ $visfields = $fs->prefs['visible_fields']
+ ? $fs->prefs['visible_fields']
+ : 'id tasktype priority severity summary status dueversion progress';
+
+
+ $db->query('INSERT INTO {projects}
+ ( project_title, theme_style, intro_message,
+ others_view, others_viewroadmap, anon_open, project_is_active,
+ visible_columns, visible_fields, lang_code, notify_email, notify_jabber, disp_intro)
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)',
+ array(Post::val('project_title'), Post::val('theme_style'),
+ Post::val('intro_message'), Post::num('others_view', 0), Post::num('others_viewroadmap', 0),
+ Post::num('anon_open', 0), $viscols, $visfields,
+ Post::val('lang_code', 'en'), '', '',
+ Post::num('disp_intro')
+ ));
+
+ // $sql = $db->query('SELECT project_id FROM {projects} ORDER BY project_id DESC', false, 1);
+ // $pid = $db->fetchOne($sql);
+ $pid = $db->insert_ID();
+
+ $cols = array( 'manage_project', 'view_tasks', 'open_new_tasks',
+ 'modify_own_tasks', 'modify_all_tasks', 'view_comments',
+ 'add_comments', 'edit_comments', 'delete_comments', 'show_as_assignees',
+ 'create_attachments', 'delete_attachments', 'view_history', 'add_votes',
+ 'close_own_tasks', 'close_other_tasks', 'assign_to_self', 'edit_own_comments',
+ 'assign_others_to_self', 'add_to_assignees', 'view_reports', 'group_open',
+ 'view_estimated_effort', 'view_current_effort_done', 'track_effort',
+ 'add_multiple_tasks', 'view_roadmap', 'view_own_tasks', 'view_groups_tasks',
+ 'edit_assignments');
+ $args = array_fill(0, count($cols), '1');
+ array_unshift($args, 'Project Managers',
+ 'Permission to do anything related to this project.',
+ intval($pid));
+
+ $db->query("INSERT INTO {groups}
+ ( group_name, group_desc, project_id,
+ ".join(',', $cols).")
+ VALUES ( ". $db->fill_placeholders($cols, 3) .")", $args);
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name,
+ show_in_list, category_owner, lft, rgt)
+ VALUES ( ?, ?, 1, 0, 1, 4)", array($pid, 'root'));
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name,
+ show_in_list, category_owner, lft, rgt )
+ VALUES ( ?, ?, 1, 0, 2, 3)", array($pid, 'Backend / Core'));
+
+ $db->query("INSERT INTO {list_os}
+ ( project_id, os_name, list_position, show_in_list )
+ VALUES (?, ?, 1, 1)", array($pid, 'All'));
+
+ $db->query("INSERT INTO {list_version}
+ ( project_id, version_name, list_position,
+ show_in_list, version_tense )
+ VALUES (?, ?, 1, 1, 2)", array($pid, '1.0'));
+
+ $_SESSION['SUCCESS'] = L('projectcreated');
+ Flyspray::redirect(createURL('pm', 'prefs', $pid));
+ break;
+
+ // ##################
+ // updating project preferences
+ // ##################
+ case 'pm.updateproject':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (Post::val('delete_project')) {
+ if (Backend::delete_project($proj->id, Post::val('move_to'))) {
+ $_SESSION['SUCCESS'] = L('projectdeleted');
+ } else {
+ $_SESSION['ERROR'] = L('projectnotdeleted');
+ }
+
+ if (Post::val('move_to')) {
+ Flyspray::redirect(createURL('pm', 'prefs', Post::val('move_to')));
+ } else {
+ Flyspray::redirect($baseurl);
+ }
+ }
+
+ if (!Post::val('project_title')) {
+ Flyspray::show_error(L('emptytitle'));
+ break;
+ }
+
+ $cols = array( 'project_title', 'theme_style', 'lang_code', 'default_task', 'default_entry',
+ 'intro_message', 'notify_email', 'notify_jabber', 'notify_subject', 'notify_reply',
+ 'feed_description', 'feed_img_url','default_due_version','use_effort_tracking',
+ 'pages_intro_msg', 'estimated_effort_format', 'current_effort_done_format');
+ $args = array_map('Post_to0', $cols);
+ $cols = array_merge($cols, $ints = array('project_is_active', 'others_view', 'others_viewroadmap', 'anon_open', 'comment_closed', 'auto_assign', 'freetagging'));
+ $args = array_merge($args, array_map(array('Post', 'num'), $ints));
+ $cols[] = 'notify_types';
+ $args[] = implode(' ', (array) Post::val('notify_types'));
+ $cols[] = 'last_updated';
+ $args[] = time();
+ $cols[] = 'disp_intro';
+ $args[] = Post::num('disp_intro');
+ $cols[] = 'default_cat_owner';
+ $args[] = Flyspray::UserNameToId(Post::val('default_cat_owner'));
+ $cols[] = 'custom_style';
+ $args[] = Post::val('custom_style');
+
+ // Convert to seconds.
+ if (Post::val('hours_per_manday')) {
+ $args[] = effort::editStringToSeconds(Post::val('hours_per_manday'), $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
+ $cols[] = 'hours_per_manday';
+ }
+
+ # TODO validation
+ if( Post::val('default_order_by2') !=''){
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'].', '.$_POST['default_order_by2'].' '.$_POST['default_order_by_dir2'];
+ } else{
+ $_POST['default_order_by']=$_POST['default_order_by'].' '.$_POST['default_order_by_dir'];
+ }
+ $cols[]='default_order_by';
+ $args[]= $_POST['default_order_by'];
+
+ $args[] = $proj->id;
+
+ $update = $db->query("UPDATE {projects}
+ SET ".join('=?, ', $cols)."=?
+ WHERE project_id = ?", $args);
+
+ $update = $db->query('UPDATE {projects} SET visible_columns = ? WHERE project_id = ?',
+ array(trim(Post::val('visible_columns')), $proj->id));
+
+ $update = $db->query('UPDATE {projects} SET visible_fields = ? WHERE project_id = ?',
+ array(trim(Post::val('visible_fields')), $proj->id));
+
+ // Update project prefs for following scripts
+ $proj = new Project($proj->id);
+ $_SESSION['SUCCESS'] = L('projectupdated');
+ Flyspray::redirect(createURL('pm', 'prefs', $proj->id));
+ break;
+
+ // ##################
+ // modifying user details/profile
+ // ##################
+ case 'admin.edituser':
+ case 'myprofile.edituser':
+ if (Post::val('delete_user')) {
+ // There probably is a bug here somewhere but I just can't find it just now.
+ // Anyway, I get the message also when just editing my details.
+ if ($user->id == (int)Post::val('user_id') && $user->perms('is_admin')) {
+ Flyspray::show_error(L('nosuicide'));
+ break;
+ }
+ else {
+ // check that he is not the last user
+ $sql = $db->query('SELECT count(*) FROM {users}');
+ if ($db->fetchOne($sql) > 1) {
+ Backend::delete_user(Post::val('user_id'));
+ $_SESSION['SUCCESS'] = L('userdeleted');
+ Flyspray::redirect(createURL('admin', 'groups'));
+ } else {
+ Flyspray::show_error(L('lastuser'));
+ break;
+ }
+ }
+ }
+
+ if (!Post::val('onlypmgroup')):
+ if ($user->perms('is_admin') || $user->id == Post::val('user_id')): // only admin or user himself can change
+
+ if (!Post::val('real_name') || (!Post::val('email_address') && !Post::val('jabber_id'))) {
+ Flyspray::show_error(L('realandnotify'));
+ break;
+ }
+
+ // Check email format
+ if (!Post::val('email_address') || !Flyspray::check_email(Post::val('email_address')))
+ {
+ Flyspray::show_error(L('novalidemail'));
+ break;
+ }
+
+ # current CleanFS template skips oldpass input requirement for admin accounts: if someone is able to catch an admin session he could simply create another admin acc for example.
+ #if ( (!$user->perms('is_admin') || $user->id == Post::val('user_id')) && !Post::val('oldpass')
+ if ( !$user->perms('is_admin') && !Post::val('oldpass') && (Post::val('changepass') || Post::val('confirmpass')) ) {
+ Flyspray::show_error(L('nooldpass'));
+ break;
+ }
+
+ if ($user->infos['oauth_uid'] && Post::val('changepass')) {
+ Flyspray::show_error(sprintf(L('oauthreqpass'), ucfirst($uesr->infos['oauth_provider'])));
+ break;
+ }
+
+ if (Post::val('changepass')) {
+ if ($fs->prefs['repeat_password'] && Post::val('changepass') != Post::val('confirmpass')) {
+ Flyspray::show_error(L('passnomatch'));
+ break;
+ }
+ if (Post::val('oldpass')) {
+ $sql = $db->query('SELECT user_pass FROM {users} WHERE user_id = ?', array(Post::val('user_id')));
+ $oldpass = $db->fetchRow($sql);
+
+ $pwtest=false;
+ if(strlen($oldpass['user_pass'])==32){
+ $pwtest=hash_equals($oldpass['user_pass'], md5(Post::val('oldpass')));
+ }elseif(strlen($oldpass['user_pass'])==40){
+ $pwtest=hash_equals($oldpass['user_pass'], sha1(Post::val('oldpass')));
+ }elseif(strlen($oldpass['user_pass'])==128){
+ $pwtest=hash_equals($oldpass['user_pass'], hash('sha512',Post::val('oldpass')));
+ }else{
+ $pwtest=password_verify(Post::val('oldpass'), $oldpass['user_pass']);
+ }
+
+ if (!$pwtest){
+ Flyspray::show_error(L('oldpasswrong'));
+ break;
+ }
+ }
+ $new_hash = Flyspray::cryptPassword(Post::val('changepass'));
+ $db->query('UPDATE {users} SET user_pass = ? WHERE user_id = ?',
+ array($new_hash, Post::val('user_id')));
+
+ // If the user is changing their password, better update their cookie hash
+ if ($user->id == Post::val('user_id')) {
+ Flyspray::setCookie('flyspray_passhash',
+ crypt($new_hash, $conf['general']['cookiesalt']), time()+3600*24*30,null,null,null,true);
+ }
+ }
+ $jabId = Post::val('jabber_id');
+ if (!empty($jabId) && Post::val('old_jabber_id') != $jabId) {
+ Notifications::JabberRequestAuth(Post::val('jabber_id'));
+ }
+
+ $db->query('UPDATE {users}
+ SET real_name = ?, email_address = ?, notify_own = ?,
+ jabber_id = ?, notify_type = ?,
+ dateformat = ?, dateformat_extended = ?,
+ tasks_perpage = ?, time_zone = ?, lang_code = ?,
+ hide_my_email = ?, notify_online = ?
+ WHERE user_id = ?',
+ array(Post::val('real_name'), Post::val('email_address'), Post::num('notify_own', 0),
+ Post::val('jabber_id', ''), Post::num('notify_type'),
+ Post::val('dateformat', 0), Post::val('dateformat_extended', 0),
+ Post::num('tasks_perpage'), Post::num('time_zone'), Post::val('lang_code', 'en'),
+ Post::num('hide_my_email', 0), Post::num('notify_online', 0), Post::num('user_id')));
+
+ # 20150307 peterdd: Now we must reload translations, because the user maybe changed his language preferences!
+ # first reload user info
+ $user=new User($user->id);
+ load_translations();
+
+ $profile_image = 'profile_image';
+
+ if(isset($_FILES[$profile_image])) {
+ if(!empty($_FILES[$profile_image]['name'])) {
+ $allowed = array('jpg', 'jpeg', 'gif', 'png');
+
+ $image_name = $_FILES[$profile_image]['name'];
+ $explode = explode('.', $image_name);
+ $image_extn = strtolower(end($explode));
+ $image_temp = $_FILES[$profile_image]['tmp_name'];
+
+ if(in_array($image_extn, $allowed)) {
+ $sql = $db->query('SELECT profile_image FROM {users} WHERE user_id = ?', array(Post::val('user_id')));
+ $avatar_oldname = $db->fetchRow($sql);
+
+ if (is_file(BASEDIR.'/avatars/'.$avatar_oldname['profile_image']))
+ unlink(BASEDIR.'/avatars/'.$avatar_oldname['profile_image']);
+
+ $avatar_name = substr(md5(time()), 0, 10).'.'.$image_extn;
+ $image_path = BASEDIR.'/avatars/'.$avatar_name;
+ move_uploaded_file($image_temp, $image_path);
+ resizeImage($avatar_name, $fs->prefs['max_avatar_size'], $fs->prefs['max_avatar_size']);
+ $db->query('UPDATE {users} SET profile_image = ? WHERE user_id = ?',
+ array($avatar_name, Post::num('user_id')));
+ } else {
+ Flyspray::show_error(L('incorrectfiletype'));
+ break;
+ }
+ }
+ }
+
+ endif; // end only admin or user himself can change
+
+ if ($user->perms('is_admin')) {
+ if($user->id == (int)Post::val('user_id')) {
+ if (Post::val('account_enabled', 0) <= 0 || Post::val('old_global_id') != 1) {
+ Flyspray::show_error(L('nosuicide'));
+ break;
+ }
+ } else {
+ $db->query('UPDATE {users} SET account_enabled = ? WHERE user_id = ?',
+ array(Post::val('account_enabled', 0), Post::val('user_id')));
+ $db->query('UPDATE {users_in_groups} SET group_id = ?
+ WHERE group_id = ? AND user_id = ?',
+ array(Post::val('group_in'), Post::val('old_global_id'), Post::val('user_id')));
+ }
+ }
+
+ endif; // end non project group changes
+
+ if ($user->perms('manage_project') && !is_null(Post::val('project_group_in')) && Post::val('project_group_in') != Post::val('old_group_id')) {
+ $db->query('DELETE FROM {users_in_groups} WHERE group_id = ? AND user_id = ?',
+ array(Post::val('old_group_id'), Post::val('user_id')));
+ if (Post::val('project_group_in')) {
+ $db->query('INSERT INTO {users_in_groups} (group_id, user_id) VALUES(?, ?)',
+ array(Post::val('project_group_in'), Post::val('user_id')));
+ }
+ }
+
+ $_SESSION['SUCCESS'] = L('userupdated');
+ if ($action === 'myprofile.edituser') {
+ Flyspray::redirect(createURL('myprofile'));
+ } elseif ($action === 'admin.edituser' && Post::val('area') === 'users') {
+ Flyspray::redirect(createURL('edituser', Post::val('user_id')));
+ } else {
+ Flyspray::redirect(createURL('user', Post::val('user_id')));
+ }
+ break;
+ // ##################
+ // approving a new user registration
+ // ##################
+ case 'approve.user':
+ if($user->perms('is_admin')) {
+ $db->query('UPDATE {users} SET account_enabled = ? WHERE user_id = ?',
+ array(1, Post::val('user_id')));
+
+ $db->query('UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?
+ WHERE submitted_by = ? AND request_type = ?',
+ array($user->id, time(), Post::val('user_id'), 3));
+ // Missing event constant, can't log yet...
+ // Missing notification constant, can't notify yet...
+ // Notification constant added, write the code for sending that message...
+
+ }
+ break;
+ // ##################
+ // updating a group definition
+ // ##################
+ case 'pm.editgroup':
+ case 'admin.editgroup':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (!Post::val('group_name')) {
+ Flyspray::show_error(L('groupanddesc'));
+ break;
+ }
+
+ $cols = array('group_name', 'group_desc');
+
+ // Add a user to a group
+ if ($uid = Post::val('uid')) {
+ $uids = preg_split('/[,;]+/', $uid, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($uids as $uid) {
+ $uid = Flyspray::usernameToId($uid);
+ if (!$uid) {
+ continue;
+ }
+
+ // If user is already a member of one of the project's groups, **move** (not add) him to the new group
+ $sql = $db->query('SELECT g.group_id
+ FROM {users_in_groups} uig, {groups} g
+ WHERE g.group_id = uig.group_id AND uig.user_id = ? AND project_id = ?',
+ array($uid, $proj->id));
+ if ($db->countRows($sql)) {
+ $oldid = $db->fetchOne($sql);
+ $db->query('UPDATE {users_in_groups} SET group_id = ? WHERE user_id = ? AND group_id = ?',
+ array(Post::val('group_id'), $uid, $oldid));
+ } else {
+ $db->query('INSERT INTO {users_in_groups} (group_id, user_id) VALUES(?, ?)',
+ array(Post::val('group_id'), $uid));
+ }
+ }
+ }
+
+ if (Post::val('delete_group') && Post::val('group_id') != '1') {
+ $db->query('DELETE FROM {groups} WHERE group_id = ?', Post::val('group_id'));
+
+ if (Post::val('move_to')) {
+ $db->query('UPDATE {users_in_groups} SET group_id = ? WHERE group_id = ?',
+ array(Post::val('move_to'), Post::val('group_id')));
+ }
+
+ $_SESSION['SUCCESS'] = L('groupupdated');
+ Flyspray::redirect(createURL( (($proj->id) ? 'pm' : 'admin'), 'groups', $proj->id));
+ }
+ // Allow all groups to update permissions except for global Admin
+ if (Post::val('group_id') != '1') {
+ $cols = array_merge($cols,
+ array('manage_project', 'view_tasks', 'edit_own_comments',
+ 'open_new_tasks', 'modify_own_tasks', 'modify_all_tasks',
+ 'view_comments', 'add_comments', 'edit_comments', 'delete_comments',
+ 'create_attachments', 'delete_attachments', 'show_as_assignees',
+ 'view_history', 'close_own_tasks', 'close_other_tasks', 'edit_assignments',
+ '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'));
+ }
+
+ $args = array_map('Post_to0', $cols);
+ $args[] = Post::val('group_id');
+ $args[] = $proj->id;
+
+ $db->query("UPDATE {groups}
+ SET ".join('=?,', $cols)."=?
+ WHERE group_id = ? AND project_id = ?", $args);
+
+ $_SESSION['SUCCESS'] = L('groupupdated');
+ break;
+
+ // ##################
+ // updating a list
+ // ##################
+ case 'update_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ $listnames = Post::val('list_name');
+ $listposition = Post::val('list_position');
+ $listshow = Post::val('show_in_list');
+ $listdelete = Post::val('delete');
+ if($lt=='tag'){
+ $listclass = Post::val('list_class');
+ }
+ foreach ($listnames as $id => $listname) {
+ if ($listname != '') {
+ if (!isset($listshow[$id])) {
+ $listshow[$id] = 0;
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?
+ AND $list_id <> ?",
+ array($proj->id, $listnames[$id], $id)
+ );
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), $listnames[$id]));
+ return;
+ }
+
+ if($lt=='tag'){
+ $update = $db->query("UPDATE $list_table_name
+ SET $list_column_name=?, list_position=?, show_in_list=?, class=?
+ WHERE $list_id=? AND project_id=?",
+ array($listnames[$id], intval($listposition[$id]), intval($listshow[$id]), $listclass[$id], $id, $proj->id)
+ );
+ } else{
+ $update = $db->query("UPDATE $list_table_name
+ SET $list_column_name=?, list_position=?, show_in_list=?
+ WHERE $list_id=? AND project_id=?",
+ array($listnames[$id], intval($listposition[$id]), intval($listshow[$id]), $id, $proj->id)
+ );
+ }
+ } else {
+ Flyspray::show_error(L('fieldsmissing'));
+ }
+ }
+
+ if (is_array($listdelete) && count($listdelete)) {
+ $deleteids = "$list_id = " . join(" OR $list_id =", array_map('intval', array_keys($listdelete)));
+ $db->query("DELETE FROM $list_table_name WHERE project_id = ? AND ($deleteids)", array($proj->id));
+ }
+
+ $_SESSION['SUCCESS'] = L('listupdated');
+ break;
+
+ // ##################
+ // adding a list item
+ // ##################
+ case 'pm.add_to_list':
+ case 'admin.add_to_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ if (!Post::val('list_name')) {
+ Flyspray::show_error(L('fillallfields'));
+ break;
+ }
+
+ $position = Post::num('list_position');
+ if (!$position) {
+ $position = intval($db->fetchOne($db->query("SELECT max(list_position)+1
+ FROM $list_table_name
+ WHERE project_id = ?",
+ array($proj->id))));
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?",
+ array($proj->id, Post::val('list_name')));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), Post::val('list_name')));
+ return;
+ }
+
+ $db->query("INSERT INTO $list_table_name
+ (project_id, $list_column_name, list_position, show_in_list)
+ VALUES (?, ?, ?, ?)",
+ array($proj->id, Post::val('list_name'), $position, '1'));
+
+ $_SESSION['SUCCESS'] = L('listitemadded');
+ break;
+
+ // ##################
+ // updating the version list
+ // ##################
+ case 'update_version_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ $listnames = Post::val('list_name');
+ $listposition = Post::val('list_position');
+ $listshow = Post::val('show_in_list');
+ $listtense = Post::val('version_tense');
+ $listdelete = Post::val('delete');
+
+ foreach ($listnames as $id => $listname) {
+ if (is_numeric($listposition[$id]) && $listnames[$id] != '') {
+ if (!isset($listshow[$id])) {
+ $listshow[$id] = 0;
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?
+ AND $list_id <> ?",
+ array($proj->id, $listnames[$id], $id));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), $listnames[$id]));
+ return;
+ }
+
+ $update = $db->query("UPDATE $list_table_name
+ SET $list_column_name = ?, list_position = ?,
+ show_in_list = ?, version_tense = ?
+ WHERE $list_id = ? AND project_id = ?",
+ array($listnames[$id], intval($listposition[$id]),
+ intval($listshow[$id]), intval($listtense[$id]), $id, $proj->id));
+ } else {
+ Flyspray::show_error(L('fieldsmissing'));
+ }
+ }
+
+ if (is_array($listdelete) && count($listdelete)) {
+ $deleteids = "$list_id = " . join(" OR $list_id =", array_map('intval', array_keys($listdelete)));
+ $db->query("DELETE FROM $list_table_name WHERE project_id = ? AND ($deleteids)", array($proj->id));
+ }
+
+ $_SESSION['SUCCESS'] = L('listupdated');
+ break;
+
+ // ##################
+ // adding a version list item
+ // ##################
+ case 'pm.add_to_version_list':
+ case 'admin.add_to_version_list':
+ if (!$user->perms('manage_project') || !isset($list_table_name)) {
+ break;
+ }
+
+ if (!Post::val('list_name')) {
+ Flyspray::show_error(L('fillallfields'));
+ break;
+ }
+
+ $position = Post::num('list_position');
+ if (!$position) {
+ $position = $db->fetchOne($db->query("SELECT max(list_position)+1
+ FROM $list_table_name
+ WHERE project_id = ?",
+ array($proj->id)));
+ }
+
+ $check = $db->query("SELECT COUNT(*)
+ FROM $list_table_name
+ WHERE (project_id = 0 OR project_id = ?)
+ AND $list_column_name = ?",
+ array($proj->id, Post::val('list_name')));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('itemexists'), Post::val('list_name')));
+ return;
+ }
+
+ $db->query("INSERT INTO $list_table_name
+ (project_id, $list_column_name, list_position, show_in_list, version_tense)
+ VALUES (?, ?, ?, ?, ?)",
+ array($proj->id, Post::val('list_name'),
+ intval($position), '1', Post::val('version_tense')));
+
+ $_SESSION['SUCCESS'] = L('listitemadded');
+ break;
+
+ // ##################
+ // updating the category list
+ // ##################
+ case 'update_category':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ $listnames = Post::val('list_name');
+ $listshow = Post::val('show_in_list');
+ $listdelete = Post::val('delete');
+ $listlft = Post::val('lft');
+ $listrgt = Post::val('rgt');
+ $listowners = Post::val('category_owner');
+
+ foreach ($listnames as $id => $listname) {
+ if ($listname != '') {
+ if (!isset($listshow[$id])) {
+ $listshow[$id] = 0;
+ }
+
+ // Check for duplicates on the same sub-level under same parent category.
+ // First, we'll have to find the right parent for the current category.
+ $sql = $db->query('SELECT *
+ FROM {list_category}
+ WHERE project_id = ? AND lft < ? and rgt > ?
+ AND lft = (SELECT MAX(lft) FROM {list_category} WHERE lft < ? and rgt > ?)',
+ array($proj->id, intval($listlft[$id]), intval($listrgt[$id]), intval($listlft[$id]), intval($listrgt[$id])));
+
+ $parent = $db->fetchRow($sql);
+
+ $check = $db->query('SELECT COUNT(*)
+ FROM {list_category} c
+ WHERE project_id = ? AND category_name = ? AND lft > ? AND rgt < ?
+ AND category_id <> ?
+ AND NOT EXISTS (SELECT *
+ FROM {list_category}
+ WHERE project_id = ?
+ AND lft > ? AND rgt < ?
+ AND lft < c.lft AND rgt > c.rgt)',
+ array($proj->id, $listname, $parent['lft'], $parent['rgt'], intval($id), $proj->id, $parent['lft'], $parent['rgt']));
+ $itemexists = $db->fetchOne($check);
+
+ // echo "<pre>" . $parent['category_name'] . "," . $listname . ", " . intval($id) . ", " . intval($listlft[$id]) . ", " . intval($listrgt[$id]) . ", " . $itemexists ."</pre>";
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('categoryitemexists'), $listname, $parent['category_name']));
+ return;
+ }
+
+
+ $update = $db->query('UPDATE {list_category}
+ SET category_name = ?,
+ show_in_list = ?, category_owner = ?,
+ lft = ?, rgt = ?
+ WHERE category_id = ? AND project_id = ?',
+ array($listname, intval($listshow[$id]), Flyspray::UserNameToId($listowners[$id]), intval($listlft[$id]), intval($listrgt[$id]), intval($id), $proj->id));
+ // Correct visibility for sub categories
+ if ($listshow[$id] == 0) {
+ foreach ($listnames as $key => $value) {
+ if ($listlft[$key] > $listlft[$id] && $listrgt[$key] < $listrgt[$id]) {
+ $listshow[$key] = 0;
+ }
+ }
+ }
+ } else {
+ Flyspray::show_error(L('fieldsmissing'));
+ }
+ }
+
+ if (is_array($listdelete) && count($listdelete)) {
+ $deleteids = "$list_id = " . join(" OR $list_id =", array_map('intval', array_keys($listdelete)));
+ $db->query("DELETE FROM {list_category} WHERE project_id = ? AND ($deleteids)", array($proj->id));
+ }
+
+ $_SESSION['SUCCESS'] = L('listupdated');
+ break;
+
+ // ##################
+ // adding a category list item
+ // ##################
+ case 'pm.add_category':
+ case 'admin.add_category':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ if (!Post::val('list_name')) {
+ Flyspray::show_error(L('fillallfields'));
+ break;
+ }
+
+ // Get right value of last node
+ // Need also left value of parent for duplicate check and category name for errormessage.
+ $sql = $db->query('SELECT rgt, lft, category_name FROM {list_category} WHERE category_id = ?', array(Post::val('parent_id', -1)));
+ $parent = $db->fetchRow($sql);
+ $right = $parent['rgt'];
+ $left = $parent['lft'];
+
+ // echo "<pre>Parent: " . Post::val('parent_id', -1) . ", left: $left, right: $right</pre>";
+
+ // If parent has subcategories, check for possible duplicates
+ // on the same sub-level and under the same parent.
+ if ($left + 1 != $right) {
+ $check = $db->query('SELECT COUNT(*)
+ FROM {list_category} c
+ WHERE project_id = ? AND category_name = ? AND lft > ? AND rgt < ?
+ AND NOT EXISTS (SELECT *
+ FROM {list_category}
+ WHERE project_id = ?
+ AND lft > ? AND rgt < ?
+ AND lft < c.lft AND rgt > c.rgt)',
+ array($proj->id, Post::val('list_name'), $left, $right, $proj->id, $left, $right));
+ $itemexists = $db->fetchOne($check);
+
+ if ($itemexists) {
+ Flyspray::show_error(sprintf(L('categoryitemexists'), Post::val('list_name'), $parent['category_name']));
+ return;
+ }
+ }
+
+ $db->query('UPDATE {list_category} SET rgt=rgt+2 WHERE rgt >= ? AND project_id = ?', array($right, $proj->id));
+ $db->query('UPDATE {list_category} SET lft=lft+2 WHERE lft >= ? AND project_id = ?', array($right, $proj->id));
+
+ $db->query("INSERT INTO {list_category}
+ ( project_id, category_name, show_in_list, category_owner, lft, rgt )
+ VALUES (?, ?, 1, ?, ?, ?)",
+ array($proj->id, Post::val('list_name'),
+ Post::val('category_owner', 0) == '' ? '0' : Flyspray::usernameToId(Post::val('category_owner', 0)), $right, $right+1));
+
+ $_SESSION['SUCCESS'] = L('listitemadded');
+ break;
+
+ // ##################
+ // adding a related task entry
+ // ##################
+ case 'details.add_related':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::val('related_task'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('relatedinvalid'));
+ break;
+ }
+ }
+
+ $sql = $db->query('SELECT project_id
+ FROM {tasks}
+ WHERE task_id = ?',
+ array(Post::val('related_task')));
+ if (!$db->countRows($sql)) {
+ Flyspray::show_error(L('relatedinvalid'));
+ break;
+ }
+
+ $sql = $db->query("SELECT related_id
+ FROM {related}
+ WHERE this_task = ? AND related_task = ?
+ OR
+ related_task = ? AND this_task = ?",
+ array($task['task_id'], Post::val('related_task'),
+ $task['task_id'], Post::val('related_task')));
+
+ if ($db->countRows($sql)) {
+ Flyspray::show_error(L('relatederror'));
+ break;
+ }
+
+ $db->query("INSERT INTO {related} (this_task, related_task) VALUES(?,?)",
+ array($task['task_id'], Post::val('related_task')));
+
+ Flyspray::logEvent($task['task_id'], 11, Post::val('related_task'));
+ Flyspray::logEvent(Post::val('related_task'), 15, $task['task_id']);
+ $notify->create(NOTIFY_REL_ADDED, $task['task_id'], Post::val('related_task'), null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ $_SESSION['SUCCESS'] = L('relatedaddedmsg');
+ break;
+
+ // ##################
+ // Removing a related task entry
+ // ##################
+ case 'remove_related':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+ if (!is_array(Post::val('related_id'))) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ foreach (Post::val('related_id') as $related) {
+ $sql = $db->query('SELECT this_task, related_task FROM {related} WHERE related_id = ?',
+ array($related));
+ $db->query('DELETE FROM {related} WHERE related_id = ? AND (this_task = ? OR related_task = ?)',
+ array($related, $task['task_id'], $task['task_id']));
+ if ($db->affectedRows()) {
+ $related_task = $db->fetchRow($sql);
+ $related_task = ($related_task['this_task'] == $task['task_id']) ? $related_task['related_task'] : $task['task_id'];
+ Flyspray::logEvent($task['task_id'], 12, $related_task);
+ Flyspray::logEvent($related_task, 16, $task['task_id']);
+ $_SESSION['SUCCESS'] = L('relatedremoved');
+ }
+ }
+
+ break;
+
+ // ##################
+ // adding a user to the notification list
+ // ##################
+ case 'details.add_notification':
+ if (Req::val('user_id')) {
+ $userId = Req::val('user_id');
+ } else {
+ $userId = Flyspray::usernameToId(Req::val('user_name'));
+ }
+ if (!Backend::add_notification($userId, Req::val('ids'))) {
+ Flyspray::show_error(L('couldnotaddusernotif'));
+ break;
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('notifyadded');
+ Flyspray::redirect(createURL('details', $task['task_id']).'#notify');
+ break;
+
+ // ##################
+ // removing a notification entry
+ // ##################
+ case 'remove_notification':
+ Backend::remove_notification(Req::val('user_id'), Req::val('ids'));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('notifyremoved');
+ # if on details page we should redirect to details with a GET
+ # but what if the request comes from another page (like myprofile for instance maybe in future)
+ Flyspray::redirect(createURL('details', $task['task_id']).'#notify');
+ break;
+
+ // ##################
+ // editing a comment
+ // ##################
+ case 'editcomment':
+ if (!($user->perms('edit_comments') || $user->perms('edit_own_comments'))) {
+ break;
+ }
+
+ $where = '';
+
+ $comment_text=Post::val('comment_text');
+ $previous_text=Post::val('previous_text');
+
+ # dokuwiki syntax plugin filters on output
+ if($conf['general']['syntax_plugin'] != 'dokuwiki'){
+ $purifierconfig = HTMLPurifier_Config::createDefault();
+ $purifier = new HTMLPurifier($purifierconfig);
+ $comment_text = $purifier->purify($comment_text);
+ $previous_text= $purifier->purify($comment_text);
+ }
+
+ $params = array($comment_text, time(), Post::val('comment_id'), $task['task_id']);
+
+ if ($user->perms('edit_own_comments') && !$user->perms('edit_comments')) {
+ $where = ' AND user_id = ?';
+ array_push($params, $user->id);
+ }
+
+ $db->query("UPDATE {comments}
+ SET comment_text = ?, last_edited_time = ?
+ WHERE comment_id = ? AND task_id = ? $where", $params);
+ $db->query("DELETE FROM {cache} WHERE topic = ? AND type = ?", array(Post::val('comment_id'), 'comm'));
+
+ Flyspray::logEvent($task['task_id'], 5, $comment_text, $previous_text, Post::val('comment_id'));
+
+ Backend::upload_files($task['task_id'], Post::val('comment_id'));
+ Backend::delete_files(Post::val('delete_att'));
+ Backend::upload_links($task['task_id'], Post::val('comment_id'));
+ Backend::delete_links(Post::val('delete_link'));
+
+ $_SESSION['SUCCESS'] = L('editcommentsaved');
+ break;
+
+ // ##################
+ // deleting a comment
+ // ##################
+ case 'details.deletecomment':
+ if (!$user->perms('delete_comments')) {
+ break;
+ }
+
+ $result = $db->query('SELECT task_id, comment_text, user_id, date_added
+ FROM {comments}
+ WHERE comment_id = ?',
+ array(Get::val('comment_id')));
+ $comment = $db->fetchRow($result);
+
+ // Check for files attached to this comment
+ $check_attachments = $db->query('SELECT *
+ FROM {attachments}
+ WHERE comment_id = ?',
+ array(Req::val('comment_id')));
+
+ if ($db->countRows($check_attachments) && !$user->perms('delete_attachments')) {
+ Flyspray::show_error(L('commentattachperms'));
+ break;
+ }
+
+ $db->query("DELETE FROM {comments} WHERE comment_id = ? AND task_id = ?",
+ array(Req::val('comment_id'), $task['task_id']));
+
+ if ($db->affectedRows()) {
+ Flyspray::logEvent($task['task_id'], 6, $comment['user_id'],
+ $comment['comment_text'], '', $comment['date_added']);
+ }
+
+ while ($attachment = $db->fetchRow($check_attachments)) {
+ $db->query("DELETE from {attachments} WHERE attachment_id = ?",
+ array($attachment['attachment_id']));
+
+ @unlink(BASEDIR .'/attachments/' . $attachment['file_name']);
+
+ Flyspray::logEvent($attachment['task_id'], 8, $attachment['orig_name']);
+ }
+
+ $_SESSION['SUCCESS'] = L('commentdeletedmsg');
+ break;
+
+ // ##################
+ // adding a reminder
+ // ##################
+ case 'details.addreminder':
+ $how_often = Post::val('timeamount1', 1) * Post::val('timetype1');
+ $start_time = Flyspray::strtotime(Post::val('timeamount2', 0));
+
+ $userId = Flyspray::usernameToId(Post::val('to_user_id'));
+ if (!Backend::add_reminder($task['task_id'], Post::val('reminder_message'), $how_often, $start_time, $userId)) {
+ Flyspray::show_error(L('usernotexist'));
+ break;
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('reminderaddedmsg');
+ break;
+
+ // ##################
+ // removing a reminder
+ // ##################
+ case 'deletereminder':
+ if (!$user->perms('manage_project') || !is_array(Post::val('reminder_id'))) {
+ break;
+ }
+
+ foreach (Post::val('reminder_id') as $reminder_id) {
+ $sql = $db->query('SELECT to_user_id FROM {reminders} WHERE reminder_id = ?',
+ array($reminder_id));
+ $reminder = $db->fetchOne($sql);
+ $db->query('DELETE FROM {reminders} WHERE reminder_id = ? AND task_id = ?',
+ array($reminder_id, $task['task_id']));
+ if ($db && $db->affectedRows()) {
+ Flyspray::logEvent($task['task_id'], 18, $reminder);
+ }
+ }
+
+ $_SESSION['SUCCESS'] = L('reminderdeletedmsg');
+ break;
+
+ // ##################
+ // change a bunch of users' groups
+ // ##################
+ case 'movetogroup':
+ // Check that both groups belong to the same project
+ $sql = $db->query('SELECT project_id FROM {groups} WHERE group_id = ? OR group_id = ?',
+ array(Post::val('switch_to_group'), Post::val('old_group')));
+ $old_pr = $db->fetchOne($sql);
+ $new_pr = $db->fetchOne($sql);
+ if ($proj->id != $old_pr || ($new_pr && $new_pr != $proj->id)) {
+ break;
+ }
+
+ if (!$user->perms('manage_project', $old_pr) || !is_array(Post::val('users'))) {
+ break;
+ }
+
+ foreach (Post::val('users') as $user_id => $val) {
+ if($user->id!=$user_id || $proj->id!=0){
+ if (Post::val('switch_to_group') == '0') {
+ $db->query('DELETE FROM {users_in_groups} WHERE user_id=? AND group_id=?',
+ array($user_id, Post::val('old_group'))
+ );
+ } else {
+ # special case: user exists in multiple global groups (shouldn't, but happened)
+ # avoids duplicate entry error
+ if($old_pr==0){
+ $sql = $db->query('SELECT group_id FROM {users_in_groups} WHERE user_id = ? AND group_id = ?',
+ array($user_id, Post::val('switch_to_group'))
+ );
+ $uigexists = $db->fetchOne($sql);
+ if($uigexists > 0){
+ $db->query('DELETE FROM {users_in_groups} WHERE user_id=? AND group_id=?',
+ array($user_id, Post::val('old_group'))
+ );
+ }
+ }
+
+ $db->query('UPDATE {users_in_groups} SET group_id=? WHERE user_id=? AND group_id=?',
+ array(Post::val('switch_to_group'), $user_id, Post::val('old_group'))
+ );
+ }
+ } else {
+ Flyspray::show_error(L('nosuicide'));
+ }
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('groupswitchupdated');
+ break;
+
+ // ##################
+ // taking ownership
+ // ##################
+ case 'takeownership':
+ Backend::assign_to_me($user->id, Req::val('ids'));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('takenownershipmsg');
+ break;
+
+ // ##################
+ // add to assignees list
+ // ##################
+ case 'addtoassignees':
+ Backend::add_to_assignees($user->id, Req::val('ids'));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('addedtoassignees');
+ break;
+
+ // ##################
+ // admin request
+ // ##################
+ case 'requestclose':
+ case 'requestreopen':
+ if ($action == 'requestclose') {
+ Flyspray::adminRequest(1, $proj->id, $task['task_id'], $user->id, Post::val('reason_given'));
+ Flyspray::logEvent($task['task_id'], 20, Post::val('reason_given'));
+ } elseif ($action == 'requestreopen') {
+ Flyspray::adminRequest(2, $proj->id, $task['task_id'], $user->id, Post::val('reason_given'));
+ Flyspray::logEvent($task['task_id'], 21, Post::val('reason_given'));
+ Backend::add_notification($user->id, $task['task_id']);
+ }
+
+ // Now, get the project managers' details for this project
+ $sql = $db->query("SELECT u.user_id
+ FROM {users} u
+ LEFT JOIN {users_in_groups} uig ON u.user_id = uig.user_id
+ LEFT JOIN {groups} g ON uig.group_id = g.group_id
+ WHERE g.project_id = ? AND g.manage_project = '1'",
+ array($proj->id));
+
+ $pms = $db->fetchCol($sql);
+ if (count($pms)) {
+ // Call the functions to create the address arrays, and send notifications
+ $notify->create(NOTIFY_PM_REQUEST, $task['task_id'], null, $notify->specificAddresses($pms), NOTIFY_BOTH, $proj->prefs['lang_code']);
+ }
+
+ $_SESSION['SUCCESS'] = L('adminrequestmade');
+ break;
+
+ // ##################
+ // denying a PM request
+ // ##################
+ case 'denypmreq':
+ $result = $db->query("SELECT task_id, project_id
+ FROM {admin_requests}
+ WHERE request_id = ?",
+ array(Req::val('req_id')));
+ $req_details = $db->fetchRow($result);
+
+ if (!$user->perms('manage_project', $req_details['project_id'])) {
+ break;
+ }
+
+ // Mark the PM request as 'resolved'
+ $db->query("UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?, deny_reason = ?
+ WHERE request_id = ?",
+ array($user->id, time(), Req::val('deny_reason'), Req::val('req_id')));
+
+ Flyspray::logEvent($req_details['task_id'], 28, Req::val('deny_reason'));
+ $notify->create(NOTIFY_PM_DENY_REQUEST, $req_details['task_id'], Req::val('deny_reason'), null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ $_SESSION['SUCCESS'] = L('pmreqdeniedmsg');
+ break;
+
+ // ##################
+ // deny a new user request
+ // ##################
+ case 'denyuserreq':
+ if($user->perms('is_admin')) {
+ $db->query("UPDATE {admin_requests}
+ SET resolved_by = ?, time_resolved = ?, deny_reason = ?
+ WHERE request_id = ?",
+ array($user->id, time(), Req::val('deny_reason'), Req::val('req_id')));
+ // Wrong event constant
+ Flyspray::logEvent(0, 28, Req::val('deny_reason'));//nee a new event number. need notification. fix smtp first
+ // Missing notification constant, can't notify yet...
+ $_SESSION['SUCCESS'] = "New user register request denied";
+ }
+ break;
+
+ // ##################
+ // adding a dependency
+ // ##################
+ case 'details.newdep':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ if (!Post::val('dep_task_id')) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ // TODO: do the checks in some other order. Think about possibility
+ // to combine many of the checks used to to see if a task exists,
+ // if it's something user is allowed to know about etc to just one
+ // function taking the necessary arguments and could be used in
+ // several other places too.
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::val('dep_task_id'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('dependaddfailed'));
+ break;
+ }
+ }
+
+ // First check that the user hasn't tried to add this twice
+ $sql1 = $db->query('SELECT COUNT(*) FROM {dependencies}
+ WHERE task_id = ? AND dep_task_id = ?',
+ array($task['task_id'], Post::val('dep_task_id')));
+
+ // or that they are trying to reverse-depend the same task, creating a mutual-block
+ $sql2 = $db->query('SELECT COUNT(*) FROM {dependencies}
+ WHERE task_id = ? AND dep_task_id = ?',
+ array(Post::val('dep_task_id'), $task['task_id']));
+
+ // Check that the dependency actually exists!
+ $sql3 = $db->query('SELECT COUNT(*) FROM {tasks} WHERE task_id = ?',
+ array(Post::val('dep_task_id')));
+
+ if ($db->fetchOne($sql1) || $db->fetchOne($sql2) || !$db->fetchOne($sql3)
+ // Check that the user hasn't tried to add the same task as a dependency
+ || Post::val('task_id') == Post::val('dep_task_id'))
+ {
+ Flyspray::show_error(L('dependaddfailed'));
+ break;
+ }
+ $notify->create(NOTIFY_DEP_ADDED, $task['task_id'], Post::val('dep_task_id'), null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ $notify->create(NOTIFY_REV_DEP, Post::val('dep_task_id'), $task['task_id'], null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ // Log this event to the task history, both ways
+ Flyspray::logEvent($task['task_id'], 22, Post::val('dep_task_id'));
+ Flyspray::logEvent(Post::val('dep_task_id'), 23, $task['task_id']);
+
+ $db->query('INSERT INTO {dependencies} (task_id, dep_task_id)
+ VALUES (?,?)',
+ array($task['task_id'], Post::val('dep_task_id')));
+
+ $_SESSION['SUCCESS'] = L('dependadded');
+ break;
+
+ // ##################
+ // removing a subtask
+ // ##################
+ case 'removesubtask':
+
+ //check if the user has permissions to remove the subtask
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ //set the subtask supertask_id to 0 removing parent child relationship
+ $db->query("UPDATE {tasks} SET supertask_id=0 WHERE task_id = ?",
+ array(Post::val('subtaskid')));
+
+ //write event log
+ Flyspray::logEvent(Get::val('task_id'), 33, Post::val('subtaskid'));
+ //post success message to the user
+ $_SESSION['SUCCESS'] = L('subtaskremovedmsg');
+ //redirect the user back to the right task
+ Flyspray::redirect(createURL('details', Get::val('task_id')));
+ break;
+
+ // ##################
+ // removing a dependency
+ // ##################
+ case 'removedep':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ $result = $db->query('SELECT * FROM {dependencies}
+ WHERE depend_id = ?',
+ array(Post::val('depend_id')));
+ $dep_info = $db->fetchRow($result);
+
+ $db->query('DELETE FROM {dependencies} WHERE depend_id = ? AND task_id = ?',
+ array(Post::val('depend_id'), $task['task_id']));
+
+ if ($db->affectedRows()) {
+ $notify->create(NOTIFY_DEP_REMOVED, $dep_info['task_id'], $dep_info['dep_task_id'], null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+ $notify->create(NOTIFY_REV_DEP_REMOVED, $dep_info['dep_task_id'], $dep_info['task_id'], null, NOTIFY_BOTH, $proj->prefs['lang_code']);
+
+ Flyspray::logEvent($dep_info['task_id'], 24, $dep_info['dep_task_id']);
+ Flyspray::logEvent($dep_info['dep_task_id'], 25, $dep_info['task_id']);
+
+ $_SESSION['SUCCESS'] = L('depremovedmsg');
+ } else {
+ Flyspray::show_error(L('erroronform'));
+ }
+
+ //redirect the user back to the right task
+ Flyspray::redirect(createURL('details', Post::val('return_task_id')));
+ break;
+
+ // ##################
+ // user requesting a password change
+ // ##################
+ case 'lostpw.sendmagic':
+ // Check that the username exists
+ $sql = $db->query('SELECT * FROM {users} WHERE user_name = ?',
+ array(Post::val('user_name')));
+
+ // If the username doesn't exist, throw an error
+ if (!$db->countRows($sql)) {
+ Flyspray::show_error(L('usernotexist'));
+ break;
+ }
+
+ $user_details = $db->fetchRow($sql);
+
+ if ($user_details['oauth_provider']) {
+ Flyspray::show_error(sprintf(L('oauthreqpass'), ucfirst($user_details['oauth_provider'])));
+ Flyspray::redirect($baseurl);
+ break;
+ }
+
+ //no microtime(), time,even with microseconds is predictable ;-)
+ $magic_url = md5(function_exists('openssl_random_pseudo_bytes') ?
+ openssl_random_pseudo_bytes(32) :
+ uniqid(mt_rand(), true));
+
+
+ // Insert the random "magic url" into the user's profile
+ $db->query('UPDATE {users}
+ SET magic_url = ?
+ WHERE user_id = ?',
+ array($magic_url, $user_details['user_id']));
+
+ if(count($user_details)) {
+ $notify->create(NOTIFY_PW_CHANGE, null, array($baseurl, $magic_url), $notify->specificAddresses(array($user_details['user_id']), NOTIFY_EMAIL));
+ }
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('magicurlsent');
+ break;
+
+ // ##################
+ // Change the user's password
+ // ##################
+ case 'lostpw.chpass':
+ // Check that the user submitted both the fields, and they are the same
+ if (!Post::val('pass1') || strlen(trim(Post::val('magic_url'))) !== 32) {
+ Flyspray::show_error(L('erroronform'));
+ break;
+ }
+
+ if ($fs->prefs['repeat_password'] && Post::val('pass1') != Post::val('pass2')) {
+ Flyspray::show_error(L('passnomatch'));
+ break;
+ }
+
+ $new_pass_hash = Flyspray::cryptPassword(Post::val('pass1'));
+ $db->query("UPDATE {users} SET user_pass = ?, magic_url = ''
+ WHERE magic_url = ?",
+ array($new_pass_hash, Post::val('magic_url')));
+
+ // TODO: Log event in a later version.
+
+ $_SESSION['SUCCESS'] = L('passchanged');
+ Flyspray::redirect($baseurl);
+ break;
+
+ // ##################
+ // making a task private
+ // ##################
+ case 'makeprivate':
+ // TODO: Have to think about this one a bit more. Are project manager
+ // rights really needed for making a task a private? Are there some
+ // other conditions that would permit it? Also making it back to public.
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET mark_private = 1
+ WHERE task_id = ?', array($task['task_id']));
+
+ Flyspray::logEvent($task['task_id'], 3, 1, 0, 'mark_private');
+
+ $_SESSION['SUCCESS'] = L('taskmadeprivatemsg');
+ break;
+
+ // ##################
+ // making a task public
+ // ##################
+ case 'makepublic':
+ if (!$user->perms('manage_project')) {
+ break;
+ }
+
+ $db->query('UPDATE {tasks}
+ SET mark_private = 0
+ WHERE task_id = ?', array($task['task_id']));
+
+ Flyspray::logEvent($task['task_id'], 3, 0, 1, 'mark_private');
+
+ $_SESSION['SUCCESS'] = L('taskmadepublicmsg');
+ break;
+
+ // ##################
+ // Adding a vote for a task
+ // ##################
+ case 'details.addvote':
+ if (Backend::add_vote($user->id, $task['task_id'])) {
+ $_SESSION['SUCCESS'] = L('voterecorded');
+ } else {
+ Flyspray::show_error(L('votefailed'));
+ break;
+ }
+ // TODO: Log event in a later version.
+ break;
+
+
+ // ##################
+ // Removing a vote for a task
+ // ##################
+ # used to remove a vote from myprofile page
+ case 'removevote':
+ # peterdd: I found no details.removevote action in source, so details.removevote is not used, but was planned on the task details page or in the old blue theme?
+ case 'details.removevote':
+ if (Backend::remove_vote($user->id, $task['task_id'])) {
+ $_SESSION['SUCCESS'] = L('voteremoved');
+ } else {
+ Flyspray::show_error(L('voteremovefailed'));
+ break;
+ }
+ // TODO: Log event in a later version, but also see if maybe done here Backend::remove_vote()...
+ break;
+
+
+ // ##################
+ // set supertask id
+ // ##################
+ case 'details.setparent':
+ if (!$user->can_edit_task($task)) {
+ Flyspray::show_error(L('nopermission'));//TODO: create a better error message
+ break;
+ }
+
+ if (!Post::val('supertask_id')) {
+ Flyspray::show_error(L('formnotcomplete'));
+ break;
+ }
+
+ // check that supertask_id is not same as task_id
+ // preventing it from referring to itself
+ if (Post::val('task_id') == Post::val('supertask_id')) {
+ Flyspray::show_error(L('selfsupertasknotallowed'));
+ break;
+ }
+
+ // Check that the supertask_id looks like unsigned integer
+ if ( !preg_match("/^[1-9][0-9]{0,8}$/", Post::val('supertask_id')) ) {
+ Flyspray::show_error(L('invalidsupertaskid'));
+ break;
+ }
+
+ $sql = $db->query('SELECT project_id FROM {tasks} WHERE task_id = ?', array(Post::val('supertask_id')) );
+ // check that supertask_id is a valid task id
+ $parent = $db->fetchRow($sql);
+ if (!$parent) {
+ Flyspray::show_error(L('invalidsupertaskid'));
+ break;
+ }
+
+ // if the user has not the permission to view all tasks, check if the task
+ // is in tasks allowed to see, otherwise tell that the task does not exist.
+ if (!$user->perms('view_tasks')) {
+ $taskcheck = Flyspray::getTaskDetails(Post::val('supertask_id'));
+ if (!$user->can_view_task($taskcheck)) {
+ Flyspray::show_error(L('invalidsupertaskid'));
+ break;
+ }
+ }
+
+ // check to see that both tasks belong to the same project
+ if ($task['project_id'] != $parent['project_id']) {
+ Flyspray::show_error(L('musthavesameproject'));
+ break;
+ }
+
+ // finally looks like all the checks are valid so update the supertask_id for the current task
+ $db->query('UPDATE {tasks}
+ SET supertask_id = ?
+ WHERE task_id = ?',
+ array(Post::val('supertask_id'),Post::val('task_id')));
+
+ // If task already had a different parent, then log removal too
+ if ($task['supertask_id']) {
+ Flyspray::logEvent($task['supertask_id'], 33, Post::val('task_id'));
+ Flyspray::logEvent(Post::val('task_id'), 35, $task['supertask_id']);
+ }
+
+ // Log the events in the task history
+ Flyspray::logEvent(Post::val('supertask_id'), 32, Post::val('task_id'));
+ Flyspray::logEvent(Post::val('task_id'), 34, Post::val('supertask_id'));
+
+ // set success message
+ $_SESSION['SUCCESS'] = L('supertaskmodified');
+
+ break;
+ case 'notifications.remove':
+ if(!isset($_POST['message_id'])) {
+ // Flyspray::show_error(L('summaryanddetails'));
+ break;
+ }
+
+ if (!is_array($_POST['message_id'])) {
+ // Flyspray::show_error(L('summaryanddetails'));
+ break;
+ }
+ if (!count($_POST['message_id'])) {
+ // Nothing to do.
+ break;
+ }
+
+ $validids = array();
+ foreach ($_POST['message_id'] as $id) {
+ if (is_numeric($id)) {
+ if (settype($id, 'int') && $id > 0) {
+ $validids[] = $id;
+ }
+ }
+ }
+
+ if (!count($validids)) {
+ // Nothing to do.
+ break;
+ }
+
+ Notifications::NotificationsHaveBeenRead($validids);
+ break;
+ case 'task.bulkupdate':
+ # TODO check if the user has the right to do each action on each task id he send with the form!
+ # TODO check if tasks have open subtasks before closing
+ # TODO SQL Transactions with rollback function if something went wrong in the middle of bulk action
+ # disabled by default and if currently allowed only for admins until proper checks are done
+ if(isset($fs->prefs['massops']) && $fs->prefs['massops']==1 && $user->perms('is_admin')){
+
+ // TODO: Log events in a later version.
+
+ if(Post::val('updateselectedtasks') == "true") {
+ //process quick actions
+ switch(Post::val('bulk_quick_action'))
+ {
+ case 'bulk_take_ownership':
+ Backend::assign_to_me(Post::val('user_id'),Post::val('ids'));
+ break;
+ case 'bulk_start_watching':
+ Backend::add_notification(Post::val('user_id'),Post::val('ids'));
+ break;
+ case 'bulk_stop_watching':
+ Backend::remove_notification(Post::val('user_id'),Post::val('ids'));
+ break;
+ }
+
+ //Process the tasks.
+ $columns = array();
+ $values = array();
+
+ //determine the tasks properties that have been modified.
+ if(!Post::val('bulk_status')==0){
+ array_push($columns,'item_status');
+ array_push($values, Post::val('bulk_status'));
+ }
+ if(!Post::val('bulk_percent_complete')==0){
+ array_push($columns,'percent_complete');
+ array_push($values, Post::val('bulk_percent_complete'));
+ }
+ if(!Post::val('bulk_task_type')==0){
+ array_push($columns,'task_type');
+ array_push($values, Post::val('bulk_task_type'));
+ }
+ if(!Post::val('bulk_category')==0){
+ array_push($columns,'product_category');
+ array_push($values, Post::val('bulk_category'));
+ }
+ if(!Post::val('bulk_os')==0){
+ array_push($columns,'operating_system');
+ array_push($values, Post::val('bulk_os'));
+ }
+ if(!Post::val('bulk_severity')==0){
+ array_push($columns,'task_severity');
+ array_push($values, Post::val('bulk_severity'));
+ }
+ if(!Post::val('bulk_priority')==0){
+ array_push($columns,'task_priority');
+ array_push($values, Post::val('bulk_priority'));
+ }
+ if(!Post::val('bulk_reportedver')==0){
+ array_push($columns,'product_version');
+ array_push($values, Post::val('bulk_reportedver'));
+ }
+ if(!Post::val('bulk_due_version')==0){
+ array_push($columns,'closedby_version');
+ array_push($values, Post::val('bulk_due_version'));
+ }
+ # TODO Does the user has similiar rights in current and target projects?
+ # TODO Does a task has subtasks? What happens to them? What if they are open/closed?
+ # But: Allowing task dependencies between tasks in different projects is a feature!
+ if(!Post::val('bulk_projects')==0){
+ array_push($columns,'project_id');
+ array_push($values, Post::val('bulk_projects'));
+ }
+ if(!is_null(Post::val('bulk_due_date'))){
+ array_push($columns,'due_date');
+ array_push($values, Flyspray::strtotime(Post::val('bulk_due_date')));
+ }
+
+ //only process if one of the task fields has been updated.
+ if(!array_count_values($columns)==0 && Post::val('ids')){
+ //add the selected task id's to the query string
+ $task_ids = Post::val('ids');
+ $valuesAndTasks = array_merge_recursive($values,$task_ids);
+
+ //execute the database update on all selected queries
+ $update = $db->query("UPDATE {tasks}
+ SET ".join('=?, ', $columns)."=?
+ WHERE". substr(str_repeat(' task_id = ? OR ', count(Post::val('ids'))), 0, -3), $valuesAndTasks);
+ }
+
+ //Set the assignments
+ if(Post::val('bulk_assignment')){
+ // Delete the current assignees for the selected tasks
+ $db->query("DELETE FROM {assigned} WHERE". substr(str_repeat(' task_id = ? OR ', count(Post::val('ids'))), 0, -3),Post::val('ids'));
+
+ // Convert assigned_to and store them in the 'assigned' table
+ foreach ((array)Post::val('ids') as $id){
+ //iterate the users that are selected on the user list.
+ foreach ((array) Post::val('bulk_assignment') as $assignee){
+ //if 'noone' has been selected then dont do the database update.
+ if(!$assignee == 0){
+ //insert the task and user id's into the assigned table.
+ $db->query('INSERT INTO {assigned}
+ (task_id,user_id)
+ VALUES (?, ?)',array($id,$assignee));
+ }
+ }
+ }
+ }
+
+ // set success message
+ $_SESSION['SUCCESS'] = L('tasksupdated');
+ break;
+ }
+ //bulk close
+ else {
+ if (!Post::val('resolution_reason')) {
+ Flyspray::show_error(L('noclosereason'));
+ break;
+ }
+ $task_ids = Post::val('ids');
+ foreach($task_ids as $task_id) {
+ $task = Flyspray::getTaskDetails($task_id);
+ if (!$user->can_close_task($task)) {
+ continue;
+ }
+
+ if ($task['is_closed']) {
+ continue;
+ }
+
+ Backend::close_task($task_id, Post::val('resolution_reason'), Post::val('closure_comment', ''), Post::val('mark100', false));
+ }
+ $_SESSION['SUCCESS'] = L('taskclosedmsg');
+ break;
+ }
+ } # end if massopsenabled
+ else{
+ Flyspray::show_error(L('massopsdisabled'));
+ }
+ }