zen.php 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302
  1. <?php
  2. /**
  3. * The zen file of execution module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Shujie Tian<tianshujie@easycorp.ltd>
  8. * @package execution
  9. * @link https://www.zentao.net
  10. */
  11. class executionZen extends execution
  12. {
  13. /**
  14. * 展示Bug列表的相关变量。
  15. * Show the bug list related variables.
  16. *
  17. * @param object $execution
  18. * @param object $project
  19. * @param int $productID
  20. * @param string $branch
  21. * @param array $products
  22. * @param string $orderBy
  23. * @param string $type
  24. * @param string $build
  25. * @param int $param
  26. * @param array $bugs
  27. * @param object $pager
  28. * @access protected
  29. * @return void
  30. */
  31. protected function assignBugVars($execution, $project, $productID, $branch, $products, $orderBy, $type, $param, $build, $bugs, $pager)
  32. {
  33. $this->loadModel('product');
  34. $this->loadModel('tree');
  35. $moduleID = $type == 'bymodule' ? $param : 0;
  36. /* Get module tree.*/
  37. $extra = array('projectID' => $execution->id, 'orderBy' => $orderBy, 'type' => 'byModule', 'build' => $build, 'branchID' => $branch);
  38. if($execution->id and empty($productID) and count($products) > 1)
  39. {
  40. $moduleTree = $this->tree->getBugTreeMenu($execution->id, $productID, 0, array('treeModel', 'createBugLink'), $extra);
  41. }
  42. elseif(!empty($products))
  43. {
  44. $productID = empty($productID) ? reset($products)->id : $productID;
  45. $moduleTree = $this->tree->getTreeMenu((int)$productID, 'bug', 0, array('treeModel', 'createBugLink'), $extra + array('branchID' => $branch, 'productID' => $productID), $branch);
  46. }
  47. else
  48. {
  49. $moduleTree = array();
  50. }
  51. $tree = $moduleID ? $this->tree->getByID($moduleID) : '';
  52. $showModule = !empty($this->config->execution->bug->showModule) ? $this->config->execution->bug->showModule : '';
  53. $build = !empty($build) ? $this->loadModel('build')->getById((int)$build) : null;
  54. /* Assign. */
  55. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->bug;
  56. $this->view->project = $project;
  57. $this->view->orderBy = $orderBy;
  58. $this->view->type = $type;
  59. $this->view->pager = $pager;
  60. $this->view->bugs = $bugs;
  61. $this->view->summary = $this->loadModel('bug')->summary($bugs);
  62. $this->view->moduleTree = $moduleTree;
  63. $this->view->moduleID = $moduleID;
  64. $this->view->modulePairs = $showModule ? $this->tree->getModulePairs($productID, 'bug', $showModule) : array();
  65. $this->view->buildID = $build ? $build->id : 0;
  66. $this->view->productID = $productID;
  67. $this->view->product = $this->product->getByID($productID);
  68. $this->view->branchID = empty($this->view->build->branch) ? $branch : $this->view->build->branch;
  69. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  70. $this->view->param = $param;
  71. $this->view->defaultProduct = (empty($productID) and !empty($products)) ? current(array_keys($products)) : $productID;
  72. $this->view->builds = $this->loadModel('build')->getBuildPairs(array($productID));
  73. $this->view->projectPairs = $this->loadModel('project')->getPairsByProgram();
  74. $this->view->switcherObjectID = $productID;
  75. }
  76. /**
  77. * 展示看板的相关变量。
  78. * Show the variables associated with the kanban.
  79. *
  80. * @param int $executionID
  81. * @access protected
  82. * @return void
  83. */
  84. protected function assignKanbanVars($executionID)
  85. {
  86. /* Get user list. */
  87. $userList = array();
  88. $users = $this->loadModel('user')->getPairs('noletter|nodeleted');
  89. $avatarPairs = $this->user->getAvatarPairs('all');
  90. foreach($avatarPairs as $account => $avatar)
  91. {
  92. if(!isset($users[$account])) continue;
  93. $userList[$account]['realname'] = $users[$account];
  94. $userList[$account]['avatar'] = $avatar;
  95. }
  96. $userList['closed']['account'] = 'Closed';
  97. $userList['closed']['realname'] = 'Closed';
  98. $userList['closed']['avatar'] = '';
  99. /* Get execution linked products. */
  100. $productID = 0;
  101. $branchID = 0;
  102. $products = $this->loadModel('product')->getProducts($executionID);
  103. $productNames = array();
  104. if($products)
  105. {
  106. $productID = key($products);
  107. $branches = $this->loadModel('branch')->getPairs($productID, '', $executionID);
  108. if($branches) $branchID = key($branches);
  109. }
  110. foreach($products as $product) $productNames[$product->id] = $product->name;
  111. /* Get execution linked plans. */
  112. $plans = $this->execution->getPlans(array_keys($products), 'skipParent', $executionID);
  113. $allPlans = array();
  114. if(!empty($plans))
  115. {
  116. foreach($plans as $plan) $allPlans += $plan;
  117. }
  118. if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
  119. $this->view->users = $users;
  120. $this->view->userList = $userList;
  121. $this->view->productID = $productID;
  122. $this->view->branchID = $branchID;
  123. $this->view->productNames = $productNames;
  124. $this->view->productNum = count($products);
  125. $this->view->allPlans = $allPlans;
  126. $this->view->isLimited = !$this->app->user->admin && strpos(",{$this->session->limitedExecutions},", ",{$executionID},") !== false;
  127. }
  128. /**
  129. * 展示维护产品相关变量。
  130. * Show the manage products related variables.
  131. *
  132. * @param object $execution
  133. * @access protected
  134. * @return void
  135. */
  136. protected function assignManageProductsVars($execution)
  137. {
  138. $branches = $this->project->getBranchesByProject($execution->id);
  139. $linkedProductIdList = empty($branches) ? array() : array_keys($branches);
  140. $allProducts = $this->loadModel('product')->getProductPairsByProject($execution->project, 'all', implode(',', $linkedProductIdList));
  141. $linkedProducts = $this->product->getProducts($execution->id, 'all', '', true, $linkedProductIdList);
  142. $linkedBranches = array();
  143. $executionStories = $this->project->getStoriesByProject($execution->id);
  144. /* If the story of the product which linked the execution, you don't allow to remove the product. */
  145. $unmodifiableProducts = array();
  146. $unmodifiableBranches = array();
  147. $linkedStoryIDList = array();
  148. $linkedBranchIdList = array();
  149. foreach($linkedProducts as $productID => $linkedProduct)
  150. {
  151. $linkedBranches[$productID] = array();
  152. if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
  153. foreach($branches[$productID] as $branchID => $branch)
  154. {
  155. $linkedBranches[$productID][$branchID] = $branchID;
  156. $linkedBranchIdList[$branchID] = $branchID;
  157. if(!empty($executionStories[$productID][$branchID]))
  158. {
  159. array_push($unmodifiableProducts, $productID);
  160. array_push($unmodifiableBranches, $branchID);
  161. $linkedStoryIDList[$productID][$branchID] = $executionStories[$productID][$branchID]->storyIDList;
  162. }
  163. }
  164. }
  165. $this->view->title = $this->lang->execution->manageProducts . $this->lang->hyphen . $execution->name;
  166. $this->view->execution = $execution;
  167. $this->view->linkedProducts = $linkedProducts;
  168. $this->view->unmodifiableProducts = $unmodifiableProducts;
  169. $this->view->unmodifiableBranches = $unmodifiableBranches;
  170. $this->view->linkedBranches = $linkedBranches;
  171. $this->view->linkedStoryIDList = $linkedStoryIDList;
  172. $this->view->allProducts = $allProducts;
  173. $this->view->branchGroups = $this->execution->getBranchByProduct(array_keys($allProducts), $execution->project, 'ignoreNormal|noclosed', $linkedBranchIdList);
  174. $this->view->allBranches = $this->execution->getBranchByProduct(array_keys($allProducts), $execution->project, 'ignoreNormal');
  175. $this->display();
  176. }
  177. /**
  178. * 展示需求关联的任务、Bug、用例的数量以及统计需求信息。
  179. * Show the number of tasks, bugs cases linked with the stories, and statistics story information.
  180. *
  181. * @param int $executionID
  182. * @param array $stories
  183. * @param string $storyType
  184. * @access protected
  185. * @return void
  186. */
  187. protected function assignCountForStory($executionID, $stories, $storyType)
  188. {
  189. /* Get related tasks, bugs, cases count of each story. */
  190. $storyIdList = array();
  191. foreach($stories as $story)
  192. {
  193. $storyIdList[$story->id] = $story->id;
  194. if(empty($story->children)) continue;
  195. foreach($story->children as $child) $storyIdList[$child->id] = $child->id;
  196. }
  197. $this->view->stories = $stories;
  198. $this->view->storyTasks = $this->loadModel('task')->getStoryTaskCounts($storyIdList, $executionID);
  199. $this->view->storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList, $executionID);
  200. $this->view->storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
  201. $this->view->summary = $this->loadModel('product')->summary($stories, 'all');
  202. }
  203. /**
  204. * 展示需求列表的相关变量。
  205. * Show the story list related variables.
  206. *
  207. * @param object $execution
  208. * @param array $products
  209. * @param int $productID
  210. * @param string $type
  211. * @param string $storyType
  212. * @param int $param
  213. * @param string $orderBy
  214. * @param object $pager
  215. * @access protected
  216. * @return void
  217. */
  218. protected function assignRelationForStory($execution, $products, $productID, $type, $storyType, $param, $orderBy, $pager)
  219. {
  220. $plans = $this->execution->getPlans(array_keys($products), 'skipParent|withMainPlan|unexpired|noclosed|sortedByDate', $execution->id);
  221. $allPlans = array();
  222. if(!empty($plans))
  223. {
  224. foreach($plans as $plan) $allPlans += $plan;
  225. }
  226. if($this->cookie->storyProductParam) $this->view->product = $this->loadModel('product')->getById((int)$this->cookie->storyProductParam);
  227. if($this->cookie->storyBranchParam)
  228. {
  229. $branchID = $this->cookie->storyBranchParam;
  230. if(strpos($branchID, ',') !== false) list($productID, $branchID) = explode(',', $branchID);
  231. $this->view->branch = $this->loadModel('branch')->getById($branchID, $productID);
  232. }
  233. $executionProductList = $this->loadModel('product')->getProducts($execution->id);
  234. $multiBranch = false;
  235. foreach($executionProductList as $executionProduct)
  236. {
  237. if($executionProduct->type != 'normal')
  238. {
  239. $multiBranch = true;
  240. break;
  241. }
  242. }
  243. $project = $this->loadModel('project')->getByID($execution->project);
  244. if(!($execution->type == 'stage' && in_array($execution->attribute, array('mix', 'request', 'design'))) && $project->multiple) $project->storyType = 'story';
  245. $productPairs = $this->loadModel('product')->getProductPairsByProject($execution->id); // Get execution's product.
  246. if(empty($productID)) $productID = (int)key($productPairs);
  247. $this->assignModuleForStory($type, $param, $storyType, $execution, $productID);
  248. $gradeList = $this->loadModel('story')->getGradeList('');
  249. $gradeGroup = array();
  250. foreach($gradeList as $grade) $gradeGroup[$grade->type][$grade->grade] = $grade->name;
  251. /* Assign. */
  252. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->story;
  253. $this->view->storyType = $storyType;
  254. $this->view->param = $param;
  255. $this->view->type = $this->session->executionStoryBrowseType;
  256. $this->view->orderBy = $orderBy;
  257. $this->view->pager = $pager;
  258. $this->view->product = $this->product->getById($productID);
  259. $this->view->allPlans = $allPlans;
  260. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  261. $this->view->multiBranch = $multiBranch;
  262. $this->view->execution = $execution;
  263. $this->view->gradeMenu = $this->loadModel('story')->getGradeMenu($storyType, $project);
  264. $this->view->maxGradeGroup = $this->story->getMaxGradeGroup();
  265. $this->view->gradeGroup = $gradeGroup;
  266. $this->view->showGrades = isset($this->config->execution->showGrades) ? $this->config->execution->showGrades : $this->story->getDefaultShowGrades($this->view->gradeMenu);
  267. $this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
  268. $this->view->branchPairs = $this->loadModel('branch')->getPairs($productID, 'withClosed');
  269. $this->view->linkedTaskStories = $this->loadModel('story')->getIdListWithTask($execution->id);
  270. }
  271. /**
  272. * 展示需求列表的模块变量。
  273. * Show the story list module variables.
  274. *
  275. * @param string $type
  276. * @param int $param
  277. * @param string $storyType
  278. * @param object $execution
  279. * @param int $productID
  280. * @access protected
  281. * @return void
  282. */
  283. protected function assignModuleForStory($type, $param, $storyType, $execution, $productID)
  284. {
  285. $this->loadModel('tree');
  286. if($this->cookie->storyModuleParam) $this->view->module = $this->loadModel('tree')->getById((int)$this->cookie->storyModuleParam);
  287. $showModule = !empty($this->config->execution->story->showModule) ? $this->config->execution->story->showModule : '';
  288. $modulePairs = $showModule ? $this->tree->getModulePairs($type == 'byproduct' ? $param : 0, 'story', $showModule) : array();
  289. $createModuleLink = $storyType == 'story' ? 'createStoryLink' : 'createRequirementLink';
  290. if(!$execution->hasProduct && !$execution->multiple)
  291. {
  292. $moduleTree = $this->tree->getTreeMenu($productID, 'story', 0, array('treeModel', $createModuleLink), array('executionID' => $execution->id, 'productID' => $productID), '', "&param=$param&storyType=$storyType");
  293. }
  294. else
  295. {
  296. $moduleTree = $this->tree->getProjectStoryTreeMenu($execution->id, 0, array('treeModel', $createModuleLink), array('storyType' => $storyType));
  297. }
  298. $this->view->moduleTree = $moduleTree;
  299. $this->view->modulePairs = $modulePairs;
  300. $this->view->moduleID = $type == 'bymodule' ? $param : 0;
  301. }
  302. /**
  303. * 展示任务看板的相关变量。
  304. * Show the task Kanban related variables.
  305. *
  306. * @param object $execution
  307. * @access protected
  308. * @return void
  309. */
  310. protected function assignTaskKanbanVars($execution)
  311. {
  312. /* Get user list. */
  313. $userList = array();
  314. $users = $this->loadModel('user')->getPairs('noletter|nodeleted');
  315. $avatarPairs = $this->user->getAvatarPairs('all');
  316. foreach($avatarPairs as $account => $avatar)
  317. {
  318. if(!isset($users[$account])) continue;
  319. $userList[$account]['realname'] = $users[$account];
  320. $userList[$account]['avatar'] = $avatar;
  321. }
  322. $userList['closed']['account'] = 'Closed';
  323. $userList['closed']['realname'] = 'Closed';
  324. $userList['closed']['avatar'] = '';
  325. /* Get execution linked products. */
  326. $productID = 0;
  327. $productNames = array();
  328. $products = $this->loadModel('product')->getProducts($execution->id);
  329. if($products) $productID = key($products);
  330. foreach($products as $product) $productNames[$product->id] = $product->name;
  331. $plans = $this->execution->getPlans(array_keys($products), 'skipParent|withMainPlan|unexpired|noclosed|sortedByDate', $execution->id);
  332. $allPlans = array();
  333. if(!empty($plans))
  334. {
  335. foreach($plans as $plan) $allPlans += $plan;
  336. }
  337. if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
  338. $project = $this->project->getByID($execution->project);
  339. $this->view->hasFrozenStories = $this->project->hasFrozenObject($execution->project, 'SRS');
  340. $this->view->title = $this->lang->execution->kanban;
  341. $this->view->userList = $userList;
  342. $this->view->realnames = $users;
  343. $this->view->productID = $productID;
  344. $this->view->productNames = $productNames;
  345. $this->view->productNum = count($products);
  346. $this->view->allPlans = $allPlans;
  347. $this->view->hiddenPlan = $project->model !== 'scrum';
  348. $this->view->execution = $execution;
  349. $this->view->project = $project;
  350. $this->view->canBeChanged = common::canModify('execution', $execution);
  351. $this->view->isLimited = !$this->app->user->admin && strpos(",{$this->session->limitedExecutions},", ",{$execution->id},") !== false;
  352. }
  353. /**
  354. * 展示用例列表的相关变量。
  355. * Show the case list related variables.
  356. *
  357. * @param int $executionID
  358. * @param int $productID
  359. * @param string $branchID
  360. * @param int $moduleID
  361. * @param int $param
  362. * @param string $orderBy
  363. * @param string $type
  364. * @param object $pager
  365. * @access protected
  366. * @return void
  367. */
  368. protected function assignTestcaseVars($executionID, $productID, $branchID, $moduleID, $param, $orderBy, $type, $pager)
  369. {
  370. $this->loadModel('tree');
  371. /* Get cases. */
  372. $cases = $this->loadModel('testcase')->getExecutionCases($type, $executionID, $productID, $branchID, $moduleID, $param, $orderBy, $pager);
  373. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false);
  374. $cases = $this->testcase->appendData($cases, 'case');
  375. $cases = $this->loadModel('story')->checkNeedConfirm($cases);
  376. /* Get module tree.*/
  377. if($executionID and empty($productID))
  378. {
  379. $moduleTree = $this->tree->getCaseTreeMenu($executionID, $productID, 0, array('treeModel', 'createCaseLink'));
  380. }
  381. else
  382. {
  383. $moduleTree = $this->tree->getTreeMenu($productID, 'case', 0, array('treeModel', 'createCaseLink'), array('projectID' => $executionID, 'productID' => $productID, 'branchID' => $branchID), $branchID);
  384. }
  385. $tree = $moduleID ? $this->tree->getByID($moduleID) : '';
  386. $showModule = !empty($this->config->execution->testcase->showModule) ? $this->config->execution->testcase->showModule : '';
  387. $this->view->cases = $cases;
  388. $this->view->scenes = $this->testcase->getSceneMenu($productID, $moduleID);;
  389. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  390. $this->view->title = $this->lang->execution->testcase;
  391. $this->view->executionID = $executionID;
  392. $this->view->productID = $productID;
  393. $this->view->product = $this->loadModel('product')->getByID((int)$productID);
  394. $this->view->orderBy = $orderBy;
  395. $this->view->pager = $pager;
  396. $this->view->type = $type;
  397. $this->view->branchID = $branchID;
  398. $this->view->branchTagOption = $this->loadModel('branch')->getPairs($productID, 'withClosed');
  399. $this->view->recTotal = $pager->recTotal;
  400. $this->view->showBranch = $this->loadModel('branch')->showBranch($productID);
  401. $this->view->stories = array( 0 => '') + $this->loadModel('story')->getPairs($productID);
  402. $this->view->moduleTree = $moduleTree;
  403. $this->view->moduleID = $moduleID;
  404. $this->view->moduleName = $moduleID ? $tree->name : $this->lang->tree->all;
  405. $this->view->modulePairs = $showModule ? $this->tree->getModulePairs($productID, 'bug', $showModule) : array();
  406. }
  407. /**
  408. * 展示测试单的相关变量。
  409. * Show the testtask related variables.
  410. *
  411. * @param array $tasks
  412. * @access protected
  413. * @return void
  414. */
  415. protected function assignTesttaskVars($tasks)
  416. {
  417. /* Compute rowspan. */
  418. $productGroup = array();
  419. $waitCount = 0;
  420. $testingCount = 0;
  421. $blockedCount = 0;
  422. $doneCount = 0;
  423. foreach($tasks as $task)
  424. {
  425. $productGroup[$task->product][] = $task;
  426. if($task->status == 'wait') $waitCount ++;
  427. if($task->status == 'doing') $testingCount ++;
  428. if($task->status == 'blocked') $blockedCount ++;
  429. if($task->status == 'done') $doneCount ++;
  430. if($task->build == 'trunk' || empty($task->buildName)) $task->buildName = $this->lang->trunk;
  431. }
  432. $lastProduct = '';
  433. foreach($tasks as $taskID => $task)
  434. {
  435. $task->rawStatus = $task->status;
  436. $task->status = $this->processStatus('testtask', $task);
  437. $task->rowspan = 0;
  438. if($lastProduct !== $task->product)
  439. {
  440. $lastProduct = $task->product;
  441. if(!empty($productGroup[$task->product])) $task->rowspan = count($productGroup[$task->product]);
  442. }
  443. }
  444. $this->view->waitCount = $waitCount;
  445. $this->view->testingCount = $testingCount;
  446. $this->view->blockedCount = $blockedCount;
  447. $this->view->doneCount = $doneCount;
  448. $this->view->tasks = $tasks;
  449. }
  450. /**
  451. * 展示执行详情的相关变量。
  452. * Show the view related variables.
  453. *
  454. * @param int $executionID
  455. * @access protected
  456. * @return void
  457. */
  458. protected function assignViewVars($executionID)
  459. {
  460. $this->executeHooks($executionID);
  461. $userPairs = array();
  462. $userList = array();
  463. $users = $this->loadModel('user')->getList('all');
  464. foreach($users as $user)
  465. {
  466. $userList[$user->account] = $user;
  467. $userPairs[$user->account] = $user->realname;
  468. }
  469. /* Get linked branches. */
  470. $branches = $this->project->getBranchesByProject($executionID);
  471. $linkedProductIdList = empty($branches) ? '' : array_keys($branches);
  472. $products = $this->loadModel('product')->getProducts($executionID, 'all', '', true, $linkedProductIdList, false);
  473. $linkedBranches = array();
  474. foreach($products as $product)
  475. {
  476. if(isset($product->branches))
  477. {
  478. foreach($product->branches as $branchID) $linkedBranches[$branchID] = $branchID;
  479. }
  480. }
  481. $this->view->users = $userPairs;
  482. $this->view->userList = $userList;
  483. $this->view->products = $products;
  484. $this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'ignoreNormal', $linkedBranches);
  485. $this->view->planGroups = $this->execution->getPlans(array_keys($products));
  486. $this->view->actions = $this->loadModel('action')->getList('execution', $executionID);
  487. $this->view->dynamics = $this->loadModel('action')->getDynamic('all', 'all', 'date_desc', 50, 'all', 'all', $executionID);
  488. $this->view->teamMembers = $this->execution->getTeamMembers($executionID);
  489. $this->view->docLibs = $this->loadModel('doc')->getLibsByObject('execution', $executionID);
  490. $this->view->statData = $this->execution->statRelatedData($executionID);
  491. }
  492. /**
  493. * 构造任务分组视图数据。
  494. * Build task group data.
  495. *
  496. * @param string $groupBy story|status|pri|assignedTo|finishedBy|closedBy|type
  497. * @param array $tasks
  498. * @param array $users
  499. * @access protected
  500. * @return array
  501. */
  502. protected function buildGroupTasks($groupBy = 'story', $tasks = array(), $users = array())
  503. {
  504. $groupTasks = array();
  505. $groupByList = array();
  506. foreach($tasks as $task)
  507. {
  508. if($groupBy == 'story')
  509. {
  510. $groupTasks[$task->story][] = $task;
  511. $groupByList[$task->story] = $task->storyTitle;
  512. }
  513. elseif($groupBy == 'status')
  514. {
  515. $statusList = arrayUnion($this->lang->task->statusList, array('changed' => $this->lang->task->storyChange));
  516. $groupTasks[$statusList[$task->status]][] = $task;
  517. }
  518. elseif($groupBy == 'assignedTo')
  519. {
  520. if(isset($task->team))
  521. {
  522. $groupTasks = $this->buildGroupMultiTask($groupBy, $task, $users, $groupTasks);
  523. }
  524. else
  525. {
  526. $groupTasks[$task->assignedToRealName][] = $task;
  527. }
  528. }
  529. elseif($groupBy == 'finishedBy')
  530. {
  531. if(isset($task->team))
  532. {
  533. $task->consumed = $task->estimate = $task->left = 0;
  534. $groupTasks = $this->buildGroupMultiTask($groupBy, $task, $users, $groupTasks);
  535. }
  536. else
  537. {
  538. $groupTasks[$users[$task->finishedBy]][] = $task;
  539. }
  540. }
  541. elseif($groupBy == 'closedBy')
  542. {
  543. $groupTasks[$users[$task->closedBy]][] = $task;
  544. }
  545. elseif($groupBy == 'type')
  546. {
  547. $groupTasks[$this->lang->task->typeList[$task->type]][] = $task;
  548. }
  549. else
  550. {
  551. $groupTasks[$task->$groupBy][] = $task;
  552. }
  553. }
  554. /* Process closed data when group by assignedTo. */
  555. if($groupBy == 'assignedTo' && isset($groupTasks['Closed']))
  556. {
  557. $closedTasks = $groupTasks['Closed'];
  558. unset($groupTasks['Closed']);
  559. $groupTasks['closed'] = $closedTasks;
  560. }
  561. return array($groupTasks, $groupByList);
  562. }
  563. /**
  564. * 构建多人任务的分组视图数据。
  565. * Build group data for multiple task.
  566. *
  567. * @param string $groupBy
  568. * @param object $task
  569. * @param array $users
  570. * @param array $groupTasks
  571. * @access protected
  572. * @return array
  573. */
  574. protected function buildGroupMultiTask($groupBy, $task, $users, $groupTasks)
  575. {
  576. foreach($task->team as $team)
  577. {
  578. if($team->left != 0 && $groupBy == 'finishedBy')
  579. {
  580. $task->estimate += $team->estimate;
  581. $task->consumed += $team->consumed;
  582. $task->left += $team->left;
  583. continue;
  584. }
  585. $cloneTask = clone $task;
  586. $cloneTask->{$groupBy} = $team->account;
  587. $cloneTask->estimate = $team->estimate;
  588. $cloneTask->consumed = $team->consumed;
  589. $cloneTask->left = $team->left;
  590. if($team->left == 0 || $groupBy == 'finishedBy') $cloneTask->status = 'done';
  591. $realname = zget($users, $team->account);
  592. $cloneTask->assignedToRealName = $realname;
  593. $groupTasks[$realname][] = $cloneTask;
  594. }
  595. if($groupBy == 'finishedBy' && !empty($task->left)) $groupTasks[$users[$task->finishedBy]][] = $task;
  596. return $groupTasks;
  597. }
  598. /**
  599. * 构建需求列表的搜索表单数据。
  600. * Build the search form data to story list.
  601. *
  602. * @param object $execution
  603. * @param int $productID
  604. * @param array $products
  605. * @param int $queryID
  606. * @param string $actionURL
  607. * @access protected
  608. * @return void
  609. */
  610. protected function buildStorySearchForm($execution, $productID, $products, $queryID, $actionURL)
  611. {
  612. $modules = array();
  613. $productModules = array();
  614. $executionModules = $this->loadModel('tree')->getTaskTreeModules($execution->id, true);
  615. if($productID)
  616. {
  617. $product = $products[$productID];
  618. $productModules = $this->tree->getOptionMenu($productID, 'story', 0, $product->branches);
  619. }
  620. else
  621. {
  622. foreach($products as $product) $productModules += $this->tree->getOptionMenu($product->id, 'story', 0, $product->branches);
  623. }
  624. if(commonModel::isTutorialMode())
  625. {
  626. $modules = $this->loadModel('tutorial')->getModulePairs();
  627. }
  628. else
  629. {
  630. foreach($productModules as $branchID => $moduleList)
  631. {
  632. foreach($moduleList as $moduleID => $moduleName)
  633. {
  634. if($moduleID && !isset($executionModules[$moduleID])) continue;
  635. $modules[$moduleID] = ((count($products) >= 2 && $moduleID) ? $product->name : '') . $moduleName;
  636. }
  637. }
  638. }
  639. $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
  640. $this->execution->buildStorySearchForm($products, $branchGroups, $modules, $queryID, $actionURL, 'executionStory', $execution);
  641. }
  642. /**
  643. * 将导入的Bug转为任务。
  644. * Change imported bugs to the tasks.
  645. *
  646. * @param object $execution
  647. * @param array $postData
  648. * @access protected
  649. * @return array
  650. */
  651. protected function buildTasksForImportBug($execution, $postData)
  652. {
  653. $this->loadModel('task');
  654. $tasks = array();
  655. $bugs = $this->loadModel('bug')->getByIdList(array_keys($postData));
  656. $showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
  657. $modules = $this->loadModel('tree')->getTaskOptionMenu($execution->id, 0, $showAllModule ? 'allModule' : '');
  658. $now = helper::now();
  659. $requiredFields = str_replace(array(',story,', ',module,'), ',', ',' . $this->config->task->create->requiredFields . ',');
  660. $requiredFields = trim($requiredFields, ',');
  661. foreach($postData as $bugID => $task)
  662. {
  663. $bug = zget($bugs, $bugID, '');
  664. if(empty($bug)) continue;
  665. unset($task->id);
  666. $task->bug = $bug;
  667. $task->project = $execution->project;
  668. $task->execution = $execution->id;
  669. $task->story = $bug->story;
  670. $task->storyVersion = $bug->storyVersion;
  671. $task->module = isset($modules[$bug->module]) ? $bug->module : 0;
  672. $task->fromBug = $bugID;
  673. $task->name = $bug->title;
  674. $task->type = 'devel';
  675. $task->consumed = 0;
  676. $task->status = 'wait';
  677. $task->openedDate = $now;
  678. $task->openedBy = $this->app->user->account;
  679. $task->estStarted = $task->estStarted ? $task->estStarted : null;
  680. $task->deadline = $task->deadline ? $task->deadline : null;
  681. if($task->estimate !== '') $task->left = $task->estimate;
  682. if(!empty($task->assignedTo)) $task->assignedDate = $now;
  683. /* Check task required fields. */
  684. foreach(explode(',', $requiredFields) as $field)
  685. {
  686. if(empty($field)) continue;
  687. if(!isset($task->$field)) continue;
  688. if(!empty($task->$field)) continue;
  689. dao::$errors["{$field}[{$bugID}]"] = 'ID: ' . $bugID . sprintf($this->lang->error->notempty, $this->lang->task->$field);
  690. return false;
  691. }
  692. if(!preg_match("/^[0-9]+(.[0-9]+)?$/", (string)$task->estimate) and !empty($task->estimate))
  693. {
  694. dao::$errors["{$field}[{$bugID}]"] = 'ID: ' . $bugID . $this->lang->task->error->estimateNumber;
  695. return false;
  696. }
  697. if(!helper::isZeroDate($task->deadline) && $task->estStarted > $task->deadline)
  698. {
  699. dao::$errors["deadline[{$bugID}]"] = 'ID: ' . $bugID . $this->lang->task->error->deadlineSmall;
  700. return false;
  701. }
  702. if(!empty($this->config->limitTaskDate))
  703. {
  704. $this->task->checkEstStartedAndDeadline($execution->id, (string)$task->estStarted, (string)$task->deadline);
  705. if(dao::isError()) return false;
  706. }
  707. $tasks[$bugID] = $task;
  708. }
  709. return $tasks;
  710. }
  711. /**
  712. * 构建导入Bug的搜索表单数据。
  713. * Build the search form data to import the Bug.
  714. *
  715. * @param object $execution
  716. * @param int $queryID
  717. * @param array $products
  718. * @param array $executions
  719. * @param array $projects
  720. * @access protected
  721. * @return void
  722. */
  723. protected function buildImportBugSearchForm($execution, $queryID, $products, $executions, $projects)
  724. {
  725. $project = $this->loadModel('project')->getByID($execution->project);
  726. $this->config->bug->search['actionURL'] = $this->createLink('execution', 'importBug', "executionID=$execution->id&browseType=bySearch&param=myQueryID");
  727. $this->config->bug->search['queryID'] = $queryID;
  728. if(!empty($products))
  729. {
  730. $this->config->bug->search['params']['product']['values'] = array(''=>'') + $products + array('all'=>$this->lang->execution->aboveAllProduct);
  731. }
  732. else
  733. {
  734. $this->config->bug->search['params']['product']['values'] = array(''=>'');
  735. }
  736. if(!$execution->multiple)
  737. {
  738. unset($this->config->bug->search['fields']['execution'], $this->config->bug->search['params']['execution']);
  739. }
  740. else
  741. {
  742. $this->config->bug->search['params']['execution']['values'] = array(''=>'') + $executions + array('all'=>$this->lang->execution->aboveAllExecution);
  743. }
  744. $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs(array_keys($products));
  745. $this->config->bug->search['module'] = 'importBug';
  746. $this->config->bug->search['params']['confirmed']['values'] = $this->lang->bug->confirmedList;
  747. $this->loadModel('tree');
  748. $bugModules = array();
  749. foreach($products as $productID => $productName)
  750. {
  751. $productModules = $this->tree->getOptionMenu($productID, 'bug', 0, 'all');
  752. foreach($productModules as $moduleID => $moduleName)
  753. {
  754. if(empty($moduleID))
  755. {
  756. $bugModules[$moduleID] = $moduleName;
  757. continue;
  758. }
  759. $bugModules[$moduleID] = $productName . $moduleName;
  760. }
  761. }
  762. $this->config->bug->search['params']['module']['values'] = $bugModules;
  763. $this->config->bug->search['params']['project']['values'] = $projects;
  764. $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array_keys($products), 'all', 'withbranch|releasetag');
  765. unset($this->config->bug->search['fields']['resolvedBy']);
  766. unset($this->config->bug->search['fields']['closedBy']);
  767. unset($this->config->bug->search['fields']['status']);
  768. unset($this->config->bug->search['fields']['toTask']);
  769. unset($this->config->bug->search['fields']['toStory']);
  770. unset($this->config->bug->search['fields']['resolution']);
  771. unset($this->config->bug->search['fields']['resolvedBuild']);
  772. unset($this->config->bug->search['fields']['resolvedDate']);
  773. unset($this->config->bug->search['fields']['closedDate']);
  774. unset($this->config->bug->search['fields']['branch']);
  775. if(empty($execution->multiple) && empty($execution->hasProduct)) unset($this->config->bug->search['fields']['plan']);
  776. if(empty($project->hasProduct))
  777. {
  778. unset($this->config->bug->search['fields']['product']);
  779. if($project->model !== 'scrum') unset($this->config->bug->search['fields']['plan']);
  780. }
  781. unset($this->config->bug->search['params']['resolvedBy']);
  782. unset($this->config->bug->search['params']['closedBy']);
  783. unset($this->config->bug->search['params']['status']);
  784. unset($this->config->bug->search['params']['toTask']);
  785. unset($this->config->bug->search['params']['toStory']);
  786. unset($this->config->bug->search['params']['resolution']);
  787. unset($this->config->bug->search['params']['resolvedBuild']);
  788. unset($this->config->bug->search['params']['resolvedDate']);
  789. unset($this->config->bug->search['params']['closedDate']);
  790. unset($this->config->bug->search['params']['branch']);
  791. $this->loadModel('search')->setSearchParams($this->config->bug->search);
  792. }
  793. /**
  794. * 检查创建的表单数据。
  795. * Check the form data for create.
  796. *
  797. * @access protected
  798. * @return bool
  799. */
  800. protected function checkPostForCreate()
  801. {
  802. if(empty($_POST['project']))
  803. {
  804. dao::$errors['project'] = $this->lang->execution->projectNotEmpty;
  805. return false;
  806. }
  807. $projectID = (int)$_POST['project'];
  808. $project = $this->loadModel('project')->fetchByID($projectID);
  809. $this->execution->checkBeginAndEndDate($projectID, $_POST['begin'], $_POST['end'], isset($_POST['parent']) ? (int)$this->post->parent : $projectID);
  810. if(dao::isError()) return false;
  811. /* Judge workdays is legitimate. */
  812. $workdays = helper::diffDate($_POST['end'], $_POST['begin']) + 1;
  813. if(isset($_POST['days']) && $_POST['days'] > $workdays)
  814. {
  815. dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
  816. return false;
  817. }
  818. if(!empty($_POST['products']))
  819. {
  820. $_POST['products'] = array_filter($_POST['products']);
  821. $this->app->loadLang('project');
  822. $multipleProducts = $this->loadModel('product')->getMultiBranchPairs();
  823. foreach($_POST['products'] as $index => $productID)
  824. {
  825. if(empty($_POST['branch'][$index])) continue;
  826. $branches = implode(',', $_POST['branch'][$index]);
  827. if(isset($multipleProducts[$productID]) && $branches == '')
  828. {
  829. dao::$errors["branch[{$index}][]"] = $this->lang->project->error->emptyBranch;
  830. return false;
  831. }
  832. }
  833. }
  834. /* Determine whether to add a sprint or a stage according to the model of the execution. */
  835. if($project->model == 'waterfall' || $project->model == 'waterfallplus')
  836. {
  837. if(empty($_POST['products'])) dao::$errors['products[0]'] = $this->lang->project->errorNoProducts;
  838. if(dao::isError()) return false;
  839. if(isset($this->config->setPercent) && $this->config->setPercent == 1)
  840. {
  841. $this->execution->checkWorkload('create', (int)$this->post->percent, $project);
  842. if(dao::isError()) return false;
  843. }
  844. }
  845. return true;
  846. }
  847. /**
  848. * 构造创建执行的数据。
  849. * Build the data for create execution.
  850. *
  851. * @access protected
  852. * @return object|false
  853. */
  854. protected function buildExecutionForCreate()
  855. {
  856. $now = helper::now();
  857. $project = $this->loadModel('project')->fetchByID((int)$_POST['project']);
  858. $type = 'sprint';
  859. if($project) $type = zget($this->config->execution->modelList, $project->model, 'sprint');
  860. $fields = $this->config->execution->form->create;
  861. foreach(explode(',', trim($this->config->execution->create->requiredFields, ',')) as $field) $fields[$field]['required'] = true;
  862. if(!isset($_POST['code'])) $fields['code']['required'] = false;
  863. if(!isset($_POST['percent'])) $fields['percent']['required'] = false;
  864. $this->config->execution->create->requiredFields = implode(',', array_keys(array_filter(array_map(function($config){return $config['required'] == true;}, $fields))));
  865. $this->correctErrorLang();
  866. $execution = form::data($fields)
  867. ->setDefault('openedBy', $this->app->user->account)
  868. ->setDefault('openedDate', $now)
  869. ->setDefault('lastEditedBy', $this->app->user->account)
  870. ->setDefault('lastEditedDate', $now)
  871. ->setDefault('type', $type)
  872. ->setDefault('team', $this->post->name)
  873. ->setDefault('parent', $this->post->project)
  874. ->setDefault('isTpl', $project->isTpl)
  875. ->setIF($this->post->parent, 'parent', $this->post->parent)
  876. ->setIF($this->post->heightType == 'auto', 'displayCards', 0)
  877. ->setIF($this->post->acl == 'open', 'whitelist', '')
  878. ->setIF($this->post->type, 'type', $this->post->type)
  879. ->get();
  880. if(!empty($execution->parent) && ($execution->project == $execution->parent)) $execution->hasProduct = $project->hasProduct;
  881. if(!empty($execution->parent) && empty($execution->attribute))
  882. {
  883. /* 子阶段与父阶段同类型。 */
  884. $parent = $this->execution->fetchByID((int)$execution->parent);
  885. $execution->attribute = $parent->attribute;
  886. }
  887. if($this->post->heightType == 'custom' && !$this->loadModel('kanban')->checkDisplayCards($execution->displayCards)) return false;
  888. if(!$this->checkPostForCreate()) return false;
  889. /* Set planDuration and realDuration. */
  890. if(in_array($this->config->edition, array('max', 'ipd')))
  891. {
  892. $execution->planDuration = $this->loadModel('programplan')->getDuration($execution->begin, $execution->end);
  893. if(!empty($execution->realBegan) && !empty($execution->realEnd)) $execution->realDuration = $this->programplan->getDuration($execution->realBegan, $execution->realEnd);
  894. }
  895. return $this->loadModel('file')->processImgURL($execution, $this->config->execution->editor->create['id'], $this->post->uid);
  896. }
  897. /**
  898. * 检查累积流图的日期。
  899. * Check Cumulative flow diagram date.
  900. *
  901. * @param string $begin
  902. * @param string $end
  903. * @param string $minDate
  904. * @param string $maxDate
  905. * @access protected
  906. * @return bool
  907. */
  908. protected function checkCFDDate($begin, $end, $minDate, $maxDate)
  909. {
  910. $dateError = array();
  911. if(empty($begin)) $dateError[] = sprintf($this->lang->error->notempty, $this->lang->execution->charts->cfd->begin);
  912. if(empty($end)) $dateError[] = sprintf($this->lang->error->notempty, $this->lang->execution->charts->cfd->end);
  913. if(empty($dateError))
  914. {
  915. if($begin < $minDate) $dateError[] = sprintf($this->lang->error->gt, $this->lang->execution->charts->cfd->begin, $minDate);
  916. if($begin > $maxDate) $dateError[] = sprintf($this->lang->error->lt, $this->lang->execution->charts->cfd->begin, $maxDate);
  917. if($end < $minDate) $dateError[] = sprintf($this->lang->error->gt, $this->lang->execution->charts->cfd->end, $minDate);
  918. if($end > $maxDate) $dateError[] = sprintf($this->lang->error->lt, $this->lang->execution->charts->cfd->end, $maxDate);
  919. }
  920. foreach($dateError as $index => $error)
  921. {
  922. dao::$errors = str_replace(array('。', '.'), array('', ''), $error);
  923. return false;
  924. }
  925. if($begin >= $end)
  926. {
  927. dao::$errors = $this->lang->execution->charts->cfd->errorBegin;
  928. return false;
  929. }
  930. if(date("Y-m-d", strtotime("-3 months", strtotime($end))) > $begin)
  931. {
  932. dao::$errors = $this->lang->execution->charts->cfd->errorDateRange;
  933. return false;
  934. }
  935. return true;
  936. }
  937. /**
  938. * 处理版本列表展示数据。
  939. * Process build list display data.
  940. *
  941. * @param array $buildList
  942. * @param int $executionID
  943. * @access protected
  944. * @return object[]
  945. */
  946. protected function processBuildListData($buildList, $executionID = 0)
  947. {
  948. $this->loadModel('build');
  949. $productIdList = array();
  950. foreach($buildList as $build) $productIdList[$build->product] = $build->product;
  951. /* Get branch name. */
  952. $showBranch = false;
  953. $branchGroups = $this->loadModel('branch')->getByProducts($productIdList);
  954. $builds = array();
  955. foreach($buildList as $build)
  956. {
  957. $build->branchName = '';
  958. if(isset($branchGroups[$build->product]))
  959. {
  960. $showBranch = true;
  961. $branchPairs = $branchGroups[$build->product];
  962. foreach(explode(',', trim($build->branch, ',')) as $branchID)
  963. {
  964. if(isset($branchPairs[$branchID])) $build->branchName .= "{$branchPairs[$branchID]},";
  965. }
  966. $build->branchName = trim($build->branchName, ',');
  967. }
  968. if($build->scmPath && $build->filePath)
  969. {
  970. $build->rowspan = 2;
  971. $buildInfo = clone $build;
  972. $buildInfo->pathType = 'scmPath';
  973. $buildInfo->path = $build->scmPath;
  974. $builds[] = $buildInfo;
  975. $buildInfo = clone $build;
  976. $buildInfo->pathType = 'filePath';
  977. $buildInfo->path = $build->filePath;
  978. $builds[] = $buildInfo;
  979. }
  980. else
  981. {
  982. $build->pathType = empty($build->scmPath) ? 'filePath' : 'scmPath';
  983. $build->path = empty($build->scmPath) ? $build->filePath : $build->scmPath;
  984. $builds[] = $build;
  985. }
  986. if(!$build->system) $build->system = '';
  987. }
  988. if(!$showBranch) unset($this->config->build->dtable->fieldList['branch']);
  989. unset($this->config->build->dtable->fieldList['execution']);
  990. return $builds;
  991. }
  992. /**
  993. * 构建产品下拉选择数据。
  994. * Build product drop-down select data.
  995. *
  996. * @param int $executionID
  997. * @param int $productID
  998. * @param object[] $products
  999. * @access protected
  1000. * @return array
  1001. */
  1002. protected function buildProductSwitcher($executionID, $productID, $products)
  1003. {
  1004. $productOption = array();
  1005. $branchOption = array();
  1006. $programIdList = array();
  1007. if(count($products) > 1) $productOption[0] = $this->lang->product->all;
  1008. foreach($products as $productData) $programIdList[$productData->program] = $productData->program;
  1009. $programPairs = $this->loadModel('program')->getPairsByList($programIdList);
  1010. $linePairs = $this->loadModel('product')->getLinePairs($programIdList);
  1011. foreach($products as $productData)
  1012. {
  1013. $programName = isset($programPairs[$productData->program]) ? $programPairs[$productData->program] . ' / ' : '';
  1014. $lineName = isset($linePairs[$productData->line]) ? $linePairs[$productData->line] . ' / ' : '';
  1015. $productOption[$productData->id] = $programName . $lineName . $productData->name;
  1016. }
  1017. $product = $this->product->getById((int)$productID);
  1018. if($product and $product->type != 'normal')
  1019. {
  1020. /* Display status of branch. */
  1021. $branches = $this->loadModel('branch')->getList($productID, $executionID, 'all');
  1022. foreach($branches as $branchInfo)
  1023. {
  1024. $branchOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
  1025. }
  1026. }
  1027. return array($productOption, $branchOption);
  1028. }
  1029. /**
  1030. * 构建执行团队成员信息。
  1031. * Build execution team member information.
  1032. *
  1033. * @param array $currentMembers
  1034. * @param array $members2Import
  1035. * @param array $deptUsers
  1036. * @param int $days
  1037. * @access protected
  1038. * @return array
  1039. */
  1040. protected function buildMembers($currentMembers, $members2Import, $deptUsers, $days)
  1041. {
  1042. $teamMembers = array();
  1043. foreach($currentMembers as $account => $member)
  1044. {
  1045. $member->memberType = 'default';
  1046. $teamMembers[$account] = $member;
  1047. }
  1048. foreach($members2Import as $account => $member2Import)
  1049. {
  1050. $member2Import->memberType = 'import';
  1051. $member2Import->days = $days;
  1052. $member2Import->limited = 'no';
  1053. $teamMembers[$account] = $member2Import;
  1054. }
  1055. $roles = $this->loadModel('user')->getUserRoles(array_keys($deptUsers));
  1056. foreach($deptUsers as $deptAccount => $userName)
  1057. {
  1058. if(isset($currentMembers[$deptAccount]) || isset($members2Import[$deptAccount])) continue;
  1059. $deptMember = new stdclass();
  1060. $deptMember->memberType = 'dept';
  1061. $deptMember->account = $deptAccount;
  1062. $deptMember->role = zget($roles, $deptAccount, '');
  1063. $deptMember->days = $days;
  1064. $deptMember->hours = $this->config->execution->defaultWorkhours;
  1065. $deptMember->limited = 'no';
  1066. $teamMembers[$deptAccount] = $deptMember;
  1067. }
  1068. for($j = 0; $j < 5; $j ++)
  1069. {
  1070. $newMember = new stdclass();
  1071. $newMember->memberType = 'add';
  1072. $newMember->account = '';
  1073. $newMember->role = '';
  1074. $newMember->days = $days;
  1075. $newMember->hours = $this->config->execution->defaultWorkhours;
  1076. $newMember->limited = 'no';
  1077. $teamMembers[] = $newMember;
  1078. }
  1079. return $teamMembers;
  1080. }
  1081. /**
  1082. * 构造待更新的团队成员数据。
  1083. * Construct the team member data to be updated.
  1084. *
  1085. * @param object $execution
  1086. * @access protected
  1087. * @return array
  1088. */
  1089. protected function buildMembersForManageMembers($execution)
  1090. {
  1091. $members = form::batchData()->get();
  1092. $oldMemberDays = $this->dao->select('`account`, `days`')->from(TABLE_TEAM)->where('root')->eq($execution->id)->andWhere('type')->eq('execution')->fetchPairs('account', 'days');
  1093. foreach($members as $rowIndex => $member)
  1094. {
  1095. $member->root = $execution->id;
  1096. if($member->hours > 24) dao::$errors["hours[$rowIndex]"] = $this->lang->execution->errorHours;
  1097. if(!empty($execution->days))
  1098. {
  1099. if(isset($oldMemberDays[$member->account]) && $member->days == $oldMemberDays[$member->account]) continue;
  1100. if($member->days > $execution->days) dao::$errors["days[$rowIndex]"] = sprintf($this->lang->execution->daysGreaterProject, $execution->days);
  1101. }
  1102. }
  1103. return $members;
  1104. }
  1105. /**
  1106. * 根据过滤规则,筛选任务分组数据。
  1107. * Filter task group data based on the filter rules.
  1108. *
  1109. * @param array $groupTasks
  1110. * @param string $groupBy
  1111. * @param string $filter
  1112. * @param int $allCount
  1113. * @param array $tasks
  1114. * @access protected
  1115. * @return array
  1116. */
  1117. protected function filterGroupTasks($groupTasks, $groupBy, $filter, $allCount, $tasks)
  1118. {
  1119. if($filter == 'all') return array($groupTasks, $allCount);
  1120. if($groupBy == 'story' && $filter == 'linked' && isset($groupTasks[0]))
  1121. {
  1122. $allCount -= count($groupTasks[0]);
  1123. unset($groupTasks[0]);
  1124. }
  1125. elseif($groupBy == 'pri' && $filter == 'noset')
  1126. {
  1127. foreach($groupTasks as $pri => $tasks)
  1128. {
  1129. if($pri)
  1130. {
  1131. $allCount -= count($tasks);
  1132. unset($groupTasks[$pri]);
  1133. }
  1134. }
  1135. }
  1136. elseif($groupBy == 'assignedTo' && $filter == 'undone')
  1137. {
  1138. $multiTaskCount = array();
  1139. foreach($groupTasks as $assignedTo => $tasks)
  1140. {
  1141. foreach($tasks as $i => $task)
  1142. {
  1143. if($task->status != 'wait' && $task->status != 'doing')
  1144. {
  1145. if($task->mode == 'multi' && !isset($multiTaskCount[$task->id]))
  1146. {
  1147. $multiTaskCount[$task->id] = true;
  1148. $allCount -= 1;
  1149. }
  1150. elseif($task->mode != 'multi')
  1151. {
  1152. $allCount -= 1;
  1153. }
  1154. unset($groupTasks[$assignedTo][$i]);
  1155. }
  1156. }
  1157. }
  1158. }
  1159. elseif(($groupBy == 'finishedBy' || $groupBy == 'closedBy') && isset($tasks['']))
  1160. {
  1161. $allCount -= count($tasks['']);
  1162. unset($tasks['']);
  1163. }
  1164. return array($groupTasks, $allCount);
  1165. }
  1166. /**
  1167. * 设置最近五次执行。
  1168. * Set the recent five executions.
  1169. *
  1170. * @param int $executionID
  1171. * @access protected
  1172. * @return void
  1173. */
  1174. protected function setRecentExecutions($executionID)
  1175. {
  1176. if($this->session->multiple)
  1177. {
  1178. $recentExecutions = isset($this->config->execution->recentExecutions) ? explode(',', $this->config->execution->recentExecutions) : array();
  1179. array_unshift($recentExecutions, $executionID);
  1180. $recentExecutions = array_slice(array_unique($recentExecutions), 0, 5);
  1181. $recentExecutions = implode(',', $recentExecutions);
  1182. $this->loadModel('setting');
  1183. if(empty($this->config->execution->recentExecutions) || $this->config->execution->recentExecutions != $recentExecutions) $this->setting->updateItem($this->app->user->account . 'common.execution.recentExecutions', $recentExecutions);
  1184. if(empty($this->config->execution->lastExecution) || $this->config->execution->lastExecution != $executionID) $this->setting->updateItem($this->app->user->account . 'common.execution.lastExecution', $executionID);
  1185. }
  1186. }
  1187. /**
  1188. * 设置任务页面的Cookie和Session。
  1189. * Set task page storage.
  1190. *
  1191. * @access protected
  1192. * @return void
  1193. * @param int $executionID
  1194. * @param string $orderBy
  1195. * @param string $browseType
  1196. * @param int $param
  1197. */
  1198. protected function setTaskPageStorage($executionID, $orderBy, $browseType, $param = 0)
  1199. {
  1200. helper::setcookie('preExecutionID', (string)$executionID);
  1201. helper::setcookie('executionTaskOrder', $orderBy);
  1202. if($this->cookie->preExecutionID != $executionID)
  1203. {
  1204. helper::setcookie('moduleBrowseParam', '0');
  1205. helper::setcookie('productBrowseParam', '0');
  1206. }
  1207. if($browseType == 'bymodule')
  1208. {
  1209. helper::setcookie('moduleBrowseParam', (string)$param);
  1210. helper::setcookie('productBrowseParam', '0');
  1211. }
  1212. elseif($browseType == 'byproduct')
  1213. {
  1214. helper::setcookie('moduleBrowseParam', '0');
  1215. helper::setcookie('productBrowseParam', (string)$param);
  1216. }
  1217. else
  1218. {
  1219. $this->session->set('taskBrowseType', $browseType);
  1220. }
  1221. if($browseType == 'bymodule' && $this->session->taskBrowseType == 'bysearch') $this->session->set('taskBrowseType', 'unclosed');
  1222. }
  1223. /**
  1224. * 构建执行看板的数据。
  1225. * Build the data to execution Kanban.
  1226. *
  1227. * @param array $projectIdList
  1228. * @param array $executions
  1229. * @access protected
  1230. * @return void
  1231. */
  1232. protected function buildExecutionKanbanData($projectIdList, $executions)
  1233. {
  1234. $projectCount = 0;
  1235. $statusCount = array();
  1236. $myExecutions = array();
  1237. $kanbanGroup = array();
  1238. $teams = $this->execution->getMembersByIdList(toIntArray($this->app->user->view->sprints));
  1239. foreach($projectIdList as $projectID)
  1240. {
  1241. foreach(array_keys($this->lang->execution->statusList) as $status)
  1242. {
  1243. if(!isset($statusCount[$status])) $statusCount[$status] = 0;
  1244. foreach($executions as $execution)
  1245. {
  1246. if($execution->status == $status)
  1247. {
  1248. if(isset($teams[$execution->id][$this->app->user->account])) $myExecutions[$status][$execution->id] = $execution;
  1249. if($execution->project == $projectID) $kanbanGroup[$projectID][$status][$execution->id] = $execution;
  1250. }
  1251. }
  1252. $statusCount[$status] += isset($kanbanGroup[$projectID][$status]) ? count($kanbanGroup[$projectID][$status]) : 0;
  1253. /* Max 2 closed executions. */
  1254. if($status == 'closed')
  1255. {
  1256. list($myExecutions, $kanbanGroup) = $this->processExecutionKanbanData($myExecutions, $kanbanGroup, $projectID, $status);
  1257. }
  1258. }
  1259. if(empty($kanbanGroup[$projectID])) continue;
  1260. $projectCount ++;
  1261. }
  1262. return array($projectCount, $statusCount, $myExecutions, $kanbanGroup);
  1263. }
  1264. /**
  1265. * 获取可以导入到执行中的Bug。
  1266. *
  1267. * @param int $executionID
  1268. * @param array $productIdList
  1269. * @param string $browseType
  1270. * @param int $queryID
  1271. * @param object $pager
  1272. * @access protected
  1273. * @return array
  1274. */
  1275. protected function getImportBugs($executionID, $productIdList, $browseType, $queryID, $pager)
  1276. {
  1277. $this->loadModel('bug');
  1278. $bugs = array();
  1279. if($browseType != "bysearch")
  1280. {
  1281. $bugs = $this->bug->getActiveAndPostponedBugs($productIdList, $executionID, $pager);
  1282. }
  1283. else
  1284. {
  1285. if($queryID)
  1286. {
  1287. $query = $this->loadModel('search')->getQuery($queryID);
  1288. if($query)
  1289. {
  1290. $this->session->set('importBugQuery', $query->sql);
  1291. $this->session->set('importBugForm', $query->form);
  1292. }
  1293. else
  1294. {
  1295. $this->session->set('importBugQuery', ' 1 = 1');
  1296. }
  1297. }
  1298. else
  1299. {
  1300. if($this->session->importBugQuery === false) $this->session->set('importBugQuery', ' 1 = 1');
  1301. }
  1302. $bugQuery = str_replace("`product` = 'all'", "`product`" . helper::dbIN($productIdList), $this->session->importBugQuery); // Search all execution.
  1303. $bugs = $this->execution->getSearchBugs($productIdList, $executionID, $bugQuery, 'id_desc', $pager);
  1304. }
  1305. return $bugs;
  1306. }
  1307. /**
  1308. * 获取打印看板的数据。
  1309. * Get printed kanban data.
  1310. *
  1311. * @param int $executionID
  1312. * @param array $stories
  1313. * @access protected
  1314. * @return array
  1315. */
  1316. protected function getPrintKanbanData($executionID, $stories)
  1317. {
  1318. $kanbanTasks = $this->execution->getKanbanTasks($executionID, "id");
  1319. $kanbanBugs = $this->loadModel('bug')->getExecutionBugs($executionID);
  1320. $users = array();
  1321. $taskAndBugs = array();
  1322. foreach($kanbanTasks as $task)
  1323. {
  1324. $status = $task->status;
  1325. $users[] = $task->assignedTo;
  1326. $taskAndBugs[$status]["task{$task->id}"] = $task;
  1327. }
  1328. foreach($kanbanBugs as $bug)
  1329. {
  1330. $status = $bug->status;
  1331. $status = $status == 'active' ? 'wait' : ($status == 'resolved' ? ($bug->resolution == 'postponed' ? 'cancel' : 'done') : $status);
  1332. $users[] = $bug->assignedTo;
  1333. $taskAndBugs[$status]["bug{$bug->id}"] = $bug;
  1334. }
  1335. $dataList = array();
  1336. $contents = array('story', 'wait', 'doing', 'done', 'cancel');
  1337. foreach($contents as $content)
  1338. {
  1339. if($content != 'story' and !isset($taskAndBugs[$content])) continue;
  1340. $dataList[$content] = $content == 'story' ? $stories : $taskAndBugs[$content];
  1341. }
  1342. return array($dataList, $users);
  1343. }
  1344. /**
  1345. * 处理执行看板数据。
  1346. * Process execution kanban data.
  1347. *
  1348. * @param array $myExecutions
  1349. * @param array $kanbanGroup
  1350. * @param int $projectID
  1351. * @param string $status
  1352. * @access protected
  1353. * @return array
  1354. */
  1355. protected function processExecutionKanbanData($myExecutions, $kanbanGroup, $projectID, $status)
  1356. {
  1357. if(isset($myExecutions[$status]) and count($myExecutions[$status]) > 2)
  1358. {
  1359. foreach($myExecutions[$status] as $executionID => $execution)
  1360. {
  1361. unset($myExecutions[$status][$executionID]);
  1362. $myExecutions[$status][$execution->closedDate] = $execution;
  1363. }
  1364. krsort($myExecutions[$status]);
  1365. $myExecutions[$status] = array_slice($myExecutions[$status], 0, 2, true);
  1366. }
  1367. if(isset($kanbanGroup[$projectID][$status]) and count($kanbanGroup[$projectID][$status]) > 2)
  1368. {
  1369. foreach($kanbanGroup[$projectID][$status] as $executionID => $execution)
  1370. {
  1371. unset($kanbanGroup[$projectID][$status][$executionID]);
  1372. $kanbanGroup[$projectID][$status][$execution->closedDate] = $execution;
  1373. }
  1374. krsort($kanbanGroup[$projectID][$status]);
  1375. $kanbanGroup[$projectID][$status] = array_slice($kanbanGroup[$projectID][$status], 0, 2);
  1376. }
  1377. return array($myExecutions, $kanbanGroup);
  1378. }
  1379. /**
  1380. * 处理打印的看板数据。
  1381. * Process printed Kanban data.
  1382. *
  1383. * @param int $executionID
  1384. * @param array $dataList
  1385. * @access protected
  1386. * @return array
  1387. */
  1388. protected function processPrintKanbanData($executionID, $dataList)
  1389. {
  1390. $prevKanbans = $this->execution->getPrevKanban($executionID);
  1391. foreach($dataList as $type => $data)
  1392. {
  1393. if(isset($prevKanbans[$type]))
  1394. {
  1395. $prevData = $prevKanbans[$type];
  1396. foreach($prevData as $id)
  1397. {
  1398. if(isset($data[$id])) unset($dataList[$type][$id]);
  1399. }
  1400. }
  1401. }
  1402. return $dataList;
  1403. }
  1404. /**
  1405. * Check if the product has multiple branch and check if the execution has a product with multiple branch.
  1406. *
  1407. * @param int $productID
  1408. * @param int $executionID
  1409. * @return bool
  1410. */
  1411. protected function hasMultipleBranch($productID, $executionID)
  1412. {
  1413. /* Check if the product is multiple branch. */
  1414. $multiBranchProduct = false;
  1415. if($productID)
  1416. {
  1417. $product = $this->loadModel('product')->getByID($productID);
  1418. if($product->type != 'normal') $multiBranchProduct = true;
  1419. }
  1420. else
  1421. {
  1422. $executionProductList = $this->loadModel('product')->getProducts($executionID);
  1423. foreach($executionProductList as $executionProduct)
  1424. {
  1425. if($executionProduct->type != 'normal')
  1426. {
  1427. $multiBranchProduct = true;
  1428. break;
  1429. }
  1430. }
  1431. }
  1432. return $multiBranchProduct;
  1433. }
  1434. /**
  1435. * 通过模块,方法和类型生成执行的链接。
  1436. * Generate the link of execution by module, method and type.
  1437. *
  1438. * @param string $module
  1439. * @param string $method
  1440. * @param string $type
  1441. * @access protected
  1442. * @return string
  1443. */
  1444. protected function getLink($module, $method, $type = '')
  1445. {
  1446. $executionModules = array('task', 'testcase', 'build', 'bug', 'case', 'testtask', 'testreport', 'doc');
  1447. if(in_array($module, array('task', 'testcase', 'story', 'testtask')) && in_array($method, array('view', 'edit', 'batchedit', 'create', 'batchcreate', 'report', 'batchrun', 'groupcase'))) $method = $module;
  1448. if(in_array($module, $executionModules) && in_array($method, array('view', 'edit', 'create'))) $method = $module;
  1449. if(in_array($module, array_merge($executionModules, array('story', 'product')))) $module = 'execution';
  1450. if($module == 'execution' && $method == 'create') return '';
  1451. if($this->config->edition != 'open')
  1452. {
  1453. $flow = $this->loadModel('workflow')->getByModule($module);
  1454. if(!empty($flow) && in_array($flow->app, array('scrum', 'waterfall', 'kanbanProject'))) $flow->app = 'project';
  1455. if(!empty($flow) && $flow->buildin == '0') return helper::createLink('flow', 'ajaxSwitchBelong', "objectID=%s&moduleName=$module") . "#app=$flow->app";
  1456. }
  1457. $link = helper::createLink($module, $method, "executionID=%s");
  1458. if($module == 'execution' && ($method == 'index' || $method == 'all'))
  1459. {
  1460. $link = helper::createLink($module, 'task', "executionID=%s");
  1461. }
  1462. elseif($module == 'execution' && $method == 'storyview')
  1463. {
  1464. $link = helper::createLink($module, 'story', "executionID=%s");
  1465. }
  1466. elseif($module == 'execution' && in_array($method, array('kanban', 'cfd')))
  1467. {
  1468. $link = helper::createLink($module, 'view', "executionID=%s");
  1469. }
  1470. elseif($module == 'execution' && $method == 'autoschedule')
  1471. {
  1472. $link = helper::createLink('task', 'autoSchedule', "executionID=%s");
  1473. }
  1474. elseif($module == 'bug' && $method == 'create' && $this->app->tab == 'execution')
  1475. {
  1476. $link = helper::createLink($module, $method, "productID=0&branch=0&executionID=%s");
  1477. }
  1478. elseif(in_array($module, array('bug', 'case', 'testtask', 'testreport')) && strpos(',view,edit,', ",$method,") !== false)
  1479. {
  1480. $link = helper::createLink('execution', $module, "executionID=%s");
  1481. }
  1482. elseif($module == 'repo' && $method == 'review')
  1483. {
  1484. $link = helper::createLink('repo', 'review', "repoID=0&browseType=all&executionID=%s") . '#app=execution';
  1485. }
  1486. elseif($module == 'repo')
  1487. {
  1488. $link = helper::createLink('repo', 'browse', "repoID=0&branchID=&executionID=%s");
  1489. }
  1490. elseif($module == 'mr')
  1491. {
  1492. $link = helper::createLink('mr', 'browse', "repoID=0&mode=status&param=opened&objectID=%s") . '#app=execution';
  1493. }
  1494. elseif($module == 'doc')
  1495. {
  1496. $link = helper::createLink('doc', $method, "type=execution&objectID=%s&from=execution");
  1497. }
  1498. elseif($module == 'pssp')
  1499. {
  1500. $link = helper::createLink($module, 'browse', "projectID=%s") . "#app={$this->app->tab}";
  1501. }
  1502. elseif(in_array($module, array('issue', 'risk', 'opportunity', 'auditplan', 'nc', 'meeting')))
  1503. {
  1504. $link = helper::createLink($module, 'browse', "executionID=%s&from=execution");
  1505. }
  1506. elseif(($module == 'testreport' && $method == 'create') || ($module == 'execution' && $method == 'cases'))
  1507. {
  1508. $link = helper::createLink('execution', 'testtask', "executionID=%s");
  1509. }
  1510. elseif($method == 'zerocase')
  1511. {
  1512. $link = helper::createLink('execution', 'testcase', "executionID=%s");
  1513. }
  1514. elseif($method == 'createrelation')
  1515. {
  1516. $link = helper::createLink('execution', 'relation', "executionID=%s");
  1517. }
  1518. elseif($module == 'project' && $method == 'deliverablechecklist')
  1519. {
  1520. $link = helper::createLink('project', 'deliverableChecklist', "projectID=%s") . "#app={$this->app->tab}";
  1521. }
  1522. if($type != '') $link .= "&type=$type";
  1523. return $link;
  1524. }
  1525. /**
  1526. * 设置cookie和session。
  1527. * Set the cookie and session.
  1528. *
  1529. * @param string $executionID
  1530. * @param string $type all|byModule|byProduct|byBranch|bySearch
  1531. * @param string $param
  1532. * @param string $orderBy
  1533. * @access protected
  1534. * @return int
  1535. */
  1536. protected function setStorageForStory($executionID, $type, $param, $orderBy)
  1537. {
  1538. $productID = 0;
  1539. if($this->cookie->storyPreExecutionID != $executionID)
  1540. {
  1541. $_COOKIE['storyModuleParam'] = $_COOKIE['storyProductParam'] = $_COOKIE['storyBranchParam'] = 0;
  1542. helper::setcookie('storyModuleParam', '0');
  1543. helper::setcookie('storyProductParam', '0');
  1544. helper::setcookie('storyBranchParam', '0');
  1545. }
  1546. if($type == 'bymodule')
  1547. {
  1548. $module = $this->loadModel('tree')->getByID((int)$param);
  1549. $productID = isset($module->root) ? $module->root : 0;
  1550. helper::setcookie('storyModuleParam', $param);
  1551. helper::setcookie('storyProductParam', '0');
  1552. helper::setcookie('storyBranchParam', '0');
  1553. }
  1554. elseif($type == 'byproduct')
  1555. {
  1556. $productID = $param;
  1557. helper::setcookie('storyModuleParam', '0');
  1558. helper::setcookie('storyProductParam', $param);
  1559. helper::setcookie('storyBranchParam', '0');
  1560. }
  1561. elseif($type == 'bybranch')
  1562. {
  1563. helper::setcookie('storyModuleParam', '0');
  1564. helper::setcookie('storyProductParam', '0');
  1565. helper::setcookie('storyBranchParam', $param);
  1566. }
  1567. $this->session->set('executionStoryBrowseType', $type);
  1568. $this->session->set('storyBrowseType', $type, 'execution');
  1569. $uri = $this->app->getURI(true);
  1570. $this->session->set('storyList', $uri, $this->app->tab);
  1571. $this->session->set('executionStoryList', $uri, 'execution');
  1572. helper::setcookie('executionStoryOrder', $orderBy);
  1573. helper::setcookie('storyPreExecutionID', $executionID);
  1574. if($this->app->tab == 'project' && $this->cookie->storyProductParam != $productID) helper::setcookie('storyProductParam', '0');
  1575. return (int)$productID;
  1576. }
  1577. /**
  1578. * Set the more link of user and return users with echo role.
  1579. *
  1580. * @param mixed $execution
  1581. * @return array
  1582. */
  1583. protected function setUserMoreLink($execution = null)
  1584. {
  1585. $appendPo = $appendPm = $appendQd = $appendRd = array();
  1586. if(is_array($execution))
  1587. {
  1588. $appendPo = $appendPm = $appendQd = $appendRd = array();
  1589. foreach($execution as $item)
  1590. {
  1591. $appendPo[$item->PO] = $item->PO;
  1592. $appendPm[$item->PM] = $item->PM;
  1593. $appendQd[$item->QD] = $item->QD;
  1594. $appendRd[$item->RD] = $item->RD;
  1595. }
  1596. }
  1597. elseif(is_object($execution))
  1598. {
  1599. $appendPo[$execution->PO] = $execution->PO;
  1600. $appendPm[$execution->PM] = $execution->PM;
  1601. $appendQd[$execution->QD] = $execution->QD;
  1602. $appendRd[$execution->RD] = $execution->RD;
  1603. }
  1604. $this->loadModel('user');
  1605. $pmUsers = $this->user->getPairs('noclosed|nodeleted|pmfirst', $appendPm, $this->config->maxCount);
  1606. if(!empty($this->config->user->moreLink)) $this->config->moreLinks["PM"] = $this->config->user->moreLink;
  1607. $poUsers = $this->user->getPairs('noclosed|nodeleted|pofirst', $appendPo, $this->config->maxCount);
  1608. if(!empty($this->config->user->moreLink)) $this->config->moreLinks["PO"] = $this->config->user->moreLink;
  1609. $qdUsers = $this->user->getPairs('noclosed|nodeleted|qdfirst', $appendQd, $this->config->maxCount);
  1610. if(!empty($this->config->user->moreLink)) $this->config->moreLinks["QD"] = $this->config->user->moreLink;
  1611. $rdUsers = $this->user->getPairs('noclosed|nodeleted|devfirst', $appendRd, $this->config->maxCount);
  1612. if(!empty($this->config->user->moreLink)) $this->config->moreLinks["RD"] = $this->config->user->moreLink;
  1613. return array($pmUsers, $poUsers, $qdUsers, $rdUsers);
  1614. }
  1615. /**
  1616. * 初始化创建执行的字段。
  1617. * Init execution fields for create.
  1618. *
  1619. * @param int $projectID
  1620. * @param array $output
  1621. * @access protected
  1622. * @return object
  1623. */
  1624. protected function initFieldsForCreate($projectID, $output = array())
  1625. {
  1626. $execution = new stdclass();
  1627. $execution->project = $projectID;
  1628. $execution->type = zget($output, 'type', 'sprint');
  1629. $execution->name = '';
  1630. $execution->code = '';
  1631. $execution->team = '';
  1632. $execution->acl = 'private';
  1633. $execution->whitelist = '';
  1634. $this->view->execution = $execution;
  1635. return $execution;
  1636. }
  1637. /**
  1638. * 通过复制执行ID设置字段值。
  1639. * Set execution fields by copy execution id.
  1640. *
  1641. * @param object $fields
  1642. * @param int $copyExecutionID
  1643. * @access protected
  1644. * @return object
  1645. */
  1646. protected function setFieldsByCopyExecution($fields, $copyExecutionID)
  1647. {
  1648. if(empty($copyExecutionID)) return $fields;
  1649. $copyExecution = $this->execution->fetchByID($copyExecutionID);
  1650. $fields->project = (int)$copyExecution->project;
  1651. $fields->type = $copyExecution->type;
  1652. $fields->name = $copyExecution->name;
  1653. $fields->code = $copyExecution->code;
  1654. $fields->team = $copyExecution->team;
  1655. $fields->acl = $copyExecution->acl;
  1656. $fields->whitelist = $copyExecution->whitelist;
  1657. $this->view->execution = $copyExecution;
  1658. return $fields;
  1659. }
  1660. /**
  1661. * 获取已关联的产品。
  1662. * Get can link products.
  1663. *
  1664. * @param int $copyExecutionID
  1665. * @param int $planID
  1666. * @param object|null $project
  1667. * @access protected
  1668. * @return array
  1669. */
  1670. protected function getLinkedProducts($copyExecutionID, $planID, $project)
  1671. {
  1672. $products = array();
  1673. if($copyExecutionID) $products = $this->loadModel('product')->getProducts($copyExecutionID);
  1674. if($planID)
  1675. {
  1676. $plan = $this->loadModel('productplan')->fetchByID($planID);
  1677. if($plan->begin == $this->config->productplan->future) $plan->begin = '';
  1678. if($plan->end == $this->config->productplan->future) $plan->end = '';
  1679. $products = $this->dao->select('t1.id, t1.name, t1.type, t2.branch')->from(TABLE_PRODUCT)->alias('t1')
  1680. ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.product')
  1681. ->where('t1.id')->eq($plan->product)
  1682. ->fetchAll('id');
  1683. $this->view->plan = $plan;
  1684. }
  1685. if(isset($project->hasProduct) && empty($project->hasProduct))
  1686. {
  1687. $product = $this->loadModel('product')->getShadowProductByProject($project->id);
  1688. $products = array($product->id => $product->name);
  1689. }
  1690. return $products;
  1691. }
  1692. /**
  1693. * 获取已关联的分支。
  1694. * Set linked branches.
  1695. *
  1696. * @param array $products
  1697. * @param int $copyExecutionID
  1698. * @param int $planID
  1699. * @param object|null $project
  1700. * @access protected
  1701. * @return void
  1702. */
  1703. protected function setLinkedBranches($products, $copyExecutionID, $planID, $project)
  1704. {
  1705. $projectID = empty($project) ? 0 : $project->id;
  1706. if(!empty($copyExecutionID))
  1707. {
  1708. $branches = $this->project->getBranchesByProject($copyExecutionID);
  1709. $plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($products), 'skipparent|unexpired');
  1710. $branchGroups = $this->execution->getBranchByProduct(array_keys($products), $projectID);
  1711. }
  1712. if(!empty($project) and $project->stageBy == 'project')
  1713. {
  1714. $products = $this->loadModel('product')->getProducts($project->id);
  1715. $branches = $this->project->getBranchesByProject($projectID);
  1716. $plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($products), 'skipparent|unexpired');
  1717. }
  1718. if($products and isset($branches))
  1719. {
  1720. $linkedBranches = array();
  1721. foreach($products as $productIndex => $product)
  1722. {
  1723. $productPlans[$productIndex] = array();
  1724. foreach($branches[$productIndex] as $branchID => $branch)
  1725. {
  1726. $linkedBranches[$productIndex][$branchID] = $branchID;
  1727. if(isset($plans[$productIndex][$branchID]))
  1728. {
  1729. foreach($plans[$productIndex][$branchID] as $plan) $productPlans[$productIndex][$branchID][$plan->id] = $plan->title;
  1730. }
  1731. }
  1732. }
  1733. $this->view->productPlans = $productPlans;
  1734. $this->view->linkedBranches = $linkedBranches;
  1735. }
  1736. if(!empty($planID))
  1737. {
  1738. $plan = $this->loadModel('productplan')->fetchByID($planID);
  1739. $productPlan = $this->productplan->getPairs($plan->product, $plan->branch, 'unexpired|withMainPlan', true);
  1740. $linkedBranches = array();
  1741. $linkedBranches[$plan->product][$plan->branch] = $plan->branch;
  1742. $this->view->linkedBranches = $linkedBranches;
  1743. }
  1744. $this->view->productPlan = isset($productPlan) ? $productPlan : array();
  1745. $this->view->branchGroups = isset($branchGroups) ? $branchGroups : $this->execution->getBranchByProduct(array_keys($products), $projectID);
  1746. if(isset($project->hasProduct) and empty($project->hasProduct))
  1747. {
  1748. $shadowProduct = $this->loadModel('product')->getShadowProductByProject($project->id);
  1749. $this->view->productPlan = $this->loadModel('productplan')->getPairs($shadowProduct->id ?? 0, '0,0', 'noclosed,unexpired', true);
  1750. }
  1751. }
  1752. /**
  1753. * 根据项目获取可关联的产品。
  1754. * Get all products for create.
  1755. *
  1756. * @param object|null $project
  1757. * @access protected
  1758. * @return void
  1759. */
  1760. protected function getAllProductsForCreate($project)
  1761. {
  1762. if(empty($project)) return array();
  1763. $allProducts = $this->loadModel('product')->getProductPairsByProject($project->id, 'noclosed');
  1764. if(!empty($project->hasProduct)) $allProducts = array(0 => '') + $allProducts;
  1765. return $allProducts;
  1766. }
  1767. /**
  1768. * 设置可复制的执行。
  1769. * Set copy projects.
  1770. *
  1771. * @param object|null $project
  1772. * @access protected
  1773. * @return void
  1774. */
  1775. protected function setCopyProjects($project)
  1776. {
  1777. $parentProject = 0;
  1778. $projectModel = '';
  1779. if($project)
  1780. {
  1781. $parentProject = $project->parent;
  1782. $projectModel = $project->model;
  1783. if($projectModel == 'agileplus') $projectModel = array('scrum', 'agileplus');
  1784. if($projectModel == 'waterfallplus') $projectModel = array('waterfall', 'waterfallplus');
  1785. }
  1786. $copyProjects = $this->loadModel('project')->getPairsByProgram($parentProject, 'noclosed', false, 'order_asc', '', $projectModel, 'multiple');
  1787. $copyProjectID = empty($project) ? key($copyProjects) : $project->id;
  1788. $this->view->copyProjects = $copyProjects;
  1789. $this->view->copyProjectID = $copyProjectID;
  1790. $this->view->copyExecutions = empty($copyProjectID) ? array() : $this->execution->getList($copyProjectID, 'all', 'all', 0, 0, 0, null, false);
  1791. }
  1792. /**
  1793. * 修正executionCommon公共语言项。
  1794. * Correct execution common lang.
  1795. *
  1796. * @param object $project
  1797. * @param string $type
  1798. * @access protected
  1799. * @return bool
  1800. */
  1801. protected function correctExecutionCommonLang($project, $type)
  1802. {
  1803. if(empty($project)) return false;
  1804. if($project->model == 'kanban')
  1805. {
  1806. global $lang;
  1807. $executionLang = $lang->execution->common;
  1808. $executionCommonLang = $lang->executionCommon;
  1809. $lang->executionCommon = $lang->execution->kanban;
  1810. $lang->execution->common = $lang->execution->kanban;
  1811. include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
  1812. $lang->execution->common = $executionLang;
  1813. $lang->executionCommon = $executionCommonLang;
  1814. $lang->execution->typeList['sprint'] = $executionCommonLang;
  1815. }
  1816. elseif($project->model == 'waterfall' || $project->model == 'waterfallplus')
  1817. {
  1818. $this->app->loadLang('stage');
  1819. global $lang;
  1820. $lang->executionCommon = $lang->execution->stage;
  1821. include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
  1822. }
  1823. if(isset($project->hasProduct) and empty($project->hasProduct)) $this->lang->execution->PO = $this->lang->common->story . $this->lang->execution->owner;
  1824. return true;
  1825. }
  1826. /**
  1827. * 修正错误提示语言。
  1828. * Correct error lang.
  1829. *
  1830. * @access protected
  1831. * @return void
  1832. */
  1833. protected function correctErrorLang()
  1834. {
  1835. $this->lang->execution->team = $this->lang->execution->teamName;
  1836. $this->lang->error->unique = $this->lang->error->repeat;
  1837. /* Redefines the language entries for the fields in the project table. */
  1838. foreach(explode(',', $this->config->execution->create->requiredFields) as $field)
  1839. {
  1840. if(isset($this->lang->execution->$field)) $this->lang->project->$field = $this->lang->execution->$field;
  1841. }
  1842. /* Replace required language. */
  1843. if($this->app->tab == 'project')
  1844. {
  1845. $this->lang->project->name = $this->lang->execution->name;
  1846. $this->lang->project->code = $this->lang->execution->code;
  1847. }
  1848. else
  1849. {
  1850. $this->lang->project->name = $this->lang->execution->execName;
  1851. $this->lang->project->code = $this->lang->execution->execCode;
  1852. }
  1853. }
  1854. /**
  1855. * 创建执行后,显示提示页面。
  1856. * Display after created.
  1857. *
  1858. * @param int $projectID
  1859. * @param int $executionID
  1860. * @param int $planID
  1861. * @param string $confirm
  1862. * @access protected
  1863. * @return void
  1864. */
  1865. protected function displayAfterCreated($projectID, $executionID, $planID, $confirm = 'no')
  1866. {
  1867. $execution = $this->execution->fetchByID($executionID);
  1868. if(!empty($planID) and $execution->lifetime != 'ops')
  1869. {
  1870. if($confirm == 'yes')
  1871. {
  1872. $this->execution->linkStories($executionID);
  1873. }
  1874. else
  1875. {
  1876. $executionProductList = $this->loadModel('product')->getProducts($executionID);
  1877. $multiBranchProduct = false;
  1878. array_map(function($executionProduct) use(&$multiBranchProduct){if($executionProduct->type != 'normal') $multiBranchProduct = true;}, $executionProductList);
  1879. $storyType = '';
  1880. $project = $this->loadModel('project')->fetchByID($projectID);
  1881. if($project)
  1882. {
  1883. $this->loadModel('story');
  1884. foreach(explode(',', $project->storyType) as $type) $storyType .= $this->lang->story->typeList[$type] . ', ';
  1885. }
  1886. if(empty($storyType)) $storyType = $this->lang->story->common;
  1887. $importPlanStoryTips = sprintf($multiBranchProduct ? $this->lang->execution->importBranchPlanStory : $this->lang->execution->importPlanStory, trim($storyType, ', '));
  1888. if($execution->type == 'stage') $importPlanStoryTips = str_replace($this->lang->executionCommon, $this->lang->execution->stage, $importPlanStoryTips);
  1889. $confirmURL = inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=$planID&confirm=yes");
  1890. $cancelURL = inlink('create', "projectID=$projectID&executionID=$executionID");
  1891. return $this->send(array('result' => 'success', 'open' => array('confirm' => $importPlanStoryTips, 'url' => $confirmURL, 'canceled' => $cancelURL)));
  1892. }
  1893. }
  1894. if(!empty($projectID) and $execution->type == 'kanban' and $this->app->tab == 'project') return $this->send(array('result' => 'success', 'load' => $this->createLink('project', 'index', "projectID=$projectID")));
  1895. if(!empty($projectID) and $execution->type == 'kanban') return $this->send(array('result' => 'success', 'load' => inlink('kanban', "executionID=$executionID")));
  1896. $this->view->title = $this->lang->execution->tips;
  1897. $this->view->executionID = $executionID;
  1898. $this->view->execution = $this->execution->fetchByID($executionID);
  1899. $this->display('execution', 'tips');
  1900. }
  1901. /**
  1902. * 更新迭代关联的计划。
  1903. * Update linked plans.
  1904. *
  1905. * @param int $executionID
  1906. * @param string $newPlans
  1907. * @param string $confirm
  1908. * @return void
  1909. */
  1910. protected function updateLinkedPlans($executionID, $newPlans = '', $confirm = 'no')
  1911. {
  1912. $projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
  1913. if(!empty($newPlans) and $confirm == 'yes')
  1914. {
  1915. $newPlans = explode(',', $newPlans);
  1916. $this->loadModel('productplan')->linkProject($executionID, $newPlans);
  1917. $this->productplan->linkProject($projectID, $newPlans);
  1918. return $this->send(array('result' => 'success', 'load' => inlink('view', "executionID=$executionID")));
  1919. }
  1920. elseif(!empty($newPlans))
  1921. {
  1922. $executionProductList = $this->loadModel('product')->getProducts($executionID); /* 无论是迭代ID还是项目ID都会查到一个与之对应的产品。*/
  1923. $multiBranchProduct = false;
  1924. foreach($executionProductList as $executionProduct)
  1925. {
  1926. if($executionProduct->type != 'normal')
  1927. {
  1928. $multiBranchProduct = true;
  1929. break;
  1930. }
  1931. }
  1932. $storyType = '';
  1933. $project = $this->loadModel('project')->fetchByID($projectID);
  1934. if($project)
  1935. {
  1936. $this->loadModel('story');
  1937. foreach(explode(',', $project->storyType) as $type) $storyType .= $this->lang->story->typeList[$type] . ', ';
  1938. }
  1939. if(empty($storyType)) $storyType = $this->lang->story->common;
  1940. $linkPlanMsg = sprintf($multiBranchProduct ? $this->lang->execution->importBranchEditPlanStory : $this->lang->execution->importEditPlanStory, trim($storyType, ', '));
  1941. $confirmURL = inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=yes");
  1942. $cancelURL = inlink('view', "executionID=$executionID");
  1943. return $this->send(array('result' => 'success', 'load' => array('confirm' => $linkPlanMsg, 'confirmed' => $confirmURL, 'canceled' => $cancelURL)));
  1944. }
  1945. }
  1946. /**
  1947. * Check if the execution can be linked to plan stories.
  1948. *
  1949. * @param int $executionID
  1950. * @param array $oldPlans
  1951. * @return void
  1952. */
  1953. protected function checkLinkPlan($executionID, $oldPlans)
  1954. {
  1955. $oldPlans = explode(',', implode(',' ,$oldPlans));
  1956. $newPlans = array();
  1957. if(isset($_POST['plans']))
  1958. {
  1959. foreach((array)$_POST['plans'] as $plans)
  1960. {
  1961. foreach($plans as $planID)
  1962. {
  1963. if(array_search($planID, $oldPlans) === false) $newPlans[$planID] = $planID;
  1964. }
  1965. }
  1966. }
  1967. $newPlans = array_filter($newPlans);
  1968. if(!empty($newPlans))
  1969. {
  1970. $newPlans = implode(',', $newPlans);
  1971. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=no")));
  1972. }
  1973. }
  1974. /**
  1975. * 获取执行关联的对象,如关联的产品、分支、需求等。
  1976. * Get linked objects of this execution.
  1977. *
  1978. * @param object $execution
  1979. * @return object
  1980. */
  1981. protected function getLinkedObjects($execution)
  1982. {
  1983. $this->loadModel('project');
  1984. $this->loadModel('product');
  1985. $this->loadModel('productplan');
  1986. $branches = $this->project->getBranchesByProject($execution->id);
  1987. $linkedProductIdList = empty($branches) ? '' : array_keys($branches);
  1988. $allProducts = $this->product->getProducts($execution->project, 'noclosed', '', false, $linkedProductIdList);
  1989. $productPlans = $linkedBranches = $linkedBranchList = array();
  1990. $linkedProducts = $this->product->getProducts($execution->id, 'all', '', true, $linkedProductIdList, false);
  1991. $plans = $this->productplan->getGroupByProduct(array_keys($linkedProducts), 'skipparent|unexpired');
  1992. $executionStories = $this->project->getStoriesByProject($execution->id);
  1993. /* If the story of the product which linked the execution, you don't allow to remove the product. */
  1994. $unmodifiableProducts = $unmodifiableBranches = $linkedStoryIDList = array();
  1995. foreach($linkedProducts as $productID => $linkedProduct)
  1996. {
  1997. if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->deleted ? $linkedProduct->name . "({$this->lang->product->deleted})" : $linkedProduct->name;
  1998. $productPlans[$productID] = array();
  1999. foreach($branches[$productID] as $branchID => $branch)
  2000. {
  2001. if(isset($plans[$productID][$branchID]))
  2002. {
  2003. foreach($plans[$productID][$branchID] as $plan) $productPlans[$productID][$branchID][$plan->id] = $plan->title;
  2004. }
  2005. $linkedBranchList[$branchID] = $branchID;
  2006. $linkedBranches[$productID][$branchID] = $branchID;
  2007. if($branchID != BRANCH_MAIN && isset($plans[$productID][BRANCH_MAIN]))
  2008. {
  2009. foreach($plans[$productID][BRANCH_MAIN] as $plan) $productPlans[$productID][BRANCH_MAIN][$plan->id] = $plan->title;
  2010. }
  2011. if(!empty($executionStories[$productID][$branchID]))
  2012. {
  2013. array_push($unmodifiableProducts, $productID);
  2014. array_push($unmodifiableBranches, $branchID);
  2015. $linkedStoryIDList[$productID][$branchID] = $executionStories[$productID][$branchID]->storyIDList;
  2016. }
  2017. }
  2018. if(!empty($linkedProduct->plans))
  2019. {
  2020. foreach($linkedProduct->plans as $linkedPlans)
  2021. {
  2022. $planList = explode(',', $linkedPlans);
  2023. $planList = array_filter($planList);
  2024. foreach($planList as $planID)
  2025. {
  2026. if(isset($plans[$productID][$planID])) continue;
  2027. $productPlans[$productID][$branchID][$planID] = '';
  2028. }
  2029. }
  2030. }
  2031. }
  2032. $productPlan = array();
  2033. if(!empty($planID))
  2034. {
  2035. $plan = $this->productplan->fetchByID((int)$planID);
  2036. $productPlan = $this->productplan->getPairs($plan->product, $plan->branch, 'unexpired|withMainPlan', true);
  2037. }
  2038. $productPlansOrder = array();
  2039. foreach($productPlans as $productID => $branchPlans)
  2040. {
  2041. foreach($branchPlans as $branchID => $plans)
  2042. {
  2043. $orderPlans = $this->loadModel('productplan')->getByIDList(array_keys($plans));
  2044. $orderPlans = $this->productplan->relationBranch($orderPlans);
  2045. $orderPlansMap = array_keys($orderPlans);
  2046. foreach($orderPlansMap as $planMapID)
  2047. {
  2048. if(empty($plans[$planMapID])) $productPlans[$productID][$branchID][$planMapID] = $orderPlans[$planMapID]->title;
  2049. $productPlansOrder[$productID][$branchID][$planMapID] = $productPlans[$productID][$branchID][$planMapID];
  2050. }
  2051. }
  2052. }
  2053. $linkedObjects = new stdclass();
  2054. $linkedObjects->currentPlan = !empty($planID) ? $planID : 0;
  2055. $linkedObjects->productPlan = $productPlan;
  2056. $linkedObjects->allProducts = $allProducts;
  2057. $linkedObjects->linkedProducts = $linkedProducts;
  2058. $linkedObjects->productPlans = array_filter($productPlansOrder);
  2059. $linkedObjects->linkedBranches = $linkedBranches;
  2060. $linkedObjects->linkedBranchList = $linkedBranchList;
  2061. $linkedObjects->linkedStoryIDList = $linkedStoryIDList;
  2062. $linkedObjects->unmodifiableProducts = $unmodifiableProducts;
  2063. $linkedObjects->unmodifiableBranches = $unmodifiableBranches;
  2064. return $linkedObjects;
  2065. }
  2066. /**
  2067. * 获取创建执行后的跳转地址。
  2068. * Get location after create.
  2069. *
  2070. * @param int $projectID
  2071. * @param int $executionID
  2072. * @param string $model
  2073. * @access protected
  2074. * @return string
  2075. */
  2076. protected function getAfterCreateLocation($projectID, $executionID, $model = '')
  2077. {
  2078. if($this->app->tab == 'doc') return $this->createLink('doc', 'projectSpace', "objectID=$executionID");
  2079. if(!empty($_POST['plans'])) return inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=1&confirm=no");
  2080. if(!empty($projectID) and $model == 'kanban')
  2081. {
  2082. if($this->app->tab == 'project') return $this->config->vision != 'lite' ? $this->createLink('project', 'index', "projectID=$projectID") : $this->createLink('project', 'execution', "status=all&projectID=$projectID");
  2083. return inlink('kanban', "executionID=$executionID");
  2084. }
  2085. $execution = $this->execution->fetchByID($executionID);
  2086. if(!empty($execution->isTpl)) return inlink('task', "executionID=$executionID");
  2087. return inlink('create', "projectID=$projectID&executionID=$executionID");
  2088. }
  2089. }