control.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. /**
  3. * The control file of tree 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 tree
  9. * @version $Id: control.php 5002 2013-07-03 08:25:39Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. class tree extends control
  13. {
  14. const NEW_CHILD_COUNT = 5;
  15. /**
  16. * 模块维护。
  17. * Module browse.
  18. *
  19. * @param int $rootID
  20. * @param string $viewType story|bug|case|doc
  21. * @param int $currentModuleID
  22. * @param string $branch
  23. * @param string $from
  24. * @access public
  25. * @return void
  26. */
  27. public function browse($rootID, $viewType, $currentModuleID = 0, $branch = 'all', $from = '')
  28. {
  29. $this->updateBrowseLang($viewType);
  30. $this->updateRawModule($rootID, $viewType);
  31. /* 可以维护模块的类型:story, bug, case, feedback, caselib, ticket, line, 另外 doc, api在列表页面维护。*/
  32. $this->app->loadLang('host');
  33. $root = $this->treeZen->setRoot($rootID, $viewType, $branch);
  34. if($viewType == 'story')
  35. {
  36. $products = $this->loadModel('product')->getPairs('', 0, '', 'all');
  37. $this->session->set('product', $rootID, $this->app->tab);
  38. $this->lang->modulePageNav = '';
  39. unset($products[$rootID]);
  40. $currentProduct = (int)key($products);
  41. $this->view->allProduct = $products;
  42. $this->view->currentProduct = $currentProduct;
  43. $this->view->productModules = $this->tree->getOptionMenu($currentProduct, 'story');
  44. }
  45. /* 无产品项目也可以维护模块。 */
  46. if($this->app->tab == 'project')
  47. {
  48. $this->loadModel('project')->setMenu($this->session->project);
  49. $this->view->projectID = $this->session->project;
  50. }
  51. /* 无产品迭代也可以维护模块。 */
  52. if($this->app->tab == 'execution')
  53. {
  54. $this->loadModel('execution')->setMenu($this->session->execution);
  55. $menuType = $viewType == 'case' ? 'testcase' : $viewType;
  56. if(isset($this->lang->execution->menu->qa['subMenu']->{$menuType})) $this->lang->execution->menu->qa['subMenu']->{$menuType}['subModule'] = 'tree';
  57. if($menuType == 'story' && isset($this->lang->execution->menu->story)) $this->lang->execution->menu->story['subModule'] = 'tree';
  58. $this->view->executionID = $this->session->execution;
  59. }
  60. if($viewType == 'feedback' or $viewType == 'ticket')
  61. {
  62. $syncConfig = json_decode($this->config->global->syncProduct, true);
  63. $this->view->syncConfig = isset($syncConfig[$viewType]) ? $syncConfig[$viewType] : array();
  64. $this->view->productID = $rootID;
  65. }
  66. /* 获取产品的分支。 Get branches of product. */
  67. $branches = ($root->rootType == 'product' && $root->type != 'normal') ? $this->loadModel('branch')->getPairs($root->id, 'withClosed') : array();
  68. $isFlowModule = false;
  69. if($this->config->edition != 'open')
  70. {
  71. $flow = $this->loadModel('workflow')->getByModule($from);
  72. $isFlowModule = !empty($flow);
  73. }
  74. if($isFlowModule) $this->config->excludeDropmenuList[] = "{$from}-browse";
  75. $this->view->title = $viewType == 'host' ? $this->lang->host->groupMaintenance : $this->lang->tree->manage;
  76. $this->view->rootID = $root->id;
  77. $this->view->root = $root;
  78. $this->view->productID = $root->rootType == 'product' ? $root->id : 0;
  79. $this->view->branches = $branches;
  80. $this->view->libID = $root->rootType == 'caselib' || $root->rootType == 'lib' ? $root->id : 0;
  81. $this->view->viewType = $viewType;
  82. $this->view->placeholder = $viewType == 'host' ? $this->lang->tree->groupName : $this->lang->tree->name;
  83. $this->view->sons = $this->tree->getSons($rootID, $currentModuleID, $viewType, $branch);
  84. $this->view->currentModuleID = $currentModuleID;
  85. $this->view->parentModules = $this->tree->getParents($currentModuleID);
  86. $this->view->branch = $branch;
  87. $this->view->from = $from;
  88. $this->view->tree = $this->tree->getProductStructure($rootID, $viewType, $branch);
  89. $this->view->canBeChanged = common::canModify($root->rootType, $root);
  90. $this->view->isFlowModule = $isFlowModule;
  91. $this->display();
  92. }
  93. /**
  94. * 任务模块维护。
  95. * Browse task module.
  96. *
  97. * @param int $rootID
  98. * @param int $productID
  99. * @param int $currentModuleID
  100. * @access public
  101. * @return void
  102. */
  103. public function browseTask($rootID, $productID = 0, $currentModuleID = 0)
  104. {
  105. $this->lang->navGroup->tree = 'execution';
  106. /* Get execution. */
  107. $execution = $this->loadModel('execution')->getById($rootID);
  108. $this->view->root = $execution;
  109. /* Get all associated products. */
  110. $products = $this->loadModel('product')->getProducts($rootID);
  111. $this->view->products = $products;
  112. $executions = $this->execution->getPairs((int)$this->session->project);
  113. /* Set menu. */
  114. $this->execution->setMenu($rootID);
  115. $this->lang->tree->menu = $this->lang->execution->menu;
  116. $this->lang->tree->menuOrder = $this->lang->execution->menuOrder;
  117. if($this->app->tab == 'project') $this->view->projectID = $rootID;
  118. unset($executions[$rootID]);
  119. $parentModules = $this->tree->getParents($currentModuleID);
  120. $newModule = (version_compare($execution->openedVersion, '4.1', '>') and $products) ? true : false;
  121. $title = $execution->multiple ? $this->lang->tree->manageExecution : $this->lang->tree->manageProject;
  122. $this->view->title = $title;
  123. $this->view->rootID = $rootID;
  124. $this->view->productID = $productID;
  125. $this->view->execution = $execution;
  126. $this->view->allProject = $executions;
  127. $this->view->newModule = $newModule;
  128. $this->view->sons = $this->tree->getTaskSons($rootID, $productID, $currentModuleID);
  129. $this->view->parentModules = $parentModules;
  130. $this->view->currentModuleID = $currentModuleID;
  131. $this->view->tree = $this->tree->getTaskStructure($rootID, $productID);
  132. $this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
  133. $this->display();
  134. }
  135. /**
  136. * 编辑模块。
  137. * Edit a module.
  138. *
  139. * @param int $moduleID
  140. * @param string $type
  141. * @param string $branch
  142. * @access public
  143. * @return void
  144. */
  145. public function edit($moduleID, $type, $branch = '0')
  146. {
  147. $this->app->loadLang('task');
  148. if(!empty($_POST))
  149. {
  150. if($type == 'docTemplate') $this->lang->tree->name = $this->lang->docTemplate->typeName;
  151. $this->tree->update($moduleID, $type);
  152. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  153. return $this->sendSuccess(array('closeModal' => true, 'load' => true, 'docApp' => array('load', null, null, null, array('noLoading' => true, 'picks' => 'module'))));
  154. }
  155. $module = $this->tree->getById($moduleID);
  156. if($type == 'task')
  157. {
  158. $this->view->optionMenu = $this->tree->getTaskOptionMenu($module->root);
  159. }
  160. elseif($type != 'chart')
  161. {
  162. $this->view->optionMenu = $this->tree->getOptionMenu($module->root, $module->type, 0, (string)$module->branch, 'noMainBranch|nodeleted', $type == 'docTemplate' ? '1' : 'all');
  163. }
  164. if($type == 'doc')
  165. {
  166. $docLib = $this->loadModel('doc')->getLibById((int)$module->root);
  167. $objectID = isset($docLib->{$docLib->type}) ? $docLib->{$docLib->type} : 0;
  168. $this->view->libs = $this->doc->getLibs($docLib->type, '', '', $objectID, 'book');
  169. }
  170. if($type == 'docTemplate')
  171. {
  172. $name = $this->lang->docTemplate->typeName;
  173. $title = $this->lang->docTemplate->editTemplateType;
  174. $parentLabel = $this->lang->docTemplate->parentModule;
  175. $scopes = array();
  176. $scopeList = $this->loadModel('doc')->getTemplateScopes();
  177. foreach($scopeList as $scope) $scopes[$scope->id] = $scope->name;
  178. $this->view->scopes = $scopes;
  179. }
  180. elseif($type == 'doc' || $type == 'api')
  181. {
  182. $name = $this->lang->tree->dir;
  183. $title = $this->lang->tree->editDir;
  184. $parentLabel = $this->lang->tree->parentCate;
  185. }
  186. elseif($type == 'line')
  187. {
  188. $name = $this->lang->tree->line;
  189. $title = $this->lang->tree->manageLine;
  190. $parentLabel = $this->lang->tree->parent;
  191. }
  192. else
  193. {
  194. $name = $this->lang->tree->name;
  195. $title = $this->lang->tree->edit;
  196. $parentLabel = $this->lang->tree->parent;
  197. }
  198. if($type == 'host') $this->app->loadLang('host');
  199. $this->view->name = $name;
  200. $this->view->title = $title;
  201. $this->view->title = $type == 'host' ? $this->lang->host->groupMaintenance : $title;
  202. $this->view->parentLabel = $parentLabel;
  203. $this->view->module = $module;
  204. $this->view->type = $type;
  205. $this->view->branch = $branch;
  206. $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted', $module->owner);
  207. $showProduct = strpos('story|bug|case', $type) !== false ? true : false;
  208. if($showProduct)
  209. {
  210. $product = $this->loadModel('product')->getById($module->root);
  211. $this->view->branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($module->root, 'withClosed') : array(0);
  212. $this->view->product = $product;
  213. $this->view->products = $this->product->getPairs('', $product->program);
  214. if($product->shadow) $showProduct = false;
  215. }
  216. $this->view->showProduct = $showProduct;
  217. /* Remove self and childs from the $optionMenu. Because it's parent can't be self or childs. */
  218. $childs = $this->tree->getAllChildId($moduleID);
  219. foreach($childs as $childModuleID) unset($this->view->optionMenu[$childModuleID]);
  220. if($type == 'doc') $this->view->optionMenu = array_merge(array(0 => '/'), $this->doc->buildNestedDocs(array(), $this->view->optionMenu, false));
  221. $this->display();
  222. }
  223. /**
  224. * 修复模块的path和grade字段。
  225. * Fix path, grades.
  226. *
  227. * @param int $rootID
  228. * @param string $type
  229. * @access public
  230. * @return void
  231. */
  232. public function fix($rootID, $type)
  233. {
  234. $this->tree->fixModulePath($rootID, $type);
  235. echo js::alert($this->lang->tree->successFixed) . js::reload('parent');
  236. }
  237. /**
  238. * 更新模块排序。
  239. * Update modules' orders.
  240. *
  241. * @param int $root
  242. * @param string $viewType
  243. * @param int $moduleID
  244. *
  245. * @access public
  246. * @return void
  247. * @param int $rootID
  248. */
  249. public function updateOrder($rootID = 0, $viewType = '', $moduleID = 0)
  250. {
  251. if(!empty($_POST))
  252. {
  253. $this->tree->updateOrder($_POST['orders']);
  254. if($viewType == 'story' and !empty($rootID) and !empty($moduleID)) $this->loadModel('action')->create('module', $rootID, 'moved', '', $moduleID);
  255. }
  256. }
  257. /**
  258. * 管理子模块。
  259. * Manage child modules.
  260. *
  261. * @param int $rootID
  262. * @param string $viewType
  263. * @param string $oldPage yes|no
  264. * @access public
  265. * @return void
  266. */
  267. public function manageChild($rootID, $viewType, $oldPage = 'no')
  268. {
  269. if(!empty($_POST))
  270. {
  271. $moduleIDList = $this->tree->manageChild($rootID, $viewType);
  272. if(dao::isError()) return $this->sendError(dao::getError());
  273. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $moduleIDList));
  274. if(isInModal())
  275. {
  276. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "renderModulePicker($rootID, '$viewType');"));
  277. }
  278. if($oldPage == 'yes') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload'));
  279. return $this->sendSuccess(array('load' => true));
  280. }
  281. }
  282. /**
  283. * 新增一个模块。
  284. * Create a module.
  285. *
  286. * @param int $rootID
  287. * @param string $viewType
  288. * @access public
  289. * @return void
  290. */
  291. public function create($rootID, $viewType)
  292. {
  293. if(!empty($_POST))
  294. {
  295. if(empty($_POST['name'])) return $this->sendError(array('name' => sprintf($this->lang->error->notempty, $this->lang->tree->name)));
  296. $count = $this->dao->select(`order`)->from(TABLE_MODULE)->where('type')->eq($viewType)->andWhere('root')->eq($rootID)->andWhere('parent')->eq('0')->count();
  297. $_POST['parentModuleID'] = 0;
  298. $_POST['modules'] = array($_POST['name']);
  299. $_POST['shorts'] = array($_POST['short']);
  300. $_POST['maxOrder'] = $count ? $count * 10 : 0;
  301. $this->tree->manageChild($rootID, $viewType);
  302. if(dao::isError()) return $this->sendError(dao::getError());
  303. return $this->sendSuccess(array('load' => true, 'closeModel' => true));
  304. }
  305. $this->view->title = $this->lang->tree->create;
  306. $this->display();
  307. }
  308. /**
  309. * 查看模块历史记录。
  310. * View module histories.
  311. *
  312. * @param int $productID
  313. * @access public
  314. * @return void
  315. */
  316. public function viewHistory($productID)
  317. {
  318. $this->view->productID = $productID;
  319. $this->view->actions = $this->loadModel('action')->getList('module', $productID);
  320. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  321. $this->display();
  322. }
  323. /**
  324. * 删除模块。
  325. * Delete a module.
  326. *
  327. * @param int $moduleID
  328. * @param string $confirm yes|no
  329. * @access public
  330. * @return void
  331. */
  332. public function delete($moduleID, $confirm = 'no')
  333. {
  334. if($confirm == 'no')
  335. {
  336. $module = $this->tree->getByID($moduleID);
  337. $confirmLang = $this->lang->tree->confirmDelete;
  338. if($module->type == 'doc' or $module->type == 'api') $confirmLang = $this->lang->tree->confirmDeleteMenu;
  339. if($module->type == 'line') $confirmLang = $this->lang->tree->confirmDeleteLine;
  340. if($module->type == 'host') $confirmLang = $this->lang->tree->confirmDeleteHost;
  341. if(strpos($this->config->tree->groupTypes, ",$module->type,") !== false) $confirmLang = $this->lang->tree->confirmDeleteGroup;
  342. $confirmURL = $this->createLink($this->app->rawModule, $this->app->rawMethod, "moduleID=$moduleID&confirm=yes");
  343. 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: '$confirmURL'});});"));
  344. }
  345. else
  346. {
  347. $result = $this->tree->remove($moduleID);
  348. if(!$result) return;
  349. return $this->send(array('result' => 'success', 'load' => true, 'closeModal' => true));
  350. }
  351. }
  352. /**
  353. * Ajax: 获取模块的下拉菜单。
  354. * Ajax: Get the option menu of modules.
  355. *
  356. * @param int $rootID
  357. * @param string $viewType
  358. * @param string $branch
  359. * @param int $rootModuleID
  360. * @param string $returnType
  361. * @param string $fieldID
  362. * @param string $extra
  363. * @param int $currentModuleID
  364. * @param string $grade
  365. * @access public
  366. * @return string the html select string.
  367. */
  368. public function ajaxGetOptionMenu($rootID, $viewType = 'story', $branch = 'all', $rootModuleID = 0, $returnType = 'html', $fieldID = '', $extra = 'nodeleted', $currentModuleID = 0, $grade = 'all')
  369. {
  370. if($viewType == 'task')
  371. {
  372. $optionMenu = $this->tree->getTaskOptionMenu($rootID, 0, $extra);
  373. }
  374. else
  375. {
  376. $optionMenu = $this->tree->getOptionMenu($rootID, $viewType, $rootModuleID, $branch, $extra, $grade);
  377. }
  378. if(strpos($extra, 'excludeModuleID') !== false)
  379. {
  380. list($excludeModule, $noMainBranch) = explode(',', $extra);
  381. parse_str($excludeModule, $output);
  382. $excludeModuleID = $output['excludeModuleID'];
  383. if(strpos($extra, 'excludeRelated') !== false)
  384. {
  385. $childs = $this->tree->getAllChildId((int)$excludeModuleID);
  386. foreach($childs as $childModuleID) unset($optionMenu[$childModuleID]);
  387. }
  388. else
  389. {
  390. if(isset($optionMenu[$excludeModuleID])) unset($optionMenu[$excludeModuleID]);
  391. }
  392. }
  393. if($returnType == 'items')
  394. {
  395. $this->printOptionMenuArray($optionMenu);
  396. }
  397. elseif($returnType == 'html')
  398. {
  399. $this->printOptionMenuHtml($optionMenu, $viewType, $fieldID, $currentModuleID);
  400. }
  401. elseif($returnType == 'mhtml')
  402. {
  403. $this->printOptionMenuMHtml($optionMenu, $viewType, $rootID);
  404. $changeFunc = '';
  405. if($viewType == 'bug' or $viewType == 'case') $changeFunc = "onchange='loadModuleRelated()'";
  406. if($viewType == 'task') $changeFunc = "onchange='setStories(this.value, $rootID)'";
  407. $field = $fieldID ? "modules[$fieldID]" : 'module';
  408. $output = html::select("$field", $optionMenu, '', "class='input' $changeFunc");
  409. return print($output);
  410. }
  411. elseif($returnType == 'json')
  412. {
  413. print(json_encode($optionMenu));
  414. }
  415. }
  416. /**
  417. * Ajax: 获取drop菜单。
  418. * Ajax: get drop menu.
  419. *
  420. * @param int $rootID
  421. * @param string $module
  422. * @param string $method
  423. * @param string $extra
  424. * @access public
  425. * @return void
  426. */
  427. public function ajaxGetDropMenu($rootID, $module, $method, $extra = '')
  428. {
  429. $this->view->productID = $rootID;
  430. $this->view->module = $module;
  431. $this->view->method = $method;
  432. $this->view->extra = $extra;
  433. $viewType = $module;
  434. if($module == 'bug') $viewType = 'bug';
  435. if($module == 'testcase') $viewType = 'case';
  436. $modules = $this->tree->getOptionMenu($rootID, $viewType);
  437. $modulesPinyin = common::convert2Pinyin($modules);
  438. $this->view->link = $viewType == 'caselib' ? helper::createLink($module, $method, "rootID=%s&type=byModule&param=%s") : helper::createLink($module, $method, "rootID=%s&branch=&type=byModule&param=%s");
  439. $this->view->viewType = $viewType;
  440. $this->view->modules = $modules;
  441. $this->view->modulesPinyin = $modulesPinyin;
  442. $this->display();
  443. }
  444. /**
  445. * Ajax: 获取模块列表。
  446. * Ajax: get modules.
  447. *
  448. * @param int $productID
  449. * @param string $viewType
  450. * @param string $branchID
  451. * @param int $number
  452. * @param int $currentModuleID
  453. * @param string $from showImport
  454. * @access public
  455. * @return string the html select string.
  456. */
  457. public function ajaxGetModules($productID, $viewType = 'story', $branchID = '0', $number = 0, $currentModuleID = 0, $from = '')
  458. {
  459. $currentModule = $this->tree->getById($currentModuleID);
  460. $currentModuleID = (isset($currentModule->branch) and $currentModule->branch == 0) ? $currentModuleID : 0;
  461. $modules = $this->tree->getOptionMenu($productID, $viewType, $startModuleID = 0, $branchID);
  462. $modules = empty($modules) ? array('' => '/') : $modules;
  463. if($viewType == 'bug' || $viewType == 'case')
  464. {
  465. $moduleList = array();
  466. foreach($modules as $moduleID => $moduleName) $moduleList[] = array('value' => $moduleID, 'text' => $moduleName);
  467. return $this->send(array('modules' => $moduleList, 'currentModuleID' => $currentModuleID));
  468. }
  469. if($this->viewType == 'json') return print(array('modules' => $modules, 'currentModuleID' => $currentModuleID));
  470. $moduleName = ($viewType == 'bug' and $from != 'showImport') ? "modules[$number]" : "module[$number]";
  471. echo html::select($moduleName, $modules, $currentModuleID, 'class=form-control');
  472. }
  473. /**
  474. * Ajax: 获取一个模块的子模块。
  475. * Ajax: get a module's son modules.
  476. *
  477. * @param int $moduleID
  478. * @param int $rootID
  479. * @param string $type
  480. * @access public
  481. * @return string json_encoded modules.
  482. */
  483. public function ajaxGetSonModules($moduleID, $rootID = 0, $type = 'story')
  484. {
  485. $modules = $this->dao->select('id,name,short')->from(TABLE_MODULE)
  486. ->where('root')->eq($rootID)
  487. ->andWhere('parent')->eq((int)$moduleID)
  488. ->andWhere('type')->eq($type)
  489. ->andWhere('deleted')->eq(0)
  490. ->fetchAll('id');
  491. echo json_encode($modules);
  492. }
  493. /**
  494. * Ajax: 创建模块。
  495. * Ajax: Create module.
  496. *
  497. * @access public
  498. * @return void
  499. */
  500. public function ajaxCreateModule()
  501. {
  502. if(!helper::isAjaxRequest()) return $this->send(array('result' => 'fail', 'message' => ''));;
  503. $module = $this->tree->createModule();
  504. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => implode('\n', dao::getError())));
  505. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true, 'module' => $module));
  506. }
  507. }