model.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <?php
  2. /**
  3. * The model file of design 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 Shujie Tian <tianshujie@easycorp.ltd>
  8. * @package design
  9. * @version $Id: model.php 5107 2020-09-02 09:46:12Z tianshujie@easycorp.ltd $
  10. * @link https://www.zentao.net
  11. */
  12. class designModel extends model
  13. {
  14. /**
  15. * 创建一个设计。
  16. * Create a design.
  17. *
  18. * @param object $design
  19. * @access public
  20. * @return bool|int
  21. */
  22. public function create($design)
  23. {
  24. $design = $this->loadModel('file')->processImgURL($design, 'desc', (string)$this->post->uid);
  25. $this->dao->insert(TABLE_DESIGN)->data($design, 'docVersions,docs')
  26. ->autoCheck()
  27. ->batchCheck($this->config->design->create->requiredFields, 'notempty')
  28. ->exec();
  29. if(dao::isError()) return false;
  30. $designID = $this->dao->lastInsertID();
  31. $this->file->updateObjectID($this->post->uid, $designID, 'design');
  32. $files = $this->file->saveUpload('design', $designID);
  33. $spec = new stdclass();
  34. $spec->design = $designID;
  35. $spec->version = 1;
  36. $spec->name = $design->name;
  37. $spec->desc = $design->desc;
  38. $spec->files = empty($files) ? '' : implode(',', array_keys($files));
  39. $spec->docs = isset($design->docs) ? $design->docs : '';
  40. $this->dao->insert(TABLE_DESIGNSPEC)->data($spec)->exec();
  41. return $designID;
  42. }
  43. /**
  44. * 批量创建设计。
  45. * Batch create designs.
  46. *
  47. * @param int $projectID
  48. * @param int $productID
  49. * @param array $designs
  50. * @access public
  51. * @return bool
  52. */
  53. public function batchCreate($projectID = 0, $productID = 0, $designs = array())
  54. {
  55. $this->loadModel('action');
  56. $stories = is_array($_POST['story']) ? $this->loadModel('story')->getByList($this->post->story) : array();
  57. foreach($designs as $rowID => $design)
  58. {
  59. $design->product = $productID;
  60. $design->project = $projectID;
  61. $design->createdBy = $this->app->user->account;
  62. if(!empty($stories[$design->story])) $design->storyVersion = $stories[$design->story]->version;
  63. $this->dao->insert(TABLE_DESIGN)->data($design)->autoCheck()->batchCheck($this->config->design->batchcreate->requiredFields, 'notempty')->exec();
  64. if(dao::isError())
  65. {
  66. foreach(dao::getError() as $field => $error) dao::$errors["{$field}[{$rowID}]"] = $error;
  67. return false;
  68. }
  69. $designID = $this->dao->lastInsertID();
  70. if($this->config->edition != 'open' && !empty($design->story))
  71. {
  72. if(!isset($stories[$design->story])) continue;
  73. $relation = new stdClass();
  74. $relation->AID = $design->story;
  75. $relation->AType = $stories[$design->story]->type;
  76. $relation->relation = 'generated';
  77. $relation->BID = $designID;
  78. $relation->BType = 'design';
  79. $relation->product = 0;
  80. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  81. }
  82. $this->action->create('design', $designID, 'created');
  83. }
  84. return true;
  85. }
  86. /**
  87. * 编辑一个设计。
  88. * Update a design.
  89. *
  90. * @param int $designID
  91. * @param object $design
  92. * @access public
  93. * @return bool|array
  94. */
  95. public function update($designID = 0, $design = null)
  96. {
  97. $oldDesign = $this->getByID($designID);
  98. if(!$oldDesign) return false;
  99. $design = $this->loadModel('file')->processImgURL($design, 'desc', (string)$this->post->uid);
  100. $this->dao->update(TABLE_DESIGN)->data($design, 'deleteFiles,renameFiles,files,docs,oldDocs,docVersions')->autoCheck()->batchCheck($this->config->design->edit->requiredFields, 'notempty')->where('id')->eq($designID)->exec();
  101. if(dao::isError()) return false;
  102. $this->file->processFileDiffsForObject('design', $oldDesign, $design);
  103. $addedFiles = empty($design->addedFiles) ? '' : implode(',', array_keys($design->addedFiles)) . ',';
  104. $designFiles = $oldDesign->files = implode(',', array_keys($oldDesign->files));
  105. foreach($design->deleteFiles as $fileID) $designFiles = str_replace(",$fileID,", ',', ",$designFiles,");
  106. $files = $addedFiles . trim($designFiles, ',');
  107. $designChanged = ($oldDesign->name != $design->name || $oldDesign->desc != $design->desc || !empty($files) || $oldDesign->docs != $design->docs);
  108. if($designChanged)
  109. {
  110. $version = $oldDesign->version + 1;
  111. $spec = new stdclass();
  112. $spec->design = $designID;
  113. $spec->version = $version;
  114. $spec->name = $design->name;
  115. $spec->desc = $design->desc;
  116. $spec->files = empty($files) ? '' : $files;
  117. $spec->docs = isset($design->docs) ? $design->docs : '';
  118. $this->dao->insert(TABLE_DESIGNSPEC)->data($spec)->exec();
  119. $this->dao->update(TABLE_DESIGN)->set('version')->eq($version)->where('id')->eq($designID)->exec();
  120. }
  121. return common::createChanges($oldDesign, $design);
  122. }
  123. /**
  124. * 更新设计的指派人。
  125. * Update assign of design.
  126. *
  127. * @param int $designID
  128. * @param object $design
  129. * @access public
  130. * @return array|bool
  131. */
  132. public function assign($designID = 0, $design = null)
  133. {
  134. $oldDesign = $this->getByID($designID);
  135. if(!$oldDesign) return false;
  136. $this->dao->update(TABLE_DESIGN)->data($design)->autoCheck()->where('id')->eq($designID)->exec();
  137. if(dao::isError()) return false;
  138. return common::createChanges($oldDesign, $design);
  139. }
  140. /**
  141. * 设计关联代码提交。
  142. * Design link commits.
  143. *
  144. * @param int $designID
  145. * @param int $repoID
  146. * @param array $revisions
  147. * @access public
  148. * @return bool
  149. */
  150. public function linkCommit($designID = 0, $repoID = 0, $revisions = array())
  151. {
  152. $repo = $this->loadModel('repo')->getByID($repoID);
  153. if(!isset($repo->SCM)) return true;
  154. /* If the repo type is Gitlab, first store the commit log in the repohistory table and get the commit ID. */
  155. if(in_array($repo->SCM, $this->config->repo->notSyncSCM))
  156. {
  157. $logs = array();
  158. foreach($this->session->designRevisions as $commit)
  159. {
  160. if(in_array($commit->revision, $revisions))
  161. {
  162. $log = new stdclass();
  163. $log->committer = $commit->committer;
  164. $log->revision = $commit->revision;
  165. $log->comment = isset($commit->comment) ? $commit->comment : '';
  166. $log->time = date('Y-m-d H:i:s', strtotime($commit->time));
  167. $logs[] = $log;
  168. }
  169. }
  170. $this->repo->saveCommit($repoID, array('commits' => $logs), 0);
  171. }
  172. $revisions = $this->dao->select('id')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->andWhere('repo')->eq($repoID)->fetchPairs('id');
  173. $this->designTao->updateLinkedCommits($designID, $repoID, $revisions);
  174. $oldCommit = $this->dao->findByID($designID)->from(TABLE_DESIGN)->fetch('commit');
  175. $revisions = implode(',', $revisions);
  176. $commit = $oldCommit ? $oldCommit . ',' . $revisions : $revisions;
  177. $design = new stdclass();
  178. $design->commit = $commit;
  179. $design->commitedBy = $this->app->user->account;
  180. $this->dao->update(TABLE_DESIGN)->data($design)->autoCheck()->where('id')->eq($designID)->exec();
  181. return !dao::isError();
  182. }
  183. /**
  184. * 设计解除代码提交关联。
  185. * Design unlink a commit.
  186. *
  187. * @param int $designID
  188. * @param int $commitID
  189. * @access public
  190. * @return bool
  191. */
  192. public function unlinkCommit($designID = 0, $commitID = 0)
  193. {
  194. /* Delete linked commit in the relation table. */
  195. $this->dao->delete()->from(TABLE_RELATION)
  196. ->where('AType')->eq('design')
  197. ->andwhere('AID')->eq($designID)
  198. ->andwhere('BType')->eq('commit')
  199. ->andwhere('relation')->eq('completedin')
  200. ->beginIF(!empty($commitID))->andWhere('BID')->eq($commitID)->fi()
  201. ->exec();
  202. $this->dao->delete()->from(TABLE_RELATION)
  203. ->where('AType')->eq('commit')
  204. ->andwhere('BID')->eq($designID)
  205. ->andwhere('BType')->eq('design')
  206. ->andwhere('relation')->eq('completedfrom')
  207. ->beginIF(!empty($commitID))->andWhere('AID')->eq($commitID)->fi()
  208. ->exec();
  209. /* Update linked commit in the design table. */
  210. $commit = $this->dao->select('BID')->from(TABLE_RELATION)->where('AType')->eq('design')->andWhere('AID')->eq($designID)->andWhere('BType')->eq('commit')->andwhere('relation')->eq('completedin')->fetchAll('BID');
  211. $commit = implode(",", array_keys($commit));
  212. $this->dao->update(TABLE_DESIGN)->set('commit')->eq($commit)->where('id')->eq($designID)->exec();
  213. return !dao::isError();
  214. }
  215. /**
  216. * 通过ID获取设计信息。
  217. * Get design information by ID.
  218. *
  219. * @param int $designID
  220. * @access public
  221. * @return object|bool
  222. * @param int $version
  223. */
  224. public function getByID($designID = 0, $version = 0)
  225. {
  226. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDesign();
  227. $design = $this->dao->select('*')->from(TABLE_DESIGN)->where('id')->eq($designID)->fetch();
  228. if(!$design) return false;
  229. if($version == 0) $version = $design->version;
  230. $this->app->loadLang('product');
  231. $this->loadModel('file');
  232. $spec = $this->dao->select('name,`desc`,files,docs,docVersions')->from(TABLE_DESIGNSPEC)->where('design')->eq($designID)->andWhere('version')->eq($version)->fetch();
  233. $design->name = !empty($spec->name) ? $spec->name : $design->name;
  234. $design->desc = !empty($spec->desc) ? $spec->desc : '';
  235. $design->files = !empty($spec->files) ? $this->file->getByIdList($spec->files) : array();
  236. $design->docs = !empty($spec->docs) ? $spec->docs : '';
  237. $design->docVersions = !empty($spec->docVersions) ? json_decode($spec->docVersions, true) : array();
  238. $design->productName = $design->product ? $this->dao->findByID($design->product)->from(TABLE_PRODUCT)->fetch('name') : $this->lang->product->all;
  239. $design->project = (int)$design->project;
  240. $design->product = (int)$design->product;
  241. $revisions = $this->dao->select('id,revision')->from(TABLE_REPOHISTORY)->where('id')->in($design->commit)->fetchPairs('id', 'revision');
  242. $design->commit = '';
  243. $relations = $this->loadModel('common')->getRelations('design', $designID, 'commit');
  244. foreach($relations as $relation)
  245. {
  246. $revision = zget($revisions, $relation->BID, '');
  247. $design->commit .= html::a(helper::createLink('design', 'revision', "revisionID={$relation->BID}&projectID={$design->project}"), "# {$revision}", '', "title='{$revision}' class='flex clip'");
  248. }
  249. if($design->story > 0)
  250. {
  251. $storyInfo = $this->loadModel('story')->fetchByID((int)$design->story);
  252. $design->storyInfo = $storyInfo;
  253. $design->needConfirm = $storyInfo->version != $design->storyVersion;
  254. }
  255. return $this->loadModel('file')->replaceImgURL($design, 'desc');
  256. }
  257. /**
  258. * 获取设计 id=>value 的键值对数组。
  259. * Get design id=>value pairs.
  260. *
  261. * @param int $productID
  262. * @param string $type all|HLDS|DDS|DBDS|ADS
  263. * @access public
  264. * @return object
  265. */
  266. public function getPairs($productID = 0, $type = 'all')
  267. {
  268. $designs = $this->dao->select('id, name')->from(TABLE_DESIGN)
  269. ->where('product')->eq($productID)
  270. ->andWhere('deleted')->eq(0)
  271. ->beginIF($type != 'all')->andWhere('type')->eq($type)->fi()
  272. ->fetchPairs();
  273. foreach($designs as $id => $name) $designs[$id] = $id . ':' . $name;
  274. return $designs;
  275. }
  276. /**
  277. * 获取设计变更后受影响的任务。
  278. * Get affected tasks after design changed.
  279. *
  280. * @param int $design
  281. * @access public
  282. * @return object
  283. */
  284. public function getAffectedScope($design = null)
  285. {
  286. if(!isset($design->id)) return $design;
  287. /* Get affected tasks. */
  288. $design->tasks = $this->dao->select('*')->from(TABLE_TASK)
  289. ->where('deleted')->eq(0)
  290. ->andWhere('status')->ne('closed')
  291. ->andWhere('design')->eq($design->id)
  292. ->orderBy('id desc')->fetchAll();
  293. return $design;
  294. }
  295. /**
  296. * 获取设计列表数据。
  297. * Get design list.
  298. *
  299. * @param int|array $productID
  300. * @param int|array $projectID
  301. * @param string $type all|bySearch|HLDS|DDS|DBDS|ADS
  302. * @param int $param
  303. * @param string $orderBy
  304. * @param int $pager
  305. * @access public
  306. * @return object[]
  307. */
  308. public function getList($projectID = 0, $productID = 0, $type = 'all', $param = 0, $orderBy = 'id_desc', $pager = null)
  309. {
  310. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDesigns();
  311. if($type == 'bySearch')
  312. {
  313. $designs = $this->getBySearch($projectID, $productID, $param, $orderBy, $pager);
  314. }
  315. else
  316. {
  317. $designs = $this->dao->select('*')->from(TABLE_DESIGN)
  318. ->where('deleted')->eq(0)
  319. ->beginIF($projectID)->andWhere('project')->in($projectID)->fi()
  320. ->beginIF($type != 'all')->andWhere('type')->in($type)->fi()
  321. ->beginIF($productID)->andWhere('product')->in(is_numeric($productID) ? "0,$productID" : array_merge($productID, array(0)))->fi()
  322. ->orderBy($orderBy)
  323. ->page($pager)
  324. ->fetchAll('id');
  325. $stories = $this->loadModel('story')->getByList(array_column($designs, 'story'));
  326. foreach($designs as $designID => $design)
  327. {
  328. if(isset($stories[$design->story]))
  329. {
  330. $storyInfo = $stories[$design->story];
  331. $designs[$designID]->needConfirm = $storyInfo->version != $design->storyVersion;
  332. }
  333. }
  334. }
  335. return $designs;
  336. }
  337. /**
  338. * 获取设计关联的代码提交。
  339. * Get commit.
  340. *
  341. * @param int $designID
  342. * @param object $pager
  343. * @access public
  344. * @return object|bool
  345. */
  346. public function getCommit($designID = 0, $pager = null)
  347. {
  348. $design = $this->dao->select('*')->from(TABLE_DESIGN)->where('id')->eq($designID)->fetch();
  349. if(!$design) return false;
  350. $design->project = (int)$design->project;
  351. $design->product = (int)$design->product;
  352. $design->commit = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('id')->in($design->commit)->page($pager)->fetchAll('id', false);
  353. $this->loadModel('repo');
  354. foreach($design->commit as $commit)
  355. {
  356. $commit->originalComment = $commit->comment;
  357. $commit->comment = $this->repo->replaceCommentLink($commit->comment);
  358. }
  359. return $design;
  360. }
  361. /**
  362. * 获取搜索后的设计列表数据。
  363. * Get designs by search.
  364. *
  365. * @param int $projectID
  366. * @param int $productID
  367. * @param int $queryID
  368. * @param string $orderBy
  369. * @param object $pager
  370. * @access public
  371. * @return object[]
  372. */
  373. public function getBySearch($projectID = 0, $productID = 0, $queryID = 0, $orderBy = 'id_desc', $pager = null)
  374. {
  375. if($queryID)
  376. {
  377. $query = $this->loadModel('search')->getQuery($queryID);
  378. if($query)
  379. {
  380. $this->session->set('designQuery', $query->sql);
  381. $this->session->set('designForm', $query->form);
  382. }
  383. }
  384. else
  385. {
  386. if($this->session->designQuery === false) $this->session->set('designQuery', ' 1 = 1');
  387. }
  388. return $this->dao->select('*')->from(TABLE_DESIGN)
  389. ->where($this->session->designQuery)
  390. ->andWhere('deleted')->eq('0')
  391. ->andWhere('project')->eq($projectID)
  392. ->beginIF($productID)->andWhere('product')->in(is_numeric($productID) ? "0,$productID" : array_merge($productID, array(0)))->fi()
  393. ->orderBy($orderBy)
  394. ->page($pager)
  395. ->fetchAll('id');
  396. }
  397. /**
  398. * 通过ID获取提交记录。
  399. * Get commit by ID.
  400. *
  401. * @param int $revisionID
  402. * @access public
  403. * @return object|bool
  404. */
  405. public function getCommitByID($revisionID = 0)
  406. {
  407. return $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('id')->eq($revisionID)->fetch();
  408. }
  409. /**
  410. * 获取设计关联的提交数据。
  411. * Get the commit data for the associated designs
  412. * @param int $repoID
  413. * @param array $revisions
  414. * @access public
  415. * @return bool
  416. */
  417. public function getLinkedCommits($repoID, $revisions)
  418. {
  419. return $this->dao->select('t1.revision,t3.id AS id,t3.name AS title')
  420. ->from(TABLE_REPOHISTORY)->alias('t1')
  421. ->leftJoin(TABLE_RELATION)->alias('t2')->on("t2.relation='completedin' AND t2.BType='commit' AND t2.BID=t1.id")
  422. ->leftJoin(TABLE_DESIGN)->alias('t3')->on("t2.AType='design' AND t2.AID=t3.id")
  423. ->where('t1.revision')->in($revisions)
  424. ->andWhere('t1.repo')->eq($repoID)
  425. ->andWhere('t3.id')->notNULL()
  426. ->orderBy('id')
  427. ->fetchGroup('revision', 'id');
  428. }
  429. /**
  430. * 确认设计的需求变更。
  431. * Confirm story change of design.
  432. *
  433. * @param int $designID
  434. * @access public
  435. * @return bool
  436. */
  437. public function confirmStoryChange($designID)
  438. {
  439. $design = $this->fetchByID($designID);
  440. if($design)
  441. {
  442. $story = $this->loadModel('story')->fetchByID((int)$design->story);
  443. if($story) $this->dao->update(TABLE_DESIGN)->set('storyVersion')->eq($story->version)->where('id')->eq($designID)->exec();
  444. }
  445. return dao::isError();
  446. }
  447. /**
  448. * 获取项目下冻结的设计类型。
  449. * Get design type of frozen.
  450. *
  451. * @param int $projectID
  452. * @access public
  453. * @return array
  454. */
  455. public function getFrozenDesignType($projectID)
  456. {
  457. return $this->dao->select('t1.deliverable')->from(TABLE_PROJECTDELIVERABLE)->alias('t1')
  458. ->leftJoin(TABLE_DELIVERABLE)->alias('t2')->on('t1.deliverable = t2.id')
  459. ->where('t1.project')->eq($projectID)
  460. ->andWhere('t1.frozen')->ne('')
  461. ->andWhere('t2.category')->in('HLDS,DDS,DBDS,ADS')
  462. ->fetchPairs();
  463. }
  464. /**
  465. * 判断当前动作是否可以点击。
  466. * Adjust the action is clickable.
  467. *
  468. * @param object $object
  469. * @param string $action
  470. * @access public
  471. * @return bool
  472. */
  473. public static function isClickable($object, $action)
  474. {
  475. $action = strtolower($action);
  476. if($action == 'confirmstorychange') return !empty($object->needConfirm);
  477. if(in_array($action, array('edit', 'delete')) && !empty($object->frozen)) return false;
  478. return true;
  479. }
  480. }