control.php 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870
  1. <?php
  2. /**
  3. * The control file of project module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  7. * @package project
  8. * @version $Id
  9. * @link https://www.zentao.net
  10. */
  11. class project extends control
  12. {
  13. /**
  14. * Construct
  15. *
  16. * @param string $moduleName
  17. * @param string $methodName
  18. * @param string $appName
  19. * @access public
  20. * @return void
  21. */
  22. public function __construct($moduleName = '', $methodName = '', $appName = '')
  23. {
  24. parent::__construct($moduleName, $methodName, $appName);
  25. $this->view->globalDisableProgram = $this->config->systemMode == 'light';
  26. }
  27. /**
  28. * 创建项目引导。
  29. * Project create guide.
  30. *
  31. * @param int $programID
  32. * @param string $from
  33. * @param int $productID
  34. * @param int $branchID
  35. * @access public
  36. * @return void
  37. */
  38. public function createGuide($programID = 0, $from = 'project', $productID = 0, $branchID = 0)
  39. {
  40. $this->view->from = $from;
  41. $this->view->programID = $programID;
  42. $this->view->productID = $productID;
  43. $this->view->branchID = $branchID;
  44. $this->display();
  45. }
  46. /**
  47. * 导出项目。
  48. * Export project.
  49. *
  50. * @param string $status
  51. * @param string $orderBy
  52. * @access public
  53. * @return void
  54. */
  55. public function export($status, $orderBy)
  56. {
  57. $this->lang->project->id = $this->lang->idAB;
  58. $this->lang->project->hasProduct = $this->lang->project->category;
  59. $this->lang->project->consumed = $this->lang->project->consume;
  60. if($_POST)
  61. {
  62. /* Get export field lists. */
  63. $fields = $this->post->exportFields ? $this->post->exportFields : explode(',', $this->config->project->list->exportFields);
  64. /* Process export field titie. */
  65. foreach($fields as $key => $fieldName)
  66. {
  67. $fields[$fieldName] = zget($this->lang->project, trim($fieldName));
  68. unset($fields[$key]);
  69. }
  70. if(!isset($this->config->setCode) || empty($this->config->setCode)) unset($fields['code']);
  71. if(isset($fields['hasProduct'])) $fields['hasProduct'] = $this->lang->project->type;
  72. /* Format the export project data. */
  73. $projects = $this->projectZen->formatExportProjects($status, $orderBy);
  74. /* Set export data. */
  75. if($this->config->edition != 'open') list($fields, $projects) = $this->loadModel('workflowfield')->appendDataFromFlow($fields, $projects);
  76. $this->post->set('fields', $fields);
  77. $this->post->set('rows', $projects);
  78. $this->post->set('kind', $this->lang->project->common);
  79. $this->fetch('file', 'export2' . $this->post->fileType, $_POST);
  80. }
  81. $this->view->fileName = zget(arrayUnion($this->lang->project->featureBar['index'], $this->lang->project->moreSelects), $status, '') . '.' . $this->lang->projectCommon;
  82. $this->view->customExport = true;
  83. $this->view->allExportFields = !isset($this->config->setCode) || empty($this->config->setCode) ? str_replace(',code,', ',', $this->config->project->list->exportFields) : $this->config->project->list->exportFields;
  84. $this->display();
  85. }
  86. /**
  87. * 设置1.5级项目下拉菜单。
  88. * Ajax get project drop menu.
  89. *
  90. * @param int $projectID
  91. * @param string $module
  92. * @param string $method
  93. * @access public
  94. * @return void
  95. * @param string $extra
  96. * @param int $useLink
  97. */
  98. public function ajaxGetDropMenu($projectID, $module = 'project', $method = 'index', $extra = '', $useLink = 1)
  99. {
  100. /* Set cookie for show all project. */
  101. $_COOKIE['showClosed'] = 1;
  102. $project = $this->project->fetchByID($projectID);
  103. /* Query user's project and program. */
  104. $projects = $this->project->getListByCurrentUser('*', !empty($project->isTpl) ? 'onlyTpl' : '');
  105. $involvedProjects = $this->project->getInvolvedListByCurrentUser();
  106. $programs = $this->loadModel('program')->getPairs(true);
  107. /* Generate project tree. */
  108. $orderedProjects = array();
  109. foreach($projects as $project)
  110. {
  111. $project->parent = $this->program->getTopByID($project->parent);
  112. $project->parent = isset($programs[$project->parent]) ? $project->parent : $project->id;
  113. $orderedProjects[$project->parent][] = $project;
  114. }
  115. $this->view->link = $useLink == 1 ? $this->project->getProjectLink($module, $method, $projectID, $extra) : '#'; // Create the link from module,method.
  116. $this->view->projectID = $projectID;
  117. $this->view->module = $module;
  118. $this->view->method = $method;
  119. $this->view->programs = $programs;
  120. $this->view->extra = $extra;
  121. $this->view->useLink = $useLink;
  122. $this->view->project = $project;
  123. $this->view->projects = $orderedProjects;
  124. $this->view->involvedProjects = $involvedProjects;
  125. $this->display();
  126. }
  127. /**
  128. * 旧页面:设置1.5级项目下拉菜单。
  129. * Ajax get project drop menu.
  130. *
  131. * @param int $projectID
  132. * @param string $module
  133. * @param string $method
  134. * @access public
  135. * @return void
  136. * @param string $extra
  137. */
  138. public function ajaxGetOldDropMenu($projectID, $module, $method, $extra = '')
  139. {
  140. /* Set cookie for show all project. */
  141. $_COOKIE['showClosed'] = 1;
  142. $project = $this->project->fetchByID($projectID);
  143. /* Query user's project and program. */
  144. $projects = $this->project->getListByCurrentUser('*', !empty($project->isTpl) ? 'onlyTpl' : '');
  145. $involvedProjects = $this->project->getInvolvedListByCurrentUser();
  146. $programs = $this->loadModel('program')->getPairs(true);
  147. /* Generate project tree. */
  148. $orderedProjects = array();
  149. foreach($projects as $project)
  150. {
  151. $project->parent = $this->program->getTopByID($project->parent);
  152. $project->parent = isset($programs[$project->parent]) ? $project->parent : $project->id;
  153. $orderedProjects[$project->parent][] = $project;
  154. }
  155. $this->view->link = $this->project->getProjectLink($module, $method, $projectID, $extra); // Create the link from module,method.
  156. $this->view->projectID = $projectID;
  157. $this->view->module = $module;
  158. $this->view->method = $method;
  159. $this->view->programs = $programs;
  160. $this->view->projects = $orderedProjects;
  161. $this->view->involvedProjects = $involvedProjects;
  162. $this->display();
  163. }
  164. /**
  165. * 移除项目团队成员时进行提示。
  166. * Ajax prompts when removing project team members.
  167. *
  168. * @param string $projectID
  169. * @param string $account
  170. * @access public
  171. * @return void
  172. */
  173. public function ajaxGetRemoveMemberTips($projectID, $account)
  174. {
  175. $projectID = (int)$projectID;
  176. $project = $this->project->getByID($projectID);
  177. if(!$project->multiple) return;
  178. /* 获取项目下相关执行的团队成员。*/
  179. /* Get team members of execution under the project. */
  180. $executions = $this->loadModel('execution')->getByProject($projectID, 'undone', 0, true);
  181. $executionMembers = $this->project->getExecutionMembers($account, array_keys($executions));
  182. if(empty($executionMembers)) return;
  183. /* 根据移除的成员参与执行的数量,输出对应的提示语。*/
  184. /* Output corresponding prompts based on the number of removed members participating in execution. */
  185. $count = count($executionMembers);
  186. $executionNames = $count != 0 ? current($executionMembers) : '';
  187. if($count > 1) $executionNames .= ',' . next($executionMembers);
  188. if($count <= 2) $this->lang->project->etc = ' ';
  189. if(strpos($this->app->getClientLang(), 'zh') !== false)
  190. {
  191. echo sprintf($this->lang->project->unlinkExecutionMember, $executionNames, $this->lang->project->etc, $count);
  192. }
  193. else
  194. {
  195. echo sprintf($this->lang->project->unlinkExecutionMember, $count, $executionNames, $this->lang->project->etc);
  196. }
  197. }
  198. /**
  199. * 产品关联项目时获取被关联产品的分支数量
  200. * AJAX: Get linked products with branch.
  201. *
  202. * @param string $projectID
  203. *
  204. * @access public
  205. * @return string
  206. */
  207. public function ajaxGetLinkedProducts($projectID)
  208. {
  209. $productsWithBranch = array();
  210. $linkedProducts = $this->project->getBranchesByProject((int)$projectID);
  211. foreach($linkedProducts as $productID => $branches)
  212. {
  213. foreach($branches as $branchID => $branchInfo) $productsWithBranch[$productID][$branchID] = $branchID;
  214. }
  215. echo json_encode($productsWithBranch);
  216. }
  217. /**
  218. * 选中项目或项目集时,获取项目表单数据:创建或编辑。
  219. * Ajax: Get selected object's form information: create or edit.
  220. *
  221. * @param string $objectType project|program
  222. * @param int $objectID
  223. * @param int $selectedProgramID
  224. * @access public
  225. * @return void
  226. */
  227. public function ajaxGetProjectFormInfo($objectType, $objectID, $selectedProgramID)
  228. {
  229. /* If selectedProgramID exist, get the info for the program. */
  230. $projectFormInfo = array();
  231. if($selectedProgramID)
  232. {
  233. $selectedProgram = $this->loadModel('program')->getByID($selectedProgramID);
  234. if($selectedProgram->budget) $availableBudget = $this->program->getBudgetLeft($selectedProgram);
  235. $projectFormInfo['selectedProgramBegin'] = $selectedProgram->begin;
  236. $projectFormInfo['selectedProgramEnd'] = $selectedProgram->end;
  237. $projectFormInfo['budgetUnit'] = $selectedProgram->budgetUnit;
  238. $projectFormInfo['selectedProgramPath'] = explode(',', $selectedProgram->path);
  239. }
  240. /* Get the available budget and program time range. */
  241. if(!empty($objectID))
  242. {
  243. /* Get the path of the last selected program. */
  244. $object = $objectType == 'project' ? $this->project->getByID($objectID) : $this->loadModel('program')->getByID($objectID);
  245. $projectFormInfo['objectPath'] = explode(',', $object->path);
  246. if(isset($availableBudget)) $availableBudget = $object->parent == $selectedProgramID ? $availableBudget + (int)$object->budget : $availableBudget;
  247. if($objectType == 'program')
  248. {
  249. $progranChildDate = $this->project->getProgramDate($objectID);
  250. $projectFormInfo['minChildBegin'] = isset($progranChildDate) ? $progranChildDate['minChildBegin'] : '';
  251. $projectFormInfo['maxChildEnd'] = isset($progranChildDate) ? $progranChildDate['maxChildEnd'] : '';
  252. }
  253. }
  254. if($objectType == 'program')
  255. {
  256. $withProgram = $this->config->systemMode == 'ALM' && helper::hasFeature('program') ? true : false;
  257. $allProducts = array('') + $this->loadModel('program')->getProductPairs($selectedProgramID, 'all', 'noclosed', '', 0, $withProgram);
  258. $projectFormInfo['allProducts'] = html::select('products[]', $allProducts, '', "class='form-control chosen' onchange='loadBranches(this)'");
  259. $projectFormInfo['plans'] = html::select('plans[][][]', '', '', 'class=\'form-control chosen\' multiple');
  260. }
  261. if(isset($availableBudget)) $projectFormInfo['availableBudget'] = $availableBudget;
  262. echo json_encode($projectFormInfo);
  263. }
  264. /**
  265. * 项目仪表盘。
  266. * Project dashboard view.
  267. *
  268. * @param int $projectID
  269. * @param string $browseType
  270. * @param int $recTotal
  271. * @param int $recPerPage
  272. * @param int $pageID
  273. * @access public
  274. * @return void
  275. */
  276. public function index($projectID = 0, $browseType = 'all', $recTotal = 0, $recPerPage = 15, $pageID = 1)
  277. {
  278. $projectID = $this->project->checkAccess($projectID, $this->project->getPairsByProgram());
  279. if(!$projectID)
  280. {
  281. if(is_bool($projectID)) return $this->sendError($this->lang->project->accessDenied, inLink('browse'));
  282. if(common::hasPriv('project', 'create')) $this->locate(inLink('create'));
  283. $this->locate(inLink('browse'));
  284. }
  285. $this->session->set('executionList', $this->app->getURI(true), 'execution');
  286. $project = $this->project->getByID($projectID);
  287. if(empty($project) || $project->type != 'project') return $this->sendError($this->lang->notFound, inLink('browse'));
  288. $this->project->setMenu($projectID);
  289. $this->loadModel('execution');
  290. /* Locate to task when set no execution. */
  291. if(!$project->multiple)
  292. {
  293. $executions = $this->execution->getList($project->id);
  294. foreach($executions as $execution)
  295. {
  296. if(!$execution->multiple) $this->locate($this->createLink('execution', 'task', "executionID={$execution->id}") . '#app=project');
  297. }
  298. }
  299. if($project->model != 'kanban' || $this->config->vision == 'lite') return print($this->fetch('block', 'dashboard', "dashboard={$project->model}project&projectID={$projectID}"));
  300. /* Load pager and get kanban list. */
  301. $this->app->loadClass('pager', true);
  302. $pager = new pager($recTotal, $recPerPage, $pageID);
  303. $this->view->title = $this->lang->project->common . $this->lang->hyphen . $this->lang->project->index;
  304. $this->view->pager = $pager;
  305. $this->view->project = $project;
  306. $this->view->browseType = $browseType;
  307. $this->view->actionList = array_merge(array('edit'), $this->config->execution->statusActions, array('delete'));
  308. $this->view->kanbanList = $this->execution->getList($projectID, 'all', $browseType, 0, 0, 0, $pager);
  309. $this->view->usersAvatar = $this->loadModel('user')->getAvatarPairs('all');
  310. $this->view->userIdPairs = $this->user->getPairs('nodeleted|showid|all');
  311. $this->view->memberGroup = $this->execution->getMembersByIdList(array_keys($this->view->kanbanList));
  312. $this->display();
  313. }
  314. /**
  315. * 渲染项目列表页面数据。
  316. * Display project list page.
  317. *
  318. * @param int $programID
  319. * @param string $browseType
  320. * @param string $param
  321. * @param string $orderBy
  322. * @param int $recTotal
  323. * @param int $recPerPage
  324. * @param int $pageID
  325. * @access public
  326. * @return void
  327. */
  328. public function browse($programID = 0, $browseType = 'doing', $param = '', $orderBy = 'order_asc', $recTotal = 0, $recPerPage = 15, $pageID = 1)
  329. {
  330. $this->session->set('projectList', $this->app->getURI(true), 'project');
  331. $browseType = strtolower($browseType);
  332. if(!in_array($browseType, array('all', 'undone', 'bysearch'))) unset($this->config->project->dtable->fieldList['status']);
  333. /* Load pager. */
  334. $this->app->loadClass('pager', true);
  335. $pager = new pager($recTotal, $recPerPage, $pageID);
  336. $queryID = ($browseType == 'bysearch') ? (int)$param : 0;
  337. $actionURL = $this->createLink('project', 'browse', "&programID=$programID&browseType=bySearch&queryID=myQueryID");
  338. $this->project->buildSearchForm($queryID, $actionURL);
  339. $programTitle = $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=project&key=programTitle");
  340. $projectStats = $this->loadModel('program')->getProjectStats($programID, $browseType, $queryID, $orderBy, $programTitle, false, $pager);
  341. $this->view->title = $this->lang->project->browse;
  342. $this->view->projectStats = $this->projectZen->processProjectListData($projectStats);
  343. $this->view->pager = $pager;
  344. $this->view->programID = $programID;
  345. $this->view->programTree = $this->project->getProgramTree($browseType);
  346. $this->view->browseType = $browseType;
  347. $this->view->param = $param;
  348. $this->view->orderBy = $orderBy;
  349. $this->view->showBatchEdit = $this->cookie->showProjectBatchEdit;
  350. $this->view->projectType = $this->cookie->projectType && !commonModel::isTutorialMode() ? $this->cookie->projectType : 'bylist';
  351. $this->view->programs = array(0 => $this->lang->program->all) + $this->program->getParentPairs();
  352. $this->view->users = $this->loadModel('user')->getPairs('noletter|pofirst|nodeleted');
  353. $this->view->userIdPairs = $this->loadModel('user')->getPairs('nodeleted|showid');
  354. $this->view->usersAvatar = $this->user->getAvatarPairs();
  355. $this->display();
  356. }
  357. /**
  358. * 项目看板。
  359. * Project kanban.
  360. *
  361. * @access public
  362. * @return void
  363. */
  364. public function kanban()
  365. {
  366. $this->view->title = $this->lang->project->kanban;
  367. $this->view->kanbanList = $this->projectZen->getKanbanData();
  368. $this->display();
  369. }
  370. /**
  371. * 设置展示项目集的层级。
  372. * Sets the level of the display program.
  373. *
  374. * @access public
  375. * @return void
  376. */
  377. public function programTitle()
  378. {
  379. $this->loadModel('setting');
  380. if($_POST)
  381. {
  382. $this->setting->setItem($this->app->user->account . '.project.programTitle', $this->post->programTitle);
  383. return $this->send(array('result' => 'success', 'load' => true));
  384. }
  385. $status = $this->setting->getItem('owner=' . $this->app->user->account . '&module=project&key=programTitle');
  386. $this->view->status = empty($status) ? '0' : $status;
  387. $this->display();
  388. }
  389. /**
  390. * Create a project.
  391. *
  392. * @param string $model
  393. * @param int $programID
  394. * @param int $copyProjectID
  395. * @param string $extra
  396. * @access public
  397. * @return void
  398. */
  399. public function create($model = 'scrum', $programID = 0, $copyProjectID = 0, $extra = '')
  400. {
  401. $this->session->set('projectModel', $model);
  402. if($model == 'kanban') unset($this->lang->project->authList['reset']);
  403. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  404. parse_str($extra, $output);
  405. if(!empty($output['showTips']) && !empty($output['project']))
  406. {
  407. $this->displayAfterCreated((int)$output['project']);
  408. return;
  409. }
  410. $workflowGroup = !empty($output['workflowGroup']) ? (int)$output['workflowGroup'] : 0;
  411. if($_POST)
  412. {
  413. if($this->post->longTime || $this->post->LONG_TIME) $this->config->project->form->create['end']['skipRequired'] = true;
  414. if(!empty($_POST['products']) && $_POST['products'][0] != '') $_POST['hasProduct'] = '1';
  415. $postData = form::data($this->config->project->form->create, 0, $workflowGroup);
  416. $project = $this->projectZen->prepareCreateExtras($postData, $copyProjectID);
  417. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  418. $projectID = $this->project->create($project, $postData);
  419. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  420. if($project->model == 'kanban') $this->project->addTeamMembers($projectID, $project, (array)$this->post->teamMembers);
  421. $this->loadModel('action')->create('project', $projectID, 'opened');
  422. /* Link the plan stories. */
  423. if(($project->hasProduct) && $this->post->plans) $this->project->addPlans($projectID, $this->post->plans);
  424. $message = $this->executeHooks($projectID);
  425. if($message) $this->lang->saveSuccess = $message;
  426. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $projectID));
  427. if(in_array($model, array('waterfall', 'waterfallplus', 'ipd')))
  428. {
  429. $productID = $this->loadModel('product')->getProductIDByProject($projectID, true);
  430. $session = $this->createLink('programplan', 'browse', "projectID=$projectID&productID=$productID&type=lists", '', false, $projectID);
  431. if(in_array($this->config->edition, array('max', 'ipd'))) $session = $this->createLink('project', 'execution', "status=undone&projectID=$projectID", '', false);
  432. $this->session->set('projectPlanList', $session, 'project');
  433. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('programplan', 'create', "projectID=$projectID&productID=0&planID=0&executionType=stage&from=projectCreate", '', false, $projectID)));
  434. }
  435. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('create', "model=$model&programID=$programID&copyProjectID=$projectID&extra=showTips=1,project=$projectID")));
  436. }
  437. $this->projectZen->buildCreateForm((string)$model, (int)$programID, (int)$copyProjectID, (string)$extra);
  438. $this->display('project', 'create');
  439. }
  440. /**
  441. * Edit a project.
  442. *
  443. * @param int $projectID
  444. * @param string $from
  445. * @param int $programID
  446. * @param string $extra
  447. * @access public
  448. */
  449. public function edit($projectID, $from = '', $programID = 0, $extra = '')
  450. {
  451. $project = $this->project->getByID($projectID);
  452. $this->project->setMenu($projectID);
  453. if($project->model == 'kanban')
  454. {
  455. unset($this->lang->project->authList['reset']);
  456. $this->lang->project->aclList = $this->lang->project->kanbanAclList;
  457. $this->lang->project->subAclList = $this->lang->project->kanbanSubAclList;
  458. }
  459. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  460. parse_str($extra, $output);
  461. $workflowGroup = !empty($output['workflowGroup']) ? (int)$output['workflowGroup'] : 0;
  462. if($_POST)
  463. {
  464. if($this->post->longTime) $this->config->project->form->edit['end']['skipRequired'] = true;
  465. if($this->post->delta == '999') $this->config->project->form->edit['end']['skipRequired'] = true;
  466. $postData = form::data($this->config->project->form->edit, $projectID, $workflowGroup);
  467. $postProductData = !empty($project->hasProduct) ? form::data($this->config->project->form->edit)->get('products,plans,branch') : new stdclass();
  468. $newProject = $this->projectZen->prepareProject($postData, $project->hasProduct);
  469. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  470. $changes = $this->project->update($newProject, $project, $postProductData);
  471. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  472. if($changes)
  473. {
  474. $actionID = $this->loadModel('action')->create('project', $projectID, 'edited');
  475. $this->action->logHistory($actionID, $changes);
  476. }
  477. $message = $this->executeHooks($projectID);
  478. if($message) $this->lang->saveSuccess = $message;
  479. if(isInModal()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true, 'closeModal' => true));
  480. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink('project', 'view', "projectID=$projectID")));
  481. }
  482. if(!empty($output['workflowGroup'])) $project->workflowGroup = $workflowGroup;
  483. if(!empty($output['model'])) $project->model = $output['model'];
  484. if(!empty($output['category'])) $project->category = $output['category'];
  485. $this->projectZen->buildEditForm($projectID, $project, $from, $programID);
  486. $this->display();
  487. }
  488. /**
  489. * Batch edit projects.
  490. *
  491. * @access public
  492. * @return void
  493. */
  494. public function batchEdit()
  495. {
  496. $this->loadModel('action');
  497. $this->loadModel('execution');
  498. if($this->post->name)
  499. {
  500. $projects = form::batchData($this->config->project->form->batchedit)->get();
  501. $allChanges = $this->project->batchUpdate($projects);
  502. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  503. if(!empty($allChanges))
  504. {
  505. foreach($allChanges as $projectID => $changes)
  506. {
  507. if(empty($changes)) continue;
  508. $actionID = $this->action->create('project', $projectID, 'Edited');
  509. $this->action->logHistory($actionID, $changes);
  510. }
  511. }
  512. $locateLink = $this->session->projectList;
  513. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $locateLink));
  514. }
  515. if(!$this->post->projectIdList)
  516. {
  517. /* Use a fallback link to locate in case session has no related data. */
  518. $locateLink = !empty($this->session->projectList) ? $this->session->projectList : $this->createLink('project', 'browse');
  519. return $this->locate($locateLink);
  520. }
  521. $projectIdList = $this->post->projectIdList;
  522. $projects = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->in($projectIdList)->fetchAll('id');
  523. /* Get program list. */
  524. $programs = $this->loadModel('program')->getParentPairs('', '');
  525. $unauthorizedIDList = array();
  526. $appendPMUsers = array();
  527. foreach($projects as $project)
  528. {
  529. if(!isset($programs[$project->parent]) and !in_array($project->parent, $unauthorizedIDList)) $unauthorizedIDList[] = $project->parent;
  530. $appendPMUsers[$project->PM] = $project->PM;
  531. }
  532. $this->view->title = $this->lang->project->batchEdit;
  533. $this->view->projects = $projects;
  534. $this->view->programs = $programs;
  535. $this->view->unauthorizedPrograms = $this->program->getPairsByList($unauthorizedIDList);
  536. $this->view->PMUsers = $this->loadModel('user')->getPairs('noclosed|nodeleted|pmfirst', $appendPMUsers);
  537. $this->display();
  538. }
  539. /**
  540. * View a project.
  541. *
  542. * @param int $projectID
  543. * @access public
  544. * @return void
  545. */
  546. public function view($projectID = 0)
  547. {
  548. $this->app->loadLang('build');
  549. $project = $this->project->fetchByID($projectID);
  550. if(!empty($project->deleted)) return $this->sendError($this->lang->project->deletedTip, $this->createLink('project', 'browse'));
  551. if(!defined('RUN_MODE') || RUN_MODE != 'api') $projectID = $this->project->checkAccess((int)$projectID, $this->project->getPairsByProgram());
  552. if(is_bool($projectID)) return $this->send(array('result' => 'success', 'load' => array('alert' => $this->lang->project->accessDenied, 'locate' => $this->createLink('project', 'browse'))));
  553. $this->session->set('teamList', $this->app->getURI(true), 'project');
  554. $projectID = $this->project->setMenu($projectID);
  555. $project = $this->project->getById((int)$projectID);
  556. if(empty($project) || strpos('scrum,waterfall,kanban,agileplus,waterfallplus,ipd', $project->model) === false)
  557. {
  558. if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'fail', 'code' => 404, 'message' => '404 Not found'));
  559. return $this->send(array('result' => 'success', 'load' => array('alert' => $this->lang->notFound, 'locate' => $this->createLink('project', 'browse'))));
  560. }
  561. $projectBranches = $this->project->getBranchesByProject($projectID);
  562. $linkedProductIdList = empty($projectBranches) ? '' : array_keys($projectBranches);
  563. $products = $this->loadModel('product')->getProducts($projectID, 'all', '', true, $linkedProductIdList, false);
  564. $linkedBranches = array();
  565. foreach($products as $product)
  566. {
  567. $product->roadmaps = '';
  568. if(isset($product->branches))
  569. {
  570. foreach($product->branches as $branchID)
  571. {
  572. $linkedBranches[$branchID] = $branchID;
  573. $product->roadmaps .= !empty($projectBranches[$product->id][$branchID]->roadmap) ? ',' . trim($projectBranches[$product->id][$branchID]->roadmap, ',') : '';
  574. }
  575. }
  576. $product->roadmaps = trim($product->roadmaps, ',');
  577. }
  578. if($this->config->edition != 'open')
  579. {
  580. $charter = $this->loadModel('charter')->getByID($project->charter);
  581. $this->view->charter = $charter;
  582. if($project->linkType == 'roadmap')
  583. {
  584. $productRoadmaps = $this->charter->getGroupDataByID($project->charter);
  585. $this->view->roadmaps = !empty($charter) ? $productRoadmaps : array();
  586. }
  587. }
  588. $this->executeHooks($projectID);
  589. list($userPairs, $userList) = $this->projectZen->buildUsers();
  590. if($this->config->edition != 'open' && !empty($project->workflowGroup))
  591. {
  592. $this->view->workflowGroup = $this->loadModel('workflowgroup')->getByID((int)$project->workflowGroup);
  593. }
  594. $this->view->title = $this->lang->project->view;
  595. $this->view->projectID = $projectID;
  596. $this->view->project = $project;
  597. $this->view->products = $products;
  598. $this->view->actions = $this->loadModel('action')->getList('project', $projectID);
  599. $this->view->teamMembers = $this->project->getTeamMembers($projectID);
  600. $this->view->statData = $this->project->getStatData($projectID);
  601. $this->view->workhour = $this->project->getWorkhour($projectID);
  602. $this->view->planGroup = $this->loadModel('execution')->getPlans(array_keys($products));
  603. $this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'ignoreNormal', $linkedBranches);
  604. $this->view->dynamics = $this->loadModel('action')->getDynamic('all', 'all', 'date_desc', 50, 'all', $projectID);
  605. $this->view->programList = $this->loadModel('program')->getPairsByList(toIntArray($project->path));
  606. $this->view->users = $userPairs;
  607. $this->view->userList = $userList;
  608. $this->display();
  609. }
  610. /**
  611. * Project browse groups.
  612. *
  613. * @param int $projectID
  614. * @param int $programID
  615. * @access public
  616. * @return void
  617. */
  618. public function group($projectID = 0, $programID = 0)
  619. {
  620. $this->loadModel('group');
  621. $this->project->setMenu($projectID);
  622. $groups = $this->group->getList($projectID);
  623. $groupUsers = array();
  624. foreach($groups as $group)
  625. {
  626. $group->users = '';
  627. $groupUsers = $this->group->getUserPairs($group->id);
  628. foreach($groupUsers as $realname) $group->users .= $realname . ' ';
  629. }
  630. $this->view->title = $this->lang->group->browse;
  631. $this->view->groups = $groups;
  632. $this->view->project = $this->dao->findById($projectID)->from(TABLE_PROJECT)->fetch();
  633. $this->view->projectID = $projectID;
  634. $this->view->programID = $programID;
  635. $this->view->groupUsers = $groupUsers;
  636. $this->display();
  637. }
  638. /**
  639. * 添加项目权限组。
  640. * Project create a group.
  641. *
  642. * @param int $projectID
  643. * @access public
  644. * @return void
  645. */
  646. public function createGroup($projectID)
  647. {
  648. $this->loadModel('group');
  649. if(!empty($_POST))
  650. {
  651. $this->lang->project->name = $this->lang->group->name;
  652. $group = form::data($this->config->group->form->create)->get();
  653. $group->project = $projectID;
  654. $this->group->create($group);
  655. if(dao::isError()) return $this->sendError(dao::getError());
  656. return $this->sendSuccess(array('load' => true));
  657. }
  658. $this->view->title = $this->lang->group->create;
  659. $this->display('group', 'create');
  660. }
  661. /**
  662. * 项目动态。
  663. * Project dynamic.
  664. *
  665. * @param int $projectID
  666. * @param string $type
  667. * @param string $param
  668. * @param int $recTotal
  669. * @param int $date
  670. * @param string $direction next|pre
  671. * @access public
  672. * @return void
  673. */
  674. public function dynamic($projectID = 0, $type = 'today', $param = '', $recTotal = 0, $date = 0, $direction = 'next')
  675. {
  676. if(empty($type)) $type = 'today';
  677. $this->loadModel('execution');
  678. $this->project->setMenu($projectID);
  679. /* Append id for second sort. */
  680. $orderBy = $direction == 'next' ? 'date_desc' : 'date_asc';
  681. /* Set the user and type. */
  682. $account = 'all';
  683. if($type == 'account')
  684. {
  685. $user = $this->loadModel('user')->getById($param, 'account');
  686. if($user) $account = $user->account;
  687. }
  688. $period = $type == 'account' ? 'all' : $type;
  689. $date = empty($date) ? '' : date('Y-m-d', $date);
  690. $actions = $this->loadModel('action')->getDynamicByProject($projectID, $account, $period, $orderBy, 50, $date, $direction);
  691. $dateGroups = $this->action->buildDateGroup($actions, $direction);
  692. if(empty($recTotal) && $dateGroups) $recTotal = $this->action->getDynamicCount($period);
  693. /* The header and position. */
  694. $project = $this->project->getByID($projectID);
  695. $this->view->title = $project->name . $this->lang->hyphen . $this->lang->project->dynamic;
  696. $this->view->userIdPairs = $this->loadModel('user')->getTeamMemberPairs($projectID, 'project');
  697. $this->view->accountPairs = $this->user->getPairs('noletter|nodeleted');
  698. /* Assign. */
  699. $this->view->projectID = $projectID;
  700. $this->view->type = $type;
  701. $this->view->orderBy = $orderBy;
  702. $this->view->account = $account;
  703. $this->view->param = $param;
  704. $this->view->dateGroups = $dateGroups;
  705. $this->view->direction = $direction;
  706. $this->view->recTotal = $recTotal;
  707. $this->display();
  708. }
  709. /**
  710. * Execution list.
  711. *
  712. * @param string $status
  713. * @param int $projectID
  714. * @param string $orderBy
  715. * @param int $productID
  716. * @param int $recTotal
  717. * @param int $recPerPage
  718. * @param int $pageID
  719. * @param int $queryID
  720. * @access public
  721. * @return void
  722. */
  723. public function execution($status = 'undone', $projectID = 0, $orderBy = 'order_asc', $productID = 0, $recTotal = 0, $recPerPage = 100, $pageID = 1, $queryID = 0)
  724. {
  725. $projects = $this->project->getPairsByProgram();
  726. $projectID = $this->project->checkAccess($projectID, $projects);
  727. if(is_bool($projectID)) return $this->send(array('result' => 'fail', 'message' => $this->lang->project->accessDenied, 'locate' => inlink('browse')));
  728. $this->loadModel('execution');
  729. $this->loadModel('task');
  730. $this->loadModel('programplan');
  731. $this->loadModel('product');
  732. $this->session->set('executionList', $this->app->getURI(true), 'project');
  733. $this->session->set('projectPlanList', $this->app->getURI(true), 'project');
  734. if($this->cookie->showTask) $this->session->set('taskList', $this->app->getURI(true), 'project');
  735. $project = $this->project->getByID($projectID);
  736. $this->project->setMenu($projectID);
  737. if(!$projectID) return $this->locate(inlink('browse'));
  738. if(!$project->multiple)
  739. {
  740. $executionID = $this->execution->getNoMultipleID($projectID);
  741. if(defined('RUN_MODE') && RUN_MODE == 'api')
  742. {
  743. $this->view->executionStats = array($this->execution->getByID($executionID));
  744. return $this->display();
  745. }
  746. return $this->locate($this->createLink('execution', 'task', "executionID=$executionID"));
  747. }
  748. if(!empty($project->model) && $project->model == 'kanban' && !(defined('RUN_MODE') && RUN_MODE == 'api')) return $this->locate(inlink('index', "projectID=$projectID"));
  749. /* Load pager and get tasks. */
  750. $this->app->loadClass('pager', true);
  751. $pager = new pager($recTotal, $recPerPage, $pageID);
  752. $sort = $orderBy;
  753. if(strpos($sort, 'rawID_') !== false) $sort = str_replace('rawID_', 'id_', $sort);
  754. if(strpos($sort, 'nameCol_') !== false) $sort = str_replace('nameCol_', 'name_', $sort);
  755. if($this->cookie->showTask)
  756. {
  757. /* Build the search form. */
  758. $actionURL = $this->createLink('project', 'execution', "status=bysearch&projectID=$projectID&orderBy=$orderBy&productID=$productID&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&queryID=myQueryID");
  759. $executions = $this->execution->fetchExecutionList($projectID, 'all', $productID);
  760. $executions = $this->execution->getPairsByList(array_keys($executions));
  761. $this->execution->buildTaskSearchForm($projectID, $executions, $queryID, $actionURL, 'projectTask');
  762. }
  763. $this->view->title = $this->lang->execution->allExecutions;
  764. $this->view->executionStats = $this->projectZen->getExecutionStats($status, $projectID, isset($executions) ? array_keys($executions) : array(), $productID, $queryID, $sort, $pager);
  765. $this->view->productList = array(0 => $this->lang->product->all) + $this->product->getProductPairsByProject($projectID, 'all', '', false);
  766. $this->view->productID = $productID;
  767. $this->view->product = $this->product->getByID($productID);
  768. $this->view->projectID = $projectID;
  769. $this->view->project = $project;
  770. $this->view->projects = $projects;
  771. $this->view->pager = $pager;
  772. $this->view->orderBy = $orderBy;
  773. $this->view->status = $status;
  774. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  775. $this->view->isStage = isset($project->model) && (in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')));
  776. $this->view->avatarList = $this->user->getAvatarPairs('');
  777. $this->view->queryID = $queryID;
  778. $this->view->hasFrozenExecutions = $this->project->hasFrozenObject($project->id, 'PP');
  779. $this->display();
  780. }
  781. /**
  782. * Project bug list.
  783. *
  784. * @param int $projectID
  785. * @param int $productID
  786. * @param string $branchID
  787. * @param string $orderBy
  788. * @param int $build
  789. * @param string $type
  790. * @param int $param
  791. * @param int $recTotal
  792. * @param int $recPerPage
  793. * @param int $pageID
  794. * @access public
  795. * @return void
  796. */
  797. public function bug($projectID = 0, $productID = 0, $branchID = 'all', $orderBy = 'status,id_desc', $build = 0, $type = 'all', $param = 0, $recTotal = 0, $recPerPage = 20, $pageID = 1)
  798. {
  799. /* Load models. */
  800. $this->loadModel('product');
  801. $this->loadModel('datatable');
  802. $this->loadModel('search');
  803. $this->project->setMenu($projectID);
  804. $this->session->set('bugList', $this->app->getURI(true), 'project');
  805. $projectID = (int)$projectID;
  806. $product = $this->product->getById($productID);
  807. $project = $this->project->getByID($projectID);
  808. $type = strtolower($type);
  809. $products = $this->product->getProducts($projectID);
  810. $this->session->set('currentProductType', !isset($product->type) ? 'normal' : $product->type);
  811. if($this->session->currentProductType != 'normal') $this->config->bug->dtable->fieldList['branch']['title'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
  812. if($this->session->currentProductType == 'normal') unset($this->config->bug->dtable->fieldList['branch']);
  813. /* 项目型项目不展示2.5级产品下拉菜单。 */
  814. if(!$project->hasProduct) $this->config->excludeSwitcherList[] = 'project-bug';
  815. /* 处理无产品项目、无迭代项目的bug搜索参数,构造搜索表单。 */
  816. $this->projectZen->processBugSearchParams($project, $type, $param, $projectID, $productID, $branchID, $orderBy, $build, $products);
  817. /* team member pairs. */
  818. $memberPairs = array();
  819. $memberPairs[''] = $this->lang->search->null;
  820. $teamMembers = $this->project->getTeamMembers($projectID);
  821. foreach($teamMembers as $key => $member) $memberPairs[$key] = $member->realname;
  822. $this->view->memberPairs = $memberPairs;
  823. /* 准备分支数据。 */
  824. $this->projectZen->prepareBranchForBug($products, $productID);
  825. /* 准备模块数据。 */
  826. $this->projectZen->prepareModuleForBug($productID, $projectID, $type, $param, $orderBy, $build, $branchID, $products);
  827. /* 构造必要的变量到视图。 */
  828. $this->projectZen->buildBugView($productID, $projectID, $project, $type, $param, $orderBy, $build, $branchID, $products, $recTotal, $recPerPage, $pageID);
  829. }
  830. /**
  831. * Project case list.
  832. *
  833. * @param int $projectID
  834. * @param int $productID
  835. * @param string $branch
  836. * @param string $browseType
  837. * @param int $param
  838. * @param string $caseType
  839. * @param string $orderBy
  840. * @param int $recTotal
  841. * @param int $recPerPage
  842. * @param int $pageID
  843. * @access public
  844. * @return void
  845. */
  846. public function testcase($projectID = 0, $productID = 0, $branch = 'all', $browseType = 'all', $param = 0, $caseType = '', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  847. {
  848. /* 项目型项目不展示2.5级产品下拉菜单。 */
  849. $project = $this->project->getByID($projectID);
  850. if(!$project->hasProduct)
  851. {
  852. $this->config->excludeSwitcherList[] = 'project-testcase';
  853. $productPairs = $this->loadModel('product')->getProductPairsByProject($projectID);
  854. $productID = key($productPairs);
  855. }
  856. echo $this->fetch('testcase', 'browse', "productID=$productID&branch=$branch&browseType=$browseType&param=$param&caseType=$caseType&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&projectID=$projectID");
  857. }
  858. /**
  859. * List of test reports for the project.
  860. *
  861. * @param int $projectID
  862. * @param string $objectType project|execution|product
  863. * @param string $extra
  864. * @param string $orderBy
  865. * @param int $recTotal
  866. * @param int $recPerPage
  867. * @param int $pageID
  868. * @access public
  869. * @return void
  870. */
  871. public function testreport($projectID = 0, $objectType = 'project', $extra = '', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  872. {
  873. echo $this->fetch('testreport', 'browse', "objectID=$projectID&objectType=$objectType&extra=$extra&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID");
  874. }
  875. /**
  876. * 项目下测试单列表。
  877. * Project test task list.
  878. *
  879. * @param int $projectID
  880. * @param int $productID
  881. * @param string $orderBy
  882. * @param int $recTotal
  883. * @param int $recPerPage
  884. * @param int $pageID
  885. * @access public
  886. * @return void
  887. */
  888. public function testtask($projectID = 0, $productID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  889. {
  890. $this->session->set('testtaskList', $this->app->getURI(true), 'project');
  891. $this->project->setMenu($projectID);
  892. /* Load pager. */
  893. $this->app->loadClass('pager', true);
  894. $pager = pager::init($recTotal, $recPerPage, $pageID);
  895. $project = $this->project->getByID($projectID);
  896. $tasks = $this->loadModel('testtask')->getProjectTasks($projectID, $productID, $orderBy, $pager);
  897. $this->projectZen->assignTesttaskVars($tasks);
  898. $this->view->title = $project->name . $this->lang->hyphen . $this->lang->project->common;
  899. $this->view->project = $project;
  900. $this->view->productID = $productID;
  901. $this->view->projectID = $projectID;
  902. $this->view->pager = $pager;
  903. $this->view->orderBy = $orderBy;
  904. $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
  905. $this->view->products = $this->loadModel('product')->getProducts($projectID, 'all', '', false);
  906. $this->view->actions = $this->loadModel('action')->getList('testtask', $projectID);
  907. $this->display();
  908. }
  909. /**
  910. * 项目版本列表。
  911. * Browse builds of a project.
  912. *
  913. * @param int $projectID
  914. * @param string $type all|product|bysearch
  915. * @param int $param
  916. * @param string $orderBy
  917. * @param int $recTotal
  918. * @param int $recPerPage
  919. * @param int $pageID
  920. * @access public
  921. * @return void
  922. */
  923. public function build($projectID = 0, $type = 'all', $param = 0, $orderBy = 't1.date_desc,t1.id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  924. {
  925. /* Set menu lang. */
  926. $this->project->setMenu($projectID);
  927. $project = $this->project->getByID($projectID);
  928. if($type == 'product') $this->session->set('buildProductID', $param);
  929. if(!$this->session->buildProductID) $this->session->set('buildProductID', $param);
  930. /* Build the search form. */
  931. $type = strtolower($type);
  932. $products = $this->loadModel('product')->getProducts($projectID, 'all', '', false);
  933. $this->project->buildProjectBuildSearchForm($products, $type == 'bysearch' ? (int)$param : 0, $projectID, $this->session->buildProductID, 'project');
  934. /* Build the search form. */
  935. $this->app->loadClass('pager', true);
  936. $pager = new pager($recTotal, $recPerPage, $pageID);
  937. /* Get builds. */
  938. $this->loadModel('build');
  939. if($type == 'bysearch')
  940. {
  941. $builds = $this->build->getProjectBuildsBySearch((int)$projectID, (int)$param, $orderBy, $pager);
  942. }
  943. else
  944. {
  945. $builds = $this->build->getProjectBuilds((int)$projectID, $type, (string)$param, $orderBy, $pager);
  946. }
  947. /* Set view data. */
  948. $this->view->title = $project->name . $this->lang->hyphen . $this->lang->execution->build;
  949. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  950. $this->view->builds = $this->projectZen->processBuildListData($builds, $projectID);
  951. $this->view->productID = $this->session->buildProductID;
  952. $this->view->project = $project;
  953. $this->view->products = $products;
  954. $this->view->system = $this->loadModel('system')->getPairs();
  955. $this->view->type = $type;
  956. $this->view->orderBy = $orderBy;
  957. $this->view->param = $param;
  958. $this->view->pager = $pager;
  959. $this->display();
  960. }
  961. /**
  962. * Manage privleges of a group.
  963. *
  964. * @param int $projectID
  965. * @param int $groupID
  966. * @param string $type
  967. * @access public
  968. * @return void
  969. */
  970. public function managePriv($projectID, $groupID = 0, $type = 'byPackage')
  971. {
  972. $this->loadModel('group');
  973. foreach($this->lang->resource as $moduleName => $action) $this->app->loadLang($moduleName);
  974. if(!empty($_POST))
  975. {
  976. $result = $this->group->updatePrivByGroup($groupID, '', '');
  977. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  978. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('group', "projectID={$projectID}")));
  979. }
  980. $group = $this->group->getById($groupID);
  981. $this->project->setMenu($projectID);
  982. $this->group->sortResource();
  983. $project = $this->project->getByID((int)$projectID);
  984. $this->projectZen->processGroupPrivs($project);
  985. $this->lang->resource = $this->project->getPrivsByModel($project->multiple ? $project->model : 'noSprint', $projectID, $project->hasProduct);
  986. $getPrivs = $this->group->getPrivs($groupID);
  987. foreach($getPrivs as $moduleName => $actions)
  988. {
  989. if(!isset($this->lang->resource->$moduleName))
  990. {
  991. unset($getPrivs[$moduleName]);
  992. }
  993. else
  994. {
  995. foreach($actions as $method => $value)
  996. {
  997. if(!isset($this->lang->resource->{$moduleName}->{$method})) unset($getPrivs[$moduleName][$method]);
  998. }
  999. }
  1000. }
  1001. /* Subsets . */
  1002. $subsets = array();
  1003. foreach($this->config->group->subset as $subsetName => $subset)
  1004. {
  1005. $subset->code = $subsetName;
  1006. $subset->allCount = 0;
  1007. $subset->selectCount = 0;
  1008. $subsets[$subset->code] = $subset;
  1009. }
  1010. $selectPrivs = $this->group->getPrivsByGroup($groupID);
  1011. $allPrivList = $this->group->getPrivsByNav();
  1012. $selectedPrivList = array();
  1013. $packages = array();
  1014. foreach($allPrivList as $privCode => $priv)
  1015. {
  1016. $subsetCode = $priv->subset;
  1017. $packageCode = $priv->package;
  1018. if(!isset($packages[$subsetCode])) $packages[$subsetCode] = array();
  1019. if(!isset($subsets[$subsetCode]))
  1020. {
  1021. $subset = new stdclass();
  1022. $subset->code = $subsetCode;
  1023. $subset->allCount = 0;
  1024. $subset->selectCount = 0;
  1025. $subsets[$subsetCode] = $subset;
  1026. }
  1027. if(!isset($packages[$subsetCode][$packageCode]))
  1028. {
  1029. $package = new stdclass();
  1030. $package->allCount = 0;
  1031. $package->selectCount = 0;
  1032. $package->subset = $subsetCode;
  1033. $package->privs = array();
  1034. $packages[$subsetCode][$packageCode] = $package;
  1035. }
  1036. $packages[$subsetCode][$packageCode]->privs[$privCode] = $priv;
  1037. $packages[$subsetCode][$packageCode]->allCount ++;
  1038. $subsets[$subsetCode]->allCount ++;
  1039. if(isset($selectPrivs[$privCode]))
  1040. {
  1041. $packages[$subsetCode][$packageCode]->selectCount ++;
  1042. $subsets[$subsetCode]->selectCount ++;
  1043. $selectedPrivList[] = $privCode;
  1044. }
  1045. }
  1046. $allPrivList = array_keys($allPrivList);
  1047. $relatedPrivData = $this->group->getRelatedPrivs($allPrivList, $selectedPrivList);
  1048. $this->view->allPrivList = $allPrivList;
  1049. $this->view->selectedPrivList = $selectedPrivList;
  1050. $this->view->relatedPrivData = $relatedPrivData;
  1051. $this->view->title = $group->name . $this->lang->hyphen . $this->lang->group->managePriv;
  1052. $this->view->subsets = $subsets;
  1053. $this->view->packages = $packages;
  1054. $this->view->group = $group;
  1055. $this->view->groupPrivs = $getPrivs;
  1056. $this->view->groupID = $groupID;
  1057. $this->view->projectID = $projectID;
  1058. $this->view->project = $project;
  1059. $this->view->type = $type;
  1060. $this->display();
  1061. }
  1062. /**
  1063. * Browse team of a project.
  1064. *
  1065. * @param int $projectID
  1066. * @access public
  1067. * @return void
  1068. */
  1069. public function team($projectID = 0)
  1070. {
  1071. $this->app->loadLang('execution');
  1072. $this->project->setMenu($projectID);
  1073. $project = $this->project->getByID($projectID);
  1074. $deptID = $this->app->user->admin ? 0 : $this->app->user->dept;
  1075. $this->view->title = $project->name . $this->lang->hyphen . $this->lang->project->team;
  1076. $this->view->projectID = $projectID;
  1077. $this->view->teamMembers = $this->project->getTeamMembers($projectID);
  1078. $this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($deptID, 'id');
  1079. $this->view->canBeChanged = common::canModify('project', $project);
  1080. $this->view->recTotal = count($this->view->teamMembers);
  1081. $this->display();
  1082. }
  1083. /**
  1084. * 移除项目成员。
  1085. * Unlink a memeber.
  1086. *
  1087. * @param int $projectID
  1088. * @param int $userID
  1089. * @param string $removeExecution yes|no
  1090. * @access public
  1091. * @return void
  1092. */
  1093. public function unlinkMember($projectID, $userID, $removeExecution = 'no')
  1094. {
  1095. $user = $this->loadModel('user')->getById($userID, 'id');
  1096. $account = $user->account;
  1097. $this->project->unlinkMember($projectID, $account, $removeExecution == 'yes');
  1098. /* if ajax request, send result. */
  1099. if(dao::isError()) return $this->sendError(dao::getError());
  1100. $this->loadModel('execution')->getLimitedExecution();
  1101. return $this->send(array('result' => 'success', 'load' => $this->createLink($this->config->vision == 'or' ? 'marketresearch' : 'project', 'team', "projectID={$projectID}")));
  1102. }
  1103. /**
  1104. * Manage project members.
  1105. *
  1106. * @param int $projectID
  1107. * @param mixed $dept
  1108. * @param int $copyProjectID
  1109. * @access public
  1110. * @return void
  1111. */
  1112. public function manageMembers($projectID, $dept = '', $copyProjectID = 0)
  1113. {
  1114. /* Load model. */
  1115. $this->loadModel('user');
  1116. $this->loadModel('dept');
  1117. $this->loadModel('execution');
  1118. $this->project->setMenu($projectID);
  1119. $project = $this->project->getById($projectID);
  1120. if(!empty($_POST))
  1121. {
  1122. $members = form::batchData($this->config->project->form->manageMembers)->get();
  1123. $this->project->manageMembers($projectID, $members);
  1124. if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1125. $this->execution->getLimitedExecution();
  1126. if(empty($project->multiple))
  1127. {
  1128. $executionID = $this->execution->getNoMultipleID($projectID);
  1129. $execution = $this->execution->getByID($executionID);
  1130. if($executionID) $this->execution->manageMembers($execution, $members);
  1131. }
  1132. return $this->send(array('message' => $this->lang->saveSuccess, 'result' => 'success', 'load' => $this->createLink('project', 'team', "projectID=$projectID")));
  1133. }
  1134. $users = $this->user->getPairs('noclosed|nodeleted|devfirst');
  1135. $roles = $this->user->getUserRoles(array_keys($users));
  1136. $deptUsers = $dept === '' ? array() : $this->dept->getDeptUserPairs((int)$dept);
  1137. $executions = $this->project->getExecutionList(array($projectID));
  1138. $executionTeams = $this->execution->getMembersByIdList(array_keys($executions));
  1139. $executionMembers = array();
  1140. foreach($executionTeams as $executionID => $executionTeam)
  1141. {
  1142. $executionMembers = array_merge($executionMembers, array_keys($executionTeam));
  1143. }
  1144. $currentMembers = $this->project->getTeamMembers($projectID);
  1145. $members2Import = $this->project->getMembers2Import($copyProjectID, array_keys($currentMembers));
  1146. $userItems = array();
  1147. foreach($users as $account => $realName) $userItems[$account] = array('value' => $account, 'text' => $realName, 'keys' => $account, 'disabled' => false);
  1148. $this->view->title = $this->lang->project->manageMembers . $this->lang->hyphen . $project->name;
  1149. $this->view->project = $project;
  1150. $this->view->users = $users;
  1151. $this->view->userItems = $userItems;
  1152. $this->view->roles = $roles;
  1153. $this->view->dept = $dept;
  1154. $this->view->depts = $this->dept->getOptionMenu();
  1155. $this->view->teams2Import = $this->loadModel('personnel')->getCopiedObjects($projectID, 'project', true);
  1156. $this->view->currentMembers = $currentMembers;
  1157. $this->view->copyProjectID = $copyProjectID;
  1158. $this->view->teamMembers = $this->projectZen->buildMembers($currentMembers, $members2Import, $deptUsers, $project->days);
  1159. $this->view->executionMembers = array_unique($executionMembers);
  1160. $this->display();
  1161. }
  1162. /**
  1163. * Manage members of a group.
  1164. *
  1165. * @param int $groupID
  1166. * @param int $deptID
  1167. * @access public
  1168. * @return void
  1169. */
  1170. public function manageGroupMember($groupID, $deptID = 0)
  1171. {
  1172. $this->loadModel('group');
  1173. if($this->server->request_method == 'POST')
  1174. {
  1175. $this->group->updateUser($groupID);
  1176. return $this->send(array('result' => 'success', 'load' => true, 'closeModal' => true));
  1177. }
  1178. $group = $this->group->getById($groupID);
  1179. $project = $this->project->getByID((int)$group->project);
  1180. $groupUsers = $this->group->getUserPairs($groupID);
  1181. $allUsers = $this->loadModel('dept')->getDeptUserPairs($deptID);
  1182. $otherUsers = array_diff_assoc($allUsers, $groupUsers);
  1183. if($project->acl != 'open')
  1184. {
  1185. $canViewMembers = $this->dao->select('account')->from(TABLE_USERVIEW)->where("CONCAT(',', projects, ',')")->like("%,$group->project,%")->fetchPairs();
  1186. foreach($otherUsers as $account => $otherUser)
  1187. {
  1188. if(!isset($canViewMembers[$account])) unset($otherUsers[$account]);
  1189. }
  1190. }
  1191. $outsideUsers = $this->loadModel('user')->getPairs('outside|noclosed|noletter|noempty');
  1192. if($project->acl != 'open')
  1193. {
  1194. foreach($outsideUsers as $account => $outsideUser)
  1195. {
  1196. if(!isset($canViewMembers[$account])) unset($outsideUsers[$account]);
  1197. }
  1198. }
  1199. $this->view->title = $group->name . $this->lang->hyphen . $this->lang->group->manageMember;
  1200. $this->view->group = $group;
  1201. $this->view->deptTree = $this->loadModel('dept')->getTreeMenu(0, array('deptModel', 'createGroupManageMemberLink'), (int)$groupID);
  1202. $this->view->groupUsers = $groupUsers;
  1203. $this->view->otherUsers = $otherUsers;
  1204. $this->view->deptID = $deptID;
  1205. $this->view->noUsers = empty($groupUsers) && empty($otherUsers);
  1206. $this->view->outsideUsers = array_diff_assoc($outsideUsers, $groupUsers);
  1207. $this->display('group', 'manageMember');
  1208. }
  1209. /**
  1210. * 复制项目权限组。
  1211. * Project copy a group.
  1212. *
  1213. * @param int $groupID
  1214. * @access public
  1215. * @return void
  1216. */
  1217. public function copyGroup($groupID)
  1218. {
  1219. $group = $this->loadModel('group')->getByID($groupID);
  1220. if(!empty($_POST))
  1221. {
  1222. $this->lang->project->name = $this->lang->group->name;
  1223. $newGroup = form::data($this->config->group->form->copy)->get();
  1224. $newGroup->project = $group->project;
  1225. $this->group->copy($groupID, $newGroup, (array)$this->post->options);
  1226. if(dao::isError()) return $this->sendError(dao::getError());
  1227. return $this->sendSuccess(array('load' => true));
  1228. }
  1229. $this->view->title = $this->lang->company->orgView . $this->lang->hyphen . $this->lang->group->copy;
  1230. $this->view->group = $group;
  1231. $this->display('group', 'copy');
  1232. }
  1233. /**
  1234. * 编辑项目权限组。
  1235. * Project edit a group.
  1236. *
  1237. * @param int $groupID
  1238. *
  1239. * @access public
  1240. * @return void
  1241. */
  1242. public function editGroup($groupID)
  1243. {
  1244. $this->loadModel('group');
  1245. if(!empty($_POST))
  1246. {
  1247. $this->lang->project->name = $this->lang->group->name;
  1248. $group = form::data($this->config->group->form->edit)->get();
  1249. $this->group->update($groupID, $group);
  1250. if(dao::isError()) return $this->sendError(dao::getError());
  1251. return $this->sendSuccess(array('load' => true));
  1252. }
  1253. $this->view->title = $this->lang->company->orgView . $this->lang->hyphen . $this->lang->group->edit;
  1254. $this->view->group = $this->group->getByID($groupID);
  1255. $this->display('group', 'edit');
  1256. }
  1257. /**
  1258. * 开始一个项目。
  1259. * Start a project.
  1260. *
  1261. * @param int $projectID
  1262. * @access public
  1263. * @return void
  1264. */
  1265. public function start($projectID)
  1266. {
  1267. $project = $this->project->getByID($projectID);
  1268. if(!empty($_POST))
  1269. {
  1270. $postData = form::data($this->config->project->form->start, $projectID);
  1271. $postData = $this->projectZen->prepareStartExtras($postData);
  1272. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1273. $changes = $this->project->start($projectID, $postData);
  1274. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1275. $this->projectZen->responseAfterStart($project, $changes, $this->post->comment);
  1276. return $this->sendSuccess(array('closeModal' => true, 'load' => true));
  1277. }
  1278. $this->projectZen->buildStartForm($project);
  1279. }
  1280. /**
  1281. * 挂起一个项目。
  1282. * Suspend a project.
  1283. *
  1284. * @param int $projectID
  1285. *
  1286. * @access public
  1287. * @return void
  1288. */
  1289. public function suspend($projectID)
  1290. {
  1291. /* Processing parameter passing while suspend project. */
  1292. if(!empty($_POST))
  1293. {
  1294. $postData = form::data($this->config->project->form->suspend, $projectID);
  1295. $postData = $this->projectZen->prepareSuspendExtras($projectID, $postData);
  1296. /* Update the database status to suspended. */
  1297. $changes = $this->project->suspend($projectID, $postData);
  1298. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1299. /* Process the data returned after the suspended. */
  1300. $comment = strip_tags($this->post->comment, $this->config->allowedTags !== null && is_array($this->config->allowedTags) ? '<' . implode('><', $this->config->allowedTags) . '>' : $this->config->allowedTags);
  1301. $this->projectZen->responseAfterSuspend($projectID, $changes, $comment);
  1302. return $this->sendSuccess(array('closeModal' => true, 'load' => true));
  1303. }
  1304. $this->app->loadLang('build');
  1305. $this->projectZen->buildSuspendForm($projectID);
  1306. }
  1307. /**
  1308. * 关闭一个项目。
  1309. * Close a project.
  1310. *
  1311. * @param int $projectID
  1312. * @access public
  1313. *
  1314. * @return void
  1315. */
  1316. public function close($projectID)
  1317. {
  1318. /* Processing parameter passing while close project. */
  1319. if(!empty($_POST))
  1320. {
  1321. $postData = form::data($this->config->project->form->close, $projectID);
  1322. $postData = $this->projectZen->prepareClosedExtras($projectID, $postData);
  1323. /* Update the database status to closed. */
  1324. $changes = $this->project->close($projectID, $postData);
  1325. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1326. /* Process the data returned after the closed. */
  1327. $comment = strip_tags((string)$this->post->comment, $this->config->allowedTags !== null && is_array($this->config->allowedTags) ? '<' . implode('><', $this->config->allowedTags) . '>' : $this->config->allowedTags);
  1328. $this->projectZen->responseAfterClose($projectID, $changes, $comment);
  1329. return $this->sendSuccess(array('closeModal' => true, 'load' => 'table'));
  1330. }
  1331. $this->app->loadLang('build');
  1332. $this->projectZen->buildClosedForm($projectID);
  1333. }
  1334. /**
  1335. * 激活项目并更新其状态。
  1336. * Activate a project.
  1337. *
  1338. * @param int $projectID
  1339. * @access public
  1340. *
  1341. * @return void
  1342. */
  1343. public function activate($projectID)
  1344. {
  1345. $project = $this->project->getByID($projectID);
  1346. if(!empty($_POST))
  1347. {
  1348. $postData = form::data($this->config->project->form->activate, $projectID);
  1349. $postData = $this->projectZen->prepareActivateExtras($projectID, $postData);
  1350. $changes = $this->project->activate($projectID, $postData);
  1351. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1352. $this->projectZen->responseAfterActivate($projectID, $changes);
  1353. return $this->sendSuccess(array('closeModal' => true, 'load' => true));
  1354. }
  1355. $this->app->loadLang('build');
  1356. $this->projectZen->buildActivateForm($project);
  1357. }
  1358. /**
  1359. * 删除一个项目,并弹窗确认。
  1360. * Delete a project and confirm.
  1361. *
  1362. * @param int $projectID
  1363. *
  1364. * @access public
  1365. * @return void
  1366. */
  1367. public function delete($projectID)
  1368. {
  1369. $projectID = (int)$projectID;
  1370. $project = $this->project->getByID($projectID);
  1371. $this->project->delete(TABLE_PROJECT, $projectID);
  1372. $this->project->deleteByTableName('zt_doclib', $projectID);
  1373. $this->loadModel('user')->updateUserView(array($projectID), 'project');
  1374. $response['result'] = 'success';
  1375. $response['closeModal'] = true;
  1376. $response['load'] = $this->app->tab == 'my' ? true : inlink('browse');
  1377. $message = $this->executeHooks($projectID);
  1378. if($message) $response['message'] = $message;
  1379. /* Delete the execution and product under the project. */
  1380. $executionIdList = $this->loadModel('execution')->getPairs($projectID);
  1381. if(!empty($executionIdList)) $this->projectZen->removeAssociatedExecutions($executionIdList);
  1382. $this->projectZen->removeAssociatedProducts($project);
  1383. $this->session->set('project', '');
  1384. return $this->send($response);
  1385. }
  1386. /**
  1387. * 更新项目排列序号。
  1388. * Update projects order.
  1389. *
  1390. * @access public
  1391. * @return void
  1392. */
  1393. public function updateOrder()
  1394. {
  1395. $idList = explode(',', trim($this->post->projects, ','));
  1396. $orderBy = $this->post->orderBy;
  1397. if(strpos($orderBy, 'order') === false) return false;
  1398. $this->project->updateOrder($idList, $orderBy);
  1399. }
  1400. /**
  1401. * 获取项目白名单列表。
  1402. * Get white list personnel.
  1403. *
  1404. * @param int $projectID
  1405. * @param string $from project|program|programProject
  1406. * @param int $recTotal
  1407. * @param int $recPerPage
  1408. * @param int $pageID
  1409. *
  1410. * @access public
  1411. * @return void
  1412. */
  1413. public function whitelist($projectID = 0, $from = 'project', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  1414. {
  1415. $projectID = $this->project->setMenu($projectID);
  1416. if(!$projectID) return $this->locate(inLink('browse'));
  1417. $project = $this->project->getByID($projectID);
  1418. if(isset($project->acl) and $project->acl == 'open') return $this->sendError($this->lang->whitelistNotNeed, inLink('browse'));
  1419. echo $this->fetch('personnel', 'whitelist', "objectID=$projectID&module=project&browseType=project&orderBy=id_desc&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&projectID=$projectID&from=$from");
  1420. }
  1421. /**
  1422. * 添加用户到项目白名单中。
  1423. * Adding users to the white list.
  1424. *
  1425. * @param int $projectID
  1426. * @param int $deptID
  1427. * @param int $copyID
  1428. * @param int $programID
  1429. * @param string $from
  1430. *
  1431. * @access public
  1432. * @return void
  1433. */
  1434. public function addWhitelist($projectID = 0, $deptID = 0, $copyID = 0, $programID = 0, $from = 'project')
  1435. {
  1436. $projectID = $this->project->setMenu($projectID);
  1437. $project = $this->project->getByID($projectID);
  1438. if(isset($project->acl) and $project->acl == 'open') return $this->sendError($this->lang->whitelistNotNeed, $this->createLink('project', 'index', "projectID=$projectID"));
  1439. echo $this->fetch('personnel', 'addWhitelist', "objectID=$projectID&dept=$deptID&copyID=$copyID&objectType=project&module=project&programID=$programID&from=$from");
  1440. }
  1441. /*
  1442. * 移除项目白名单人员。
  1443. * Removing users from the white list.
  1444. *
  1445. * @param int $id
  1446. * @param string $confirm
  1447. *
  1448. * @access public
  1449. * @return void
  1450. */
  1451. /**
  1452. * @param int $id
  1453. */
  1454. public function unbindWhitelist($id = 0, $confirm = 'no')
  1455. {
  1456. echo $this->fetch('personnel', 'unbindWhitelist', "id=$id&confirm=$confirm");
  1457. }
  1458. /**
  1459. * 管理项目关联产品。
  1460. * Manage products of project.
  1461. *
  1462. * @param string $projectID
  1463. * @access public
  1464. * @return void
  1465. */
  1466. public function manageProducts($projectID)
  1467. {
  1468. /* 如果是无产品项目,则返回。*/
  1469. /* If hasProduct is 0, return. */
  1470. $projectID = (int)$projectID;
  1471. $project = $this->project->getById($projectID);
  1472. if(!$project->hasProduct) return $this->sendError($this->lang->project->cannotManageProducts, true);
  1473. $executions = $this->loadModel('execution')->getPairs($projectID);
  1474. $IdList = array_keys($executions);
  1475. if(!empty($_POST))
  1476. {
  1477. /* 如果没有选择产品,则提示错误*/
  1478. /* If no product is selected, prompt error. */
  1479. $postProducts = $this->post->products;
  1480. $postOtherProduct = $this->post->otherProducts;
  1481. if(empty($postProducts) && empty($postOtherProduct))
  1482. {
  1483. return $this->send(array('result' => 'fail', 'message' => $this->lang->project->errorNoProducts));
  1484. }
  1485. $this->projectZen->updateLinkedProducts($projectID, $project, $IdList);
  1486. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1487. /* 成功关联产品后,跳转页面。*/
  1488. /* After successfully associating the product, jump to the page. */
  1489. return $this->sendSuccess(array('closeModal' => true, 'load' => true));
  1490. }
  1491. /* Set menu. */
  1492. $this->setProjectMenu($projectID, $project->parent);
  1493. /* 提取无法被移除的关联产品及分支。*/
  1494. /* Extract associated products and branches that cannot be removed. */
  1495. $this->projectZen->extractUnModifyForm($projectID, $project);
  1496. /* Organizing render pages requires data. */
  1497. $this->view->title = $this->lang->project->manageProducts . $this->lang->hyphen . $project->name;
  1498. $this->view->project = $project;
  1499. $this->view->executions = $executions;
  1500. $this->view->branches = $this->project->getBranchesByProject($projectID);
  1501. $this->display();
  1502. }
  1503. /**
  1504. * 获取项目页面中下拉执行列表。
  1505. * AJAX: get executions of a project in html select.
  1506. *
  1507. * @param int $projectID
  1508. * @param string $mode
  1509. * @param string $type all|sprint|stage|kanban
  1510. * @param string $pageType old
  1511. *
  1512. * @access public
  1513. * @return void
  1514. */
  1515. public function ajaxGetExecutions($projectID, $mode = '', $type = 'all', $pageType = '')
  1516. {
  1517. $executions = array();
  1518. if($projectID) $executions = (array)$this->loadModel('execution')->getPairs($projectID, $type, $mode);
  1519. $project = $this->project->getByID($projectID);
  1520. if($pageType == 'old')
  1521. {
  1522. $disabled = empty($project->multiple) ? 'disabled' : '';
  1523. return print(html::select('execution', array(0 => '') + $executions, '', "class='form-control $disabled' $disabled"));
  1524. }
  1525. $data = array();
  1526. $data['items'] = array();
  1527. $data['multiple'] = empty($project->multiple) ? false : true;
  1528. foreach($executions as $id => $name) $data['items'][] = array('text' => $name, 'value' => $id, 'keys' => $name);
  1529. echo json_encode($data);
  1530. }
  1531. /**
  1532. * 根据执行ID获取对应的项目下拉列表。
  1533. * AJAX: get a project by execution id.
  1534. *
  1535. * @param int $executionID
  1536. * @access public
  1537. * @return string
  1538. */
  1539. public function ajaxGetPairsByExecution($executionID)
  1540. {
  1541. $execution = $this->loadModel('execution')->getByID($executionID);
  1542. $projectPairs = $this->loadModel('project')->getPairsByIdList(array($execution->project));
  1543. if($this->app->getViewType() == 'json')
  1544. {
  1545. $project = array('id' => key($projectPairs), 'name' => reset($projectPairs));
  1546. $pinyin = common::convert2Pinyin(array(reset($projectPairs)));
  1547. $project['namePinyin'] = zget($pinyin, $project['name']);
  1548. return print(json_encode($project));
  1549. }
  1550. else
  1551. {
  1552. return print(html::select('project', $projectPairs, $execution->project, "class='form-control' onchange='loadProductExecutions({$execution->project}, this.value)'"));
  1553. }
  1554. }
  1555. /**
  1556. * 判断项目下是否有对应概念的需求。
  1557. * AJAX: check if the project has the concept of story.
  1558. *
  1559. * @param string $projectID
  1560. * @param string $storyType
  1561. * @access public
  1562. * @return bool
  1563. */
  1564. public function ajaxGetStoryByType($projectID, $storyType)
  1565. {
  1566. $story = $this->dao->select('t1.id')->from(TABLE_STORY)->alias('t1')
  1567. ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id = t2.story')
  1568. ->where('t1.deleted')->eq(0)
  1569. ->andWhere('t2.project')->eq($projectID)
  1570. ->andWhere('t1.type')->eq($storyType)
  1571. ->fetch('id');
  1572. return print(!empty($story));
  1573. }
  1574. /**
  1575. * Ajax get product drop menu.
  1576. *
  1577. * @param int $projectID
  1578. * @param int $productID
  1579. * @access public
  1580. * @return void
  1581. * @param string $currentMethod
  1582. */
  1583. public function ajaxSwitcherMenu($projectID, $productID, $currentMethod = '')
  1584. {
  1585. $currentModule = $currentMethod == 'zerocase' ? 'testcase' : 'project';
  1586. $params = $currentMethod == 'zerocase' ? "productID={id}&branchID=0&orderBy=id_desc&projectID={$projectID}" : "projectID={$projectID}&productID={id}";
  1587. $this->view->link = helper::createLink($currentModule, $currentMethod, $params);
  1588. $this->view->productID = $productID;
  1589. $this->view->products = $this->loadModel('product')->getProducts($projectID);
  1590. $this->view->projectID = $projectID;
  1591. $this->view->currentMethod = $currentMethod;
  1592. $this->display();
  1593. }
  1594. /**
  1595. * 检查是否同步阶段数据。
  1596. * AJAX: check whether stage data is sync.
  1597. *
  1598. * @param int $executionID
  1599. * @access public
  1600. * @return void
  1601. */
  1602. public function ajaxCheckHasStageData($executionID)
  1603. {
  1604. return print($this->project->hasStageData($executionID));
  1605. }
  1606. }