edit.html.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. /**
  3. * The edit file of story 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 Yidong Wang<Yidong@easycorp.ltd>
  8. * @package story
  9. * @link https://www.zentao.net
  10. */
  11. namespace zin;
  12. $uid = uniqid();
  13. $canEditContent = strpos(',draft,changing,', ",{$story->status},") !== false;
  14. $forceReview = $this->story->checkForceReview($story->type);
  15. $assignedToList = $story->status == 'closed' ? array('closed' => 'Closed') : $users;
  16. $storyEstimate = $story->estimate ? helper::formatHours($story->estimate) : 0;
  17. $planCount = !empty($story->planTitle) ? count($story->planTitle) : 0;
  18. $multiplePlan = ($product->type != 'normal' && empty($story->branch) && $planCount > 1) || ($story->type != 'story');
  19. $showPlan = $config->vision == 'or' ? false : true;
  20. $minStage = $story->stage;
  21. $stageList = implode(',', array_keys($this->lang->story->stageList));
  22. $minStagePos = strpos($stageList, $minStage);
  23. if(!empty($story->stages) && isset($fields['stage']['options']))
  24. {
  25. foreach($story->stages as $branch => $stage)
  26. {
  27. $position = strpos(",{$stageList},", ",{$stage},");
  28. if($position !== false && $position > $minStagePos)
  29. {
  30. $minStage = $stage;
  31. $minStagePos = $position;
  32. }
  33. }
  34. }
  35. if(!empty($story->files))
  36. {
  37. foreach($story->files as $file) $file->extra = $story->version;
  38. }
  39. if($story->type == 'story')
  40. {
  41. unset($fields['stage']['options']['delivered']);
  42. unset($fields['stage']['options']['delivering']);
  43. }
  44. if($app->tab == 'product') data('activeMenuID', $story->type);
  45. jsVar('storyType', $story->type);
  46. jsVar('storyID', $story->id);
  47. jsVar('storyStatus', $story->status);
  48. jsVar('isParent', $story->isParent);
  49. jsVar('oldProductID', $story->product);
  50. jsVar('oldGrade', $story->grade);
  51. jsVar('oldParent', $story->parent);
  52. jsVar('lastReviewer', explode(',', $lastReviewer));
  53. jsVar('reviewedBy', explode(',', trim($story->reviewedBy, ',')));
  54. jsVar('defaultReviewer', array_keys($fields['reviewer']['options']));
  55. jsVar('storyReviewers', $storyReviewers);
  56. jsVar('reviewerNotEmpty', $lang->story->notice->reviewerNotEmpty);
  57. jsVar('notDeleted', $lang->story->notice->notDeleted);
  58. jsVar('twins', $story->twins);
  59. jsVar('relievedTwinsTip', $lang->story->relievedTwinsTip);
  60. jsVar('changeProductTips', $lang->story->changeProductTips);
  61. jsVar('moveChildrenTips', $lang->story->moveChildrenTips);
  62. jsVar('executionID', isset($objectID) ? $objectID : 0);
  63. jsVar('langTreeManage', $lang->tree->manage);
  64. jsVar('feedbackSource', $config->story->feedbackSource);
  65. jsVar('relievedTip', $lang->story->relievedTip);
  66. detailHeader
  67. (
  68. to::prefix($lang->story->edit),
  69. to::title
  70. (
  71. entityLabel
  72. (
  73. set::level(1),
  74. set::entityID($story->id),
  75. set::reverse(true),
  76. span(setID('storyTitle'), $story->title)
  77. )
  78. )
  79. );
  80. detailBody
  81. (
  82. setID('dataform'),
  83. set::isForm(true),
  84. set::ajax(array('beforeSubmit' => jsRaw('clickSubmit'))),
  85. on::change('[name=parent]', 'loadGrade'),
  86. on::change('[name=grade]', 'checkGrade'),
  87. $canEditContent ? set::actions(array
  88. (
  89. array('btnType' => 'submit', 'class' => 'primary', 'data-status' => 'active', 'text' => $lang->save),
  90. array('btnType' => 'submit', 'class' => 'secondary', 'data-status' => 'draft', 'text' => $story->status == 'changing' ? $lang->story->doNotSubmit : $lang->story->saveDraft),
  91. isInModal() ? null : array('text' => $lang->goback, 'back' => 'APP')
  92. )) : null,
  93. sectionList
  94. (
  95. section
  96. (
  97. set::required(true),
  98. set::title($lang->story->title),
  99. inputControl
  100. (
  101. input
  102. (
  103. setClass('filter-none'),
  104. set::name('title'),
  105. set::value($fields['title']['default']),
  106. set::readonly(!$canEditContent)
  107. ),
  108. set::suffixWidth('40'),
  109. to::suffix
  110. (
  111. colorPicker
  112. (
  113. set::heading($lang->story->colorTag),
  114. set::name('color'),
  115. set::value($story->color),
  116. set::syncColor('#title, #storyTitle')
  117. )
  118. )
  119. )
  120. ),
  121. section
  122. (
  123. set::required(true),
  124. set::title($lang->story->reviewers),
  125. !$canEditContent ? set::hidden(true) : null,
  126. inputGroup
  127. (
  128. $story->status != 'reviewing' ? picker
  129. (
  130. setID('reviewer'),
  131. set::name('reviewer[]'),
  132. set::items($fields['reviewer']['options']),
  133. set::value($fields['reviewer']['default']),
  134. set::multiple(true),
  135. on::change('changeReviewer')
  136. ) : null,
  137. $forceReview ? null : span
  138. (
  139. setClass('input-group-addon'),
  140. checkbox
  141. (
  142. setID('needNotReview'),
  143. set::name('needNotReview'),
  144. set::checked(empty($fields['reviewer']['default'])),
  145. set::value(1),
  146. set::text($lang->story->needNotReview),
  147. on::change('changeNeedNotReview(e.target)')
  148. )
  149. ),
  150. formHidden('needNotReview', $forceReview ? 0 : 1)
  151. )
  152. ),
  153. section
  154. (
  155. set::title($lang->story->legendSpec),
  156. $canEditContent ? formGroup(editor(set::name('spec'), set::uid($uid), html($story->spec))) : set::content($story->spec),
  157. $canEditContent ? null : set::useHtml(true)
  158. ),
  159. section
  160. (
  161. set::title($lang->story->verify),
  162. $canEditContent ? formGroup(editor(set::name('verify'), set::uid($uid), html($story->verify))) : set::content($story->verify),
  163. $canEditContent ? null : set::useHtml(true)
  164. ),
  165. empty($twins) ? null : section
  166. (
  167. set::title($lang->story->changeSyncTip),
  168. to::suffix(set::title($lang->story->syncTip), icon('help')),
  169. h::ul
  170. (
  171. array_values(array_map(function($twin) use($story, $branches, $lang)
  172. {
  173. $branch = isset($branches[$twin->branch]) ? $branches[$twin->branch] : '';
  174. $stage = $lang->story->stageList[$twin->stage];
  175. $labelClass = $story->branch == $twin->branch ? 'primary' : '';
  176. return h::li
  177. (
  178. setClass('twins'),
  179. $branch ? label(setClass($labelClass . ' circle branch size-sm'), set::title($branch), $branch) : null,
  180. label(setClass('circle size-sm'), $twin->id),
  181. common::hasPriv('story', 'view') ? a(set::href($this->createLink('story', 'view', "id={$twin->id}")), setClass('title'), set::title($twin->title), setData(array('toggle' => 'modal')), $twin->title) : span(setClass('title'), $twin->title),
  182. label(setClass('size-sm'), set::title($stage), $stage),
  183. common::hasPriv('story', 'relieved') ? a(set::title($lang->story->relievedTwins), setClass("relievedTwins unlink size-xs"), on::click('unlinkTwins'), setData(array('id' => $twin->id)), icon('unlink')) : null
  184. );
  185. }, $twins))
  186. )
  187. ),
  188. section
  189. (
  190. setID('files'),
  191. setClass(!$canEditContent ? 'hidden' : ''),
  192. set::title($lang->story->legendAttach),
  193. $canEditContent ? fileSelector(set::defaultFiles($story->files), set::extra($story->version)) : null
  194. ),
  195. section
  196. (
  197. setID('comment'),
  198. set::title($lang->story->comment),
  199. formGroup(editor(set::name('comment'), set::uid($uid)))
  200. )
  201. ),
  202. history(set::objectID($story->id)),
  203. detailSide
  204. (
  205. set::isForm(true),
  206. tableData(setClass('mt-5'), set::title($lang->story->legendBasicInfo), !$product->shadow ? item
  207. (
  208. set::trClass(zget($fields['product'], 'className', '')),
  209. set::name($lang->story->product),
  210. row
  211. (
  212. on::change('[name=branch]')->call('loadBranch'),
  213. picker
  214. (
  215. setID('product'),
  216. set::name('product'),
  217. set::items($fields['product']['options']),
  218. set::value($fields['product']['default']),
  219. on::change('loadProduct'),
  220. set::required(true)
  221. ),
  222. span
  223. (
  224. setClass('branchIdBox'),
  225. setClass($product->type == 'normal' ? 'hidden' : ''),
  226. $product->type != 'normal' ? picker(setID('branch'), set::width('100px'), set::name('branch'), set::items($fields['branch']['options']), set::value($fields['branch']['default'])) : null
  227. )
  228. )
  229. ) : null, $story->parent > 0 && $product->type != 'normal' ? item
  230. (
  231. set::name(sprintf($lang->product->branch, $lang->product->branchName[$product->type])),
  232. picker(setID('branch'), set::name('branch'), set::items($fields['branch']['options']), set::value($fields['branch']['default']))
  233. ) : null, item
  234. (
  235. set::name($lang->story->module),
  236. modulePicker
  237. (
  238. set::items($fields['module']['options']),
  239. set::value($fields['module']['default']),
  240. set::manageLink(createLink('tree', 'browse', "rootID={$story->product}&view=story&currentModuleID=0&branch={$story->branch}"))
  241. )
  242. ), ($story->parent >= 0 && ($showGrade || $story->type != 'epic')) && !$story->twins ? item
  243. (
  244. set::trClass(zget($fields['parent'], 'className', '')),
  245. set::name($lang->story->parent),
  246. picker(setID('parent'), set::name('parent'), set::items($fields['parent']['options']), set::value($fields['parent']['default']))
  247. ) : null, $showGrade ? item
  248. (
  249. set::name($lang->story->grade),
  250. picker(setID('grade'), set::name('grade'), set::required(true), set::items($fields['grade']['options']), set::value($story->grade), set::disabled($gradeRule == 'stepwise'))
  251. ) : picker(setID('grade'), set::name('grade'), set::required(true), set::items($fields['grade']['options']), set::value($story->grade), set::hidden(true)), $showPlan ? item
  252. (
  253. set::trClass(zget($fields['plan'], 'className', '')),
  254. set::name($lang->story->plan),
  255. inputGroup
  256. (
  257. span(setID('planIdBox'), picker(set::name($multiplePlan ? 'plan[]' : 'plan'), set::items($fields['plan']['options']), set::value($fields['plan']['default']), set::multiple($multiplePlan))),
  258. empty($fields['plan']['options']) ? btn(set::url($this->createLink('productplan', 'create', "productID={$story->product}&branch={$story->branch}")), setData(array('toggle' => 'modal')), icon('plus')) : null,
  259. empty($fields['plan']['options']) ? btn(set('onclick', "loadProductPlans({$story->product}, {$story->branch})"), setClass('refresh'), icon('refresh')) : null
  260. )
  261. ) : null, item
  262. (
  263. set::trClass('sourceBox'),
  264. set::name($lang->story->source),
  265. picker(setID('source'), set::name('source'), set::items($lang->{$story->type}->sourceList), set::value($fields['source']['default']), on::change('window.toggleFeedback(e.target)'))
  266. ), item
  267. (
  268. set::trClass('sourceNoteBox'),
  269. set::name($lang->story->sourceNote),
  270. input(set::name('sourceNote'), set::value($story->sourceNote))
  271. ), item
  272. (
  273. set::name($lang->story->status),
  274. span(setClass("status-{$story->status}"), $this->processStatus('story', $story)),
  275. formHidden('status', $story->status)
  276. ), item
  277. (
  278. set::name($lang->story->stage),
  279. $story->isParent == '0' && $story->type == 'story' ? picker(setID('stage'), set::name('stage'), set::items($fields['stage']['options']), set::value($minStage)) : formHidden('stage', $story->stage),
  280. $story->isParent == '0' && $story->type == 'story' ? null : zget($fields['stage']['options'], $story->stage)
  281. ), item
  282. (
  283. set::name($lang->story->category),
  284. picker(setID('category'), set::name('category'), set::items($lang->{$story->type}->categoryList), set::value($fields['category']['default']))
  285. ), item
  286. (
  287. set::name($lang->story->pri),
  288. priPicker(set::name('pri'), set::items($lang->{$story->type}->priList), set::value($fields['pri']['default']))
  289. ), item
  290. (
  291. set::name($lang->story->estimate),
  292. $story->isParent == '0' ? input(set::name('estimate'), set::value($storyEstimate)) : $storyEstimate
  293. ), item
  294. (
  295. set::trClass(in_array($story->source, $config->story->feedbackSource) ? 'feedbackBox' : 'feedbackBox hidden'),
  296. set::name($lang->story->feedbackBy),
  297. input(set::name('feedbackBy'), set::value($story->feedbackBy))
  298. ), item
  299. (
  300. set::trClass(in_array($story->source, $config->story->feedbackSource) ? 'feedbackBox' : 'feedbackBox hidden'),
  301. set::name($lang->story->notifyEmail),
  302. input(set::name('notifyEmail'), set::value($story->notifyEmail))
  303. ), item
  304. (
  305. set::name($lang->story->keywords),
  306. input(set::name('keywords'), set::value($story->keywords))
  307. ), item
  308. (
  309. set::name($lang->story->mailto),
  310. mailto(set::items($fields['mailto']['options']), set::value($fields['mailto']['default']))
  311. )),
  312. tableData(set::title($lang->story->legendLifeTime), item
  313. (
  314. set::name($lang->story->openedBy),
  315. zget($users, $story->openedBy)
  316. ), item
  317. (
  318. set::name($lang->story->assignedTo),
  319. picker
  320. (
  321. setID('assignedTo'),
  322. set::name('assignedTo'),
  323. set::items($assignedToList),
  324. set::value($fields['assignedTo']['default'])
  325. )
  326. ), $story->status == 'reviewing' ? item
  327. (
  328. set::name($lang->story->reviewers),
  329. picker
  330. (
  331. setID('reviewer'),
  332. set::name('reviewer[]'),
  333. set::items($fields['reviewer']['options']),
  334. set::value($fields['reviewer']['default']),
  335. set::multiple(true),
  336. on::change('changeReviewer')
  337. )
  338. ) : null, $story->status == 'closed' ? item
  339. (
  340. set::name($lang->story->closedBy),
  341. picker(setID('closedBy'), set::name('closedBy'), set::items($fields['closedBy']['options']), set::value($fields['closedBy']['default']))
  342. ) : null, $story->status == 'closed' ? item
  343. (
  344. set::name($lang->story->closedReason),
  345. picker(setID('closedReason'), set::name('closedReason'), set::items($fields['closedReason']['options']), set::value($fields['closedReason']['default']), on::change('setStory'))
  346. ) : null),
  347. $story->status == 'closed' ? tableData(set::title($lang->story->legendMisc), item
  348. (
  349. set::trClass('duplicateStoryBox'),
  350. set::name($lang->story->duplicateStory),
  351. picker(setID('duplicateStory'), set::name('duplicateStory'), set::items($fields['duplicateStory']['options']), set::value($fields['duplicateStory']['default']), set::placeholder($lang->bug->placeholder->duplicate))
  352. )) : null
  353. )
  354. );
  355. render();