zen.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. /**
  3. * The zen file of release module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Shujie Tian<tianshujie@easycorp.ltd>
  8. * @package release
  9. * @link https://www.zentao.net
  10. */
  11. class releaseZen extends release
  12. {
  13. /**
  14. * 构造待创建的发布数据。
  15. * Build the release data to be create.
  16. *
  17. * @param int $productID
  18. * @param int $branch
  19. * @param int $projectID
  20. * @access protected
  21. * @return object|false
  22. */
  23. protected function buildReleaseForCreate($productID, $branch, $projectID = 0)
  24. {
  25. $productID = $this->post->product ? $this->post->product : $productID;
  26. $branch = $this->post->branch ? $this->post->branch : $branch;
  27. $newSystem = $this->post->newSystem;
  28. if(empty($projectID))
  29. {
  30. $product = $this->loadModel('product')->getById($productID);
  31. if($product->shadow)
  32. {
  33. $projectID = $this->dao->select('t2.id')->from(TABLE_PROJECTPRODUCT)->alias('t1')
  34. ->leftJoin(TABLE_PROJECT)->alias('t2')
  35. ->on('t1.project=t2.id')
  36. ->where('t1.product')->eq($productID)
  37. ->andWhere('t2.type')->eq('project')
  38. ->fetch('id');
  39. }
  40. }
  41. if(!$newSystem && !$this->post->system) $this->config->release->form->create['system']['required'] = true;
  42. if($newSystem && !$this->post->systemName)
  43. {
  44. $this->config->release->form->create['systemName'] = array('type' => 'string', 'required' => true, 'filter' => 'trim');
  45. $this->lang->release->systemName = $this->lang->release->system;
  46. }
  47. $release = form::data()
  48. ->add('product', (int)$productID)
  49. ->add('branch', (int)$branch)
  50. ->setIF($projectID, 'project', $projectID)
  51. ->setIF($this->post->build === false, 'build', 0)
  52. ->setIF($this->post->status != 'normal', 'releasedDate', null)
  53. ->get();
  54. /* Check build if build is required. */
  55. if(strpos($this->config->release->create->requiredFields, 'build') !== false && empty($release->build)) dao::$errors['build'] = sprintf($this->lang->error->notempty, $this->lang->release->build);
  56. if(!$newSystem && $this->post->system)
  57. {
  58. $system = $this->loadModel('system')->fetchByID((int)$this->post->system);
  59. if(!$system) dao::$errors['system'][] = sprintf($this->lang->error->notempty, $this->lang->release->system);
  60. if($system->integrated == '1')
  61. {
  62. $releases = (array)$this->post->releases;
  63. $release->build = '';
  64. $release->releases = trim(implode(',', array_filter($releases)), ',');
  65. if(!$release->releases) dao::$errors['releases[' . key($releases) . ']'][] = sprintf($this->lang->error->notempty, $this->lang->release->name);
  66. }
  67. }
  68. if(dao::isError()) return false;
  69. if($newSystem && $this->post->systemName)
  70. {
  71. $system = new stdclass();
  72. $system->name = trim($this->post->systemName);
  73. $system->product = $productID;
  74. $system->createdBy = $this->app->user->account;
  75. $system->createdDate = helper::now();
  76. $release->system = $this->loadModel('system')->create($system);
  77. }
  78. return $release;
  79. }
  80. /**
  81. * 构建搜索表单字段。
  82. * Build search form fields.
  83. *
  84. * @param int $queryID
  85. * @param string $actionURL
  86. * @param object $product
  87. * @param string $branch
  88. * @access protected
  89. * @return void
  90. */
  91. protected function buildSearchForm($queryID, $actionURL, $product, $branch)
  92. {
  93. $this->config->release->search['queryID'] = $queryID;
  94. $this->config->release->search['actionURL'] = $actionURL;
  95. if($product->type != 'normal') $this->config->release->search['params']['branch']['values'] = $this->loadModel('branch')->getPairs($product->id, 'all');
  96. $this->config->release->search['params']['build']['values'] = $this->loadmodel('build')->getBuildPairs(array($product->id), $branch, 'notrunk|withbranch|hasproject', 0, 'execution', '', false);
  97. $this->loadModel('search')->setSearchParams($this->config->release->search);
  98. }
  99. /**
  100. * 获取发布列表的搜索条件。
  101. * Get the search condition of release list.
  102. *
  103. * @param int $queryID
  104. * @access protected
  105. * @return string
  106. */
  107. protected function getSearchQuery($queryID)
  108. {
  109. if($queryID)
  110. {
  111. $query = $this->loadModel('search')->getQuery($queryID);
  112. if($query)
  113. {
  114. $this->session->set('releaseQuery', $query->sql);
  115. $this->session->set('releaseForm', $query->form);
  116. }
  117. }
  118. if($this->session->releaseQuery === false) $this->session->set('releaseQuery', ' 1 = 1');
  119. $releaseQuery = $this->session->releaseQuery;
  120. /* Replace the condition of all branch to 1. */
  121. $allBranch = "`branch` = 'all'";
  122. if(strpos($releaseQuery, $allBranch) !== false) $releaseQuery = str_replace($allBranch, '1', $releaseQuery);
  123. $releaseQuery = preg_replace('/`(\w+)`/', 't1.`$1`', $releaseQuery);
  124. return $releaseQuery;
  125. }
  126. /**
  127. * 获取发布不可关联的需求ID列表。
  128. * Get the story id list that can not be linked to the release.
  129. *
  130. * @param object $release
  131. * @access protected
  132. * @return array
  133. */
  134. public function getExcludeStoryIdList($release)
  135. {
  136. $parentIdList = $this->dao->select('id')->from(TABLE_STORY)
  137. ->where('product')->eq($release->product)
  138. ->andWhere('type')->eq('story')
  139. ->andWhere('isParent')->eq('1')
  140. ->andWhere('status')->notIN('draft,reviewing,changing')
  141. ->fetchPairs();
  142. foreach(explode(',', $release->stories) as $storyID)
  143. {
  144. if(!$storyID) continue;
  145. if(!isset($parentIdList[$storyID])) $parentIdList[$storyID] = $storyID;
  146. }
  147. return $parentIdList;
  148. }
  149. /**
  150. * 生成的发布详情页面的需求数据。
  151. * Generate the story data for the release view page.
  152. *
  153. * @param object $release
  154. * @param string $type
  155. * @param string $link
  156. * @param string $param
  157. * @param string $orderBy
  158. * @param object $storyPager
  159. * @param object $bugPager
  160. * @param object $leftBugPager
  161. * @access protected
  162. * @return void
  163. */
  164. protected function assignVarsForView($release, $type, $link, $param, $orderBy, $storyPager = null, $bugPager = null, $leftBugPager = null)
  165. {
  166. $sort = common::appendOrder($orderBy);
  167. if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
  168. $sort .= ',buildID_asc';
  169. $storyIdList = trim($release->stories, ',');
  170. $bugIdList = trim($release->bugs, ',');
  171. $leftBugIdList = trim($release->leftBugs, ',');
  172. if($release->releases)
  173. {
  174. $linkedReleases = $this->release->getListByCondition(explode(',', $release->releases));
  175. foreach($linkedReleases as $linkedRelease)
  176. {
  177. $storyIdList .= ',' . $linkedRelease->stories;
  178. $bugIdList .= ',' . $linkedRelease->bugs;
  179. $leftBugIdList .= ',' . $linkedRelease->leftBugs;
  180. }
  181. }
  182. $stories = $this->release->getStoryList($storyIdList, $release->branch, $type == 'story' ? $sort : '', $storyPager);
  183. $sort = common::appendOrder($orderBy);
  184. $bugs = $this->release->getBugList($bugIdList, $type == 'bug' ? $sort : '', $bugPager);
  185. if($type == 'leftBug' && strpos($orderBy, 'severity_') !== false) $sort = str_replace('severity_', 'severityOrder_', $sort);
  186. $leftBugs = $this->release->getBugList($leftBugIdList, $type == 'leftBug' ? $sort : '', $leftBugPager, 'left');
  187. $product = $this->loadModel('product')->getByID($release->product);
  188. $this->view->title = "RELEASE #$release->id $release->name/" . $product->name;
  189. $this->view->actions = $this->loadModel('action')->getList('release', $release->id);
  190. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  191. $this->view->storyPager = $storyPager;
  192. $this->view->stories = $stories;
  193. $this->view->release = $release;
  194. $this->view->orderBy = $orderBy;
  195. $this->view->type = $type;
  196. $this->view->link = $link;
  197. $this->view->grades = $this->loadModel('story')->getGradePairs('story', 'all');
  198. $this->view->showGrade = $this->config->edition == 'ipd';
  199. $this->view->param = $param;
  200. $this->view->storyCases = $this->loadModel('testcase')->getStoryCaseCounts(array_column($stories, 'id'));
  201. $this->view->summary = $this->product->summary($stories);
  202. $this->view->builds = $this->loadModel('build')->getBuildPairs(array($release->product), 'all', 'withbranch|hasproject|hasdeleted', 0, 'execution', '', true);
  203. $this->view->bugs = $bugs;
  204. $this->view->leftBugs = $leftBugs;
  205. $this->view->bugPager = $bugPager;
  206. $this->view->leftBugPager = $leftBugPager;
  207. if($this->app->getViewType() == 'json')
  208. {
  209. unset($this->view->storyPager);
  210. unset($this->view->bugPager);
  211. unset($this->view->leftBugPager);
  212. }
  213. }
  214. /**
  215. * 构造关联需求的搜索表单。
  216. * Build the search form of link story.
  217. *
  218. * @param object $release
  219. * @param int $queryID
  220. * @access protected
  221. * @return void
  222. */
  223. protected function buildLinkStorySearchForm($release, $queryID)
  224. {
  225. $this->app->loadLang('story');
  226. $this->loadModel('product');
  227. unset($this->config->product->search['fields']['product']);
  228. unset($this->config->product->search['fields']['project']);
  229. unset($this->config->product->search['fields']['grade']);
  230. unset($this->config->product->search['params']['product']);
  231. unset($this->config->product->search['params']['project']);
  232. unset($this->config->product->search['params']['grade']);
  233. $this->config->product->search['actionURL'] = $this->createLink($this->app->rawModule, 'view', "releaseID={$release->id}&type=story&link=true&param=" . helper::safe64Encode('&browseType=bySearch&queryID=myQueryID'));
  234. $this->config->product->search['queryID'] = $queryID;
  235. $this->config->product->search['style'] = 'simple';
  236. $this->config->product->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($release->product, $release->branch, 'withMainPlan', true);
  237. $this->config->product->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $this->lang->story->statusList);
  238. $searchModules = array();
  239. $moduleGroups = $this->loadModel('tree')->getOptionMenu($release->product, 'story', 0, explode(',', $release->branch));
  240. foreach($moduleGroups as $modules) $searchModules += $modules;
  241. $this->config->product->search['params']['module']['values'] = $searchModules;
  242. if($release->productType == 'normal')
  243. {
  244. unset($this->config->product->search['fields']['branch']);
  245. unset($this->config->product->search['params']['branch']);
  246. }
  247. else
  248. {
  249. $branches = $this->loadModel('branch')->getPairsByIdList(explode(',', trim($release->branch, ',')));
  250. $this->config->product->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$release->productType]);
  251. $this->config->product->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches;
  252. }
  253. $this->loadModel('search')->setSearchParams($this->config->product->search);
  254. }
  255. /**
  256. * 构造关联Bug的搜索表单。
  257. * Build the search form of link bug.
  258. *
  259. * @param object $release
  260. * @param int $queryID
  261. * @param string $type
  262. * @access protected
  263. * @return void
  264. */
  265. protected function buildLinkBugSearchForm($release, $queryID, $type)
  266. {
  267. $this->loadModel('bug');
  268. unset($this->config->bug->search['fields']['product']);
  269. unset($this->config->bug->search['fields']['project']);
  270. unset($this->config->bug->search['params']['product']);
  271. unset($this->config->bug->search['params']['project']);
  272. $this->config->bug->search['actionURL'] = $this->createLink($this->app->rawModule, 'view', "releaseID={$release->id}&type={$type}&link=true&param=" . helper::safe64Encode('&browseType=bySearch&queryID=0'));
  273. $this->config->bug->search['queryID'] = $queryID;
  274. $this->config->bug->search['style'] = 'simple';
  275. $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($release->product, $release->branch, 'withMainPlan', true);
  276. $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($release->product, $release->branch);
  277. $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($release->product), 'all', 'releasetag');
  278. $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
  279. $searchModules = array();
  280. $moduleGroups = $this->loadModel('tree')->getOptionMenu($release->product, 'bug', 0, explode(',', $release->branch));
  281. foreach($moduleGroups as $modules) $searchModules += $modules;
  282. $this->config->bug->search['params']['module']['values'] = $searchModules;
  283. if($release->productType == 'normal')
  284. {
  285. unset($this->config->bug->search['fields']['branch']);
  286. unset($this->config->bug->search['params']['branch']);
  287. }
  288. else
  289. {
  290. $branches = $this->loadModel('branch')->getPairsByIdList(explode(',', trim($release->branch, ',')));
  291. $this->config->bug->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$release->productType]);
  292. $this->config->bug->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches;
  293. }
  294. $this->loadModel('search')->setSearchParams($this->config->bug->search);
  295. }
  296. /**
  297. * 构造导出的需求列表数据。
  298. * Build the story list data for export.
  299. *
  300. * @param object $release
  301. * @access protected
  302. * @return string
  303. */
  304. protected function buildStoryDataForExport($release)
  305. {
  306. $this->loadModel('story');
  307. $html = "<h3>{$this->lang->release->stories}</h3>";
  308. $fields = array('id' => $this->lang->story->id, 'title' => $this->lang->story->title);
  309. $stories = $this->release->getStoryList($release->stories, (int)$release->branch);
  310. if(empty($stories)) return $html;
  311. $html .= '<table><tr>';
  312. foreach($fields as $fieldLabel) $html .= "<th><nobr>$fieldLabel</nobr></th>\n";
  313. $html .= '</tr>';
  314. $stories = array_map(function($story){$story->title = "<a href='" . common::getSysURL() . $this->createLink('story', 'view', "storyID=$story->id") . "' target='_blank'>$story->title</a>"; return $story;}, $stories);
  315. foreach($stories as $row)
  316. {
  317. $html .= "<tr valign='top'>\n";
  318. foreach($fields as $fieldName => $fieldLabel) $html .= "<td><nobr>" . zget($row, $fieldName, '') . "</nobr></td>\n";
  319. $html .= "</tr>\n";
  320. }
  321. $html .= '</table>';
  322. return $html;
  323. }
  324. /**
  325. * 构造导出的解决的Bug或遗留Bug列表数据。
  326. * Build the resolved or generated bug list data for export.
  327. *
  328. * @param object $release
  329. * @param string $type bug|leftbug
  330. * @access protected
  331. * @return string
  332. */
  333. protected function buildBugDataForExport($release, $type = 'bug')
  334. {
  335. $this->loadModel('bug');
  336. $title = $type == 'bug' ? $this->lang->release->bugs : $this->lang->release->generatedBugs;
  337. $html = "<h3>{$title}</h3>";
  338. $fields = array('id' => $this->lang->bug->id, 'title' => $this->lang->bug->title);
  339. $bugIdList = $type == 'bug' ? $release->bugs : $release->leftBugs;
  340. $bugs = $this->release->getBugList($bugIdList);
  341. if(empty($bugs)) return $html;
  342. $html .= '<table><tr>';
  343. foreach($fields as $fieldLabel) $html .= "<th><nobr>$fieldLabel</nobr></th>\n";
  344. $html .= '</tr>';
  345. $bugs = array_map(function($bug){$bug->title = "<a href='" . common::getSysURL() . $this->createLink('bug', 'view', "bugID=$bug->id") . "' target='_blank'>$bug->title</a>"; return $bug;}, $bugs);
  346. foreach($bugs as $row)
  347. {
  348. $html .= "<tr valign='top'>\n";
  349. foreach($fields as $fieldName => $fieldLabel) $html .= "<td><nobr>" . zget($row, $fieldName, '') . "</nobr></td>\n";
  350. $html .= "</tr>\n";
  351. }
  352. $html .= '</table>';
  353. return $html;
  354. }
  355. }