zen.php 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628
  1. <?php
  2. /**
  3. * The zen file of product 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 chen.tao<chentao@easycorp.ltd>
  8. * @package product
  9. * @link https://www.zentao.net
  10. */
  11. class productZen extends product
  12. {
  13. /**
  14. * 为控制层的all函数设置共享环境数据。
  15. * Set shared environment data for all function of control layer.
  16. *
  17. * @access protected
  18. * @return void
  19. */
  20. protected function setMenu4All()
  21. {
  22. /* Set redirect URI. */
  23. $this->session->set('productList', $this->app->getURI(true), 'product');
  24. /* Set activated menu for mobile view. */
  25. if($this->app->viewType == 'mhtml')
  26. {
  27. $productID = $this->product->checkAccess(0, $this->products);
  28. $this->product->setMenu($productID);
  29. }
  30. }
  31. /**
  32. * 设置项目页面导航菜单。
  33. * Set navigation menus for project page.
  34. *
  35. * @param int $productID
  36. * @param string $branch
  37. * @param string $preBranch
  38. * @access protected
  39. * @return void
  40. */
  41. protected function setProjectMenu($productID, $branch, $preBranch)
  42. {
  43. $branch = ($preBranch !== '' and $branch === '') ? $preBranch : $branch;
  44. helper::setcookie('preBranch', $branch);
  45. $this->session->set('createProjectLocate', $this->app->getURI(true), 'product');
  46. $this->product->setMenu($productID, $branch);
  47. }
  48. /**
  49. * 为创建产品设置导航数据,主要是替换占位符。
  50. * Set menu for create product page.
  51. *
  52. * @param int $programID
  53. * @access protected
  54. * @return void
  55. */
  56. protected function setCreateMenu($programID)
  57. {
  58. if($this->app->tab == 'program') common::setMenuVars('program', $programID);
  59. if($this->app->tab == 'doc') unset($this->lang->doc->menu->product['subMenu']);
  60. if($this->app->getViewType() != 'mhtml') return;
  61. if($this->app->rawModule == 'projectstory' and $this->app->rawMethod == 'story') return $this->loadModel('project')->setMenu();
  62. $this->product->setMenu();
  63. }
  64. /**
  65. * 为编辑产品设置导航数据,主要是替换占位符。
  66. * Set menu for edit product page.
  67. *
  68. * @param int $productID
  69. * @param int $programID
  70. * @access protected
  71. * @return void
  72. */
  73. protected function setEditMenu($productID, $programID)
  74. {
  75. if($programID) return common::setMenuVars('program', $programID);
  76. $this->product->setMenu($productID);
  77. }
  78. /**
  79. * 设置跟踪矩阵导航
  80. * Set menu for track
  81. *
  82. * @param int $productID
  83. * @param string $branch
  84. * @param int $projectID
  85. * @access protected
  86. * @return void
  87. */
  88. protected function setTrackMenu($productID, $branch, $projectID)
  89. {
  90. helper::setcookie('preBranch', $branch);
  91. /* Save session. */
  92. $uri = $this->app->getURI(true);
  93. $this->session->set('storyList', $uri, 'product');
  94. $this->session->set('taskList', $uri, 'execution');
  95. $this->session->set('designList', $uri, 'project');
  96. $this->session->set('bugList', $uri, 'qa');
  97. $this->session->set('caseList', $uri, 'qa');
  98. $this->session->set('revisionList', $uri, 'repo');
  99. if($projectID) return $this->loadModel('project')->setMenu($projectID);
  100. $productID = $this->product->checkAccess($productID, $this->products);
  101. $this->product->setMenu($productID, $branch);
  102. }
  103. /**
  104. * 为showErrorNone方法,根据不同模块,设置不同的二级或三级导航配置。
  105. * Set menu for showErrorNone page.
  106. *
  107. * @param string $moduleName project|qa|execution
  108. * @param string $activeMenu
  109. * @param int $objectID
  110. * @access protected
  111. * @return void
  112. */
  113. protected function setShowErrorNoneMenu($moduleName, $activeMenu, $objectID)
  114. {
  115. if($this->app->getViewType() == 'mhtml') return $this->product->setMenu();
  116. if($moduleName == 'qa') $this->setShowErrorNoneMenu4QA($activeMenu);
  117. if($moduleName == 'project') $this->setShowErrorNoneMenu4Project($activeMenu, $objectID);
  118. if($moduleName == 'execution') $this->setShowErrorNoneMenu4Execution($activeMenu, $objectID);
  119. }
  120. /**
  121. * 为showErrorNone方法,设置在测试视图中的二级导航配置。
  122. * Set menu for showErrorNone page in qa.
  123. *
  124. * @param string $activeMenu
  125. * @access private
  126. * @return void
  127. */
  128. private function setShowErrorNoneMenu4QA($activeMenu)
  129. {
  130. $this->loadModel('qa')->setMenu();
  131. $this->view->moduleName = 'qa';
  132. $this->app->rawModule = $activeMenu;
  133. if($activeMenu == 'testcase') unset($this->lang->qa->menu->testcase['subMenu']);
  134. if($activeMenu == 'testsuite') unset($this->lang->qa->menu->testcase['subMenu']);
  135. if($activeMenu == 'testtask') unset($this->lang->qa->menu->testtask['subMenu']);
  136. if($activeMenu == 'testreport') unset($this->lang->qa->menu->testtask['subMenu']);
  137. }
  138. /**
  139. * 为showErrorNone方法,设置在项目视图中的二级或三级导航配置。
  140. * Set menu for showErrorNone page in project.
  141. *
  142. * @param string $activeMenu
  143. * @param int $projectID
  144. * @access private
  145. * @return void
  146. */
  147. private function setShowErrorNoneMenu4Project($activeMenu, $projectID)
  148. {
  149. $this->loadModel('project')->setMenu($projectID);
  150. $this->app->rawModule = $activeMenu;
  151. $project = $this->project->getByID($projectID);
  152. $model = zget($project, 'model', 'scrum');
  153. $this->lang->project->menu = $this->lang->{$model}->menu;
  154. $this->lang->project->menuOrder = $this->lang->{$model}->menuOrder;
  155. if($activeMenu == 'bug') $this->lang->{$model}->menu->qa['subMenu']->bug['subModule'] = 'product';
  156. if($activeMenu == 'testcase') $this->lang->{$model}->menu->qa['subMenu']->testcase['subModule'] = 'product';
  157. if($activeMenu == 'testtask') $this->lang->{$model}->menu->qa['subMenu']->testtask['subModule'] = 'product';
  158. if($activeMenu == 'testreport') $this->lang->{$model}->menu->qa['subMenu']->testreport['subModule'] = 'product';
  159. if($activeMenu == 'projectrelease') $this->lang->{$model}->menu->release['subModule'] = 'projectrelease';
  160. }
  161. /**
  162. * 为showErrorNone方法,设置在执行视图中的三级导航配置。
  163. * Set menu for showErrorNone page in execution.
  164. *
  165. * @param string $activeMenu
  166. * @param int $executionID
  167. * @access private
  168. * @return void
  169. */
  170. private function setShowErrorNoneMenu4Execution($activeMenu, $executionID)
  171. {
  172. $this->loadModel('execution')->setMenu($executionID);
  173. $this->app->rawModule = $activeMenu;
  174. if($activeMenu == 'bug') $this->lang->execution->menu->qa['subMenu']->bug['subModule'] = 'product';
  175. if($activeMenu == 'testcase') $this->lang->execution->menu->qa['subMenu']->testcase['subModule'] = 'product';
  176. if($activeMenu == 'testtask') $this->lang->execution->menu->qa['subMenu']->testtask['subModule'] = 'product';
  177. if($activeMenu == 'testreport') $this->lang->execution->menu->qa['subMenu']->testreport['subModule'] = 'product';
  178. }
  179. /**
  180. * 通过extra获取返回链接。
  181. * Get goback link for create by extra.
  182. *
  183. * @param string $extra
  184. * @access private
  185. * @return string
  186. */
  187. protected function getBackLink4Create($extra)
  188. {
  189. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  190. parse_str($extra, $output);
  191. $backLink = '';
  192. $from = zget($output, 'from', '');
  193. if($from == 'qa') $backLink = $this->createLink('qa', 'index');
  194. if($from == 'global') $backLink = $this->createLink('product', 'all');
  195. return $backLink;
  196. }
  197. /**
  198. * 设置表单字段下拉选项。
  199. * Set form select options.
  200. *
  201. * @param int $programID
  202. * @param array $fields
  203. * @access private
  204. * @return array
  205. */
  206. private function setSelectFormOptions($programID, $fields)
  207. {
  208. $users = $this->loadModel('user')->getPairs('nodeleted|noclosed');
  209. /* 追加字段的name、title属性,展开user数据。 */
  210. foreach($fields as $field => $attr)
  211. {
  212. if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users;
  213. if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field;
  214. if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->product, $field);
  215. }
  216. /* 设置下拉菜单内容。 */
  217. if(isset($fields['groups'])) $fields['groups']['options'] = $this->loadModel('group')->getPairs();
  218. if(isset($fields['program'])) $fields['program']['options'] = $this->loadModel('program')->getTopPairs('noclosed');
  219. if(isset($fields['line'])) $fields['line']['options'] = $this->product->getLinePairs($programID, true);
  220. if($this->config->edition != 'open' && isset($fields['workflowGroup']))
  221. {
  222. $groupPairs = $this->loadModel('workflowGroup')->getPairs('product', 'scrum', 1, 'normal', '0');
  223. $fields['workflowGroup']['options'] = $this->workflowGroup->appendBuildinLabel($groupPairs);
  224. }
  225. return $fields;
  226. }
  227. /**
  228. * 获取创建产品页面的表单配置。
  229. * Get form fields for create.
  230. *
  231. * @param int $programID
  232. * @param string $extra
  233. * @access protected
  234. * @return array
  235. */
  236. protected function getFormFields4Create($programID = 0, $extra = '')
  237. {
  238. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  239. parse_str($extra, $output);
  240. $fields = $this->setSelectFormOptions($programID, $this->config->product->form->create);
  241. $fields['program']['default'] = $programID ? $programID : '';
  242. $fields['PO']['default'] = $this->app->user->account;
  243. /* Set required. */
  244. foreach($fields as $field => $attr)
  245. {
  246. if(!empty($output[$field])) $fields[$field]['default'] = $output[$field];
  247. if(strpos(",{$this->config->product->create->requiredFields},", ",$field,") !== false) $fields[$field]['required'] = true;
  248. }
  249. return $fields;
  250. }
  251. /**
  252. * 获取编辑产品页面的表单配置。
  253. * Get form fields for create.
  254. *
  255. * @param object $product
  256. * @access private
  257. * @return array
  258. */
  259. protected function getFormFields4Edit($product)
  260. {
  261. /* Init fields. */
  262. $programID = (int)$product->program;
  263. $fields = $this->setSelectFormOptions($programID, $this->config->product->form->edit);
  264. $fields['changeProjects'] = array('type' => 'string', 'control' => 'hidden', 'required' => false, 'default' => '');
  265. /* Check program priv, and append to program list that is not exist product's program. */
  266. $hasPrivPrograms = $this->app->user->view->programs;
  267. if($programID and strpos(",{$hasPrivPrograms},", ",{$programID},") === false) $fields['program']['control'] = 'hidden';
  268. if(isset($fields['program']) and !isset($fields['program']['options'][$programID]) and $programID)
  269. {
  270. $program = $this->loadModel('program')->getByID($programID);
  271. $fields['program']['options'] += array($programID => $program->name);
  272. }
  273. /* Set default value by product. */
  274. foreach($fields as $field => $attr)
  275. {
  276. if(isset($product->{$field})) $fields[$field]['default'] = $product->{$field};
  277. if(strpos(",{$this->config->product->edit->requiredFields},", ",$field,") !== false) $fields[$field]['required'] = true;
  278. }
  279. return $fields;
  280. }
  281. /**
  282. * 获取关闭产品页面的表单配置。
  283. * Get form fields for close product page.
  284. *
  285. * @access private
  286. * @return array
  287. */
  288. protected function getFormFields4Close()
  289. {
  290. /* Init fields. */
  291. $fields = $this->config->product->form->close;
  292. $fields['comment'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => '', 'width' => 'full');
  293. return $fields;
  294. }
  295. /**
  296. * 获取激活产品页面的表单配置。
  297. * Get form fields for activate product page.
  298. *
  299. * @access private
  300. * @return array
  301. */
  302. protected function getFormFields4Activate()
  303. {
  304. /* Init fields. */
  305. $fields = $this->config->product->form->activate;
  306. $fields['comment'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => '', 'width' => 'full');
  307. return $fields;
  308. }
  309. /**
  310. * Get product lines and product lines of program.
  311. *
  312. * @param array $programIdList
  313. * @access protected
  314. * @return array
  315. */
  316. protected function getProductLines($programIdList = array())
  317. {
  318. /* Get all product lines. */
  319. $productLines = $this->product->getLines($programIdList);
  320. /* Collect product lines of program lines. */
  321. $linePairs = array();
  322. foreach($programIdList as $programID) $linePairs[$programID] = array();
  323. foreach($productLines as $line) $linePairs[$line->root][$line->id] = $line->name;
  324. return array($productLines, $linePairs);
  325. }
  326. /**
  327. * 获取产品导出字段。
  328. * Get export fields.
  329. *
  330. * @access protected
  331. * @return array
  332. */
  333. protected function getExportFields()
  334. {
  335. $fieldList = $this->loadModel('datatable') ->getSetting('product', 'all');
  336. $extendFieldList = $this->product->getFlowExtendFields();
  337. foreach($extendFieldList as $field => $name)
  338. {
  339. if(strpos(strtolower($fieldName), 'epic') !== false && !$this->config->enableER) continue;
  340. if(strpos(strtolower($fieldName), 'requirement') !== false && !$this->config->URAndSR) continue;
  341. $fieldName = trim($fieldName);
  342. $fieldPairs[$fieldName] = zget($productLang, $fieldName);
  343. $extCol = $config->product->dtable->extendField;
  344. $extCol['name'] = $field;
  345. $extCol['title'] = $name;
  346. $fieldList[$field] = $extCol;
  347. }
  348. $fieldPairs = array();
  349. foreach($fieldList as $fieldKey => $field)
  350. {
  351. if(isset($field['headerGroup'])) $field['title'] = $field['headerGroup'] . ' - ' . $field['title'];
  352. $fieldPairs[$fieldKey] = $field['title'];
  353. }
  354. if($this->config->systemMode == 'light') unset($fieldPairs['productLine'], $fieldPairs['program']);
  355. return $fieldPairs;
  356. }
  357. /**
  358. * 获取导出产品数据。
  359. * Get export product data.
  360. *
  361. * @param int $programID
  362. * @param string $browseType
  363. * @param string $orderBy
  364. * @param int $param
  365. * @param object $pager
  366. * @access protected
  367. * @return array
  368. */
  369. protected function getExportData($programID, $browseType, $orderBy, $param = 0, $pager = null)
  370. {
  371. $users = $this->user->getPairs('noletter');
  372. $products = strtolower($browseType) == 'bysearch' ? $this->product->getListBySearch((int)$param) : $this->product->getList($programID, $browseType);
  373. $productStats = $this->product->getStats(array_keys($products), $orderBy, $pager, 'story', $programID);
  374. $data = array();
  375. foreach($productStats as $product) $data[] = $this->product->formatDataForList($product, $users);
  376. return $data;
  377. }
  378. /**
  379. * 根据产品和已授权项目集,获取关联产品的未授权项目集。
  380. * Get unauthorized programs by products and authorized programs.
  381. *
  382. * @param array $products
  383. * @param array $authPrograms
  384. * @access private
  385. * @return array
  386. */
  387. private function getUnauthProgramsOfProducts($products, $authPrograms)
  388. {
  389. $unauthPrograms = array();
  390. $programIdList = array();
  391. foreach($products as $product)
  392. {
  393. if($product->program and !isset($authPrograms[$product->program])) $programIdList[$product->program] = $product->program;
  394. }
  395. if($programIdList) $unauthPrograms = $this->program->getPairsByList($programIdList);
  396. return $unauthPrograms;
  397. }
  398. /**
  399. * 获取在ajaxGetDropMenu方法中使用的产品。
  400. * Get products for ajaxGetDropMenu method.
  401. *
  402. * @param string $shadow 0|all
  403. * @param string $module
  404. * @access protected
  405. * @return array
  406. */
  407. protected function getProducts4DropMenu($shadow = '0', $module = '')
  408. {
  409. if($this->config->edition != 'open')
  410. {
  411. $flow = $this->loadModel('workflow')->getByModule($module);
  412. if($flow && $flow->belong == 'product') return $this->product->getList(0, 'all', 0, 0, $shadow);
  413. }
  414. if($this->app->tab == 'project') return $this->product->getProducts($this->session->project);
  415. if($this->app->tab == 'feedback') return $this->loadModel('feedback')->getGrantProducts(false, false, 'all');
  416. return $this->product->getList(0, 'all', 0, 0, $shadow);
  417. }
  418. /**
  419. * 创建完成后,做页面跳转。
  420. * Locate after create product.
  421. *
  422. * @param int $productID
  423. * @param int $programID
  424. * @access private
  425. * @return array
  426. */
  427. private function getCreatedLocate($productID, $programID)
  428. {
  429. $tab = $this->app->tab;
  430. $moduleName = $tab == 'program' ? 'program' : $this->moduleName;
  431. $methodName = $tab == 'program' ? 'product' : 'browse';
  432. $param = $tab == 'program' ? "programID=$programID" : "productID=$productID";
  433. $location = isInModal() ? true : $this->createLink($moduleName, $methodName, $param);
  434. if($tab == 'doc') $location = $this->createLink('doc', 'productSpace', "objectID=$productID");
  435. return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $location, 'closeModal' => true);
  436. }
  437. /**
  438. * 编辑完成后,做页面跳转。
  439. * Locate after edit product.
  440. *
  441. * @param int $productID
  442. * @param int $programID
  443. * @access private
  444. * @return array
  445. */
  446. private function getEditedLocate($productID, $programID)
  447. {
  448. $moduleName = $programID ? 'program' : 'product';
  449. $methodName = $programID ? 'product' : 'view';
  450. $param = $programID ? "programID=$programID" : "product=$productID";
  451. $location = $this->createLink($moduleName, $methodName, $param);
  452. if(!$programID) $this->session->set('productList', $this->createLink('product', 'browse', $param), 'product');
  453. return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $location);
  454. }
  455. /**
  456. * 构建批量编辑产品页面数据。
  457. * Build form for batch edit page
  458. *
  459. * @param int $programID
  460. * @param array $productIdList
  461. * @access protected
  462. * @return void
  463. */
  464. protected function buildBatchEditForm($programID, $productIdList)
  465. {
  466. $products = $this->product->getByIdList($productIdList);
  467. /* 获取项目集和产品线,并将项目集分成授权和非授权。以便view的下拉菜单使用对应的数据。 */
  468. $authPrograms = array();
  469. $unauthPrograms = array();
  470. $lines = array();
  471. if(in_array($this->config->systemMode, array('ALM', 'PLM')))
  472. {
  473. $authPrograms = $this->loadModel('program')->getTopPairs();
  474. $unauthPrograms = $this->getUnauthProgramsOfProducts($products, $authPrograms);
  475. /* Get product lines by programs.*/
  476. $programIdList = array_merge(array(0 => ''), array_keys($authPrograms), array_keys($unauthPrograms));
  477. list(, $lines) = $this->getProductLines($programIdList);
  478. }
  479. /* 给view层赋值。 */
  480. $this->view->title = $this->lang->product->batchEdit;
  481. $this->view->lines = $lines;
  482. $this->view->products = $products;
  483. $this->view->fields = $this->setSelectFormOptions(0, $this->config->product->form->batchEdit);
  484. $this->view->programID = $programID;
  485. $this->view->authPrograms = $authPrograms;
  486. $this->view->unauthPrograms = $unauthPrograms;
  487. unset($this->lang->product->typeList['']);
  488. $this->display();
  489. }
  490. /**
  491. * 构建创建产品的数据。
  492. * Build product data for create.
  493. *
  494. * @param int $workflowGroup
  495. * @access protected
  496. * @return object
  497. */
  498. protected function buildProductForCreate($workflowGroup = 0)
  499. {
  500. $productData = form::data($this->config->product->form->create, 0, $workflowGroup)
  501. ->setIF($this->config->systemMode == 'light', 'program', (int)zget($this->config->global, 'defaultProgram', 0))
  502. ->setIF($this->post->acl == 'open', 'whitelist', '')
  503. ->setDefault('vision', $this->config->vision)
  504. ->get();
  505. return $this->loadModel('file')->processImgURL($productData, $this->config->product->editor->create['id'], $this->post->uid);
  506. }
  507. /**
  508. * 构建编辑产品的数据。
  509. * Build product data for edit.
  510. *
  511. * @param int $productID
  512. * @param int $workflowGroup
  513. * @access protected
  514. * @return object
  515. */
  516. protected function buildProductForEdit($productID, $workflowGroup = 0)
  517. {
  518. $productData = form::data($this->config->product->form->edit, $productID, $workflowGroup)
  519. ->setIF($this->post->acl == 'open', 'whitelist', '')
  520. ->get();
  521. return $this->loadModel('file')->processImgURL($productData, $this->config->product->editor->edit['id'], $this->post->uid);
  522. }
  523. /**
  524. * 构建激活产品数据。
  525. * Build product data for activate.
  526. *
  527. * @param int $productID
  528. * @access protected
  529. * @return object
  530. */
  531. protected function buildProductForActivate($productID)
  532. {
  533. $productData = form::data($this->config->product->form->activate, $productID)
  534. ->setIF($this->config->vision == 'or', 'status', 'normal')
  535. ->get();
  536. return $this->loadModel('file')->processImgURL($productData, $this->config->product->editor->activate['id'], $this->post->uid);
  537. }
  538. /**
  539. * 构建关闭产品数据。
  540. * Build product data for close.
  541. *
  542. * @param int $productID
  543. * @access protected
  544. * @return object
  545. */
  546. protected function buildProductForClose($productID)
  547. {
  548. $productData = form::data($this->config->product->form->close, $productID)->get();
  549. return $this->loadModel('file')->processImgURL($productData, $this->config->product->editor->close['id'], $this->post->uid);
  550. }
  551. /**
  552. * 预处理产品线数据。
  553. * Prepare manage line extras.
  554. *
  555. * @param form $form
  556. * @access protected
  557. * @return array|false
  558. */
  559. protected function prepareManageLineExtras($form)
  560. {
  561. $data = $form->get();
  562. /* 拼装产品线列表。列表项目集编号为键,该项目集下的产品线名称列表为值。 */
  563. /* Build product line list. The program id as key, line name list as value. */
  564. $lines = array();
  565. foreach($data->modules as $id => $name)
  566. {
  567. if(empty($name)) continue;
  568. if(!helper::hasFeature('program'))
  569. {
  570. $lines[0][$id] = $name;
  571. continue;
  572. }
  573. $programID = $data->programs[$id];
  574. if(!isset($lines[$programID])) $lines[$programID] = array();
  575. if(in_array($name, $lines[$programID]))
  576. {
  577. dao::$errors["modules[$id]"] = sprintf($this->lang->product->nameIsDuplicate, $name);
  578. return false;
  579. }
  580. $lines[$programID][$id] = $name;
  581. }
  582. return $lines;
  583. }
  584. /**
  585. * 成功插入产品数据后,其他的额外操作。
  586. * Process after create product.
  587. *
  588. * @param int $productID
  589. * @param int $programID
  590. * @access protected
  591. * @return array
  592. */
  593. protected function responseAfterCreate($productID, $programID)
  594. {
  595. $message = $this->executeHooks($productID);
  596. if($message) $this->lang->saveSuccess = $message;
  597. /* 移动到control。*/
  598. if($this->viewType == 'json') return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $productID);
  599. return $this->getCreatedLocate($productID, $programID);
  600. }
  601. /**
  602. * 成功更新产品数据后,其他的额外操作。
  603. * Process after edit product.
  604. *
  605. * @param int $productID
  606. * @param int $programID
  607. * @access protected
  608. * @return array
  609. */
  610. protected function responseAfterEdit($productID, $programID)
  611. {
  612. $message = $this->executeHooks($productID);
  613. if($message) $this->lang->saveSuccess = $message;
  614. return $this->getEditedLocate($productID, $programID);
  615. }
  616. /**
  617. * 成功批量更新产品数据后,其他的额外操作。
  618. * Response after batch edit products.
  619. *
  620. * @param int $programID
  621. * @access protected
  622. * @return array
  623. */
  624. protected function responseAfterBatchEdit($programID)
  625. {
  626. /* Get location. */
  627. $location = $this->createLink('program', 'product', "programID=$programID");
  628. if(empty($programID)) $location = $this->createLink('program', 'productView');
  629. if($this->app->tab == 'product') $location = $this->createLink('product', 'all');
  630. return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $location);
  631. }
  632. /**
  633. * 获取研发需求列表页面关联的产品信息。
  634. * Get the product of the browse page.
  635. *
  636. * @param int $productID
  637. * @access protected
  638. * @return object|null
  639. */
  640. protected function getBrowseProduct($productID)
  641. {
  642. $product = $this->product->getById($productID);
  643. if(empty($product)) return null;
  644. /* If product does not exist in $this->products list, then attach it to the list. */
  645. if(!isset($this->products[$product->id])) $this->products[$product->id] = $product->name;
  646. return $product;
  647. }
  648. /**
  649. * 获取产品分支ID。
  650. * Get branchID.
  651. *
  652. * @param object|null $product
  653. * @param string $branch
  654. * @access protected
  655. * @return string
  656. */
  657. protected function getBranchID($product, $branch)
  658. {
  659. if(empty($product) || $product->type == 'normal') return 'all';
  660. /* 获取分支和分支ID。*/
  661. $branchPairs = $this->loadModel('branch')->getPairs($product->id, 'all');
  662. return ($this->cookie->preBranch !== '' and $branch === '' and isset($branchPairs[$this->cookie->preBranch])) ? (string)$this->cookie->preBranch : $branch;
  663. }
  664. /**
  665. * 设置导航菜单。
  666. * Set navigation menu.
  667. *
  668. * @param int $projectID
  669. * @param int $productID
  670. * @param string $branch
  671. * @param string $storyType
  672. * @access protected
  673. * @return void
  674. */
  675. protected function setMenu4Browse($projectID, $productID, $branch, $storyType)
  676. {
  677. if($this->app->tab == 'project')
  678. {
  679. $this->loadModel('project')->setMenu($projectID);
  680. return;
  681. }
  682. $this->product->setMenu($productID, $branch, $storyType);
  683. }
  684. /**
  685. * browse方法保存并修改cookie数据。
  686. * Save and modify cookies for browse function.
  687. *
  688. * @param int $productID
  689. * @param string $branch
  690. * @param int $param
  691. * @param string $storyType
  692. * @param string $orderBy
  693. * @access protected
  694. * @return void
  695. * @param string $browseType
  696. */
  697. protected function saveAndModifyCookie4Browse($productID, $branch, $param, $browseType, $orderBy)
  698. {
  699. /* Set module ID of story. */
  700. if($this->cookie->preProductID != $productID or $this->cookie->preBranch != $branch or $browseType == 'bybranch')
  701. {
  702. $_COOKIE['storyModule'] = 0;
  703. helper::setcookie('storyModule', '0', 0);
  704. }
  705. if($browseType == 'bymodule' or $browseType == '')
  706. {
  707. helper::setcookie('storyModule', (string)$param, 0);
  708. /* The module ID from project app. */
  709. if($this->app->tab == 'project') helper::setcookie('storyModuleParam', (string)$param, 0);
  710. /* Re-init the story branch. */
  711. $_COOKIE['storyBranch'] = 'all';
  712. helper::setcookie('storyBranch', 'all', 0);
  713. /* For rendering module tree. */
  714. if($browseType == '') helper::setcookie('treeBranch', $branch, 0);
  715. }
  716. if($browseType == 'bybranch') helper::setcookie('storyBranch', $branch, 0);
  717. /* Save sort order of product stories list. */
  718. helper::setcookie('productStoryOrder', $orderBy, 0);
  719. /* Set product ID and branch of the pre visited product. */
  720. helper::setcookie('preProductID', (string)$productID);
  721. helper::setcookie('preBranch', $branch, 0);
  722. }
  723. /**
  724. * 获取模块ID。
  725. * Get module ID.
  726. *
  727. * @param int $param
  728. * @param string $browseType
  729. * @access protected
  730. * @return int
  731. */
  732. protected function getModuleId($param, $browseType)
  733. {
  734. if($browseType == 'bymodule') return $param;
  735. $cookieModule = $this->app->tab == 'project' ? $this->cookie->storyModuleParam : $this->cookie->storyModule;
  736. if($browseType != 'bysearch' && $browseType != 'bybranch' && $cookieModule) return (int)$cookieModule;
  737. return 0;
  738. }
  739. /**
  740. * 获取模块的树形数据结构。
  741. * Get the tree structure of modules.
  742. *
  743. * @param int $projectID
  744. * @param int $productID
  745. * @param string $branch
  746. * @param int $param
  747. * @param string $storyType
  748. * @param string $browseType
  749. * @access protected
  750. * @return array|string
  751. */
  752. protected function getModuleTree($projectID, $productID, &$branch, $param, $storyType, $browseType)
  753. {
  754. if(common::isTutorialMode()) return array();
  755. /* Set moduleTree. */
  756. $createModuleLink = 'createStoryLink';
  757. if($storyType == 'requirement') $createModuleLink = 'createRequirementLink';
  758. if($storyType == 'epic') $createModuleLink = 'createEpicLink';
  759. if($browseType == '')
  760. {
  761. $browseType = 'unclosed';
  762. $branch = is_bool($this->cookie->treeBranch) && empty($this->cookie->treeBranch) ? 'all' : $this->cookie->treeBranch;
  763. }
  764. /* If invoked by projectstory module and not choose product, then get the modules of project story. */
  765. if(!isset($this->tree)) $this->loadModel('tree');
  766. $project = $projectID ? $this->loadModel('project')->getByID($projectID) : null;
  767. if(!empty($projectID) && !empty($project->hasProduct) && $this->app->rawModule == 'projectstory')
  768. {
  769. return $this->tree->getProjectStoryTreeMenu($projectID, 0, array('treeModel', $createModuleLink), array('storyType' => $storyType));
  770. }
  771. /* Pre generate parameters. */
  772. $userFunc = array('treeModel', $createModuleLink);
  773. $extra = array('projectID' => $projectID, 'productID' => $productID);
  774. return $this->tree->getTreeMenu($productID, 'story', 0, $userFunc, $extra, $branch, "&param=$param&storyType=$storyType");
  775. }
  776. /**
  777. * 获取项目下的产品列表。
  778. * Get products of the project.
  779. *
  780. * @param int $projectID
  781. * @param bool $isProjectStory
  782. * @access protected
  783. * @return array
  784. */
  785. protected function getProjectProductList($projectID, $isProjectStory)
  786. {
  787. if($isProjectStory) return $this->product->getProducts($projectID);
  788. return array();
  789. }
  790. /**
  791. * 获取产品计划。
  792. * Get product plans of the project.
  793. *
  794. * @param array $projectProducts
  795. * @param int $projectID
  796. * @param string $storyType
  797. * @param bool $isProjectStory
  798. * @access protected
  799. * @return array
  800. */
  801. protected function getProductPlans($projectProducts, $projectID, $storyType, $isProjectStory)
  802. {
  803. if($isProjectStory && $storyType == 'story') return $this->loadModel('execution')->getPlans(array_keys($projectProducts), 'skipParent,unexpired,noclosed', $projectID);
  804. return array();
  805. }
  806. /**
  807. * 获取需求列表及分页对象。
  808. * Get stories.
  809. *
  810. * @param int $projectID
  811. * @param int $productID
  812. * @param string $branchID
  813. * @param int $moduleID
  814. * @param int $param
  815. * @param string $storyType requirement|story
  816. * @param string $browseType
  817. * @param string $orderBy
  818. * @param object $pager
  819. * @access protected
  820. * @return array
  821. */
  822. public function getStories($projectID, $productID, $branchID = '', $moduleID = 0, $param = 0, $storyType = 'all', $browseType = 'allstory', $orderBy = 'id_desc', $pager = null)
  823. {
  824. /* Append id for second sort. */
  825. $sort = common::appendOrder($orderBy);
  826. if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
  827. $isProjectStory = $this->app->rawModule == 'projectstory';
  828. if(!isset($this->story)) $this->loadModel('story');
  829. /* Get stories. */
  830. if($isProjectStory)
  831. {
  832. $this->products = $this->product->getProducts($projectID, 'all', '', false);
  833. if($browseType == 'bybranch') $param = $branchID;
  834. $stories = $this->story->getExecutionStories($projectID, $productID, $sort, $browseType, (string)$param, 'all', '', $pager);
  835. }
  836. else
  837. {
  838. $queryID = ($browseType == 'bysearch') ? $param : 0;
  839. $stories = $this->product->getStories($productID, $branchID, $browseType, (int)$queryID, $moduleID, $storyType, $sort, $pager);
  840. }
  841. if(!empty($stories)) $stories = $this->story->mergeReviewer($stories);
  842. return $stories;
  843. }
  844. /**
  845. * 根据需求类型获取需求。
  846. * Get stories by story type.
  847. *
  848. * @param int $productID
  849. * @param string $branch
  850. * @param string $storyType
  851. * @param string $orderBy
  852. * @param object $pager
  853. * @access public
  854. * @return array
  855. */
  856. public function getStoriesByStoryType($productID, $branch = '', $storyType = 'all', $orderBy = 'id_desc', $pager = null)
  857. {
  858. /* Append id for second sort. */
  859. $sort = common::appendOrder($orderBy);
  860. if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
  861. $parentIdList = array();
  862. if($storyType == 'requirement,story' && !empty($this->config->enableER)) $parentIdList = $this->dao->select('id')->from(TABLE_STORY)->where('type')->eq('epic')->andWhere('isParent')->eq(1)->fetchPairs('id', 'id');
  863. if($storyType == 'story' && (!empty($this->config->URAndSR) || $this->config->edition == 'ipd')) $parentIdList = $this->dao->select('id')->from(TABLE_STORY)->where('type')->eq('requirement')->andWhere('isParent')->eq(1)->fetchPairs('id', 'id');
  864. $productQuery = $this->loadModel('story')->buildProductsCondition($productID, $branch);
  865. $stories = $this->dao->select("*,plan,path")->from(TABLE_STORY)
  866. ->where('deleted')->eq(0)
  867. ->andWhere($productQuery)
  868. ->andWhere('parent', true)->eq(0)
  869. ->beginIF($parentIdList)->orWhere("parent")->in($parentIdList)->fi()
  870. ->markRight(1)
  871. ->andWhere("FIND_IN_SET('{$this->config->vision}', vision)")
  872. ->beginIF($storyType != 'all')->andWhere('type')->in($storyType)->fi()
  873. ->beginIF(empty($this->config->enableER))->andWhere('type')->ne('epic')->fi()
  874. ->beginIF(empty($this->config->URAndSR) && $this->config->edition != 'ipd')->andWhere('type')->ne('requirement')->fi()
  875. ->orderBy($orderBy)
  876. ->page($pager)
  877. ->fetchAll('id');
  878. return $stories;
  879. }
  880. /**
  881. * 获取分支的配置数据。
  882. * Get branch options.
  883. *
  884. * @param array $projectProducts
  885. * @param int $projectID
  886. * @access protected
  887. * @return array
  888. */
  889. protected function getBranchOptions($projectProducts, $projectID)
  890. {
  891. $this->loadModel('branch');
  892. $branchOptions = array();
  893. foreach($projectProducts as $product)
  894. {
  895. if(!$product || $product->type == 'normal') continue;
  896. $branches = $this->branch->getList($product->id, $projectID, 'all');
  897. foreach($branches as $branchInfo) $branchOptions[$product->id][$branchInfo->id] = $branchInfo->name;
  898. }
  899. return $branchOptions;
  900. }
  901. /**
  902. * 获取分支和分支标签的显示项。
  903. * Get options of branch and branch tag.
  904. *
  905. * @param int $productID
  906. * @param object|null $product
  907. * @param bool $isProjectStory
  908. * @access protected
  909. * @return array[]
  910. * @param int $projectID
  911. */
  912. protected function getBranchAndTagOption($projectID, $product, $isProjectStory)
  913. {
  914. $branchOption = array();
  915. $branchTagOption = array();
  916. if(empty($product) and $isProjectStory) return [$branchOption, $branchTagOption];
  917. if($product and $product->type != 'normal')
  918. {
  919. $branches = $this->loadModel('branch')->getList($product->id, $projectID, 'all');
  920. foreach($branches as $branchInfo)
  921. {
  922. $branchOption[$branchInfo->id] = $branchInfo->name;
  923. $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
  924. }
  925. }
  926. return [$branchOption, $branchTagOption];
  927. }
  928. /**
  929. * 获取产品看板。
  930. * Get kanban list for product.
  931. *
  932. * @param string $browseType
  933. * @access protected
  934. * @return array
  935. */
  936. protected function getKanbanList($browseType = 'my')
  937. {
  938. $kanbanList = array();
  939. list($productList, $planList, $projectList, $projectProduct, $latestExecutions, $releaseList) = $this->product->getStats4Kanban($browseType);
  940. $programPairs = array(0 => helper::hasFeature('program') ? $this->lang->project->noProgram : '') + $this->loadModel('program')->getPairs(true, 'order_asc');
  941. $productList = $this->getProductList4Kanban($productList, $planList, $projectList, $releaseList, $projectProduct);
  942. foreach($productList as $programID => $productList)
  943. {
  944. $region = array();
  945. $heading = new stdclass();
  946. $heading->title = zget($programPairs, $programID, $programID);
  947. $region['key'] = $programID;
  948. $region['id'] = $programID;
  949. $region['heading'] = $heading;
  950. $lanes = array();
  951. $items = array();
  952. $columnCards = array();
  953. foreach($productList as $laneKey => $laneData)
  954. {
  955. $lanes[] = array('name' => $laneKey, 'title' => $laneData->name);
  956. $columns = array();
  957. foreach(array('unexpiredPlan', 'doing', 'doingProject', 'doingExecution', 'normalRelease') as $columnKey)
  958. {
  959. $column = array('name' => $columnKey, 'title' => $this->lang->product->{$columnKey});
  960. if($columnKey == 'doingExecution' || $columnKey == 'doingProject') $column['parentName'] = 'doing';
  961. $columns[] = $column;
  962. $cardList = !empty($laneData->{$columnKey}) ? $laneData->{$columnKey} : array();
  963. foreach($cardList as $card)
  964. {
  965. $items[$laneKey][$columnKey][] = array('id' => $card->id, 'name' => $card->id, 'title' => isset($card->name) ? $card->name : $card->title, 'status' => isset($card->status) ? $card->status : '', 'cardType' => $columnKey, 'delay' => !empty($card->delay) ? $card->delay : 0, 'progress' => isset($card->progress) ? $card->progress : 0, 'marker' => isset($card->marker) ? $card->marker : 0);
  966. if(!isset($columnCards[$columnKey])) $columnCards[$columnKey] = 0;
  967. $columnCards[$columnKey] ++;
  968. if($columnKey == 'doingProject')
  969. {
  970. if(!empty($latestExecutions[$card->id]))
  971. {
  972. $execution = $latestExecutions[$card->id];
  973. $items[$laneKey]['doingExecution'][] = array('id' => $execution->id, 'name' => $execution->id, 'title' => $execution->name, 'status' => $execution->status, 'cardType' => 'doingExecution', 'delay' => !empty($execution->delay) ? $execution->delay : 0, 'progress' => $execution->progress);
  974. if(!isset($columnCards['doingExecution'])) $columnCards['doingExecution'] = 0;
  975. $columnCards['doingExecution'] ++;
  976. }
  977. }
  978. }
  979. }
  980. }
  981. foreach($columns as $key => $column) $columns[$key]['cards'] = !empty($columnCards[$column['name']]) ? $columnCards[$column['name']] : 0;
  982. $groupData['key'] = $programID;
  983. $groupData['data']['lanes'] = $lanes;
  984. $groupData['data']['cols'] = $columns;
  985. $groupData['data']['items'] = $items;
  986. $region['items'] = array($groupData);
  987. $kanbanList[] = $region;
  988. }
  989. return $kanbanList;
  990. }
  991. /**
  992. * 保存需求页面session变量。
  993. * Save session variables for browse page.
  994. *
  995. * @param object|null $product
  996. * @param string $browseType
  997. * @access protected
  998. * @return void
  999. */
  1000. protected function saveSession4Browse($product, $browseType)
  1001. {
  1002. $uri = $this->app->getURI(true);
  1003. /* For setMenu. */
  1004. if($this->app->tab == 'project')
  1005. {
  1006. $this->session->set('storyList', $uri, 'project');
  1007. }
  1008. else
  1009. {
  1010. $this->session->set('productList', $uri, 'product');
  1011. $this->session->set('storyList', $uri, 'product');
  1012. }
  1013. if(!empty($product)) $this->session->set('currentProductType', $product->type);
  1014. /* Save browse type into session for buildSearchForm. */
  1015. if($browseType != 'bymodule' && $browseType != 'bybranch') $this->session->set('storyBrowseType', $browseType);
  1016. }
  1017. /**
  1018. * 构建搜索表单。
  1019. * Build search form.
  1020. *
  1021. * @param object|bool $project
  1022. * @param int $projectID
  1023. * @param int $productID
  1024. * @param string $branch
  1025. * @param int $param
  1026. * @param string $storyType
  1027. * @param string $browseType
  1028. * @param bool $isProjectStory
  1029. * @access protected
  1030. * @return void
  1031. * @param string $from
  1032. * @param int $blockID
  1033. */
  1034. protected function buildSearchFormForBrowse($project, $projectID, &$productID, $branch, $param, $storyType, $browseType, $isProjectStory, $from, $blockID)
  1035. {
  1036. if($isProjectStory && !$productID && !empty($this->products)) $productID = (int)key($this->products); // If toggle a project by the #swapper component on the story page of the projectstory module, the $productID may be empty. Make sure it has value.
  1037. if($this->config->edition == 'ipd' && $storyType == 'story') unset($this->config->product->search['fields']['roadmap']);
  1038. if(isset($project->hasProduct) && empty($project->hasProduct))
  1039. {
  1040. /* The none-product project don't need display the product in the search form. */
  1041. unset($this->config->product->search['fields']['product']);
  1042. unset($this->config->product->search['params']['product']);
  1043. /* The none-product and none-scrum project don't need display the plan in the search form. */
  1044. if($project->model != 'scrum')
  1045. {
  1046. unset($this->config->product->search['fields']['plan']);
  1047. unset($this->config->product->search['params']['plan']);
  1048. }
  1049. }
  1050. if($this->config->edition == 'ipd' && $storyType == 'requirement')
  1051. {
  1052. $this->config->product->search['params']['roadmap']['values'] = $this->loadModel('roadmap')->getPairs($productID);
  1053. }
  1054. /* Build search form. */
  1055. $params = $isProjectStory ? "projectID=$projectID&productID=0" : "productID=$productID";
  1056. $actionURL = $this->createLink($this->app->rawModule, $this->app->rawMethod, $params . "&branch=$branch&browseType=bySearch&queryID=myQueryID&storyType=$storyType&orderBy=&recTotal=0&recPerPage=20&pageID=1&projectID=$projectID&from=$from&blockID=$blockID");
  1057. $this->config->product->search['onMenuBar'] = 'yes';
  1058. $this->config->product->search['module'] = $storyType;
  1059. if($this->app->rawModule != 'product') $this->config->product->search['module'] = $this->app->rawModule;
  1060. $queryID = ($browseType == 'bysearch') ? $param : 0;
  1061. $this->product->buildSearchForm($productID, $this->products, $queryID, $actionURL, $storyType, $branch, $projectID);
  1062. }
  1063. /**
  1064. * 构建矩阵搜索表单。
  1065. * Build search form for track.
  1066. *
  1067. * @param int $productID
  1068. * @param string $branch
  1069. * @param int $projectID
  1070. * @param string $browseType
  1071. * @param int $param
  1072. * @param string $storyType
  1073. * @access protected
  1074. * @return void
  1075. */
  1076. protected function buildSearchFormForTrack(&$productID, $branch, $projectID, $browseType, $param, $storyType)
  1077. {
  1078. if($this->config->edition == 'ipd' && $storyType == 'story') unset($this->config->product->search['fields']['roadmap']);
  1079. if($this->config->edition == 'ipd' && $storyType == 'requirement')
  1080. {
  1081. $this->config->product->search['params']['roadmap']['values'] = $this->loadModel('roadmap')->getPairs($productID);
  1082. }
  1083. /* Build search form. */
  1084. $actionURL = $this->createLink('product', 'track', "productID={$productID}&branch={$branch}&projectID={$projectID}&browseType=bySearch&param=myQueryID&storyType=$storyType");
  1085. if($this->app->rawModule == 'projectstory') $actionURL = $this->createLink('projectstory', 'track', "projectID={$projectID}&productID={$productID}&branch={$branch}&browseType=bySearch&param=myQueryID&storyType=$storyType");
  1086. $this->config->product->search['module'] = $this->app->rawModule . 'Track';
  1087. $queryID = ($browseType == 'bysearch') ? (int)$param : 0;
  1088. $this->product->buildSearchForm($productID, $this->products, $queryID, $actionURL, $storyType, $branch, $projectID);
  1089. }
  1090. /**
  1091. * 获取需求的ID列表。
  1092. * Get ID list of stories.
  1093. *
  1094. * @param array $stories
  1095. * @access protected
  1096. * @return array
  1097. */
  1098. protected function getStoryIdList($stories)
  1099. {
  1100. $storyIdList = array();
  1101. foreach($stories as $story)
  1102. {
  1103. $storyIdList[$story->id] = $story->id;
  1104. if(!empty($story->children))
  1105. {
  1106. foreach($story->children as $child) $storyIdList[$child->id] = $child->id;
  1107. }
  1108. }
  1109. return $storyIdList;
  1110. }
  1111. /**
  1112. * 路线图页面保存session数据。
  1113. * Save session variables data.
  1114. *
  1115. * @access protected
  1116. * @return void
  1117. */
  1118. protected function saveSession4Roadmap()
  1119. {
  1120. $this->session->set('releaseList', $this->app->getURI(true), 'product');
  1121. $this->session->set('productPlanList', $this->app->getURI(true), 'product');
  1122. }
  1123. /**
  1124. * 回复产品不存在提示消息。
  1125. * Response product not found message.
  1126. *
  1127. * @access protected
  1128. * @return void
  1129. */
  1130. protected function responseNotFound4View()
  1131. {
  1132. if(defined('RUN_MODE') && RUN_MODE == 'api')
  1133. {
  1134. $this->send(array('status' => 'fail', 'code' => 404, 'message' => '404 Not found'));
  1135. return;
  1136. }
  1137. $this->send(array('result' => 'success', 'load' => array('alert' => $this->lang->notFound, 'locate' => $this->createLink('product', 'all'))));
  1138. return;
  1139. }
  1140. /**
  1141. * 将返回链接保存到session中。
  1142. * Save back uri in session.
  1143. *
  1144. * @access protected
  1145. * @return void
  1146. */
  1147. protected function saveBackUriSessionForDynamic()
  1148. {
  1149. $uri = $this->app->getURI(true);
  1150. $this->session->set('productList', $uri, 'product');
  1151. $this->session->set('productPlanList', $uri, 'product');
  1152. $this->session->set('releaseList', $uri, 'product');
  1153. $this->session->set('storyList', $uri, 'product');
  1154. $this->session->set('projectList', $uri, 'project');
  1155. $this->session->set('executionList', $uri, 'execution');
  1156. $this->session->set('taskList', $uri, 'execution');
  1157. $this->session->set('buildList', $uri, 'execution');
  1158. $this->session->set('bugList', $uri, 'qa');
  1159. $this->session->set('caseList', $uri, 'qa');
  1160. $this->session->set('testtaskList', $uri, 'qa');
  1161. }
  1162. /**
  1163. * 获取操作记录。
  1164. * Get actions of the product.
  1165. *
  1166. * @param string $account
  1167. * @param string $orderBy
  1168. * @param int $productID
  1169. * @param string $type
  1170. * @param string $date
  1171. * @param string $direction next|pre
  1172. * @access protected
  1173. * @return [array, object]
  1174. */
  1175. protected function getActionsForDynamic($account, $orderBy, $productID, $type, $date, $direction)
  1176. {
  1177. /* Build parameters. */
  1178. $period = $type == 'account' ? 'all' : $type;
  1179. $date = empty($date) ? '' : date('Y-m-d', (int)$date);
  1180. $actions = $this->loadModel('action')->getDynamicByProduct($productID, $account, $period, $orderBy, 50, $date, $direction);
  1181. $dateGroups = $this->action->buildDateGroup($actions, $direction);
  1182. return array($actions, $dateGroups);
  1183. }
  1184. /**
  1185. * 获取产品仪表盘操作记录。
  1186. * Get actions of the product for dashboard.
  1187. *
  1188. * @param int $productID
  1189. * @access protected
  1190. * @return array
  1191. */
  1192. protected function getActions4Dashboard($productID)
  1193. {
  1194. /* Load pager. */
  1195. $this->app->loadClass('pager', true);
  1196. $pager = new pager(0, 30, 1);
  1197. return $this->loadModel('action')->getDynamic('all', 'all', 'date_desc', $pager, $productID);
  1198. }
  1199. /**
  1200. * 保存产品仪表盘的返回链接session。
  1201. * Save back uri to session.
  1202. *
  1203. * @access protected
  1204. * @return void
  1205. */
  1206. protected function saveBackUriSession4Dashboard()
  1207. {
  1208. $uri = $this->app->getURI(true);
  1209. $this->session->set('productPlanList', $uri, 'product');
  1210. $this->session->set('releaseList', $uri, 'product');
  1211. }
  1212. /**
  1213. * 获取产品看板页面的产品列表。
  1214. * Get product list for Kanban.
  1215. *
  1216. * @param array $productList
  1217. * @param array $planList
  1218. * @param array $projectList
  1219. * @param array $releaseList
  1220. * @param array $projectProduct
  1221. * @access protected
  1222. * @return array
  1223. */
  1224. protected function getProductList4Kanban($productList, $planList, $projectList, $releaseList, $projectProduct)
  1225. {
  1226. $kanbanList = array();
  1227. foreach($productList as $productID => $product)
  1228. {
  1229. if($product->status != 'normal') continue;
  1230. $projects = array();
  1231. if(isset($projectProduct[$productID]))
  1232. {
  1233. foreach($projectProduct[$productID] as $projectID => $project)
  1234. {
  1235. if(isset($projectList[$projectID])) $projects[$projectID] = $projectList[$projectID];
  1236. }
  1237. }
  1238. $product->unexpiredPlan = zget($planList, $productID, array());
  1239. $product->normalRelease = zget($releaseList, $productID, array());
  1240. $product->doingProject = $projects;
  1241. if(!helper::hasFeature('program')) $product->program = 0;
  1242. $kanbanList[$product->program][$productID] = $product;
  1243. }
  1244. return $kanbanList;
  1245. }
  1246. /**
  1247. * 获取空的小时对象数据。
  1248. * Get empty hour data object.
  1249. *
  1250. * @access protected
  1251. * @return object
  1252. */
  1253. protected function getEmptyHour()
  1254. {
  1255. $hour = new stdclass();
  1256. $hour->totalEstimate = 0;
  1257. $hour->totalConsumed = 0;
  1258. $hour->totalLeft = 0;
  1259. $hour->progress = 0;
  1260. return $hour;
  1261. }
  1262. /**
  1263. * 设置需求列表数据。
  1264. * Set the data of requirement list.
  1265. *
  1266. * @param array $stories
  1267. * @param string $browseType
  1268. * @param string $storyType
  1269. * @param bool $isProjectStory
  1270. * @param object|null $product
  1271. * @param object|null $project
  1272. * @param string $branch
  1273. * @param string $branchID
  1274. * @param string $from
  1275. * @access protected
  1276. * @return void
  1277. */
  1278. protected function assignBrowseData($stories, $browseType, $storyType, $isProjectStory, $product, $project, $branch, $branchID, $from = '')
  1279. {
  1280. $productID = $product ? (int)$product->id : 0;
  1281. $projectID = $project ? (int)$project->id : 0;
  1282. $productName = ($isProjectStory && empty($product)) ? $this->lang->product->all : $this->products[$productID];
  1283. $storyIdList = $this->getStoryIdList($stories);
  1284. $projectProducts = $this->getProjectProductList($projectID, $isProjectStory);
  1285. list($branchOpt, $branchTagOpt) = $this->getBranchAndTagOption($projectID, $product, $isProjectStory);
  1286. $gradeList = $this->loadModel('story')->getGradeList('');
  1287. $gradeGroup = array();
  1288. foreach($gradeList as $grade) $gradeGroup[$grade->type][$grade->grade] = $grade->name;
  1289. /* Set show module by config. */
  1290. $showModule = empty($this->config->product->browse->showModule) ? 0 : $this->config->product->browse->showModule;
  1291. if($isProjectStory) $showModule = empty($this->config->projectstory->story->showModule) ? 0 : $this->config->projectstory->story->showModule;
  1292. $this->view->title = $productName . $this->lang->hyphen . ($storyType === 'story' ? $this->lang->product->browse : $this->lang->product->requirement);
  1293. $this->view->productID = $productID;
  1294. $this->view->product = $product;
  1295. $this->view->projectID = $projectID;
  1296. $this->view->project = $project;
  1297. $this->view->stories = $stories;
  1298. $this->view->gradeGroup = $gradeGroup;
  1299. $this->view->gradeMenu = $this->loadModel('story')->getGradeMenu($storyType, $project);
  1300. $this->view->gradePairs = $this->story->getGradePairs($storyType);
  1301. $this->view->maxGradeGroup = $this->story->getMaxGradeGroup();
  1302. $this->view->storyType = $storyType;
  1303. $this->view->browseType = $browseType;
  1304. $this->view->isProjectStory = $isProjectStory;
  1305. $this->view->branch = $branch;
  1306. $this->view->branchID = $branchID;
  1307. $this->view->modulePairs = !empty($showModule) ? $this->loadModel('tree')->getModulePairs($productID, 'story', $showModule) : array();
  1308. $this->view->branchOptions = (empty($product) && $isProjectStory) ? $this->getBranchOptions($projectProducts, $projectID) : array($productID => $branchOpt);
  1309. $this->view->branchTagOption = $branchTagOpt;
  1310. $this->view->projectProducts = $projectProducts;
  1311. $module = ($this->app->tab == 'product' || $from == 'doc' || $from == 'ai') ? $storyType : $this->app->tab;
  1312. $this->view->showGrades = !empty($this->config->{$module}->showGrades) ? $this->config->{$module}->showGrades : $this->story->getDefaultShowGrades($this->view->gradeMenu);
  1313. $storyType = $isProjectStory ? 'all' : $storyType;
  1314. $this->view->summary = $this->product->summary($stories, $storyType);
  1315. $this->view->plans = $this->loadModel('productplan')->getPairs($productID, isset($projectProducts[$productID]) ? array(BRANCH_MAIN) + $projectProducts[$productID]->branches : (($branch === 'all' || empty($branch)) ? '' : $branch), 'unexpired,noclosed', true);
  1316. $this->view->users = $this->loadModel('user')->getPairs('noletter|pofirst|nodeleted');
  1317. $this->view->modules = $this->loadModel('tree')->getOptionMenu($productID, 'story', 0, $branchID);
  1318. $this->view->storyTasks = $this->loadModel('task')->getStoryTaskCounts($storyIdList);
  1319. $this->view->storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList);
  1320. $this->view->storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
  1321. $this->view->reports = in_array($this->config->edition, array('max', 'ipd')) ? $this->loadModel('researchreport')->getPairs() : array();
  1322. $this->view->roadmaps = ($this->config->edition == 'ipd' && $storyType != 'story') ? array(0 => '') + $this->loadModel('roadmap')->getPairs($productID) : array();
  1323. $this->view->noclosedRoadmaps = ($this->config->edition == 'ipd' && $storyType != 'story') ? array(0 => '') + $this->loadModel('roadmap')->getPairs($productID, '', 'noclosed') : array();
  1324. $this->view->hasFrozenStories = $isProjectStory ? $this->loadModel('project')->hasFrozenObject($projectID, 'SRS') : false;
  1325. $this->display();
  1326. }
  1327. /**
  1328. * 处理项目列表展示数据。
  1329. * Process project list display data.
  1330. *
  1331. * @param array $projectList
  1332. * @access protected
  1333. * @return array
  1334. */
  1335. protected function processProjectListData($projectList)
  1336. {
  1337. $userPairs = $this->loadModel('user')->getPairs();
  1338. $userList = $this->user->getListByAccounts(array_keys($userPairs), 'account');
  1339. $this->loadModel('story');
  1340. $this->loadModel('execution');
  1341. $this->loadModel('project');
  1342. foreach($projectList as $project)
  1343. {
  1344. $project = $this->project->formatDataForList($project, $userList);
  1345. $projectStories = $this->story->getExecutionStories($project->id);
  1346. $project->storyCount = count($projectStories);
  1347. $project->storyPoints = 0;
  1348. foreach($projectStories as $story) $project->storyPoints += $story->estimate;
  1349. $project->storyPoints .= ' ' . $this->config->hourUnit;
  1350. $executions = $this->execution->getStatData($project->id, 'all', 0, 0, false, 'skipParent');
  1351. $project->executionCount = count($executions);
  1352. $project->from = 'project';
  1353. $project->actions = $this->project->buildActionList($project);
  1354. }
  1355. return array_values($projectList);
  1356. }
  1357. /**
  1358. * 为跟踪矩阵设置自定义列。
  1359. * Get custom fields for track.
  1360. *
  1361. * @param string $storyType epic|requirement|story
  1362. * @access protected
  1363. * @return array
  1364. */
  1365. public function getCustomFieldsForTrack($storyType)
  1366. {
  1367. $listFields = array();
  1368. $listFields['requirement'] = $this->lang->URCommon;
  1369. $listFields['story'] = $this->lang->SRCommon;
  1370. $listFields['project'] = $this->lang->story->project;
  1371. $listFields['execution'] = $this->lang->story->execution;
  1372. $listFields['design'] = $this->lang->story->design;
  1373. $listFields['commit'] = $this->lang->story->repoCommit;
  1374. $listFields['task'] = $this->lang->story->tasks;
  1375. $listFields['bug'] = $this->lang->story->bugs;
  1376. $listFields['case'] = $this->lang->story->cases;
  1377. if($storyType == 'requirement' || $storyType == 'story') unset($listFields['requirement']);
  1378. if($storyType == 'story') unset($listFields['story']);
  1379. $systemShowFields = $this->loadModel('setting')->getItem("owner=system&module=product&section=trackFields&key={$storyType}");
  1380. $accountShowFields = $this->setting->getItem("owner={$this->app->user->account}&module=product&section=trackFields&key={$storyType}");
  1381. $showFields = empty($accountShowFields) ? $systemShowFields : $accountShowFields;
  1382. $showFields = empty($showFields) ? array_keys($listFields) : explode(',', $showFields);
  1383. return array('list' => $listFields, 'show' => array_merge(array($storyType), $showFields));
  1384. }
  1385. /**
  1386. * 获取根据矩阵可用的需求类型。
  1387. * Get active storyType list for track.
  1388. *
  1389. * @param int $projectID
  1390. * @param int $productID
  1391. * @access protected
  1392. * @return array
  1393. */
  1394. public function getActiveStoryTypeForTrack($projectID = 0, $productID = 0)
  1395. {
  1396. $storyTypeList = $this->lang->story->typeList;
  1397. /* Check story type by linked story's type. */
  1398. if($this->app->rawModule == 'projectstory' && $projectID)
  1399. {
  1400. $stories = $this->getStories($projectID, $productID);
  1401. if($stories)
  1402. {
  1403. $havingType = array();
  1404. foreach($stories as $story) $havingType[$story->type] = $story->type;
  1405. if(!isset($havingType['epic'])) unset($storyTypeList['epic']);
  1406. if(!isset($havingType['epic']) && !isset($havingType['requirement'])) unset($storyTypeList['requirement']);
  1407. }
  1408. }
  1409. return $storyTypeList;
  1410. }
  1411. /**
  1412. * 格式化导出数据。
  1413. * Format export data.
  1414. *
  1415. * @param array $products
  1416. * @access public
  1417. * @return array
  1418. */
  1419. public function formatExportData($products)
  1420. {
  1421. if(empty($products) || $this->config->vision == 'or') return $products;
  1422. foreach($products as $product)
  1423. {
  1424. $product->testCaseCoverage = isset($product->testCaseCoverage) ? $product->testCaseCoverage . '%' : '0%';
  1425. $product->epicCompleteRate = isset($product->epicCompleteRate) ? $product->epicCompleteRate . '%' : '0%';
  1426. $product->requirementCompleteRate = isset($product->requirementCompleteRate) ? $product->requirementCompleteRate . '%' : '0%';
  1427. $product->storyCompleteRate = isset($product->storyCompleteRate) ? $product->storyCompleteRate . '%' : '0%';
  1428. $product->bugFixedRate = isset($product->bugFixedRate) ? $product->bugFixedRate . '%' : '0%';
  1429. }
  1430. return $products;
  1431. }
  1432. }