PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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_

Large files files are truncated, but you can click here to view the full file