control.php 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393
  1. <?php
  2. /**
  3. * The control file of task module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package task
  9. * @version $Id: control.php 5106 2013-07-12 01:28:54Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. class task extends control
  13. {
  14. /**
  15. * Construct function, load model of project and story modules.
  16. *
  17. * @access public
  18. * @return void
  19. */
  20. public function __construct($module = '', $method = '')
  21. {
  22. parent::__construct($module, $method);
  23. $this->loadModel('project');
  24. $this->loadModel('execution');
  25. $this->loadModel('story');
  26. $this->loadModel('tree');
  27. }
  28. /**
  29. * 创建一个任务。
  30. * Create a task.
  31. *
  32. * @param int $executionID
  33. * @param int $storyID
  34. * @param int $moduleID
  35. * @param int $taskID
  36. * @param int $todoID
  37. * @param string $cardPosition
  38. * @param int $bugID
  39. * @access public
  40. * @return void
  41. */
  42. public function create($executionID = 0, $storyID = 0, $moduleID = 0, $taskID = 0, $todoID = 0, $cardPosition = '', $bugID = 0)
  43. {
  44. /* Analytic parameter. */
  45. $cardPosition = str_replace(array(',', ' '), array('&', ''), $cardPosition);
  46. parse_str($cardPosition, $output);
  47. $this->session->set('executionStoryList', $this->app->getURI(true), 'execution');
  48. /* Set menu and get execution information. */
  49. $executionID = $this->taskZen->setMenu($executionID);
  50. $execution = $this->execution->getById($executionID);
  51. /* If you do not have permission to access any execution, go to the create execution page. */
  52. if(!$this->execution->checkPriv($executionID)) $this->locate($this->createLink('execution', 'create'));
  53. /* Check whether the execution has permission to create tasks. */
  54. if($this->taskZen->isLimitedInExecution($executionID)) return $this->send(array('load' => array('locate' => $this->createLink('execution', 'task', "executionID={$executionID}"), 'alert' => sprintf($this->lang->task->createDenied, $execution->multiple ? $this->lang->executionCommon : $this->lang->projectCommon))));
  55. /* Submit the data process after create the task form. */
  56. if(!empty($_POST))
  57. {
  58. $taskData = $this->taskZen->buildTaskForCreate($this->post->execution ? (int)$this->post->execution : $executionID);
  59. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  60. $this->dao->begin();
  61. if($this->post->type == 'test' && $this->post->selectTestStory == 'on')
  62. {
  63. /* Prepare to create the data for the test subtask and to check the data format. */
  64. $testTasks = $this->taskZen->buildTestTasksForCreate($taskData->execution);
  65. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  66. $taskIdList = $this->task->createTaskOfTest($taskData, $testTasks);
  67. }
  68. elseif($this->post->type == 'affair')
  69. {
  70. $taskIdList = $this->task->createTaskOfAffair($taskData, is_array($this->post->assignedTo) ? $this->post->assignedTo : array($this->post->assignedTo));
  71. }
  72. elseif($this->post->multiple)
  73. {
  74. $teamData = form::data($this->config->task->form->team->create)->get();
  75. $taskIdList = $this->task->createMultiTask($taskData, $teamData);
  76. }
  77. else
  78. {
  79. $taskIdList = $this->task->create($taskData);
  80. }
  81. if(dao::isError())
  82. {
  83. $this->dao->rollBack();
  84. return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  85. }
  86. /* Update other data related to the task after it is created. */
  87. $columnID = isset($output['columnID']) ? (int)$output['columnID'] : 0;
  88. $taskIdList = (array)$taskIdList;
  89. $taskData->id = current($taskIdList);
  90. $this->task->afterCreate($taskData, $taskIdList, $bugID, $todoID);
  91. if($this->post->lane) $this->task->updateKanbanData($taskData->execution, $taskIdList, (int)$this->post->lane, $columnID);
  92. setCookie("lastTaskModule", (string)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true);
  93. if(!empty($_POST['fileList']))
  94. {
  95. $fileList = $this->post->fileList;
  96. if($fileList) $fileList = json_decode($fileList, true);
  97. $this->loadModel('file')->saveDefaultFiles($fileList, 'task', $taskIdList);
  98. }
  99. /* Get the information returned after a task is created. */
  100. $response = $this->taskZen->responseAfterCreate($taskData, $execution, $this->post->after ? $this->post->after : '');
  101. return $this->send($response);
  102. }
  103. /* Shows the variables needed to create the task page. */
  104. $this->taskZen->assignCreateVars($execution, $storyID, $moduleID, $taskID, $todoID, $bugID, $output, $cardPosition);
  105. }
  106. /**
  107. * 批量创建任务。
  108. * Batch create tasks.
  109. *
  110. * @param int $executionID
  111. * @param int $storyID
  112. * @param int $moduleID
  113. * @param int $taskID
  114. * @param string $cardPosition
  115. * @access public
  116. * @return void
  117. */
  118. public function batchCreate($executionID, $storyID = 0, $moduleID = 0, $taskID = 0, $cardPosition = '')
  119. {
  120. /* Analytic parameter. */
  121. $cardPosition = str_replace(array(',', ' '), array('&', ''), $cardPosition);
  122. parse_str($cardPosition, $output);
  123. $this->taskZen->setMenu($executionID);
  124. $execution = $this->execution->getById($executionID);
  125. /* Check whether the execution has permission to create tasks. */
  126. if($this->taskZen->isLimitedInExecution($executionID)) return $this->send(array('load' => array('locate' => $this->createLink('execution', 'task', "executionID={$executionID}"), 'alert' => sprintf($this->lang->task->createDenied, $execution->multiple ? $this->lang->executionCommon : $this->lang->projectCommon))));
  127. if(!empty($_POST))
  128. {
  129. $parent = $taskID > 0 ? $this->task->fetchById($taskID) : null;
  130. /* Process the request data for the batch create tasks. */
  131. $taskData = $this->taskZen->buildTasksForBatchCreate($execution, $taskID, $output);
  132. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  133. $taskIdList = $this->task->batchCreate($taskData, $output);
  134. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  135. /* Update other data related to the task after it is created. */
  136. $this->task->afterBatchCreate($taskIdList, $parent);
  137. if(!isset($output['laneID']) || !isset($output['columnID'])) $this->loadModel('kanban')->updateLane($executionID, 'task');
  138. $response = $this->taskZen->responseAfterbatchCreate($taskIdList, $execution);
  139. return $this->send($response);
  140. }
  141. $this->taskZen->buildBatchCreateForm($execution, $storyID, $moduleID, $taskID, $output);
  142. }
  143. /**
  144. * 编辑一个任务。
  145. * Edit a task.
  146. *
  147. * @param int $taskID
  148. * @param string $from ''|taskkanban
  149. * @access public
  150. * @return void
  151. */
  152. public function edit($taskID, $from = '')
  153. {
  154. $this->taskZen->commonAction($taskID);
  155. if(!empty($_POST))
  156. {
  157. /* Prepare and check data. */
  158. $task = form::data($this->config->task->form->edit)->add('id', $taskID)->get();
  159. $task = $this->taskZen->buildTaskForEdit($task);
  160. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  161. /* For team task. */
  162. $teamData = $this->post->team ? form::data($this->config->task->form->team->edit)->get() : new stdclass();
  163. /* Update task. */
  164. $changes = $this->task->update($task, $teamData);
  165. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  166. /* Execute hooks and synchronize the status of related objects. */
  167. $message = $this->executeHooks($taskID);
  168. $message = $message ?: $this->lang->saveSuccess;
  169. if($task->status == 'doing') $this->loadModel('common')->syncPPEStatus($taskID);
  170. $response = $this->taskZen->responseAfterEdit($taskID, $from, $changes, $message);
  171. return $this->send($response);
  172. }
  173. $this->taskZen->buildEditForm($taskID);
  174. }
  175. /**
  176. * 批量编辑任务。
  177. * Batch edit tasks.
  178. *
  179. * @param int $executionID
  180. * @param string $from
  181. * @access public
  182. * @return void
  183. */
  184. public function batchEdit($executionID = 0, $from = '')
  185. {
  186. $this->taskZen->setMenu($executionID);
  187. if($this->post->name)
  188. {
  189. /* Batch edit tasks. */
  190. $taskData = form::batchData()->get();
  191. $oldTasks = $taskData ? $this->task->getByIdList(array_keys($taskData)) : array();
  192. $taskData = $this->taskZen->buildTasksForBatchEdit($taskData, $oldTasks);
  193. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  194. $allChanges = $this->task->batchUpdate($taskData);
  195. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  196. $this->task->afterBatchUpdate($taskData, $oldTasks);
  197. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  198. $response = $this->taskZen->responseAfterBatchEdit($allChanges);
  199. return $this->send($response);
  200. }
  201. if(!$this->post->taskIdList)
  202. {
  203. $url = !empty($this->session->taskList) ? $this->session->taskList : $this->createLink('execution', 'all');
  204. $this->locate($url);
  205. }
  206. if($this->app->tab == 'my' && $this->config->vision == 'rnd')
  207. {
  208. $this->loadModel('my');
  209. if($from == 'work' || $from == 'contribute')
  210. {
  211. $this->lang->my->menu->{$from}['subModule'] = 'task';
  212. $this->lang->my->menu->{$from}['subMenu']->task['subModule'] = 'task';
  213. }
  214. }
  215. $this->taskZen->assignBatchEditVars($executionID);
  216. }
  217. /**
  218. * 指派任务。
  219. * Update assign of task.
  220. *
  221. * @param int $executionID
  222. * @param int $taskID
  223. * @param string $from ''|taskkanban
  224. * @access public
  225. * @return void
  226. */
  227. public function assignTo($executionID, $taskID, $from = '')
  228. {
  229. $this->taskZen->commonAction($taskID);
  230. $task = $this->task->getByID($taskID);
  231. if(!empty($task->team) && $task->mode == 'multi' && strpos('done,cancel,closed', $task->status) === false)
  232. {
  233. echo $this->fetch('task', 'manageTeam', "executionID=$executionID&taskID=$taskID&from=$from");
  234. return;
  235. }
  236. if(!empty($_POST))
  237. {
  238. $task = form::data($this->config->task->form->assign, $taskID)->add('id', $taskID)->get();
  239. $this->task->assign($task);
  240. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  241. $message = $this->executeHooks($taskID);
  242. $message = $message ?: $this->lang->saveSuccess;
  243. $response = $this->taskZen->responseAfterAssignTo($taskID, $from, $message);
  244. return $this->send($response);
  245. }
  246. $this->view->task = $task;
  247. $this->view->title = $this->view->execution->name . $this->lang->hyphen . $this->lang->task->assign;
  248. $this->taskZen->buildUsersAndMembersToForm($executionID, $taskID);
  249. $this->display();
  250. }
  251. /**
  252. * 批量更改任务所属模块。
  253. * Batch change the module of task.
  254. *
  255. * @param int $moduleID
  256. * @access public
  257. * @return void
  258. */
  259. public function batchChangeModule($moduleID)
  260. {
  261. if($this->post->taskIdList)
  262. {
  263. $taskIdList = array_unique($this->post->taskIdList);
  264. $this->task->batchChangeModule($taskIdList, $moduleID);
  265. if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
  266. }
  267. if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
  268. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true));
  269. }
  270. /**
  271. * 批量指派任务。
  272. * Batch update assign of task.
  273. *
  274. * @param int $executionID
  275. * @param string $assignedTo
  276. * @access public
  277. * @return void
  278. */
  279. public function batchAssignTo($executionID, $assignedTo)
  280. {
  281. if($this->post->taskIdList)
  282. {
  283. $taskData = $this->taskZen->buildTasksForBatchAssignTo($this->post->taskIdList, $assignedTo);
  284. foreach($taskData as $task)
  285. {
  286. $this->task->assign($task);
  287. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  288. }
  289. if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
  290. }
  291. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('execution', 'task', "executionID={$executionID}")));
  292. }
  293. /**
  294. * 查看一个任务。View a task.
  295. *
  296. * @param int $taskID
  297. * @access public
  298. * @return void
  299. */
  300. public function view($taskID)
  301. {
  302. $task = $this->task->getById($taskID, true, $vision = 'all'); // TODO: $vision is for compatibling with viewing drill data.
  303. if(!$task)
  304. {
  305. if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'fail', 'code' => 404, 'message' => '404 Not found'));
  306. return $this->sendError($this->lang->notFound, $this->config->vision == 'lite' ? $this->createLink('project', 'index') : $this->createLink('execution', 'all'));
  307. }
  308. if(!$this->loadModel('common')->checkPrivByObject('execution', $task->execution)) return $this->sendError($this->lang->execution->accessDenied, $this->createLink('execution', 'all'));
  309. /* 为视图设置常用的公共变量和设置菜单为任务所属执行. Set common variables to view and set menu to the execution of the task. */
  310. $this->session->project = $task->project;
  311. $this->taskZen->commonAction($taskID, $vision = 'all');
  312. $this->session->set('executionList', $this->app->getURI(true), 'execution'); // This allow get var of session as `$_SESSION['app-execution']['executionList']`.
  313. $execution = $this->view->execution ?? $this->execution->getById($task->execution);
  314. $isModalOrApi = helper::isAjaxRequest('modal') || (defined('RUN_MODE') && RUN_MODE == 'api');
  315. if(!$isModalOrApi && $execution->type == 'kanban')
  316. {
  317. helper::setcookie('taskToOpen', (string)$taskID);
  318. return $this->send(array('load' => $this->createLink('execution', 'kanban', "executionID=$execution->id")));
  319. }
  320. /* 检查和设置任务的相关信息如果它来自缺陷或需求。Check and set related info if the task came from bug or story. */
  321. if($task->fromBug != 0)
  322. {
  323. $bug = $this->loadModel('bug')->getById($task->fromBug);
  324. $task->bugSteps = '';
  325. if($bug)
  326. {
  327. $task->bugSteps = $this->loadModel('file')->setImgSize($bug->steps);
  328. foreach($bug->files as $file) $task->files[] = $file;
  329. }
  330. $this->view->fromBug = $bug;
  331. if($this->app->tab == 'qa') $this->view->productID = $bug->product;
  332. }
  333. else
  334. {
  335. $story = $this->story->getById($task->story, $task->storyVersion);
  336. $task->storySpec = empty($story) ? '' : $this->loadModel('file')->setImgSize($story->spec);
  337. $task->storyVerify = empty($story) ? '' : $this->loadModel('file')->setImgSize($story->verify);
  338. $task->storyFiles = zget($story, 'files', array());
  339. $task->storyTitle = !empty($story) ? $story->title : '';
  340. }
  341. if($task->team) $this->lang->task->assign = $this->lang->task->transfer;
  342. $this->lang->task->statusList['changed'] = $this->lang->task->storyChange;
  343. $this->view->title = "TASK#$task->id $task->name / $execution->name";
  344. $this->view->execution = $execution;
  345. $this->view->task = $task;
  346. $this->view->actions = $this->loadModel('action')->getList('task', $taskID);
  347. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  348. $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('task', $taskID);
  349. $this->view->product = $this->tree->getProduct($task->module);
  350. $this->view->modulePath = $this->tree->getParents($task->module);
  351. $this->view->linkMRTitles = $this->loadModel('mr')->getLinkedMRPairs($taskID, 'task');
  352. $this->view->linkCommits = $this->loadModel('repo')->getCommitsByObject($taskID, 'task');
  353. $this->view->linkedBugs = $this->loadModel('bug')->getLinkedBugsByTaskID($taskID);
  354. $this->view->hasGitRepo = $this->taskZen->checkGitRepo($execution->id);
  355. $this->display();
  356. }
  357. /**
  358. * 确认需求变更。
  359. * Confirm story change.
  360. *
  361. * @param int $taskID
  362. * @access public
  363. * @return void
  364. */
  365. public function confirmStoryChange($taskID)
  366. {
  367. $this->task->confirmStoryChange($taskID);
  368. $message = $this->executeHooks($taskID);
  369. return $this->send(array('result' => 'success', 'message' => $message ?: $this->lang->saveSuccess, 'load' => true));
  370. }
  371. /**
  372. * 开始一个任务。
  373. * Start a task.
  374. *
  375. * @param int $taskID
  376. * @param string $cardPosition
  377. * @access public
  378. * @return void
  379. */
  380. public function start($taskID, $cardPosition = '')
  381. {
  382. /* Analytic parameter. */
  383. $cardPosition = str_replace(array(',', ' '), array('&', ''), $cardPosition);
  384. parse_str($cardPosition, $output);
  385. /* Common actions of task module and task. */
  386. $this->taskZen->commonAction($taskID);
  387. $task = $this->task->getById($taskID);
  388. $currentTeam = !empty($task->team) ? $this->task->getTeamByAccount($task->team) : '';
  389. /* Submit the data process after start the task form. */
  390. if(!empty($_POST))
  391. {
  392. /* Prepare the data information before start the task. */
  393. $taskData = $this->taskZen->buildTaskForStart($task);
  394. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  395. /* Record task effort. */
  396. $effort = $this->buildEffortForStart($task, $taskData);
  397. if($this->post->comment) $effort->work = $this->post->comment;
  398. if($effort->consumed > 0) $effortID = $this->task->addTaskEffort($effort);
  399. if($task->mode == 'linear' && !empty($effortID)) $this->task->updateEffortOrder($effortID, $currentTeam->order);
  400. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  401. /* Start a task. */
  402. $changes = $this->task->start($task, $taskData);
  403. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  404. $message = $this->executeHooks($task->id);
  405. $message = $message ?: $this->lang->saveSuccess;
  406. /* Update other data related to the task after it is started. */
  407. $result = $this->task->afterStart($task, $changes, (float)$this->post->left, $output, $message);
  408. if(is_array($result)) $this->send($result);
  409. /* Get the information returned after a task is started. */
  410. $from = zget($output, 'from');
  411. $response = $this->taskZen->responseAfterChangeStatus($task, $from, $message);
  412. return $this->send($response);
  413. }
  414. /* Shows the variables needed to start the task page. */
  415. $assignedTo = empty($task->assignedTo) ? $this->app->user->account : $task->assignedTo;
  416. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->task->start;
  417. $this->view->assignedTo = !empty($task->team) ? $this->task->getAssignedTo4Multi($task->team, $task) : $assignedTo;
  418. $this->view->canRecordEffort = $this->task->canOperateEffort($task);
  419. $this->view->currentTeam = $currentTeam;
  420. $this->taskZen->buildUsersAndMembersToForm($task->execution, $taskID);
  421. $this->display();
  422. }
  423. /**
  424. * 任务查看工时/新增工时的方法。
  425. * View and add task's workhour.
  426. *
  427. * @param int $taskID
  428. * @param string $from
  429. * @param string $orderBy
  430. * @access public
  431. * @return void
  432. */
  433. public function recordWorkhour($taskID, $from = '', $orderBy = '')
  434. {
  435. $this->taskZen->commonAction($taskID);
  436. if(!empty($_POST))
  437. {
  438. $workhour = form::batchData($this->config->task->form->recordWorkhour)->get();
  439. $changes = $this->task->recordWorkhour($taskID, $workhour);
  440. if(dao::isError()) return $this->send(array('message' => dao::getError(), 'result' => 'fail'));
  441. /* 更新任务对应的项目集、项目和执行的状态。*/
  442. $this->loadModel('common')->syncPPEStatus($taskID);
  443. $task = $this->task->getById($taskID);
  444. $response = $this->taskZen->responseAfterRecord($task, $changes, $from);
  445. return $this->send($response);
  446. }
  447. $this->taskZen->buildRecordForm($taskID, $from, $orderBy);
  448. }
  449. /**
  450. * 编辑一条日志。
  451. * Edit a effort.
  452. *
  453. * @param int $effortID
  454. * @access public
  455. * @return void
  456. */
  457. public function editEffort($effortID)
  458. {
  459. $effort = $this->task->getEffortByID($effortID);
  460. if(!empty($_POST))
  461. {
  462. if($this->config->edition != 'open') $oldEffort = $this->loadModel('effort')->fetchByID($effortID);
  463. $this->lang->task->consumed = $this->lang->task->currentConsumed;
  464. $formData = form::data($this->config->task->form->editEffort)->add('id', $effortID)->get();
  465. $changes = $this->task->updateEffort($formData);
  466. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  467. $actionID = $this->loadModel('action')->create('task', $effort->objectID, 'EditEffort', $this->post->work);
  468. $this->action->logHistory($actionID, $changes);
  469. if($this->config->edition != 'open')
  470. {
  471. $effort = $this->effort->fetchByID($effortID);
  472. $actionID = $this->action->create('effort', $effortID, 'edited');
  473. $this->action->logHistory($actionID, common::createChanges($oldEffort, $effort));
  474. }
  475. $url = $this->createLink($this->app->rawModule, 'recordWorkhour', "taskID={$effort->objectID}");
  476. return $this->send(array(
  477. 'result' => 'success',
  478. 'message' => $this->lang->saveSuccess,
  479. 'callback' => "loadModal('$url')"
  480. ));
  481. }
  482. $this->view->title = $this->lang->task->editEffort;
  483. $this->view->effort = $effort;
  484. $this->view->task = $this->task->getByID($effort->objectID);
  485. $this->display();
  486. }
  487. /**
  488. * 删除任务工时。Delete the work hour from the task.
  489. *
  490. * @param int $effortID
  491. * @param string $confirm
  492. * @access public
  493. * @return void
  494. */
  495. public function deleteWorkhour($effortID, $confirm = 'no')
  496. {
  497. $effort = $this->task->getEffortByID($effortID);
  498. $taskID = $effort->objectID;
  499. $task = $this->task->getById($taskID);
  500. /* Show a confirm message if the task has no consumed effort. */
  501. if($confirm == 'no' && $task->consumed - $effort->consumed == 0)
  502. {
  503. $formUrl = $this->createLink('task', 'deleteWorkhour', "effortID=$effortID&confirm=yes");
  504. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm('{$this->lang->task->confirmDeleteLastEffort}').then((res) => {if(res) $.ajaxSubmit({url: '$formUrl'});});"));
  505. }
  506. $changes = $this->task->deleteWorkhour($effortID);
  507. if(dao::isError() || empty($changes)) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  508. $actionID = $this->loadModel('action')->create('task', $taskID, 'DeleteEstimate');
  509. $this->action->logHistory($actionID, $changes);
  510. /* Delete task burn. */
  511. if($this->config->edition != 'open')
  512. {
  513. $this->dao->update(TABLE_BURN)
  514. ->set("`consumed` = `consumed` - {$effort->consumed}")
  515. ->where('task')->eq($taskID)
  516. ->andWhere('date')->eq($effort->date)
  517. ->exec();
  518. }
  519. /* The task status will be set to wait as the consumed effort is set to 0. */
  520. if($task->consumed - $effort->consumed == 0) $this->action->create('task', $taskID, 'Adjusttasktowait');
  521. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'callback' => "loadModal('" . inLink('recordworkhour', "taskID={$taskID}") . "', '#modal-record-hours-task-{$taskID}')"));
  522. }
  523. /**
  524. * 完成任务操作。
  525. * Finish a task.
  526. *
  527. * @param int $taskID
  528. * @param string $extra
  529. * @access public
  530. * @return void
  531. */
  532. public function finish($taskID, $extra = '')
  533. {
  534. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  535. parse_str($extra, $output);
  536. $this->taskZen->commonAction($taskID);
  537. $task = $this->view->task;
  538. $currentTeam = !empty($task->team) ? $this->task->getTeamByAccount($task->team) : '';
  539. $canRecordEffort = $this->task->canOperateEffort($task);
  540. if(!$canRecordEffort)
  541. {
  542. $deniedNotice = sprintf($this->lang->task->deniedNotice, $this->lang->task->teamMember, $this->lang->task->finish);
  543. if($task->assignedTo != $this->app->user->account && $task->mode == 'linear') $deniedNotice = sprintf($this->lang->task->deniedNotice, $task->assignedToRealName, $this->lang->task->finish);
  544. $this->view->deniedNotice = $deniedNotice;
  545. }
  546. if(!empty($_POST))
  547. {
  548. if(!$canRecordEffort) return $this->send(array('result' => 'fail', 'message' => $deniedNotice));
  549. $taskData = $this->taskZen->buildTaskForFinish($task);
  550. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  551. if($task->mode == 'linear' && $taskData->finishedBy != $task->assignedTo) return $this->send(array('result' => 'fail', 'message' => $this->lang->task->error->finishedBy));
  552. /* Get and record estimate for task. */
  553. $effort = $this->taskZen->buildEffortForFinish($task, $taskData);
  554. if($effort->consumed > 0) $effortID = $this->task->addTaskEffort($effort);
  555. if($task->mode == 'linear' && !empty($effortID)) $this->task->updateEffortOrder($effortID, isset($currentTeam->order) ? $currentTeam->order : 1);
  556. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  557. $changes = $this->task->finish($task, $taskData);
  558. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  559. $message = $this->executeHooks($taskID);
  560. $message = $message ?: $this->lang->saveSuccess;
  561. /* Update other data related to the task after it is started. */
  562. $result = $this->task->afterStart($task, $changes, 0, $output, $message);
  563. if(is_array($result)) return $this->send($result);
  564. if($taskData->status == 'done') $this->loadModel('score')->create('task', 'finish', $task->id);
  565. if($this->config->edition != 'open' && $task->feedback) $this->loadModel('feedback')->updateStatus('task', $task->feedback, $taskData->status, $task->status, $task->id);
  566. /* Get the information returned after a task is started. */
  567. $from = zget($output, 'from');
  568. $response = $this->taskZen->responseAfterChangeStatus($task, $from, $message);
  569. return $this->send($response);
  570. }
  571. $task = $this->view->task;
  572. $task->nextBy = $task->openedBy;
  573. if(!empty($task->team))
  574. {
  575. $task->nextBy = $this->task->getAssignedTo4Multi($task->team, $task, 'next');
  576. $task->myConsumed = zget($currentTeam, 'consumed', 0);
  577. }
  578. $this->taskZen->buildUsersAndMembersToForm($task->execution, $taskID);
  579. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->task->finish;
  580. $this->view->canRecordEffort = $canRecordEffort;
  581. $this->display();
  582. }
  583. /**
  584. * 暂停任务。
  585. * Pause task.
  586. *
  587. * @param int $taskID
  588. * @param string $extra
  589. * @access public
  590. * @return void
  591. */
  592. public function pause($taskID, $extra = '')
  593. {
  594. $this->taskZen->commonAction($taskID);
  595. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  596. parse_str($extra, $output);
  597. if(!empty($_POST))
  598. {
  599. $oldTask = $this->task->getByID($taskID);
  600. /* Init task data. */
  601. $task = form::data($this->config->task->form->pause, $taskID)->add('id', $taskID)->get();
  602. /* Pause task. */
  603. $changes = $this->task->pause($task, $output);
  604. if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
  605. /* Record log. */
  606. if($this->post->comment != '' || !empty($changes))
  607. {
  608. $actionID = $this->loadModel('action')->create('task', $taskID, 'Paused', $this->post->comment);
  609. $this->action->logHistory($actionID, $changes);
  610. }
  611. $message = $this->executeHooks($taskID);
  612. $message = $message ?: $this->lang->saveSuccess;
  613. /* Get response after the suspended task. */
  614. $from = zget($output, 'from');
  615. $response = $this->taskZen->responseAfterChangeStatus($oldTask, $from, $message);
  616. return $this->send($response);
  617. }
  618. /* Show the variables associated. */
  619. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->task->pause;
  620. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  621. $this->display();
  622. }
  623. /**
  624. * 重新开始一个任务。
  625. * Restart a task.
  626. *
  627. * @param int $taskID
  628. * @param string $from
  629. * @access public
  630. * @return void
  631. */
  632. public function restart($taskID, $from = '')
  633. {
  634. /* Common actions of task module and task. */
  635. $this->taskZen->commonAction($taskID);
  636. $task = $this->task->getById($taskID);
  637. $account = $this->app->user->account;
  638. $currentTeam = !empty($task->team) ? $this->task->getTeamByAccount($task->team, $account, array()) : '';
  639. if(!empty($_POST))
  640. {
  641. /* Prepare the data information before restart the task. */
  642. $taskData = $this->taskZen->buildTaskForStart($task);
  643. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  644. /* Record task effort. */
  645. $effort = $this->taskZen->buildEffortForStart($task, $taskData);
  646. if($this->post->comment) $effort->work = $this->post->comment;
  647. if($effort->consumed > 0) $effortID = $this->task->addTaskEffort($effort);
  648. if($task->mode == 'linear' && !empty($effortID)) $this->task->updateEffortOrder($effortID, $currentTeam->order);
  649. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  650. /* Restart a task. */
  651. $changes = $this->task->start($task, $taskData);
  652. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  653. $message = $this->executeHooks($taskID);
  654. $message = $message ?: $this->lang->saveSuccess;
  655. $result = $this->task->afterStart($task, $changes, (float)$this->post->left, array(), $message);
  656. if(is_array($result)) $this->send($result);
  657. $response = $this->taskZen->responseAfterChangeStatus($task, $from, $message);
  658. return $this->send($response);
  659. }
  660. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->task->restart;
  661. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  662. $this->view->members = $this->loadModel('user')->getTeamMemberPairs($task->execution, 'execution', 'nodeleted');
  663. $this->view->assignedTo = $task->assignedTo == '' ? $this->app->user->account : $task->assignedTo;
  664. $this->view->canRecordEffort = $this->task->canOperateEffort($task);
  665. $this->view->currentTeam = $currentTeam;
  666. $this->display();
  667. }
  668. /**
  669. * 关闭一个任务。
  670. * Close a task.
  671. *
  672. * @param int $taskID
  673. * @param string $cardPosition
  674. * @access public
  675. * @return void
  676. */
  677. public function close($taskID, $cardPosition = '')
  678. {
  679. $cardPosition = str_replace(array(',', ' '), array('&', ''), $cardPosition);
  680. parse_str($cardPosition, $output);
  681. $this->taskZen->commonAction($taskID);
  682. if(!empty($_POST))
  683. {
  684. $task = $this->task->getById($taskID);
  685. /* Prepare the data information before start the task. */
  686. $taskData = $this->taskZen->buildTaskForClose($task);
  687. $result = $this->task->close($task, $taskData, $output);
  688. if(!$result) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  689. if(is_array($result)) return $this->send($result);
  690. $message = $this->executeHooks($taskID);
  691. $message = $message ?: $this->lang->saveSuccess;
  692. /* Get the information returned after a task is started. */
  693. $from = zget($output, 'from');
  694. $response = $this->taskZen->responseAfterChangeStatus($task, $from, $message);
  695. return $this->send($response);
  696. }
  697. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->task->finish;
  698. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  699. $this->display();
  700. }
  701. /**
  702. * 批量取消任务。
  703. * Batch cancel tasks.
  704. *
  705. * @access public
  706. * @return void
  707. */
  708. public function batchCancel()
  709. {
  710. $taskIdList = $this->post->taskIdList ? array_unique($this->post->taskIdList) : array();
  711. if($taskIdList) $taskIdList = array_filter(array_unique($taskIdList));
  712. if($taskIdList)
  713. {
  714. foreach($taskIdList as $taskID)
  715. {
  716. $task = $this->task->fetchById((int)$taskID);
  717. if(!in_array($task->status, $this->config->task->unfinishedStatus)) continue;
  718. $taskData = $this->taskZen->buildTaskForCancel($task);
  719. $this->task->cancel($task, $taskData);
  720. }
  721. }
  722. return $this->send(array('result' => 'success', 'load' => true));
  723. }
  724. /**
  725. * 批量关闭任务。
  726. * Batch close tasks.
  727. *
  728. * @param string $confirm yes|no
  729. * @access public
  730. * @return void
  731. */
  732. public function batchClose($confirm = 'no')
  733. {
  734. $skipTasks = array();
  735. $taskIdList = $this->post->taskIdList ? array_unique($this->post->taskIdList) : array();
  736. $taskList = $this->task->getByIdList($taskIdList);
  737. if($confirm == 'no')
  738. {
  739. foreach($taskList as $taskID => $task)
  740. {
  741. if(!in_array($task->status, array('done', 'cancel')))
  742. {
  743. $skipTasks[$taskID] = $taskID;
  744. }
  745. }
  746. if(!empty($skipTasks))
  747. {
  748. $skipTasks = implode(',', $skipTasks);
  749. $url = $this->createLink('task', 'batchClose', "confirm=yes");
  750. $confirm = sprintf($this->lang->task->error->skipClose, $skipTasks);
  751. $data['url'] = $url;
  752. $data['data'] = array('taskIdList[]' => $this->post->taskIdList);
  753. $data = json_encode($data);
  754. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm('{$confirm}').then((res) => {if(res) $.ajaxSubmit($data);});"));
  755. }
  756. }
  757. foreach($taskList as $taskID => $task)
  758. {
  759. if($task->status == 'closed') continue;
  760. if($task->parent > 0 && isset($taskList[$task->parent])) continue; // 同时选中父子,关闭父的时候就关闭子了,不用重复关闭子
  761. $taskData = $this->taskZen->buildTaskForClose($task);
  762. $this->task->close($task, $taskData);
  763. }
  764. if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
  765. return $this->send(array('result' => 'success', 'load' => true));
  766. }
  767. /**
  768. * 取消一个任务。
  769. * Cancel a task.
  770. *
  771. * @param int $taskID
  772. * @param string $cardPosition
  773. * @param string $from
  774. * @access public
  775. * @return void
  776. */
  777. public function cancel($taskID, $cardPosition = '', $from = '')
  778. {
  779. $this->taskZen->commonAction($taskID);
  780. $cardPosition = str_replace(array(',', ' '), array('&', ''), $cardPosition);
  781. parse_str($cardPosition, $output);
  782. if(!empty($_POST))
  783. {
  784. $oldTask = $this->task->getByID($taskID);
  785. $task = $this->taskZen->buildTaskForCancel($oldTask);
  786. $result = $this->task->cancel($oldTask, $task, $output);
  787. if(!$result) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  788. $message = $this->executeHooks($taskID);
  789. if(helper::isAjaxRequest('modal')) return $this->send($this->taskZen->responseModal($oldTask, $from, $message));
  790. return $this->send(array('result' => 'success', 'message' => $message ?: $this->lang->saveSuccess, 'closeModal' => true, 'load' => $this->createLink('task', 'view', "taskID=$taskID")));
  791. }
  792. $this->view->title = $this->view->execution->name . $this->lang->hyphen . $this->lang->task->cancel;
  793. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  794. $this->display();
  795. }
  796. /**
  797. * 激活一个任务
  798. * Activate a task.
  799. *
  800. * @param int $taskID
  801. * @param string $cardPosition
  802. * @param string $from
  803. * @access public
  804. * @return void
  805. */
  806. public function activate($taskID, $cardPosition = '', $from = '')
  807. {
  808. /* Analytic parameter. */
  809. $cardPosition = str_replace(array(',', ' '), array('&', ''), $cardPosition);
  810. parse_str($cardPosition, $output);
  811. $this->taskZen->commonAction($taskID);
  812. if(!empty($_POST))
  813. {
  814. /* Prepare the data information before activate the task. */
  815. $task = $this->taskZen->buildTaskForActivate($taskID);
  816. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  817. $teamData = form::data($this->config->task->form->team->edit)->get();
  818. $this->task->activate($task, (string)$this->post->comment, $teamData, $output);
  819. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  820. $message = $this->executeHooks($taskID);
  821. $message = $message ?: $this->lang->saveSuccess;
  822. /* Get the information returned after a task is started. */
  823. $task = $this->task->fetchByID($taskID);
  824. $response = $this->taskZen->responseAfterChangeStatus($task, $from, $message);
  825. return $this->send($response);
  826. }
  827. if(!isset($this->view->members[$this->view->task->finishedBy])) $this->view->members[$this->view->task->finishedBy] = $this->view->task->finishedBy; // Ensure that the completion person is on the user list.
  828. /* Get task teammembers. */
  829. if(!empty($this->view->task->team))
  830. {
  831. $teamAccounts = array_column($this->view->task->team, 'account');
  832. $teamMembers = array();
  833. foreach($this->view->members as $account => $name)
  834. {
  835. if(!$account || in_array($account, $teamAccounts)) $teamMembers[$account] = $name;
  836. }
  837. $this->view->teamMembers = $teamMembers;
  838. }
  839. $this->view->title = $this->view->execution->name . $this->lang->hyphen . $this->lang->task->activate;
  840. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  841. $this->view->isMultiple = !empty($this->view->task->team);
  842. $this->display();
  843. }
  844. /**
  845. * 删除一个任务。
  846. * Delete a task.
  847. *
  848. * @param int $taskID
  849. * @param string $from
  850. * @access public
  851. * @return void
  852. */
  853. public function delete($taskID, $from = '')
  854. {
  855. $task = $this->task->fetchByID($taskID);
  856. /* 如果是父任务,先删除所有子任务 */
  857. if($task->isParent)
  858. {
  859. dao::$filterTpl = 'never';
  860. $childIdList = $this->task->getAllChildId($taskID, false);
  861. $childTasks = $this->task->getByIdList($childIdList);
  862. foreach($childTasks as $childID => $childTask)
  863. {
  864. if(strpos(",{$childTask->path},", ",$taskID,") === false) continue;
  865. $this->task->delete(TABLE_TASK, $childID);
  866. if($childTask->fromBug != 0) $this->dao->update(TABLE_BUG)->set('toTask')->eq(0)->where('id')->eq($childTask->fromBug)->exec();
  867. if($childTask->story) $this->loadModel('story')->setStage($childTask->story);
  868. }
  869. }
  870. /* 删除当前任务 */
  871. $this->task->delete(TABLE_TASK, $taskID);
  872. if($task->parent > 0)
  873. {
  874. $this->task->updateParentStatus($task->id);
  875. $this->loadModel('action')->create('task', $task->parent, 'deleteChildrenTask', '', $taskID);
  876. }
  877. if($task->fromBug != 0) $this->dao->update(TABLE_BUG)->set('toTask')->eq(0)->where('id')->eq($task->fromBug)->exec();
  878. if($task->story) $this->loadModel('story')->setStage($task->story);
  879. $message = $this->executeHooks($taskID);
  880. $message = $message ?: $this->lang->saveSuccess;
  881. /* 在看板中删除任务时的返回。*/
  882. /* Respond when delete in kanban. */
  883. if($from == 'taskkanban') return $this->send(array('result' => 'success', 'closeModal' => true, 'callback' => "refreshKanban()"));
  884. if($from == 'view') return $this->send(array('result' => 'success', 'message' => $message, 'closeModal' => true, 'load' => true));
  885. $link = $this->session->taskList ? $this->session->taskList : $this->createLink('execution', 'task', "executionID={$task->execution}");
  886. $link = isInModal() ? true : $link;
  887. return $this->send(array('result' => 'success', 'message' => $message, 'load' => $link, 'closeModal' => true));
  888. }
  889. /**
  890. * AJAX: return tasks of a user in html select.
  891. *
  892. * @param int $userID
  893. * @param string $id
  894. * @param string $status
  895. * @param int $appendID
  896. * @access public
  897. * @return string
  898. */
  899. public function ajaxGetUserTasks($userID = 0, $id = '', $status = 'wait,doing', $appendID = 0)
  900. {
  901. if($userID == 0) $userID = $this->app->user->id;
  902. $user = $this->loadModel('user')->getById($userID, 'id');
  903. $account = $user->account;
  904. $tasks = $this->task->getUserTaskPairs($account, $status, array(), array($appendID));
  905. $suspendedTasks = $this->task->getUserSuspendedTasks($account);
  906. $items = array();
  907. foreach($tasks as $taskID => $taskName)
  908. {
  909. if(isset($suspendedTasks[$taskID]))
  910. {
  911. unset($tasks[$taskID]);
  912. continue;
  913. }
  914. $items[] = array('text' => $taskName, 'value' => $taskID);
  915. }
  916. $fieldName = $id ? "tasks[$id]" : 'task';
  917. return print(json_encode(array('name' => $fieldName, 'items' => $items)));
  918. }
  919. /**
  920. * AJAX: return execution tasks in html select.
  921. *
  922. * @param int $executionID
  923. * @access public
  924. * @return string
  925. */
  926. public function ajaxGetExecutionTasks($executionID)
  927. {
  928. $tasks = $this->task->getExecutionTaskPairs((int)$executionID);
  929. $items = array();
  930. foreach($tasks as $taskID => $taskName) $items[] = array('text' => $taskName, 'value' => $taskID, 'keys' => $taskName);
  931. return print(json_encode($items));
  932. }
  933. /**
  934. * 禅道客户端获取任务动态。
  935. * AJAX: get the actions of a task. for web app.
  936. *
  937. * @param int $taskID
  938. * @access public
  939. * @return void
  940. */
  941. public function ajaxGetDetail($taskID)
  942. {
  943. $this->view->actions = $this->loadModel('action')->getList('task', $taskID);
  944. $this->display();
  945. }
  946. /**
  947. * The report page.
  948. *
  949. * @param int $executionID
  950. * @param string $browseType
  951. * @param string $chartType default|pie|bar|line
  952. * @access public
  953. * @return void
  954. */
  955. public function report($executionID, $browseType = 'all', $chartType = 'default')
  956. {
  957. $this->loadModel('report');
  958. $this->view->charts = array();
  959. $this->execution->setMenu($executionID);
  960. /* Build chart data. */
  961. $chartList = array();
  962. $this->view->datas = array();
  963. if(!empty($_POST)) $chartList = $this->taskZen->buildChartData($chartType);
  964. /* If the project is non-execution, the chart of tasks by execution is not shown. */
  965. $execution = $this->loadModel('execution')->getByID($executionID);
  966. if(!$execution->multiple) unset($this->lang->task->report->charts['tasksPerExecution']);
  967. if($this->app->tab == 'project') $this->view->projectID = $execution->project;
  968. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->task->report->common;
  969. $this->view->executionID = $executionID;
  970. $this->view->execution = $execution;
  971. $this->view->browseType = $browseType;
  972. $this->view->chartType = $chartType;
  973. $this->view->charts = $chartList;
  974. $this->view->checkedCharts = $this->post->charts ? implode(',', $this->post->charts) : '';
  975. $this->display();
  976. }
  977. /**
  978. * 导出任务数据。
  979. * get data to export.
  980. *
  981. * @param int $executionID
  982. * @param string $orderBy
  983. * @param string $type browse type, such as: all,unclosed,assignedtome,myinvolved,assignedbyme,needconfirm,etc
  984. * @access public
  985. * @return void
  986. */
  987. public function export($executionID, $orderBy, $type)
  988. {
  989. /* Get execution info and export fields. */
  990. $execution = $this->execution->getByID($executionID);
  991. $allExportFields = $this->config->task->exportFields;
  992. if($execution->lifetime == 'ops' || in_array($execution->attribute, array('request', 'review'))) $allExportFields = str_replace(' story,', '', $allExportFields);
  993. if($_POST)
  994. {
  995. $this->loadModel('file');
  996. if($type == 'bysearch') $this->config->task->dtable->fieldList['module']['dataSource'] = array('module' => 'tree', 'method' => 'getAllModulePairs');
  997. /* Create field lists. */
  998. $fields = $this->taskZen->getExportFields($allExportFields);
  999. /* Get tasks. */
  1000. $tasks = $this->task->getExportTasks($orderBy);
  1001. if($type == 'group') $tasks = $this->taskZen->processExportGroup($executionID, $tasks, $orderBy);
  1002. /* Process export data. */
  1003. $tasks = $this->taskZen->processExportData($tasks, $execution->project);
  1004. if($this->config->edition != 'open') list($fields, $tasks) = $this->loadModel('workflowfield')->appendDataFromFlow($fields, $tasks);
  1005. $this->post->set('fields', $fields);
  1006. $this->post->set('rows', $tasks);
  1007. $this->post->set('kind', 'task');
  1008. $this->fetch('file', 'export2' . $this->post->fileType, $_POST);
  1009. }
  1010. if(isset($this->lang->execution->featureBar['task'][$type]))
  1011. {
  1012. $browseType = $this->lang->execution->featureBar['task'][$type];
  1013. }
  1014. else
  1015. {
  1016. $browseType = isset($this->lang->execution->statusSelects[$type]) ? $this->lang->execution->statusSelects[$type] : '';
  1017. }
  1018. $this->view->fileName = $execution->name . $this->lang->dash . $browseType . $this->lang->task->common;
  1019. $this->view->allExportFields = $allExportFields;
  1020. $this->view->customExport = true;
  1021. $this->view->orderBy = $orderBy;
  1022. $this->view->type = $type;
  1023. $this->view->executionID = $executionID;
  1024. $this->view->execution = $execution;
  1025. $this->display();
  1026. }
  1027. /**
  1028. * AJAX: Get the json of the task by ID.
  1029. * Note: This function is NOT used in open edition.
  1030. *
  1031. * @param int $taskID
  1032. * @access public
  1033. * @return void
  1034. */
  1035. public function ajaxGetByID($taskID)
  1036. {
  1037. $task = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();
  1038. if(!$task) return;
  1039. $realname = $this->dao->select('realname')->from(TABLE_USER)->where('account')->eq($task->assignedTo)->fetch('realname');
  1040. $assignedTo = $task->assignedTo == 'closed' ? 'Closed' : $task->assignedTo;
  1041. $task->assignedTo = $realname ? $realname : $assignedTo;
  1042. if($task->story)
  1043. {
  1044. $this->app->loadLang('story');
  1045. $stage = $this->dao->select('stage')->from(TABLE_STORY)->where('id')->eq($task->story)->andWhere('version')->eq($task->storyVersion)->fetch('stage');
  1046. $task->storyStage = zget($this->lang->story->stageList, $stage);
  1047. }
  1048. return print(json_encode($task));
  1049. }
  1050. /**
  1051. * 管理多人任务的团队。
  1052. * Update assign of multi task.
  1053. *
  1054. * @param int $executionID
  1055. * @param int $taskID
  1056. * @param string $from ''|taskkanban
  1057. * @access public
  1058. * @return void
  1059. */
  1060. public function manageTeam($executionID, $taskID, $from = '')
  1061. {
  1062. $this->taskZen->commonAction($taskID);
  1063. if(!empty($_POST))
  1064. {
  1065. /* Update assign of multi task. */
  1066. $postData = form::data($this->config->task->form->manageTeam);
  1067. $task = $this->taskZen->prepareManageTeam($postData, $taskID);
  1068. $postData->team = $this->post->team;
  1069. $postData->teamEstimate = $this->post->teamEstimate;
  1070. $postData->teamConsumed = $this->post->teamConsumed;
  1071. $postData->teamLeft = $this->post->teamLeft;
  1072. $postData->teamSource = $this->post->teamSource;
  1073. $changes = $this->task->updateTeam($task, $postData);
  1074. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1075. /* Record log. */
  1076. $actionID = $this->loadModel('action')->create('task', $taskID, 'Edited');
  1077. $this->action->logHistory($actionID, $changes);
  1078. $message = $this->executeHooks($taskID);
  1079. $message = $message ?: $this->lang->saveSuccess;
  1080. $response = $this->taskZen->responseAfterAssignTo($taskID, $from, $message);
  1081. return $this->send($response);
  1082. }
  1083. if($this->app->rawMethod == 'assignto')
  1084. {
  1085. if($this->view->execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$this->view->execution->id}") : '';
  1086. if(!$this->view->execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$this->view->execution->project}") : '';
  1087. $this->view->manageLink = $manageLink;
  1088. }
  1089. $this->view->members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
  1090. $this->view->users = $this->loadModel('user')->getPairs();
  1091. $this->display();
  1092. }
  1093. /**
  1094. * AJAX: 返回一个html格式的需求选择列表用于创建测试类型的任务。该需求的名称将作为创建任务的子任务名称。
  1095. * AJAX: Return the stories selection list in html format for create the task with test type. The child task has same name with the story.
  1096. *
  1097. * @param int $executionID
  1098. * @param int $taskID The task ID which the task copy from. This will be used for creating a task from a existed task.
  1099. * @access public
  1100. * @return string
  1101. */
  1102. public function ajaxGetTestStories($executionID, $taskID = 0)
  1103. {
  1104. $stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', '', 'ignoreID', 'active', 'story', false);
  1105. $testStoryIdList = $this->story->getTestStories(array_keys($stories), $executionID);
  1106. $testStories = array();
  1107. foreach($stories as $testStoryID => $storyTitle)
  1108. {
  1109. /**
  1110. * 如果在执行中已经为该需求创建了测试类型任务,则跳过这个需求不再给前台展示。
  1111. * If a test type task has already been created for the requirement during execution, the requirement is skipped and not shown to the foreground.
  1112. */
  1113. if(empty($testStoryID) || isset($testStoryIdList[$testStoryID])) continue;
  1114. $testStories[$testStoryID] = $storyTitle;
  1115. }
  1116. $execution = $this->loadModel('execution')->fetchByID($executionID);
  1117. if($execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$execution->id}") : '';
  1118. if(!$execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$execution->project}") : '';
  1119. $this->view->testStories = $testStories;
  1120. $this->view->task = $this->loadModel('task')->getByID($taskID);
  1121. $this->view->members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
  1122. $this->view->manageLink = $manageLink;
  1123. $this->display();
  1124. }
  1125. /**
  1126. * AJAX: 获取批量创建任务的需求下拉列表。
  1127. * AJAX: Get the stories selection list for batch create tasks.
  1128. *
  1129. * @param int $executionID
  1130. * @param int $moduleID
  1131. * @param string $zeroTaskStory
  1132. * @access public
  1133. * @return void
  1134. */
  1135. public function ajaxGetStories($executionID, $moduleID, $zeroTaskStory = 'false')
  1136. {
  1137. $moduleType = $moduleID ? $this->task->fetchByID($moduleID, 'module')->type : '';
  1138. $stories = $this->loadModel('story')->getExecutionStoryPairs($executionID, 0, 'all', $moduleType == 'task' ? 0 : $moduleID, 'full', 'active', 'story', false);
  1139. $taskCountList = $this->task->getStoryTaskCounts(array_keys($stories), $executionID);
  1140. $items = array();
  1141. foreach($stories as $storyID => $storyName)
  1142. {
  1143. if(empty($storyName)) continue;
  1144. if($zeroTaskStory == 'true' && $taskCountList[$storyID] > 0) continue;
  1145. $items[] = array('text' => $storyName, 'value' => $storyID, 'keys' => $storyName);
  1146. }
  1147. return print(json_encode($items));
  1148. }
  1149. /**
  1150. * AJAX: 获取任务的开始时间和截止时间。
  1151. * AJAX: Get the start time and end time of the task.
  1152. *
  1153. * @param int $taskID
  1154. * @access public
  1155. * @return void
  1156. */
  1157. public function ajaxGetTaskEstStartedAndDeadline($taskID)
  1158. {
  1159. $task = $this->task->fetchById($taskID);
  1160. $overParentEstStartedLang = !empty($task) ? sprintf($this->lang->task->overParentEsStarted, $task->estStarted) : '';
  1161. $overParentDeadlineLang = !empty($task) ? sprintf($this->lang->task->overParentDeadline, $task->deadline) : '';
  1162. return print(json_encode(array('estStarted' => $task->estStarted, 'deadline' => $task->deadline, 'overParentEstStartedLang' => $overParentEstStartedLang, 'overParentDeadlineLang' => $overParentDeadlineLang)));
  1163. }
  1164. /**
  1165. * 创建代码分支。
  1166. * Create repo branch.
  1167. *
  1168. * @param int $taskID
  1169. * @param int $repoID
  1170. * @access public
  1171. * @return void
  1172. */
  1173. public function createBranch($taskID, $repoID = 0)
  1174. {
  1175. return print($this->fetch('repo', 'createBranch', array('objectID' => $taskID, 'repoID' => $repoID)));
  1176. }
  1177. /**
  1178. * 取消代码分支的关联。
  1179. * Unlink code branch.
  1180. *
  1181. * @access public
  1182. * @return void
  1183. */
  1184. public function unlinkBranch()
  1185. {
  1186. return print($this->fetch('repo', 'unlinkBranch'));
  1187. }
  1188. }