treestory.html.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <?php
  2. /**
  3. * The treestory view file of execution module of ZenTaoPMS.
  4. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  5. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  6. * @author Yanyi Cao <caoyanyi@easycorp.ltd>
  7. * @package execution
  8. * @link https://www.zentao.net
  9. */
  10. namespace zin;
  11. /* Get min stage. */
  12. $minStage = $story->stage;
  13. $stageList = implode(',', array_keys($this->lang->story->stageList));
  14. $minStagePos = strpos($stageList, $minStage);
  15. if($story->stages and $branches)
  16. {
  17. foreach($story->stages as $branch => $stage)
  18. {
  19. if(strpos($stageList, $stage) !== false and strpos($stageList, $stage) > $minStagePos)
  20. {
  21. $minStage = $stage;
  22. $minStagePos = strpos($stageList, $stage);
  23. }
  24. }
  25. }
  26. $reviewed = $lang->noData;
  27. $reviewedBy = explode(',', $story->reviewedBy);
  28. if(count($reviewedBy) > 1)
  29. {
  30. $reviewed = '';
  31. foreach($reviewedBy as $account) $reviewed .= ' ' . zget($users, trim($account));
  32. }
  33. $moduleTitle = '';
  34. $moduleItems = array();
  35. if(empty($modulePath))
  36. {
  37. $moduleTitle .= '/';
  38. $moduleItems[] = '/';
  39. }
  40. else
  41. {
  42. foreach($modulePath as $key => $module)
  43. {
  44. $moduleTitle .= $module->name;
  45. if(!common::hasPriv('product', 'browse'))
  46. {
  47. $moduleItems[] = $module->name;
  48. }
  49. else
  50. {
  51. $arrow = '';
  52. if(isset($modulePath[$key + 1]))
  53. {
  54. $moduleTitle .= '/';
  55. $arrow = $lang->arrow;
  56. }
  57. $moduleItems[] = a
  58. (
  59. setClass('text-primary'),
  60. set::href(createLink('product', 'browse', "productID={$story->product}&branch={$story->branch}&browseType=byModule&param={$module->id}")),
  61. html($module->name . $arrow)
  62. );
  63. }
  64. }
  65. }
  66. $mailtoList = array();
  67. $mailto = explode(',', $story->mailto);
  68. if(empty($mailto))
  69. {
  70. $mailtoList[] = $lang->noData;
  71. }
  72. else
  73. {
  74. foreach($mailto as $account)
  75. {
  76. $mailtoList[] = span
  77. (
  78. setClass('mr-1'),
  79. zget($users, trim($account))
  80. );
  81. }
  82. }
  83. div
  84. (
  85. setClass('section-list', 'canvas', 'pt-4', 'pb-6', 'px-4', 'mb-4'),
  86. div
  87. (
  88. setClass('flex items-center flex-nowrap mb-4'),
  89. label
  90. (
  91. setClass('flex-none rounded-full gray-outline'),
  92. $story->id
  93. ),
  94. span
  95. (
  96. setClass('mx-2 text-lg font-bold clip'),
  97. $story->title
  98. ),
  99. label
  100. (
  101. setClass('flex-none rounded-full story-status status-' . $story->status),
  102. $this->processStatus('story', $story)
  103. )
  104. ),
  105. div
  106. (
  107. setClass('flex items-center flex-nowrap mb-4'),
  108. div
  109. (
  110. setClass('flex-1'),
  111. $lang->story->stage,
  112. span
  113. (
  114. setClass('ml-2 font-bold'),
  115. $lang->story->stageList[$story->stage]
  116. )
  117. ),
  118. div
  119. (
  120. setClass('flex-1'),
  121. $lang->story->estimate,
  122. span
  123. (
  124. setClass('ml-2 font-bold'),
  125. helper::formatHours($story->estimate)
  126. )
  127. )
  128. ),
  129. btngroup
  130. (
  131. setID('actionButtons'),
  132. setClass('mb-4 mt-4'),
  133. hasPriv('story', 'change') ? btn
  134. (
  135. setClass('text-primary'),
  136. set::icon('alter'),
  137. set::hint($lang->story->operateList['changed']),
  138. set::url(createLink('story', 'change', array('storyID' => $story->id))),
  139. set::disabled(!$this->story->isClickable($story, 'change')),
  140. set('data-app', $app->tab)
  141. ) : null,
  142. hasPriv('story', 'delete') ? btn
  143. (
  144. setClass('text-primary ml-2 ajax-submit'),
  145. set::icon('trash'),
  146. set::hint($lang->story->delete),
  147. set::url(createLink('story', 'delete', array('storyID' => $story->id))),
  148. set::disabled(!$this->story->isClickable($story, 'delete')),
  149. set('data-confirm', $story->type == 'requirement' ? str_replace($lang->SRCommon, $lang->URCommon, $lang->story->confirmDelete) : $lang->story->confirmDelete)
  150. ) : null,
  151. hasPriv('story', 'review') ? btn
  152. (
  153. setClass('text-primary'),
  154. set::icon('search'),
  155. set::hint($lang->story->review),
  156. set::url(createLink('story', 'review', array('storyID' => $story->id))),
  157. set::disabled(!$this->story->isClickable($story, 'review')),
  158. set('data-app', $app->tab)
  159. ) : null,
  160. hasPriv('story', 'close') ? btn
  161. (
  162. setClass('text-primary'),
  163. set::icon('off'),
  164. set::hint($lang->story->close),
  165. set::url(createLink('story', 'close', array('storyID' => $story->id))),
  166. set::disabled(!$this->story->isClickable($story, 'close')),
  167. set('data-toggle', 'modal')
  168. ) : null,
  169. hasPriv('story', 'edit') ? btn
  170. (
  171. setClass('text-primary ml-2'),
  172. set::icon('edit'),
  173. set::hint($lang->story->edit),
  174. set::url(createLink('story', 'edit', array('storyID' => $story->id))),
  175. set::disabled(!$this->story->isClickable($story, 'edit')),
  176. set('data-app', $app->tab)
  177. ) : null,
  178. $story->isParent == '0' && hasPriv('testcase', 'create') ? btn
  179. (
  180. setClass('text-primary ml-2'),
  181. set::icon('sitemap'),
  182. set::hint($lang->testcase->create),
  183. set::url(createLink('testcase', 'create', "productID={$story->product}&branch={$story->branch}&module=0&from=execution&param={$executionID}&storyID={$story->id}")),
  184. set::disabled($story->type != 'story'),
  185. set('data-app', $app->tab)
  186. ) : null
  187. ),
  188. hr(),
  189. section
  190. (
  191. set::title($lang->story->legendSpec),
  192. set::content(empty($story->spec) ? $lang->noData : $story->spec),
  193. set::useHtml(true)
  194. ),
  195. hr(),
  196. section
  197. (
  198. set::title($lang->story->legendVerify),
  199. set::content(empty($story->verify) ? $lang->noData : $story->verify),
  200. set::useHtml(true)
  201. ),
  202. h::hr(set::className('mt-5 mb-1')),
  203. fileList
  204. (
  205. set::padding(false),
  206. set::files($story->files)
  207. ),
  208. hr(),
  209. section
  210. (
  211. set::title($lang->story->legendBasicInfo),
  212. tableData
  213. (
  214. $product->shadow ? null : item
  215. (
  216. set::name($lang->story->product),
  217. common::hasPriv('product', 'view') ? a(set::href($this->createLink('product', 'view', "productID=$story->product")), $product->name) : $product->name
  218. ),
  219. $product->type == 'normal' ? null : item
  220. (
  221. set::name($lang->story->branch),
  222. common::hasPriv('product', 'view') ? a(set::href($this->createLink('product', 'view', "productID=$story->product&branch=$story->branch")), $branches[$story->branch]) : $branches[$story->branch],
  223. a(set::href($this->createLink('product', 'view', "productID=$story->product")), $product->name)
  224. ),
  225. item
  226. (
  227. set::name($lang->story->module),
  228. set::title($moduleTitle),
  229. $moduleItems
  230. ),
  231. ($story->type != 'requirement' and $story->parent != -1) ? item
  232. (
  233. set::trClass('plan-line'),
  234. set::name($lang->story->plan),
  235. empty($story->planTitle) ? null : array_values(array_map(function($planID, $planTitle)
  236. {
  237. $items = array();
  238. $items[] = common::hasPriv('productplan', 'view') ? a(set::href(helper::createLink('productplan', 'view', "planID={$planID}")), $planTitle) : $planTitle;
  239. $items[] = h::br();
  240. return $items;
  241. }, array_keys($story->planTitle), array_values($story->planTitle)))
  242. ) : null,
  243. item
  244. (
  245. set::id('source'),
  246. set::name($lang->story->source),
  247. zget($lang->story->sourceList, $story->source, '')
  248. ),
  249. item
  250. (
  251. set::id('sourceNoteBox'),
  252. set::name($lang->story->sourceNote),
  253. $story->sourceNote
  254. ),
  255. item
  256. (
  257. set::name($lang->story->status),
  258. span
  259. (
  260. setClass("status-{$story->status}"),
  261. $this->processStatus('story', $story)
  262. )
  263. ),
  264. $story->type == 'requirement' ? null : item
  265. (
  266. set::trClass('stage-line'),
  267. set::name($lang->story->stage),
  268. zget($lang->story->stageList, $minStage, '')
  269. ),
  270. item
  271. (
  272. set::name($lang->story->category),
  273. zget($lang->story->categoryList, $story->category)
  274. ),
  275. item
  276. (
  277. set::name($lang->story->pri),
  278. priLabel($story->pri, set::text($lang->story->priList))
  279. ),
  280. item
  281. (
  282. set::name($lang->story->estimate),
  283. $story->estimate . $config->hourUnit
  284. ),
  285. in_array($story->source, $config->story->feedbackSource) ? item
  286. (
  287. set::name($lang->story->feedbackBy),
  288. $story->feedbackBy
  289. ) : null,
  290. in_array($story->source, $config->story->feedbackSource) ? item
  291. (
  292. set::name($lang->story->notifyEmail),
  293. $story->notifyEmail
  294. ) : null,
  295. item
  296. (
  297. set::name($lang->story->keywords),
  298. $story->keywords
  299. ),
  300. item
  301. (
  302. set::name($lang->story->legendMailto),
  303. $mailtoList
  304. )
  305. ),
  306. hr(),
  307. section
  308. (
  309. set::title($lang->story->legendRelated),
  310. tableData
  311. (
  312. set::useTable(false),
  313. $story->type == 'story' ? item
  314. (
  315. set::collapse(true),
  316. set::name($lang->story->legendBugs),
  317. empty($bugs) ? null : h::ul
  318. (
  319. array_values(array_map(function($bug) use($lang)
  320. {
  321. return h::li
  322. (
  323. set::title($bug->title),
  324. label(setClass('circle size-sm'), $bug->id),
  325. common::hasPriv('bug', 'view') ? a(set::href(helper::createLink('bug', 'view', "bugID=$bug->id")), setClass('title'), set('data-toggle', 'modal'), set::title($bug->title), $bug->title) : span(setClass('title'), $bug->title),
  326. label(setClass("status-{$bug->status} size-sm ml-1"), $lang->bug->statusList[$bug->status])
  327. );
  328. }, $bugs))
  329. )
  330. ) : null,
  331. $story->type == 'story' ? item
  332. (
  333. set::collapse(true),
  334. set::name($lang->story->legendCases),
  335. empty($cases) ? null : h::ul
  336. (
  337. array_values(array_map(function($case)
  338. {
  339. return h::li
  340. (
  341. set::title($case->title),
  342. label(setClass('circle size-sm'), $case->id),
  343. common::hasPriv('testcase', 'view') ? a(set::href(helper::createLink('testcase', 'view', "caseID=$case->id")), setClass('title'), set('data-toggle', 'modal'), set::title($case->title), $case->title) : span(setClass('title'), $case->title)
  344. );
  345. }, $cases))
  346. )
  347. ) : null,
  348. item
  349. (
  350. set::collapse(true),
  351. set::name($lang->story->linkStories),
  352. empty($story->linkStoryTitles) ? null : h::ul
  353. (
  354. array_values(array_map(function($storyID, $storyTitle) use($storyProducts, $story)
  355. {
  356. global $app;
  357. $hasPriv = ($app->user->admin || strpos(",{$app->user->view->products},", ",{$storyProducts[$storyID]},") !== false);
  358. return h::li
  359. (
  360. set::title($storyTitle),
  361. label(setClass('circle size-sm'), $storyID),
  362. $hasPriv ? a(set::href(helper::createLink('story', 'view', "storyID=$storyID&version=0&param=0&storyType=$story->type")), setClass('title'), set('data-toggle', 'modal'), set::title($storyTitle), $storyTitle) : span(setClass('title'), $storyTitle)
  363. );
  364. }, array_keys($story->linkStoryTitles), array_values($story->linkStoryTitles)))
  365. )
  366. )
  367. )
  368. ),
  369. hr(),
  370. section
  371. (
  372. set::title($lang->story->legendLifeTime),
  373. tableData
  374. (
  375. item
  376. (
  377. set::name($lang->story->openedBy),
  378. zget($users, $story->openedBy) . $lang->at . $story->openedDate
  379. ),
  380. item
  381. (
  382. set::name($lang->story->assignedTo),
  383. $story->assignedTo ? zget($users, $story->assignedTo) . $lang->at . $story->assignedDate : $lang->noData
  384. ),
  385. item
  386. (
  387. set::name($lang->story->reviewedBy),
  388. $reviewed
  389. ),
  390. item
  391. (
  392. set::name($lang->story->reviewedDate),
  393. $story->reviewedBy ? $story->reviewedDate : $lang->noData
  394. ),
  395. item
  396. (
  397. set::name($lang->story->closedBy),
  398. $story->closedBy ? zget($users, $story->closedBy) . $lang->at . $story->closedDate : $lang->noData
  399. ),
  400. item
  401. (
  402. set::tdClass('resolution'),
  403. set::name($lang->story->closedReason),
  404. $story->closedReason ? zget($lang->story->reasonList, $story->closedReason) : $lang->noData,
  405. isset($story->extraStories[$story->duplicateStory]) ? a(set::href(createLink($story->extraStories[$story->duplicateStory]->type, 'view', "storyID=$story->duplicateStory")), set::title($story->extraStories[$story->duplicateStory]->title), "#{$story->duplicateStory} {$story->extraStories[$story->duplicateStory]->title}") : null
  406. ),
  407. item
  408. (
  409. set::name($lang->story->lastEditedBy),
  410. $story->lastEditedBy ? zget($users, $story->lastEditedBy) . $lang->at . $story->lastEditedDate : $lang->noData
  411. )
  412. )
  413. )
  414. )
  415. );
  416. history
  417. (
  418. set::objectType('story'),
  419. set::objectID($story->id),
  420. set::commentBtn(true),
  421. set::commentUrl(createLink('action', 'comment', array('objectType' => 'story', 'objectID' => $story->id)))
  422. );