zen.php 79 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909
  1. <?php
  2. /**
  3. * The zen file of project 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 sunguangming <sunguangming@easycorp.ltd>
  8. * @link https://www.zentao.net
  9. */
  10. class projectZen extends project
  11. {
  12. /**
  13. * Append extras data to post data.
  14. *
  15. * @param object $postData
  16. * @param int $copyProjectID
  17. * @access protected
  18. * @return object|false
  19. */
  20. protected function prepareCreateExtras($postData, $copyProjectID = 0)
  21. {
  22. $project = $postData->setDefault('status', 'wait')
  23. ->setIF($this->post->longTime || $this->post->delta == '999', 'end', LONG_TIME)
  24. ->setIF($this->post->longTime || $this->post->delta == '999', 'days', 0)
  25. ->setIF($this->post->acl == 'open', 'whitelist', '')
  26. ->setIF(!isset($_POST['whitelist']), 'whitelist', '')
  27. ->setIF($this->post->multiple != 'on', 'multiple', '0')
  28. ->setIF($this->post->multiple == 'on' || !in_array($this->post->model, array('scrum', 'kanban')) || $this->config->vision == 'lite', 'multiple', '1')
  29. ->setIF($this->post->model == 'ipd', 'stageBy', 'project')
  30. ->setDefault('openedBy', $this->app->user->account)
  31. ->setDefault('openedDate', helper::now())
  32. ->setDefault('team', $this->post->name)
  33. ->setDefault('lastEditedBy', $this->app->user->account)
  34. ->setDefault('lastEditedDate', helper::now())
  35. ->setDefault('days', '0')
  36. ->add('type', 'project')
  37. ->join('whitelist', ',')
  38. ->join('auth', ',')
  39. ->join('storyType', ',')
  40. ->stripTags($this->config->project->editor->create['id'], $this->config->allowedTags)
  41. ->get();
  42. if($this->post->newProduct == 'on') $project->stageBy = 'project';
  43. $copyProject = $this->project->getByID($copyProjectID);
  44. if($copyProject)
  45. {
  46. if(in_array($this->post->model, array('scrum', 'kanban'))) $project->multiple = $copyProject->multiple;
  47. $project->hasProduct = $copyProject->hasProduct;
  48. }
  49. if(!isset($this->config->setCode) || $this->config->setCode == 0) unset($project->code);
  50. /* Lean mode relation defaultProgram. */
  51. if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
  52. if(!$this->checkProductAndBranch($project, (object)$_POST)) return false;
  53. if(!$this->checkDaysAndBudget($project, (object)$_POST)) return false;
  54. if(!$this->checkProductNameUnqiue($project, (object)$_POST)) return false;
  55. return $project;
  56. }
  57. /**
  58. * Check work days legtimate.
  59. *
  60. * @param object $project
  61. * @access protected
  62. * @return bool
  63. */
  64. protected function checkWorkdaysLegtimate($project)
  65. {
  66. $workdays = helper::diffDate($project->end, $project->begin) + 1;
  67. if(isset($project->days) && $project->days > $workdays)
  68. {
  69. dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
  70. return false;
  71. }
  72. return true;
  73. }
  74. /**
  75. * Validate $postData and prepare $project for update.
  76. *
  77. * @param object $postData
  78. * @param int $hasProduct
  79. * @access protected
  80. * @return object|false
  81. */
  82. protected function prepareProject($postData, $hasProduct)
  83. {
  84. $project = $postData->setDefault('team', $this->post->name)
  85. ->setDefault('lastEditedBy', $this->app->user->account)
  86. ->setDefault('lastEditedDate', helper::now())
  87. ->setDefault('days', '0')
  88. ->setIF($this->post->longTime || $this->post->delta == '999', 'end', LONG_TIME)
  89. ->setIF($this->post->longTime || $this->post->delta == '999', 'days', 0)
  90. ->setIF($this->post->future, 'budget', 0)
  91. ->setIF($this->post->budget != 0, 'budget', round((float)$this->post->budget, 2))
  92. ->join('whitelist', ',')
  93. ->join('auth', ',')
  94. ->join('storyType', ',')
  95. ->stripTags($this->config->project->editor->edit['id'], $this->config->allowedTags)
  96. ->remove('products,plans,branch')
  97. ->get();
  98. if($hasProduct)
  99. {
  100. /* Check if products not empty. */
  101. if(!$this->post->products || (empty($this->post->products[0]) && !array_filter($this->post->products)))
  102. {
  103. dao::$errors['products[0]'] = $this->app->rawMethod == 'create' ? $this->lang->project->error->productNotEmpty : $this->lang->project->errorNoProducts;
  104. return false;
  105. }
  106. $project->parent = (int)$project->parent;
  107. /* Check if products and branch valid. */
  108. if(!$this->project->checkBranchAndProduct($project->parent, (array)$this->post->products, (array)$this->post->branch)) return false;
  109. }
  110. /* Check if work days legtimate. */
  111. if(!$this->checkWorkdaysLegtimate($project)) return false;
  112. if(!isset($this->config->setCode) || $this->config->setCode == 0) unset($project->code);
  113. /* Lean mode relation defaultProgram. */
  114. if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
  115. return $project;
  116. }
  117. /**
  118. * Check product and branch not empty.
  119. *
  120. * @param object $project
  121. * @param object $rawdata
  122. * @access protected
  123. * @return bool
  124. */
  125. private function checkProductAndBranch($project, $rawdata)
  126. {
  127. if(!empty($rawdata->products))
  128. {
  129. $topProgramID = (int)$this->loadModel('program')->getTopByID((int)$project->parent);
  130. $multipleProducts = $this->loadModel('product')->getMultiBranchPairs($topProgramID);
  131. foreach($rawdata->products as $index => $productID)
  132. {
  133. if($this->app->apiVersion == 'v1')
  134. {
  135. $product = $this->loadModel('product')->getById($productID);
  136. if(empty($product)) dao::$errors['products'][$index][$productID] = $this->lang->project->api->error->productNotFound;
  137. }
  138. if(isset($multipleProducts[$productID]))
  139. {
  140. foreach($rawdata->branch[$index] as $branchID)
  141. {
  142. if($branchID == '')
  143. {
  144. dao::$errors["branch[{$index}][]"] = $this->lang->project->error->emptyBranch;
  145. }
  146. }
  147. }
  148. }
  149. }
  150. return !dao::isError();
  151. }
  152. /**
  153. * Check days and budget by rules.
  154. *
  155. * @param object $project
  156. * @param object $rawdata
  157. * @access protected
  158. * @return bool
  159. */
  160. private function checkDaysAndBudget($project, $rawdata)
  161. {
  162. /* Judge workdays is legitimate. */
  163. $workdays = helper::diffDate($project->end, $project->begin) + 1;
  164. if(isset($project->days) && $project->days > $workdays)
  165. {
  166. dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
  167. return false;
  168. }
  169. /* 如果没有选择长期,则判断计划结束日期不能为空. */
  170. if($this->post->delta != 999 and !$project->end)
  171. {
  172. dao::$errors['end'] = $this->lang->project->copyProject->endTips;
  173. return false;
  174. }
  175. if(!empty($project->budget))
  176. {
  177. if(!is_numeric($project->budget))
  178. {
  179. dao::$errors['budget'] = sprintf($this->lang->project->error->budgetNumber);
  180. return false;
  181. }
  182. elseif(is_numeric($project->budget) && ($project->budget < 0))
  183. {
  184. dao::$errors['budget'] = sprintf($this->lang->project->error->budgetGe0);
  185. return false;
  186. }
  187. else
  188. {
  189. $project->budget = round((float)$rawdata->budget, 2);
  190. }
  191. }
  192. return true;
  193. }
  194. /**
  195. * Check product name unique and not empty.
  196. *
  197. * @param object $project
  198. * @param object $rawdata
  199. * @access protected
  200. * @return bool
  201. */
  202. private function checkProductNameUnqiue($project, $rawdata)
  203. {
  204. /* When select create new product, product name cannot be empty and duplicate. */
  205. if($project->hasProduct && isset($rawdata->newProduct))
  206. {
  207. if(empty($rawdata->productName))
  208. {
  209. $this->app->loadLang('product');
  210. dao::$errors['productName'] = sprintf($this->lang->error->notempty, $this->lang->product->name);
  211. return false;
  212. }
  213. else
  214. {
  215. $programID = isset($project->parent) ? $project->parent : 0;
  216. $existProductName = $this->dao->select('name')->from(TABLE_PRODUCT)->where('name')->eq($rawdata->productName)->andWhere('program')->eq($programID)->fetch('name');
  217. if(!empty($existProductName))
  218. {
  219. dao::$errors['productName'] = $this->lang->project->error->existProductName;
  220. return false;
  221. }
  222. }
  223. }
  224. return true;
  225. }
  226. /**
  227. * Send variables to create page.
  228. *
  229. * @param string $model
  230. * @param int $programID
  231. * @param int $copyProjectID
  232. * @param string $extra
  233. * @access protected
  234. * @return void
  235. */
  236. protected function buildCreateForm($model, $programID, $copyProjectID, $extra)
  237. {
  238. $this->loadModel('product');
  239. $this->loadModel('program');
  240. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  241. parse_str($extra, $output);
  242. if($this->app->tab == 'program' && $programID) common::setMenuVars('program', $programID);
  243. if($this->app->tab == 'product' && !empty($output['productID'])) $this->product->setMenu($output['productID']);
  244. if($this->app->tab == 'doc') unset($this->lang->doc->menu->project['subMenu']);
  245. if($copyProjectID) $copyProject = isset($this->view->copyProject) ? $this->view->copyProject : $this->getCopyProject((int)$copyProjectID);
  246. $shadow = $copyProjectID && empty($copyProject->hasProduct) ? 1 : 0;
  247. if($model == 'kanban')
  248. {
  249. $this->lang->project->aclList = $this->lang->project->kanbanAclList;
  250. $this->lang->project->subAclList = $this->lang->project->kanbanSubAclList;
  251. }
  252. if($this->view->globalDisableProgram) $programID = $this->config->global->defaultProgram;
  253. $programID = (int)$programID;
  254. $topProgramID = $this->program->getTopByID($programID);
  255. $withProgram = $this->config->systemMode == 'ALM' && helper::hasFeature('program');
  256. $allProducts = $this->program->getProductPairs($programID, 'all', 'noclosed', '', $shadow, $withProgram);
  257. $parentProgram = $this->program->getByID($programID);
  258. $linkedProducts = $linkedBranches = array();
  259. if($copyProjectID)
  260. {
  261. if($copyProject->hasProduct)
  262. {
  263. $linkedProducts = $this->product->getProducts($copyProjectID, 'all', '', true, array(), false);
  264. $linkedBranches = $this->project->getBranchesByProject($copyProjectID);
  265. }
  266. }
  267. else
  268. {
  269. if(!empty($output['productID']))
  270. {
  271. $linkedProduct = $this->product->getByID((int)$output['productID']);
  272. $linkedProduct->branches = array($output['branchID'] => $output['branchID']);
  273. $linkedProducts = array($output['productID'] => $linkedProduct);
  274. }
  275. if(isset($output['branchID'])) $linkedBranches = array($output['branchID'] => $output['branchID']);
  276. }
  277. $productPlans = $this->loadModel('productplan')->getGroupByProduct(array_keys($allProducts), 'skipparent|unexpired');
  278. foreach($productPlans as $productID => $branchPlans)
  279. {
  280. foreach($branchPlans as $branchID => $plans)
  281. {
  282. foreach($plans as $planID => $plan)
  283. {
  284. if(empty($plan)) continue;
  285. $planBegin = $plan->begin == $this->config->productplan->future ? $this->lang->productplan->future : $plan->begin;
  286. $planEnd = $plan->end == $this->config->productplan->future ? $this->lang->productplan->future : $plan->end;
  287. $planDate = $plan->begin == $this->config->productplan->future && $plan->end == $this->config->productplan->future ? ' ' . $this->lang->productplan->future : " [{$planBegin} ~ {$planEnd}]";
  288. $productPlans[$productID][$branchID][$planID] = $plan->title . $planDate;
  289. }
  290. }
  291. }
  292. /* Get copy projects. */
  293. $copyProjects = $this->project->getPairsByModel($model, '', 0, false);
  294. $copyProjectPairs = !commonModel::isTutorialMode() ? array_combine(array_keys($copyProjects), array_column($copyProjects, 'name')) : $copyProjects;
  295. $this->view->title = $this->lang->project->create;
  296. $this->view->gobackLink = (isset($output['from']) && $output['from'] == 'global') ? $this->createLink('project', 'browse') : '';
  297. $this->view->model = $model;
  298. $this->view->PMUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst');
  299. $this->view->users = $this->user->getPairs('noclosed|nodeleted');
  300. $this->view->programID = $programID;
  301. $this->view->productID = isset($output['productID']) ? $output['productID'] : 0;
  302. $this->view->branchID = isset($output['branchID']) ? $output['branchID'] : 0;
  303. $this->view->allProducts = $allProducts;
  304. $this->view->multiBranchProducts = $this->product->getMultiBranchPairs((int)$topProgramID);
  305. $this->view->copyProjects = $copyProjects;
  306. $this->view->copyPinyinList = common::convert2Pinyin($copyProjectPairs);
  307. $this->view->copyProjectID = $copyProjectID;
  308. $this->view->parentProgram = $parentProgram;
  309. $this->view->programList = $this->program->getParentPairs();
  310. $this->view->URSRPairs = $this->loadModel('custom')->getURSRPairs();
  311. $this->view->availableBudget = $parentProgram ? $this->program->getBudgetLeft($parentProgram) : 0;
  312. $this->view->budgetUnitList = $this->project->getBudgetUnitList();
  313. $this->view->branchGroups = $this->loadModel('execution')->getBranchByProduct(array_keys($allProducts));
  314. $this->view->productPlans = $productPlans;
  315. $this->view->groups = $this->loadModel('group')->getPairs();
  316. if(!isset($this->view->linkedProducts)) $this->view->linkedProducts = $linkedProducts;
  317. if(!isset($this->view->linkedBranches)) $this->view->linkedBranches = $linkedBranches;
  318. }
  319. /**
  320. * Send variables to edit page.
  321. *
  322. * @param int $projectID
  323. * @param object $project
  324. * @param string $from
  325. * @param int $programID
  326. * @access protected
  327. * @return void
  328. */
  329. protected function buildEditForm($projectID, $project, $from = '', $programID = 0)
  330. {
  331. $withProgram = $this->config->systemMode == 'ALM' && helper::hasFeature('program');
  332. $allProducts = $this->loadModel('program')->getProductPairs($project->parent, 'all', 'noclosed', '', 0, $withProgram);
  333. $branchGroups = $this->loadModel('execution')->getBranchByProduct(array_keys($allProducts));
  334. $projectBranches = $this->project->getBranchesByProject($projectID);
  335. $linkedProductIdList = empty($projectBranches) ? '' : array_keys($projectBranches);
  336. $parentProject = $this->program->getByID($project->parent);
  337. $linkedProducts = $this->loadModel('product')->getProducts($projectID, 'all', '', true, $linkedProductIdList, false);
  338. $plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($linkedProducts), 'skipparent|unexpired');
  339. $productPlans = $linkedBranches = array();
  340. foreach($linkedProducts as $productID => $linkedProduct)
  341. {
  342. if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
  343. foreach($projectBranches[$productID] as $branchID => $branch) $linkedBranches[$productID][$branchID] = $branchID;
  344. if(!empty($plans[$productID]))
  345. {
  346. if(isset($plans[$productID]['']) && !isset($plans[$productID][0]))
  347. {
  348. $plans[$productID][0] = $plans[$productID][''];
  349. unset($plans[$productID]['']);
  350. }
  351. foreach($plans[$productID] as $branchID => $branchPlans)
  352. {
  353. if(isset($branchPlans['']))
  354. {
  355. if(isset($branchPlans[0]))
  356. {
  357. $branchPlans[0] = array_merge($branchPlans[0], $branchPlans['']);
  358. }
  359. else
  360. {
  361. $branchPlans[0] = $branchPlans[''];
  362. }
  363. unset($branchPlans['']);
  364. }
  365. }
  366. }
  367. if(!empty($linkedProduct->branches))
  368. {
  369. foreach($linkedProduct->branches as $branchID)
  370. {
  371. if(!empty($plans[$productID][$branchID]))
  372. {
  373. foreach($plans[$productID][$branchID] as $plan)
  374. {
  375. $productPlans[$productID][$branchID][$plan->id] = $plan->begin == $this->config->productplan->future && $plan->end == $this->config->productplan->future ? $plan->title . ' ' . $this->lang->productplan->future : $plan->title . " [{$plan->begin} ~ {$plan->end}]";
  376. }
  377. }
  378. }
  379. }
  380. }
  381. $productPlansOrder = array();
  382. foreach($productPlans as $productID => $branchPlans)
  383. {
  384. foreach($branchPlans as $branchID => $plans)
  385. {
  386. $orderPlans = $this->loadModel('productplan')->getByIDList(array_keys($plans));
  387. $orderPlans = $this->productplan->relationBranch($orderPlans);
  388. $orderPlansMap = array_keys($orderPlans);
  389. $branchName = !empty($branchGroups[$productID][$branchID]) ? ' / ' . $branchGroups[$productID][$branchID] : '';
  390. foreach($orderPlansMap as $planMapID)
  391. {
  392. if(empty($plans[$planMapID])) $productPlans[$productID][$branchID][$planMapID] = $orderPlans[$planMapID]->title;
  393. $productPlansOrder[$productID][$branchID][$planMapID] = $productPlans[$productID][$branchID][$planMapID] . $branchName;
  394. }
  395. }
  396. }
  397. $program = $this->program->getByID($project->parent);
  398. $programList = $this->program->getParentPairs();
  399. $disableParent = false;
  400. if(!isset($programList[$project->parent]))
  401. {
  402. $disableParent = true;
  403. $programList = array($project->parent => $program->name);
  404. }
  405. $this->view->title = $this->lang->project->edit;
  406. $this->view->PMUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst', $project->PM);
  407. $this->view->users = $this->user->getPairs('noclosed|nodeleted');
  408. $this->view->project = $project;
  409. $this->view->programList = $programList;
  410. $this->view->parentProgram = $program;
  411. $this->view->projectID = $projectID;
  412. $this->view->allProducts = $allProducts;
  413. $this->view->multiBranchProducts = $this->product->getMultiBranchPairs();
  414. $this->view->productPlans = array_filter($productPlansOrder);
  415. $this->view->linkedProducts = $linkedProducts;
  416. $this->view->linkedBranches = $linkedBranches;
  417. $this->view->unmodifiableProducts = $this->getUnmodifiableProducts($projectID, $project);
  418. $this->view->branchGroups = $branchGroups;
  419. $this->view->executions = $this->execution->getPairs($projectID);
  420. $this->view->URSRPairs = $this->loadModel('custom')->getURSRPairs();
  421. $this->view->parentProject = $parentProject;
  422. $this->view->availableBudget = $parentProject ? $this->program->getBudgetLeft($parentProject) + (float)$project->budget : $project->budget;
  423. $this->view->budgetUnitList = $this->project->getBudgetUnitList();
  424. $this->view->model = $project->model;
  425. $this->view->disableModel = $this->project->checkCanChangeModel($projectID, $project->model) ? false : true;
  426. $this->view->teamMembers = $this->user->getTeamMemberPairs($projectID, 'project');
  427. $this->view->from = $from;
  428. $this->view->programID = $programID;
  429. $this->view->disableParent = $disableParent;
  430. $this->view->groups = $this->loadModel('group')->getPairs();
  431. if(in_array($this->config->edition, array('max', 'ipd')) && $this->project->checkUploadedDeliverable($projectID))
  432. {
  433. $this->view->disableModel = true;
  434. }
  435. }
  436. /**
  437. * 展示创建任务后的提示。
  438. * Display tips after created.
  439. *
  440. * @param int $projectID
  441. * @access protected
  442. * @return void
  443. */
  444. protected function displayAfterCreated($projectID)
  445. {
  446. $this->session->set('project', $projectID);
  447. $project = $this->project->getByID($projectID);
  448. $products = $this->loadModel('product')->getProducts($projectID);
  449. $this->view->title = $this->lang->project->tips;
  450. $this->view->project = $project;
  451. $this->view->projectID = $projectID;
  452. $this->view->products = $products;
  453. $this->view->executionID = empty($project->multiple) ? $this->loadModel('execution')->getNoMultipleID($projectID) : 0;
  454. $this->display('project', 'tips');
  455. }
  456. /**
  457. * Get copy project and send variables to create page.
  458. *
  459. * @param int $copyProjectID
  460. * @access protected
  461. * @return void
  462. */
  463. private function getCopyProject($copyProjectID)
  464. {
  465. $copyProject = $this->project->getByID($copyProjectID);
  466. $products = $this->product->getProducts($copyProjectID);
  467. foreach($products as $product)
  468. {
  469. $branches = implode(',', $product->branches);
  470. $copyProject->productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id, $branches, 'noclosed', true);
  471. }
  472. $this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'noclosed');
  473. $this->view->products = $products;
  474. $this->view->copyProject = $copyProject;
  475. return $copyProject;
  476. }
  477. /**
  478. * Append extras data to post data.
  479. *
  480. * @param object $postData
  481. * @access protected
  482. * @return int|object
  483. */
  484. protected function prepareStartExtras($postData)
  485. {
  486. return $postData->add('status', 'doing')
  487. ->add('lastEditedBy', $this->app->user->account)
  488. ->add('lastEditedDate', helper::now())
  489. ->get();
  490. }
  491. /**
  492. * Append extras data to post data.
  493. *
  494. * @param int $iprojectID
  495. * @param object $postData
  496. *
  497. * @access protected
  498. * @return object
  499. * @param int $projectID
  500. */
  501. protected function prepareSuspendExtras($projectID, $postData)
  502. {
  503. $editorIdList = $this->config->project->editor->suspend['id'];
  504. return $postData->add('id', $projectID)
  505. ->setDefault('status', 'suspended')
  506. ->setDefault('lastEditedBy', $this->app->user->account)
  507. ->setDefault('lastEditedDate', helper::now())
  508. ->setDefault('suspendedDate', helper::now())
  509. ->stripTags($editorIdList, $this->config->allowedTags)
  510. ->get();
  511. }
  512. /**
  513. * Append extras data to post data.
  514. *
  515. * @param int $iprojectID
  516. * @param object $postData
  517. *
  518. * @access protected
  519. * @return object
  520. * @param int $projectID
  521. */
  522. protected function prepareClosedExtras($projectID, $postData)
  523. {
  524. $editorIdList = $this->config->project->editor->suspend['id'];
  525. return $postData->add('id', $projectID)
  526. ->setDefault('status', 'closed')
  527. ->setDefault('closedBy', $this->app->user->account)
  528. ->setDefault('closedDate', helper::now())
  529. ->setDefault('lastEditedBy', $this->app->user->account)
  530. ->setDefault('lastEditedDate', helper::now())
  531. ->stripTags($editorIdList, $this->config->allowedTags)
  532. ->get();
  533. }
  534. /**
  535. * 为项目的bug列表准备分支数据。
  536. * Prepare branches for project bug list.
  537. *
  538. * @param array $products
  539. * @param int $productID
  540. * @access protected
  541. * @return void
  542. */
  543. protected function prepareBranchForBug($products, $productID)
  544. {
  545. $this->loadModel('branch');
  546. $branchOption = array();
  547. $branchTagOption = array();
  548. foreach($products as $product)
  549. {
  550. if($productID && $productID != $product->id) continue;
  551. if($product && $product->type != 'normal')
  552. {
  553. /* Display status of branch. */
  554. $branches = $this->branch->getList($product->id, 0, 'all');
  555. foreach($branches as $branchInfo)
  556. {
  557. $branchOption[$branchInfo->id] = $branchInfo->name;
  558. $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
  559. }
  560. }
  561. }
  562. $this->view->branchOption = $branchOption;
  563. $this->view->branchTagOption = $branchTagOption;
  564. }
  565. /**
  566. * 为项目bug列表准备模块数据。
  567. * Prepare modules for project bug list.
  568. *
  569. * @param int $productID
  570. * @param int $projectID
  571. * @param string $type
  572. * @param int $param
  573. * @param string $orderBy
  574. * @param int $build
  575. * @param string $branchID
  576. * @param array $products
  577. * @access protected
  578. * @return void
  579. */
  580. protected function prepareModuleForBug($productID, $projectID, $type, $param, $orderBy, $build, $branchID, $products)
  581. {
  582. $moduleID = $type != 'bysearch' ? $param : 0;
  583. $modules = $this->loadModel('tree')->getAllModulePairs('bug');
  584. /* Get module tree.*/
  585. $extra = array('projectID' => $projectID, 'orderBy' => $orderBy, 'type' => $type, 'build' => $build, 'branchID' => $branchID);
  586. if($projectID and empty($productID) and count($products) > 1)
  587. {
  588. $moduleTree = $this->tree->getBugTreeMenu($projectID, $productID, 0, array('treeModel', 'createBugLink'), $extra);
  589. }
  590. elseif(!empty($products))
  591. {
  592. $productID = empty($productID) ? reset($products)->id : $productID;
  593. $moduleTree = $this->tree->getTreeMenu($productID, 'bug', 0, array('treeModel', 'createBugLink'), $extra + array('productID' => $productID, 'branchID' => $branchID), $branchID);
  594. }
  595. else
  596. {
  597. $moduleTree = array();
  598. }
  599. $tree = $moduleID ? $this->tree->getByID($moduleID) : '';
  600. $showModule = !empty($this->config->project->bug->showModule) ? $this->config->project->bug->showModule : '';
  601. $this->view->moduleTree = $moduleTree;
  602. $this->view->modules = $modules;
  603. $this->view->moduleID = $moduleID;
  604. $this->view->moduleName = !empty($tree->name) ? $tree->name : $this->lang->tree->all;
  605. $this->view->modulePairs = $showModule ? $this->tree->getModulePairs($productID, 'bug', $showModule) : array();
  606. }
  607. /**
  608. * 处理项目bug列表的搜索表单。
  609. * Process search form for project bug list.
  610. *
  611. * @param object $project
  612. * @param string $type
  613. * @param int $param
  614. * @param int $projectID
  615. * @param int $productID
  616. * @param string $branchID
  617. * @param string $orderBy
  618. * @param int $build
  619. * @param array $products
  620. * @access protected
  621. * @return void
  622. */
  623. protected function processBugSearchParams($project, $type, $param, $projectID, $productID, $branchID, $orderBy, $build, $products)
  624. {
  625. $this->loadModel('bug');
  626. if(!$project->hasProduct)
  627. {
  628. unset($this->config->bug->search['fields']['product']);
  629. if($project->model != 'scrum') unset($this->config->bug->search['fields']['plan']);
  630. }
  631. if(!$project->multiple and !$project->hasProduct) unset($this->config->bug->search['fields']['plan']);
  632. unset($this->config->bug->search['fields']['project']);
  633. $queryID = ($type == 'bysearch') ? (int)$param : 0;
  634. /* Build the search form. */
  635. $actionURL = $this->createLink('project', 'bug', "projectID=$projectID&productID=$productID&branchID=$branchID&orderBy=$orderBy&build=$build&type=bysearch&queryID=myQueryID");
  636. $this->loadModel('execution')->buildBugSearchForm($products, $queryID, $actionURL, 'project');
  637. }
  638. /**
  639. * 处理项目版本列表的搜索表单。
  640. * Process search form for project build list.
  641. *
  642. * @param object $project
  643. * @param object $product
  644. * @param array $products
  645. * @param string $type
  646. * @param int $param
  647. * @access protected
  648. * @return void
  649. */
  650. protected function processBuildSearchParams($project, $product, $products, $type, $param)
  651. {
  652. /* 为无迭代项目和多分支产品处理搜索表单的参数。 */
  653. if($project->multiple)
  654. {
  655. $executionPairs = $this->loadModel('execution')->getByProject($project->id, 'all', '', true, $project->model == 'waterfall');
  656. $this->config->build->search['fields']['execution'] = zget($this->lang->project->executionList, $project->model);
  657. $this->config->build->search['params']['execution'] = array('operator' => '=', 'control' => 'select', 'values' => $executionPairs);
  658. }
  659. if(!$project->hasProduct) unset($this->config->build->search['fields']['product']);
  660. if(!empty($product->type) && $product->type != 'normal')
  661. {
  662. $branches = array(BRANCH_MAIN => $this->lang->branch->main) + $this->loadModel('branch')->getPairs($product->id, '', $projectID);
  663. $this->config->build->search['fields']['branch'] = sprintf($this->lang->build->branchName, $this->lang->product->branchName[$product->type]);
  664. $this->config->build->search['params']['branch'] = array('operator' => '=', 'control' => 'select', 'values' => $branches);
  665. }
  666. /* Build the search form. */
  667. $type = strtolower($type);
  668. $queryID = $type == 'bysearch' ? (int)$param : 0;
  669. $actionURL = $this->createLink($this->app->rawModule, $this->app->rawMethod, "projectID={$project->id}&type=bysearch&queryID=myQueryID");
  670. $this->project->buildProjectBuildSearchForm($products, $queryID, $actionURL, 'project', $project);
  671. }
  672. /**
  673. * 处理项目权限分组可勾选的项。
  674. * Process group privs for project.
  675. *
  676. * @param object $project
  677. * @access protected
  678. * @return void
  679. */
  680. protected function processGroupPrivs($project)
  681. {
  682. if($project->hasProduct)
  683. {
  684. unset($this->lang->resource->productplan);
  685. unset($this->lang->resource->tree);
  686. }
  687. else
  688. {
  689. $this->lang->productplan->common = $this->lang->productplan->plan;
  690. $this->lang->projectstory->common = $this->lang->projectstory->storyCommon;
  691. $this->lang->projectstory->story = $this->lang->projectstory->storyList;
  692. $this->lang->projectstory->view = $this->lang->projectstory->storyView;
  693. unset($this->lang->resource->project->manageProducts);
  694. unset($this->lang->resource->projectstory->linkStory);
  695. unset($this->lang->resource->projectstory->importplanstories);
  696. unset($this->lang->resource->projectstory->unlinkStory);
  697. unset($this->lang->resource->projectstory->batchUnlinkStory);
  698. unset($this->lang->resource->story->view);
  699. unset($this->lang->resource->tree->browseTask);
  700. unset($this->lang->resource->tree->browsehost);
  701. unset($this->lang->resource->tree->editHost);
  702. unset($this->lang->resource->tree->fix);
  703. }
  704. if($project->model == 'waterfall' or $project->model == 'waterfallplus')
  705. {
  706. unset($this->lang->resource->productplan);
  707. unset($this->lang->resource->projectplan);
  708. }
  709. if($project->model == 'scrum') unset($this->lang->resource->projectstory->track);
  710. if(!$project->multiple and !$project->hasProduct)
  711. {
  712. unset($this->lang->resource->story->batchChangePlan);
  713. unset($this->lang->resource->execution->importplanstories);
  714. }
  715. }
  716. /**
  717. * 为bug列表视图构造必要的变量。
  718. *
  719. * @param int $productID
  720. * @param int $projectID
  721. * @param object $project
  722. * @param string $type
  723. * @param int $param
  724. * @param string $orderBy
  725. * @param int $build
  726. * @param string $branchID
  727. * @param array $products
  728. * @param int $recTotal
  729. * @param int $recPerPage
  730. * @param int $pageID
  731. * @access protected
  732. * @return void
  733. */
  734. protected function buildBugView($productID, $projectID, $project, $type, $param, $orderBy, $build, $branchID, $products, $recTotal, $recPerPage, $pageID)
  735. {
  736. $this->loadModel('bug');
  737. $this->loadModel('user');
  738. /* Load pager and get bugs, user. */
  739. $this->app->loadClass('pager', true);
  740. $pager = new pager($recTotal, $recPerPage, $pageID);
  741. $sort = common::appendOrder($orderBy);
  742. $bugs = $this->bug->getProjectBugs($projectID, $productID, $branchID, $build, $type, $param, $sort, '', $pager);
  743. $bugs = $this->bug->processBuildForBugs($bugs);
  744. $bugs = $this->bug->batchAppendDelayedDays($bugs);
  745. if($this->config->edition != 'open') $bugRelatedObjectList = $this->loadModel('custom')->getRelatedObjectList(array_keys($bugs), 'bug', 'byRelation', true);
  746. /* Get story and task id list. */
  747. $storyIdList = $taskIdList = array();
  748. foreach($bugs as $bug)
  749. {
  750. if($bug->story) $storyIdList[$bug->story] = $bug->story;
  751. if($bug->task) $taskIdList[$bug->task] = $bug->task;
  752. if($bug->toTask) $taskIdList[$bug->toTask] = $bug->toTask;
  753. if($this->config->edition != 'open') $bug->relatedObject = zget($bugRelatedObjectList, $bug->id, 0);
  754. }
  755. $storyList = $storyIdList ? $this->loadModel('story')->getPairsByList($storyIdList) : array();
  756. $taskList = $taskIdList ? $this->loadModel('task')->getPairsByIdList($taskIdList) : array();
  757. $this->view->title = $project->name . $this->lang->hyphen . $this->lang->bug->common;
  758. $this->view->bugs = $bugs;
  759. $this->view->build = $this->loadModel('build')->getById($build);
  760. $this->view->buildID = $this->view->build ? $this->view->build->id : 0;
  761. $this->view->pager = $pager;
  762. $this->view->orderBy = $orderBy;
  763. $this->view->type = $type;
  764. $this->view->param = $param;
  765. $this->view->productID = $productID;
  766. $this->view->project = $project;
  767. $this->view->branchID = empty($this->view->build->branch) ? $branchID : $this->view->build->branch;
  768. $this->view->builds = $this->loadModel('build')->getBuildPairs(array($productID));
  769. $this->view->users = $this->user->getPairs('noletter');
  770. $this->view->executions = array(0 => '') + $this->loadModel('execution')->getPairs($projectID, 'all', 'empty|withdelete');
  771. $this->view->plans = array(0 => '') + $this->loadModel('productplan')->getPairs($productID ? $productID : array_keys($products));
  772. $this->view->stories = $storyList;
  773. $this->view->tasks = $taskList;
  774. $this->view->projectPairs = $this->project->getPairsByProgram();
  775. $this->view->switcherParams = "projectID={$projectID}&productID={$productID}&currentMethod=bug";
  776. $this->view->switcherText = isset($products[$productID]) ? $products[$productID]->name : $this->lang->product->all;
  777. $this->view->switcherObjectID = $productID;
  778. if(empty($project->hasProduct)) $this->config->excludeSwitcherList[] = 'project-bug';
  779. $this->display();
  780. }
  781. /**
  782. * Send variables to view page.
  783. *
  784. * @param object $project
  785. * @access protected
  786. *
  787. * @return void
  788. */
  789. protected function buildStartForm($project)
  790. {
  791. $this->view->title = $this->lang->project->start;
  792. $this->view->project = $project;
  793. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  794. $this->view->actions = $this->loadModel('action')->getList('project', $project->id);
  795. $this->display();
  796. }
  797. /**
  798. * After starting the project, do other operations.
  799. *
  800. * @param object $project
  801. * @param array $changes
  802. * @param string $comment
  803. *
  804. * @access protected
  805. * @return void
  806. */
  807. protected function responseAfterStart($project, $changes, $comment)
  808. {
  809. if($comment != '' || !empty($changes))
  810. {
  811. $actionID = $this->loadModel('action')->create('project', $project->id, 'Started', $comment);
  812. $this->action->logHistory($actionID, $changes);
  813. }
  814. $this->loadModel('common')->syncPPEStatus($project->id);
  815. $this->executeHooks($project->id);
  816. }
  817. /**
  818. * After suspending the project, do other operations.
  819. *
  820. * @param int $projectID
  821. * @param array $changes
  822. * @param string $comment
  823. *
  824. * @access protected
  825. * @return void
  826. */
  827. protected function responseAfterSuspend($projectID, $changes, $comment)
  828. {
  829. if($comment != '' || !empty($changes))
  830. {
  831. $actionID = $this->loadModel('action')->create('project', $projectID, 'Suspended', $comment);
  832. $this->action->logHistory($actionID, $changes);
  833. }
  834. $this->loadModel('common')->syncPPEStatus($projectID);
  835. $this->executeHooks($projectID);
  836. }
  837. /**
  838. * Send variables to suspend page.
  839. *
  840. * @param int $projectID
  841. * @access protected
  842. */
  843. protected function buildSuspendForm($projectID)
  844. {
  845. $this->view->title = $this->lang->project->suspend;
  846. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  847. $this->view->actions = $this->loadModel('action')->getList('project', $projectID);
  848. $this->view->project = $this->project->getByID($projectID);
  849. $this->display();
  850. }
  851. /**
  852. * After closing the project, do other operations.
  853. *
  854. * @param int $projectID
  855. * @param array $changes
  856. * @param string $comment
  857. *
  858. * @access protected
  859. * @return void
  860. */
  861. protected function responseAfterClose($projectID, $changes, $comment)
  862. {
  863. if($comment != '' || !empty($changes))
  864. {
  865. $actionID = $this->loadModel('action')->create('project', $projectID, 'Closed', $comment);
  866. $this->action->logHistory($actionID, $changes);
  867. }
  868. $this->loadModel('common')->syncPPEStatus($projectID);
  869. $this->executeHooks($projectID);
  870. }
  871. /**
  872. * Send variables to close page.
  873. *
  874. * @param int $projectID
  875. * @access protected
  876. *
  877. * @return void
  878. */
  879. protected function buildClosedForm($projectID)
  880. {
  881. $project = $this->project->getByID($projectID);
  882. $confirmTip = '';
  883. if(empty($project->multiple))
  884. {
  885. $executionID = $this->loadModel('execution')->getNoMultipleID($projectID);
  886. $unclosedTasks = $executionID ? $this->loadModel('task')->getUnclosedTasksByExecution($executionID) : array();
  887. if(!empty($unclosedTasks)) $confirmTip = sprintf($this->lang->execution->confirmCloseExecution, implode($this->lang->comma, $unclosedTasks));
  888. $confirmTip = str_replace($this->lang->executionCommon, $this->lang->projectCommon, $confirmTip);
  889. }
  890. else
  891. {
  892. $unclosedExecutions = $this->loadModel('execution')->getByProject($projectID, 'noclosed', 0, true);
  893. if(!empty($unclosedExecutions))
  894. {
  895. $unclosedExecutions = implode($this->lang->comma, $unclosedExecutions);
  896. $unclosedExecutions = str_replace("{$project->name}/", '', $unclosedExecutions);
  897. $confirmTip = sprintf($this->lang->project->confirmCloseProject, $unclosedExecutions);
  898. }
  899. }
  900. $this->view->title = $this->lang->project->close;
  901. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  902. $this->view->project = $project;
  903. $this->view->actions = $this->loadModel('action')->getList('project', $projectID);
  904. $this->view->confirmTip = $confirmTip;
  905. $this->display();
  906. }
  907. /**
  908. * Send variables to activate page.
  909. *
  910. * @param object $project
  911. * @access protected
  912. *
  913. * @return void
  914. */
  915. protected function buildActivateForm($project)
  916. {
  917. $newBegin = date('Y-m-d');
  918. $dateDiff = helper::diffDate($newBegin, $project->begin);
  919. $dateTime = (int)(strtotime($project->end) + $dateDiff * 24 * 3600);
  920. $newEnd = date('Y-m-d', $dateTime);
  921. $this->view->title = $this->lang->project->activate;
  922. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  923. $this->view->actions = $this->loadModel('action')->getList('project', $project->id);
  924. $this->view->newBegin = $newBegin;
  925. $this->view->newEnd = $newEnd;
  926. $this->view->project = $project;
  927. $this->display();
  928. }
  929. /**
  930. * After activateing the project, do other operations.
  931. *
  932. * @param int $projectID
  933. * @param array $changes
  934. *
  935. * @access protected
  936. * @return void
  937. */
  938. protected function responseAfterActivate($projectID, $changes)
  939. {
  940. if($this->post->comment != '' || !empty($changes))
  941. {
  942. $actionID = $this->loadModel('action')->create('project', $projectID, 'Activated', $this->post->comment);
  943. $this->action->logHistory($actionID, $changes);
  944. }
  945. $this->executeHooks($projectID);
  946. }
  947. /**
  948. * Append extras data to post data.
  949. *
  950. * @param int $iprojectID
  951. * @param object $postData
  952. *
  953. * @access protected
  954. * @return object
  955. * @param int $projectID
  956. */
  957. protected function prepareActivateExtras($projectID, $postData)
  958. {
  959. $rawdata = $postData->rawdata;
  960. $oldProject = $this->project->getByID($projectID);
  961. $editorIdList = $this->config->project->editor->activate['id'];
  962. return $postData->add('id', $projectID)
  963. ->setDefault('realEnd', null)
  964. ->setDefault('status', 'doing')
  965. ->setDefault('lastEditedBy', $this->app->user->account)
  966. ->setDefault('lastEditedDate', helper::now())
  967. ->setIF($rawdata->begin == '0000-00-00', 'begin', '')
  968. ->setIF($rawdata->end == '0000-00-00', 'end', '')
  969. ->stripTags($editorIdList, $this->config->allowedTags)
  970. ->add('closedBy', '')
  971. ->add('closedDate', null)
  972. ->add('closedReason', '')
  973. ->get();
  974. }
  975. /**
  976. * 从项目中删除所有关联的执行。
  977. * removes all associated executions from the be deleted project
  978. *
  979. * @param array $executionIdList
  980. *
  981. * @access protected
  982. * @return void
  983. */
  984. protected function removeAssociatedExecutions($executionIdList)
  985. {
  986. $this->project->deleteByTableName(TABLE_EXECUTION, array_keys($executionIdList));
  987. foreach($executionIdList as $executionID => $execution) $this->loadModel('action')->create('execution', $executionID, 'deleted', '', actionModel::CAN_UNDELETED);
  988. $this->loadModel('user')->updateUserView($executionIdList, 'sprint');
  989. }
  990. /**
  991. * 从项目中删除所有关联的产品。
  992. * removes all associated products from the be deleted project
  993. *
  994. * @param object $project
  995. *
  996. * @access protected
  997. * @return void
  998. */
  999. protected function removeAssociatedProducts($project)
  1000. {
  1001. /* Delete shadow product.*/
  1002. if(!$project->hasProduct)
  1003. {
  1004. $productID = $this->loadModel('product')->getProductIDByProject($project->id);
  1005. $product = $this->product->getById($productID);
  1006. if($product->shadow) $this->project->deleteByTableName('zt_product', $productID);
  1007. }
  1008. }
  1009. /**
  1010. * 管理项目的关联产品:构建数据,更新产品变动,记录日志。
  1011. * Manage project related products: build data, update product changes, and record actions.
  1012. *
  1013. * @param int $projectID
  1014. * @param object $project
  1015. * @param array $IdList
  1016. * @access protected
  1017. * @return bool
  1018. */
  1019. protected function updateLinkedProducts($projectID, $project, $IdList)
  1020. {
  1021. /* 获取旧产品数据,并更新。*/
  1022. /* Get old product data and update it. */
  1023. $formerProducts = $this->loadModel('product')->getProducts($projectID);
  1024. $this->project->updateProducts($projectID);
  1025. if(dao::isError()) return false;
  1026. /* 如果关联产品新增或删除,记录动态。*/
  1027. /* If add or delete associated products and record their dynamics. */
  1028. $currentProducts = $this->product->getProducts($projectID);
  1029. $formerIds = array_keys($formerProducts);
  1030. $currentIds = array_keys($currentProducts);
  1031. $changes = array_merge(array_diff($formerIds, $currentIds), array_diff($currentIds, $formerIds));
  1032. $this->loadModel('execution');
  1033. /* 如果是无迭代项目,更新关联产品。*/
  1034. /* IF multiple is 0 Update associated products. */
  1035. if(empty($project->multiple))
  1036. {
  1037. $executionID = $this->execution->getNoMultipleID($projectID);
  1038. if($executionID) $this->execution->updateProducts($executionID, $_POST);
  1039. }
  1040. /* 如果是瀑布项目单套阶段,更新关联产品。*/
  1041. /* If it is a single stage of the waterfall project, update associated products. */
  1042. if($project->stageBy == 'project' && in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')))
  1043. {
  1044. foreach($IdList as $executionID)
  1045. {
  1046. $this->execution->updateProducts($executionID, $_POST);
  1047. if($changes) $this->loadModel('action')->create('execution', $executionID, 'Managed', '', implode(',', $currentIds));
  1048. }
  1049. }
  1050. /* 如果有迭代并且是非瀑布项目,记录关联产品执行到action表。*/
  1051. /* If it is multiple project and model isn't waterfall project, record to table action. */
  1052. if($project->multiple && $project->model != 'waterfall' && $project->model != 'waterfallplus')
  1053. {
  1054. $this->recordExecutionsOfUnlinkedProducts($formerProducts, $currentIds, $IdList);
  1055. }
  1056. return true;
  1057. }
  1058. /**
  1059. * 记录取消关联产品的执行到action表。
  1060. * Record the execution of disassociated products to the action table.
  1061. *
  1062. * @param array $formerProducts
  1063. * @param array $selectedIds
  1064. * @param array $executionIdList
  1065. * @access protected
  1066. * @return void
  1067. */
  1068. protected function recordExecutionsOfUnlinkedProducts($formerProducts, $selectedIds, $executionIdList)
  1069. {
  1070. $executionProductGroup = $this->loadModel('project')->getExecutionProductGroup($executionIdList); //项目下所有执行对应的关联产品。
  1071. $unlinkedProductIds = array_diff(array_keys($formerProducts), $selectedIds); //取消关联的产品。
  1072. if(!empty($unlinkedProductIds))
  1073. {
  1074. $unlinkedProductPairs = array(); //取消关联的产品id->name键值对。
  1075. foreach($unlinkedProductIds as $productID) $unlinkedProductPairs[$productID] = $formerProducts[$productID]->name;
  1076. $executions = array(); //取消关联产品的执行
  1077. foreach($executionProductGroup as $executionID => $products) //遍历执行对应关联产品键值对。
  1078. {
  1079. $unlinkedExecutionProducts = array_intersect_key($unlinkedProductPairs, $products); //获取执行中解除的关联产品。
  1080. if($unlinkedExecutionProducts) $executions[$executionID] = $unlinkedExecutionProducts;
  1081. }
  1082. foreach($executions as $executionID => $unlinkedExecutionProducts)
  1083. {
  1084. $this->loadModel('action')->create('execution', $executionID, 'unlinkproduct', '', implode(',', $unlinkedExecutionProducts));
  1085. }
  1086. }
  1087. }
  1088. /**
  1089. * 提取关联用户故事无法移除的项目产品
  1090. * Extract related stories cannot be removed product.
  1091. *
  1092. * @param int $projectID
  1093. * @param object $project
  1094. *
  1095. * @access protected
  1096. * @return void
  1097. */
  1098. protected function extractUnModifyForm($projectID, $project)
  1099. {
  1100. $linkedBranches = array();
  1101. $linkedBranchIdList = array();
  1102. $branches = $this->project->getBranchesByProject($projectID);
  1103. $linkedProductIdList = empty($branches) ? array() : array_keys($branches);
  1104. $allProducts = $this->loadModel('program')->getProductPairs($project->parent, 'all', 'noclosed', implode(',', $linkedProductIdList));
  1105. $linkedProducts = $this->loadModel('product')->getProducts($projectID, 'all', '', true, $linkedProductIdList);
  1106. $projectStories = $this->project->getStoriesByProject($projectID);
  1107. $projectBranches = $this->project->getBranchGroup($projectID, array_keys($linkedProducts));
  1108. /* If the story of the product which linked the project,don't allow to remove the product. */
  1109. $unmodifiableProducts = array();
  1110. $unmodifiableBranches = array();
  1111. $unmodifiableMainBranches = array();
  1112. foreach($linkedProducts as $productID => $linkedProduct)
  1113. {
  1114. $linkedBranches[$productID] = array();
  1115. foreach($branches[$productID] as $branchID => $branch)
  1116. {
  1117. $linkedBranches[$productID][$branchID] = $branchID;
  1118. $linkedBranchIdList[$branchID] = $branchID;
  1119. if(!empty($projectStories[$productID][$branchID]) || !empty($projectBranches[$productID][$branchID]))
  1120. {
  1121. if($branchID == BRANCH_MAIN) $unmodifiableMainBranches[$productID] = $branchID;
  1122. array_push($unmodifiableProducts, $productID);
  1123. array_push($unmodifiableBranches, $branchID);
  1124. }
  1125. }
  1126. }
  1127. /* Build the product from other linked products. */
  1128. if($this->config->systemMode == 'ALM') $this->buildProductForm($project, $allProducts, $linkedBranchIdList, $linkedBranches, $linkedProducts);
  1129. $this->view->linkedBranches = $linkedBranches;
  1130. $this->view->linkedProducts = $linkedProducts;
  1131. $this->view->unmodifiableProducts = !in_array($project->model, array('waterfall', 'waterfallplus')) ? $unmodifiableProducts : array();
  1132. $this->view->disabledProducts = $this->project->getDisabledProducts($project, $linkedProducts);
  1133. $this->view->unmodifiableBranches = $unmodifiableBranches;
  1134. $this->view->unmodifiableMainBranches = $unmodifiableMainBranches;
  1135. $this->view->allProducts = $allProducts;
  1136. $this->view->allBranches = $this->loadModel('branch')->getByProducts(array_keys($allProducts), 'ignoreNormal');
  1137. if($this->config->systemMode != 'ALM') $this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($allProducts), 'ignoreNormal|noclosed', $linkedBranchIdList);
  1138. }
  1139. /**
  1140. * 初始化项目集下其他关联产品中当前产品
  1141. * Build the current product under the projectprogram.
  1142. *
  1143. * @param object $project
  1144. * @param array $allProducts
  1145. * @param array $linkedBranchIdList
  1146. * @param array $linkedBranches
  1147. * @param array $linkedProducts
  1148. *
  1149. * @access protected
  1150. * @return void
  1151. */
  1152. protected function buildProductForm($project, $allProducts, $linkedBranchIdList, $linkedBranches, $linkedProducts)
  1153. {
  1154. $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($allProducts), 'ignoreNormal|noclosed', $linkedBranchIdList);
  1155. $topProgramID = $project->parent ? $this->program->getTopByPath($project->path) : 0;
  1156. $productsGroupByProgram = $this->loadModel('product')->getProductsGroupByProgram();
  1157. $currentProducts = array();
  1158. $otherProducts = array();
  1159. foreach($productsGroupByProgram as $programID => $programProducts)
  1160. {
  1161. if($programID != $topProgramID)
  1162. {
  1163. $otherProducts += $this->getOtherProducts($programProducts, $branchGroups, $linkedBranches, $linkedProducts);
  1164. }
  1165. else
  1166. {
  1167. $currentProducts += $programProducts;
  1168. }
  1169. }
  1170. foreach($linkedProducts as $product)
  1171. {
  1172. if($product->deleted == '1') $product->name = $product->name . ' (' . $this->lang->product->deleted . ')';
  1173. }
  1174. $this->view->currentProducts = $currentProducts;
  1175. $this->view->otherProducts = $otherProducts;
  1176. $this->view->branchGroups = $branchGroups;
  1177. $this->view->linkedBranches = $linkedBranches;
  1178. $this->view->linkedProducts = $linkedProducts;
  1179. }
  1180. /**
  1181. * 获取其他的关联产品
  1182. * Get other products under the projectprogram.
  1183. *
  1184. * @param array $programProducts
  1185. * @param array $branchGroups
  1186. * @param array $linkedBranches
  1187. * @param array $linkedProducts
  1188. *
  1189. * @access protected
  1190. * @return array
  1191. */
  1192. protected function getOtherProducts($programProducts, $branchGroups, $linkedBranches, $linkedProducts)
  1193. {
  1194. $otherProducts = array();
  1195. foreach($programProducts as $productID => $productName)
  1196. {
  1197. if(!empty($branchGroups[$productID]))
  1198. {
  1199. foreach($branchGroups[$productID] as $branchID => $branchName)
  1200. {
  1201. if(isset($linkedProducts[$productID]) && isset($linkedBranches[$productID][$branchID])) continue;
  1202. $otherProducts["{$productID}_{$branchID}"] = $productName . '_' . $branchName;
  1203. }
  1204. }
  1205. else
  1206. {
  1207. if(isset($linkedProducts[$productID])) continue;
  1208. $otherProducts[$productID] = $productName;
  1209. }
  1210. }
  1211. return $otherProducts;
  1212. }
  1213. /**
  1214. * 根据当前所在模块更新二级菜单
  1215. * set project menu
  1216. *
  1217. * @param int $projectID
  1218. * @param int $project
  1219. * @access protected
  1220. * @return void
  1221. * @param int $projectParent
  1222. */
  1223. protected function setProjectMenu($projectID, $projectParent)
  1224. {
  1225. if($this->app->tab == 'program')
  1226. {
  1227. common::setMenuVars('program', $projectParent);
  1228. }
  1229. elseif($this->app->tab == 'project')
  1230. {
  1231. $this->project->setMenu($projectID);
  1232. }
  1233. }
  1234. /**
  1235. * 处理项目列表展示数据。
  1236. * Process project list display data.
  1237. *
  1238. * @param array $projectList
  1239. * @access protected
  1240. * @return array
  1241. */
  1242. protected function processProjectListData($projectList)
  1243. {
  1244. $userList = $this->dao->select('id,account,realname,avatar')->from(TABLE_USER)->fetchAll('account');
  1245. $storyGroup = $this->loadModel('story')->fetchStoriesByProjectIdList(array_keys($projectList));
  1246. $executionGroup = $this->loadModel('execution')->fetchExecutionsByProjectIdList(array_keys($projectList));
  1247. foreach($projectList as $project)
  1248. {
  1249. $project = $this->project->formatDataForList($project, $userList);
  1250. $projectStories = zget($storyGroup, $project->id, array());
  1251. $project->storyCount = count($projectStories);
  1252. $project->storyPoints = 0;
  1253. foreach($projectStories as $story)
  1254. {
  1255. if($story->isParent) continue;
  1256. $project->storyPoints += $story->estimate;
  1257. }
  1258. $project->storyPoints = helper::formatHours($project->storyPoints) . ' ' . $this->config->hourUnit;
  1259. $executions = zget($executionGroup, $project->id, array());
  1260. $project->executionCount = count($executions);
  1261. $project->from = 'project';
  1262. $project->actions = $this->project->buildActionList($project);
  1263. $project->estimate = helper::formatHours($project->estimate);
  1264. $project->consume = helper::formatHours($project->consume);
  1265. $project->left = helper::formatHours($project->left);
  1266. }
  1267. /* 交付物提交进度。 */
  1268. if(in_array($this->config->edition, array('max', 'ipd'))) $projectList = $this->project->countDeliverable($projectList, 'project');
  1269. return array_values($projectList);
  1270. }
  1271. /**
  1272. * 处理版本列表展示数据。
  1273. * Process build list display data.
  1274. *
  1275. * @param array $buildList
  1276. * @param int $projectID
  1277. * @access protected
  1278. * @return object[]
  1279. */
  1280. protected function processBuildListData($buildList, $projectID)
  1281. {
  1282. $this->loadModel('build');
  1283. $this->loadModel('branch');
  1284. $showBranch = false;
  1285. $productIdList = array();
  1286. foreach($buildList as $build) $productIdList[$build->product] = $build->product;
  1287. /* Get branch name. */
  1288. $branchGroups = $this->branch->getByProducts($productIdList);
  1289. $builds = array();
  1290. $project = $this->project->getByID($projectID);
  1291. foreach($buildList as $build)
  1292. {
  1293. $build->branchName = '';
  1294. if(isset($branchGroups[$build->product]))
  1295. {
  1296. $showBranch = true;
  1297. $branchPairs = $branchGroups[$build->product];
  1298. foreach(explode(',', trim($build->branch, ',')) as $branchID)
  1299. {
  1300. if(isset($branchPairs[$branchID])) $build->branchName .= "{$branchPairs[$branchID]},";
  1301. }
  1302. $build->branchName = trim($build->branchName, ',');
  1303. if(empty($build->branchName) and empty($build->builds)) $build->branchName = $this->lang->branch->main;
  1304. }
  1305. if($project->multiple && empty($build->execution))
  1306. {
  1307. $rowspan = $build->scmPath && $build->filePath ? 2 : 1;
  1308. $buildCount = count($build->builds);
  1309. $rowspan = $buildCount > $rowspan ? $buildCount : $rowspan;
  1310. $pathRowspan = $build->scmPath && $build->filePath ? floor($rowspan/2) : $rowspan;
  1311. if($buildCount >= 2)
  1312. {
  1313. $i = 1;
  1314. foreach($build->builds as $childBuild)
  1315. {
  1316. $buildInfo = clone $build;
  1317. $buildInfo->executionName = $childBuild->executionName;
  1318. $buildInfo->rowspan = $rowspan;
  1319. if($i <= $pathRowspan)
  1320. {
  1321. $buildInfo->pathRowspan = $pathRowspan;
  1322. $buildInfo->pathType = empty($build->scmPath) ? 'filePath' : 'scmPath';
  1323. $buildInfo->path = empty($build->scmPath) ? $build->filePath : $build->scmPath;
  1324. }
  1325. elseif($i > $pathRowspan)
  1326. {
  1327. $buildInfo->pathRowspan = $rowspan - $pathRowspan;
  1328. $buildInfo->pathType = 'filePath';
  1329. $buildInfo->path = $build->filePath;
  1330. }
  1331. $builds[] = $buildInfo;
  1332. $i ++;
  1333. }
  1334. }
  1335. else
  1336. {
  1337. $childBuild = !empty($build->builds) ? current($build->builds) : array();
  1338. $build->executionName = !empty($childBuild) ? $childBuild->executionName : '';
  1339. if($build->scmPath && $build->filePath)
  1340. {
  1341. $build->rowspan = 2;
  1342. $build->executionRowspan = 2;
  1343. $buildInfo = clone $build;
  1344. $buildInfo->pathType = 'scmPath';
  1345. $buildInfo->path = $build->scmPath;
  1346. $builds[] = $buildInfo;
  1347. $buildInfo = clone $build;
  1348. $buildInfo->pathType = 'filePath';
  1349. $buildInfo->path = $build->filePath;
  1350. $builds[] = $buildInfo;
  1351. }
  1352. else
  1353. {
  1354. $build->pathType = empty($build->scmPath) ? 'filePath' : 'scmPath';
  1355. $build->path = empty($build->scmPath) ? $build->filePath : $build->scmPath;
  1356. $builds[] = $build;
  1357. }
  1358. }
  1359. }
  1360. else
  1361. {
  1362. if($build->scmPath && $build->filePath)
  1363. {
  1364. $build->rowspan = 2;
  1365. $build->executionRowspan = 2;
  1366. $buildInfo = clone $build;
  1367. $buildInfo->pathType = 'scmPath';
  1368. $buildInfo->path = $build->scmPath;
  1369. $builds[] = $buildInfo;
  1370. $buildInfo = clone $build;
  1371. $buildInfo->pathType = 'filePath';
  1372. $buildInfo->path = $build->filePath;
  1373. $builds[] = $buildInfo;
  1374. }
  1375. else
  1376. {
  1377. $build->pathType = empty($build->scmPath) ? 'filePath' : 'scmPath';
  1378. $build->path = empty($build->scmPath) ? $build->filePath : $build->scmPath;
  1379. $builds[] = $build;
  1380. }
  1381. }
  1382. if(!$build->system) $build->system = '';
  1383. }
  1384. /* Set data table column. */
  1385. if(!$project->hasProduct) unset($this->config->build->dtable->fieldList['product']);
  1386. if(!$showBranch || !$project->hasProduct) unset($this->config->build->dtable->fieldList['branch']);
  1387. if(!$project->multiple) unset($this->config->build->dtable->fieldList['execution']);
  1388. $this->config->build->dtable->fieldList['name']['link'] = array('module' => 'projectbuild', 'method' => 'view', 'params' => 'buildID={id}');
  1389. $this->config->build->dtable->fieldList['execution']['title'] = zget($this->lang->project->executionList, $project->model);
  1390. return $builds;
  1391. }
  1392. /**
  1393. * 构建项目团队成员信息。
  1394. * Build project team member information.
  1395. *
  1396. * @param array $currentMembers
  1397. * @param array $members2Import
  1398. * @param array $deptUsers
  1399. * @param int $days
  1400. * @access protected
  1401. * @return array
  1402. */
  1403. protected function buildMembers($currentMembers, $members2Import, $deptUsers, $days)
  1404. {
  1405. $teamMembers = array();
  1406. foreach($currentMembers as $account => $member)
  1407. {
  1408. $member->memberType = 'default';
  1409. $teamMembers[$account] = $member;
  1410. }
  1411. $roles = $this->loadModel('user')->getUserRoles(array_keys($deptUsers));
  1412. foreach($deptUsers as $deptAccount => $userName)
  1413. {
  1414. if(isset($currentMembers[$deptAccount]) || isset($members2Import[$deptAccount])) continue;
  1415. $deptMember = new stdclass();
  1416. $deptMember->memberType = 'dept';
  1417. $deptMember->account = $deptAccount;
  1418. $deptMember->role = zget($roles, $deptAccount, '');
  1419. $deptMember->days = $days;
  1420. $deptMember->hours = $this->config->execution->defaultWorkhours;
  1421. $deptMember->limited = 'no';
  1422. $teamMembers[$deptAccount] = $deptMember;
  1423. }
  1424. foreach($members2Import as $account => $member2Import)
  1425. {
  1426. $member2Import->memberType = 'import';
  1427. $member2Import->days = $days;
  1428. $member2Import->limited = 'no';
  1429. $teamMembers[$account] = $member2Import;
  1430. }
  1431. for($j = 0; $j < 5; $j ++)
  1432. {
  1433. $newMember = new stdclass();
  1434. $newMember->memberType = 'add';
  1435. $newMember->account = '';
  1436. $newMember->role = '';
  1437. $newMember->days = $days;
  1438. $newMember->hours = $this->config->execution->defaultWorkhours;
  1439. $newMember->limited = 'no';
  1440. $teamMembers[] = $newMember;
  1441. }
  1442. return $teamMembers;
  1443. }
  1444. /**
  1445. * 构造用户键值对和列表。
  1446. * Build user pairs and user list.
  1447. *
  1448. * @access protected
  1449. * @return array
  1450. */
  1451. protected function buildUsers()
  1452. {
  1453. $userList = array();
  1454. $userPairs = array();
  1455. $users = $this->loadModel('user')->getList('all');
  1456. foreach($users as $user)
  1457. {
  1458. $userList[$user->account] = $user;
  1459. $userPairs[$user->account] = $user->realname;
  1460. }
  1461. return array($userPairs, $userList);
  1462. }
  1463. /**
  1464. * 格式化导出的项目数据。
  1465. * Format the export project data.
  1466. *
  1467. * @param string $status
  1468. * @param string $orderBy
  1469. * @access protected
  1470. * @return array
  1471. */
  1472. protected function formatExportProjects($status, $orderBy)
  1473. {
  1474. $this->loadModel('product');
  1475. $projects = $this->project->getList($status, $orderBy);
  1476. $users = $this->loadModel('user')->getPairs('noletter');
  1477. $canViewProjects = $this->app->user->view->projects;
  1478. $storyGroup = $this->loadModel('story')->fetchStoriesByProjectIdList(array_keys($projects));
  1479. $executionGroup = $this->loadModel('execution')->fetchExecutionsByProjectIdList(array_keys($projects));
  1480. if($this->config->edition != 'open') $workflowGroups = $this->loadModel('workflowGroup')->getPairs('project', 'all');
  1481. foreach($projects as $i => $project)
  1482. {
  1483. if(!$this->app->user->admin && strpos(",{$canViewProjects},", ",{$project->id},") === false)
  1484. {
  1485. unset($projects[$i]);
  1486. continue;
  1487. }
  1488. if($this->post->exportType == 'selected')
  1489. {
  1490. $checkedItem = $this->cookie->checkedItem;
  1491. if(strpos(",$checkedItem,", ",{$project->id},") === false)
  1492. {
  1493. unset($projects[$i]);
  1494. continue;
  1495. }
  1496. }
  1497. $projectBudget = $project->budget === '' ? '0' : $project->budget;
  1498. $project->PM = zget($users, $project->PM);
  1499. $project->status = $this->processStatus('project', $project);
  1500. $project->model = zget($this->lang->project->modelList, $project->model);
  1501. $project->budget = !empty($projectBudget) ? zget($this->lang->project->currencySymbol, $project->budgetUnit) . ' ' . $projectBudget : $this->lang->project->future;
  1502. $project->parent = $project->parentName;
  1503. $project->end = $project->end == LONG_TIME ? $this->lang->project->longTime : $project->end;
  1504. $project->invested = !empty($this->config->execution->defaultWorkhours) ? round($project->consumed / $this->config->execution->defaultWorkhours, 2) : 0;
  1505. $project->invested .= " {$this->lang->project->manDay}";
  1506. $project->progress = floor((float)$project->progress) . '%';
  1507. if($this->config->edition != 'open') $project->workflowGroup = zget($workflowGroups, $project->workflowGroup);
  1508. $linkedProducts = $this->product->getProducts($project->id, 'all', '', false);
  1509. $project->linkedProducts = implode(',', $linkedProducts);
  1510. if(!$project->hasProduct) $project->linkedProducts = '';
  1511. $project->hasProduct = zget($this->lang->project->projectTypeList, $project->hasProduct);
  1512. $projectStories = zget($storyGroup, $project->id, array());
  1513. $project->storyCount = count($projectStories);
  1514. $project->storyPoints = 0;
  1515. foreach($projectStories as $story)
  1516. {
  1517. if($story->isParent) continue;
  1518. $project->storyPoints += $story->estimate;
  1519. }
  1520. $project->storyPoints .= ' ' . $this->config->hourUnit;
  1521. $executions = zget($executionGroup, $project->id, array());
  1522. $project->executionCount = count($executions);
  1523. }
  1524. return $projects;
  1525. }
  1526. /**
  1527. * 遍历执行列表及子阶段,返回ID列表。
  1528. * Expand executions, return id list.
  1529. *
  1530. * @param object $stats
  1531. * @access protected
  1532. * @return array
  1533. */
  1534. protected function expandExecutionIdList($stats)
  1535. {
  1536. $executionIdList = array();
  1537. foreach($stats as $execution)
  1538. {
  1539. $executionIdList[$execution->id] = $execution->id;
  1540. if(empty($execution->children)) continue;
  1541. foreach($execution->children as $child)
  1542. {
  1543. $childrenIdList = $this->expandExecutionIdList(array($child));
  1544. foreach($childrenIdList as $childID) $executionIdList[$childID] = $childID;
  1545. }
  1546. }
  1547. return $executionIdList;
  1548. }
  1549. /**
  1550. * 展示测试单的相关变量。
  1551. * Show the testtask related variables.
  1552. *
  1553. * @param array $tasks
  1554. * @access protected
  1555. * @return void
  1556. */
  1557. protected function assignTesttaskVars($tasks)
  1558. {
  1559. /* Compute rowspan. */
  1560. $productGroup = array();
  1561. $waitCount = 0;
  1562. $testingCount = 0;
  1563. $blockedCount = 0;
  1564. $doneCount = 0;
  1565. foreach($tasks as $task)
  1566. {
  1567. $productGroup[$task->product][] = $task;
  1568. if($task->status == 'wait') $waitCount ++;
  1569. if($task->status == 'doing') $testingCount ++;
  1570. if($task->status == 'blocked') $blockedCount ++;
  1571. if($task->status == 'done') $doneCount ++;
  1572. if($task->build == 'trunk' || empty($task->buildName)) $task->buildName = $this->lang->trunk;
  1573. }
  1574. $lastProduct = '';
  1575. foreach($tasks as $taskID => $task)
  1576. {
  1577. $task->rawStatus = $task->status;
  1578. $task->status = $this->processStatus('testtask', $task);
  1579. $task->rowspan = 0;
  1580. if($lastProduct !== $task->product)
  1581. {
  1582. $lastProduct = $task->product;
  1583. if(!empty($productGroup[$task->product])) $task->rowspan = count($productGroup[$task->product]);
  1584. }
  1585. }
  1586. $this->view->waitCount = $waitCount;
  1587. $this->view->testingCount = $testingCount;
  1588. $this->view->blockedCount = $blockedCount;
  1589. $this->view->doneCount = $doneCount;
  1590. $this->view->tasks = $tasks;
  1591. }
  1592. /**
  1593. * Get kanban data.
  1594. *
  1595. * @access protected
  1596. * @return void
  1597. */
  1598. protected function getKanbanData()
  1599. {
  1600. list($kanbanGroup, $latestExecutions) = $this->project->getStats4Kanban();
  1601. $programPairs = array(0 => helper::hasFeature('program') ? $this->lang->project->noProgram : '') + $this->loadModel('program')->getPairs(true, 'order_asc');
  1602. $kanbanList = array();
  1603. foreach($kanbanGroup as $regionKey => $region)
  1604. {
  1605. if(!$region) continue;
  1606. $lanes = array();
  1607. $items = array();
  1608. $columnCards = array();
  1609. foreach($region as $laneKey => $laneData)
  1610. {
  1611. $lanes[] = array('name' => "lane$laneKey", 'title' => zget($programPairs, $laneKey));
  1612. $columns = array();
  1613. foreach(array('wait', 'doing', 'closed') as $columnKey)
  1614. {
  1615. $columns[] = array('name' => $columnKey, 'title' => $columnKey != 'doing' ? $this->lang->project->{$columnKey . 'Projects'} : $this->lang->project->statusList[$columnKey]);
  1616. if($columnKey == 'doing')
  1617. {
  1618. $columns[] = array('name' => 'doingProjects', 'parentName' => 'doing', 'title' => $this->lang->project->doingProjects, 'order' => 1);
  1619. $columns[] = array('name' => 'doingExecutions', 'parentName' => 'doing', 'title' => $this->lang->project->doingExecutions, 'order' => 2);
  1620. }
  1621. $cardList = !empty($laneData[$columnKey]) ? $laneData[$columnKey] : array();
  1622. foreach($cardList as $card)
  1623. {
  1624. $cardColumnKey = $columnKey == 'doing' ? 'doingProjects' : $columnKey;
  1625. $items["lane$laneKey"][$cardColumnKey][] = array('id' => $card->id, 'name' => $card->id, 'title' => $card->name, 'status' => $card->status, 'cardType' => 'project', 'delay' => !empty($card->delay) ? $card->delay : 0, 'progress' => $card->progress);
  1626. if(!isset($columnCards[$cardColumnKey])) $columnCards[$cardColumnKey] = 0;
  1627. $columnCards[$cardColumnKey] ++;
  1628. if($cardColumnKey == 'doingProjects')
  1629. {
  1630. if(!empty($latestExecutions[$card->id]))
  1631. {
  1632. $cardColumnKey = 'doingExecutions';
  1633. $execution = $latestExecutions[$card->id];
  1634. $items["lane$laneKey"][$cardColumnKey][] = array('id' => $execution->id, 'name' => $execution->id, 'title' => $execution->name, 'status' => $execution->status, 'cardType' => 'execution', 'delay' => !empty($execution->delay) ? $execution->delay : 0, 'progress' => $execution->progress);
  1635. if(!isset($columnCards[$cardColumnKey])) $columnCards[$cardColumnKey] = 0;
  1636. $columnCards[$cardColumnKey] ++;
  1637. }
  1638. }
  1639. }
  1640. }
  1641. }
  1642. foreach($columns as $key => $column) $columns[$key]['cards'] = !empty($columnCards[$column['name']]) ? $columnCards[$column['name']] : 0;
  1643. $groupData['key'] = $regionKey;
  1644. $groupData['data']['lanes'] = $lanes;
  1645. $groupData['data']['cols'] = $columns;
  1646. $groupData['data']['items'] = $items;
  1647. $kanbanList[] = array('items' => array($groupData), 'key' => $regionKey, 'heading' => array('title' => $this->lang->project->typeList[$regionKey]), 'kanbanProps' => array('laneNameWidth' => helper::hasFeature('program') ? null : '0'));
  1648. }
  1649. return $kanbanList;
  1650. }
  1651. /**
  1652. * 瀑布项目专属规则:
  1653. * 1.按产品创建阶段的:
  1654. * 项目中已经关联需求的产品及分支不可以解除关联。
  1655. * 项目中已经关联迭代的产品不能解除关联。
  1656. * 2.按项目创建阶段的:
  1657. * 项目中已经关联需求的产品及分支不可以解除关联。
  1658. * 项目中已经关联迭代的产品解除后,迭代中的关联产品同步解除关联。
  1659. * 3.项目至少要关联一个产品。
  1660. *
  1661. * Get unmodifiable products.
  1662. *
  1663. * @param int $projectID
  1664. * @param object $project
  1665. * @access protected
  1666. * @return array
  1667. */
  1668. protected function getUnmodifiableProducts($projectID, $project)
  1669. {
  1670. $linkedProducts = $this->loadModel('product')->getProducts($projectID, 'all', '', true);
  1671. $unmodifiableProducts = array();
  1672. if(in_array($project->model, array('waterfall', 'waterfallplus')))
  1673. {
  1674. $projectStories = $this->project->getStoriesByProject($projectID);
  1675. $projectExecutions = $this->loadModel('execution')->getPairs($projectID);
  1676. $executionProducts = $this->dao->select('project,product')->from(TABLE_PROJECTPRODUCT)->where('project')->in(array_keys($projectExecutions))->fetchGroup('product', 'project');
  1677. foreach($linkedProducts as $productID => $linkedProduct)
  1678. {
  1679. if(isset($projectStories[$productID])) $unmodifiableProducts[$productID] = $productID;
  1680. if(isset($executionProducts[$productID]) && $project->stageBy == 'product') $unmodifiableProducts[$productID] = $productID;
  1681. }
  1682. }
  1683. return $unmodifiableProducts;
  1684. }
  1685. /**
  1686. * 获取执行列表:
  1687. * Get execution stats.
  1688. *
  1689. * @param string $status
  1690. * @param int $projectID
  1691. * @param array $executionIdList
  1692. * @param int $productID
  1693. * @param int $queryID
  1694. * @param string $sort
  1695. * @param object $pager
  1696. * @access protected
  1697. * @return array
  1698. */
  1699. protected function getExecutionStats($status, $projectID, $executionIdList, $productID, $queryID, $sort, $pager)
  1700. {
  1701. if($this->cookie->showTask && strtolower($status) == 'bysearch')
  1702. {
  1703. $tasks = $this->loadModel('programplan')->getGanttTasks($projectID, $executionIdList, strtolower($status), $queryID, $pager);
  1704. $executionTasks = array();
  1705. $executionIdList = array();
  1706. foreach($tasks as $task)
  1707. {
  1708. $executionIdList[$task->execution] = $task->execution;
  1709. if(!isset($executionTasks[$task->execution])) $executionTasks[$task->execution] = array();
  1710. $executionTasks[$task->execution][$task->id] = $task;
  1711. }
  1712. $executions = $this->loadModel('execution')->getByIdList($executionIdList, '', $sort);
  1713. $executions = $this->execution->batchProcessExecution($executions, $projectID, $productID, true, '', $executionTasks);
  1714. $executionStats = array_values($executions);
  1715. }
  1716. else
  1717. {
  1718. $executionStats = $this->loadModel('execution')->getStatData($projectID, $status, $productID, 0, (bool)$this->cookie->showTask, $queryID, $sort, $pager);
  1719. }
  1720. return $executionStats;
  1721. }
  1722. }