view.html.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <?php
  2. /**
  3. * The view view file of todo 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 Mengyi Liu <liumengyi@easycorp.ltd>
  7. * @package todo
  8. * @link https://www.zentao.net
  9. */
  10. namespace zin;
  11. jsVar('selectProduct', $lang->todo->selectProduct);
  12. jsVar('selectExecution', $lang->execution->selectExecution);
  13. jsVar('todoID', $todo->id);
  14. jsVar('vision', $config->vision);
  15. $isInModal = isAjaxRequest('modal');
  16. /* Generate title suffix for bug,task,story type. */
  17. $fnGenerateTitleSuffix = function() use($todo)
  18. {
  19. if($todo->type == 'bug') return btn(set::url(createLink('bug', 'view', "id={$todo->objectID}")), set::text(' BUG#' . $todo->objectID), setClass('ghost'));
  20. if($todo->type == 'task') return btn(set::url(createLink('task', 'view', "id={$todo->objectID}")), set::text(' TASK#' . $todo->objectID), setClass('ghost'));
  21. if($todo->type == 'story') return btn(set::url(createLink('story', 'view', "id={$todo->objectID}")), set::text(' STORY#' . $todo->objectID), setClass('ghost'));
  22. };
  23. /* Render modal for creating story. */
  24. if(hasPriv('story', 'create') && $config->vision != 'lite')
  25. {
  26. modal
  27. (
  28. setID('productModal'),
  29. set::modalProps(array('title' => $lang->product->select)),
  30. empty($products) ? div
  31. (
  32. setClass('text-center pb-8 mt-2'),
  33. span($lang->product->noProduct),
  34. btn
  35. (
  36. setID('createProductBtn'),
  37. setClass('secondary-pale'),
  38. set::icon('plus'),
  39. set::url($this->createLink('product', 'create')),
  40. setData(array('toggle' => 'modal', 'size' => 'lg')),
  41. $lang->product->create
  42. )
  43. ) : form
  44. (
  45. setClass('mt-2'),
  46. set::actions(array()),
  47. formGroup
  48. (
  49. inputGroup
  50. (
  51. picker
  52. (
  53. set::name('product'),
  54. set::items($products),
  55. set::required(true)
  56. ),
  57. btn
  58. (
  59. setID('toStoryButton'),
  60. setClass('primary'),
  61. $lang->todo->reasonList['story'],
  62. on::click('toStory')
  63. )
  64. )
  65. )
  66. )
  67. );
  68. }
  69. if(hasPriv('story', 'create') && $config->vision == 'lite')
  70. {
  71. modal
  72. (
  73. setID('projectModal'),
  74. set::modalProps(array('title' => $lang->project->select)),
  75. form
  76. (
  77. setClass('mt-2'),
  78. set::actions(array()),
  79. formGroup
  80. (
  81. inputGroup
  82. (
  83. picker
  84. (
  85. set::name('projectToStory'),
  86. set::items($projects),
  87. set::required(true)
  88. ),
  89. btn
  90. (
  91. setID('toStoryButton'),
  92. setClass('primary'),
  93. $lang->todo->reasonList['story'],
  94. on::click('toStory')
  95. )
  96. )
  97. )
  98. )
  99. );
  100. }
  101. /* Render modal for creating task. */
  102. if(hasPriv('task', 'create'))
  103. {
  104. modal
  105. (
  106. setID('executionModal'),
  107. set::modalProps(array('title' => $lang->execution->selectExecution)),
  108. to::footer
  109. (
  110. div
  111. (
  112. setClass('toolbar gap-4 w-full justify-center'),
  113. btn($lang->todo->reasonList['task'], setID('toTaskButton'), setClass('primary')),
  114. on::click('toTask')
  115. )
  116. ),
  117. form
  118. (
  119. setClass('mt-2'),
  120. set::actions(array()),
  121. formGroup
  122. (
  123. set::label($lang->todo->project),
  124. picker
  125. (
  126. on::change('getExecutionByProject'),
  127. set::name('project'),
  128. set::items($projects),
  129. set::required(true)
  130. )
  131. ),
  132. formGroup
  133. (
  134. set::label($lang->todo->execution),
  135. picker
  136. (
  137. set::name('execution'),
  138. set::items($executions),
  139. set::required(true)
  140. )
  141. )
  142. )
  143. );
  144. }
  145. /* Render modal for creating bug. */
  146. if(hasPriv('bug', 'create'))
  147. {
  148. modal
  149. (
  150. setID('projectProductModal'),
  151. set::modalProps(array('title' => $lang->product->select)),
  152. to::footer
  153. (
  154. div
  155. (
  156. setClass('toolbar gap-4 w-full justify-center'),
  157. btn($lang->todo->reasonList['bug'], setID('toBugButton'), setClass('primary')),
  158. on::click('toBug')
  159. )
  160. ),
  161. form
  162. (
  163. setClass('mt-2'),
  164. set::actions(array()),
  165. formGroup
  166. (
  167. set::label($lang->todo->project),
  168. picker
  169. (
  170. set::name('bugProject'),
  171. set::items($projects),
  172. set::required(true),
  173. on::change('getProductByProject')
  174. )
  175. ),
  176. formGroup
  177. (
  178. set::label($lang->todo->product),
  179. picker
  180. (
  181. set::name('bugProduct'),
  182. set::items($projectProducts),
  183. set::required(true)
  184. )
  185. )
  186. )
  187. );
  188. }
  189. /* Generate goback url. */
  190. $fnGenerateGoBackUrl = function() use ($app, $todo, $user)
  191. {
  192. if($this->session->todoList)
  193. {
  194. $browseLink = empty($todo->deleted) ? $this->session->todoList : $this->createLink('action', 'trash');
  195. }
  196. elseif($todo->account == $app->user->account)
  197. {
  198. $browseLink = $this->createLink('my', 'todo');
  199. }
  200. else
  201. {
  202. $browseLink = $this->createLink('user', 'todo', "userID=$user->id");
  203. }
  204. return $browseLink;
  205. };
  206. /* Generate action buttons and related menus within float toolbar. */
  207. $fnGenerateFloatToolbarBtns = function() use ($lang, $config, $todo, $projects, $isInModal, $fnGenerateGoBackUrl)
  208. {
  209. /* Deleted item without action buttons. */
  210. if($todo->deleted) return array();
  211. /* Verify privilege of current account. */
  212. if(!$this->app->user->admin && $this->app->user->account != $todo->account && $this->app->user->account != $todo->assignedTo) return array();
  213. /* Prepare variables for verifying. */
  214. $status = $todo->status;
  215. $canStart = hasPriv('todo', 'start');
  216. $canActivate = hasPriv('todo', 'activate');
  217. $canClose = hasPriv('todo', 'close');
  218. $canEdit = hasPriv('todo', 'edit');
  219. $canDelete = hasPriv('todo', 'delete');
  220. $actionList = array('prefix' => array(), 'main' => array(), 'suffix' => array());
  221. !$isInModal && $actionList['prefix'][] = array('icon' => 'back', 'url' => $fnGenerateGoBackUrl(), 'hint' => $lang->goback . $lang->backShortcutKey, 'text' => $lang->goback);
  222. /* Common action buttons. */
  223. $canStart && $status == 'wait' ? $actionList['main'][] = array('icon' => 'play', 'url' => createLink('todo', 'start', "todoID={$todo->id}"), 'text' => $lang->todo->abbr->start, 'class' => 'ajax-submit') : null;
  224. $canActivate && ($status == 'done' || $status == 'closed') ? $actionList['main'][] = array('icon' => 'magic', 'url' => createLink('todo', 'activate', "todoID={$todo->id}"), 'text' => $lang->activate, 'class' => 'ajax-submit') : null;
  225. $canClose && $status == 'done' ? $actionList['main'][] = array('icon' => 'off', 'url' => createLink('todo', 'close', "todoID={$todo->id}"), 'text' => $lang->close) : null;
  226. $canEdit ? $actionList['main'][] = array('icon' => 'edit', 'url' => createLink('todo', 'edit', "todoID={$todo->id}"), 'data-load' => $isInModal ? 'modal' : null, 'text' => $lang->edit) : null;
  227. $canDelete ? $actionList['main'][] = array('icon' => 'trash', 'url' => createLink('todo', 'delete', "todoID={$todo->id}&confirm=yes"), 'text' => $lang->delete, 'class' => 'ajax-submit', 'data-confirm' => $lang->todo->confirmDelete) : null;
  228. /* The status is 'done' or 'closed' without more action buttons. */
  229. if($status == 'done' || $status == 'closed') return $actionList;
  230. $actionList['main'][] = array('icon' => 'checked', 'url' => createLink('todo', 'finish', "todoID={$todo->id}"), 'text' => $lang->todo->abbr->finish, 'class' => 'ajax-submit');
  231. $canCreateStory = hasPriv('story', 'create');
  232. $canCreateTask = hasPriv('task', 'create');
  233. $canCreateBug = hasPriv('bug', 'create') && $config->vision != 'lite';
  234. $printBtn = $config->vision == 'lite' && empty($projects) ? false : true;
  235. /* Render more button. */
  236. if($printBtn && ($canCreateStory || $canCreateTask || $canCreateBug))
  237. {
  238. $actionList['suffix'][] = array('url' => '#navActions', 'text' => $lang->todo->transform, 'data-toggle' => 'dropdown', 'data-placement' => 'top-end', 'caret' => 'up');
  239. }
  240. /* Popup menu of more button. */
  241. $storyTarget = $canCreateStory && $config->vision == 'lite' ? '#projectModal' : '#productModal';
  242. $suffixItems = array();
  243. if($canCreateStory) $suffixItems[] = array('text' => $lang->todo->reasonList['story'], 'id' => 'toStoryLink', 'data-url' => '###', 'data-toggle' => 'modal', 'data-target' => $storyTarget, 'data-backdrop' => false, 'data-moveable' => true, 'data-position' => 'center', 'data-size' => 'sm');
  244. if($canCreateTask) $suffixItems[] = array('text' => $lang->todo->reasonList['task'], 'id' => 'toTaskLink', 'data-url' => '###', 'data-toggle' => 'modal', 'data-target' => '#executionModal', 'data-backdrop' => false, 'data-moveable' => true, 'data-position' => 'center', 'data-size' => 'sm');
  245. if($canCreateBug) $suffixItems[] = array('text' => $lang->todo->reasonList['bug'], 'id' => 'toBugLink', 'data-url' => '###', 'data-toggle' => 'modal', 'data-target' => '#projectProductModal', 'data-backdrop' => false, 'data-moveable' => true, 'data-position' => 'center', 'data-size' => 'sm');
  246. menu
  247. (
  248. setID('navActions'),
  249. setClass('menu dropdown-menu'),
  250. set::items($suffixItems)
  251. );
  252. return $actionList;
  253. };
  254. $actionList = $fnGenerateFloatToolbarBtns();
  255. /* Generate from data and item. */
  256. $fnGenerateFrom = function() use ($app, $lang, $config, $todo)
  257. {
  258. if(!in_array($todo->type, array('story', 'task', 'bug')) || empty($todo->object)) return array(null, null);
  259. /* Generate from data. */
  260. $app->loadLang($todo->type);
  261. $objectData = array();
  262. foreach($config->todo->related[$todo->type]['title'] as $index => $relatedTitle)
  263. {
  264. $content = zget($todo->object, $config->todo->related[$todo->type]['content'][$index], '');
  265. $objectData[] = item
  266. (
  267. set::title($lang->{$todo->type}->{$relatedTitle}),
  268. empty($content) ? $lang->noData : html($content)
  269. );
  270. }
  271. $fromItemData = section
  272. (
  273. set::title(zget($lang->todo->fromList, $todo->type)),
  274. sectionCard
  275. (
  276. entityLabel
  277. (
  278. set::entityID($todo->objectID),
  279. set::text($todo->name)
  280. ),
  281. $objectData
  282. )
  283. );
  284. /* Generate from item. */
  285. $fromItem = item
  286. (
  287. set::name(zget($lang->todo->fromList, $todo->type)),
  288. a
  289. (
  290. set::href(createLink($todo->type, 'view', "id={$todo->objectID}", '', false)),
  291. setData
  292. (
  293. array
  294. (
  295. 'toggle' => 'modal',
  296. 'data-type' => 'html',
  297. 'type' => 'ajax'
  298. )
  299. ),
  300. $todo->name
  301. )
  302. );
  303. return array($fromItem, $fromItemData);
  304. };
  305. list($fromItem, $fromItemData) = $fnGenerateFrom();
  306. /* Generate cycle configuration information. */
  307. $todo->config = json_decode($todo->config);
  308. $fnGenerateCycleCfg = function() use ($lang, $todo)
  309. {
  310. $cfg = '';
  311. if($todo->config->type == 'day')
  312. {
  313. if(isset($todo->config->day)) $cfg .= $lang->todo->every . $todo->config->day . $lang->day;
  314. if(isset($todo->config->specifiedDate))
  315. {
  316. $specifiedNotes = $lang->todo->specify;
  317. if(isset($todo->config->cycleYear)) $specifiedNotes .= $lang->todo->everyYear;
  318. $specifiedNotes .= zget($lang->datepicker->monthNames, $todo->config->specify->month) . $todo->config->specify->day . $lang->todo->day;
  319. $cfg .= $specifiedNotes;
  320. }
  321. }
  322. elseif($todo->config->type == 'week')
  323. {
  324. foreach(explode(',', $todo->config->week) as $week) $cfg .= $lang->todo->dayNames[$week] . ' ';
  325. }
  326. elseif($todo->config->type == 'month')
  327. {
  328. foreach(explode(',', $todo->config->month) as $month) $cfg .= $month . ' ';
  329. }
  330. $cfg .= '<br />';
  331. if($todo->config->beforeDays) $cfg .= sprintf($lang->todo->lblBeforeDays, $todo->config->beforeDays);
  332. return $cfg;
  333. };
  334. /* ZIN: layout. */
  335. detailHeader
  336. (
  337. $isInModal ? to::prefix('') : '',
  338. to::title
  339. (
  340. entityLabel
  341. (
  342. set::entityID($todo->id),
  343. set::level(1),
  344. set::text($todo->name)
  345. ),
  346. $todo->deleted ? span(setClass('label danger circle'), $lang->todo->deleted) : null
  347. )
  348. );
  349. detailBody
  350. (
  351. sectionList
  352. (
  353. section
  354. (
  355. set::title($lang->todo->desc),
  356. set::content(nl2br($todo->desc)),
  357. set::useHtml(true),
  358. to::actions($fnGenerateTitleSuffix())
  359. ),
  360. $fromItemData,
  361. history(),
  362. /* Render float toolbar. */
  363. $actionList ? center(floatToolbar(set($actionList))) : null
  364. ),
  365. detailSide
  366. (
  367. /* Basic information. */
  368. tabs(set::collapse(true), tabPane
  369. (
  370. set::key('legendBasic'),
  371. set::title($lang->todo->legendBasic),
  372. set::active(true),
  373. tableData
  374. (
  375. item(set::name($lang->todo->pri), priLabel($todo->pri, set::text($lang->todo->priList))),
  376. item(set::name($lang->todo->status), zget($lang->todo->statusList, $todo->status)),
  377. item(set::name($lang->todo->type), zget($lang->todo->typeList, $todo->type)),
  378. $fromItem,
  379. item(set::name($lang->todo->account), zget($users, $todo->account)),
  380. item(set::name($lang->todo->date), formatTime($todo->date, DT_DATE1)),
  381. item(set::name($lang->todo->beginAndEnd), isset($times[$todo->begin]) ? $times[$todo->begin] : '', isset($times[$todo->end]) ? ' ~ ' . $times[$todo->end] : ''),
  382. !isset($todo->assignedTo) ? null : item(set::name($lang->todo->assignedTo), zget($users, $todo->assignedTo)),
  383. !isset($todo->assignedTo) ? null : item(set::name($lang->todo->assignedBy), zget($users, $todo->assignedBy)),
  384. !isset($todo->assignedTo) ? null : item(set::name($lang->todo->assignedDate), formatTime($todo->assignedDate, DT_DATE1))
  385. )
  386. )),
  387. /* Cycle information. */
  388. $todo->cycle ? tabs(set::collapse(true), tabPane
  389. (
  390. set::key('cycle'),
  391. set::title($lang->todo->cycle),
  392. set::active(true),
  393. tableData
  394. (
  395. item(set::name($lang->todo->beginAndEnd), $todo->config->begin . " ~ " . zget($todo->config, 'end', '')),
  396. item(set::name($lang->todo->cycleConfig), html($fnGenerateCycleCfg()))
  397. )
  398. )) : null
  399. )
  400. );
  401. render();