| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203 |
- <?php
- /**
- * The tao file of project module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
- * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author sunguangming <sunguangming@easycorp.ltd>
- * @link https://www.zentao.net
- */
- class projectTao extends projectModel
- {
- /**
- * Update project table when start a project.
- *
- * @param int $projectID
- * @param object $project
- * @access protected
- * @return bool
- */
- protected function doStart($projectID, $project)
- {
- $this->dao->update(TABLE_PROJECT)->data($project, 'comment')
- ->autoCheck()
- ->check($this->config->project->start->requiredFields, 'notempty')
- ->checkIF($project->realBegan != '', 'realBegan', 'le', helper::today())
- ->checkFlow()
- ->where('id')->eq($projectID)
- ->exec();
- return !dao::isError();
- }
- /**
- * Update project table when suspend a project.
- *
- * @param int $projectID
- * @param object $project
- *
- * @access protected
- * @return bool
- */
- protected function doSuspend($projectID, $project)
- {
- $this->dao->update(TABLE_PROJECT)->data($project)
- ->autoCheck()
- ->checkFlow()
- ->where('id')->eq($projectID)
- ->exec();
- return !dao::isError();
- }
- /**
- * Update project table when close a project.
- *
- * @param int $projectID
- * @param object $project
- * @param object $oldProject
- *
- * @access protected
- * @return bool
- */
- protected function doClosed($projectID, $project, $oldProject)
- {
- $this->lang->error->ge = $this->lang->project->ge;
- $this->dao->update(TABLE_PROJECT)->data($project)
- ->autoCheck()
- ->check($this->config->project->close->requiredFields, 'notempty')
- ->checkIF($project->realEnd != '', 'realEnd', 'le', helper::today())
- ->checkIF($project->realEnd != '', 'realEnd', 'ge', $oldProject->realBegan)
- ->checkFlow()
- ->where('id')->eq($projectID)
- ->exec();
- return !dao::isError();
- }
- /**
- * 激活项目时更新项目的信息。
- * Update project table when activate a project.
- *
- * @param int $projectID
- * @param object $project
- * @access protected
- * @return bool
- */
- protected function doActivate($projectID ,$project)
- {
- $this->dao->update(TABLE_PROJECT)->data($project , 'readjustTime, readjustTask, comment')
- ->autoCheck()
- ->checkFlow()
- ->where('id')->eq((int)$projectID)
- ->exec();
- return !dao::isError();
- }
- /**
- * 更新项目主表。
- * Update project table when edit a project.
- *
- * @param int $projectID
- * @param object $project
- * @access protected
- * @return bool
- */
- public function doUpdate($projectID, $project)
- {
- $project->id = $projectID;
- $this->dao->update(TABLE_PROJECT)->data($project)
- ->autoCheck('begin,end')
- ->check('end', 'gt', $project->begin)
- ->checkIF(!empty($project->name), 'name', 'unique', "id != $projectID and `type` = 'project' and `parent` = '$project->parent' and `model` = " . $this->dao->sqlobj->quote($project->model) . " and `deleted` = '0'")
- ->checkIF(!empty($project->code), 'code', 'unique', "id != $projectID and `type` = 'project' and `parent` = '$project->parent' and `deleted` = '0'")
- ->checkFlow()
- ->where('id')->eq($projectID)
- ->exec();
- return !dao::isError();
- }
- /**
- * 新增项目。
- * Insert a project to project table.
- *
- * @param object $project
- * @access protected
- * @return bool
- */
- protected function doCreate($project)
- {
- $this->lang->error->unique = $this->lang->error->repeat;
- $this->dao->insert(TABLE_PROJECT)->data($project)
- ->autoCheck()
- ->batchCheck($this->config->project->create->requiredFields, 'notempty')
- ->checkIF(!empty($project->name), 'name', 'unique', "`type`='project' and `parent` = " . $this->dao->sqlobj->quote($project->parent) . " and `model` = " . $this->dao->sqlobj->quote($project->model) . " and `deleted` = '0'")
- ->checkIF(!empty($project->code), 'code', 'unique', "`type`='project' and `parent` = " . $this->dao->sqlobj->quote($project->parent) . " and `model` = " . $this->dao->sqlobj->quote($project->model) . " and `deleted` = '0'")
- ->checkIF($project->end != '', 'end', 'gt', $project->begin)
- ->checkFlow()
- ->exec();
- return !dao::isError();
- }
- /**
- * 获取未完成的任务。
- * Fetch undone tasks.
- *
- * @param int $projectID
- * @access protected
- * @return object[]
- */
- protected function fetchUndoneTasks($projectID)
- {
- return $this->dao->select('id,estStarted,deadline,status')->from(TABLE_TASK)
- ->where('deadline')->notZeroDate()
- ->andWhere('status')->in('wait,doing')
- ->andWhere('project')->eq($projectID)
- ->fetchAll();
- }
- /**
- * 更新任务的起止日期。
- * Update start and end date of tasks.
- *
- * @param array $tasks
- * @param object $oldProject
- * @param object $project
- * @access protected
- * @return bool
- */
- protected function updateTasksStartAndEndDate($tasks, $oldProject, $project)
- {
- $beginTimeStamp = strtotime($project->begin);
- foreach($tasks as $task)
- {
- if($task->status == 'wait' && !helper::isZeroDate($task->estStarted))
- {
- $taskDays = helper::diffDate($task->deadline, $task->estStarted);
- $taskOffset = helper::diffDate($task->estStarted, $oldProject->begin);
- $estStartedTimeStamp = $beginTimeStamp + $taskOffset * 24 * 3600;
- $estStarted = date('Y-m-d', $estStartedTimeStamp);
- $deadline = date('Y-m-d', $estStartedTimeStamp + $taskDays * 24 * 3600);
- if($estStarted > $project->end) $estStarted = $project->end;
- if($deadline > $project->end) $deadline = $project->end;
- $this->dao->update(TABLE_TASK)
- ->set('estStarted')->eq($estStarted)
- ->set('deadline')->eq($deadline)
- ->where('id')->eq($task->id)
- ->exec();
- if(dao::isError()) return false;
- }
- else
- {
- $taskOffset = helper::diffDate($task->deadline, $oldProject->begin);
- $deadline = date('Y-m-d', $beginTimeStamp + $taskOffset * 24 * 3600);
- if($deadline > $project->end) $deadline = $project->end;
- $this->dao->update(TABLE_TASK)->set('deadline')->eq($deadline)->where('id')->eq($task->id)->exec();
- if(dao::isError()) return false;
- }
- }
- return true;
- }
- /**
- * 插入项目团队成员。
- * Insert project members.
- *
- * @param array $members
- * @param int $projectID
- * @param array $oldJoin
- * @access protected
- * @return array
- */
- protected function insertMember($members, $projectID, $oldJoin)
- {
- $accounts = array();
- foreach($members as $member)
- {
- if(empty($member->account)) continue;
- $account = $member->account;
- $accounts[] = $account;
- $data = new stdclass();
- $data->role = $member->role;
- $data->days = $member->days;
- $data->hours = $member->hours;
- $data->limited = isset($member->limited) ? $member->limited : 'no';
- $data->root = $projectID;
- $data->account = $account;
- $data->join = isset($oldJoin[$account]) ? $oldJoin[$account] : helper::today();
- $data->type = 'project';
- $this->dao->insert(TABLE_TEAM)->data($data)->exec();
- }
- return $accounts;
- }
- /**
- * 更新项目团队成员的视图权限
- * Update member view when project member changed.
- *
- * @param int $projectID
- * @param array $accounts
- * @param array $oldJoin
- * @access protected
- * @return void
- */
- protected function updateMemberView($projectID, $accounts, $oldJoin)
- {
- /* Only changed account update userview. */
- $oldAccounts = array_keys($oldJoin);
- $removedAccounts = array_diff($oldAccounts, $accounts);
- $addedAccounts = array_diff($accounts, $oldAccounts);
- $changedAccounts = array_merge($removedAccounts, $addedAccounts);
- $changedAccounts = array_unique($changedAccounts);
- $childSprints = $this->dao->select('id')->from(TABLE_PROJECT)
- ->where('project')->eq($projectID)
- ->andWhere('type')->in('stage,sprint,kanban')
- ->andWhere('deleted')->eq('0')
- ->fetchPairs();
- $linkedProducts = $this->dao->select("t2.id")->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
- ->where('t2.deleted')->eq(0)
- ->andWhere('t1.project')->eq($projectID)
- ->andWhere('t2.vision')->eq($this->config->vision)
- ->fetchPairs();
- $this->loadModel('user')->updateUserView(array($projectID), 'project', $changedAccounts);
- if(!empty($childSprints)) $this->user->updateUserView($childSprints, 'sprint', $changedAccounts);
- if(!empty($linkedProducts)) $this->user->updateUserView(array_keys($linkedProducts), 'product', $changedAccounts);
- if(empty($addedAccounts) && empty($removedAccounts)) return;
- /* Log history. */
- $actionID = $this->loadModel('action')->create('project', $projectID, 'ManagedTeam');
- $users = $this->loadModel('user')->getPairs('noletter');
- $addedAccountList = array_map(function($account) use ($users) { return zget($users, $account); }, $addedAccounts);
- $removedAccountList = array_map(function($account) use ($users) { return zget($users, $account); }, $removedAccounts);
- if(!empty($addedAccountList)) $changes[] = array('field' => 'addDiff', 'old' => '', 'new' => '', 'diff' => join(',', $addedAccountList));
- if(!empty($removedAccountList)) $changes[] = array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => join(',', $removedAccountList));
- if(!empty($changes)) $this->action->logHistory($actionID, $changes);
- /* Remove execution members. */
- if($this->post->removeExecution == 'yes' and !empty($childSprints) and !empty($removedAccountList))
- {
- $childTeams = $this->dao->select('root,account')->from(TABLE_TEAM)
- ->where('root')->in($childSprints)
- ->andWhere('type')->eq('execution')
- ->andWhere('account')->in($removedAccounts)
- ->fetchGroup('root', 'account');
- $this->dao->delete()->from(TABLE_TEAM)
- ->where('root')->in($childSprints)
- ->andWhere('type')->eq('execution')
- ->andWhere('account')->in($removedAccounts)
- ->exec();
- foreach($childTeams as $sprintID => $childTeam)
- {
- $childAccounts = array_keys($childTeam);
- $removedAccountList = array_intersect($childAccounts, $removedAccounts);
- if(empty($removedAccountList)) continue;
- $removedAccountList = array_map(function($account) use ($users) { return zget($users, $account); }, $removedAccountList);
- $changes = array(array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => join(',', $removedAccountList)));
- $actionID = $this->action->create('execution', $sprintID, 'syncProjectTeam');
- $this->action->logHistory($actionID, $changes);
- }
- }
- }
- /**
- * 获取项目的详情,包含project表的所有内容。
- * Get project details, including all contents of the project.
- *
- * @param int $projectID
- * @param string $type
- * @access protected
- * @return object|false
- */
- protected function fetchProjectInfo($projectID, $type = '')
- {
- $project = $this->dao->select('*')
- ->from(TABLE_PROJECT)
- ->where('id')->eq($projectID)
- ->beginIF(!empty($type))->andWhere('`type`')->in($type)->fi()
- ->fetch();
- /* Filter the date is empty or 1970. */
- if($project && helper::isZeroDate($project->end)) $project->end = '';
- return $project;
- }
- /**
- * 创建项目后,创建默认的项目主库。
- * Create doclib after create a project.
- *
- * @param int $projectID
- * @param object $project
- * @param object $program
- * @access protected
- * @return bool
- */
- protected function createDocLib($projectID, $project, $program)
- {
- /* Create doc lib. */
- $this->app->loadLang('doc');
- $authorizedUsers = array();
- if($project->parent && $project->acl == 'program')
- {
- $stakeHolders = $this->loadModel('stakeholder')->getStakeHolderPairs($project->parent);
- $authorizedUsers = array_keys($stakeHolders);
- foreach(explode(',', $project->whitelist) as $user)
- {
- if(empty($user)) continue;
- $authorizedUsers[$user] = $user;
- }
- $authorizedUsers[$project->PM] = $project->PM;
- $authorizedUsers[$project->openedBy] = $project->openedBy;
- $authorizedUsers[$program->PM] = $program->PM;
- $authorizedUsers[$program->openedBy] = $program->openedBy;
- }
- $lib = new stdclass();
- $lib->project = $projectID;
- $lib->name = $this->lang->doclib->main['project'];
- $lib->type = 'project';
- $lib->main = '1';
- $lib->acl = 'default';
- $lib->users = ',' . implode(',', array_filter($authorizedUsers)) . ',';
- $lib->vision = zget($project, 'vision', 'rnd');
- $lib->addedBy = $this->app->user->account;
- $lib->addedDate = helper::now();
- $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
- return !dao::isError();
- }
- /**
- * 创建项目时,如果直接输入了产品名,则创建产品并与项目关联。
- * Create doclib after create a project.
- *
- * @param int $projectID
- * @param object $project
- * @param object $postData
- * @param object $program
- * @access protected
- * @return bool
- */
- protected function createProduct($projectID, $project, $postData, $program)
- {
- /* If parent not empty, link products or create products. */
- $product = new stdclass();
- $product->name = $project->hasProduct && !empty($postData->rawdata->productName) ? $postData->rawdata->productName : zget($project, 'name', '');
- $product->shadow = zget($project, 'vision', 'rnd') == 'rnd' ? (int)empty($project->hasProduct) : 1;
- $product->bind = $postData->rawdata->parent ? 0 : 1;
- $product->program = $project->parent ? current(array_filter(explode(',', zget($program, 'path', '')))) : 0;
- $product->acl = $project->acl == 'open' ? 'open' : 'private';
- $product->PO = $project->PM;
- $product->QD = '';
- $product->RD = '';
- $product->whitelist = '';
- $product->createdBy = $this->app->user->account;
- $product->createdDate = helper::now();
- $product->status = 'normal';
- $product->line = 0;
- $product->desc = '';
- $product->createdVersion = $this->config->version;
- $product->vision = zget($project, 'vision', 'rnd');
- if($this->config->edition != 'open') $product->workflowGroup = $this->dao->select('id')->from(TABLE_WORKFLOWGROUP)->where('code')->eq('productproject')->fetch('id');
- $this->app->loadLang('product');
- $this->dao->insert(TABLE_PRODUCT)->data($product)
- ->check('name', 'notempty')
- ->checkIF(!empty($product->name), 'name', 'unique', "`program` = {$product->program} and `deleted` = '0'")
- ->exec();
- if(dao::isError()) return false;
- $productID = $this->dao->lastInsertId();
- /* Update whitelist and user view. */
- if(!$project->hasProduct) $this->loadModel('personnel')->updateWhitelist(explode(',', $project->whitelist), 'product', $productID);
- $this->loadModel('action')->create('product', $productID, 'opened');
- $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($productID * 5)->where('id')->eq($productID)->exec();
- if($product->acl != 'open') $this->loadModel('user')->updateUserView(array($productID), 'product');
- $productSettingList = isset($this->config->global->productSettingList) ? json_decode($this->config->global->productSettingList, true) : array();
- if($this->config->edition != 'open' && $productSettingList)
- {
- $productSettingList[] = $productID;
- $this->loadModel('setting')->setItem('system.common.global.productSettingList', json_encode($productSettingList));
- }
- /* Link product. */
- $projectProduct = new stdclass();
- $projectProduct->project = $projectID;
- $projectProduct->product = $productID;
- $projectProduct->branch = 0;
- $projectProduct->plan = 0;
- $this->dao->insert(TABLE_PROJECTPRODUCT)->data($projectProduct)->exec();
- if(dao::isError()) return false;
- /* Create doc lib. */
- if($project->hasProduct) $this->createProductDocLib($productID);
- return !dao::isError();
- }
- /**
- * 创建产品后,创建默认的产品主库。
- * Create doclib after create a product.
- *
- * @param int $productID
- * @access protected
- * @return bool
- */
- protected function createProductDocLib($productID)
- {
- $this->app->loadLang('doc');
- $lib = new stdclass();
- $lib->product = $productID;
- $lib->name = $this->lang->doclib->main['product'];
- $lib->type = 'product';
- $lib->main = '1';
- $lib->acl = 'default';
- $lib->addedBy = $this->app->user->account;
- $lib->addedDate = helper::now();
- $this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
- return !dao::isError();
- }
- /**
- * 获取创建项目时选择的产品数量。
- * Get products count from post.
- *
- * @param object $project
- * @param object $rawdata
- * @access protected
- * @return int
- */
- protected function getLinkedProductsCount($project, $rawdata)
- {
- if(!isset($project) || !empty($project->newProject)) return 0;
- $linkedProductsCount = 0;
- if($project->hasProduct && isset($rawdata->products))
- {
- foreach($rawdata->products as $product)
- {
- if(empty($product)) continue;
- $linkedProductsCount ++;
- }
- }
- return $linkedProductsCount;
- }
- /**
- * 创建项目后,将项目创建者加到项目管理员分组。
- * Create project admin after create a project.
- *
- * @param int $projectID
- * @access protected
- * @return bool
- */
- protected function addProjectAdmin($projectID)
- {
- $projectAdmin = $this->dao->select('*')->from(TABLE_PROJECTADMIN)->where('account')->eq($this->app->user->account)->fetch();
- if(!empty($projectAdmin) && $projectAdmin->projects != 'all')
- {
- $newProject = $projectAdmin->projects . ",$projectID";
- $this->dao->update(TABLE_PROJECTADMIN)->set('projects')->eq($newProject)->where('account')->eq($projectAdmin->account)->andWhere('`group`')->eq($projectAdmin->group)->exec();
- }
- else
- {
- $maxGroupID = $this->dao->select('max(`group`) as maxGroupID')->from(TABLE_PROJECTADMIN)->fetch('maxGroupID');
- $projectAdmin = new stdclass();
- $projectAdmin->account = $this->app->user->account;
- $projectAdmin->group = (int)$maxGroupID + 1;
- $projectAdmin->projects = $projectID;
- $this->dao->replace(TABLE_PROJECTADMIN)->data($projectAdmin)->exec();
- }
- return !dao::isError();
- }
- /**
- * 生成project模块的项目下拉框跳转链接.
- * Build project link in project page.
- *
- * @param string $method
- * @access protected
- * @return string
- */
- protected function buildLinkForProject($method)
- {
- if($method == 'execution')
- return helper::createLink($module, $method, "status=all&projectID=%s");
- if($method == 'managePriv')
- return helper::createLink($module, 'group', "projectID=%s");
- if($method == 'showerrornone')
- return helper::createLink('projectstory', 'story', "projectID=%s");
- $methods = ',bug,testcase,testtask,testreport,build,dynamic,view,manageproducts,team,managemembers,whitelist,addwhitelist,group,';
- if(strpos($methods, ',' . $method . ',') !== false)
- return helper::createLink($module, $method, "projectID=%s");
- }
- /**
- * 生成bug模块的项目下拉框跳转链接.
- * Build project link in bug page.
- *
- * @param string $method
- * @access protected
- * @return string
- */
- protected function buildLinkForBug($method)
- {
- if($method == 'create')
- return helper::createLink($module, $method, "productID=0&branch=0&extras=projectID=%s");
- if($method == 'edit')
- return helper::createLink('project', 'bug', "projectID=%s");
- }
- /**
- * 生成story模块的项目下拉框跳转链接.
- * Build project link in story page.
- *
- * @param string $method
- * @access protected
- * @return string
- */
- protected function buildLinkForStory($method)
- {
- if($method == 'change' || $method == 'create')
- return helper::createLink('projectstory', 'story', "projectID=%s");
- if($method == 'zerocase')
- return helper::createLink('project', 'testcase', "projectID=%s");
- }
- /**
- * 构造批量更新项目的数据。
- * Build bathc update project data.
- *
- * @param array $data
- * @param array $oldProjects
- * @access public
- * @return array
- */
- public function buildBatchUpdateProjects($data, $oldProjects)
- {
- if(empty($data)) return array();
- $extendFields = $this->getFlowExtendFields();
- $projects = array();
- foreach($data as $projectID => $project)
- {
- $projectID = (int)$projectID;
- $projects[$projectID] = new stdClass();
- $projects[$projectID]->id = $projectID;
- $projects[$projectID]->name = $project->name;
- $projects[$projectID]->model = $oldProjects[$projectID]->model;
- $projects[$projectID]->PM = $project->PM;
- $projects[$projectID]->begin = $project->begin;
- $projects[$projectID]->end = $project->end == $this->lang->project->longTime ? LONG_TIME : $project->end;
- $projects[$projectID]->acl = $project->acl;
- $projects[$projectID]->days = $project->days;
- $projects[$projectID]->lastEditedBy = $this->app->user->account;
- $projects[$projectID]->lastEditedDate = helper::now();
- if(isset($project->parent)) $projects[$projectID]->parent = $project->parent;
- if(isset($project->code)) $projects[$projectID]->code = $project->code;
- if($project->end == $this->lang->project->longTime) $projects[$projectID]->days = 0;
- foreach($extendFields as $extendField)
- {
- $projects[$projectID]->{$extendField->field} = $project->{$extendField->field};
- if(is_array($projects[$projectID]->{$extendField->field})) $projects[$projectID]->{$extendField->field} = implode(',', $projects[$projectID]->{$extendField->field});
- $projects[$projectID]->{$extendField->field} = htmlSpecialString($projects[$projectID]->{$extendField->field});
- }
- }
- return $projects;
- }
- /**
- * 删除项目团队成员。
- * Delete project team member.
- *
- * @param array|int $projectIdList
- * @param string $type
- * @param string $account
- * @param string $realname
- * @param array $changes
- * @access protected
- * @return bool
- */
- protected function unlinkTeamMember($projectIdList, $type, $account, $realname, $changes)
- {
- $accountProjects = $this->dao->select('root')->from(TABLE_TEAM)
- ->where('root')->in($projectIdList)
- ->andWhere('type')->eq($type)
- ->andWhere('account')->eq($account)
- ->fetchPairs();
- $this->dao->delete()->from(TABLE_TEAM)
- ->where('root')->in($projectIdList)
- ->andWhere('type')->eq($type)
- ->andWhere('account')->eq($account)
- ->exec();
- $this->loadModel('action');
- foreach($accountProjects as $projectID)
- {
- $actionID = $this->action->create($type, $projectID, $type == 'project' ? 'managedTeam' : 'syncProjectTeam');
- $this->action->logHistory($actionID, $changes);
- }
- return !dao::isError();
- }
- /**
- * 对项目按照我的、其它的和关闭的进行分类。
- * Classify projects according to my, other and closed.
- *
- * @param array $projects
- * @access protected
- * @return array
- */
- protected function classifyProjects($projects)
- {
- $classifiedProjects = array('myProjects' => array(), 'otherProjects' => array(), 'closedProjects' => array());
- foreach($projects as $project)
- {
- if(strpos('wait,doing,closed', $project->status) === false) continue;
- /* Convert predefined HTML entities to characters. */
- $project->name = htmlspecialchars_decode($project->name, ENT_QUOTES);
- $projectPath = explode(',', trim($project->path, ','));
- $topProgram = !empty($project->parent) ? $projectPath[0] : $project->parent;
- if(!helper::hasFeature('program')) $topProgram = 0;
- if($project->PM == $this->app->user->account)
- {
- if($project->status != 'closed')
- {
- $classifiedProjects['myProjects'][$topProgram][$project->status][] = $project;
- }
- else
- {
- $classifiedProjects['closedProjects']['my'][$topProgram][$project->closedDate] = $project;
- }
- }
- else
- {
- if($project->status != 'closed')
- {
- $classifiedProjects['otherProjects'][$topProgram][$project->status][] = $project;
- }
- else
- {
- $classifiedProjects['closedProjects']['other'][$topProgram][$project->closedDate] = $project;
- }
- }
- }
- return $classifiedProjects;
- }
- /**
- * 对groups列表进行排序和缩减。
- * Sort and reduce projects.
- *
- * @param array $projectsStats
- * @param int $retainNum
- * @access protected
- * @return array
- */
- protected function sortAndReduceClosedProjects($projectsStats, $retainNum = 2)
- {
- $sortedAndReducedProjects = array('my' => $projectsStats['myProjects'], 'other' => $projectsStats['otherProjects']);
- $closedProjects = $projectsStats['closedProjects'];
- foreach($closedProjects as $group => $groupedProjects)
- {
- foreach($groupedProjects as $topProgram => $projects)
- {
- krsort($projects);
- if($retainNum > 0)
- {
- $sortedAndReducedProjects[$group][$topProgram]['closed'] = array_slice($projects, 0, $retainNum);
- }
- }
- }
- return $sortedAndReducedProjects;
- }
- /**
- * 根据项目集ID查询所有项目集的层级。
- * Get all program level of a program.
- *
- * @param int $program
- * @param string $path
- * @param int $grade
- * @access protected
- * @return string
- */
- protected function getParentProgram($path, $grade)
- {
- $programList = $this->dao->select('id,name')->from(TABLE_PROGRAM)
- ->where('id')->in(trim($path, ','))
- ->andWhere('grade')->lt($grade)
- ->orderBy('grade asc')
- ->fetchPairs();
- return implode('/', $programList);
- }
- /**
- * 根据状态和和我参与的查询项目列表。
- * Get project list by status and with my participation.
- *
- * @param string $status
- * @param string $orderBy
- * @param bool $involved
- * @param object $pager
- * @access protected
- * @return array
- */
- protected function fetchProjectList($status, $orderBy, $involved, $pager)
- {
- $query = '';
- if($status == 'bysearch' && $this->session->projectQuery !== false) $query = $this->session->projectQuery;
- $query = str_replace('`id`','t1.id', $query);
- return $this->dao->select('DISTINCT t1.*')->from(TABLE_PROJECT)->alias('t1')
- ->leftJoin(TABLE_TEAM)->alias('t2')->on('t1.id=t2.root')
- ->leftJoin(TABLE_STAKEHOLDER)->alias('t3')->on('t1.id=t3.objectID')
- ->where('t1.deleted')->eq('0')
- ->andWhere('t1.vision')->eq($this->config->vision)
- ->andWhere('t1.type')->eq('project')
- ->beginIF(!in_array($status, array('all', 'undone', 'review', 'unclosed', 'delayed', 'bysearch'), true))->andWhere('t1.status')->eq($status)->fi()
- ->beginIF(in_array($status, array('undone', 'unclosed')))->andWhere('t1.status')->in('wait,doing')->fi()
- ->beginIF($status == 'delayed')->andWhere('t1.status')->notIn('done,closed,suspend')->andWhere('t1.end')->lt(helper::today())->fi()
- ->beginIF($status == 'review')
- ->andWhere("FIND_IN_SET('{$this->app->user->account}', t1.reviewers)")
- ->andWhere('t1.reviewStatus')->eq('doing')
- ->fi()
- ->beginIF($this->cookie->involved || $involved)
- ->andWhere('t2.type')->eq('project')
- ->andWhere('t1.openedBy', true)->eq($this->app->user->account)
- ->orWhere('t1.PM')->eq($this->app->user->account)
- ->orWhere('t2.account')->eq($this->app->user->account)
- ->orWhere('(t3.user')->eq($this->app->user->account)
- ->andWhere('t3.deleted')->eq(0)
- ->markRight(1)
- ->orWhere("CONCAT(',', t1.whitelist, ',')")->like("%,{$this->app->user->account},%")
- ->markRight(1)
- ->fi()
- ->beginIF($status == 'bysearch' && $query)->andWhere($query)->fi()
- ->orderBy($orderBy)
- ->page($pager, 't1.id')
- ->fetchAll('id');
- }
- /**
- * 通过条件查询和数量限制查询项目列表。
- * Get project list by query and with limit.
- *
- * @param string $status
- * @param int $projectID
- * @param string $orderBy
- * @param int $limit
- * @param string $excludedModel
- * @access protected
- * @return array
- */
- protected function fetchProjectListByQuery($status = 'all', $projectID = 0, $orderBy = 'order_asc', $limit = 0, $excludedModel = '')
- {
- return $this->dao->select('*')->from(TABLE_PROJECT)
- ->where('type')->eq('project')
- ->andWhere('vision')->eq($this->config->vision)
- ->andWhere('deleted')->eq(0)
- ->beginIF($projectID)->andWhere('hasProduct')->eq('1')->fi()
- ->beginIF($projectID)->andWhere('multiple')->eq('1')->fi()
- ->beginIF($excludedModel)->andWhere('model')->ne($excludedModel)->fi()
- ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
- ->beginIF($status == 'undone')->andWhere('status')->notIN('done,closed')->fi()
- ->beginIF($status == 'unclosed')->andWhere('status')->ne('closed')->fi()
- ->beginIF($status && !in_array($status, array('all', 'undone', 'unclosed', 'involved')))->andWhere('status')->eq($status)->fi()
- ->beginIF($status == 'involved')
- ->andWhere('openedBy', true)->eq($this->app->user->account)
- ->orWhere('PM')->eq($this->app->user->account)
- ->orWhere("CONCAT(',', whitelist, ',')")->like("%,{$this->app->user->account},%")
- ->markRight(1)
- ->fi()
- ->beginIF($projectID)->orWhere('id')->eq($projectID)->fi()
- ->orderBy($orderBy)
- ->beginIF($limit)->limit($limit)->fi()
- ->fetchAll('id');
- }
- /**
- * 根据项目ID列表查询团队成员数量。
- * Get project team member count by project id list.
- *
- * @param array $projectIdList
- * @access protected
- * @return array
- */
- protected function fetchMemberCountByIdList($projectIdList)
- {
- return $this->dao->select('t1.root, count(t1.id) as count')->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
- ->where('t1.root')->in($projectIdList)
- ->andWhere('t2.deleted')->eq(0)
- ->groupBy('t1.root')
- ->fetchPairs();
- }
- /**
- * 根据项目ID列表查询任务的总预计工时。
- * Get task all estimate by project id list.
- *
- * @param array $projectIdList
- * @param string $fields
- * @access protected
- * @return array
- */
- protected function fetchTaskEstimateByIdList($projectIdList, $fields = 'estimate')
- {
- $fields = explode(',', $fields);
- $selectFields = 't2.project';
- foreach($fields as $field) $selectFields .= ", ROUND(SUM(t1.{$field}), 1) AS {$field}";
- return $this->dao->select($selectFields)->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
- ->where('t1.parent')->lt(1)
- ->andWhere('t2.project')->in($projectIdList)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->groupBy('t2.project')
- ->fetchAll('project');
- }
- /**
- * 通过项目ID列表查询需求的数量。
- * Get the number of stories associated with the project.
- *
- * @param array $projectIdList
- * @access protected
- * @return array
- */
- protected function getTotalStoriesByProject($projectIdList)
- {
- return $this->dao->select("t1.project, count(t2.id) as `allStories`, count(if(t2.status = 'active' or t2.status = 'changing', 1, null)) as `leftStories`, count(if(t2.status = 'closed' and t2.`closedReason` = 'done', 1, null)) as `doneStories`")->from(TABLE_PROJECTSTORY)->alias('t1')
- ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id')
- ->where('t1.project')->in($projectIdList)
- ->andWhere('deleted')->eq('0')
- ->groupBy('project')
- ->fetchAll('project');
- }
- /**
- * 删除团队成员。
- * Delete Members.
- *
- * @param int $projectID
- * @param string $openedBy
- * @param array $deleteMembers
- * @access protected
- * @return bool
- */
- protected function deleteMembers($projectID, $openedBy, $deleteMembers)
- {
- $this->dao->delete()->from(TABLE_TEAM)
- ->where('root')->eq($projectID)
- ->andWhere('type')->eq('project')
- ->andWhere('account')->in($deleteMembers)
- ->andWhere('account')->ne($openedBy)
- ->exec();
- return !dao::isError();
- }
- /**
- * 通过项目ID获取任务数量统计。
- * Get the number of tasks associated with the project.
- *
- * @param array $projectIdList
- * @access protected
- * @return array
- */
- protected function getTotalTaskByProject($projectIdList)
- {
- $executionIdList = $this->dao->select('id')->from(TABLE_EXECUTION)->where('project')->in($projectIdList)->andWhere('deleted')->eq(0)->fetchPairs();
- return $this->dao->select("project, count(id) as allTasks, count(if(status = 'wait', 1, null)) as waitTasks, count(if(status = 'doing', 1, null)) as doingTasks, count(if(status = 'done', 1, null)) as doneTasks, count(if(status = 'wait' or status = 'pause' or status = 'cancel', 1, null)) as leftTasks, count(if(status = 'done' or status = 'closed', 1, null)) as litedoneTasks")->from(TABLE_TASK)
- ->where('execution')->in($executionIdList)
- ->andWhere('deleted')->eq(0)
- ->groupBy('project')
- ->fetchAll('project');
- }
- /**
- * 返回请求被拒绝的跳转信息。
- * return accessDenied response.
- *
- * @access protected
- * @return string
- */
- protected function accessDenied()
- {
- $this->session->set('project', '0');
- return $this->app->control->sendError($this->lang->project->accessDenied, helper::createLink('project', 'browse'));
- }
- /**
- * 修改无迭代项目下执行的状态。
- * Modify the execution status when changing the status of no execution project.
- *
- * @param int $projectID
- * @param string $status
- * @access protected
- * @return array|false|int
- */
- protected function changeExecutionStatus($projectID, $status)
- {
- if(!in_array($status, array('start', 'suspend', 'activate', 'close'))) return false;
- $execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('project')->eq($projectID)->andWhere('multiple')->eq('0')->fetch();
- if(!$execution) return false;
- $project = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch();
- $postData = new stdclass();
- $postData->status = $status;
- $postData->begin = $execution->begin;
- $postData->end = $execution->end;
- $postData->uid = '';
- $postData->comment = '';
- if($status == 'close') $postData->realEnd = $project->realEnd;
- return $this->loadModel('execution')->$status($execution->id, $postData);
- }
- /**
- * 通过项目ID获取Bug数量统计。
- * Get the number of bugs associated with the project.
- *
- * @param array $projectIdList
- * @access protected
- * @return array
- */
- protected function getTotalBugByProject($projectIdList)
- {
- return $this->dao->select("project, count(id) AS allBugs, count(if(status = 'active', 1, null)) AS leftBugs, count(if(status = 'resolved', 1, null)) AS doneBugs")->from(TABLE_BUG)
- ->where('project')->in($projectIdList)
- ->andWhere('deleted')->eq(0)
- ->groupBy('project')
- ->fetchAll('project');
- }
- /**
- * 根据项目模式设置菜单。
- * Set menu by project model.
- *
- * @param string $projectModel
- * @access protected
- * @return bool
- */
- protected function setMenuByModel($projectModel)
- {
- global $lang, $config, $app;
- $model = 'scrum';
- if(in_array($projectModel, $this->config->project->waterfallList))
- {
- $model = 'waterfall';
- $lang->project->createExecution = str_replace($lang->executionCommon, $lang->project->stage, $lang->project->createExecution);
- $lang->project->lastIteration = str_replace($lang->executionCommon, $lang->project->stage, $lang->project->lastIteration);
- $this->loadModel('execution');
- $executionCommonLang = $lang->executionCommon;
- $lang->executionCommon = $lang->project->stage;
- include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
- $lang->execution->typeList['sprint'] = $executionCommonLang;
- if($app->rawModule == 'project' && $app->rawMethod == 'executionreport') $lang->waterfall->menu->execution['subModule'] .= ',project';
- }
- elseif($projectModel == 'kanban')
- {
- $model = 'kanbanProject';
- $lang->executionCommon = $lang->project->kanban;
- }
- if(isset($lang->$model))
- {
- $key = 'project-' . $model;
- if(isset($config->customMenu->$key))
- {
- $lang->{$model}->menuOrder = array();
- $lang->{$model}->dividerMenu = '';
- $items = json_decode($config->customMenu->$key);
- $prevItem = '';
- foreach($items as $item)
- {
- $lang->{$model}->menuOrder[$item->order] = $item->name;
- if($prevItem == 'divider') $lang->{$model}->dividerMenu .= $item->name . ',';
- $prevItem = $item->name;
- }
- if($lang->{$model}->dividerMenu) $lang->{$model}->dividerMenu = ',' . trim($lang->{$model}->dividerMenu, ',') . ',';
- }
- if($this->config->inCompose)
- {
- $repoServers = $this->loadModel('pipeline')->getPairs($this->config->pipeline->checkRepoServers);
- if(empty($repoServers)) unset($lang->{$model}->menu->devops);
- }
- $lang->project->menu = $lang->{$model}->menu;
- $lang->project->menuOrder = $lang->{$model}->menuOrder;
- $lang->project->dividerMenu = $lang->{$model}->dividerMenu;
- }
- return true;
- }
- /**
- * 根据是否产品型项目设置菜单。
- * Set menu by whether product project.
- *
- * @param int $projectID
- * @param int $hasProduct
- * @param string $model
- * @access protected
- * @return bool
- */
- protected function setMenuByProduct($projectID, $hasProduct, $model)
- {
- global $lang;
- if($hasProduct)
- {
- unset($lang->project->menu->projectplan);
- unset($lang->project->menu->settings['subMenu']->module);
- }
- $projectProduct = (int)$this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetch('product');
- if(isset($lang->project->menu->settings['subMenu']->module['link'])) $lang->project->menu->settings['subMenu']->module['link'] = sprintf($lang->project->menu->settings['subMenu']->module['link'], $projectProduct);
- if(!$hasProduct && in_array($model, $this->config->project->scrumList) && isset($lang->project->menu->projectplan)) $lang->project->menu->projectplan['link'] = sprintf($lang->project->menu->projectplan['link'], $projectProduct);
- if(!$hasProduct) unset($lang->project->menu->settings['subMenu']->products);
- if(!in_array($model, $this->config->project->scrumList)) unset($lang->project->menu->projectplan);
- return true;
- }
- /**
- * 根据访问模块的导航组设置菜单。
- * Set menu lang for module.
- *
- * @param string $navGroup
- * @param int $executionID
- * @param object $project
- * @access protected
- * @return bool
- */
- protected function setNavGroupMenu($navGroup, $executionID, $project)
- {
- global $lang;
- foreach($lang->$navGroup->menu as $label => $menu)
- {
- $objectID = 0;
- if(strpos($this->config->project->multiple['project'], ",{$label},") !== false) $objectID = $project->id;
- if(strpos($this->config->project->multiple['execution'], ",{$label},") !== false)
- {
- $objectID = $executionID;
- $lang->$navGroup->menu->{$label}['subModule'] = 'project';
- }
- $lang->$navGroup->menu->$label = commonModel::setMenuVarsEx($menu, $objectID);
- if(isset($menu['subMenu']))
- {
- foreach($menu['subMenu'] as $key1 => $subMenu) $lang->$navGroup->menu->{$label}['subMenu']->$key1 = common::setMenuVarsEx($subMenu, $objectID);
- }
- if(!isset($menu['dropMenu'])) continue;
- foreach($menu['dropMenu'] as $key2 => $dropMenu)
- {
- if(strpos($this->config->project->multiple['project'], ",{$label},") !== false) $objectID = $project->id;
- if(strpos($this->config->project->multiple['execution'], ",{$key2},") !== false)
- {
- $objectID = $executionID;
- $lang->$navGroup->menu->{$label}['subModule'] = 'project';
- }
- $lang->$navGroup->menu->{$label}['dropMenu']->$key2 = common::setMenuVarsEx($dropMenu, $objectID);
- if(!isset($dropMenu['subMenu'])) continue;
- foreach($dropMenu['subMenu'] as $key3 => $subMenu) $lang->$navGroup->menu->{$label}['dropMenu']->$key3 = common::setMenuVarsEx($subMenu, $objectID);
- }
- }
- return true;
- }
- /**
- * 创建里程碑报告。
- *
- * @param int $projectID
- * @access protected
- * @return bool
- */
- protected function createMilestoneReport($projectID)
- {
- $reportID = $this->dao->select('id')->from(TABLE_DOC)->where('project')->eq($projectID)->andWhere('reportModule')->eq('milestone')->andWhere('type')->eq('projectReport')->fetch('id');
- if(!empty($reportID)) return true;
- $report = new stdclass();
- $report->title = $this->lang->project->milestoneReport;
- $report->project = $projectID;
- $report->reportModule = 'milestone';
- $report->templateType = 'projectReport';
- $report->addedBy = 'system';
- $report->addedDate = helper::now();
- $this->dao->insert(TABLE_DOC)->data($report)->exec();
- return !dao::isError();
- }
- }
|