* @package design * @version $Id: model.php 5107 2020-09-02 09:46:12Z tianshujie@easycorp.ltd $ * @link https://www.zentao.net */ class designModel extends model { /** * 创建一个设计。 * Create a design. * * @param object $design * @access public * @return bool|int */ public function create(object $design): bool|int { $design = $this->loadModel('file')->processImgURL($design, 'desc', (string)$this->post->uid); $this->dao->insert(TABLE_DESIGN)->data($design, 'docVersions,docs') ->autoCheck() ->batchCheck($this->config->design->create->requiredFields, 'notempty') ->exec(); if(dao::isError()) return false; $designID = $this->dao->lastInsertID(); $this->file->updateObjectID($this->post->uid, $designID, 'design'); $files = $this->file->saveUpload('design', $designID); $spec = new stdclass(); $spec->design = $designID; $spec->version = 1; $spec->name = $design->name; $spec->desc = $design->desc; $spec->files = empty($files) ? '' : implode(',', array_keys($files)); $spec->docs = isset($design->docs) ? $design->docs : ''; $this->dao->insert(TABLE_DESIGNSPEC)->data($spec)->exec(); return $designID; } /** * 批量创建设计。 * Batch create designs. * * @param int $projectID * @param int $productID * @param array $designs * @access public * @return bool */ public function batchCreate(int $projectID = 0, int $productID = 0, array $designs = array()): bool { $this->loadModel('action'); $stories = is_array($_POST['story']) ? $this->loadModel('story')->getByList($this->post->story) : array(); foreach($designs as $rowID => $design) { $design->product = $productID; $design->project = $projectID; $design->createdBy = $this->app->user->account; if(!empty($stories[$design->story])) $design->storyVersion = $stories[$design->story]->version; $this->dao->insert(TABLE_DESIGN)->data($design)->autoCheck()->batchCheck($this->config->design->batchcreate->requiredFields, 'notempty')->exec(); if(dao::isError()) { foreach(dao::getError() as $field => $error) dao::$errors["{$field}[{$rowID}]"] = $error; return false; } $designID = $this->dao->lastInsertID(); if($this->config->edition != 'open' && !empty($design->story)) { if(!isset($stories[$design->story])) continue; $relation = new stdClass(); $relation->AID = $design->story; $relation->AType = $stories[$design->story]->type; $relation->relation = 'generated'; $relation->BID = $designID; $relation->BType = 'design'; $relation->product = 0; $this->dao->replace(TABLE_RELATION)->data($relation)->exec(); } $this->action->create('design', $designID, 'created'); } return true; } /** * 编辑一个设计。 * Update a design. * * @param int $designID * @param object $design * @access public * @return bool|array */ public function update(int $designID = 0, ?object $design = null): bool|array { $oldDesign = $this->getByID($designID); if(!$oldDesign) return false; $design = $this->loadModel('file')->processImgURL($design, 'desc', (string)$this->post->uid); $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(); if(dao::isError()) return false; $this->file->processFileDiffsForObject('design', $oldDesign, $design); $addedFiles = empty($design->addedFiles) ? '' : implode(',', array_keys($design->addedFiles)) . ','; $designFiles = $oldDesign->files = implode(',', array_keys($oldDesign->files)); foreach($design->deleteFiles as $fileID) $designFiles = str_replace(",$fileID,", ',', ",$designFiles,"); $files = $addedFiles . trim($designFiles, ','); $designChanged = ($oldDesign->name != $design->name || $oldDesign->desc != $design->desc || !empty($files) || $oldDesign->docs != $design->docs); if($designChanged) { $version = $oldDesign->version + 1; $spec = new stdclass(); $spec->design = $designID; $spec->version = $version; $spec->name = $design->name; $spec->desc = $design->desc; $spec->files = empty($files) ? '' : $files; $spec->docs = isset($design->docs) ? $design->docs : ''; $this->dao->insert(TABLE_DESIGNSPEC)->data($spec)->exec(); $this->dao->update(TABLE_DESIGN)->set('version')->eq($version)->where('id')->eq($designID)->exec(); } return common::createChanges($oldDesign, $design); } /** * 更新设计的指派人。 * Update assign of design. * * @param int $designID * @param object $design * @access public * @return array|bool */ public function assign(int $designID = 0, ?object $design = null): array|bool { $oldDesign = $this->getByID($designID); if(!$oldDesign) return false; $this->dao->update(TABLE_DESIGN)->data($design)->autoCheck()->where('id')->eq($designID)->exec(); if(dao::isError()) return false; return common::createChanges($oldDesign, $design); } /** * 设计关联代码提交。 * Design link commits. * * @param int $designID * @param int $repoID * @param array $revisions * @access public * @return bool */ public function linkCommit(int $designID = 0, int $repoID = 0, array $revisions = array()): bool { $repo = $this->loadModel('repo')->getByID($repoID); if(!isset($repo->SCM)) return true; /* If the repo type is Gitlab, first store the commit log in the repohistory table and get the commit ID. */ if(in_array($repo->SCM, $this->config->repo->notSyncSCM)) { $logs = array(); foreach($this->session->designRevisions as $commit) { if(in_array($commit->revision, $revisions)) { $log = new stdclass(); $log->committer = $commit->committer; $log->revision = $commit->revision; $log->comment = isset($commit->comment) ? $commit->comment : ''; $log->time = date('Y-m-d H:i:s', strtotime($commit->time)); $logs[] = $log; } } $this->repo->saveCommit($repoID, array('commits' => $logs), 0); } $revisions = $this->dao->select('id')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->andWhere('repo')->eq($repoID)->fetchPairs('id'); $this->designTao->updateLinkedCommits($designID, $repoID, $revisions); $oldCommit = $this->dao->findByID($designID)->from(TABLE_DESIGN)->fetch('commit'); $revisions = implode(',', $revisions); $commit = $oldCommit ? $oldCommit . ',' . $revisions : $revisions; $design = new stdclass(); $design->commit = $commit; $design->commitedBy = $this->app->user->account; $this->dao->update(TABLE_DESIGN)->data($design)->autoCheck()->where('id')->eq($designID)->exec(); return !dao::isError(); } /** * 设计解除代码提交关联。 * Design unlink a commit. * * @param int $designID * @param int $commitID * @access public * @return bool */ public function unlinkCommit(int $designID = 0, int $commitID = 0): bool { /* Delete linked commit in the relation table. */ $this->dao->delete()->from(TABLE_RELATION) ->where('AType')->eq('design') ->andwhere('AID')->eq($designID) ->andwhere('BType')->eq('commit') ->andwhere('relation')->eq('completedin') ->beginIF(!empty($commitID))->andWhere('BID')->eq($commitID)->fi() ->exec(); $this->dao->delete()->from(TABLE_RELATION) ->where('AType')->eq('commit') ->andwhere('BID')->eq($designID) ->andwhere('BType')->eq('design') ->andwhere('relation')->eq('completedfrom') ->beginIF(!empty($commitID))->andWhere('AID')->eq($commitID)->fi() ->exec(); /* Update linked commit in the design table. */ $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'); $commit = implode(",", array_keys($commit)); $this->dao->update(TABLE_DESIGN)->set('commit')->eq($commit)->where('id')->eq($designID)->exec(); return !dao::isError(); } /** * 通过ID获取设计信息。 * Get design information by ID. * * @param int $designID * @access public * @return object|bool */ public function getByID(int $designID = 0, int $version = 0): object|bool { if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDesign(); $design = $this->dao->select('*')->from(TABLE_DESIGN)->where('id')->eq($designID)->fetch(); if(!$design) return false; if($version == 0) $version = $design->version; $this->app->loadLang('product'); $this->loadModel('file'); $spec = $this->dao->select('name,`desc`,files,docs,docVersions')->from(TABLE_DESIGNSPEC)->where('design')->eq($designID)->andWhere('version')->eq($version)->fetch(); $design->name = !empty($spec->name) ? $spec->name : $design->name; $design->desc = !empty($spec->desc) ? $spec->desc : ''; $design->files = !empty($spec->files) ? $this->file->getByIdList($spec->files) : array(); $design->docs = !empty($spec->docs) ? $spec->docs : ''; $design->docVersions = !empty($spec->docVersions) ? json_decode($spec->docVersions, true) : array(); $design->productName = $design->product ? $this->dao->findByID($design->product)->from(TABLE_PRODUCT)->fetch('name') : $this->lang->product->all; $design->project = (int)$design->project; $design->product = (int)$design->product; $revisions = $this->dao->select('id,revision')->from(TABLE_REPOHISTORY)->where('id')->in($design->commit)->fetchPairs('id', 'revision'); $design->commit = ''; $relations = $this->loadModel('common')->getRelations('design', $designID, 'commit'); foreach($relations as $relation) { $revision = zget($revisions, $relation->BID, ''); $design->commit .= html::a(helper::createLink('design', 'revision', "revisionID={$relation->BID}&projectID={$design->project}"), "# {$revision}", '', "title='{$revision}' class='flex clip'"); } if($design->story > 0) { $storyInfo = $this->loadModel('story')->fetchByID((int)$design->story); $design->storyInfo = $storyInfo; $design->needConfirm = $storyInfo->version != $design->storyVersion; } return $this->loadModel('file')->replaceImgURL($design, 'desc'); } /** * 获取设计 id=>value 的键值对数组。 * Get design id=>value pairs. * * @param int $productID * @param string $type all|HLDS|DDS|DBDS|ADS * @access public * @return object */ public function getPairs(int $productID = 0, string $type = 'all'): array { $designs = $this->dao->select('id, name')->from(TABLE_DESIGN) ->where('product')->eq($productID) ->andWhere('deleted')->eq(0) ->beginIF($type != 'all')->andWhere('type')->eq($type)->fi() ->fetchPairs(); foreach($designs as $id => $name) $designs[$id] = $id . ':' . $name; return $designs; } /** * 获取设计变更后受影响的任务。 * Get affected tasks after design changed. * * @param int $design * @access public * @return object */ public function getAffectedScope(?object $design = null): object { if(!isset($design->id)) return $design; /* Get affected tasks. */ $design->tasks = $this->dao->select('*')->from(TABLE_TASK) ->where('deleted')->eq(0) ->andWhere('status')->ne('closed') ->andWhere('design')->eq($design->id) ->orderBy('id desc')->fetchAll(); return $design; } /** * 获取设计列表数据。 * Get design list. * * @param int|array $productID * @param int|array $projectID * @param string $type all|bySearch|HLDS|DDS|DBDS|ADS * @param int $param * @param string $orderBy * @param int $pager * @access public * @return object[] */ public function getList(int|array $projectID = 0, int|array $productID = 0, string $type = 'all', int $param = 0, string $orderBy = 'id_desc', ?object $pager = null): array { if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDesigns(); if($type == 'bySearch') { $designs = $this->getBySearch($projectID, $productID, $param, $orderBy, $pager); } else { $designs = $this->dao->select('*')->from(TABLE_DESIGN) ->where('deleted')->eq(0) ->beginIF($projectID)->andWhere('project')->in($projectID)->fi() ->beginIF($type != 'all')->andWhere('type')->in($type)->fi() ->beginIF($productID)->andWhere('product')->in(is_numeric($productID) ? "0,$productID" : array_merge($productID, array(0)))->fi() ->orderBy($orderBy) ->page($pager) ->fetchAll('id'); $stories = $this->loadModel('story')->getByList(array_column($designs, 'story')); foreach($designs as $designID => $design) { if(isset($stories[$design->story])) { $storyInfo = $stories[$design->story]; $designs[$designID]->needConfirm = $storyInfo->version != $design->storyVersion; } } } return $designs; } /** * 获取设计关联的代码提交。 * Get commit. * * @param int $designID * @param object $pager * @access public * @return object|bool */ public function getCommit(int $designID = 0, ?object $pager = null): object|bool { $design = $this->dao->select('*')->from(TABLE_DESIGN)->where('id')->eq($designID)->fetch(); if(!$design) return false; $design->project = (int)$design->project; $design->product = (int)$design->product; $design->commit = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('id')->in($design->commit)->page($pager)->fetchAll('id', false); $this->loadModel('repo'); foreach($design->commit as $commit) { $commit->originalComment = $commit->comment; $commit->comment = $this->repo->replaceCommentLink($commit->comment); } return $design; } /** * 获取搜索后的设计列表数据。 * Get designs by search. * * @param int $projectID * @param int $productID * @param int $queryID * @param string $orderBy * @param object $pager * @access public * @return object[] */ public function getBySearch(int $projectID = 0, int $productID = 0, int $queryID = 0, string $orderBy = 'id_desc', ?object $pager = null): array { if($queryID) { $query = $this->loadModel('search')->getQuery($queryID); if($query) { $this->session->set('designQuery', $query->sql); $this->session->set('designForm', $query->form); } } else { if($this->session->designQuery === false) $this->session->set('designQuery', ' 1 = 1'); } return $this->dao->select('*')->from(TABLE_DESIGN) ->where($this->session->designQuery) ->andWhere('deleted')->eq('0') ->andWhere('project')->eq($projectID) ->beginIF($productID)->andWhere('product')->in(is_numeric($productID) ? "0,$productID" : array_merge($productID, array(0)))->fi() ->orderBy($orderBy) ->page($pager) ->fetchAll('id'); } /** * 通过ID获取提交记录。 * Get commit by ID. * * @param int $revisionID * @access public * @return object|bool */ public function getCommitByID(int $revisionID = 0): object|bool { return $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('id')->eq($revisionID)->fetch(); } /** * 获取设计关联的提交数据。 * Get the commit data for the associated designs * @param int $repoID * @param array $revisions * @access public * @return bool */ public function getLinkedCommits(int $repoID, array $revisions): array { return $this->dao->select('t1.revision,t3.id AS id,t3.name AS title') ->from(TABLE_REPOHISTORY)->alias('t1') ->leftJoin(TABLE_RELATION)->alias('t2')->on("t2.relation='completedin' AND t2.BType='commit' AND t2.BID=t1.id") ->leftJoin(TABLE_DESIGN)->alias('t3')->on("t2.AType='design' AND t2.AID=t3.id") ->where('t1.revision')->in($revisions) ->andWhere('t1.repo')->eq($repoID) ->andWhere('t3.id')->notNULL() ->orderBy('id') ->fetchGroup('revision', 'id'); } /** * 确认设计的需求变更。 * Confirm story change of design. * * @param int $designID * @access public * @return bool */ public function confirmStoryChange(int $designID) { $design = $this->fetchByID($designID); if($design) { $story = $this->loadModel('story')->fetchByID((int)$design->story); if($story) $this->dao->update(TABLE_DESIGN)->set('storyVersion')->eq($story->version)->where('id')->eq($designID)->exec(); } return dao::isError(); } /** * 获取项目下冻结的设计类型。 * Get design type of frozen. * * @param int $projectID * @access public * @return array */ public function getFrozenDesignType(int $projectID): array { return $this->dao->select('t1.deliverable')->from(TABLE_PROJECTDELIVERABLE)->alias('t1') ->leftJoin(TABLE_DELIVERABLE)->alias('t2')->on('t1.deliverable = t2.id') ->where('t1.project')->eq($projectID) ->andWhere('t1.frozen')->ne('') ->andWhere('t2.category')->in('HLDS,DDS,DBDS,ADS') ->fetchPairs(); } /** * 判断当前动作是否可以点击。 * Adjust the action is clickable. * * @param object $object * @param string $action * @access public * @return bool */ public static function isClickable(object $object, string $action): bool { $action = strtolower($action); if($action == 'confirmstorychange') return !empty($object->needConfirm); if(in_array($action, array('edit', 'delete')) && !empty($object->frozen)) return false; return true; } }