PageRenderTime 36ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/dotproject/modules/tasks/tasks.class.php

http://github.com/BigBlueHat/dotproject
PHP | 2710 lines | 1942 code | 329 blank | 439 comment | 405 complexity | f736d4c1b8a3ac83f068dbce67d73e6b MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php /* TASKS $Id$ */
  2. if (!defined('DP_BASE_DIR')) {
  3. die('You should not access this file directly.');
  4. }
  5. require_once($AppUI->getSystemClass('libmail'));
  6. require_once($AppUI->getSystemClass('dp'));
  7. require_once($AppUI->getModuleClass('projects'));
  8. require_once($AppUI->getSystemClass('event_queue'));
  9. require_once($AppUI->getSystemClass('date'));
  10. // user based access
  11. $task_access = array('0'=>'Public',
  12. '4'=>'Privileged',
  13. '2'=>'Participant',
  14. '1'=>'Protected',
  15. '3'=>'Private');
  16. /*
  17. * TASK DYNAMIC VALUE:
  18. * 0 = default(OFF), no dep tracking of others, others do track
  19. * 1 = dynamic, umbrella task, no dep tracking, others do track
  20. * 11 = OFF, no dep tracking, others do not track
  21. * 21 = FEATURE, dep tracking, others do not track
  22. * 31 = ON, dep tracking, others do track
  23. */
  24. // When calculating a task's start date only consider
  25. // end dates of tasks with these dynamic values.
  26. $tracked_dynamics = array('0' => '0',
  27. '1' => '1',
  28. '2' => '31');
  29. // Tasks with these dynamics have their dates updated when
  30. // one of their dependencies changes. (They track dependencies)
  31. $tracking_dynamics = array('0' => '21',
  32. '1' => '31');
  33. /*
  34. * CTask Class
  35. */
  36. class CTask extends CDpObject
  37. {
  38. /** @var int */
  39. var $task_id = NULL;
  40. /** @var string */
  41. var $task_name = NULL;
  42. /** @var int */
  43. var $task_parent = NULL;
  44. var $task_milestone = NULL;
  45. var $task_project = NULL;
  46. var $task_owner = NULL;
  47. var $task_start_date = NULL;
  48. var $task_duration = NULL;
  49. var $task_duration_type = NULL;
  50. /** @deprecated */
  51. var $task_hours_worked = NULL;
  52. var $task_end_date = NULL;
  53. var $task_status = NULL;
  54. var $task_priority = NULL;
  55. var $task_percent_complete = NULL;
  56. var $task_description = NULL;
  57. var $task_target_budget = NULL;
  58. var $task_related_url = NULL;
  59. var $task_creator = NULL;
  60. var $task_order = NULL;
  61. var $task_client_publish = NULL;
  62. var $task_dynamic = NULL;
  63. var $task_access = NULL;
  64. var $task_notify = NULL;
  65. var $task_departments = NULL;
  66. var $task_contacts = NULL;
  67. var $task_custom = NULL;
  68. var $task_type = NULL;
  69. function CTask() {
  70. $this->CDpObject('tasks', 'task_id');
  71. }
  72. function __toString() {
  73. return $this -> link . '/' . $this -> type . '/' . $this -> length;
  74. }
  75. // overload check
  76. function check() {
  77. global $AppUI;
  78. if ($this->task_id === NULL) {
  79. return 'task id is NULL';
  80. }
  81. // ensure changes to checkboxes are honoured
  82. $this->task_milestone = intval($this->task_milestone);
  83. $this->task_dynamic = intval($this->task_dynamic);
  84. $this->task_percent_complete = intval($this->task_percent_complete);
  85. if ($this->task_milestone) {
  86. $this->task_duration = '0';
  87. } else if (!($this->task_duration)) {
  88. $this->task_duration = '1';
  89. }
  90. if (!$this->task_creator) {
  91. $this->task_creator = $AppUI->user_id;
  92. }
  93. if (!$this->task_duration_type) {
  94. $this->task_duration_type = 1;
  95. }
  96. if (!$this->task_related_url) {
  97. $this->task_related_url = '';
  98. }
  99. if (!$this->task_notify) {
  100. $this->task_notify = 0;
  101. }
  102. /*
  103. * Check for bad or circular task relationships (dep or child-parent).
  104. * These checks are definately not exhaustive it is still quite possible
  105. * to get things in a knot.
  106. * Note: some of these checks may be problematic and might have to be removed
  107. */
  108. static $addedit;
  109. if (!isset($addedit)) {
  110. $addedit = dPgetParam($_POST, 'dosql', '') == 'do_task_aed' ? true : false;
  111. }
  112. $this_dependencies = array();
  113. /*
  114. * If we are called from addedit then we want to use the incoming
  115. * list of dependencies and attempt to stop bad deps from being created
  116. */
  117. if ($addedit) {
  118. $hdependencies = dPgetParam($_POST, 'hdependencies', '0');
  119. if ($hdependencies) {
  120. $this_dependencies = explode(',', $hdependencies);
  121. }
  122. } else {
  123. $this_dependencies = explode(',', $this->getDependencies());
  124. }
  125. // Set to false for recursive updateDynamic calls etc.
  126. $addedit = false;
  127. // Has a parent
  128. if ($this->task_id && $this->task_id != $this->task_parent) {
  129. $this_children = $this->getChildren();
  130. $this_child_deps = array();
  131. foreach ($this_children as $child) {
  132. $child_dep_str = $this->staticGetDependencies($child);
  133. $this_child_deps = array_unique(array_merge($this_child_deps,
  134. explode(',', $child_dep_str)));
  135. }
  136. $current_task = $this;
  137. $parents_children = array();
  138. //itrative walk through parent tasks
  139. while ($current_task->task_id != $current_task->task_parent) {
  140. $current_parent = new CTask();
  141. $current_parent->load($current_task->task_parent);
  142. $parents_children = array_unique(array_merge($parents_children,
  143. $current_parent->getChildren()));
  144. // Task parent (of any level) cannot be a child of task or its children
  145. // extra precaution against any UI bugs allowing otherwise
  146. if (in_array($current_parent->task_id, $parents_children)) {
  147. return (($this->task_id == $current_parent->task_parent)
  148. ? 'BadParent_CircularParent'
  149. : array('BadParent_CircularGrandParent',
  150. ('(' . $current_parent->task_id . ')')));
  151. }
  152. //Task's children cannot have a parent (of any level) as a dependency
  153. if (in_array($current_parent->task_id, $this_child_deps)) {
  154. return 'BadParent_ChildDepOnParent';
  155. }
  156. //Task cannot have a parent (of any level) as a dependency
  157. if (in_array($current_parent->task_id, $this_dependencies)) {
  158. return (($current_task == $this) ? 'BadDep_CannotDependOnParent'
  159. : array('BadDep_CircularGrandParent',
  160. ('(' . $current_parent->task_id . ')')));
  161. }
  162. $parents_dependents = explode(',', $current_parent->dependentTasks());
  163. $this_intersect = array_intersect($this_dependencies, $parents_dependents);
  164. //Any tasks dependent on a dynamic parent task cannot be dependencies of task
  165. if (array_sum($this_intersect)) {
  166. $ids = '(' . implode(',', $intersect) . ')';
  167. return array('BadDep_CircularDepOnParentDependent', $ids);
  168. }
  169. $current_task = $current_parent;
  170. }
  171. } // parent
  172. // Have deps
  173. if (array_sum($this_dependencies)) {
  174. if ($this->task_dynamic == 1) {
  175. return 'BadDep_DynNoDep';
  176. }
  177. $this_dependents = (($this->task_id) ? explode(',', $this->dependentTasks()) : array());
  178. // Treat parent task, like a dependent
  179. if ($this->task_id != $this->task_parent) {
  180. array_push($this_dependents, $this->task_parent);
  181. $dep_string = $this->dependentTasks($this->task_parent);
  182. $this_dependents = array_unique(array_merge($this_dependents,
  183. explode(',', $dep_string)));
  184. }
  185. $more_dependents = array();
  186. // Treat dependents' parents them like dependents too and pull parent dependents
  187. foreach ($this_dependents as $dependent) {
  188. $dependent_task = new CTask();
  189. $dependent_task->load($dependent);
  190. if ($dependent_task->task_id != $dependent_task->task_parent) {
  191. $current_task = $dependent_task;
  192. while ($current_task->task_id != $current_task->task_parent) {
  193. $current_parent_id = $current_task->task_parent;
  194. array_push($more_dependents, $current_parent_id);
  195. $dep_string = $this->dependentTasks($dependent_task->task_parent);
  196. $more_dependents = array_unique(array_merge($more_dependents,
  197. explode(',', $dep_string)));
  198. $current_task = new CTask();
  199. $current_task->load($current_parent_id);
  200. }
  201. }
  202. }
  203. $this_dependents = array_unique(array_merge($this_dependents, $more_dependents));
  204. // Task dependencies can not be dependent on this task
  205. $intersect = array_intersect($this_dependencies, $this_dependents);
  206. if (array_sum($intersect)) {
  207. $ids = '(' . implode(',', $intersect) . ')';
  208. return array('BadDep_CircularDep', $ids);
  209. }
  210. }
  211. //Is dynamic and no child
  212. if (dPgetConfig('check_task_empty_dynamic') && $this->task_dynamic == 1) {
  213. $children_of_dynamic = $this->getChildren();
  214. if (empty($children_of_dynamic)) {
  215. return 'BadDyn_NoChild';
  216. }
  217. }
  218. return NULL;
  219. }
  220. /*
  221. * overload the load function
  222. * We need to update dynamic tasks of type '1' on each load process!
  223. * @param int $oid optional argument, if not specifed then the value of current key is used
  224. * @return any result from the database operation
  225. */
  226. function load($oid=null, $strip=false, $skipUpdate=false) {
  227. // use parent function to load the given object
  228. $loaded = parent::load($oid, $strip);
  229. /*
  230. ** Update the values of a dynamic task from
  231. ** the children's properties each time the
  232. ** dynamic task is loaded.
  233. ** Additionally store the values in the db.
  234. ** Only treat umbrella tasks of dynamics '1'.
  235. */
  236. if ($this->task_dynamic == 1 && !($skipUpdate)) {
  237. // update task from children
  238. $this->htmlDecode();
  239. $this->updateDynamics(true);
  240. /*
  241. ** Use parent function to store the updated values in the db
  242. ** instead of store function of this object in order to
  243. ** prevent from infinite loops.
  244. */
  245. parent::store();
  246. $loaded = parent::load($oid, $strip);
  247. }
  248. // return whether the object load process has been successful or not
  249. return $loaded;
  250. }
  251. /*
  252. * call the load function but don't update dynamics
  253. */
  254. function peek($oid=null, $strip=false) {
  255. $loadme = $this->load($oid, $strip, true);
  256. return $loadme;
  257. }
  258. function updateDynamics($fromChildren = false) {
  259. //Has a parent or children, we will check if it is dynamic so that it's info is updated also
  260. $q = new DBQuery;
  261. $modified_task = new CTask();
  262. if ($fromChildren) {
  263. if (version_compare(phpversion(), '5.0.0', '>=')) {
  264. $modified_task = $this;
  265. } else {
  266. $modified_task =& $this;
  267. }
  268. } else {
  269. $modified_task->load($this->task_parent);
  270. $modified_task->htmlDecode();
  271. }
  272. if ($modified_task->task_dynamic == '1') {
  273. //Update allocated hours based on children with duration type of 'hours'
  274. $q->addTable($this->_tbl);
  275. $q->addQuery('SUM(task_duration * task_duration_type)');
  276. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  277. . $modified_task->task_id . ' AND task_duration_type = 1 ');
  278. $q->addGroup('task_parent');
  279. $sql = $q->prepare();
  280. $q->clear();
  281. $children_allocated_hours1 = (float) db_loadResult($sql);
  282. /*
  283. * Update allocated hours based on children with duration type of 'days'
  284. * use the daily working hours instead of the full 24 hours to calculate
  285. * dynamic task duration!
  286. */
  287. $q->addTable($this->_tbl);
  288. $q->addQuery(' SUM(task_duration * ' . dPgetConfig('daily_working_hours') . ')');
  289. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  290. . $modified_task->task_id . ' AND task_duration_type <> 1 ');
  291. $q->addGroup('task_parent');
  292. $sql = $q->prepare();
  293. $q->clear();
  294. $children_allocated_hours2 = (float) db_loadResult($sql);
  295. // sum up the two distinct duration values for the children with duration type 'hrs'
  296. // and for those with the duration type 'day'
  297. $children_allocated_hours = $children_allocated_hours1 + $children_allocated_hours2;
  298. if ($modified_task->task_duration_type == 1) {
  299. $modified_task->task_duration = round($children_allocated_hours, 2);
  300. } else {
  301. $modified_task->task_duration = round($children_allocated_hours
  302. / dPgetConfig('daily_working_hours'), 2);
  303. }
  304. //Update worked hours based on children
  305. $q->addTable('tasks', 't');
  306. $q->innerJoin('task_log', 'tl', 't.task_id = tl.task_log_task');
  307. $q->addQuery('SUM(task_log_hours)');
  308. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  309. . $modified_task->task_id . ' AND task_dynamic <> 1 ');
  310. $sql = $q->prepare();
  311. $q->clear();
  312. $children_hours_worked = (float) db_loadResult($sql);
  313. //Update worked hours based on dynamic children tasks
  314. $q->addTable('tasks');
  315. $q->addQuery('SUM(task_hours_worked)');
  316. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  317. . $modified_task->task_id . ' AND task_dynamic = 1 ');
  318. $sql = $q->prepare();
  319. $q->clear();
  320. $children_hours_worked += (float) db_loadResult($sql);
  321. $modified_task->task_hours_worked = $children_hours_worked;
  322. //Update percent complete
  323. //hours
  324. $q->addTable('tasks');
  325. $q->addQuery('SUM(task_percent_complete * task_duration * task_duration_type)');
  326. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  327. . $modified_task->task_id . ' AND task_duration_type = 1 ');
  328. $sql = $q->prepare();
  329. $q->clear();
  330. $real_children_hours_worked = (float) db_loadResult($sql);
  331. //"days"
  332. $q->addTable('tasks');
  333. $q->addQuery('SUM(task_percent_complete * task_duration * '
  334. . dPgetConfig('daily_working_hours') . ')');
  335. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  336. . $modified_task->task_id . ' AND task_duration_type <> 1 ');
  337. $sql = $q->prepare();
  338. $q->clear();
  339. $real_children_hours_worked += (float) db_loadResult($sql);
  340. $total_hours_allocated = (float)($modified_task->task_duration
  341. * (($modified_task->task_duration_type > 1)
  342. ? dPgetConfig('daily_working_hours') : 1));
  343. if ($total_hours_allocated > 0) {
  344. $modified_task->task_percent_complete = ceil($real_children_hours_worked
  345. / $total_hours_allocated);
  346. } else {
  347. $q->addTable('tasks');
  348. $q->addQuery('AVG(task_percent_complete)');
  349. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  350. . $modified_task->task_id);
  351. $sql = $q->prepare();
  352. $q->clear();
  353. $modified_task->task_percent_complete = db_loadResult($sql);
  354. }
  355. //Update start date
  356. $q->addTable('tasks');
  357. $q->addQuery('MIN(task_start_date)');
  358. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  359. . $modified_task->task_id . ' AND ! isnull(task_start_date)'
  360. . " AND task_start_date <> '0000-00-00 00:00:00'");
  361. $sql = $q->prepare();
  362. $q->clear();
  363. $d = db_loadResult($sql);
  364. if ($d) {
  365. $modified_task->task_start_date = $d;
  366. } else {
  367. $modified_task->task_start_date = '0000-00-00 00:00:00';
  368. }
  369. //Update end date
  370. $q->addTable('tasks');
  371. $q->addQuery('MAX(task_end_date)');
  372. $q->addWhere('task_parent = ' . $modified_task->task_id . ' AND task_id <> '
  373. . $modified_task->task_id . ' AND ! isnull(task_end_date)');
  374. $sql = $q->prepare();
  375. $q->clear();
  376. $modified_task->task_end_date = db_loadResult($sql);
  377. //If we are updating a dynamic task from its children we don't want to store() it
  378. //when the method exists the next line in the store calling function will do that
  379. if ($fromChildren == false) {
  380. $modified_task->store();
  381. }
  382. }
  383. }
  384. /*
  385. * Copy the current task
  386. *
  387. * @author handco <handco@users.sourceforge.net>
  388. * @param int id of the destination project
  389. * @return object The new record object or null if error
  390. */
  391. function copy($destProject_id = 0, $destTask_id = -1) {
  392. $newObj = $this->duplicate();
  393. // Copy this task to another project if it's specified
  394. if ($destProject_id != 0) {
  395. $newObj->task_project = $destProject_id;
  396. }
  397. if ($destTask_id == 0) {
  398. $newObj->task_parent = $newObj->task_id;
  399. } else if ($destTask_id > 0) {
  400. $newObj->task_parent = $destTask_id;
  401. }
  402. if ($newObj->task_parent == $this->task_id) {
  403. $newObj->task_parent = '';
  404. }
  405. $newObj->store();
  406. // Copy assigned users as well
  407. $q = new DBQuery();
  408. $q->addQuery('user_id, user_type, perc_assignment, user_task_priority');
  409. $q->addTable('user_tasks');
  410. $q->addWhere('task_id = ' . $this->task_id);
  411. $users = $q->loadList();
  412. $q->setDelete('user_tasks');
  413. $q->addWhere('task_id = ' . $newObj->task_id);
  414. $q->exec();
  415. $q->clear();
  416. $fields = array('user_id', 'user_type', 'perc_assignment', 'user_task_priority', 'task_id');
  417. foreach ($users as $user) {
  418. $user['task_id'] = $newObj->task_id;
  419. $values = array_values($user);
  420. $q->addTable('user_tasks');
  421. $q->addInsert($fields, $values, true);
  422. $q->exec();
  423. $q->clear();
  424. }
  425. //copy dependancies
  426. $dep_list = $this->getDependencies();
  427. $newObj->updateDependencies($dep_list);
  428. return $newObj;
  429. }// end of copy()
  430. function deepCopy($destProject_id = 0, $destTask_id = 0) {
  431. $children = $this->getChildren();
  432. $newObj = $this->copy($destProject_id, $destTask_id);
  433. if (!empty($children)) {
  434. $tempTask = new CTask();
  435. $new_child_ids = array();
  436. foreach ($children as $child) {
  437. $tempTask->peek($child);
  438. $tempTask->htmlDecode($child);
  439. $newChild = $tempTask->deepCopy($destProject_id, $newObj->task_id);
  440. $newChild->store();
  441. //old id to new id translation table
  442. $old_id = $tempTask->task_id;
  443. $new_child_ids[$old_id] = $newChild->task_id;
  444. }
  445. /*
  446. * We cannot update beyond the new child id without complicating matters
  447. * by mapping "old" id's to new in an array that would be accessible in
  448. * *every* level of recursive call and get executed just before returning
  449. * from a given call. Also we may not want to do this as there could be
  450. * good reasons for keeping some of the old non-child dependancy ids anyway
  451. */
  452. //update dependancies on old child ids to new child id
  453. $dep_list = $newObj->getDependencies();
  454. if ($dep_list) {
  455. $dep_array = explode(',', $dep_list);
  456. foreach ($dep_array as $key => $dep_id) {
  457. if ($new_child_ids[$dep_id]) {
  458. $dep_array[$key] = $new_child_ids[$dep_id];
  459. }
  460. }
  461. $dep_list = implode(',', $dep_array);
  462. $newObj->updateDependencies($dep_list);
  463. }
  464. }
  465. $newObj->store();
  466. return $newObj;
  467. }
  468. function move($destProject_id = 0, $destTask_id = -1) {
  469. if ($destProject_id) {
  470. $this->task_project = $destProject_id;
  471. }
  472. if ($destTask_id >= 0) {
  473. $this->task_parent = (($destTask_id) ? $destTask_id : $this->task_id);
  474. }
  475. $this->store();
  476. }
  477. function deepMove($destProject_id = 0, $destTask_id = 0) {
  478. $children = $this->getChildren();
  479. $this->move($destProject_id, $destTask_id);
  480. if (!empty($children)) {
  481. foreach ($children as $child) {
  482. $tempChild = new CTask();
  483. $tempChild->peek($child);
  484. $tempChild->htmlDecode($child);
  485. $tempChild->deepMove($destProject_id, $this->task_id);
  486. $tempChild->store();
  487. }
  488. }
  489. $this->store();
  490. }
  491. /**
  492. * @todo Parent store could be partially used
  493. */
  494. function store() {
  495. GLOBAL $AppUI;
  496. $q = new DBQuery;
  497. $this->dPTrimAll();
  498. $importing_tasks = false;
  499. $msg = $this->check();
  500. if ($msg) {
  501. $return_msg = array(get_class($this) . '::store-check', 'failed', '-');
  502. if (is_array($msg)) {
  503. return array_merge($return_msg, $msg);
  504. } else {
  505. array_push($return_msg, $msg);
  506. return $return_msg;
  507. }
  508. }
  509. if ($this->task_id) {
  510. addHistory('tasks', $this->task_id, 'update', $this->task_name, $this->task_project);
  511. $this->_action = 'updated';
  512. // Load and globalize the old, not yet updated task object
  513. // e.g. we need some info later to calculate the shifting time for depending tasks
  514. // see function update_dep_dates
  515. GLOBAL $oTsk;
  516. $oTsk = new CTask();
  517. $oTsk->peek($this->task_id);
  518. // if task_status changed, then update subtasks
  519. if ($this->task_status != $oTsk->task_status) {
  520. $this->updateSubTasksStatus($this->task_status);
  521. }
  522. // Moving this task to another project?
  523. if ($this->task_project != $oTsk->task_project) {
  524. $this->updateSubTasksProject($this->task_project);
  525. }
  526. if ($this->task_dynamic == 1) {
  527. $this->updateDynamics(true);
  528. }
  529. // shiftDependentTasks needs this done first
  530. $this->check();
  531. $ret = db_updateObject('tasks', $this, 'task_id', false);
  532. // Milestone or task end date, or dynamic status has changed,
  533. // shift the dates of the tasks that depend on this task
  534. if (($this->task_end_date != $oTsk->task_end_date)
  535. || ($this->task_dynamic != $oTsk->task_dynamic)
  536. || ($this->task_milestone == '1')) {
  537. $this->shiftDependentTasks();
  538. }
  539. } else {
  540. $this->_action = 'added';
  541. if ($this->task_start_date == '')
  542. $this->task_start_date = '0000-00-00 00:00:00';
  543. if ($this->task_end_date == '')
  544. $this->task_end_date = '0000-00-00 00:00:00';
  545. $ret = db_insertObject('tasks', $this, 'task_id');
  546. addHistory('tasks', $this->task_id, 'add', $this->task_name, $this->task_project);
  547. if (!$this->task_parent) {
  548. $q->addTable('tasks');
  549. $q->addUpdate('task_parent', $this->task_id);
  550. $q->addWhere('task_id = ' . $this->task_id);
  551. $q->exec();
  552. $q->clear();
  553. } else {
  554. // importing tasks do not update dynamics
  555. $importing_tasks = true;
  556. }
  557. // insert entry in user tasks
  558. $q->addTable('user_tasks');
  559. $q->addInsert('user_id', $AppUI->user_id);
  560. $q->addInsert('task_id', $this->task_id);
  561. $q->addInsert('user_type', '0');
  562. $q->exec();
  563. $q->clear();
  564. }
  565. //split out related departments and store them seperatly.
  566. $q->setDelete('task_departments');
  567. $q->addWhere('task_id=' . $this->task_id);
  568. $q->exec();
  569. $q->clear();
  570. // print_r($this->task_departments);
  571. if (!empty($this->task_departments)) {
  572. $departments = explode(',', $this->task_departments);
  573. foreach ($departments as $department) {
  574. $q->addTable('task_departments');
  575. $q->addInsert('task_id', $this->task_id);
  576. $q->addInsert('department_id', $department);
  577. $q->exec();
  578. $q->clear();
  579. }
  580. }
  581. //split out related contacts and store them seperatly.
  582. $q->setDelete('task_contacts');
  583. $q->addWhere('task_id=' . $this->task_id);
  584. $q->exec();
  585. $q->clear();
  586. if (!empty($this->task_contacts)) {
  587. $contacts = explode(',', $this->task_contacts);
  588. foreach ($contacts as $contact) {
  589. $q->addTable('task_contacts');
  590. $q->addInsert('task_id', $this->task_id);
  591. $q->addInsert('contact_id', $contact);
  592. $q->exec();
  593. $q->clear();
  594. }
  595. }
  596. // if is child update parent task
  597. if ($this->task_parent != $this->task_id) {
  598. if (!$importing_tasks) {
  599. $this->updateDynamics(true);
  600. }
  601. $pTask = new CTask();
  602. $pTask->load($this->task_parent);
  603. $pTask->updateDynamics();
  604. if ($oTsk->task_parent != $this->task_parent) {
  605. $old_parent = new CTask();
  606. $old_parent->load($oTsk->task_parent);
  607. $old_parent->updateDynamics();
  608. }
  609. }
  610. // update dependencies
  611. if (!empty($this->task_id)) {
  612. $this->updateDependencies($this->getDependencies());
  613. } else {
  614. // print_r($this);
  615. }
  616. if (!$ret) {
  617. return get_class($this) . '::store failed <br />' . db_error();
  618. } else {
  619. return NULL;
  620. }
  621. }
  622. /**
  623. * @todo Parent store could be partially used
  624. * @todo Can't delete a task with children
  625. */
  626. function delete() {
  627. $q = new DBQuery;
  628. if (!($this->task_id)) {
  629. return 'invalid task id';
  630. }
  631. //load task first because we need info on it to update the parent tasks later
  632. $task = new CTask();
  633. $task->load($this->task_id);
  634. //get child tasks so we can delete them too (no orphans)
  635. $childrenlist = $task->getDeepChildren();
  636. //delete task (if we're actually allowed to delete this task)
  637. $err_msg = parent::delete($task->task_id, $task->task_name, $task->task_project);
  638. if ($err_msg) {
  639. return $err_msg;
  640. }
  641. $this->_action = 'deleted';
  642. if ($task->task_parent != $task->task_id) {
  643. //Has parent, run the update sequence, this child will no longer be in the database
  644. $this->updateDynamics();
  645. }
  646. $q->clear();
  647. //delete children
  648. if (!empty($childrenlist)) {
  649. foreach ($childrenlist as $child_id) {
  650. $ctask = new CTask();
  651. $ctask->load($child_id);
  652. //ignore permissions on child tasks by deleteing task directly from the database
  653. $q->setDelete('tasks');
  654. $q->addWhere('task_id=' . $ctask->task_id);
  655. if (!($q->exec())) {
  656. return db_error();
  657. }
  658. $q->clear();
  659. addHistory('tasks', $ctask->task_id, 'delete',
  660. $ctask->task_name, $ctask->task_project);
  661. $this->updateDynamics(); //to update after children are deleted (see above)
  662. }
  663. $this->_action = 'deleted with children';
  664. }
  665. //delete affiliated task_logs (overrides any task_log permissions)
  666. $q->setDelete('task_log');
  667. if (!empty($childrenlist)) {
  668. $q->addWhere('task_log_task IN (' . implode(', ', $childrenlist)
  669. . ', ' . $this->task_id . ')');
  670. } else {
  671. $q->addWhere('task_log_task=' . $this->task_id);
  672. }
  673. if (!($q->exec())) {
  674. return db_error();
  675. }
  676. $q->clear();
  677. //delete affiliated task_dependencies
  678. $q->setDelete('task_dependencies');
  679. if (!empty($childrenlist)) {
  680. $q->addWhere('dependencies_task_id IN (' . implode(', ', $childrenlist)
  681. . ', ' . $task->task_id . ')');
  682. } else {
  683. $q->addWhere('dependencies_task_id=' . $task->task_id);
  684. }
  685. if (!($q->exec())) {
  686. return db_error();
  687. }
  688. $q->clear();
  689. // delete linked user tasks
  690. $q->setDelete('user_tasks');
  691. if (!empty($childrenlist)) {
  692. $q->addWhere('task_id IN (' . implode(', ', $childrenlist)
  693. . ', ' . $task->task_id . ')');
  694. } else {
  695. $q->addWhere('task_id=' . $task->task_id);
  696. }
  697. if (!($q->exec())) {
  698. return db_error();
  699. }
  700. $q->clear();
  701. return NULL;
  702. }
  703. function updateDependencies($cslist) {
  704. $q = new DBQuery;
  705. // delete all current entries
  706. $q->setDelete('task_dependencies');
  707. $q->addWhere('dependencies_task_id=' . $this->task_id);
  708. $q->exec();
  709. $q->clear();
  710. // process dependencies
  711. $tarr = explode(',', $cslist);
  712. foreach ($tarr as $task_id) {
  713. if (intval($task_id) > 0) {
  714. $q->addTable('task_dependencies');
  715. $q->addReplace('dependencies_task_id', $this->task_id);
  716. $q->addReplace('dependencies_req_task_id', $task_id);
  717. $q->exec();
  718. $q->clear();
  719. }
  720. }
  721. }
  722. /**
  723. * Retrieve the tasks dependencies
  724. *
  725. * @author handco <handco@users.sourceforge.net>
  726. * @return string comma delimited list of tasks id's
  727. **/
  728. function getDependencies() {
  729. // Call the static method for this object
  730. $result = $this->staticGetDependencies($this->task_id);
  731. return $result;
  732. } // end of getDependencies ()
  733. /**
  734. * Retrieve the tasks dependencies
  735. *
  736. * @author handco <handco@users.sourceforge.net>
  737. * @param integer ID of the task we want dependencies
  738. * @return string comma delimited list of tasks id's
  739. **/
  740. function staticGetDependencies($taskId) {
  741. $q = new DBQuery;
  742. if (empty($taskId)) {
  743. return '';
  744. }
  745. $q->addTable('task_dependencies', 'td');
  746. $q->addQuery('dependencies_req_task_id');
  747. $q->addWhere('td.dependencies_task_id = ' . $taskId);
  748. $sql = $q->prepare();
  749. $q->clear();
  750. $list = db_loadColumn ($sql);
  751. $result = $list ? implode (',', $list) : '';
  752. return $result;
  753. } // end of staticGetDependencies ()
  754. function notifyOwner() {
  755. $q = new DBQuery;
  756. GLOBAL $AppUI, $locale_char_set;
  757. $q->addTable('projects');
  758. $q->addQuery('project_name');
  759. $q->addWhere('project_id=' . $this->task_project);
  760. $sql = $q->prepare();
  761. $q->clear();
  762. $projname = htmlspecialchars_decode(db_loadResult($sql));
  763. $mail = new Mail;
  764. $mail->Subject(($projname . '::' . $this->task_name . ' '
  765. . $AppUI->_($this->_action, UI_OUTPUT_RAW)), $locale_char_set);
  766. // c = creator
  767. // a = assignee
  768. // o = owner
  769. $q->addTable('tasks', 't');
  770. $q->leftJoin('user_tasks', 'u', 'u.task_id = t.task_id');
  771. $q->leftJoin('users', 'o', 'o.user_id = t.task_owner');
  772. $q->leftJoin('contacts', 'oc', 'oc.contact_id = o.user_contact');
  773. $q->leftJoin('users', 'c', 'c.user_id = t.task_creator');
  774. $q->leftJoin('contacts', 'cc', 'cc.contact_id = c.user_contact');
  775. $q->leftJoin('users', 'a', 'a.user_id = u.user_id');
  776. $q->leftJoin('contacts', 'ac', 'ac.contact_id = a.user_contact');
  777. $q->addQuery('t.task_id, cc.contact_email as creator_email'
  778. . ', cc.contact_first_name as creator_first_name'
  779. . ', cc.contact_last_name as creator_last_name'
  780. . ', oc.contact_email as owner_email'
  781. . ', oc.contact_first_name as owner_first_name'
  782. . ', oc.contact_last_name as owner_last_name'
  783. . ', a.user_id as assignee_id, ac.contact_email as assignee_email'
  784. . ', ac.contact_first_name as assignee_first_name'
  785. . ', ac.contact_last_name as assignee_last_name');
  786. $q->addWhere(' t.task_id = ' . $this->task_id);
  787. $sql = $q->prepare();
  788. $q->clear();
  789. $users = db_loadList($sql);
  790. if (count($users)) {
  791. $body = ($AppUI->_('Project', UI_OUTPUT_RAW) . ': ' . $projname . "\n"
  792. . $AppUI->_('Task', UI_OUTPUT_RAW) . ': ' . $this->task_name . "\n"
  793. . $AppUI->_('URL', UI_OUTPUT_RAW) . ': ' . DP_BASE_URL
  794. . '/index.php?m=tasks&a=view&task_id=' . $this->task_id . "\n\n"
  795. . $AppUI->_('Description', UI_OUTPUT_RAW) . ': ' . "\n"
  796. . $this->task_description . "\n\n"
  797. . $AppUI->_('Creator', UI_OUTPUT_RAW) . ': ' . $AppUI->user_first_name . ' '
  798. . $AppUI->user_last_name . "\n\n"
  799. . $AppUI->_('Progress', UI_OUTPUT_RAW) . ': '
  800. . $this->task_percent_complete . '%' . "\n\n"
  801. . dPgetParam($_POST, 'task_log_description'));
  802. $mail->Body($body, isset($GLOBALS['locale_char_set'])
  803. ? $GLOBALS['locale_char_set']
  804. : '');
  805. $mail->From ('"' . $AppUI->user_first_name . ' ' . $AppUI->user_last_name
  806. . '" <' . $AppUI->user_email . '>');
  807. }
  808. if ($mail->ValidEmail($users[0]['owner_email'])) {
  809. $mail->To($users[0]['owner_email'], true);
  810. $mail->Send();
  811. }
  812. return '';
  813. }
  814. //additional comment will be included in email body
  815. function notify($comment = '') {
  816. $q = new DBQuery;
  817. GLOBAL $AppUI, $locale_char_set;
  818. $df = $AppUI->getPref('SHDATEFORMAT');
  819. $df .= ' ' . $AppUI->getPref('TIMEFORMAT');
  820. $sql = 'SELECT project_name FROM projects WHERE project_id=' . $this->task_project;
  821. $projname = htmlspecialchars_decode(db_loadResult($sql));
  822. $mail = new Mail;
  823. $mail->Subject(($projname . '::' . $this->task_name . ' '
  824. . $AppUI->_($this->_action, UI_OUTPUT_RAW)), $locale_char_set);
  825. // c = creator
  826. // a = assignee
  827. // o = owner
  828. $q->addTable('tasks', 't');
  829. $q->leftJoin('user_tasks', 'ut', 'ut.task_id = t.task_id');
  830. $q->leftJoin('users', 'o', 'o.user_id = t.task_owner');
  831. $q->leftJoin('contacts', 'oc', 'oc.contact_id = o.user_contact');
  832. $q->leftJoin('users', 'c', 'c.user_id = t.task_creator');
  833. $q->leftJoin('contacts', 'cc', 'cc.contact_id = c.user_contact');
  834. $q->leftJoin('users', 'a', 'a.user_id = ut.user_id');
  835. $q->leftJoin('contacts', 'ac', 'ac.contact_id = a.user_contact');
  836. $q->addQuery('t.task_id, c.user_id as creator_id, cc.contact_email as creator_email'
  837. . ', cc.contact_first_name as creator_first_name'
  838. . ', cc.contact_last_name as creator_last_name'
  839. . ', o.user_id as owner_id, oc.contact_email as owner_email'
  840. . ', oc.contact_first_name as owner_first_name'
  841. . ', oc.contact_last_name as owner_last_name'
  842. . ', a.user_id as assignee_id, ac.contact_email as assignee_email'
  843. . ', ac.contact_first_name as assignee_first_name'
  844. . ', ac.contact_last_name as assignee_last_name');
  845. $q->addWhere(' t.task_id = ' . $this->task_id);
  846. $users = $q->loadList();
  847. if (count($users)) {
  848. $task_start_date = new CDate($this->task_start_date);
  849. $task_finish_date = new CDate($this->task_end_date);
  850. $priority = dPgetSysVal('TaskPriority');
  851. $body = ($AppUI->_('Project', UI_OUTPUT_RAW) . ': ' . $projname . "\n"
  852. . $AppUI->_('Task', UI_OUTPUT_RAW) . ': ' . $this->task_name);
  853. $body .= "\n" . $AppUI->_('Priority'). ': ' . $priority[$this->task_priority];
  854. $body .= ("\n" . $AppUI->_('Start Date', UI_OUTPUT_RAW) . ': '
  855. . $task_start_date->format($df) . "\n"
  856. . $AppUI->_('Finish Date', UI_OUTPUT_RAW) . ': '
  857. . ($this->task_end_date != '' ? $task_finish_date->format($df) : '') . "\n"
  858. . $AppUI->_('URL', UI_OUTPUT_RAW) . ': ' . DP_BASE_URL
  859. . '/index.php?m=tasks&a=view&task_id=' . $this->task_id . "\n\n"
  860. . $AppUI->_('Description', UI_OUTPUT_RAW) . ': ' . "\n"
  861. . $this->task_description);
  862. if ($users[0]['creator_email']) {
  863. $body .= ("\n\n" . $AppUI->_('Creator', UI_OUTPUT_RAW). ':' . "\n"
  864. . $users[0]['creator_first_name'] . ' ' . $users[0]['creator_last_name' ]
  865. . ', ' . $users[0]['creator_email']);
  866. }
  867. $body .= ("\n\n" . $AppUI->_('Owner', UI_OUTPUT_RAW).':' . "\n"
  868. . $users[0]['owner_first_name'] . ' ' . $users[0]['owner_last_name' ]
  869. . ', ' . $users[0]['owner_email']);
  870. if ($comment != '') {
  871. $body .= "\n\n".$comment;
  872. }
  873. $mail->Body($body, (isset($GLOBALS['locale_char_set'])
  874. ? $GLOBALS['locale_char_set'] : ''));
  875. $mail->From ('"' . $AppUI->user_first_name . ' ' . $AppUI->user_last_name
  876. . '" <' . $AppUI->user_email . '>');
  877. $owner_is_assigned = false;
  878. foreach ($users as $row) {
  879. if ($mail->ValidEmail($row['assignee_email'])) {
  880. $mail->To($row['assignee_email'], true);
  881. $mail->Send();
  882. }
  883. if ($row['assignee_id'] == $row['owner_id']) {
  884. $owner_is_assigned = true;
  885. }
  886. }
  887. if ($AppUI->getPref('MAILALL') && !($owner_is_assigned)) {
  888. $last_record = array_pop($users);
  889. $owner_email = $last_record['owner_email'];
  890. array_push($users, $last_record);
  891. if ($mail->ValidEmail($owner_email)) {
  892. $mail->To($owner_email, true);
  893. $mail->Send();
  894. }
  895. }
  896. }
  897. return '';
  898. }
  899. /**
  900. * Email the task log to assignees, task contacts, project contacts, and others
  901. * based upon the information supplied by the user.
  902. */
  903. function email_log(&$log, $assignees, $task_contacts, $project_contacts, $others, $extras) {
  904. global $AppUI, $locale_char_set, $dPconfig;
  905. $mail_recipients = array();
  906. $q = new DBQuery;
  907. if (isset($assignees) && $assignees == 'on') {
  908. $q->addTable('user_tasks', 'ut');
  909. $q->leftJoin('users', 'ua', 'ua.user_id = ut.user_id');
  910. $q->leftJoin('contacts', 'c', 'c.contact_id = ua.user_contact');
  911. $q->addQuery('c.contact_email, c.contact_first_name, c.contact_last_name');
  912. $q->addWhere('ut.task_id = ' . $this->task_id);
  913. if (! $AppUI->getPref('MAILALL')) {
  914. $q->addWhere('ua.user_id <>' . $AppUI->user_id);
  915. }
  916. $req =& $q->exec(QUERY_STYLE_NUM);
  917. for ($req; ! $req->EOF; $req->MoveNext()) {
  918. list($email, $first, $last) = $req->fields;
  919. if (! isset($mail_recipients[$email])) {
  920. $mail_recipients[$email] = trim($first) . ' ' . trim($last);
  921. }
  922. }
  923. $q->clear();
  924. }
  925. if (isset($task_contacts) && $task_contacts == 'on') {
  926. $q->addTable('task_contacts', 'tc');
  927. $q->leftJoin('contacts', 'c', 'c.contact_id = tc.contact_id');
  928. $q->addQuery('c.contact_email, c.contact_first_name, c.contact_last_name');
  929. $q->addWhere('tc.task_id = ' . $this->task_id);
  930. $req =& $q->exec(QUERY_STYLE_NUM);
  931. for ($req; ! $req->EOF; $req->MoveNext()) {
  932. list($email, $first, $last) = $req->fields;
  933. if (! isset($mail_recipients[$email])) {
  934. $mail_recipients[$email] = $first . ' ' . $last;
  935. }
  936. }
  937. $q->clear();
  938. }
  939. if (isset($project_contacts) && $project_contacts == 'on') {
  940. $q->addTable('project_contacts', 'pc');
  941. $q->leftJoin('contacts', 'c', 'c.contact_id = pc.contact_id');
  942. $q->addQuery('c.contact_email, c.contact_first_name, c.contact_last_name');
  943. $q->addWhere('pc.project_id = ' . $this->task_project);
  944. $req =& $q->exec(QUERY_STYLE_NUM);
  945. for ($req; ! $req->EOF; $req->MoveNext()) {
  946. list($email, $first, $last) = $req->fields;
  947. if (! isset($mail_recipients[$email])) {
  948. $mail_recipients[$email] = $first . ' ' . $last;
  949. }
  950. }
  951. $q->clear();
  952. }
  953. if (isset($others)) {
  954. $others = trim($others, " \r\n\t,"); // get rid of empty elements.
  955. if (mb_strlen($others) > 0) {
  956. $q->addTable('contacts', 'c');
  957. $q->addQuery('c.contact_email, c.contact_first_name, c.contact_last_name');
  958. $q->addWhere('c.contact_id in (' . $others . ')');
  959. $req =& $q->exec(QUERY_STYLE_NUM);
  960. for ($req; ! $req->EOF; $req->MoveNext()) {
  961. list($email, $first, $last) = $req->fields;
  962. if (! isset($mail_recipients[$email])) {
  963. $mail_recipients[$email] = $first . ' ' . $last;
  964. }
  965. }
  966. $q->clear();
  967. }
  968. }
  969. if (isset($extras) && $extras) {
  970. // Search for semi-colons, commas or spaces and allow any to be separators
  971. $extra_list = preg_split('/[\s,;]+/', $extras);
  972. foreach ($extra_list as $email) {
  973. if ($email && ! isset($mail_recipients[$email])) {
  974. $mail_recipients[$email] = $email;
  975. }
  976. }
  977. }
  978. $q->clear(); // Reset to the default state.
  979. if (count($mail_recipients) == 0) {
  980. return false;
  981. }
  982. // Build the email and send it out.
  983. $char_set = isset($locale_char_set) ? $locale_char_set : '';
  984. $mail = new Mail;
  985. // Grab the subject from user preferences
  986. $prefix = $AppUI->getPref('TASKLOGSUBJ');
  987. $mail->Subject($prefix . ' ' . $log->task_log_name, $char_set);
  988. $q->addTable('projects');
  989. $q->addQuery('project_name');
  990. $q->addWhere('project_id=' . $this->task_project);
  991. $sql = $q->prepare();
  992. $q->clear();
  993. $projname = htmlspecialchars_decode(db_loadResult($sql));
  994. $body = $AppUI->_('Project', UI_OUTPUT_RAW) . ': ' . $projname . "\n";
  995. if ($this->task_parent != $this->task_id) {
  996. $q->addTable('tasks');
  997. $q->addQuery('task_name');
  998. $q->addWhere('task_id = ' . $this->task_parent);
  999. $req =& $q->exec(QUERY_STYLE_NUM);
  1000. if ($req) {
  1001. $body .= $AppUI->_('Parent Task', UI_OUTPUT_RAW) . ': '
  1002. . htmlspecialchars_decode($req->fields[0]) . "\n";
  1003. }
  1004. $q->clear();
  1005. }
  1006. $body .= $AppUI->_('Task', UI_OUTPUT_RAW) . ': ' . $this->task_name . "\n";
  1007. $task_types = dPgetSysVal('TaskType');
  1008. $body .= $AppUI->_('Task Type', UI_OUTPUT_RAW) . ':' . $task_types[$this->task_type] . "\n";
  1009. $body .= $AppUI->_('URL', UI_OUTPUT_RAW)
  1010. . ': ' . DP_BASE_URL . '/index.php?m=tasks&a=view&task_id=' . $this->task_id . "\n\n";
  1011. $body .= $AppUI->_('Summary', UI_OUTPUT_RAW) . ': ' . $log->task_log_name . "\n\n";
  1012. $body .= $log->task_log_description;
  1013. // Append the user signature to the email - if it exists.
  1014. $q->addTable('users');
  1015. $q->addQuery('user_signature');
  1016. $q->addWhere('user_id = ' . $AppUI->user_id);
  1017. if ($res = $q->exec()) {
  1018. if ($res->fields['user_signature']) {
  1019. $body .= "\n--\n" . $res->fields['user_signature'];
  1020. }
  1021. }
  1022. $q->clear();
  1023. $mail->Body($body, $char_set);
  1024. $mail->From($AppUI->user_first_name . ' ' . $AppUI->user_last_name . ' <'
  1025. . $AppUI->user_email . '>');
  1026. $recipient_list = '';
  1027. foreach ($mail_recipients as $email => $name) {
  1028. if ($mail->ValidEmail($email)) {
  1029. $mail->To($email);
  1030. $recipient_list .= $email . ' (' . $name . ")\n";
  1031. } else {
  1032. $recipient_list .= "Invalid email address '{$email}' for {$name}, not sent\n";
  1033. }
  1034. }
  1035. $mail->Send();
  1036. // Now update the log
  1037. $save_email = @$AppUI->getPref('TASKLOGNOTE');
  1038. if ($save_email) {
  1039. $log->task_log_description .= "\nEmailed " . date('d/m/Y H:i:s')
  1040. . " to:\n{$recipient_list}";
  1041. return true;
  1042. }
  1043. return false; // No update needed.
  1044. }
  1045. /**
  1046. * @param Date Start date of the period
  1047. * @param Date End date of the period
  1048. * @param integer The target company
  1049. */
  1050. function getTasksForPeriod($start_date, $end_date, $company_id=0, $user_id=null,
  1051. $filter_proj_archived=false, $filter_proj_completed=false) {
  1052. GLOBAL $AppUI;
  1053. $q = new DBQuery;
  1054. // convert to default db time stamp
  1055. $db_start = $start_date->format(FMT_DATETIME_MYSQL);
  1056. $db_end = $end_date->format(FMT_DATETIME_MYSQL);
  1057. // Allow for possible passing of user_id 0 to stop user filtering
  1058. if (!isset($user_id)) {
  1059. $user_id = $AppUI->user_id;
  1060. }
  1061. // filter tasks for not allowed projects
  1062. $tasks_filter = '';
  1063. // check permissions on projects
  1064. $proj = new CProject();
  1065. $task_filter_where = $proj->getAllowedSQL($AppUI->user_id, 't.task_project');
  1066. // exclude read denied projects
  1067. $deny = $proj->getDeniedRecords($AppUI->user_id);
  1068. // check permissions on tasks
  1069. $obj = new CTask();
  1070. $allow = $obj->getAllowedSQL($AppUI->user_id, 't.task_id');
  1071. $parent_task_allow = $obj->getAllowedSQL($AppUI->user_id, 't.task_parent');
  1072. $q->addTable('tasks', 't');
  1073. if ($user_id) {
  1074. $q->innerJoin('user_tasks', 'ut', 't.task_id=ut.task_id');
  1075. }
  1076. $q->innerJoin('projects', 'p', 't.task_project = p.project_id');
  1077. $q->addQuery('DISTINCT t.task_id, t.task_name, t.task_start_date, t.task_end_date'
  1078. . ', t.task_duration, t.task_duration_type'
  1079. . ', p.project_color_identifier AS color, p.project_name');
  1080. $q->addWhere('task_status > -1'
  1081. . " AND ((task_start_date <= '{$db_end}'"
  1082. . " AND (task_end_date >= '{$db_start}'"
  1083. . " OR task_end_date = '0000-00-00 00:00:00' OR task_end_date = NULL)"
  1084. . " OR task_start_date BETWEEN '$db_start' AND '$db_end'))");
  1085. if ($user_id) {
  1086. $q->addWhere("ut.user_id = '$user_id'");
  1087. }
  1088. if ($company_id) {
  1089. $q->addWhere('p.project_company = ' . $company_id);
  1090. }
  1091. if (count($task_filter_where) > 0) {
  1092. $q->addWhere('(' . implode(' AND ', $task_filter_where) . ')');
  1093. }
  1094. if (count($deny) > 0) {
  1095. $q->addWhere('(t.task_project NOT IN (' . implode(', ', $deny) . '))');
  1096. }
  1097. if (count($allow) > 0) {
  1098. $q->addWhere('(' . implode(' AND ', $allow) . ')');
  1099. }
  1100. if (count($parent_task_allow) > 0) {
  1101. $q->addWhere('(' . implode(' AND ', $parent_task_allow) . ')');
  1102. }
  1103. if ($filter_proj_archived) {
  1104. $q->addWhere('p.project_status <> 7');
  1105. }
  1106. if ($filter_proj_archived) {
  1107. $q->addWhere('p.project_status <> 5');
  1108. }
  1109. $q->addOrder('t.task_start_date');
  1110. // assemble query
  1111. $sql = $q->prepare();
  1112. $q->clear();
  1113. //echo "<pre>$sql</pre>";
  1114. // execute and return
  1115. return db_loadList($sql);
  1116. }
  1117. function canAccess($user_id) {
  1118. $q = new DBQuery;
  1119. //check whether we are explicitly denied at task level
  1120. $denied_tasks = $this->getDeniedRecords($user_id);
  1121. if (in_array($this->task_id, $denied_tasks)) {
  1122. return false;
  1123. }
  1124. switch ($this->task_access) {
  1125. case 0:
  1126. //public
  1127. $retval = true;
  1128. $proj_obj = new CProject();
  1129. $denied_projects = $proj_obj->getDeniedRecords($user_id);
  1130. if (in_array($this->task_project, $denied_projects)) {
  1131. $retval = false;
  1132. }
  1133. break;
  1134. case 1:
  1135. //protected
  1136. $q->addTable('users', 'u');
  1137. $q->innerJoin('contacts', 'c', 'c.contact_id=u.user_contact');
  1138. $q->addQuery('c.contact_company');
  1139. $q->addWhere('u.user_id=' . $user_id . ' OR u.user_id=' . $this->task_owner);
  1140. $sql = $q->prepare();
  1141. $q->clear();
  1142. $user_owner_companies = db_loadColumn($sql);
  1143. $company_match = true;
  1144. foreach ($user_owner_companies as $current_company) {
  1145. $company_match = $company_match && ((!(isset($last_company)))
  1146. || $last_company == $current_company);
  1147. $last_company = $current_company;
  1148. }
  1149. case 2:
  1150. //participant
  1151. $company_match = ((isset($company_match)) ? $company_match : true);
  1152. $q->addTable('user_tasks', 'ut');
  1153. $q->addQuery('COUNT(*)');
  1154. $q->addWhere('ut.user_id=' . $user_id . ' AND ut.task_id=' . $this->task_id);
  1155. $sql = $q->prepare();
  1156. $q->clear();
  1157. $count = db_loadResult($sql);
  1158. $retval = (($company_match && $count > 0) || $this->task_owner == $user_id);
  1159. break;
  1160. case 3:
  1161. //private
  1162. $retval = ($this->task_owner == $user_id);
  1163. break;
  1164. case 4:
  1165. //privileged
  1166. $retval = true;
  1167. if ($this->task_project != '') {
  1168. $q->clear();
  1169. $q->addTable('users', 'u');
  1170. $q->innerJoin('contacts', 'c', 'c.contact_id=u.user_contact');
  1171. $q->addQuery('c.contact_company');
  1172. $q->addWhere('u.user_id = ' . $user_id);
  1173. $user_company = $q->loadResult();
  1174. $q->clear();
  1175. $q->addTable('projects', 'p');
  1176. $q->addQuery('p.project_company');
  1177. $q->addWhere('p.project_id = ' . $this->task_project);
  1178. $project_company = $q->loadResult();
  1179. $q->clear();
  1180. $q->addTable('user_tasks', 'ut');
  1181. $q->addQuery('COUNT(*) AS user_task_count');
  1182. $q->addWhere('ut.user_id = ' . $user_id . ' AND ut.task_id = ' . $this->task_id);
  1183. $count = $q->loadResult();
  1184. $q->clear();
  1185. $retval = (($user_company == $project_company) || $this->task_owner == $user_id
  1186. || $count);
  1187. }
  1188. break;
  1189. default:
  1190. $retval = false;
  1191. break;
  1192. }
  1193. return $retval;
  1194. }
  1195. /**
  1196. * retrieve tasks are dependent of another.
  1197. * @param integer ID of the master task
  1198. * @param boolean true if is a dep call (recurse call)
  1199. * @param boolean false for no recursion (needed for calc_end_date)
  1200. **/
  1201. function dependentTasks ($taskId = false, $isDep = false, $recurse = true) {
  1202. $q = new DBQuery;
  1203. global $aDeps;
  1204. // Initialize the dependencies array
  1205. if ($isDep == false) {
  1206. $aDeps = array();
  1207. }
  1208. if (!$taskId) {
  1209. $taskId = $this->task_id;
  1210. }
  1211. if (empty($taskId)) {
  1212. return '';
  1213. }
  1214. // retrieve dependent tasks
  1215. $q->addTable('task_dependencies', 'td');
  1216. $q->innerJoin('tasks', 't', 'td.dependencies_task_id = t.task_id'); // only "real" task ids
  1217. $q->addQuery('dependencies_task_id');
  1218. $q->addWhere('td.dependencies_req_task_id = ' . $taskId);
  1219. $sql = $q->prepare();
  1220. $q->clear();
  1221. $aBuf = db_loadColumn($sql);
  1222. $aBuf = !empty($aBuf) ? $aBuf : array();
  1223. //$aBuf = array_values(db_loadColumn ($sql));
  1224. if ($recurse) {
  1225. // recurse to find sub dependents
  1226. foreach ($aBuf as $depId) {
  1227. if (!in_array($depId, $aDeps)) { //make sure we haven't done a call with this id yet
  1228. $aDeps[] = $depId;
  1229. $this->dependentTasks($depId, true);
  1230. }
  1231. }
  1232. } else {
  1233. $aDeps = $aBuf;
  1234. }
  1235. // return if we are in a dependency call
  1236. if ($isDep) {
  1237. return;
  1238. }
  1239. return implode (',', $aDeps);
  1240. } // end of dependentTasks()
  1241. /*
  1242. * shift dependents tasks dates
  1243. * @return void
  1244. */
  1245. function shiftDependentTasks () {
  1246. // Get tasks that depend on this task
  1247. $csDeps = explode(',', $this->dependentTasks('', '', false));
  1248. if ($csDeps[0] == '') {
  1249. return;
  1250. }
  1251. // Stage 1: Update dependent task dates
  1252. foreach ($csDeps as $task_id) {
  1253. $this->update_dep_dates($task_id);
  1254. }
  1255. // Stage 2: Now shift the dependent tasks' dependents
  1256. foreach ($csDeps as $task_id) {
  1257. $newTask = new CTask();
  1258. $newTask->load($task_id);
  1259. $newTask->shiftDependentTasks();
  1260. }
  1261. return;
  1262. } // end of shiftDependentTasks()
  1263. /*
  1264. * Update this task's dates in the DB.
  1265. * start date: based on latest end date of dependencies
  1266. * end date: based on start date + appropriate task time span
  1267. *
  1268. * @param integer task_id of task to update
  1269. */
  1270. function update_dep_dates($task_id) {
  1271. GLOBAL $tracking_dynamics;
  1272. $q = new DBQuery;
  1273. $newTask = new CTask();
  1274. $newTask->load($task_id);
  1275. // Do not update tasks that are not tracking dependencies
  1276. if (!in_array($newTask->task_dynamic, $tracking_dynamics)) {
  1277. return;
  1278. }
  1279. // load original task dates and calculate task time span
  1280. $tsd = new CDate($newTask->task_start_date);
  1281. $ted = new CDate($newTask->task_end_date);
  1282. $duration = $tsd->calcDuration($ted);
  1283. // reset start date
  1284. $nsd = new CDate ($newTask->get_deps_max_end_date($newTask));
  1285. // prefer Wed 8:00 over Tue 16:00 as start date
  1286. $nsd = $nsd->next_working_day();
  1287. $new_start_date = $nsd->format(FMT_DATETIME_MYSQL);
  1288. // Add task time span to End Date again
  1289. $ned = new CDate();
  1290. $ned->copy($nsd);
  1291. $ned->addDuration($duration, '1');
  1292. // make sure one didn't land on a non-working day
  1293. $ned = $ned->next_working_day(true);
  1294. // prefer tue 16:00 over wed 8:00 as an end date
  1295. $ned = $ned->prev_working_day();
  1296. $new_end_date = $ned->format(FMT_DATETIME_MYSQL);
  1297. // update the db
  1298. $q->addTable('tasks');
  1299. $q->addUpdate('task_start_date', $new_start_date);
  1300. $q->addUpdate('task_end_date', $new_end_date);
  1301. $q->addWhere('task_dynamic <> 1 AND task_id = ' . $task_id);
  1302. $q->exec();
  1303. $q->clear();
  1304. if ($newTask->task_parent != $newTask->task_id) {
  1305. $newTask->updateDynamics();
  1306. }
  1307. return;
  1308. }
  1309. /*
  1310. ** Time related calculations have been moved to /classes/date.class.php
  1311. ** some have been replaced with more _robust_ functions
  1312. **
  1313. ** Affected functions:
  1314. ** prev_working_day()
  1315. ** next_working_day()
  1316. ** calc_task_end_date() renamed to addDuration()
  1317. ** calc_end_date() renamed to calcDuration()
  1318. **
  1319. ** @date 20050525
  1320. ** @responsible gregorerhardt
  1321. ** @purpose reusability, consistence
  1322. */
  1323. /*
  1324. Get the last end date of all of this task's dependencies
  1325. @param Task object
  1326. returns FMT_DATETIME_MYSQL date
  1327. */
  1328. function get_deps_max_end_date($taskObj) {
  1329. global $tracked_dynamics;
  1330. $q = new DBQuery;
  1331. $deps = $taskObj->getDependencies();
  1332. $obj = new CTask();
  1333. $last_end_date = false;
  1334. // Don't respect end dates of excluded tasks
  1335. if ($tracked_dynamics && !empty($deps)) {
  1336. $track_these = implode(',', $tracked_dynamics);
  1337. $q->addTable('tasks');
  1338. $q->addQuery('MAX(task_end_date)');
  1339. $q->addWhere('task_id IN (' . $deps . ') AND task_dynamic IN (' . $track_these . ')');
  1340. $sql = $q->prepare();
  1341. $q->clear();
  1342. $last_end_date = db_loadResult($sql);
  1343. }
  1344. if (!$last_end_date) {
  1345. // Set to project start date
  1346. $id = $taskObj->task_project;
  1347. $q->addTable('projects');
  1348. $q->addQuery('project_start_date');
  1349. $q->addWhere('project_id = ' . $id);
  1350. $sql = $q->prepare();
  1351. $q->clear();
  1352. $last_end_date = db_loadResult($sql);
  1353. }
  1354. return $last_end_date;
  1355. }
  1356. /**
  1357. * Function that returns the amount of hours this
  1358. * task consumes per user each day
  1359. */
  1360. function getTaskDurationPerDay($use_percent_assigned = false) {
  1361. $duration = $this->task_duration * ($this->task_duration_type == 24
  1362. ? dPgetConfig('daily_working_hours')
  1363. : $this->task_duration_type);
  1364. $task_start_date = new CDate($this->task_start_date);
  1365. $task_finish_date = new CDate($this->task_end_date);
  1366. $assigned_users = $this->getAssignedUsers();
  1367. if ($use_percent_assigned) {
  1368. $number_assigned_users = 0;
  1369. foreach ($assigned_users as $u) {
  1370. $number_assigned_users += ($u['perc_assignment'] / 100);
  1371. }
  1372. } else {
  1373. $number_assigned_users = count($assigned_users);
  1374. }
  1375. $day_diff = $task_finish_date->dateDiff($task_start_date);
  1376. $number_of_days_worked = 0;
  1377. $actual_date = $task_start_date;
  1378. for ($i=0; $i<=$day_diff; $i++) {
  1379. if ($actual_date->isWorkingDay()) {
  1380. $number_of_days_worked++;
  1381. }
  1382. $actual_date->addDays(1);
  1383. }
  1384. // May be it was a Sunday task
  1385. if ($number_of_days_worked == 0) {
  1386. $number_of_days_worked = 1;
  1387. }
  1388. if ($number_assigned_users == 0) {
  1389. $number_assigned_users = 1;
  1390. }
  1391. return ($duration/$number_assigned_users) / $number_of_days_worked;
  1392. }
  1393. /**
  1394. * Function that returns the amount of hours this
  1395. * task consumes per user each week
  1396. */
  1397. function getTaskDurationPerWeek($use_percent_assigned = false) {
  1398. $duration = ($this->task_duration_type == 24 ? dPgetConfig('daily_working_hours')
  1399. : $this->task_duration_type) * $this->task_duration;
  1400. $task_start_date = new CDate($this->task_start_date);
  1401. $task_finish_date = new CDate($this->task_end_date);
  1402. $assigned_users = $this->getAssignedUsers();
  1403. if ($use_percent_assigned) {
  1404. $number_assigned_users = 0;
  1405. foreach ($assigned_users as $u) {
  1406. $number_assigned_users += ($u['perc_assignment'] / 100);
  1407. }
  1408. } else {
  1409. $number_assigned_users = count($assigned_users);
  1410. }
  1411. $number_of_weeks_worked = $task_finish_date->workingDaysInSpan($task_start_date)
  1412. / count(explode(',', dPgetConfig('cal_working_days')));
  1413. $number_of_weeks_worked = (($number_of_weeks_worked < 1)
  1414. ? ceil($number_of_weeks_worked) : $number_of_weeks_worked);
  1415. // zero adjustment
  1416. if ($number_of_weeks_worked == 0) {
  1417. $number_of_weeks_worked = 1;
  1418. }
  1419. if ($number_assigned_users == 0) {
  1420. $number_assigned_users = 1;
  1421. }
  1422. return ($duration/$number_assigned_users) / $number_of_weeks_worked;
  1423. }
  1424. // unassign a user from task
  1425. function removeAssigned($user_id) {
  1426. $q = new DBQuery;
  1427. // delete all current entries
  1428. $q->setDelete('user_tasks');
  1429. $q->addWhere('task_id = ' . $this->task_id . ' AND user_id = ' . $user_id);
  1430. $q->exec();
  1431. $q->clear();
  1432. }
  1433. //using user allocation percentage ($perc_assign)
  1434. // @return returns the Names of the over-assigned users (if any), otherwise false
  1435. function updateAssigned($cslist, $perc_assign, $del=true, $rmUsers=false) {
  1436. $q = new DBQuery;
  1437. // process assignees
  1438. $tarr = explode(',', $cslist);
  1439. // delete all current entries from $cslist
  1440. if ($del == true && $rmUsers == true) {
  1441. foreach ($tarr as $user_id) {
  1442. $user_id = (int)$user_id;
  1443. if (!empty($user_id)) {
  1444. $this->removeAssigned($user_id);
  1445. }
  1446. }
  1447. return false;
  1448. } else if ($del == true) { // delete all users assigned to this task (to properly update)
  1449. $q->setDelete('user_tasks');
  1450. $q->addWhere('task_id = ' . $this->task_id);
  1451. $q->exec();
  1452. $q->clear();
  1453. }
  1454. // get Allocation info in order to check if overAssignment occurs
  1455. $alloc = $this->getAllocation('user_id');
  1456. $overAssignment = false;
  1457. foreach ($tarr as $user_id) {
  1458. if (intval($user_id) > 0) {
  1459. $perc = $perc_assign[$user_id];
  1460. if (dPgetConfig('check_overallocation')
  1461. && $perc > $alloc[$user_id]['freeCapacity']) {
  1462. // add Username of the overAssigned User
  1463. $overAssignment .= ' ' . $alloc[$user_id]['userFC'];
  1464. }
  1465. else {
  1466. $q->addTable('user_tasks');
  1467. $q->addReplace('user_id', $user_id);
  1468. $q->addReplace('task_id', $this->task_id);
  1469. $q->addReplace('perc_assignment', $perc);
  1470. $q->exec();
  1471. $q->clear();
  1472. }
  1473. }
  1474. }
  1475. return $overAssignment;
  1476. }
  1477. function getAssignedUsers() {
  1478. $q = new DBQuery;
  1479. $q->addTable('users', 'u');
  1480. $q->innerJoin('user_tasks', 'ut', 'ut.user_id = u.user_id');
  1481. $q->leftJoin('contacts', 'co', ' co.contact_id = u.user_contact');
  1482. $q->addQuery('u.*, ut.perc_assignment, ut.user_task_priority'
  1483. . ', co.contact_first_name, co.contact_last_name');
  1484. $q->addWhere('ut.task_id = ' . $this->task_id);
  1485. $sql = $q->prepare();
  1486. $q->clear();
  1487. return db_loadHashList($sql, 'user_id');
  1488. }
  1489. /**
  1490. * Calculate the extent of utilization of user assignments
  1491. * @param string hash a hash for the returned hashList
  1492. * @param array users an array of user_ids calculating their assignment capacity
  1493. * @return array returns hashList of extent of utilization for assignment of the users
  1494. */
  1495. function getAllocation($hash = NULL, $users = NULL) {
  1496. if (! dPgetConfig('check_overallocation') && ! dPgetConfig('direct_edit_assignment')) {
  1497. return array();
  1498. }
  1499. $q = new DBQuery;
  1500. // retrieve the systemwide default preference for the assignment maximum
  1501. $q->addTable('user_preferences');
  1502. $q->addQuery('pref_value');
  1503. $q->addWhere("pref_user = 0 AND pref_name = 'TASKASSIGNMAX'");
  1504. $sql = $q->prepare();
  1505. $q->clear();
  1506. $result = db_loadHash($sql, $sysChargeMax);
  1507. if (! $result) {
  1508. $scm = 0;
  1509. } else {
  1510. $scm = $sysChargeMax['pref_value'];
  1511. }
  1512. /*
  1513. * provide actual assignment charge, individual chargeMax
  1514. * and freeCapacity of users' assignments to tasks
  1515. */
  1516. $q->addTable('users', 'u');
  1517. $q->leftJoin('contacts', 'c', 'c.contact_id = u.user_contact');
  1518. $q->leftJoin('user_tasks', 'ut', 'ut.user_id = u.user_id');
  1519. $q->leftJoin('user_preferences', 'up', 'up.pref_user = u.user_id');
  1520. $q->addQuery("u.user_id, CONCAT(CONCAT_WS(' [', CONCAT_WS(' '"
  1521. . ', contact_first_name, contact_last_name), IF(IFNULL((IFNULL(up.pref_value'
  1522. . ', ' . $scm . ') - SUM(ut.perc_assignment)), up.pref_value) > 0'
  1523. . ', IFNULL((IFNULL(up.pref_value, ' . $scm . ') - SUM(ut.perc_assignment))'
  1524. . ', up.pref_value), 0)), ' . "'%]')" . ' AS userFC'
  1525. . ', IFNULL(SUM(ut.perc_assignment), 0) AS charge, u.user_username'
  1526. . ', IFNULL(up.pref_value,' . $scm . ') AS chargeMax'
  1527. . ', IF(IFNULL((IFNULL(up.pref_value, ' . $scm . ') '
  1528. . '- SUM(ut.perc_assignment)), up.pref_value) > 0'
  1529. . ', IFNULL((IFNULL(up.pref_value, ' . $scm . ') - SUM(ut.perc_assignment))'
  1530. . ', up.pref_value), 0) AS freeCapacity');
  1531. if (!empty($users)) { // use userlist if available otherwise pull data for all users
  1532. $q->addWhere('u.user_id IN (' . implode(',', $users) . ')');
  1533. }
  1534. $q->addGroup('u.user_id');
  1535. $q->addOrder('contact_last_name, contact_first_name');
  1536. $sql = $q->prepare();
  1537. $q->clear();
  1538. //echo "<pre>$sql</pre>";
  1539. return db_loadHashList($sql, $hash);
  1540. }
  1541. function getUserSpecificTaskPriority($user_id = 0, $task_id = NULL) {
  1542. $q = new DBQuery;
  1543. // use task_id of given object if the optional parameter task_id is empty
  1544. $task_id = empty($task_id) ? $this->task_id : $task_id;
  1545. $q->addTable('user_tasks');
  1546. $q->addQuery('user_task_priority');
  1547. $q->addWhere('user_id = ' . $user_id . ' AND task_id = ' . $task_id);
  1548. $sql = $q->prepare();
  1549. $q->clear();
  1550. $prio = db_loadHash($sql, $priority);
  1551. return $prio ? $priority['user_task_priority'] : NULL;
  1552. }
  1553. function updateUserSpecificTaskPriority($user_task_priority = 0, $user_id = 0
  1554. , $task_id = NULL) {
  1555. $q = new DBQuery;
  1556. // use task_id of given object if the optional parameter task_id is empty
  1557. $task_id = empty($task_id) ? $this->task_id : $task_id;
  1558. $q->addTable('user_tasks');
  1559. $q->addReplace('user_id', $user_id);
  1560. $q->addReplace('task_id', $task_id);
  1561. $q->addReplace('user_task_priority', $user_task_priority);
  1562. $q->exec();
  1563. $q->clear();
  1564. }
  1565. function getProject() {
  1566. $q = new DBQuery;
  1567. $q->addTable('projects');
  1568. $q->addQuery('project_name, project_short_name, project_color_identifier');
  1569. $q->addWhere("project_id = '" . $this->task_project ."'");
  1570. $sql = $q->prepare();
  1571. $q->clear();
  1572. $proj = db_loadHash($sql, $projects);
  1573. return $projects;
  1574. }
  1575. //Returns task children IDs
  1576. function getChildren() {
  1577. $q = new DBQuery;
  1578. $q->addTable('tasks');
  1579. $q->addQuery('task_id');
  1580. $q->addWhere("task_id <> '" . $this->task_id . "' AND task_parent = '" . $this->task_id ."'");
  1581. $sql = $q->prepare();
  1582. $q->clear();
  1583. return db_loadColumn($sql);
  1584. }
  1585. // Returns task deep children IDs
  1586. function getDeepChildren() {
  1587. $q = new DBQuery;
  1588. $q->addTable('tasks');
  1589. $q->addQuery('task_id');
  1590. $q->addWhere("task_id <> '" . $this->task_id . "' AND task_parent = '" . $this->task_id ."'");
  1591. $sql = $q->prepare();
  1592. $q->clear();
  1593. $children = db_loadColumn($sql);
  1594. if ($children) {
  1595. $deep_children = array();
  1596. $tempTask = new CTask();
  1597. foreach ($children as $child) {
  1598. $tempTask->peek($child);
  1599. $deep_children = array_merge($deep_children, $tempTask->getDeepChildren());
  1600. }
  1601. return array_merge($children, $deep_children);
  1602. }
  1603. return array();
  1604. }
  1605. /**
  1606. * This function, recursively, updates all tasks status
  1607. * to the one passed as parameter
  1608. */
  1609. function updateSubTasksStatus($new_status, $task_id = null) {
  1610. $q = new DBQuery;
  1611. if (is_null($task_id)) {
  1612. $task_id = $this->task_id;
  1613. }
  1614. // get children
  1615. $q->addTable('tasks');
  1616. $q->addQuery('task_id');
  1617. $q->addWhere("task_parent = '" . $task_id . "'");
  1618. $sql = $q->prepare();
  1619. $q->clear();
  1620. $tasks_id = db_loadColumn($sql);
  1621. if (count($tasks_id) == 0) {
  1622. return true;
  1623. }
  1624. // update status of children
  1625. $q->addTable('tasks');
  1626. $q->addUpdate('task_status', $new_status);
  1627. $q->addWhere("task_parent = '" . $task_id . "'");
  1628. $q->exec();
  1629. $q->clear();
  1630. // update status of children's children
  1631. foreach ($tasks_id as $id) {
  1632. if ($id != $task_id) {
  1633. $this->updateSubTasksStatus($new_status, $id);
  1634. }
  1635. }
  1636. }
  1637. /**
  1638. * This function recursively updates all tasks project
  1639. * to the one passed as parameter
  1640. */
  1641. function updateSubTasksProject($new_project , $task_id = null) {
  1642. $q = new DBQuery;
  1643. if (is_null($task_id)) {
  1644. $task_id = $this->task_id;
  1645. }
  1646. $q->addTable('tasks');
  1647. $q->addQuery('task_id');
  1648. $q->addWhere("task_parent = '" . $task_id . "'");
  1649. $sql = $q->prepare();
  1650. $q->clear();
  1651. $tasks_id = db_loadColumn($sql);
  1652. if (count($tasks_id) == 0) {
  1653. return true;
  1654. }
  1655. // update project of children
  1656. $q->addTable('tasks');
  1657. $q->addUpdate('task_project', $new_project);
  1658. $q->addWhere("task_parent = '" . $task_id . "'");
  1659. $q->exec();
  1660. $q->clear();
  1661. foreach ($tasks_id as $id) {
  1662. if ($id != $task_id) {
  1663. $this->updateSubTasksProject($new_project, $id);
  1664. }
  1665. }
  1666. }
  1667. function canUserEditTimeInformation() {
  1668. global $AppUI;
  1669. $project = new CProject();
  1670. $project->load($this->task_project);
  1671. // Code to see if the current user is
  1672. // enabled to change time information related to task
  1673. $can_edit_time_information = false;
  1674. // Let's see if all users are able to edit task time information
  1675. if (dPgetConfig('restrict_task_time_editing') == true && $this->task_id > 0) {
  1676. // Am I the task owner?
  1677. if ($this->task_owner == $AppUI->user_id) {
  1678. $can_edit_time_information = true;
  1679. }
  1680. // Am I the project owner?
  1681. if ($project->project_owner == $AppUI->user_id) {
  1682. $can_edit_time_information = true;
  1683. }
  1684. // Am I sys admin?
  1685. if (getPermission('admin', 'edit')) {
  1686. $can_edit_time_information = true;
  1687. }
  1688. } else if (dPgetConfig('restrict_task_time_editing') == false || $this->task_id == 0) {
  1689. // If all users are able, then don't check anything
  1690. $can_edit_time_information = true;
  1691. }
  1692. return $can_edit_time_information;
  1693. }
  1694. /**
  1695. * Injects a reminder event into the event queue.
  1696. * Repeat interval is one day, repeat count
  1697. * and days to trigger before event overdue is
  1698. * set in the system config.
  1699. */
  1700. function addReminder() {
  1701. $day = 86400;
  1702. if (!dPgetConfig('task_reminder_control')) {
  1703. return;
  1704. }
  1705. if (! $this->task_end_date) { // No end date, can't do anything.
  1706. return $this->clearReminder(true); // Also no point if it is changed to null
  1707. }
  1708. if ($this->task_percent_complete >= 100) {
  1709. return $this->clearReminder(true);
  1710. }
  1711. $eq = new EventQueue;
  1712. $pre_charge = dPgetConfig('task_reminder_days_before', 1);
  1713. $repeat = dPgetConfig('task_reminder_repeat', 100);
  1714. /*
  1715. * If we don't need any arguments (and we don't) then we set this to null.
  1716. * We can't just put null in the call to add as it is passed by reference.
  1717. */
  1718. $args = null;
  1719. // Find if we have a reminder on this task already
  1720. $old_reminders = $eq->find('tasks', 'remind', $this->task_id);
  1721. if (count($old_reminders)) {
  1722. /*
  1723. * It shouldn't be possible to have more than one reminder,
  1724. * but if we do, we may as well clean them up now.
  1725. */
  1726. foreach ($old_reminders as $old_id => $old_data) {
  1727. $eq->remove($old_id);
  1728. }
  1729. }
  1730. // Find the end date of this task, then subtract the required number of days.
  1731. $date = new CDate($this->task_end_date);
  1732. $today = new CDate(date('Y-m-d'));
  1733. if (CDate::compare($date, $today) < 0) {
  1734. $start_day = time();
  1735. } else {
  1736. $start_day = $date->getDate(DATE_FORMAT_UNIXTIME);
  1737. $start_day -= ($day * $pre_charge);
  1738. }
  1739. $eq->add(array($this, 'remind'), $args, 'tasks', false, $this->task_id, 'remind',
  1740. $start_day, $day, $repeat);
  1741. }
  1742. /**
  1743. * Called by the Event Queue processor to process a reminder
  1744. * on a task.
  1745. * @access public
  1746. * @param string $module Module name (not used)
  1747. * @param string $type Type of event (not used)
  1748. * @param integer $id ID of task being reminded
  1749. * @param integer $owner Originator of event
  1750. * @param mixed $args event-specific arguments.
  1751. * @return mixed true, dequeue event, false, event stays in queue.
  1752. -1, event is destroyed.
  1753. */
  1754. function remind($module, $type, $id, $owner, &$args) {
  1755. global $locale_char_set, $AppUI;
  1756. $q = new DBQuery;
  1757. $df = $AppUI->getPref('SHDATEFORMAT');
  1758. $tf = $AppUI->getPref('TIMEFORMAT');
  1759. // If we don't have preferences set for these, use ISO defaults.
  1760. if (! $df) {
  1761. $df = '%Y-%m-%d';
  1762. }
  1763. if (! $tf) {
  1764. $tf = '%H:%m';
  1765. }
  1766. $df .= ' ' . $tf;
  1767. // At this stage we won't have an object yet
  1768. if (! $this->load($id)) {
  1769. return -1; // No point it trying again later.
  1770. }
  1771. $this->htmlDecode();
  1772. // Only remind on working days.
  1773. $today = new CDate();
  1774. if (! $today->isWorkingDay()) {
  1775. return true;
  1776. }
  1777. // Check if the task is completed
  1778. if ($this->task_percent_complete == 100) {
  1779. return -1;
  1780. }
  1781. // Grab the assignee list
  1782. $q->addTable('user_tasks', 'ut');
  1783. $q->leftJoin('users', 'u', 'u.user_id = ut.user_id');
  1784. $q->leftJoin('contacts', 'c', 'c.contact_id = u.user_contact');
  1785. $q->addQuery('c.contact_id, contact_first_name, contact_last_name, contact_email');
  1786. $q->addWhere('ut.task_id = ' . $id);
  1787. $contacts = $q->loadHashList('contact_id');
  1788. $q->clear();
  1789. // Now we also check the owner of the task, as we will need
  1790. // to notify them as well.
  1791. $owner_is_not_assignee = false;
  1792. $q->addTable('users', 'u');
  1793. $q->leftJoin('contacts', 'c', 'c.contact_id = u.user_contact');
  1794. $q->addQuery('c.contact_id, contact_first_name, contact_last_name, contact_email');
  1795. $q->addWhere('u.user_id = ' . $this->task_owner);
  1796. if ($q->exec(ADODB_FETCH_NUM)) {
  1797. list($owner_contact, $owner_first_name, $owner_last_name, $owner_email) = $q->fetchRow();
  1798. if (! isset($contacts[$owner_contact])) {
  1799. $owner_is_not_assignee = true;
  1800. $contacts[$owner_contact] = array(
  1801. 'contact_id' => $owner_contact,
  1802. 'contact_first_name' => $owner_first_name,
  1803. 'contact_last_name' => $owner_last_name,
  1804. 'contact_email' => $owner_email
  1805. );
  1806. }
  1807. }
  1808. $q->clear();
  1809. // build the subject line, based on how soon the
  1810. // task will be overdue.
  1811. $starts = new CDate($this->task_start_date);
  1812. $expires = new CDate($this->task_end_date);
  1813. $now = new CDate();
  1814. $diff = $expires->dateDiff($now);
  1815. $prefix = $AppUI->_('Task Due', UI_OUTPUT_RAW);
  1816. if ($diff == 0) {
  1817. $msg = $AppUI->_('TODAY', UI_OUTPUT_RAW);
  1818. } else if ($diff == 1) {
  1819. $msg = $AppUI->_('TOMORROW', UI_OUTPUT_RAW);
  1820. } else if ($diff < 0) {
  1821. $msg = $AppUI->_(array('OVERDUE', abs($diff), 'DAYS'));
  1822. $prefix = $AppUI->_('Task', UI_OUTPUT_RAW);
  1823. } else {
  1824. $msg = $AppUI->_(array($diff, 'DAYS'));
  1825. }
  1826. $q->addTable('projects');
  1827. $q->addQuery('project_name');
  1828. $q->addWhere('project_id = ' . $this->task_project);
  1829. $project_name = htmlspecialchars_decode($q->loadResult());
  1830. $q->clear();
  1831. $subject = $prefix . ' ' .$msg . ' ' . $this->task_name . '::' . $project_name;
  1832. $body = ($AppUI->_('Task Due', UI_OUTPUT_RAW) . ': ' . $msg . "\n"
  1833. . $AppUI->_('Project', UI_OUTPUT_RAW) . ': ' . $project_name . "\n"
  1834. . $AppUI->_('Task', UI_OUTPUT_RAW) . ': ' . $this->task_name . "\n"
  1835. . $AppUI->_('Start Date', UI_OUTPUT_RAW) . ': ' . $starts->format($df) . "\n"
  1836. . $AppUI->_('Finish Date', UI_OUTPUT_RAW) . ': ' . $expires->format($df) . "\n"
  1837. . $AppUI->_('URL', UI_OUTPUT_RAW) . ': ' . DP_BASE_URL
  1838. . '/index.php?m=tasks&a=view&task_id=' . $this->task_id . '&reminded=1' . "\n\n"
  1839. . $AppUI->_('Resources', UI_OUTPUT_RAW) . ":\n");
  1840. foreach ($contacts as $contact) {
  1841. if ($owner_is_not_assignee || $contact['contact_id'] != $owner_contact) {
  1842. $body .= ($contact['contact_first_name'] . ' ' . $contact['contact_last_name']
  1843. . ' <' . $contact['contact_email'] . ">\n");
  1844. }
  1845. }
  1846. $body .= ("\n" . $AppUI->_('Description', UI_OUTPUT_RAW) . ":\n"
  1847. . $this->task_description . "\n");
  1848. $mail = new Mail;
  1849. foreach ($contacts as $contact) {
  1850. if ($mail->ValidEmail($contact['contact_email'])) {
  1851. $mail->To($contact['contact_email']);
  1852. }
  1853. }
  1854. $mail->From ('"' . $owner_first_name . ' ' . $owner_last_name . '" <' . $owner_email . '>');
  1855. $mail->Subject($subject, $locale_char_set);
  1856. $mail->Body($body, $locale_char_set);
  1857. return $mail->Send();
  1858. }
  1859. /**
  1860. *
  1861. */
  1862. function clearReminder($dont_check = false) {
  1863. $ev = new EventQueue;
  1864. $event_list = $ev->find('tasks', 'remind', $this->task_id);
  1865. if (count($event_list)) {
  1866. foreach ($event_list as $id => $data) {
  1867. if ($dont_check || $this->task_percent_complete >= 100) {
  1868. $ev->remove($id);
  1869. }
  1870. }
  1871. }
  1872. }
  1873. }
  1874. /**
  1875. * CTaskLog Class
  1876. */
  1877. class CTaskLog extends CDpObject
  1878. {
  1879. var $task_log_id = NULL;
  1880. var $task_log_task = NULL;
  1881. var $task_log_name = NULL;
  1882. var $task_log_description = NULL;
  1883. var $task_log_creator = NULL;
  1884. var $task_log_hours = NULL;
  1885. var $task_log_date = NULL;
  1886. var $task_log_costcode = NULL;
  1887. var $task_log_problem = NULL;
  1888. var $task_log_reference = NULL;
  1889. var $task_log_related_url = NULL;
  1890. function CTaskLog() {
  1891. $this->CDpObject('task_log', 'task_log_id');
  1892. // ensure changes to checkboxes are honoured
  1893. $this->task_log_problem = intval($this->task_log_problem);
  1894. }
  1895. function dPTrimAll() {
  1896. $spacedDescription = $this->task_log_description;
  1897. parent::dPTrimAll();
  1898. $this->task_log_description = $spacedDescription;
  1899. }
  1900. // overload check method
  1901. function check() {
  1902. $this->task_log_hours = (float) $this->task_log_hours;
  1903. return NULL;
  1904. }
  1905. }
  1906. function openClosedTask($task) {
  1907. global $tasks_opened;
  1908. global $tasks_closed;
  1909. $tasks_closed = (($tasks_closed) ? $tasks_closed : array());
  1910. $tasks_opened = (($tasks_opened) ? $tasks_opened : array());
  1911. $to_open_task = new CTask();
  1912. if (is_array($task) || $task > 0) {
  1913. if (is_array($task)) {
  1914. $task_dynamic = $task['task_dynamic'];
  1915. $task_id = $task['task_id'];
  1916. } else {
  1917. $to_open_task->peek($task);
  1918. $task_dynamic = $to_open_task->task_dynamic;
  1919. $task_id = $task;
  1920. }
  1921. // don't "open" non-dynamic tasks
  1922. if ($task_dynamic == 1) {
  1923. // only unset that which is set
  1924. $index = array_search($task_id, $tasks_closed);
  1925. if ($index !== false) {
  1926. unset($tasks_closed[$index]);
  1927. }
  1928. //don't double open or we can't close properly
  1929. if (!in_array($task_id, $tasks_opened)) {
  1930. $tasks_opened[] = $task_id;
  1931. }
  1932. }
  1933. }
  1934. }
  1935. function openClosedTaskRecursive($task_id) {
  1936. global $tasks_opened;
  1937. global $tasks_closed;
  1938. $tasks_closed = (($tasks_closed) ? $tasks_closed : array());
  1939. $tasks_opened = (($tasks_opened) ? $tasks_opened : array());
  1940. $open_task = new CTask();
  1941. if ($task_id > 0) {
  1942. openClosedTask($task_id);
  1943. $open_task->peek($task_id) ;
  1944. $children_to_open = $open_task->getChildren();
  1945. foreach ($children_to_open as $to_open) {
  1946. openClosedTaskRecursive($to);
  1947. }
  1948. }
  1949. }
  1950. function closeOpenedTask($task) {
  1951. global $tasks_opened;
  1952. global $tasks_closed;
  1953. $tasks_closed = (($tasks_closed) ? $tasks_closed : array());
  1954. $tasks_opened = (($tasks_opened) ? $tasks_opened : array());
  1955. $to_close_task = new CTask();
  1956. if (is_array($task) || $task > 0) {
  1957. if (is_array($task)) {
  1958. $task_id = $task['task_id'];
  1959. $task_dynamic = $task['task_dynamic'];
  1960. } else {
  1961. $to_close_task->peek($task);
  1962. $task_id = $task;
  1963. $task_dynamic = $to_close_task->task_dynamic;
  1964. }
  1965. // don't "close" non-dynamic tasks
  1966. if ($task_dynamic == 1) {
  1967. // only unset that which is set
  1968. $index = array_search($task_id, $tasks_opened);
  1969. if ($index !== false) {
  1970. unset($tasks_opened[$index]);
  1971. }
  1972. //don't double close or we can't open properly
  1973. if (!in_array($task_id, $tasks_closed)) {
  1974. $tasks_closed[] = $task_id;
  1975. }
  1976. }
  1977. }
  1978. }
  1979. function closeOpenedTaskRecursive($task_id) {
  1980. global $tasks_opened;
  1981. global $tasks_closed;
  1982. $tasks_closed = (($tasks_closed) ? $tasks_closed : array());
  1983. $tasks_opened = (($tasks_opened) ? $tasks_opened : array());
  1984. $close_task = new CTask();
  1985. if ($task_id > 0) {
  1986. closeOpenedTask($task_id);
  1987. $close_task->peek($task_id) ;
  1988. $children_to_close = $close_task->getChildren();
  1989. foreach ($children_to_close as $to_close) {
  1990. closeOpenedTaskRecursive($to_close);
  1991. }
  1992. }
  1993. }
  1994. //This kludgy function echos children tasks as threads
  1995. function showtask(&$a, $level=0, $is_opened = true, $today_view = false, $hideOpenCloseLink=false
  1996. , $allowRepeat = false) {
  1997. global $AppUI, $done, $query_string, $durnTypes, $userAlloc, $showEditCheckbox;
  1998. global $tasks_opened, $tasks_closed, $user_id;
  1999. $tasks_closed = (($tasks_closed) ? $tasks_closed : array());
  2000. $tasks_opened = (($tasks_opened) ? $tasks_opened : array());
  2001. $done = (($done) ? $done : array());
  2002. $now = new CDate();
  2003. $df = $AppUI->getPref('SHDATEFORMAT');
  2004. $df .= ' ' . $AppUI->getPref('TIMEFORMAT');
  2005. $show_all_assignees = dPgetConfig('show_all_task_assignees', false);
  2006. if (!isset($done[$a['task_id']])) {
  2007. $done[$a['task_id']] = 1;
  2008. } else if (!($allowRepeat)) {
  2009. //by default, we shouldn't allow repeat displays of the same task
  2010. return;
  2011. }
  2012. $task_obj = new CTask();
  2013. $task_obj->peek($a['task_id']);
  2014. if (!($task_obj->canAccess((($user_id) ? $user_id : $AppUI->user_id)))) {
  2015. //don't show tasks that we can't access
  2016. return;
  2017. }
  2018. if ($is_opened) {
  2019. openClosedTask($a);
  2020. } else {
  2021. closeOpenedTask($a);
  2022. }
  2023. $start_date = intval($a['task_start_date']) ? new CDate($a['task_start_date']) : null;
  2024. $end_date = intval($a['task_end_date']) ? new CDate($a['task_end_date']) : null;
  2025. $last_update = ((isset($a['last_update']) && intval($a['last_update']))
  2026. ? new CDate($a['last_update']) : null);
  2027. // prepare coloured highlight of task time information
  2028. $style = '';
  2029. if ($start_date) {
  2030. if ($now->after($start_date) && $a['task_percent_complete'] == 0) {
  2031. $style = 'background-color:#ffeebb';
  2032. } else if ($now->after($start_date) && $a['task_percent_complete'] < 100) {
  2033. $style = 'background-color:#e6eedd';
  2034. }
  2035. if (!empty($end_date) && $now->after($end_date)) {
  2036. $style = 'background-color:#cc6666;color:#ffffff';
  2037. }
  2038. if (!$end_date) {
  2039. /*
  2040. ** end date calc has been moved to calcEndByStartAndDuration()-function
  2041. ** called from array_csort and tasks.php
  2042. ** perhaps this fallback if-clause could be deleted in the future,
  2043. ** didn't want to remove it shortly before the 2.0.2
  2044. */
  2045. $end_date = new CDate('0000-00-00 00:00:00');
  2046. }
  2047. if ($a['task_percent_complete'] == 100) {
  2048. $style = 'background-color:#aaddaa; color:#00000';
  2049. }
  2050. $days = $end_date->dateDiff($now);
  2051. }
  2052. $s = "\n<tr>";
  2053. // edit icon
  2054. $s .= "\n\t<td>";
  2055. $canEdit = getPermission('tasks', 'edit', $a['task_id']);
  2056. $canViewLog = getPermission('task_log', 'view', $a['task_id']);
  2057. if ($canEdit) {
  2058. $s .= ("\n\t\t".'<a href="?m=tasks&a=addedit&task_id=' . $a['task_id'] . '">'
  2059. . "\n\t\t\t".'<img src="./images/icons/pencil.gif" alt="' . $AppUI->_('Edit Task')
  2060. . '" border="0" width="12" height="12">' . "\n\t\t</a>");
  2061. }
  2062. $s .= "\n\t</td>";
  2063. // pinned
  2064. $pin_prefix = $a['task_pinned'] ? '' : 'un';
  2065. $s .= ("\n\t<td>\n\t\t" . '<a href="?m=tasks&pin=' . ($a['task_pinned']?0:1)
  2066. . '&task_id=' . $a['task_id'] . '">'
  2067. . "\n\t\t\t".'<img src="./images/icons/' . $pin_prefix . 'pin.gif" alt="'
  2068. . $AppUI->_($pin_prefix . 'pin Task') . '" border="0" width="12" height="12">'
  2069. . "\n\t\t</a>\n\t</td>");
  2070. // New Log
  2071. $s .= ("\n\t" . '<td align="center">');
  2072. if ($canViewLog && $a['task_dynamic'] != 1) {
  2073. $s .= ('<a href="?m=tasks&a=view&task_id=' . $a['task_id'] . '&tab=1">'
  2074. . $AppUI->_('Log') . '</a>');
  2075. } else {
  2076. $s .= $AppUI->_('-');
  2077. }
  2078. $s .= ('</td>');
  2079. // percent complete and priority
  2080. $s .= ("\n\t" . '<td align="right">' . intval($a['task_percent_complete']) . '%</td>'
  2081. . "\n\t" . '<td align="center" nowrap="nowrap">');
  2082. if (@$a['task_log_problem']>0) {
  2083. $s .= ('<a href="?m=tasks&a=view&task_id='
  2084. . $a['task_id'] . '&tab=0&problem=1">'
  2085. . dPshowImage('./images/icons/dialog-warning5.png', 16, 16, 'Problem', 'Problem!')
  2086. . '</a>');
  2087. } else if ($a['task_priority'] != 0) {
  2088. $s .= "\n\t\t" . dPshowImage(('./images/icons/priority' . (($a['task_priority'] > 0)
  2089. ? '+' : '-')
  2090. . abs($a['task_priority']) . '.gif'), 13, 16, '', '');
  2091. }
  2092. $s .= ((@$a['file_count'] > 0) ? '<img src="./images/clip.png" alt="F">' : '') . '</td>';
  2093. // dots
  2094. $s .= '<td width="' . (($today_view) ? '50%' : '90%') . '">';
  2095. //level
  2096. if ($level == -1) {
  2097. $s .= '...';
  2098. }
  2099. for ($y=0; $y < $level; $y++) {
  2100. $s .= ('<img src="' . (($y+1 == $level) ? './images/corner-dots.gif' : './images/shim.gif')
  2101. . '" width="16" height="12" border="0">');
  2102. }
  2103. // name link
  2104. /*
  2105. $alt = ((mb_strlen($a['task_description']) > 80)
  2106. ? (mb_substr($a['task_description'], 0, 80) . '...') : $a['task_description']);
  2107. // instead of the statement below
  2108. $alt = str_replace('"', '&quot;', $alt);
  2109. $alt = htmlspecialchars($alt);
  2110. $alt = str_replace("\r", ' ', $alt);
  2111. $alt = str_replace("\n", ' ', $alt);
  2112. */
  2113. $alt = ((!empty($a['task_description']))
  2114. ? ('onmouseover="return overlib(' . "'"
  2115. . htmlspecialchars('<div><p>' . str_replace(array("\r\n", "\n", "\r"), '</p><p>',
  2116. addslashes($a['task_description']))
  2117. , ENT_QUOTES) . '</p></div>' . "', CAPTION, '"
  2118. . $AppUI->_('Description') . "'" . ', CENTER);" onmouseout="nd();"')
  2119. : ' ');
  2120. if ($a['task_milestone'] > 0) {
  2121. $s .= ('&nbsp;<a href="./index.php?m=tasks&a=view&task_id=' . $a['task_id'] . '" '
  2122. . $alt . '>' . '<b>' . $a['task_name'] . '</b></a>'
  2123. . '<img src="./images/icons/milestone.gif" border="0"></td>');
  2124. } else if ($a['task_dynamic'] == 1) {
  2125. if (! ($today_view || $hideOpenCloseLink)) {
  2126. $s .= ('<a href="index.php' . $query_string
  2127. . (($is_opened)
  2128. ? ('&close_task_id='.$a['task_id']
  2129. . '"><img src="images/icons/collapse.gif" align="center"')
  2130. : ('&open_task_id='.$a['task_id'] . '"><img src="images/icons/expand.gif"'))
  2131. . ' border="0" /></a>');
  2132. }
  2133. $s .= ('&nbsp;<a href="./index.php?m=tasks&a=view&task_id=' . $a['task_id'] . '" '
  2134. . $alt . '><b><i>' . $a['task_name'] . '</i></b></a></td>');
  2135. } else {
  2136. $s .= ('&nbsp;<a href="./index.php?m=tasks&a=view&task_id=' . $a['task_id'] . '" '
  2137. . $alt . '>' . $a['task_name'] . '</a></td>');
  2138. }
  2139. if ($today_view) { // Show the project name
  2140. $s .= ('<td width="50%"><a href="./index.php?m=projects&a=view&project_id='
  2141. . $a['task_project'] . '">' . '<span style="padding:2px;background-color:#'
  2142. . $a['project_color_identifier'] . ';color:'
  2143. . bestColor($a['project_color_identifier']) . '">' . $a['project_name'] . '</span>'
  2144. . '</a></td>');
  2145. }
  2146. // task owner
  2147. if (! $today_view) {
  2148. $s .= ('<td nowrap="nowrap" align="center">' . '<a href="?m=admin&a=viewuser&user_id='
  2149. . $a['user_id'] . '">' . $a['user_username'] . '</a>' . '</td>');
  2150. }
  2151. // $s .= '<td nowrap="nowrap" align="center">' . $a['user_username'] . '</td>';
  2152. if (isset($a['task_assigned_users']) && ($assigned_users = $a['task_assigned_users'])) {
  2153. $a_u_tmp_array = array();
  2154. if ($show_all_assignees) {
  2155. $s .= '<td align="center">';
  2156. foreach ($assigned_users as $val) {
  2157. /*
  2158. $a_u_tmp_array[] = ('<a href="mailto:' . $val['user_email'] . '">'
  2159. . $val['user_username'] . '</a>');
  2160. */
  2161. $a_u_tmp_array[] = ('<a href="?m=admin&a=viewuser&user_id=' . $val['user_id'] . '"'
  2162. . 'title="' . $AppUI->_('Extent of Assignment') . ':'
  2163. . $userAlloc[$val['user_id']]['charge'] . '%; '
  2164. . $AppUI->_('Free Capacity') . ':'
  2165. . $userAlloc[$val['user_id']]['freeCapacity'] . '%' . '">'
  2166. . $val['user_username'] . ' (' . $val['perc_assignment'] . '%)</a>');
  2167. }
  2168. $s .= join (', ', $a_u_tmp_array) . '</td>';
  2169. } else {
  2170. $s .= ('<td align="center" nowrap="nowrap">'
  2171. .'<a href="?m=admin&a=viewuser&user_id=' . $assigned_users[0]['user_id']
  2172. . '" title="' . $AppUI->_('Extent of Assignment') . ':'
  2173. . $userAlloc[$assigned_users[0]['user_id']]['charge']. '%; '
  2174. . $AppUI->_('Free Capacity') . ':'
  2175. . $userAlloc[$assigned_users[0]['user_id']]['freeCapacity'] . '%">'
  2176. . $assigned_users[0]['user_username']
  2177. .' (' . $assigned_users[0]['perc_assignment'] .'%)</a>');
  2178. if ($a['assignee_count'] > 1) {
  2179. $s .= (' <a href="javascript: void(0);" onClick="toggle_users('
  2180. . "'users_" . $a['task_id'] . "'" . ');" title="'
  2181. . join (', ', $a_u_tmp_array) .'">(+' . ($a['assignee_count'] - 1) . ')</a>'
  2182. . '<span style="display: none" id="users_' . $a['task_id'] . '">');
  2183. $a_u_tmp_array[] = $assigned_users[0]['user_username'];
  2184. for ($i = 1, $xi = count($assigned_users); $i < $xi; $i++) {
  2185. $a_u_tmp_array[] = $assigned_users[$i]['user_username'];
  2186. $s .= ('<br /><a href="?m=admin&a=viewuser&user_id='
  2187. . $assigned_users[$i]['user_id'] . '" title="'
  2188. . $AppUI->_('Extent of Assignment') . ':'
  2189. . $userAlloc[$assigned_users[$i]['user_id']]['charge'] . '%; '
  2190. . $AppUI->_('Free Capacity') . ':'
  2191. . $userAlloc[$assigned_users[$i]['user_id']]['freeCapacity'] . '%">'
  2192. . $assigned_users[$i]['user_username'] . ' ('
  2193. . $assigned_users[$i]['perc_assignment'] . '%)</a>');
  2194. }
  2195. $s .= '</span>';
  2196. }
  2197. $s .= '</td>';
  2198. }
  2199. } else if (! $today_view) {
  2200. // No users asigned to task
  2201. $s .= '<td align="center">-</td>';
  2202. }
  2203. // duration or milestone
  2204. $s .= ('<td nowrap="nowrap" align="center" style="' . $style . '">'
  2205. . ($start_date ? $start_date->format($df) : '-') . '</td>'
  2206. . '<td align="center" nowrap="nowrap" style="' . $style . '">' . $a['task_duration']
  2207. . ' ' . $AppUI->_($durnTypes[$a['task_duration_type']]) . '</td>'
  2208. . '<td nowrap="nowrap" align="center" style="' . $style . '">'
  2209. . ($end_date ? $end_date->format($df) : '-') . '</td>');
  2210. if ($today_view) {
  2211. $s .= ('<td nowrap="nowrap" align="center" style="' . $style . '">'
  2212. . $a['task_due_in'] . '</td>');
  2213. } else if ($AppUI->isActiveModule('history') && getPermission('history', 'view')) {
  2214. $s .= ('<td nowrap="nowrap" align="center" style="' . $style.'">'
  2215. . ($last_update ? $last_update->format($df) : '-') . '</td>');
  2216. }
  2217. // Assignment checkbox
  2218. if ($showEditCheckbox) {
  2219. $s .= ("\n\t" . '<td align="center">' . '<input type="checkbox" name="selected_task['
  2220. . $a['task_id'] . ']" value="' . $a['task_id'] . '"/></td>');
  2221. }
  2222. $s .= '</tr>';
  2223. echo $s;
  2224. }
  2225. function findchild(&$tarr, $parent, $level=0) {
  2226. global $tasks_opened, $tasks_closed, $tasks_filtered, $children_of;
  2227. $tasks_closed = (($tasks_closed) ? $tasks_closed : array());
  2228. $tasks_opened = (($tasks_opened) ? $tasks_opened : array());
  2229. $level = $level+1;
  2230. foreach ($tarr as $x => $task) {
  2231. if ($task['task_parent'] == $parent && $task['task_parent'] != $task['task_id']) {
  2232. $is_opened = (!($task['task_dynamic']) || !(in_array($task['task_id'], $tasks_closed)));
  2233. //check for child
  2234. $no_children = empty($children_of[$task['task_id']]);
  2235. showtask($task, $level, $is_opened, false, $no_children);
  2236. if ($is_opened && !($no_children)) {
  2237. /*
  2238. * Yes, this is stupid, but there was previously a bug where if you had
  2239. * two dynamic tasks at the same level and the child of a dynamic task,
  2240. * they would only both display if the first one was closed. The moment
  2241. * you opened the first one, the second would disappear.
  2242. *
  2243. * There is something screwy happening in this function in the pass by
  2244. * reference. I suspect it's a PHP4 vs PHP5 oddity.
  2245. */
  2246. $tmp = $tarr;
  2247. findchild($tmp, $task['task_id'], $level);
  2248. }
  2249. }
  2250. }
  2251. }
  2252. /*
  2253. array_csort($data_array [,$col, $order, $type [, $col, $order, $type [...]]]);
  2254. $data_array - multi-dimensional array of query results to sort
  2255. $col - data table "column" to sort by
  2256. $order - SORT_ASC or SORT_DESC flag values
  2257. $type - SORT_REGULAR, SORT_NUMERIC, or SORT_STRING flag values
  2258. ...any number of column, order, and type values can be passed
  2259. but they must be specified in that order and all sets except
  2260. the last must be defined fully
  2261. Examples -
  2262. valid:
  2263. array_csort($data_array,$col, $order, $type, $col2, $order2, $type2);
  2264. array_csort($data_array,$col, $order, $type, $col2, $order2,);
  2265. array_csort($data_array,$col, $order, $type, $col2, $type2);
  2266. array_csort($data_array);
  2267. invalid:
  2268. array_csort($data_array,$col, $type, $col2, $order2, $type2);
  2269. */
  2270. function array_csort() {
  2271. $args = func_get_args();
  2272. $marray = array_shift($args);
  2273. if (empty($marray)) {
  2274. return array();
  2275. }
  2276. $i = 0;
  2277. $msortline = 'return(array_multisort(';
  2278. $sortarr = array();
  2279. foreach ($args as $arg) {
  2280. if ($i % 3) {
  2281. $msortline .= $arg . ', ';
  2282. } else {
  2283. foreach ($marray as $j => $item) {
  2284. /* we have to calculate the end_date via start_date+duration for
  2285. ** end='0000-00-00 00:00:00' before sorting, see mantis #1509:
  2286. ** Task definition writes the following to the DB:
  2287. ** A without start date: start = end = NULL
  2288. ** B with start date and empty end date: start = startdate,
  2289. end = '0000-00-00 00:00:00'
  2290. ** C start + end date: start= startdate, end = end date
  2291. ** A the end_date for the middle task (B) is ('dynamically') calculated on display
  2292. ** via start_date+duration, it may be that the order gets wrong due to the fact
  2293. ** that sorting has taken place _before_.
  2294. */
  2295. if ($item['task_end_date'] == '0000-00-00 00:00:00') {
  2296. $item['task_end_date'] = calcEndByStartAndDuration($marray[$j]);
  2297. }
  2298. $sortarr[$i][$j] = $marray[$j][$arg];
  2299. }
  2300. $msortline .= '$sortarr[' . $i . '], ';
  2301. }
  2302. $i++;
  2303. }
  2304. $msortline .= '$marray));';
  2305. eval($msortline);
  2306. return $marray;
  2307. }
  2308. /*
  2309. ** Calc End Date via Startdate + Duration
  2310. ** @param array task A DB row from the earlier fetched tasklist
  2311. ** @return string Return calculated end date in MySQL-TIMESTAMP format
  2312. */
  2313. function calcEndByStartAndDuration($task) {
  2314. $end_date = new CDate($task['task_start_date']);
  2315. $end_date->addSeconds(@$task['task_duration'] * $task['task_duration_type'] * SEC_HOUR);
  2316. return $end_date->format(FMT_DATETIME_MYSQL);
  2317. }
  2318. function sort_by_item_title($title, $item_name, $item_type) {
  2319. global $AppUI, $project_id, $task_id, $min_view;
  2320. global $task_sort_item1, $task_sort_type1, $task_sort_order1;
  2321. global $task_sort_item2, $task_sort_type2, $task_sort_order2;
  2322. if ($task_sort_item2 == $item_name) {
  2323. $item_order = $task_sort_order2;
  2324. }
  2325. if ($task_sort_item1 == $item_name) {
  2326. $item_order = $task_sort_order1;
  2327. }
  2328. //Hack for Problem Log/Priority Sorting
  2329. if ($item_name == 'task_log_problem_priority' && $task_sort_item2 == 'task_priority') {
  2330. $item_order = $task_sort_order2;
  2331. }
  2332. if (isset($item_order)) {
  2333. echo ('<img src="./images/arrow-' . (($item_order == SORT_ASC) ? 'up' : 'down')
  2334. . '.gif" width="11" height="11">');
  2335. } else {
  2336. $item_order = SORT_DESC;
  2337. }
  2338. /* flip the sort order for the link */
  2339. $item_order = ($item_order == SORT_ASC) ? SORT_DESC : SORT_ASC;
  2340. echo ('<a href="./index.php?');
  2341. foreach ($_GET as $var => $val) {
  2342. if (!(in_array($var, array('task_sort_item1', 'task_sort_type1', 'task_sort_order1',
  2343. 'task_sort_item2', 'task_sort_type2', 'task_sort_order2')))) {
  2344. echo ((($not_first) ? '&' : '') . $var . '=' . $val);
  2345. $not_first = 1;
  2346. }
  2347. }
  2348. if ($item_name == 'task_log_problem_priority') {
  2349. echo '&task_sort_item1=task_log_problem';
  2350. echo '&task_sort_type1=' . $item_type;
  2351. echo '&task_sort_order1=' . SORT_DESC;
  2352. echo '&task_sort_item2=task_priority';
  2353. echo '&task_sort_type2=' . $item_type;
  2354. echo '&task_sort_order2=' . $item_order;
  2355. } else {
  2356. echo '&task_sort_item1=' . $item_name;
  2357. echo '&task_sort_type1=' . $item_type;
  2358. echo '&task_sort_order1=' . $item_order;
  2359. if ((($task_sort_item1 && $task_sort_item1 != $item_name)
  2360. || $task_sort_item2) && $task_sort_item2 != 'task_priority') {
  2361. $item_num = (($task_sort_item1 == $item_name) ? '2' : '1');
  2362. echo '&task_sort_item2=' . ${'task_sort_item' . $item_num};
  2363. echo '&task_sort_type2=' . ${'task_sort_type' . $item_num};
  2364. echo '&task_sort_order2=' . ${'task_sort_order' . $item_num};
  2365. }
  2366. }
  2367. echo '" class="hdr">';
  2368. echo $AppUI->_($title);
  2369. echo '</a>';
  2370. }
  2371. ?>