zen.php 98 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277
  1. <?php
  2. /**
  3. * The zen file of task module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Shujie Tian <tianshujie@easysoft.ltd>
  8. * @package task
  9. * @link https://www.zentao.net
  10. */
  11. class taskZen extends task
  12. {
  13. /**
  14. * 执行数组。
  15. * Execution's [id] => 'name' pairs.
  16. *
  17. * @var array
  18. * @access private
  19. */
  20. private $executionPairs = array();
  21. /**
  22. * 展示创建任务的相关变量。
  23. * Show the variables associated with the creation task.
  24. *
  25. * @param object $execution
  26. * @param int $storyID
  27. * @param int $moduleID
  28. * @param int $taskID
  29. * @param int $todoID
  30. * @param int $bugID
  31. * @param array $output
  32. * @param string $cardPosition
  33. * @access protected
  34. * @return void
  35. */
  36. protected function assignCreateVars($execution, $storyID, $moduleID, $taskID, $todoID, $bugID, $output, $cardPosition)
  37. {
  38. /* Get information about the task. */
  39. $task = $this->setTaskByObjectID($storyID, $moduleID, $taskID, $todoID, $bugID, $output);
  40. $this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
  41. /* Get module information. */
  42. $executionID = $execution->id;
  43. $modulePairs = $this->tree->getTaskOptionMenu($executionID, 0 ,$this->view->showAllModule ? 'allModule' : '');
  44. if(!isset($modulePairs[$task->module])) $task->module = 0;
  45. $this->view->task = $task;
  46. /* Display relevant variables. */
  47. $this->assignExecutionForCreate($execution, $output);
  48. $this->assignStoryForCreate($executionID, $moduleID);
  49. if($execution->type == 'kanban') $this->assignKanbanForCreate($executionID, $output);
  50. /* Set Custom fields. */
  51. foreach(explode(',', $this->config->task->list->customCreateFields) as $field) $customFields[$field] = $this->lang->task->$field;
  52. $executionTypeLang = zget($this->lang->execution->typeList, $execution->type, '');
  53. $this->lang->task->noticeLinkStory = sprintf($this->lang->task->noticeLinkStory, $executionTypeLang);
  54. $parents = $this->task->getParentTaskPairs($executionID);
  55. $parents = $this->task->addTaskLabel($parents);
  56. if($execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$execution->id}") : '';
  57. if(!$execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$execution->project}") : '';
  58. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->task->create;
  59. $this->view->customFields = $customFields;
  60. $this->view->modulePairs = $modulePairs;
  61. $this->view->showFields = $this->config->task->custom->createFields;
  62. $this->view->gobackLink = (isset($output['from']) && $output['from'] == 'global') ? $this->createLink('execution', 'task', "executionID={$executionID}") : '';
  63. $this->view->execution = $execution;
  64. $this->view->project = $this->loadModel('project')->fetchById($execution->project);
  65. $this->view->storyID = $storyID;
  66. $this->view->blockID = helper::isAjaxRequest('modal') ? $this->loadModel('block')->getSpecifiedBlockID('my', 'assigntome', 'assigntome') : 0;
  67. $this->view->hideStory = $this->task->isNoStoryExecution($execution);
  68. $this->view->from = $storyID || $todoID || $bugID ? 'other' : 'task';
  69. $this->view->taskID = $taskID;
  70. $this->view->parents = $parents;
  71. $this->view->loadUrl = $this->createLink('task', 'create', "executionID={execution}&storyID={$storyID}&moduleID={$moduleID}&task={$taskID}&todoID={$todoID}&cardPosition={$cardPosition}&bugID={$bugID}");
  72. $this->view->assignedToOptions = $this->getAssignedToOptions($manageLink);
  73. $this->view->manageLink = $manageLink;
  74. $this->display();
  75. }
  76. /**
  77. * 设置创建页面展示的执行相关数据。
  78. * Set the execution-related data for the create page display.
  79. *
  80. * @param object $execution
  81. * @param array $output
  82. * @access protected
  83. * @return void
  84. */
  85. protected function assignExecutionForCreate($execution, $output)
  86. {
  87. $projectID = $execution ? $execution->project : 0;
  88. $lifetimeList = array();
  89. $attributeList = array();
  90. $executions = $this->executionPairs;
  91. /* 全局创建,过滤模板执行。*/
  92. if(!empty($output['from']) && $output['from'] == 'global')
  93. {
  94. dao::$filterTpl = 'always';
  95. $executions = $this->execution->getByProject(0, 'all', 0, true);
  96. }
  97. elseif($projectID)
  98. {
  99. $executions = $this->execution->getByProject($projectID, 'all', 0, true);
  100. $executionList = $this->execution->getByIdList(array_keys($executions));
  101. foreach($executionList as $executionItem)
  102. {
  103. if(!common::canModify('execution', $executionItem)) unset($executions[$executionItem->id]);
  104. }
  105. }
  106. $executionList = $this->execution->getByIdList(array_keys($executions));
  107. foreach($executionList as $executionItem)
  108. {
  109. $executionKey = $executionItem->id;
  110. $lifetimeList[$executionKey] = $executionItem->lifetime;
  111. $attributeList[$executionKey] = $executionItem->attribute;
  112. }
  113. $this->view->projectID = $projectID;
  114. $this->view->executions = $executions;
  115. $this->view->lifetimeList = $lifetimeList;
  116. $this->view->attributeList = $attributeList;
  117. $this->view->productID = $this->loadModel('product')->getProductIDByProject($projectID);
  118. $this->view->features = $this->execution->getExecutionFeatures($execution);
  119. $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
  120. $this->view->members = $this->user->getTeamMemberPairs($execution->id, 'execution', 'nodeleted');
  121. }
  122. /**
  123. * 设置创建页面展示的需求相关数据。
  124. * Set the stories related data for the create page display.
  125. *
  126. * @param int $executionID
  127. * @param int $moduleID
  128. * @access protected
  129. * @return void
  130. */
  131. protected function assignStoryForCreate($executionID, $moduleID)
  132. {
  133. $stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', $moduleID, 'full', 'active', 'story', false);
  134. $testStoryIdList = $this->loadModel('story')->getTestStories(array_keys($stories), $executionID);
  135. $testStories = array();
  136. foreach($stories as $testStoryID => $storyTitle)
  137. {
  138. if(empty($testStoryID) || isset($testStoryIdList[$testStoryID])) continue;
  139. $testStories[$testStoryID] = $storyTitle;
  140. }
  141. $this->view->testStories = $testStories;
  142. $this->view->testStoryIdList = $testStoryIdList;
  143. $this->view->stories = $this->story->addGradeLabel($stories);
  144. }
  145. /**
  146. * 设置创建页面展示的看板相关数据。
  147. * Set Kanban related data for create page display.
  148. *
  149. * @param int $executionID
  150. * @param array $output
  151. * @access protected
  152. * @return void
  153. */
  154. protected function assignKanbanForCreate($executionID, $output)
  155. {
  156. $this->loadModel('kanban');
  157. $regionID = isset($output['regionID']) ? (int)$output['regionID'] : 0;
  158. $laneID = isset($output['laneID']) ? (int)$output['laneID'] : 0;
  159. $regionPairs = $this->kanban->getRegionPairs($executionID, 0, 'execution');
  160. $regionID = $regionID ? $regionID : key($regionPairs);
  161. $lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'task');
  162. $laneID = $laneID ? $laneID : key($lanePairs);
  163. $this->view->regionID = $regionID;
  164. $this->view->laneID = $laneID;
  165. $this->view->regionPairs = $regionPairs;
  166. $this->view->lanePairs = $lanePairs;
  167. }
  168. /**
  169. * 展示批量编辑任务的相关变量。
  170. * Show the variables associated with the batch edit task.
  171. *
  172. * @param int $executionID
  173. * @access protected
  174. * @return void
  175. */
  176. protected function assignBatchEditVars($executionID)
  177. {
  178. $this->loadModel('tree');
  179. /* Set menu and related variables. */
  180. if($executionID)
  181. {
  182. $execution = $this->execution->getById($executionID);
  183. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->task->batchEdit;
  184. $this->view->execution = $execution;
  185. $this->view->project = $this->loadModel('project')->fetchById($execution->project);
  186. $this->view->modules = $this->tree->getTaskOptionMenu($executionID, 0, !empty($this->config->task->allModule) ? 'allModule' : '');
  187. }
  188. else
  189. {
  190. $this->view->title = $this->lang->task->batchEdit;
  191. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  192. $this->view->modules = array();
  193. }
  194. /* Check if the request data size exceeds the PHP limit. */
  195. $tasks = $this->task->getByIdList($this->post->taskIdList);
  196. $parentTaskIdList = array();
  197. foreach($tasks as $taskID => $task)
  198. {
  199. $tasks[$taskID]->consumed = 0;
  200. $parentTaskIdList[$task->parent] = $task->parent;
  201. }
  202. $countInputVars = count($tasks) * (count(explode(',', $this->config->task->custom->batchEditFields)) + 3);
  203. $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars);
  204. if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars);
  205. $this->config->task->batchedit->requiredFields = $this->config->task->edit->requiredFields;
  206. foreach(explode(',', $this->config->task->list->customBatchEditFields) as $field)
  207. {
  208. if(!empty($execution) && $execution->type == 'stage' && strpos('estStarted,deadline', $field) !== false) continue;
  209. $customFields[$field] = $this->lang->task->$field;
  210. if($this->app->getClientLang() == 'en' && $field == 'record') $customFields['record'] = $this->lang->task->consumedAB;
  211. }
  212. $this->view->customFields = $customFields;
  213. $this->view->showFields = $this->config->task->custom->batchEditFields;
  214. $executionTeams = array();
  215. $executionIdList = array_unique(array_column($tasks, 'execution'));
  216. $executionTeamList = $this->execution->getMembersByIdList($executionIdList);
  217. foreach($executionIdList as $id) $executionTeams[$id] = array_column((array)zget($executionTeamList, $id, array()), 'account');
  218. $moduleGroup = array();
  219. if(!$executionID)
  220. {
  221. foreach($tasks as $task)
  222. {
  223. if(isset($moduleGroup[$task->execution])) continue;
  224. $executionInfo = $this->execution->fetchByID($task->execution);
  225. $executionModules = $this->tree->getTaskOptionMenu($task->execution, 0, 'allModule');
  226. foreach($executionModules as $moduleID => $moduleName) $moduleGroup[$task->execution][] = array('text' => $executionInfo->name . $moduleName, 'value' => $moduleID);
  227. }
  228. }
  229. list($childTasks, $nonStoryChildTasks) = $this->task->getChildTasksByList(array_keys($tasks));
  230. $childrenDateLimit = array();
  231. foreach($childTasks as $parent => $children)
  232. {
  233. $childDateLimit = array('estStarted' => '', 'deadline' => '');
  234. foreach($children as $child)
  235. {
  236. if(!helper::isZeroDate($child->estStarted) && (empty($childDateLimit['estStarted']) || $childDateLimit['estStarted'] > $child->estStarted)) $childDateLimit['estStarted'] = $child->estStarted;
  237. if(!helper::isZeroDate($child->deadline) && (empty($childDateLimit['deadline']) || $childDateLimit['deadline'] < $child->deadline)) $childDateLimit['deadline'] = $child->deadline;
  238. }
  239. $childrenDateLimit[$parent] = $childDateLimit;
  240. }
  241. $storyPairs = $this->story->getExecutionStoryPairs($executionID, 0, 'all', '', 'full', 'active', 'story', false);;
  242. $storyList = $this->story->getByList(array_keys($storyPairs));
  243. $stories = array();
  244. foreach($storyList as $story)
  245. {
  246. $stories[0][] = array('value' => $story->id, 'text' => $storyPairs[$story->id]);
  247. if($story->module) $stories[$story->module][] = array('value' => $story->id, 'text' => $storyPairs[$story->id]);
  248. foreach($tasks as $task)
  249. {
  250. if($task->module) $stories[$task->module][] = array('value' => $story->id, 'text' => $storyPairs[$story->id]);
  251. }
  252. }
  253. $manageLinkList['project'] = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={projectID}") : '';
  254. $manageLinkList['execution'] = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "executionID={executionID}") : '';
  255. /* Assign. */
  256. $this->view->executionID = $executionID;
  257. $this->view->tasks = $tasks;
  258. $this->view->teams = $this->task->getTeamMembersByIdList($this->post->taskIdList);
  259. $this->view->executionTeams = $executionTeams;
  260. $this->view->users = $this->loadModel('user')->getPairs('nodeleted');
  261. $this->view->moduleGroup = $moduleGroup;
  262. $this->view->childTasks = $childTasks;
  263. $this->view->nonStoryChildTasks = $nonStoryChildTasks;
  264. $this->view->childrenDateLimit = $childrenDateLimit;
  265. $this->view->stories = $stories;
  266. $this->view->parentTasks = $this->task->getByIdList($parentTaskIdList);
  267. $this->view->noSprintPairs = $this->loadModel('project')->getProjectExecutionPairs();
  268. $this->view->manageLinkList = $manageLinkList;
  269. $this->display();
  270. }
  271. /**
  272. * 构建任务编辑表单。
  273. * Build task edit form.
  274. *
  275. * @param int $taskID
  276. * @access protected
  277. * @return void
  278. */
  279. protected function buildEditForm($taskID)
  280. {
  281. $task = $this->view->task;
  282. /* Get the task parent id,name pairs. */
  283. $tasks = $this->task->getParentTaskPairs($this->view->execution->id, strVal($task->parent), $taskID);
  284. $tasks = $this->task->addTaskLabel($tasks);
  285. if(isset($tasks[$taskID])) unset($tasks[$taskID]);
  286. /* Prepare to assign to relevant parameters. */
  287. if(!isset($this->view->members[$task->assignedTo])) $this->view->members[$task->assignedTo] = $task->assignedTo;
  288. if(isset($this->view->members['closed']) || $task->status == 'closed') $this->view->members['closed'] = 'Closed';
  289. /* Get the executions of the task. */
  290. $executions = !empty($task->project) ? $this->execution->getByProject($task->project, 'all', 0, true) : array();
  291. /* Get task members. */
  292. $taskMembers = $this->view->members;
  293. if(!empty($task->team))
  294. {
  295. foreach($task->members as $teamAccount)
  296. {
  297. if(!isset($this->view->members[$teamAccount])) continue;
  298. $taskMembers[$teamAccount] = $this->view->members[$teamAccount];
  299. }
  300. }
  301. /* Get execution stories. */
  302. $moduleID = $task->module;
  303. if($moduleID)
  304. {
  305. $moduleID = $this->loadModel('tree')->getStoryModule($moduleID);
  306. $moduleID = $this->tree->getAllChildID($moduleID);
  307. }
  308. $storyPairs = $this->story->getExecutionStoryPairs($this->view->execution->id, 0, 'all', $moduleID, 'full', 'all', 'story', false);
  309. $storyList = $this->story->getByList(array_keys($storyPairs));
  310. $stories = array();
  311. foreach($storyList as $story)
  312. {
  313. if($story->status != 'active' && $task->story != $story->id) continue;
  314. $stories[$story->id] = $story->title;
  315. }
  316. $syncChildren = array();
  317. $childDateLimit = array('estStarted' => '', 'deadline' => '');
  318. if(!empty($task->children))
  319. {
  320. foreach($task->children as $child)
  321. {
  322. if(empty($child->story)) $syncChildren[] = $child->id;
  323. if(!helper::isZeroDate($child->estStarted) && (empty($childDateLimit['estStarted']) || $childDateLimit['estStarted'] > $child->estStarted)) $childDateLimit['estStarted'] = $child->estStarted;
  324. if(!helper::isZeroDate($child->deadline) && (empty($childDateLimit['deadline']) || $childDateLimit['deadline'] < $child->deadline)) $childDateLimit['deadline'] = $child->deadline;
  325. }
  326. }
  327. if($this->view->execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$this->view->execution->id}") : '';
  328. if(!$this->view->execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$this->view->execution->project}") : '';
  329. $this->view->title = $this->lang->task->edit . 'TASK' . $this->lang->hyphen . $this->view->task->name;
  330. $this->view->stories = $this->story->addGradeLabel($stories);
  331. $this->view->tasks = $tasks;
  332. $this->view->taskMembers = $taskMembers;
  333. $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "{$task->openedBy},{$task->canceledBy},{$task->closedBy}");
  334. $this->view->showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
  335. $this->view->modules = $this->tree->getTaskOptionMenu($task->execution, 0, $this->view->showAllModule ? 'allModule' : '');
  336. $this->view->executions = $executions;
  337. $this->view->syncChildren = $syncChildren;
  338. $this->view->childDateLimit = $childDateLimit;
  339. $this->view->parentTask = !empty($task->parent) ? $this->task->getById($task->parent) : null;
  340. $this->view->manageLink = $manageLink;
  341. $this->view->project = $task->project ? $this->loadModel('project')->fetchById($task->project) : null;
  342. $this->display();
  343. }
  344. /**
  345. * 构建指派给表单。
  346. * Build form for assignTo page.
  347. *
  348. * @param int $executionID
  349. * @param int $taskID
  350. * @access protected
  351. * @return void
  352. */
  353. protected function buildUsersAndMembersToForm($executionID, $taskID)
  354. {
  355. $task = $this->task->getByID($taskID);
  356. $projectModel = $this->dao->findById($task->project)->from(TABLE_PROJECT)->fetch('model');
  357. $memberType = $projectModel == 'research' ? 'project' : 'execution';
  358. $objectID = $projectModel == 'research' ? $task->project : $executionID;
  359. $members = $this->loadModel('user')->getTeamMemberPairs($objectID, $memberType, 'nodeleted');
  360. /* Compute next assignedTo. */
  361. if(!empty($task->team))
  362. {
  363. if(in_array($task->status, $this->config->task->unfinishedStatus)) $task->nextUser = $this->task->getAssignedTo4Multi($task->team, $task, 'next');
  364. $members = $this->task->getMemberPairs($task);
  365. }
  366. if(!isset($members[$task->assignedTo])) $members[$task->assignedTo] = $task->assignedTo;
  367. if(isset($members['closed']) || $task->status == 'closed') $members['closed'] = 'Closed';
  368. if($this->view->execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$this->view->execution->id}") : '';
  369. if(!$this->view->execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$this->view->execution->project}") : '';
  370. $this->view->members = $members;
  371. $this->view->users = $this->loadModel('user')->getPairs();
  372. $this->view->manageLink = $manageLink;
  373. }
  374. /**
  375. * 构建批量创建任务的表单数据。
  376. * Build batch create form.
  377. *
  378. * @param object $execution
  379. * @param int $storyID
  380. * @param int $moduleID
  381. * @param int $taskID
  382. * @param array $output
  383. * @access protected
  384. * @return void
  385. */
  386. protected function buildBatchCreateForm($execution, $storyID, $moduleID, $taskID, $output)
  387. {
  388. /* 获取区域和泳道下拉数据,并设置区域和泳道的默认值。 */
  389. /* Get region and lane dropdown data and set default values for regions and lanes. */
  390. if($execution->type == 'kanban') $this->assignKanbanForCreate($execution->id, $output);
  391. if($taskID)
  392. {
  393. $task = $this->dao->findById($taskID)->from(TABLE_TASK)->fetch();
  394. $this->view->parentTitle = $task->name;
  395. $this->view->parentPri = $task->pri;
  396. $this->view->parentTask = $task;
  397. }
  398. /* 获取模块和需求下拉数据。 Get module and story dropdown data. */
  399. $showAllModule = !empty($this->config->execution->task->allModule) || $execution->type == 'kanban' ? 'allModule' : '';
  400. $modules = $this->loadModel('tree')->getTaskOptionMenu($execution->id, 0, $showAllModule);
  401. $story = $this->story->getByID($storyID);
  402. $stories = $this->story->getExecutionStoryPairs($execution->id, 0, 'all', $story ? $story->module : 0, 'short', 'active', 'story', false);
  403. list($customFields, $checkedFields) = $this->getCustomFields($execution, 'batchCreate');
  404. if(isset($customFields['story'])) $customFields['preview'] = $customFields['copyStory'] = '';
  405. $showFields = $this->config->task->custom->batchCreateFields;
  406. if(strpos(",$showFields,", ',story,') !== false)
  407. {
  408. $showFields .= ',preview,copyStory';
  409. }
  410. else
  411. {
  412. $showFields = trim(str_replace(array(',copyStory,', ',preview,'), ',', ",{$showFields},"), ',');
  413. }
  414. $this->config->task->batchcreate->requiredFields = $this->config->task->create->requiredFields;
  415. if($execution->multiple) $manageLink = common::hasPriv('execution', 'manageMembers') ? $this->createLink('execution', 'manageMembers', "execution={$execution->id}") : '';
  416. if(!$execution->multiple) $manageLink = common::hasPriv('project', 'manageMembers') ? $this->createLink('project', 'manageMembers', "projectID={$execution->project}") : '';
  417. $this->view->title = $this->lang->task->batchCreate;
  418. $this->view->execution = $execution;
  419. $this->view->project = $this->loadModel('project')->fetchById($execution->project);
  420. $this->view->modules = $modules;
  421. $this->view->parent = $taskID;
  422. $this->view->storyID = $storyID;
  423. $this->view->story = $story;
  424. $this->view->moduleID = $story ? $story->module : $moduleID;
  425. $this->view->stories = array_filter($stories);
  426. $this->view->storyTasks = $this->task->getStoryTaskCounts(array_keys($stories), $execution->id);
  427. $this->view->members = $this->loadModel('user')->getTeamMemberPairs($execution->id, 'execution', 'nodeleted');
  428. $this->view->taskConsumed = isset($task) && $task->isParent == 0 ? $task->consumed : 0;
  429. $this->view->customFields = $customFields;
  430. $this->view->checkedFields = $checkedFields;
  431. $this->view->hideStory = $this->task->isNoStoryExecution($execution);
  432. $this->view->showFields = $showFields;
  433. $this->view->manageLink = $manageLink;
  434. $this->display();
  435. }
  436. /**
  437. * 构造任务记录日志的表单数据。
  438. * Build record workhour form.
  439. *
  440. * @param int $taskID
  441. * @param string $from
  442. * @param string $orderBy
  443. * @access protected
  444. * @return void
  445. */
  446. protected function buildRecordForm($taskID, $from, $orderBy)
  447. {
  448. $task = $this->task->getById($taskID);
  449. if(!empty($task->team) and $task->mode == 'linear')
  450. {
  451. if(empty($orderBy))
  452. {
  453. $orderBy = 'id_desc';
  454. }
  455. else
  456. {
  457. /* The id sort with order or date style. */
  458. $orderBy .= preg_replace('/(order_|date_)/', ',id_', $orderBy);
  459. }
  460. }
  461. if(!$orderBy) $orderBy = 'id_desc';
  462. /* Set the fold state of the current task. */
  463. $referer = strtolower($_SERVER['HTTP_REFERER']);
  464. if(strpos($referer, 'recordworkhour') and $this->cookie->taskEffortFold !== false)
  465. {
  466. $taskEffortFold = $this->cookie->taskEffortFold;
  467. }
  468. else
  469. {
  470. $taskEffortFold = 0;
  471. $currentAccount = $this->app->user->account;
  472. if($task->assignedTo == $currentAccount) $taskEffortFold = 1;
  473. if(!empty($task->team))
  474. {
  475. $teamMember = array_column($task->team, 'account');
  476. if(in_array($currentAccount, $teamMember)) $taskEffortFold = 1;
  477. }
  478. }
  479. $this->view->title = $this->lang->task->record;
  480. $this->view->task = $task;
  481. $this->view->from = $from;
  482. $this->view->orderBy = $orderBy;
  483. $this->view->efforts = $this->task->getTaskEfforts($task->id, '', 0, $orderBy);
  484. $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
  485. $this->view->taskEffortFold = $taskEffortFold;
  486. $this->display();
  487. }
  488. /**
  489. * 构造待更新的任务数据。
  490. * Build the task data to be update.
  491. *
  492. * @param object $task
  493. * @access protected
  494. * @return object|false
  495. */
  496. protected function buildTaskForEdit($task)
  497. {
  498. $oldTask = $this->task->getByID($task->id);
  499. /* Check if the fields is valid. */
  500. if($task->estimate < 0 or $task->left < 0 or $task->consumed < 0) dao::$errors[] = $this->lang->task->error->recordMinus;
  501. if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($oldTask->execution, (string)$task->estStarted, (string)$task->deadline);
  502. if(!empty($_POST['lastEditedDate']) && $oldTask->lastEditedDate != $this->post->lastEditedDate) dao::$errors[] = $this->lang->error->editedByOther;
  503. if(dao::isError()) return false;
  504. $now = helper::now();
  505. $task = form::data($this->config->task->form->edit, $task->id)
  506. ->add('id', $task->id)
  507. ->add('lastEditedDate', $now)
  508. ->setDefault('design', $oldTask->design)
  509. ->setIF(!$task->assignedTo && !empty($oldTask->team) && !empty($this->post->team), 'assignedTo', $this->task->getAssignedTo4Multi($this->post->team, $oldTask))
  510. ->setIF($task->assignedTo != $oldTask->assignedTo, 'assignedDate', $now)
  511. ->setIF($task->mode == 'single', 'mode', '')
  512. ->setIF(!$oldTask->mode && !$task->assignedTo && !empty($this->post->team), 'assignedTo', zget($this->post->team, 0, ''))
  513. ->setIF($task->story !== false && $task->story != $oldTask->story, 'storyVersion', $this->loadModel('story')->getVersion((int)$task->story))
  514. ->setIF($task->status == 'wait' && $task->left == $oldTask->left && $task->consumed == 0 && $task->estimate, 'left', $task->estimate)
  515. ->setIF($task->status == 'done', 'left', 0)
  516. ->setIF($task->status == 'done' && empty($task->finishedBy), 'finishedBy', $this->app->user->account)
  517. ->setIF($task->status == 'done' && empty($task->finishedDate), 'finishedDate', $now)
  518. ->setIF($task->status == 'cancel' && empty($task->canceledBy), 'canceledBy', $this->app->user->account)
  519. ->setIF($task->status == 'cancel' && empty($task->canceledDate), 'canceledDate', $now)
  520. ->setIF($task->status == 'cancel', 'assignedTo', $oldTask->openedBy)
  521. ->setIF($task->status == 'cancel', 'assignedDate', $now)
  522. ->setIF($task->status == 'closed' && empty($task->closedBy), 'closedBy', $this->app->user->account)
  523. ->setIF($task->status == 'closed' && empty($task->closedDate), 'closedDate', $now)
  524. ->setIF($task->consumed > 0 && $task->left > 0 && $task->status == 'wait', 'status', 'doing')
  525. ->setIF($oldTask->parent >= 0 && empty($task->parent), 'parent', 0)
  526. ->setIF($oldTask->parent < 0, 'estimate', $oldTask->estimate)
  527. ->setIF($oldTask->parent < 0, 'left', $oldTask->left)
  528. ->setIF($oldTask->name != $task->name || $oldTask->estStarted != $task->estStarted || $oldTask->deadline != $task->deadline, 'version', $oldTask->version + 1)
  529. ->stripTags($this->config->task->editor->edit['id'], $this->config->allowedTags)
  530. ->get();
  531. $project = $this->loadModel('project')->fetchById($oldTask->project);
  532. $parents = $this->getParentEstStartedAndDeadline(array($task->parent));
  533. $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', isset($parents[$task->parent]) ? $parents[$task->parent] : null);
  534. $team = $this->post->team ? array_filter($this->post->team) : array();
  535. if($task->mode && empty($team)) dao::$errors['assignedTo'] = $this->lang->task->teamNotEmpty;
  536. if(dao::isError()) return false;
  537. return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->edit['id'], (string)$this->post->uid);
  538. }
  539. /**
  540. * 构造待批量指派的任务数据。
  541. * Build the task data to batch assign to.
  542. *
  543. * @param string[] $taskIdList
  544. * @param string $assignedTo
  545. * @access protected
  546. * @return object[]
  547. */
  548. protected function buildTasksForBatchAssignTo($taskIdList, $assignedTo)
  549. {
  550. $taskIdList = array_unique($taskIdList);
  551. $multipleTasks = $this->dao->select('task, account')->from(TABLE_TASKTEAM)->where('task')->in($taskIdList)->fetchGroup('task', 'account');
  552. $tasks = $this->task->getByIdList($taskIdList);
  553. /* Filter tasks. */
  554. foreach($tasks as $taskID => $task)
  555. {
  556. /* 不是完成,取消状态的多人任务不能指派。*/
  557. /* Multiple tasks that are not finished or canceled cannot be assigned. */
  558. if(isset($multipleTasks[$taskID]) && strpos('wait,doing,pause', $task->status) !== false) unset($tasks[$taskID]);
  559. /* 多人任务不能指派给任务团队外的人。*/
  560. /* Multiple tasks cannot be assigned to users that are not in the task team. */
  561. if(isset($multipleTasks[$taskID]) && !isset($multipleTasks[$taskID][$assignedTo])) unset($tasks[$taskID]);
  562. if($task->status == 'closed') unset($tasks[$taskID]);
  563. }
  564. /* Prepare data. */
  565. $now = helper::now();
  566. $prepareTasks = array();
  567. foreach($tasks as $task)
  568. {
  569. $prepareTask = new stdclass();
  570. $prepareTask->id = $task->id;
  571. $prepareTask->lastEditedBy = $this->app->user->account;
  572. $prepareTask->lastEditedDate = $now;
  573. $prepareTask->assignedDate = $now;
  574. $prepareTask->assignedTo = $assignedTo;
  575. $prepareTasks[] = clone $prepareTask;
  576. }
  577. return $prepareTasks;
  578. }
  579. /**
  580. * 处理批量创建任务的请求数据。
  581. * Process the request data for the batch create tasks.
  582. *
  583. * @param object $execution
  584. * @param int $taskID
  585. * @param array $output
  586. * @access protected
  587. * @return false|object[]
  588. */
  589. protected function buildTasksForBatchCreate($execution, $taskID, $output)
  590. {
  591. $this->loadModel('story');
  592. /* Check parent name is not empty when has child task. */
  593. $levelNames = array();
  594. foreach($this->post->level as $i => $level)
  595. {
  596. $level = (int)$level;
  597. $levelNames[$level]['name'] = trim($this->post->name[$i]);
  598. $levelNames[$level]['index'] = $i;
  599. $preLevel = $level - 1;
  600. if($level > 0 && !empty($levelNames[$level]['name']) && empty($levelNames[$preLevel]['name'])) dao::$errors["name[" . $levelNames[$preLevel]['index'] . "]"] = $this->lang->task->error->emptyParentName;
  601. }
  602. if(dao::isError()) return false;
  603. if($execution && $this->task->isNoStoryExecution($execution)) unset($this->config->task->form->batchcreate['story']);
  604. $tasks = form::batchData()->get();
  605. foreach($tasks as $task)
  606. {
  607. $task->project = $execution->project;
  608. $task->execution = $execution->id;
  609. $task->left = $task->estimate;
  610. $task->parent = $taskID;
  611. $task->lane = !empty($task->lane) ? $task->lane : zget($output, 'laneID', 0);
  612. $task->column = !empty($task->column) ? $task->column : zget($output, 'columnID', 0);
  613. $task->storyVersion = !empty($task->story) ? $this->story->getVersion($task->story) : 1;
  614. if($task->assignedTo) $task->assignedDate = helper::now();
  615. }
  616. /* Check if the input post data meets the requirements. */
  617. $this->checkBatchCreateTask($execution->id, $tasks);
  618. if(dao::isError()) return false;
  619. return $tasks;
  620. }
  621. /**
  622. * 构造待创建的任务数据。
  623. * Build the task data to create.
  624. *
  625. * @param int $executionID
  626. * @access protected
  627. * @return object
  628. */
  629. protected function buildTaskForCreate($executionID)
  630. {
  631. $formConfig = $this->config->task->form->create;
  632. if($this->post->type == 'affair') $formConfig['assignedTo']['type'] = 'array';
  633. if($this->post->type == 'test')
  634. {
  635. $formConfig['story']['skipRequired'] = true;
  636. $formConfig['module']['skipRequired'] = true;
  637. if($this->post->selectTestStory == 'on') $formConfig['estStarted']['skipRequired'] = $formConfig['deadline']['skipRequired'] = $formConfig['estimate']['skipRequired'] = true;
  638. }
  639. if($this->post->multiple && strpos($this->config->task->create->requiredFields, 'assignedTo') !== false)
  640. {
  641. $this->config->task->create->requiredFields = str_replace(',assignedTo', '', $this->config->task->create->requiredFields);
  642. }
  643. $execution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch();
  644. if($execution && $this->task->isNoStoryExecution($execution)) unset($formConfig['story']);
  645. $team = $this->post->team ? array_filter($this->post->team) : array();
  646. $task = form::data($formConfig)->setDefault('execution', $executionID)
  647. ->setDefault('project', $execution->project)
  648. ->setDefault('left', 0)
  649. ->setIF($this->post->estimate, 'left', $this->post->estimate)
  650. ->setIF($this->post->mode, 'mode', $this->post->mode)
  651. ->setIF($this->post->story, 'storyVersion', $this->post->story ? $this->loadModel('story')->getVersion((int)$this->post->story) : 1)
  652. ->setIF(!$this->post->multiple || count($team) < 1, 'mode', '')
  653. ->setIF($this->task->isNoStoryExecution($execution), 'story', 0)
  654. ->setIF($this->post->assignedTo, 'assignedDate', helper::now())
  655. ->setIF(!$this->post->estStarted, 'estStarted', null)
  656. ->setIF(!$this->post->deadline, 'deadline', null)
  657. ->setIF($this->post->type == 'test' && $this->post->selectTestStory == 'on' && !empty($this->post->testStory), 'story', 0)
  658. ->get();
  659. /* Processing image link. */
  660. $task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->create['id'], (string)$this->post->uid);
  661. /* Check if the input post data meets the requirements. */
  662. $this->checkCreateTask($task, $team);
  663. return $task;
  664. }
  665. /**
  666. * 构造批量编辑的任务数据。
  667. * Build the tasks data to batch edit.
  668. *
  669. * @param array $taskData
  670. * @param array $oldTasks
  671. * @access protected
  672. * @return array
  673. */
  674. protected function buildTasksForBatchEdit($taskData, $oldTasks)
  675. {
  676. $now = helper::now();
  677. foreach($taskData as $taskID => $task)
  678. {
  679. $oldTask = $oldTasks[$taskID];
  680. $task->parent = $oldTask->parent;
  681. $task->assignedTo = $task->status == 'closed' ? 'closed' : $task->assignedTo;
  682. $task->assignedDate = !empty($task->assignedTo) && $oldTask->assignedTo != $task->assignedTo ? $now : $oldTask->assignedDate;
  683. $task->version = $oldTask->name != $task->name || $oldTask->estStarted != $task->estStarted || $oldTask->deadline != $task->deadline ? $oldTask->version + 1 : $oldTask->version;
  684. $task->consumed = $task->consumed < 0 ? $task->consumed : $task->consumed + $oldTask->consumed;
  685. $task->storyVersion = ($task->story && $oldTask->story != $task->story) ? $this->loadModel('story')->getVersion((int)$task->story) : $oldTask->storyVersion;
  686. if(empty($task->closedReason) && $task->status == 'closed')
  687. {
  688. if($oldTask->status == 'done') $task->closedReason = 'done';
  689. if($oldTask->status == 'cancel') $task->closedReason = 'cancel';
  690. }
  691. $task = $this->processTaskByStatus($task, $oldTask);
  692. if($task->assignedTo) $task->assignedDate = $now;
  693. }
  694. $this->checkBatchEditTask($taskData, $oldTasks);
  695. if(dao::isError()) return false;
  696. return $taskData;
  697. }
  698. /**
  699. * 构造激活的任务数据。
  700. * Build the task data to activate.
  701. *
  702. * @param int $taskID
  703. * @access protected
  704. * @return object|bool
  705. */
  706. protected function buildTaskForActivate($taskID)
  707. {
  708. $task = form::data($this->config->task->form->activate, $taskID)->add('id', $taskID)->get();
  709. if($task->left && $task->left < 0)
  710. {
  711. dao::$errors['left'] = sprintf($this->lang->task->error->recordMinus, $this->lang->task->left);
  712. return false;
  713. }
  714. unset($task->comment);
  715. return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->activate['id'], (string)$this->post->uid);
  716. }
  717. /**
  718. * 处理开始任务的请求数据。
  719. * Process the request data for the start task.
  720. *
  721. * @param object $oldTask
  722. * @access protected
  723. * @return false|object
  724. */
  725. protected function buildTaskForStart($oldTask)
  726. {
  727. $now = helper::now();
  728. $task = form::data($this->config->task->form->start, $oldTask->id)->add('id', $oldTask->id)
  729. ->setIF($oldTask->assignedTo != $this->app->user->account, 'assignedDate', $now)
  730. ->get();
  731. $task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->start['id'], (string)$this->post->uid);
  732. if($task->left == 0 && empty($oldTask->team))
  733. {
  734. $task->status = 'done';
  735. $task->finishedBy = $this->app->user->account;
  736. $task->finishedDate = $now;
  737. $task->assignedTo = $oldTask->openedBy;
  738. }
  739. /* Check if the input post data meets the requirements. */
  740. $result = $this->checkStart($oldTask, $task);
  741. if(!$result) return false;
  742. return $task;
  743. }
  744. /**
  745. * 构造取消的任务数据。
  746. * Build the task data to cancel.
  747. *
  748. * @param object $oldTask
  749. * @access protected
  750. * @return object
  751. */
  752. protected function buildTaskForCancel($oldTask)
  753. {
  754. $now = helper::now();
  755. $task = form::data($this->config->task->form->cancel, $oldTask->id)
  756. ->add('id', $oldTask->id)
  757. ->setDefault('status', 'cancel')
  758. ->setDefault('assignedTo', $oldTask->openedBy)
  759. ->setDefault('assignedDate', $now)
  760. ->setDefault('finishedBy', '')
  761. ->setDefault('canceledBy, lastEditedBy', $this->app->user->account)
  762. ->setDefault('canceledDate, lastEditedDate', $now)
  763. ->setIF(empty($oldTask->finishedDate), 'finishedDate', null)
  764. ->remove('comment')
  765. ->get();
  766. return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->cancel['id'], (string)$this->post->uid);
  767. }
  768. /**
  769. * 构造待创建的测试类型的子任务数据。
  770. * Build subtask data for the test type to create.
  771. *
  772. * @param int $executionID
  773. * @access protected
  774. * @return array
  775. */
  776. protected function buildTestTasksForCreate($executionID)
  777. {
  778. /* Set data for the type of test task that has linked stories. */
  779. $postData = form::data($this->config->task->form->testTask->create)->get();
  780. if($postData->selectTestStory == 'off') return array();
  781. $testTasks = array();
  782. $execution = $this->loadModel('execution')->getByID($executionID);
  783. $now = helper::now();
  784. foreach($postData->testStory as $key => $storyID)
  785. {
  786. if(empty($storyID)) continue;
  787. /* Set task data. */
  788. $task = new stdclass();
  789. $task->execution = $executionID;
  790. $task->story = $storyID;
  791. $task->pri = !empty($postData->testPri[$key]) ? $postData->testPri[$key] : ($_POST['pri'] ?? 3);
  792. $task->estStarted = !empty($postData->testEstStarted[$key]) ? $postData->testEstStarted[$key] : null;
  793. $task->deadline = !empty($postData->testDeadline[$key]) ? $postData->testDeadline[$key] : null;
  794. $task->assignedTo = !empty($postData->testAssignedTo[$key]) ? $postData->testAssignedTo[$key] : '';
  795. $task->estimate = !empty($postData->testEstimate[$key]) ? (float)$postData->testEstimate[$key] : 0;
  796. $task->left = !empty($postData->testEstimate[$key]) ? (float)$postData->testEstimate[$key] : 0;
  797. $task->type = 'test'; /* Setting the task type to test to prevent duplicate tasks from being created. */
  798. $task->vision = $this->config->vision;
  799. $task->project = $execution->project;
  800. $task->openedBy = $this->app->user->account;
  801. $task->openedDate = $now;
  802. $testTasks[] = $task;
  803. }
  804. $this->checkCreateTestTasks($testTasks);
  805. if(dao::isError()) return false;
  806. return $testTasks;
  807. }
  808. /**
  809. * 处理开始任务的日志数据。
  810. * Process the effort data for the start task.
  811. *
  812. * @param object $oldTask
  813. * @param object $task
  814. * @access protected
  815. * @return object
  816. */
  817. protected function buildEffortForStart($oldTask, $task)
  818. {
  819. $account = $this->app->user->account;
  820. $currentTeam = !empty($oldTask->team) ? $this->task->getTeamByAccount($oldTask->team, $account, array()) : '';
  821. $effort = new stdclass();
  822. $effort->date = helper::today();
  823. $effort->task = $task->id;
  824. $effort->consumed = zget($task, 'consumed', 0);
  825. $effort->left = zget($task, 'left', 0);
  826. $effort->work = zget($task, 'work', '');
  827. $effort->account = $account;
  828. $effort->consumed = !empty($oldTask->team) && $currentTeam ? $effort->consumed - $currentTeam->consumed : $effort->consumed - $oldTask->consumed;
  829. return $effort;
  830. }
  831. /**
  832. * 构建并检查完成任务所需的数据。
  833. * Build and check the request data for the finish task.
  834. *
  835. * @param object $oldTask
  836. * @access protected
  837. * @return object
  838. */
  839. protected function buildTaskForFinish($oldTask)
  840. {
  841. $now = helper::now();
  842. $task = form::data($this->config->task->form->finish, $oldTask->id)
  843. ->setIF(!$this->post->realStarted && helper::isZeroDate($oldTask->realStarted), 'realStarted', $now)
  844. ->setDefault('assignedTo', $oldTask->openedBy)
  845. ->get();
  846. if(!$this->post->realStarted)
  847. {
  848. dao::$errors['realStarted'][] = sprintf($this->lang->error->notempty, $this->lang->task->realStarted);
  849. }
  850. else
  851. {
  852. $task->realStarted = date('Y-m-d H:i', strtotime($task->realStarted));
  853. }
  854. if(strpos(",{$this->config->task->finish->requiredFields},", ',comment,') !== false && empty($_POST['comment'])) dao::$errors['comment'] = sprintf($this->lang->error->notempty, $this->lang->comment);
  855. if(!$this->post->currentConsumed && $oldTask->consumed == '0') dao::$errors['currentConsumed'][] = $this->lang->task->error->consumedEmpty;
  856. if(!$this->post->finishedDate) dao::$errors['finishedDate'][] = sprintf($this->lang->error->notempty, $this->lang->task->finishedDate);
  857. if($task->realStarted > $task->finishedDate) dao::$errors['finishedDate'][] = $this->lang->task->error->finishedDateSmall;
  858. $task->consumed = $oldTask->consumed + (float)$this->post->currentConsumed;
  859. return $task;
  860. }
  861. /**
  862. * 处理完成任务的日志数据。
  863. * Process the effort data for the finish task.
  864. *
  865. * @param object $oldTask
  866. * @param object $task
  867. * @access protected
  868. * @return object
  869. */
  870. protected function buildEffortForFinish($oldTask, $task)
  871. {
  872. $consumed = (float)$this->post->currentConsumed;
  873. if($consumed < 0) dao::$errors[] = $this->lang->task->error->consumedSmall;
  874. $effort = new stdclass();
  875. $effort->date = helper::isZeroDate($task->finishedDate) ? helper::today() : substr($task->finishedDate, 0, 10);
  876. $effort->task = $oldTask->id;
  877. $effort->left = 0;
  878. $effort->work = zget($task, 'work', '');
  879. $effort->account = $this->app->user->account;
  880. $effort->consumed = $consumed;
  881. if($this->post->comment) $effort->work = $this->post->comment;
  882. return $effort;
  883. }
  884. /**
  885. * 处理关闭任务的请求数据。
  886. * Process the request data for the close task.
  887. *
  888. * @param object $oldTask
  889. * @access protected
  890. * @return object
  891. */
  892. protected function buildTaskForClose($oldTask)
  893. {
  894. $task = form::data($this->config->task->form->close, $oldTask->id)->add('id', $oldTask->id)
  895. ->setIF($oldTask->status == 'done', 'closedReason', 'done')
  896. ->setIF($oldTask->status == 'cancel', 'closedReason', 'cancel')
  897. ->get();
  898. return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->start['id'], (string)$this->post->uid);
  899. }
  900. /**
  901. * 构造图表数据。
  902. * Build chart data.
  903. *
  904. * @param string $chartType
  905. * @access protected
  906. * @return void
  907. */
  908. protected function buildChartData($chartType)
  909. {
  910. $this->loadModel('report');
  911. $chartList = array();
  912. $dataList = array();
  913. foreach($this->post->charts as $chart)
  914. {
  915. $chartFunc = 'getDataOf' . $chart;
  916. $chartData = $this->task->$chartFunc();
  917. $chartOption = $this->lang->task->report->$chart;
  918. if(!empty($chartType) and $chartType != 'default') $chartOption->type = $chartType;
  919. $this->task->mergeChartOption($chart);
  920. $chartList[$chart] = $chartOption;
  921. $dataList[$chart] = $this->report->computePercent($chartData);
  922. }
  923. $this->view->datas = $dataList;
  924. return $chartList;
  925. }
  926. /**
  927. * 任务模块的一些常用操作。
  928. * Common actions of task module.
  929. *
  930. * @param int $taskID
  931. * @access protected
  932. * @return void
  933. * @param string $vision
  934. */
  935. protected function commonAction($taskID, $vision = '')
  936. {
  937. $this->view->task = $this->task->getByID($taskID, true, $vision);
  938. $this->view->execution = $this->execution->getByID($this->view->task->execution);
  939. $this->view->members = $this->loadModel('user')->getTeamMemberPairs($this->view->execution->id, 'execution','nodeleted');
  940. $this->view->actions = $this->loadModel('action')->getList('task', $taskID);
  941. /* Set menu. */
  942. $this->setMenu($this->view->execution->id);
  943. }
  944. /**
  945. * 检查任务的开始时间和截止时间是否合法。
  946. * Check if the start and end time of the task is legal.
  947. *
  948. * @param object $task
  949. * @param bool $isDateLimit
  950. * @param object $parent
  951. * @param int|null $rowID
  952. * @access public
  953. * @return void
  954. */
  955. public function checkLegallyDate($task, $isDateLimit, $parent, $rowID = null)
  956. {
  957. $beginIndex = $rowID === null ? 'estStarted' : "estStarted[$rowID]";
  958. $endIndex = $rowID === null ? 'deadline' : "deadline[$rowID]";
  959. $beginIsZeroDate = helper::isZeroDate($task->estStarted);
  960. $endIsZeroDate = helper::isZeroDate($task->deadline);
  961. if(!$beginIsZeroDate and !$endIsZeroDate and $task->deadline < $task->estStarted) dao::$errors[$endIndex] = $this->lang->task->error->deadlineSmall;
  962. if(!$isDateLimit || empty($parent)) return;
  963. if(!$beginIsZeroDate && !helper::isZeroDate($parent->estStarted) && $task->estStarted < $parent->estStarted) dao::$errors[$beginIndex] = sprintf($this->lang->task->overParentEsStarted, $parent->estStarted);
  964. if(!$endIsZeroDate && !helper::isZeroDate($parent->deadline) && $task->deadline > $parent->deadline) dao::$errors[$endIndex] = sprintf($this->lang->task->overParentDeadline, $parent->deadline);
  965. }
  966. /**
  967. * 检查传入的创建数据是否符合要求。
  968. * Check if the input post meets the requirements.
  969. *
  970. * @param object $task
  971. * @param array $team
  972. * @access protected
  973. * @return bool
  974. */
  975. protected function checkCreateTask($task, $team)
  976. {
  977. /* Check if the estimate is positive. */
  978. if($task->estimate < 0) dao::$errors['estimate'] = $this->lang->task->error->recordMinus;
  979. if($this->post->multiple && empty($team)) dao::$errors['assignedTo'] = $this->lang->task->teamNotEmpty;
  980. if(dao::isError()) return false;
  981. /* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */
  982. if(!empty($this->config->limitTaskDate))
  983. {
  984. $this->task->checkEstStartedAndDeadline($task->execution, (string)$task->estStarted, (string)$task->deadline);
  985. if(dao::isError()) return false;
  986. }
  987. $project = $this->dao->findById($task->project)->from(TABLE_PROJECT)->fetch();
  988. $parents = $this->getParentEstStartedAndDeadline(array($task->parent));
  989. $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', isset($parents[$task->parent]) ? $parents[$task->parent] : null);
  990. return !dao::isError();
  991. }
  992. /**
  993. * 批量创建前检查必填项。
  994. * Check required fields before batch create tasks.
  995. *
  996. * @param int $executionID
  997. * @param object[] $tasks
  998. * @access protected
  999. * @return bool
  1000. */
  1001. protected function checkBatchCreateTask($executionID, $tasks)
  1002. {
  1003. /* Set required fields. */
  1004. $requiredFields = $this->config->task->create->requiredFields;
  1005. $execution = $this->loadModel('execution')->fetchById($executionID);
  1006. if($this->task->isNoStoryExecution($execution)) $requiredFields = str_replace(',story,', ',', ',' . $requiredFields . ',');
  1007. $requiredFields = array_filter(explode(',', $requiredFields));
  1008. $levels = array();
  1009. $project = $this->loadModel('project')->fetchById($execution->project);
  1010. $parentIdList = array_filter(array_column($tasks, 'parent', 'parent'));
  1011. $parents = $this->getParentEstStartedAndDeadline($parentIdList);
  1012. foreach($tasks as $rowIndex => $task)
  1013. {
  1014. $levels[$task->level] = $rowIndex;
  1015. if(mb_strlen($task->name) > 255) dao::$errors["name[$rowIndex]"] = sprintf($this->lang->task->error->length, 255);
  1016. if(!empty($this->post->estimate[$rowIndex]) and !preg_match("/^[0-9]+(.[0-9]+)?$/", (string)$this->post->estimate[$rowIndex]))
  1017. {
  1018. dao::$errors["estimate[$rowIndex]"] = $this->lang->task->error->estimateNumber;
  1019. }
  1020. /* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */
  1021. if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($executionID, (string)$task->estStarted, (string)$task->deadline);
  1022. /* Check start and end date. */
  1023. if(!helper::isZeroDate($task->deadline) && $task->deadline < $task->estStarted)
  1024. {
  1025. dao::$errors["deadline[$rowIndex]"] = $this->lang->task->error->deadlineSmall;
  1026. }
  1027. $parentTask = isset($parents[$task->parent]) ? $parents[$task->parent] : null;
  1028. if($task->level > 0 && isset($levels[$task->level - 1])) $parentTask = zget($tasks, $levels[$task->level - 1], null);
  1029. $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', $parentTask, $rowIndex);
  1030. /* Check if the estimate is positive. */
  1031. if($task->estimate < 0) dao::$errors["estimate[$rowIndex]"] = $this->lang->task->error->recordMinus;
  1032. /* Check if the required fields are empty. */
  1033. foreach($requiredFields as $field)
  1034. {
  1035. if($field == 'estimate' && $this->post->isParent[$rowIndex] == '1') continue;
  1036. if(empty($task->$field)) dao::$errors[$field . "[$rowIndex]"] = sprintf($this->lang->error->notempty, $this->lang->task->$field);
  1037. }
  1038. }
  1039. return !dao::isError();
  1040. }
  1041. /**
  1042. * 检查传入的批量编辑数据是否符合要求。
  1043. * Check if the input post meets the requirements.
  1044. *
  1045. * @param array $tasks
  1046. * @param array $oldTasks
  1047. * @access protected
  1048. * @return bool
  1049. */
  1050. protected function checkBatchEditTask($tasks, $oldTasks)
  1051. {
  1052. $oldTask = reset($oldTasks);
  1053. $project = $this->loadModel('project')->fetchById($oldTask->project);
  1054. $parentIdList = array_filter(array_column($tasks, 'parent', 'parent'));
  1055. $parents = $this->getParentEstStartedAndDeadline($parentIdList);
  1056. foreach($tasks as $taskID => $task)
  1057. {
  1058. $oldTask = $oldTasks[$taskID];
  1059. /* Check work hours. */
  1060. if(in_array($task->status, array('doing', 'pause')) && empty($oldTask->mode) && empty($task->left) && !$oldTask->isParent)
  1061. {
  1062. dao::$errors["left[{$taskID}]"] = (array)sprintf($this->lang->task->error->leftEmptyAB, zget($this->lang->task->statusList, $task->status));
  1063. }
  1064. if($task->estimate < 0) dao::$errors["estimate[$taskID]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->estimateAB);
  1065. if($task->consumed < 0 ) dao::$errors["consumed[{$taskID}]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->consumedThisTime);
  1066. if($task->left < 0) dao::$errors["left[$taskID]"] = (array)sprintf($this->lang->task->error->recordMinus, $this->lang->task->leftAB);
  1067. if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($oldTask->execution, (string)$task->estStarted, (string)$task->deadline, $taskID);
  1068. if($task->status == 'cancel') continue;
  1069. if($task->status == 'done' && !$task->consumed) dao::$errors["consumed[{$taskID}]"] = (array)sprintf($this->lang->error->notempty, $this->lang->task->consumedThisTime);
  1070. $parentTask = isset($parents[$task->parent]) ? $parents[$task->parent] : null;
  1071. if(isset($tasks[$task->parent])) $parentTask = zget($tasks, $task->parent, null);
  1072. $this->checkLegallyDate($task, $project->taskDateLimit == 'limit', $parentTask, $taskID);
  1073. }
  1074. return !dao::isError();
  1075. }
  1076. /**
  1077. * 检查关联需求的测试类型任务数据格式是否符合要求。
  1078. * Check if the test type task data format of the linked stories meets the requirements.
  1079. *
  1080. * @param object[] $tasks
  1081. * @access protected
  1082. * @return bool
  1083. */
  1084. protected function checkCreateTestTasks($tasks)
  1085. {
  1086. if(empty($tasks))
  1087. {
  1088. dao::$errors[] = $this->lang->task->error->noTestTask;
  1089. return false;
  1090. }
  1091. foreach($tasks as $rowID => $task)
  1092. {
  1093. $index = $rowID + 1;
  1094. /* Check if the estimate is positive. */
  1095. if($task->estimate < 0)
  1096. {
  1097. dao::$errors["testEstimate[{$index}]"] = sprintf($this->lang->task->error->recordMinus, $this->lang->task->estimate);
  1098. return false;
  1099. }
  1100. /* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */
  1101. if(!empty($this->config->limitTaskDate))
  1102. {
  1103. $this->task->checkEstStartedAndDeadline($task->execution, (string)$task->estStarted, (string)$task->deadline);
  1104. if(dao::isError())
  1105. {
  1106. $error = current(dao::getError());
  1107. dao::$errors["testDeadline[{$index}]"] = $error;
  1108. return false;
  1109. }
  1110. }
  1111. /* Check start and end date. */
  1112. if($task->estStarted > $task->deadline)
  1113. {
  1114. dao::$errors["testDeadline[{$index}]"] = $this->lang->task->error->deadlineSmall;
  1115. return false;
  1116. }
  1117. /* Checking the required fields of task data. */
  1118. $this->dao->insert(TABLE_TASK)->data($task)->batchCheck($this->config->task->create->requiredFields, 'notempty');
  1119. if(dao::isError())
  1120. {
  1121. $errors = dao::getError();
  1122. foreach($errors as $field => $error)
  1123. {
  1124. $fieldName = 'test' . ucfirst($field);
  1125. dao::$errors["{$fieldName}[{$index}]"] = $error;
  1126. }
  1127. return false;
  1128. }
  1129. }
  1130. return !dao::isError();
  1131. }
  1132. /**
  1133. * 检查传入的开始数据是否符合要求。
  1134. * Check if the input post meets the requirements.
  1135. *
  1136. * @param object $oldTask
  1137. * @param object $task
  1138. * @access protected
  1139. * @return bool
  1140. */
  1141. protected function checkStart($oldTask, $task)
  1142. {
  1143. $currentTeam = !empty($oldTask->team) ? $this->task->getTeamByAccount($oldTask->team) : array();
  1144. if(!empty($oldTask->team))
  1145. {
  1146. if($currentTeam && $task->consumed < $currentTeam->consumed) dao::$errors['consumed'] = $this->lang->task->error->consumedSmall;
  1147. if($currentTeam && $currentTeam->status == 'doing' && $oldTask->status == 'doing') dao::$errors[] = $this->lang->task->error->alreadyStarted;
  1148. }
  1149. else
  1150. {
  1151. if($task->consumed < $oldTask->consumed) dao::$errors['consumed'] = $this->lang->task->error->consumedSmall;
  1152. if($oldTask->status == 'doing') dao::$errors[] = $this->lang->task->error->alreadyStarted;
  1153. }
  1154. if(!$task->left && !$task->consumed) dao::$errors['message'] = $this->lang->task->noticeTaskStart;
  1155. if($task->left && $task->left < 0) dao::$errors['left'] = sprintf($this->lang->task->error->recordMinus, $this->lang->task->left);
  1156. return !dao::isError();
  1157. }
  1158. /**
  1159. * 格式化导出任务的字段。
  1160. * Format export task.
  1161. *
  1162. * @param object $task
  1163. * @param array $projects
  1164. * @param array $executions
  1165. * @param array $users
  1166. * @access protected
  1167. * @return object
  1168. */
  1169. protected function formatExportTask($task, $projects, $executions, $users)
  1170. {
  1171. $taskLang = $this->lang->task;
  1172. if($this->post->fileType == 'csv')
  1173. {
  1174. $task->desc = htmlspecialchars_decode((string)$task->desc);
  1175. $task->desc = str_replace("<br />", "\n", $task->desc);
  1176. $task->desc = str_replace('"', '""', $task->desc);
  1177. $task->desc = str_replace('&nbsp;', ' ', $task->desc);
  1178. }
  1179. if(isset($projects[$task->project])) $task->project = $projects[$task->project] . "(#$task->project)";
  1180. if(isset($executions[$task->execution])) $task->execution = $executions[$task->execution] . "(#$task->execution)";
  1181. if(isset($taskLang->typeList[$task->type])) $task->type = $taskLang->typeList[$task->type];
  1182. if(isset($taskLang->priList[$task->pri])) $task->pri = $taskLang->priList[$task->pri];
  1183. if(isset($taskLang->statusList[$task->status])) $task->status = $this->processStatus('task', $task);
  1184. if(isset($taskLang->reasonList[$task->closedReason])) $task->closedReason = $taskLang->reasonList[$task->closedReason];
  1185. if(isset($taskLang->modeList[$task->mode])) $task->mode = $taskLang->modeList[$task->mode];
  1186. if(isset($users[$task->openedBy])) $task->openedBy = $users[$task->openedBy];
  1187. if(isset($users[$task->assignedTo])) $task->assignedTo = $users[$task->assignedTo];
  1188. if(isset($users[$task->finishedBy])) $task->finishedBy = $users[$task->finishedBy];
  1189. if(isset($users[$task->canceledBy])) $task->canceledBy = $users[$task->canceledBy];
  1190. if(isset($users[$task->closedBy])) $task->closedBy = $users[$task->closedBy];
  1191. if(isset($users[$task->lastEditedBy])) $task->lastEditedBy = $users[$task->lastEditedBy];
  1192. $task->openedDate = helper::isZeroDate($task->openedDate) ? '' : substr($task->openedDate, 0, 10);
  1193. $task->assignedDate = helper::isZeroDate($task->assignedDate) ? '' : substr($task->assignedDate, 0, 10);
  1194. $task->finishedDate = helper::isZeroDate($task->finishedDate) ? '' : substr($task->finishedDate, 0, 10);
  1195. $task->canceledDate = helper::isZeroDate($task->canceledDate) ? '' : substr($task->canceledDate, 0, 10);
  1196. $task->closedDate = helper::isZeroDate($task->closedDate) ? '' : substr($task->closedDate, 0, 10);
  1197. $task->lastEditedDate = helper::isZeroDate($task->lastEditedDate) ? '' : substr($task->lastEditedDate, 0, 10);
  1198. $task->rawEstimate = $task->estimate;
  1199. $task->rawConsumed = $task->consumed;
  1200. $task->rawLeft = $task->left;
  1201. $task->estimate = $task->estimate . $this->lang->execution->workHourUnit;
  1202. $task->consumed = $task->consumed . $this->lang->execution->workHourUnit;
  1203. $task->left = $task->left . $this->lang->execution->workHourUnit;
  1204. return $task;
  1205. }
  1206. /**
  1207. * 为表单获取自定义字段。
  1208. * Get task's custom fields for form.
  1209. *
  1210. * @param object $execution
  1211. * @param string $action
  1212. * @access protected
  1213. * @return array
  1214. */
  1215. protected function getCustomFields($execution, $action)
  1216. {
  1217. /* 设置自定义字段列表。 Set custom field list. */
  1218. $customFormField = 'custom' . ucfirst($action). 'Fields';
  1219. foreach(explode(',', $this->config->task->list->{$customFormField}) as $field)
  1220. {
  1221. if($execution->type == 'stage' && strpos('estStarted,deadline', $field) !== false) continue;
  1222. $customFields[$field] = $this->lang->task->$field;
  1223. }
  1224. /* 设置已勾选的自定义字段。 Set checked custom fields. */
  1225. $checkedFields = $this->config->task->custom->{$action . 'Fields'};
  1226. if($execution->lifetime == 'ops' || $execution->attribute == 'request' || $execution->attribute == 'review')
  1227. {
  1228. unset($customFields['story']);
  1229. $checkedFields = str_replace(',story,', ',', ",{$checkedFields},");
  1230. $checkedFields = trim($checkedFields, ',');
  1231. }
  1232. return array($customFields, $checkedFields);
  1233. }
  1234. /**
  1235. * 获取导出的字段。
  1236. * Get export fields.
  1237. *
  1238. * @param string $allExportFields
  1239. * @access protected
  1240. * @return void
  1241. */
  1242. protected function getExportFields($allExportFields)
  1243. {
  1244. $fields = $this->post->exportFields ? $this->post->exportFields : explode(',', $allExportFields);
  1245. /* Compatible with the new UI widget. */
  1246. if($this->post->exportFields && strpos($fields[0], ',') !== false) $fields = explode(',', $fields[0]);
  1247. foreach($fields as $key => $fieldName)
  1248. {
  1249. $fieldName = trim($fieldName);
  1250. $fields[$fieldName] = isset($this->lang->task->$fieldName) ? $this->lang->task->$fieldName : $fieldName;
  1251. unset($fields[$key]);
  1252. }
  1253. return $fields;
  1254. }
  1255. /**
  1256. * 处理创建后选择跳转的返回信息。
  1257. * Process the return information for selecting a jump after creation.
  1258. *
  1259. * @param object $task
  1260. * @param int $executionID
  1261. * @param string $afterChoose continueAdding|toTaskList|toStoryList
  1262. * @param array $response
  1263. * @access public
  1264. * @return array
  1265. */
  1266. public function generalCreateResponse($task, $executionID, $afterChoose, $response = array())
  1267. {
  1268. /* Set the universal return value. */
  1269. if(empty($response))
  1270. {
  1271. $response['result'] = 'success';
  1272. $response['message'] = $this->lang->saveSuccess;
  1273. }
  1274. /* Set the response to continue adding task to story. */
  1275. $executionID = $task->execution;
  1276. if($afterChoose == 'continueAdding')
  1277. {
  1278. $storyID = $task->story ? $task->story : 0;
  1279. $moduleID = $task->module ? $task->module : 0;
  1280. $response['message'] = $this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding'];
  1281. $response['load'] = $this->createLink('task', 'create', "executionID={$executionID}&storyID={$storyID}&moduleID={$moduleID}");
  1282. return $response;
  1283. }
  1284. /* Set the response to return task list. */
  1285. if($afterChoose == 'toTaskList')
  1286. {
  1287. helper::setcookie('moduleBrowseParam', '0');
  1288. $response['load'] = $this->createLink('execution', 'task', "executionID={$executionID}&status=unclosed&param=0&orderBy=id_desc");
  1289. return $response;
  1290. }
  1291. /* Set the response to return story list. */
  1292. $back = $this->config->vision == 'lite' ? 'projectstory-story' : 'execution-story';
  1293. $response['callback'] = "openUrl(" . json_encode(array('back' => $back)) . ")";
  1294. return $response;
  1295. }
  1296. /**
  1297. * 检查当前用户在该执行中是否是受限用户。
  1298. * Checks if the current user is a limited user in this execution.
  1299. *
  1300. * @param int $executionID
  1301. * @access protected
  1302. * @return bool
  1303. */
  1304. protected function isLimitedInExecution($executionID)
  1305. {
  1306. if($this->app->user->admin) return false;
  1307. $limitedExecutions = $this->execution->getLimitedExecution();
  1308. if(strpos(",{$limitedExecutions},", ",$executionID,") !== false) return true;
  1309. return false;
  1310. }
  1311. /**
  1312. * 准备管理团队的数据。
  1313. * Prepare manage team data.
  1314. *
  1315. * @param form $postData
  1316. * @param int $taskID
  1317. * @access protected
  1318. * @return object
  1319. */
  1320. protected function prepareManageTeam($postData, $taskID)
  1321. {
  1322. return $postData->add('id', $taskID)
  1323. ->add('lastEditedBy', $this->app->user->account)
  1324. ->get();
  1325. }
  1326. /**
  1327. * 处理导出的任务数据。
  1328. * Process export data.
  1329. *
  1330. * @param array $tasks
  1331. * @param int $projectID
  1332. * @access protected
  1333. * @return object[]
  1334. */
  1335. protected function processExportData($tasks, $projectID)
  1336. {
  1337. /* Get users and executions. */
  1338. $users = $this->loadModel('user')->getPairs('noletter');
  1339. $projects = $this->loadModel('project')->getPairs();
  1340. $executions = $this->loadModel('execution')->fetchPairs(0, 'all', true, true);
  1341. $allExecutions = $this->loadModel('execution')->fetchPairs(0, 'all', false, true);
  1342. /* Get related objects id lists. */
  1343. $relatedStoryIdList = array();
  1344. $relatedBugIdList = array();
  1345. foreach($tasks as $task)
  1346. {
  1347. $relatedStoryIdList[$task->story] = $task->story;
  1348. $relatedBugIdList[$task->fromBug] = $task->fromBug;
  1349. }
  1350. /* Get related objects title or names. */
  1351. $relatedStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($relatedStoryIdList)->fetchPairs();
  1352. $relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('task')->andWhere('objectID')->in(array_keys($tasks))->andWhere('extra')->ne('editor')->fetchGroup('objectID');
  1353. $relatedModules = $this->loadModel('tree')->getAllModulePairs('task');
  1354. $bugs = $this->loadModel('bug')->getByIdList($relatedBugIdList);
  1355. foreach($tasks as $task)
  1356. {
  1357. $task->story = isset($relatedStories[$task->story]) ? $relatedStories[$task->story] . "(#$task->story)" : '';
  1358. $task->fromBug = empty($task->fromBug) ? '' : "#$task->fromBug " . $bugs[$task->fromBug]->title;
  1359. if(isset($relatedModules[$task->module])) $task->module = $relatedModules[$task->module] . "(#$task->module)";
  1360. if(isset($allExecutions[$task->execution]) && !isset($executions[$task->execution])) $task->execution = '';
  1361. /* Convert username to real name. */
  1362. if(!empty($task->mailto))
  1363. {
  1364. $mailtoList = explode(',', $task->mailto);
  1365. $task->mailto = '';
  1366. foreach($mailtoList as $mailto)
  1367. {
  1368. if(!empty($mailto)) $task->mailto .= ',' . zget($users, $mailto);
  1369. }
  1370. $task->mailto = trim($task->mailto, ',');
  1371. }
  1372. /* Compute task progress. */
  1373. if(!isset($task->progress))
  1374. {
  1375. if($task->consumed == 0 && $task->left == 0)
  1376. {
  1377. $task->progress = 0;
  1378. }
  1379. elseif($task->consumed != 0 && $task->left == 0)
  1380. {
  1381. $task->progress = 100;
  1382. }
  1383. else
  1384. {
  1385. $task->progress = round($task->consumed / ($task->consumed + $task->left), 2) * 100;
  1386. }
  1387. $task->progress .= '%';
  1388. }
  1389. if(!empty($task->team))
  1390. {
  1391. $task->name = '[' . $this->lang->task->multipleAB . '] ' . $task->name;
  1392. unset($task->team);
  1393. }
  1394. if($task->isParent && strpos($task->name, "[{$this->lang->task->parentAB}]") === false)
  1395. {
  1396. $task->name = '[' . $this->lang->task->parentAB . '] ' . $task->name;
  1397. }
  1398. elseif($task->parent > 0 && strpos($task->name, "[{$this->lang->task->childrenAB}]") === false)
  1399. {
  1400. $task->name = '[' . $this->lang->task->childrenAB . '] ' . $task->name;
  1401. }
  1402. $task = $this->formatExportTask($task, $projects, $executions, $users);
  1403. /* Set related files. */
  1404. $task->files = '';
  1405. if(isset($relatedFiles[$task->id]))
  1406. {
  1407. foreach($relatedFiles[$task->id] as $file)
  1408. {
  1409. $fileURL = common::getSysURL() . $this->createLink('file', 'download', "fileID={$file->id}");
  1410. $task->files .= html::a($fileURL, $file->title, '_blank') . '<br />';
  1411. }
  1412. }
  1413. }
  1414. return $tasks;
  1415. }
  1416. /**
  1417. * 处理导出任务分组信息。
  1418. * Process export task group information.
  1419. *
  1420. * @param int $executionID
  1421. * @param array $tasks
  1422. * @param string $orderBy
  1423. * @access protected
  1424. * @return object[]
  1425. */
  1426. protected function processExportGroup($executionID, $tasks, $orderBy)
  1427. {
  1428. $stories = $this->loadModel('story')->getExecutionStories($executionID);
  1429. $groupTasks = array();
  1430. foreach($tasks as $task)
  1431. {
  1432. $task->storyTitle = isset($stories[$task->story]) ? $stories[$task->story]->title : '';
  1433. if(!isset($task->team))
  1434. {
  1435. $groupTasks[$task->$orderBy][] = $task;
  1436. continue;
  1437. }
  1438. if($orderBy == 'finishedBy') $task->consumed = $task->estimate = $task->left = 0;
  1439. foreach($task->team as $team)
  1440. {
  1441. if($orderBy == 'finishedBy' and $team->left != 0)
  1442. {
  1443. $task->estimate += $team->estimate;
  1444. $task->consumed += $team->consumed;
  1445. $task->left += $team->left;
  1446. continue;
  1447. }
  1448. $cloneTask = clone $task;
  1449. $cloneTask->estimate = $team->estimate;
  1450. $cloneTask->consumed = $team->consumed;
  1451. $cloneTask->left = $team->left;
  1452. if($team->left == 0) $cloneTask->status = 'done';
  1453. if($orderBy == 'assignedTo') $cloneTask->assignedTo = $team->account;
  1454. if($orderBy == 'finishedBy') $cloneTask->finishedBy = $team->account;
  1455. $groupTasks[$team->account][] = $cloneTask;
  1456. }
  1457. if(!empty($task->left) and $orderBy == 'finishedBy') $groupTasks[$task->finishedBy][] = $task;
  1458. }
  1459. $tasks = array();
  1460. foreach($groupTasks as $groupTask)
  1461. {
  1462. foreach($groupTask as $task) $tasks[] = $task;
  1463. }
  1464. return $tasks;
  1465. }
  1466. /**
  1467. * 通过任务状态处理任务的人员、日期字段。
  1468. * Process the person and date fields of a task by status.
  1469. *
  1470. * @param object $task
  1471. * @param object $oldTask
  1472. * @access protected
  1473. * @return object
  1474. */
  1475. protected function processTaskByStatus($task, $oldTask)
  1476. {
  1477. $now = helper::now();
  1478. $currentAccount = $this->app->user->account;
  1479. switch($task->status)
  1480. {
  1481. case 'done':
  1482. $task->left = 0;
  1483. $task->finishedBy = $oldTask->status == 'done' ? $oldTask->finishedBy : $currentAccount;
  1484. $task->finishedDate = $oldTask->status == 'done' ? $oldTask->finishedDate : $now;
  1485. $task->canceledBy = '';
  1486. $task->canceledDate = null;
  1487. break;
  1488. case 'cancel':
  1489. $task->canceledBy = $oldTask->status == 'cancel' ? $oldTask->canceledBy : $currentAccount;
  1490. $task->canceledDate = $oldTask->status == 'cancel' ? $oldTask->canceledDate : $now;
  1491. $task->assignedTo = $oldTask->openedBy;
  1492. $task->assignedDate = $now;
  1493. $task->finishedBy = '';
  1494. $task->finishedDate = null;
  1495. break;
  1496. case 'closed':
  1497. $task->closedBy = $oldTask->status == 'closed' ? $oldTask->closedBy : $currentAccount;
  1498. $task->closedDate = $oldTask->status == 'closed' ? $oldTask->closedDate : $now;
  1499. if(isset($task->closedReason) && $task->closedReason == 'cancel' && isset($task->finishedDate) && helper::isZeroDate($task->finishedDate)) $task->finishedDate = null;
  1500. break;
  1501. case 'wait':
  1502. if($task->consumed > 0 and $task->left > 0) $task->status = 'doing';
  1503. if($task->consumed > 0 and $task->left == 0) // 消耗了工时,且预计剩余为0,则更新为已完成
  1504. {
  1505. $task->status = 'done';
  1506. $task->finishedBy = $currentAccount;
  1507. $task->finishedDate = helper::now();
  1508. }
  1509. if($task->left == $oldTask->left and $task->consumed == 0) $task->left = $task->estimate;
  1510. break;
  1511. case 'pause':
  1512. $task->finishedDate = null;
  1513. default:
  1514. break;
  1515. }
  1516. if(in_array($task->status, array('wait', 'doing')))
  1517. {
  1518. $task->canceledBy = '';
  1519. $task->finishedBy = '';
  1520. $task->closedBy = '';
  1521. $task->canceledDate = null;
  1522. $task->finishedDate = null;
  1523. $task->closedDate = null;
  1524. $task->closedReason = '';
  1525. }
  1526. return $task;
  1527. }
  1528. /**
  1529. * 编辑任务后返回响应.
  1530. * Response after edit.
  1531. *
  1532. * @param int $taskID
  1533. * @param string $from ''|taskkanban
  1534. * @param array[] $changes
  1535. * @param string $message
  1536. * @access protected
  1537. * @return array
  1538. */
  1539. protected function responseAfterEdit($taskID, $from, $changes, $message = '')
  1540. {
  1541. if(defined('RUN_MODE') && RUN_MODE == 'api') return array('status' => 'success', 'data' => $taskID);
  1542. $response['result'] = 'success';
  1543. $response['message'] = $message ?: $this->lang->saveSuccess;
  1544. $response['closeModal'] = true;
  1545. $task = $this->task->getById($taskID);
  1546. if($task->fromBug != 0)
  1547. {
  1548. foreach($changes as $change)
  1549. {
  1550. if($change['field'] == 'status')
  1551. {
  1552. $response['callback'] = "confirmBug('" . sprintf($this->lang->task->remindBug, $task->fromBug) . "', {$task->id}, {$task->fromBug})";
  1553. return $response;
  1554. }
  1555. }
  1556. }
  1557. if(helper::isAjaxRequest('modal')) return $this->responseModal($task, $from, $message);
  1558. $response['load'] = $this->createLink('task', 'view', "taskID=$taskID");
  1559. return $response;
  1560. }
  1561. /**
  1562. * 批量编辑任务后返回响应。
  1563. * Response after batch edit.
  1564. *
  1565. * @param array[] $allChanges
  1566. * @access protected
  1567. * @return array
  1568. */
  1569. protected function responseAfterBatchEdit($allChanges)
  1570. {
  1571. $response['result'] = 'success';
  1572. $response['message'] = $this->lang->saveSuccess;
  1573. if(!empty($allChanges))
  1574. {
  1575. foreach($allChanges as $taskID => $changes)
  1576. {
  1577. $task = $this->task->getById($taskID);
  1578. if(!$task->fromBug) continue;
  1579. foreach($changes as $change)
  1580. {
  1581. if($change['field'] == 'status')
  1582. {
  1583. $response['callback'] = "confirmBug('" . sprintf($this->lang->task->remindBug, $task->fromBug) . "', {$task->id}, {$task->fromBug})";
  1584. return $response;
  1585. }
  1586. }
  1587. }
  1588. }
  1589. $response['load'] = $this->session->taskList;
  1590. return $response;
  1591. }
  1592. /**
  1593. * 指派后返回响应.
  1594. * Response after assignto.
  1595. *
  1596. * @param int $taskID
  1597. * @param string $from ''|taskkanban
  1598. * @param string $message
  1599. * @access protected
  1600. * @return array
  1601. */
  1602. protected function responseAfterAssignTo($taskID, $from, $message = '')
  1603. {
  1604. if($this->viewType == 'json' || (defined('RUN_MODE') && RUN_MODE == 'api')) return array('result' => 'success');
  1605. $task = $this->task->getById($taskID);
  1606. if(helper::isAjaxRequest('modal')) return $this->responseModal($task, $from, $message);
  1607. $message = $message ?: $this->lang->saveSuccess;
  1608. return array('result' => 'success', 'message' => $message, 'closeModal' => true, 'load' => $this->createLink('task', 'view', "taskID=$taskID"));
  1609. }
  1610. /**
  1611. * 如果页面是弹窗,则调用此方法得到正确的返回链接。
  1612. * Get response information of a modal page.
  1613. *
  1614. * @param object $task
  1615. * @param string $from ''|taskkanban
  1616. * @param string $message
  1617. * @access protected
  1618. * @return array
  1619. */
  1620. protected function responseModal($task, $from, $message = '')
  1621. {
  1622. $response['result'] = 'success';
  1623. $response['message'] = $message ?: $this->lang->saveSuccess;
  1624. $response['closeModal'] = $this->app->rawMethod != 'recordworkhour';
  1625. if($this->app->rawMethod == 'recordworkhour')
  1626. {
  1627. $response['callback'] = "loadModal('" . inLink('recordworkhour', "taskID={$task->id}") . "', '#modal-record-hours-task-{$task->id}')";
  1628. return $response;
  1629. }
  1630. $execution = $this->loadModel('execution')->getByID((int)$task->execution);
  1631. $inLiteKanban = $this->config->vision == 'lite' && $this->app->tab == 'project' && $this->session->kanbanview == 'kanban';
  1632. if((($this->app->tab == 'execution' || $inLiteKanban) && $execution->type == 'kanban') || $from == 'taskkanban')
  1633. {
  1634. $response['callback'] = 'refreshKanban()';
  1635. return $response;
  1636. }
  1637. $response['load'] = $from != 'edittask';
  1638. return $response;
  1639. }
  1640. /**
  1641. * 处理创建任务后的返回信息。
  1642. * The information return after process the create task.
  1643. *
  1644. * @param object $taskID
  1645. * @param object $execution
  1646. * @param string $afterChoose continueAdding|toTaskList|toStoryList
  1647. * @access protected
  1648. * @return array
  1649. * @param object $task
  1650. */
  1651. protected function responseAfterCreate($task, $execution, $afterChoose)
  1652. {
  1653. /* If there is a database error, return the error message. */
  1654. if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
  1655. $message = $this->executeHooks($task->id);
  1656. $response['result'] = 'success';
  1657. $response['message'] = $message ?: $this->lang->saveSuccess;
  1658. $response['closeModal'] = true;
  1659. /* Return task id when call the API. */
  1660. if($this->viewType == 'json' || (defined('RUN_MODE') && RUN_MODE == 'api')) return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $task->id);
  1661. /* Processing the return information of pop-up windows. */
  1662. if(helper::isAjaxRequest('modal') || isonlybody())
  1663. {
  1664. /* If it is Kanban execution, refresh the Kanban statically through callback. */
  1665. if($this->app->tab == 'execution' && $execution->type == 'kanban' || $this->config->vision == 'lite')
  1666. {
  1667. $response['closeModal'] = true;
  1668. $response['callback'] = 'refreshKanban()';
  1669. return $response;
  1670. }
  1671. $response['load'] = true;
  1672. return $response;
  1673. }
  1674. /* Locate the browser. */
  1675. if($this->app->getViewType() == 'xhtml')
  1676. {
  1677. $response['load'] = $this->createLink('task', 'view', "taskID={$task->id}", 'html');
  1678. return $response;
  1679. }
  1680. if($this->config->vision == 'lite') return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => $this->createLink('execution', 'task', "executionID={$execution->id}"));
  1681. /* If it is Kanban execution, locate the kanban page. */
  1682. if($afterChoose != 'continueAdding' && $execution->type == 'kanban') return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => $this->createLink('execution', 'kanban', "executionID={$execution->id}"));
  1683. /* Process the return information for selecting a jump after creation. */
  1684. return $this->generalCreateResponse($task, $execution->id, $afterChoose, $response);
  1685. }
  1686. /**
  1687. * 处理批量创建任务后的返回信息。
  1688. * The information return after process the batch create task.
  1689. *
  1690. * @param array $taskIdList
  1691. * @param object $execution
  1692. * @access protected
  1693. * @return array
  1694. */
  1695. protected function responseAfterbatchCreate($taskIdList, $execution)
  1696. {
  1697. if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
  1698. /* Return task id when call the API. */
  1699. if($this->viewType == 'json' || (defined('RUN_MODE') && RUN_MODE == 'api')) return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $taskIdList);
  1700. $response['result'] = 'success';
  1701. $response['message'] = $this->lang->saveSuccess;
  1702. if(isInModal())
  1703. {
  1704. $response['closeModal'] = true;
  1705. $response['callback'] = 'loadCurrentPage()';
  1706. if(($execution->multiple && $this->app->tab == 'execution') || (!$execution->multiple && $this->app->tab == 'project') || $this->config->vision == 'lite') $response['callback'] = "refreshKanban()";
  1707. return $response;
  1708. }
  1709. $link = $this->createLink('execution', 'task', "executionID={$execution->id}");
  1710. if($this->app->tab == 'my') $link = $this->createLink('my', 'work', 'mode=task');
  1711. if($this->app->tab == 'project' && $execution->multiple && $this->config->vision != 'lite') $link = $this->createLink('project', 'execution', "browseType=all&projectID={$execution->project}");
  1712. if($this->app->tab == 'project' && $execution->multiple && $this->config->vision == 'lite') $link = $this->createLink('execution', 'task', "kanbanID={$execution->id}");
  1713. $response['load'] = $link;
  1714. return $response;
  1715. }
  1716. /**
  1717. * 处理开始任务后的返回信息。
  1718. * The information return after process the start task.
  1719. *
  1720. * @param object $task
  1721. * @param string $from ''|taskkanban
  1722. * @param string $message
  1723. * @access protected
  1724. * @return array
  1725. */
  1726. protected function responseAfterChangeStatus($task, $from, $message = '')
  1727. {
  1728. $message = $message ?: $this->lang->saveSuccess;
  1729. if($this->viewType == 'json' || (defined('RUN_MODE') && RUN_MODE == 'api')) return array('result' => 'success');
  1730. if(isInModal()) return $this->responseModal($task, $from, $message);
  1731. return array('result' => 'success', 'message' => $message, 'load' => true, 'closeModal' => true);
  1732. }
  1733. /**
  1734. * 在记录工时后获取跳转链接。
  1735. * Get response information after record effort.
  1736. *
  1737. * @param object $task
  1738. * @param array $changes
  1739. * @param string $from
  1740. * @access protected
  1741. * @return int|array
  1742. */
  1743. protected function responseAfterRecord($task, $changes, $from)
  1744. {
  1745. /* Remind whether to update status of the bug, if task which from that bug has been finished. */
  1746. if($changes and $this->task->needUpdateBugStatus($task))
  1747. {
  1748. $response = $this->task->getRemindBugLink($task, $changes);
  1749. if($response) return $this->send($response);
  1750. }
  1751. if(helper::isAjaxRequest('modal')) return $this->responseModal($task, $from);
  1752. $response['result'] = 'success';
  1753. $response['message'] = $this->lang->saveSuccess;
  1754. $response['load'] = $this->createLink('execution', 'browse', "executionID={$task->execution}&tab=task");
  1755. return $response;
  1756. }
  1757. /**
  1758. * 通过传入的对象ID设置任务信息。
  1759. * Set task through the input object ID.
  1760. *
  1761. * @param int $storyID
  1762. * @param int $moduleID
  1763. * @param int $taskID
  1764. * @param int $todoID
  1765. * @param int $bugID
  1766. * @param array $output feedback扩展使用
  1767. * @access public
  1768. * @return object
  1769. */
  1770. public function setTaskByObjectID($storyID, $moduleID, $taskID, $todoID, $bugID, $output = array())
  1771. {
  1772. $task = $this->config->task->create->template;
  1773. $task->module = $moduleID;
  1774. /* If exist task, copy task information by task id. */
  1775. if($taskID)
  1776. {
  1777. /* Emptying consumed hours when copy task. */
  1778. $task = $this->task->getByID($taskID);
  1779. if($task->mode == 'multi')
  1780. {
  1781. foreach($task->team as $teamMember) $teamMember->consumed = 0;
  1782. }
  1783. foreach($task->files as $file)
  1784. {
  1785. $file->name = $file->title;
  1786. $file->url = $this->createLink('file', 'download', "fileID={$file->id}");
  1787. }
  1788. }
  1789. /* If exist todo, copy todo information by todo id. */
  1790. if($todoID)
  1791. {
  1792. $todo = $this->loadModel('todo')->getById($todoID);
  1793. $task->name = $todo->name;
  1794. $task->pri = $todo->pri;
  1795. $task->desc = $todo->desc;
  1796. }
  1797. /* If exist bug, copy bug information by bug id. */
  1798. if($bugID)
  1799. {
  1800. $bug = $this->loadModel('bug')->getById($bugID);
  1801. $task->name = $bug->title;
  1802. $task->story = $bug->story;
  1803. $task->pri = !empty($bug->pri) ? $bug->pri : $this->config->task->default->pri;
  1804. $task->assignedTo = array($bug->assignedTo);
  1805. }
  1806. /* If exist story, copy story module by story id. */
  1807. if($storyID)
  1808. {
  1809. $task->story = $storyID;
  1810. $task->module = $this->dao->findByID($storyID)->from(TABLE_STORY)->fetch('module');
  1811. }
  1812. else
  1813. {
  1814. $task->module = $task->module ? $task->module : (int)$this->cookie->lastTaskModule;
  1815. }
  1816. return $task;
  1817. }
  1818. /**
  1819. * 设置任务二级导航。
  1820. * Set the task secondary navigation.
  1821. *
  1822. * @param int $executionID
  1823. * @access protected
  1824. * @return int
  1825. */
  1826. protected function setMenu($executionID)
  1827. {
  1828. $execution = $this->execution->getById($executionID);
  1829. if(!$execution || (!empty($execution) && $execution->multiple))
  1830. {
  1831. /* If the admin denied modification of closed executions, only query not closed executions. */
  1832. $queryMode = ($this->app->methodName == 'view' || ($execution && common::canModify('execution', $execution))) ? '' : 'noclosed';
  1833. /* Get executions the current user can access. */
  1834. $this->executionPairs = $this->execution->getPairs(0, 'all', $queryMode);
  1835. /* Call checkAccess method to judge the user can access the execution or not, if not return the first one he can access. */
  1836. $executionID = $this->execution->checkAccess($executionID, $this->executionPairs);
  1837. }
  1838. /* Set Menu. */
  1839. if($this->app->tab == 'project' || (!empty($execution) && !$execution->multiple))
  1840. {
  1841. $this->project->setMenu(isset($execution) ? (int)$execution->project : $this->session->project);
  1842. $this->view->projectID = $execution->project;
  1843. }
  1844. else
  1845. {
  1846. $this->execution->setMenu($executionID);
  1847. }
  1848. return $executionID;
  1849. }
  1850. /**
  1851. * 检查是否有gitlab, gitea, gogs, gitfox 代码库。
  1852. * Check gitlab, gitea, gogs, gitfox repo for execution.
  1853. *
  1854. * @param int $executionID
  1855. * @access protected
  1856. * @return bool
  1857. */
  1858. protected function checkGitRepo($executionID)
  1859. {
  1860. $this->loadModel('repo');
  1861. $repoList = $this->repo->getListBySCM(implode(',', $this->config->repo->gitServiceTypeList), 'haspriv');
  1862. $productIds = $this->loadModel('product')->getProductIDByProject($executionID, false);
  1863. foreach($repoList as $repo)
  1864. {
  1865. $linkedProducts = explode(',', $repo->product);
  1866. foreach($productIds as $productID)
  1867. {
  1868. if(in_array($productID, $linkedProducts)) return true;
  1869. }
  1870. }
  1871. return false;
  1872. }
  1873. /**
  1874. * 获取指派给配置。
  1875. * Get assigned to options.
  1876. *
  1877. * @param string $manageLink
  1878. * @access protected
  1879. * @return array
  1880. */
  1881. protected function getAssignedToOptions($manageLink)
  1882. {
  1883. $options = array();
  1884. $options['single']['multiple'] = false;
  1885. $options['single']['checkbox'] = false;
  1886. $options['single']['toolbar'] = false;
  1887. $options['multiple']['multiple'] = true;
  1888. $options['multiple']['checkbox'] = true;
  1889. $options['multiple']['toolbar'][] = array('key' => 'selectAll', 'text' => $this->lang->selectAll);
  1890. $options['multiple']['toolbar'][] = array('key' => 'cancelSelect', 'text' => $this->lang->cancelSelect);
  1891. if($manageLink)
  1892. {
  1893. $options['single']['toolbar'] = array();
  1894. $options['single']['toolbar'][] = $options['multiple']['toolbar'][] = array('className' => 'text-primary manageTeamBtn', 'key' => 'manageTeam', 'text' => $this->lang->execution->manageTeamMember, 'icon' => 'plus-solid-circle', 'url' => $manageLink, 'data-toggle' => 'modal', 'data-size' => 'lg', 'data-dismiss' => 'pick');
  1895. }
  1896. return $options;
  1897. }
  1898. /**
  1899. * 获取父任务的开始时间和截止时间。
  1900. * Get the start and end time of the parent task.
  1901. *
  1902. * @param array $parentIdList
  1903. * @access protected
  1904. * @return array
  1905. */
  1906. protected function getParentEstStartedAndDeadline($parentIdList)
  1907. {
  1908. $pathPairs = $this->dao->select('id,path')->from(TABLE_TASK)->where('id')->in($parentIdList)->fetchPairs();
  1909. if(empty($pathPairs)) return array();
  1910. $allParentIdList = array_filter(array_unique(explode(',', implode(',', $pathPairs))));
  1911. $allParents = $this->dao->select('id,estStarted,deadline')->from(TABLE_TASK)->where('id')->in($allParentIdList)->fetchAll('id');
  1912. $parents = array();
  1913. foreach($pathPairs as $parentID => $path)
  1914. {
  1915. $parent = new stdClass();
  1916. $parent->estStarted = null;
  1917. $parent->deadline = null;
  1918. foreach(array_reverse(array_filter(explode(',', $path))) as $taskID)
  1919. {
  1920. if(!isset($allParents[$taskID])) continue;
  1921. $task = $allParents[$taskID];
  1922. if(empty($parent->estStarted) && !helper::isZeroDate($task->estStarted)) $parent->estStarted = $task->estStarted;
  1923. if(empty($parent->deadline) && !helper::isZeroDate($task->deadline)) $parent->deadline = $task->deadline;
  1924. if(!empty($parent->estStarted) && !empty($parent->deadline)) break;
  1925. }
  1926. $parents[$parentID] = $parent;
  1927. }
  1928. return $parents;
  1929. }
  1930. /**
  1931. * 处理过滤条件显示内容。
  1932. * Process filter title.
  1933. *
  1934. * @param string $browseType
  1935. * @param int $param
  1936. * @access public
  1937. * @return string
  1938. */
  1939. public function processFilterTitle($browseType, $param)
  1940. {
  1941. if($browseType != 'bysearch' && $browseType != 'bymodule' && $browseType != 'byproduct')
  1942. {
  1943. $statusName = zget($this->lang->execution->featureBar['task'], $browseType, '');
  1944. if(empty($statusName)) $statusName = zget($this->lang->execution->statusSelects, $browseType);
  1945. return sprintf($this->lang->task->report->tpl->feature, $statusName);
  1946. }
  1947. if($browseType == 'byproduct' && $param)
  1948. {
  1949. $productName = $this->loadModel('product')->getById($param)->name;
  1950. return sprintf($this->lang->task->report->tpl->feature, $productName);
  1951. }
  1952. $searchConfig = $this->loadModel('search')->processSearchParams('task');
  1953. $fieldParams = $searchConfig['params'];
  1954. if($browseType == 'bymodule') return sprintf($this->lang->task->report->tpl->search, $this->config->execution->search['fields']['module'], '=', zget($fieldParams['module']['values'], $param));
  1955. $leftConditions = array();
  1956. $rightConditions = array();
  1957. $searchFields = $this->session->taskForm;
  1958. $fieldNames = $searchConfig['fields'];
  1959. if(!$searchFields) return sprintf($this->lang->task->report->tpl->feature, $this->lang->all);
  1960. $this->app->loadLang('search');
  1961. $groupAndOr = 'and';
  1962. $users = $this->loadModel('user')->getPairs('noletter');
  1963. foreach($searchFields as $index => $field)
  1964. {
  1965. if($index == 6) $groupAndOr = $field['groupAndOr'];
  1966. if(!isset($field['field'])) continue;
  1967. if(isset($field['value']) && $field['value'] === '') continue;
  1968. if(!empty($fieldParams[$field['field']]['values']))
  1969. {
  1970. if($fieldParams[$field['field']]['values'] == 'users') $fieldParams[$field['field']]['values'] = $users;
  1971. $field['value'] = zget($fieldParams[$field['field']]['values'], $field['value']);
  1972. }
  1973. $fieldName = zget($fieldNames, $field['field']);
  1974. $operator = zget($this->lang->search->operators, $field['operator']);
  1975. if($index == 0 || $index == 3) $field['andOr'] = '';
  1976. if($index < 3) $leftConditions[] = zget($this->lang->search->andor, $field['andOr']) . sprintf($this->lang->task->report->tpl->search, $fieldName, $operator, $field['value']);
  1977. elseif($index < 6) $rightConditions[] = zget($this->lang->search->andor, $field['andOr']) . sprintf($this->lang->task->report->tpl->search, $fieldName, $operator, $field['value']);
  1978. }
  1979. if(empty($leftConditions) && empty($rightConditions)) return sprintf($this->lang->task->report->tpl->feature, $this->lang->all);
  1980. if(empty($leftConditions)) return implode('', $rightConditions);
  1981. if(empty($rightConditions)) return implode('', $leftConditions);
  1982. return sprintf($this->lang->task->report->tpl->multi, implode('', $leftConditions), zget($this->lang->search->andor, $groupAndOr), implode('', $rightConditions));
  1983. }
  1984. /**
  1985. * 获取报表任务列表。
  1986. * Get report task list.
  1987. *
  1988. * @param object $execution
  1989. * @param string $browseType
  1990. * @param int $param
  1991. * @access public
  1992. * @return array
  1993. */
  1994. public function getReportTaskList($execution, $browseType = '', $param = 0)
  1995. {
  1996. if(!$execution->multiple) unset($this->lang->task->report->charts['tasksPerExecution']);
  1997. $this->loadModel('execution')->setMenu($execution->id);
  1998. if($this->app->tab == 'project') $this->view->projectID = $execution->project;
  1999. $mode = $this->app->tab == 'execution' ? 'multiple' : '';
  2000. $executions = $this->execution->getPairs(0, 'all', "nocode,noprefix,{$mode}");
  2001. /* Set queryID, moduleID and productID. */
  2002. $queryID = $moduleID = $productID = 0;
  2003. if($browseType == 'bysearch') $queryID = (int)$param;
  2004. if($browseType == 'bymodule') $moduleID = (int)$param;
  2005. if($browseType == 'byproduct') $productID = (int)$param;
  2006. return $this->execution->getTasks((int)$productID, $execution->id, $executions, $browseType, $queryID, (int)$moduleID, 'id_desc');
  2007. }
  2008. }