Files
2025-11-13 10:16:30 +08:00

513 lines
19 KiB
PHP
Executable File

<?php
declare(strict_types=1);
/**
* The model file of design module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Shujie Tian <tianshujie@easycorp.ltd>
* @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')
->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));
$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));
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;
$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): 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;
$this->app->loadLang('product');
$design->files = $this->loadModel('file')->getByObject('design', $designID);
$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();
}
/**
* 判断当前动作是否可以点击。
* 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);
return true;
}
}