control.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. /**
  3. * The control file of action module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package action
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. */
  12. class action extends control
  13. {
  14. /**
  15. * 创建一个动作或者删除所有的补丁动作,此方法由Ztools使用。
  16. * Create a action or delete all patch actions, this method is used by the Ztools.
  17. *
  18. * @param string $objectType
  19. * @param string $actionType
  20. * @param string $objectName
  21. * @access public
  22. * @return void
  23. */
  24. public function create($objectType, $actionType, $objectName)
  25. {
  26. $actionID = $this->action->create($objectType, 0, $actionType, '', $objectName);
  27. if($actionID)
  28. {
  29. $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess));
  30. }
  31. else
  32. {
  33. $this->send(array('result' => 'fail', 'message' => 'error'));
  34. }
  35. }
  36. /**
  37. * 回收站。
  38. * Trash.
  39. *
  40. * @param string $browseType
  41. * @param string $type all|hidden
  42. * @param bool $byQuery
  43. * @param int $queryID
  44. * @param string $orderBy
  45. * @param int $recTotal
  46. * @param int $recPerPage
  47. * @param int $pageID
  48. * @access public
  49. * @return void
  50. */
  51. public function trash($browseType = 'all', $type = 'all', $byQuery = false, $queryID = 0, $orderBy = 'date_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  52. {
  53. /* Url存入session。 */
  54. /* Save url into session. */
  55. $this->actionZen->saveUrlIntoSession();
  56. /* 保存用于替换搜索语言项的对象名称。 */
  57. /* Save the object name used to replace the search language item. */
  58. $this->session->set('objectName', zget($this->lang->action->objectTypes, $browseType, ''), 'admin');
  59. /* 生成表单搜索数据。 */
  60. /* Build the search form. */
  61. $actionURL = $this->createLink('action', 'trash', "browseType=$browseType&type=$type&byQuery=true&queryID=myQueryID");
  62. $this->action->buildTrashSearchForm($queryID, $actionURL);
  63. /* 分页初始化。 */
  64. /* Load paper. */
  65. $this->app->loadClass('pager', true);
  66. $pager = pager::init($recTotal, $recPerPage, $pageID);
  67. /* 生成排序规则。 */
  68. /* Generate the sort rules. */
  69. $sort = common::appendOrder($orderBy);
  70. $trashes = $byQuery ? $this->action->getTrashesBySearch($browseType, $type, $queryID, $sort, $pager) : $this->action->getTrashes($browseType, $type, $sort, $pager);
  71. $objectTypeList = array_keys($this->action->getTrashObjectTypes($type));
  72. /* 获取头部模块标题导航。 */
  73. /* Build the header navigation title. */
  74. $preferredType = $this->actionZen->getTrashesHeaderNavigation($objectTypeList);
  75. /* 初始化项目、产品、执行列表。 */
  76. /* Initialize the project, product, execution list. */
  77. $projectList = array();
  78. $productList = array();
  79. $executionList = array();
  80. /* 获取执行所属的项目名称。 */
  81. /* Get the projects name of executions. */
  82. if($browseType == 'execution') $this->view->projectList = $projectList = $this->loadModel('project')->getByIdList(array_column($trashes, 'project'), 'all');
  83. /* 获取用户故事所属的产品名称。 */
  84. /* Get the products name of story. */
  85. if(in_array($browseType, array('story', 'requirement')))
  86. {
  87. $storyIdList = array();
  88. foreach($trashes as $trash) $storyIdList[] = $trash->objectID;
  89. $this->view->productList = $this->loadModel('story')->getByList($storyIdList, 'all');
  90. }
  91. /* 获取任务的执行名称。 */
  92. /* Get the executions name of task. */
  93. if($browseType == 'task') $this->view->executionList = $executionList = $this->loadModel('execution')->getByIdList(array_column($trashes, 'execution'), 'all');
  94. /* 补充操作记录的信息。 */
  95. /* Supplement the information recorded by the operation. */
  96. foreach($trashes as $trash) $this->actionZen->processTrash($trash, $projectList, $productList, $executionList);
  97. $this->view->title = $this->lang->action->trash;
  98. $this->view->trashes = $trashes;
  99. $this->view->type = $type;
  100. $this->view->currentObjectType = $browseType;
  101. $this->view->orderBy = $orderBy;
  102. $this->view->pager = $pager;
  103. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  104. $this->view->preferredType = $preferredType;
  105. $this->view->byQuery = $byQuery;
  106. $this->view->queryID = $queryID;
  107. $this->display();
  108. }
  109. /**
  110. * 恢复一个回收站对象。
  111. * Undelete an object.
  112. *
  113. * @param int $actionID
  114. * @param string $browseType
  115. * @param string $confirmChange
  116. * @access public
  117. * @return void
  118. */
  119. public function undelete($actionID, $browseType = 'all', $confirmChange = 'no')
  120. {
  121. $result = $this->actionZen->checkActionExist($actionID);
  122. if(is_array($result) && $result['result'] == 'fail') return $this->send($result);
  123. $oldAction = $result;
  124. /* 当对象类型为program、project、execution、product时,需要检查是否有重复的对象。 */
  125. /* When the object type is program, project, execution, product, you need to check if there are duplicate objects. */
  126. if(in_array($oldAction->objectType, array('program', 'project', 'execution', 'product', 'build')))
  127. {
  128. $table = $this->config->objectTables[$oldAction->objectType];
  129. list($repeatObject, $object) = $this->action->getRepeatObject($oldAction, $table);
  130. if($repeatObject)
  131. {
  132. list($replaceName, $replaceCode) = $this->actionZen->getReplaceNameAndCode($repeatObject, $object, $table);
  133. if($confirmChange == 'no')
  134. {
  135. $message = $this->actionZen->getConfirmNoMessage($repeatObject, $object, $oldAction, $replaceName, $replaceCode);
  136. if($message)
  137. {
  138. $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes");
  139. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$message}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'}); });"));
  140. }
  141. }
  142. elseif($confirmChange == 'yes')
  143. {
  144. $this->actionZen->recoverObject($repeatObject, $object, $replaceName, $replaceCode, $table, $oldAction);
  145. }
  146. }
  147. if($oldAction->objectType == 'execution')
  148. {
  149. $confirmLang = $this->actionZen->restoreStages($oldAction, $confirmChange);
  150. $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes");
  151. if($confirmLang !== true) return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$confirmLang}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'});});"));
  152. }
  153. }
  154. if($oldAction->objectType == 'module' && $confirmChange == 'no')
  155. {
  156. $module = $this->loadModel('tree')->getById($oldAction->objectID);
  157. $repeatName = $this->tree->checkUnique($module);
  158. if($module->type == 'doc' && $module->parent > 0 && !$repeatName)
  159. {
  160. $parents = $this->tree->getParents($oldAction->objectID, true);
  161. $isDeletedParent = false;
  162. foreach($parents as $parent)
  163. {
  164. if($parent->id == $oldAction->objectID) continue;
  165. if($parent->deleted)
  166. {
  167. $isDeletedParent = true;
  168. break;
  169. }
  170. }
  171. if($isDeletedParent)
  172. {
  173. $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes");
  174. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$this->lang->action->undeleteModuleTip}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'});});"));
  175. }
  176. }
  177. }
  178. elseif($oldAction->objectType == 'task' && $confirmChange == 'no')
  179. {
  180. $task = $this->loadModel('task')->getById($oldAction->objectID);
  181. $isDeleted = $this->dao->select('deleted')->from(TABLE_EXECUTION)->where('id')->eq($task->execution)->fetch('deleted');
  182. $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes");
  183. if($isDeleted) return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$this->lang->action->undeleteTaskTip}'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'});});"));
  184. }
  185. elseif($oldAction->objectType == 'board')
  186. {
  187. $board = $this->loadModel('board')->getCanvas($oldAction->objectID);
  188. $isDeleted = $this->dao->select('deleted')->from(TABLE_BOARD_LIB)->where('id')->eq($board->lib)->fetch('deleted');
  189. $url = $this->createLink('action', 'trash', "browseType=boardspace");
  190. if($isDeleted) return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$this->lang->action->undeleteBoardTip}'}).then((res) => {if(res) loadPage('{$url}');});"));
  191. }
  192. elseif($oldAction->objectType == 'doctemplate')
  193. {
  194. $templateScopeAndType = $this->dao->select('id,lib,module')->from(TABLE_DOC)->where('id')->eq($oldAction->objectID)->fetchAll('id');
  195. $scopeID = zget($templateScopeAndType[$oldAction->objectID], 'lib', 0);
  196. $typeID = zget($templateScopeAndType[$oldAction->objectID], 'module', 0);
  197. if($confirmChange == 'no')
  198. {
  199. $isDeletedType = $this->dao->select('deleted')->from(TABLE_MODULE)->where('type')->eq('docTemplate')->andWhere('id')->eq($typeID)->fetch('deleted');
  200. $isDeletedScope = $this->dao->select('deleted')->from(TABLE_DOCLIB)->where('type')->eq('template')->andWhere('id')->eq($scopeID)->fetch('deleted');
  201. if($isDeletedType == '1' || $isDeletedScope == '1')
  202. {
  203. $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes");
  204. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$this->lang->action->undeleteTemplateTip}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'});});"));
  205. }
  206. }
  207. if($confirmChange == 'yes')
  208. {
  209. $this->dao->update(TABLE_DOCLIB)->set('deleted')->eq('0')->where('id')->eq($scopeID)->exec();
  210. $this->dao->update(TABLE_MODULE)->set('deleted')->eq('0')->where('id')->eq($typeID)->exec();
  211. $parentType = $this->dao->select('parent')->from(TABLE_MODULE)->where('id')->eq($typeID)->fetch('parent');
  212. if(!empty($parentType)) $this->dao->update(TABLE_MODULE)->set('deleted')->eq('0')->where('id')->eq($parentType)->exec();
  213. }
  214. }
  215. $result = $this->action->undelete($actionID);
  216. if(true !== $result) return $this->send(array('result' => 'fail', 'load' => array('confirm' => $result)));
  217. $extra = $oldAction->extra == actionModel::BE_HIDDEN ? 'hidden' : 'all';
  218. $sameTypeObjects = $this->action->getTrashes($oldAction->objectType, $extra, 'id_desc', null);
  219. $browseType = ($sameTypeObjects && $browseType != 'all') ? $oldAction->objectType : 'all';
  220. return $this->send(array('result' => 'success', 'load' => $this->createLink('action', 'trash', "browseType=$browseType&type=$extra")));
  221. }
  222. /**
  223. * 隐藏一个已经被删除的对象。
  224. * Hide an deleted object.
  225. *
  226. * @param int $actionID
  227. * @param string $browseType
  228. * @access public
  229. * @return void
  230. */
  231. public function hideOne($actionID, $browseType = 'all')
  232. {
  233. $result = $this->actionZen->checkActionExist($actionID);
  234. if(is_array($result) && isset($result['result']) && $result['result'] == 'fail') return $this->send($result);
  235. $this->action->hideOne($actionID);
  236. $oldAction = $result;
  237. $sameTypeObjects = $this->action->getTrashes($oldAction->objectType, 'all', 'id_desc', null);
  238. $browseType = ($sameTypeObjects && $browseType != 'all') ? $oldAction->objectType : 'all';
  239. return $this->send(array('result' => 'success', 'load' => $this->createLink('action', 'trash', "browseType={$browseType}")));
  240. }
  241. /**
  242. * 隐藏所有被删除的对象。
  243. * Hide all deleted objects.
  244. *
  245. * @param string $confirm yes|no
  246. * @access public
  247. * @return void
  248. */
  249. public function hideAll($confirm = 'no')
  250. {
  251. if($confirm == 'no')
  252. {
  253. $url = inlink('hideAll', "confirm=yes");
  254. $message = $this->lang->action->confirmHideAll;
  255. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$message}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'});});"));
  256. }
  257. $this->action->hideAll();
  258. return $this->send(array('result' => 'success', 'load' => true));
  259. }
  260. /**
  261. * 评论。
  262. * Comment.
  263. *
  264. * @param string $objectType
  265. * @param int $objectID
  266. * @access public
  267. * @return void
  268. */
  269. public function comment($objectType, $objectID)
  270. {
  271. if(!empty($_POST))
  272. {
  273. $isInZinPage = isInModal() || in_array($objectType, $this->config->action->newPageModule);
  274. /* 当评论的是任务,需判断当前用户是否拥有任务的权限。 */
  275. /* When commenting on a task, you need to determine whether the current user has the permission of the task. */
  276. if(strtolower($objectType) == 'task')
  277. {
  278. $task = $this->loadModel('task')->getById($objectID);
  279. $executions = explode(',', $this->app->user->view->sprints);
  280. if(!in_array($task->execution, $executions)) return $this->send(array('result' => 'fail', 'message' => $this->lang->error->accessDenied));
  281. }
  282. /* 当评论的是用户故事,需判断当前用户是否有此用户故事的权限。 */
  283. /* When commenting on a story, you need to determine whether the current user has the permission of the story. */
  284. elseif(strtolower($objectType) == 'story')
  285. {
  286. $story = $this->loadModel('story')->getById($objectID);
  287. $executions = explode(',', $this->app->user->view->sprints);
  288. $products = explode(',', $this->app->user->view->products);
  289. if(!array_intersect(array_keys($story->executions), $executions) && !in_array($story->product, $products) && empty($story->lib)) return $this->send(array('result' => 'fail', 'message' => $this->lang->error->accessDenied));
  290. }
  291. /* 获取评论内容并生成一条action数据。 */
  292. $commentData = form::data($this->config->action->form->comment)
  293. ->setIF($this->post->comment, 'actioncomment', $this->post->comment)
  294. ->get();
  295. if(!$commentData->actioncomment && $_FILES)
  296. {
  297. $files = array_filter($_FILES['files']['name']);
  298. if(!empty($files)) $commentData->actioncomment = implode(',', $files);
  299. }
  300. if($commentData->actioncomment)
  301. {
  302. $actionID = $this->action->create($objectType, $objectID, 'Commented', $commentData->actioncomment);
  303. if(empty($actionID))
  304. {
  305. if($isInZinPage) return $this->send(array('result' => 'fail', 'message' => $this->lang->error->accessDenied));
  306. return print(js::error($this->lang->error->accessDenied));
  307. }
  308. if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $actionID));
  309. }
  310. if($isInZinPage) return $this->send(array('status' => 'success', 'closeModal' => true, 'callback' => array('name' => 'zui.HistoryPanel.update', 'params' => array('objectType' => $objectType, 'objectID' => (int)$objectID))));
  311. /* 用于旧页面。*/
  312. /* For oldPage. */
  313. return print(js::reload('parent'));
  314. }
  315. $this->view->title = $this->lang->action->create;
  316. $this->view->objectType = $objectType;
  317. $this->view->objectID = $objectID;
  318. $this->display();
  319. }
  320. /**
  321. * 编辑评论。
  322. * Edit comment.
  323. *
  324. * @param int $actionID
  325. * @access public
  326. * @return void
  327. */
  328. public function editComment($actionID)
  329. {
  330. $action = $this->action->getById($actionID);
  331. if(!empty($_POST))
  332. {
  333. /* 获取表单内的数据。 */
  334. /* Get form data. */
  335. $commentData = form::data($this->config->action->form->editComment)->get();
  336. $error = false;
  337. /* 判断是否符合更新的条件。 */
  338. /* Determine whether the update conditions are met. */
  339. if(strlen(trim(strip_tags($commentData->lastComment, '<img>'))) != 0)
  340. {
  341. $error = $this->action->updateComment($actionID, $commentData);
  342. }
  343. if(!$error)
  344. {
  345. /* 不符合更新条件,返回错误。 */
  346. /* The update conditions are not met and an error is returned. */
  347. dao::$errors['submit'][] = $this->lang->action->historyEdit;
  348. return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  349. }
  350. $action = $this->action->getById($actionID);
  351. if(isInModal() || in_array($action->objectType, $this->config->action->newPageModule))
  352. {
  353. return $this->send(array('status' => 'success', 'closeModal' => true, 'callback' => array('name' => 'zui.HistoryPanel.update', 'params' => array('objectType' => $action->objectType, 'objectID' => (int)$action->objectID))));
  354. }
  355. return $this->send(array('result' => 'success', 'locate' => 'reload'));
  356. }
  357. $action = $this->loadModel('file')->replaceImgURL($action, 'comment');
  358. $this->view->title = $this->lang->action->editComment;
  359. $this->view->actionID = $actionID;
  360. $this->view->objectType = $action->objectType;
  361. $this->view->comment = $this->action->formatActionComment($action->comment);
  362. $this->view->files = $this->file->getByObject('comment', $actionID);
  363. $this->display();
  364. }
  365. /**
  366. * 通过 Ajax 获取操作记录列表。
  367. * Get action list by ajax.
  368. *
  369. * @param string $objectType
  370. * @param int $objectID
  371. * @access public
  372. * @return void
  373. */
  374. public function ajaxGetList($objectType, $objectID)
  375. {
  376. $this->app->loadLang($objectType);
  377. $actions = $this->action->getList($objectType, $objectID);
  378. $actions = $this->action->buildActionList($actions);
  379. return $this->send($actions);
  380. }
  381. /**
  382. * Clear dynamic records older than one month.
  383. *
  384. * @access public
  385. * @return void
  386. */
  387. public function cleanActions()
  388. {
  389. $this->action->cleanActions();
  390. }
  391. /**
  392. * 通过 Ajax 获取该日期的更多记录。
  393. * Get more actions by ajax.
  394. *
  395. * @param int $lastActionID
  396. * @access public
  397. * @return void
  398. */
  399. public function ajaxGetMoreActions($lastActionID)
  400. {
  401. $actions = $this->action->getMoreActions($lastActionID);
  402. $lastAction = end($actions);
  403. $this->view->actions = $actions;
  404. $this->view->hasMore = $lastAction ? $this->action->hasMoreAction($lastAction) : false;
  405. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  406. $this->display();
  407. }
  408. }