znt/module/execution/zen.php

2300 lines
95 KiB
PHP

<?php
declare(strict_types=1);
/**
* The zen file of execution 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 Shujie Tian<tianshujie@easycorp.ltd>
* @package execution
* @link https://www.zentao.net
*/
class executionZen extends execution
{
/**
* 展示Bug列表的相关变量。
* Show the bug list related variables.
*
* @param object $execution
* @param object $project
* @param int $productID
* @param string $branch
* @param array $products
* @param string $orderBy
* @param string $type
* @param string $build
* @param int $param
* @param array $bugs
* @param object $pager
* @access protected
* @return void
*/
protected function assignBugVars(object $execution, object $project, int $productID, string $branch, array $products, string $orderBy, string $type, int $param, string $build, array $bugs, object $pager)
{
$this->loadModel('product');
$this->loadModel('tree');
$moduleID = $type == 'bymodule' ? $param : 0;
/* Get module tree.*/
$extra = array('projectID' => $execution->id, 'orderBy' => $orderBy, 'type' => 'byModule', 'build' => $build, 'branchID' => $branch);
if($execution->id and empty($productID) and count($products) > 1)
{
$moduleTree = $this->tree->getBugTreeMenu($execution->id, $productID, 0, array('treeModel', 'createBugLink'), $extra);
}
elseif(!empty($products))
{
$productID = empty($productID) ? reset($products)->id : $productID;
$moduleTree = $this->tree->getTreeMenu((int)$productID, 'bug', 0, array('treeModel', 'createBugLink'), $extra + array('branchID' => $branch, 'productID' => $productID), $branch);
}
else
{
$moduleTree = array();
}
$tree = $moduleID ? $this->tree->getByID($moduleID) : '';
$showModule = !empty($this->config->execution->bug->showModule) ? $this->config->execution->bug->showModule : '';
$build = !empty($build) ? $this->loadModel('build')->getById((int)$build) : null;
/* Assign. */
$this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->bug;
$this->view->project = $project;
$this->view->orderBy = $orderBy;
$this->view->type = $type;
$this->view->pager = $pager;
$this->view->bugs = $bugs;
$this->view->summary = $this->loadModel('bug')->summary($bugs);
$this->view->moduleTree = $moduleTree;
$this->view->moduleID = $moduleID;
$this->view->modulePairs = $showModule ? $this->tree->getModulePairs($productID, 'bug', $showModule) : array();
$this->view->buildID = $build ? $build->id : 0;
$this->view->productID = $productID;
$this->view->product = $this->product->getByID($productID);
$this->view->branchID = empty($this->view->build->branch) ? $branch : $this->view->build->branch;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->param = $param;
$this->view->defaultProduct = (empty($productID) and !empty($products)) ? current(array_keys($products)) : $productID;
$this->view->builds = $this->loadModel('build')->getBuildPairs(array($productID));
$this->view->projectPairs = $this->loadModel('project')->getPairsByProgram();
$this->view->switcherObjectID = $productID;
}
/**
* 展示看板的相关变量。
* Show the variables associated with the kanban.
*
* @param int $executionID
* @access protected
* @return void
*/
protected function assignKanbanVars(int $executionID)
{
/* Get user list. */
$userList = array();
$users = $this->loadModel('user')->getPairs('noletter|nodeleted');
$avatarPairs = $this->user->getAvatarPairs('all');
foreach($avatarPairs as $account => $avatar)
{
if(!isset($users[$account])) continue;
$userList[$account]['realname'] = $users[$account];
$userList[$account]['avatar'] = $avatar;
}
$userList['closed']['account'] = 'Closed';
$userList['closed']['realname'] = 'Closed';
$userList['closed']['avatar'] = '';
/* Get execution linked products. */
$productID = 0;
$branchID = 0;
$products = $this->loadModel('product')->getProducts($executionID);
$productNames = array();
if($products)
{
$productID = key($products);
$branches = $this->loadModel('branch')->getPairs($productID, '', $executionID);
if($branches) $branchID = key($branches);
}
foreach($products as $product) $productNames[$product->id] = $product->name;
/* Get execution linked plans. */
$plans = $this->execution->getPlans(array_keys($products), 'skipParent', $executionID);
$allPlans = array();
if(!empty($plans))
{
foreach($plans as $plan) $allPlans += $plan;
}
if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
$this->view->users = $users;
$this->view->userList = $userList;
$this->view->productID = $productID;
$this->view->branchID = $branchID;
$this->view->productNames = $productNames;
$this->view->productNum = count($products);
$this->view->allPlans = $allPlans;
$this->view->isLimited = !$this->app->user->admin && strpos(",{$this->session->limitedExecutions},", ",{$executionID},") !== false;
}
/**
* 展示维护产品相关变量。
* Show the manage products related variables.
*
* @param object $execution
* @access protected
* @return void
*/
protected function assignManageProductsVars(object $execution)
{
$branches = $this->project->getBranchesByProject($execution->id);
$linkedProductIdList = empty($branches) ? array() : array_keys($branches);
$allProducts = $this->loadModel('product')->getProductPairsByProject($execution->project, 'all', implode(',', $linkedProductIdList));
$linkedProducts = $this->product->getProducts($execution->id, 'all', '', true, $linkedProductIdList);
$linkedBranches = array();
$executionStories = $this->project->getStoriesByProject($execution->id);
/* If the story of the product which linked the execution, you don't allow to remove the product. */
$unmodifiableProducts = array();
$unmodifiableBranches = array();
$linkedStoryIDList = array();
$linkedBranchIdList = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
$linkedBranches[$productID] = array();
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranches[$productID][$branchID] = $branchID;
$linkedBranchIdList[$branchID] = $branchID;
if(!empty($executionStories[$productID][$branchID]))
{
array_push($unmodifiableProducts, $productID);
array_push($unmodifiableBranches, $branchID);
$linkedStoryIDList[$productID][$branchID] = $executionStories[$productID][$branchID]->storyIDList;
}
}
}
$this->view->title = $this->lang->execution->manageProducts . $this->lang->hyphen . $execution->name;
$this->view->execution = $execution;
$this->view->linkedProducts = $linkedProducts;
$this->view->unmodifiableProducts = $unmodifiableProducts;
$this->view->unmodifiableBranches = $unmodifiableBranches;
$this->view->linkedBranches = $linkedBranches;
$this->view->linkedStoryIDList = $linkedStoryIDList;
$this->view->allProducts = $allProducts;
$this->view->branchGroups = $this->execution->getBranchByProduct(array_keys($allProducts), $execution->project, 'ignoreNormal|noclosed', $linkedBranchIdList);
$this->view->allBranches = $this->execution->getBranchByProduct(array_keys($allProducts), $execution->project, 'ignoreNormal');
$this->display();
}
/**
* 展示需求关联的任务、Bug、用例的数量以及统计需求信息。
* Show the number of tasks, bugs cases linked with the stories, and statistics story information.
*
* @param int $executionID
* @param array $stories
* @param string $storyType
* @access protected
* @return void
*/
protected function assignCountForStory(int $executionID, array $stories, string $storyType): void
{
/* Get related tasks, bugs, cases count of each story. */
$storyIdList = array();
foreach($stories as $story)
{
$storyIdList[$story->id] = $story->id;
if(empty($story->children)) continue;
foreach($story->children as $child) $storyIdList[$child->id] = $child->id;
}
$this->view->stories = $stories;
$this->view->storyTasks = $this->loadModel('task')->getStoryTaskCounts($storyIdList, $executionID);
$this->view->storyBugs = $this->loadModel('bug')->getStoryBugCounts($storyIdList, $executionID);
$this->view->storyCases = $this->loadModel('testcase')->getStoryCaseCounts($storyIdList);
$this->view->summary = $this->loadModel('product')->summary($stories, 'all');
}
/**
* 展示需求列表的相关变量。
* Show the story list related variables.
*
* @param object $execution
* @param array $products
* @param int $productID
* @param string $type
* @param string $storyType
* @param int $param
* @param string $orderBy
* @param object $pager
* @access protected
* @return void
*/
protected function assignRelationForStory(object $execution, array $products, int $productID, string $type, string $storyType, int $param, string $orderBy, object $pager): void
{
$plans = $this->execution->getPlans(array_keys($products), 'skipParent|withMainPlan|unexpired|noclosed|sortedByDate', $execution->id);
$allPlans = array();
if(!empty($plans))
{
foreach($plans as $plan) $allPlans += $plan;
}
if($this->cookie->storyProductParam) $this->view->product = $this->loadModel('product')->getById((int)$this->cookie->storyProductParam);
if($this->cookie->storyBranchParam)
{
$branchID = $this->cookie->storyBranchParam;
if(strpos($branchID, ',') !== false) list($productID, $branchID) = explode(',', $branchID);
$this->view->branch = $this->loadModel('branch')->getById($branchID, $productID);
}
$executionProductList = $this->loadModel('product')->getProducts($execution->id);
$multiBranch = false;
foreach($executionProductList as $executionProduct)
{
if($executionProduct->type != 'normal')
{
$multiBranch = true;
break;
}
}
$project = $this->loadModel('project')->getByID($execution->project);
if(!($execution->type == 'stage' && in_array($execution->attribute, array('mix', 'request', 'design'))) && $project->multiple) $project->storyType = 'story';
$productPairs = $this->loadModel('product')->getProductPairsByProject($execution->id); // Get execution's product.
if(empty($productID)) $productID = (int)key($productPairs);
$this->assignModuleForStory($type, $param, $storyType, $execution, $productID);
$gradeList = $this->loadModel('story')->getGradeList('');
$gradeGroup = array();
foreach($gradeList as $grade) $gradeGroup[$grade->type][$grade->grade] = $grade->name;
/* Assign. */
$this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->story;
$this->view->storyType = $storyType;
$this->view->param = $param;
$this->view->type = $this->session->executionStoryBrowseType;
$this->view->orderBy = $orderBy;
$this->view->pager = $pager;
$this->view->product = $this->product->getById($productID);
$this->view->allPlans = $allPlans;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->multiBranch = $multiBranch;
$this->view->execution = $execution;
$this->view->gradeMenu = $this->loadModel('story')->getGradeMenu($storyType, $project);
$this->view->maxGradeGroup = $this->story->getMaxGradeGroup();
$this->view->gradeGroup = $gradeGroup;
$this->view->showGrades = isset($this->config->execution->showGrades) ? $this->config->execution->showGrades : $this->story->getDefaultShowGrades($this->view->gradeMenu);
$this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
$this->view->branchPairs = $this->loadModel('branch')->getPairs($productID, 'withClosed');
$this->view->linkedTaskStories = $this->loadModel('story')->getIdListWithTask($execution->id);
}
/**
* 展示需求列表的模块变量。
* Show the story list module variables.
*
* @param string $type
* @param int $param
* @param string $storyType
* @param object $execution
* @param int $productID
* @access protected
* @return void
*/
protected function assignModuleForStory(string $type, int $param, string $storyType, object $execution, int $productID): void
{
$this->loadModel('tree');
if($this->cookie->storyModuleParam) $this->view->module = $this->loadModel('tree')->getById((int)$this->cookie->storyModuleParam);
$showModule = !empty($this->config->execution->story->showModule) ? $this->config->execution->story->showModule : '';
$modulePairs = $showModule ? $this->tree->getModulePairs($type == 'byproduct' ? $param : 0, 'story', $showModule) : array();
$createModuleLink = $storyType == 'story' ? 'createStoryLink' : 'createRequirementLink';
if(!$execution->hasProduct && !$execution->multiple)
{
$moduleTree = $this->tree->getTreeMenu($productID, 'story', 0, array('treeModel', $createModuleLink), array('executionID' => $execution->id, 'productID' => $productID), '', "&param=$param&storyType=$storyType");
}
else
{
$moduleTree = $this->tree->getProjectStoryTreeMenu($execution->id, 0, array('treeModel', $createModuleLink), array('storyType' => $storyType));
}
$this->view->moduleTree = $moduleTree;
$this->view->modulePairs = $modulePairs;
$this->view->moduleID = $type == 'bymodule' ? $param : 0;
}
/**
* 展示任务看板的相关变量。
* Show the task Kanban related variables.
*
* @param object $execution
* @access protected
* @return void
*/
protected function assignTaskKanbanVars(object $execution)
{
/* Get user list. */
$userList = array();
$users = $this->loadModel('user')->getPairs('noletter|nodeleted');
$avatarPairs = $this->user->getAvatarPairs('all');
foreach($avatarPairs as $account => $avatar)
{
if(!isset($users[$account])) continue;
$userList[$account]['realname'] = $users[$account];
$userList[$account]['avatar'] = $avatar;
}
$userList['closed']['account'] = 'Closed';
$userList['closed']['realname'] = 'Closed';
$userList['closed']['avatar'] = '';
/* Get execution linked products. */
$productID = 0;
$productNames = array();
$products = $this->loadModel('product')->getProducts($execution->id);
if($products) $productID = key($products);
foreach($products as $product) $productNames[$product->id] = $product->name;
$plans = $this->execution->getPlans(array_keys($products), 'skipParent|withMainPlan|unexpired|noclosed|sortedByDate', $execution->id);
$allPlans = array();
if(!empty($plans))
{
foreach($plans as $plan) $allPlans += $plan;
}
if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
$project = $this->project->getByID($execution->project);
$this->view->hasFrozenStories = $this->project->hasFrozenObject($execution->project, 'SRS');
$this->view->title = $this->lang->execution->kanban;
$this->view->userList = $userList;
$this->view->realnames = $users;
$this->view->productID = $productID;
$this->view->productNames = $productNames;
$this->view->productNum = count($products);
$this->view->allPlans = $allPlans;
$this->view->hiddenPlan = $project->model !== 'scrum';
$this->view->execution = $execution;
$this->view->project = $project;
$this->view->canBeChanged = common::canModify('execution', $execution);
$this->view->isLimited = !$this->app->user->admin && strpos(",{$this->session->limitedExecutions},", ",{$execution->id},") !== false;
}
/**
* 展示用例列表的相关变量。
* Show the case list related variables.
*
* @param int $executionID
* @param int $productID
* @param string $branchID
* @param int $moduleID
* @param int $param
* @param string $orderBy
* @param string $type
* @param object $pager
* @access protected
* @return void
*/
protected function assignTestcaseVars(int $executionID, int $productID, string $branchID, int $moduleID, int $param, string $orderBy, string $type, object $pager)
{
$this->loadModel('tree');
/* Get cases. */
$cases = $this->loadModel('testcase')->getExecutionCases($type, $executionID, $productID, $branchID, $moduleID, $param, $orderBy, $pager);
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false);
$cases = $this->testcase->appendData($cases, 'case');
$cases = $this->loadModel('story')->checkNeedConfirm($cases);
/* Get module tree.*/
if($executionID and empty($productID))
{
$moduleTree = $this->tree->getCaseTreeMenu($executionID, $productID, 0, array('treeModel', 'createCaseLink'));
}
else
{
$moduleTree = $this->tree->getTreeMenu($productID, 'case', 0, array('treeModel', 'createCaseLink'), array('projectID' => $executionID, 'productID' => $productID, 'branchID' => $branchID), $branchID);
}
$tree = $moduleID ? $this->tree->getByID($moduleID) : '';
$showModule = !empty($this->config->execution->testcase->showModule) ? $this->config->execution->testcase->showModule : '';
$this->view->cases = $cases;
$this->view->scenes = $this->testcase->getSceneMenu($productID, $moduleID);;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->title = $this->lang->execution->testcase;
$this->view->executionID = $executionID;
$this->view->productID = $productID;
$this->view->product = $this->loadModel('product')->getByID((int)$productID);
$this->view->orderBy = $orderBy;
$this->view->pager = $pager;
$this->view->type = $type;
$this->view->branchID = $branchID;
$this->view->branchTagOption = $this->loadModel('branch')->getPairs($productID, 'withClosed');
$this->view->recTotal = $pager->recTotal;
$this->view->showBranch = $this->loadModel('branch')->showBranch($productID);
$this->view->stories = array( 0 => '') + $this->loadModel('story')->getPairs($productID);
$this->view->moduleTree = $moduleTree;
$this->view->moduleID = $moduleID;
$this->view->moduleName = $moduleID ? $tree->name : $this->lang->tree->all;
$this->view->modulePairs = $showModule ? $this->tree->getModulePairs($productID, 'bug', $showModule) : array();
}
/**
* 展示测试单的相关变量。
* Show the testtask related variables.
*
* @param array $tasks
* @access protected
* @return void
*/
protected function assignTesttaskVars(array $tasks)
{
/* Compute rowspan. */
$productGroup = array();
$waitCount = 0;
$testingCount = 0;
$blockedCount = 0;
$doneCount = 0;
foreach($tasks as $task)
{
$productGroup[$task->product][] = $task;
if($task->status == 'wait') $waitCount ++;
if($task->status == 'doing') $testingCount ++;
if($task->status == 'blocked') $blockedCount ++;
if($task->status == 'done') $doneCount ++;
if($task->build == 'trunk' || empty($task->buildName)) $task->buildName = $this->lang->trunk;
}
$lastProduct = '';
foreach($tasks as $taskID => $task)
{
$task->rawStatus = $task->status;
$task->status = $this->processStatus('testtask', $task);
$task->rowspan = 0;
if($lastProduct !== $task->product)
{
$lastProduct = $task->product;
if(!empty($productGroup[$task->product])) $task->rowspan = count($productGroup[$task->product]);
}
}
$this->view->waitCount = $waitCount;
$this->view->testingCount = $testingCount;
$this->view->blockedCount = $blockedCount;
$this->view->doneCount = $doneCount;
$this->view->tasks = $tasks;
}
/**
* 展示执行详情的相关变量。
* Show the view related variables.
*
* @param int $executionID
* @access protected
* @return void
*/
protected function assignViewVars(int $executionID)
{
$this->executeHooks($executionID);
$userPairs = array();
$userList = array();
$users = $this->loadModel('user')->getList('all');
foreach($users as $user)
{
$userList[$user->account] = $user;
$userPairs[$user->account] = $user->realname;
}
/* Get linked branches. */
$branches = $this->project->getBranchesByProject($executionID);
$linkedProductIdList = empty($branches) ? '' : array_keys($branches);
$products = $this->loadModel('product')->getProducts($executionID, 'all', '', true, $linkedProductIdList, false);
$linkedBranches = array();
foreach($products as $product)
{
if(isset($product->branches))
{
foreach($product->branches as $branchID) $linkedBranches[$branchID] = $branchID;
}
}
$this->view->users = $userPairs;
$this->view->userList = $userList;
$this->view->products = $products;
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'ignoreNormal', $linkedBranches);
$this->view->planGroups = $this->execution->getPlans(array_keys($products));
$this->view->actions = $this->loadModel('action')->getList('execution', $executionID);
$this->view->dynamics = $this->loadModel('action')->getDynamic('all', 'all', 'date_desc', 50, 'all', 'all', $executionID);
$this->view->teamMembers = $this->execution->getTeamMembers($executionID);
$this->view->docLibs = $this->loadModel('doc')->getLibsByObject('execution', $executionID);
$this->view->statData = $this->execution->statRelatedData($executionID);
}
/**
* 构造任务分组视图数据。
* Build task group data.
*
* @param string $groupBy story|status|pri|assignedTo|finishedBy|closedBy|type
* @param array $tasks
* @param array $users
* @access protected
* @return array
*/
protected function buildGroupTasks(string $groupBy = 'story', array $tasks = array(), array $users = array()): array
{
$groupTasks = array();
$groupByList = array();
foreach($tasks as $task)
{
if($groupBy == 'story')
{
$groupTasks[$task->story][] = $task;
$groupByList[$task->story] = $task->storyTitle;
}
elseif($groupBy == 'status')
{
$statusList = arrayUnion($this->lang->task->statusList, array('changed' => $this->lang->task->storyChange));
$groupTasks[$statusList[$task->status]][] = $task;
}
elseif($groupBy == 'assignedTo')
{
if(isset($task->team))
{
$groupTasks = $this->buildGroupMultiTask($groupBy, $task, $users, $groupTasks);
}
else
{
$groupTasks[$task->assignedToRealName][] = $task;
}
}
elseif($groupBy == 'finishedBy')
{
if(isset($task->team))
{
$task->consumed = $task->estimate = $task->left = 0;
$groupTasks = $this->buildGroupMultiTask($groupBy, $task, $users, $groupTasks);
}
else
{
$groupTasks[$users[$task->finishedBy]][] = $task;
}
}
elseif($groupBy == 'closedBy')
{
$groupTasks[$users[$task->closedBy]][] = $task;
}
elseif($groupBy == 'type')
{
$groupTasks[$this->lang->task->typeList[$task->type]][] = $task;
}
else
{
$groupTasks[$task->$groupBy][] = $task;
}
}
/* Process closed data when group by assignedTo. */
if($groupBy == 'assignedTo' && isset($groupTasks['Closed']))
{
$closedTasks = $groupTasks['Closed'];
unset($groupTasks['Closed']);
$groupTasks['closed'] = $closedTasks;
}
return array($groupTasks, $groupByList);
}
/**
* 构建多人任务的分组视图数据。
* Build group data for multiple task.
*
* @param string $groupBy
* @param object $task
* @param array $users
* @param array $groupTasks
* @access protected
* @return array
*/
protected function buildGroupMultiTask(string $groupBy, object $task, array $users, array $groupTasks): array
{
foreach($task->team as $team)
{
if($team->left != 0 && $groupBy == 'finishedBy')
{
$task->estimate += $team->estimate;
$task->consumed += $team->consumed;
$task->left += $team->left;
continue;
}
$cloneTask = clone $task;
$cloneTask->{$groupBy} = $team->account;
$cloneTask->estimate = $team->estimate;
$cloneTask->consumed = $team->consumed;
$cloneTask->left = $team->left;
if($team->left == 0 || $groupBy == 'finishedBy') $cloneTask->status = 'done';
$realname = zget($users, $team->account);
$cloneTask->assignedToRealName = $realname;
$groupTasks[$realname][] = $cloneTask;
}
if($groupBy == 'finishedBy' && !empty($task->left)) $groupTasks[$users[$task->finishedBy]][] = $task;
return $groupTasks;
}
/**
* 构建需求列表的搜索表单数据。
* Build the search form data to story list.
*
* @param object $execution
* @param int $productID
* @param array $products
* @param int $queryID
* @param string $actionURL
* @access protected
* @return void
*/
protected function buildStorySearchForm(object $execution, int $productID, array $products, int $queryID, string $actionURL): void
{
$modules = array();
$productModules = array();
$executionModules = $this->loadModel('tree')->getTaskTreeModules($execution->id, true);
if($productID)
{
$product = $products[$productID];
$productModules = $this->tree->getOptionMenu($productID, 'story', 0, $product->branches);
}
else
{
foreach($products as $product) $productModules += $this->tree->getOptionMenu($product->id, 'story', 0, $product->branches);
}
if(commonModel::isTutorialMode())
{
$modules = $this->loadModel('tutorial')->getModulePairs();
}
else
{
foreach($productModules as $branchID => $moduleList)
{
foreach($moduleList as $moduleID => $moduleName)
{
if($moduleID && !isset($executionModules[$moduleID])) continue;
$modules[$moduleID] = ((count($products) >= 2 && $moduleID) ? $product->name : '') . $moduleName;
}
}
}
$branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
$this->execution->buildStorySearchForm($products, $branchGroups, $modules, $queryID, $actionURL, 'executionStory', $execution);
}
/**
* 将导入的Bug转为任务。
* Change imported bugs to the tasks.
*
* @param object $execution
* @param array $postData
* @access protected
* @return array
*/
protected function buildTasksForImportBug(object $execution, array $postData)
{
$this->loadModel('task');
$tasks = array();
$bugs = $this->loadModel('bug')->getByIdList(array_keys($postData));
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
$modules = $this->loadModel('tree')->getTaskOptionMenu($execution->id, 0, $showAllModule ? 'allModule' : '');
$now = helper::now();
$requiredFields = str_replace(array(',story,', ',module,'), ',', ',' . $this->config->task->create->requiredFields . ',');
$requiredFields = trim($requiredFields, ',');
foreach($postData as $bugID => $task)
{
$bug = zget($bugs, $bugID, '');
if(empty($bug)) continue;
unset($task->id);
$task->bug = $bug;
$task->project = $execution->project;
$task->execution = $execution->id;
$task->story = $bug->story;
$task->storyVersion = $bug->storyVersion;
$task->module = isset($modules[$bug->module]) ? $bug->module : 0;
$task->fromBug = $bugID;
$task->name = $bug->title;
$task->type = 'devel';
$task->consumed = 0;
$task->status = 'wait';
$task->openedDate = $now;
$task->openedBy = $this->app->user->account;
$task->estStarted = $task->estStarted ? $task->estStarted : null;
$task->deadline = $task->deadline ? $task->deadline : null;
if($task->estimate !== '') $task->left = $task->estimate;
if(!empty($task->assignedTo)) $task->assignedDate = $now;
/* Check task required fields. */
foreach(explode(',', $requiredFields) as $field)
{
if(empty($field)) continue;
if(!isset($task->$field)) continue;
if(!empty($task->$field)) continue;
dao::$errors["{$field}[{$bugID}]"] = 'ID: ' . $bugID . sprintf($this->lang->error->notempty, $this->lang->task->$field);
return false;
}
if(!preg_match("/^[0-9]+(.[0-9]+)?$/", (string)$task->estimate) and !empty($task->estimate))
{
dao::$errors["{$field}[{$bugID}]"] = 'ID: ' . $bugID . $this->lang->task->error->estimateNumber;
return false;
}
if(!helper::isZeroDate($task->deadline) && $task->estStarted > $task->deadline)
{
dao::$errors["deadline[{$bugID}]"] = 'ID: ' . $bugID . $this->lang->task->error->deadlineSmall;
return false;
}
if(!empty($this->config->limitTaskDate))
{
$this->task->checkEstStartedAndDeadline($execution->id, (string)$task->estStarted, (string)$task->deadline);
if(dao::isError()) return false;
}
$tasks[$bugID] = $task;
}
return $tasks;
}
/**
* 构建导入Bug的搜索表单数据。
* Build the search form data to import the Bug.
*
* @param object $execution
* @param int $queryID
* @param array $products
* @param array $executions
* @param array $projects
* @access protected
* @return void
*/
protected function buildImportBugSearchForm(object $execution, int $queryID, array $products, array $executions, array $projects)
{
$project = $this->loadModel('project')->getByID($execution->project);
$this->config->bug->search['actionURL'] = $this->createLink('execution', 'importBug', "executionID=$execution->id&browseType=bySearch&param=myQueryID");
$this->config->bug->search['queryID'] = $queryID;
if(!empty($products))
{
$this->config->bug->search['params']['product']['values'] = array(''=>'') + $products + array('all'=>$this->lang->execution->aboveAllProduct);
}
else
{
$this->config->bug->search['params']['product']['values'] = array(''=>'');
}
if(!$execution->multiple)
{
unset($this->config->bug->search['fields']['execution'], $this->config->bug->search['params']['execution']);
}
else
{
$this->config->bug->search['params']['execution']['values'] = array(''=>'') + $executions + array('all'=>$this->lang->execution->aboveAllExecution);
}
$this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs(array_keys($products));
$this->config->bug->search['module'] = 'importBug';
$this->config->bug->search['params']['confirmed']['values'] = $this->lang->bug->confirmedList;
$this->loadModel('tree');
$bugModules = array();
foreach($products as $productID => $productName)
{
$productModules = $this->tree->getOptionMenu($productID, 'bug', 0, 'all');
foreach($productModules as $moduleID => $moduleName)
{
if(empty($moduleID))
{
$bugModules[$moduleID] = $moduleName;
continue;
}
$bugModules[$moduleID] = $productName . $moduleName;
}
}
$this->config->bug->search['params']['module']['values'] = $bugModules;
$this->config->bug->search['params']['project']['values'] = $projects;
$this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array_keys($products), 'all', 'withbranch|releasetag');
unset($this->config->bug->search['fields']['resolvedBy']);
unset($this->config->bug->search['fields']['closedBy']);
unset($this->config->bug->search['fields']['status']);
unset($this->config->bug->search['fields']['toTask']);
unset($this->config->bug->search['fields']['toStory']);
unset($this->config->bug->search['fields']['resolution']);
unset($this->config->bug->search['fields']['resolvedBuild']);
unset($this->config->bug->search['fields']['resolvedDate']);
unset($this->config->bug->search['fields']['closedDate']);
unset($this->config->bug->search['fields']['branch']);
if(empty($execution->multiple) && empty($execution->hasProduct)) unset($this->config->bug->search['fields']['plan']);
if(empty($project->hasProduct))
{
unset($this->config->bug->search['fields']['product']);
if($project->model !== 'scrum') unset($this->config->bug->search['fields']['plan']);
}
unset($this->config->bug->search['params']['resolvedBy']);
unset($this->config->bug->search['params']['closedBy']);
unset($this->config->bug->search['params']['status']);
unset($this->config->bug->search['params']['toTask']);
unset($this->config->bug->search['params']['toStory']);
unset($this->config->bug->search['params']['resolution']);
unset($this->config->bug->search['params']['resolvedBuild']);
unset($this->config->bug->search['params']['resolvedDate']);
unset($this->config->bug->search['params']['closedDate']);
unset($this->config->bug->search['params']['branch']);
$this->loadModel('search')->setSearchParams($this->config->bug->search);
}
/**
* 检查创建的表单数据。
* Check the form data for create.
*
* @access protected
* @return bool
*/
protected function checkPostForCreate(): bool
{
if(empty($_POST['project']))
{
dao::$errors['project'] = $this->lang->execution->projectNotEmpty;
return false;
}
$projectID = (int)$_POST['project'];
$project = $this->loadModel('project')->fetchByID($projectID);
$this->execution->checkBeginAndEndDate($projectID, $_POST['begin'], $_POST['end'], isset($_POST['parent']) ? (int)$this->post->parent : $projectID);
if(dao::isError()) return false;
/* Judge workdays is legitimate. */
$workdays = helper::diffDate($_POST['end'], $_POST['begin']) + 1;
if(isset($_POST['days']) && $_POST['days'] > $workdays)
{
dao::$errors['days'] = sprintf($this->lang->project->workdaysExceed, $workdays);
return false;
}
if(!empty($_POST['products']))
{
$_POST['products'] = array_filter($_POST['products']);
$this->app->loadLang('project');
$multipleProducts = $this->loadModel('product')->getMultiBranchPairs();
foreach($_POST['products'] as $index => $productID)
{
if(empty($_POST['branch'][$index])) continue;
$branches = implode(',', $_POST['branch'][$index]);
if(isset($multipleProducts[$productID]) && $branches == '')
{
dao::$errors["branch[{$index}][]"] = $this->lang->project->error->emptyBranch;
return false;
}
}
}
/* Determine whether to add a sprint or a stage according to the model of the execution. */
if($project->model == 'waterfall' || $project->model == 'waterfallplus')
{
if(empty($_POST['products'])) dao::$errors['products[0]'] = $this->lang->project->errorNoProducts;
if(dao::isError()) return false;
if(isset($this->config->setPercent) && $this->config->setPercent == 1)
{
$this->execution->checkWorkload('create', (int)$this->post->percent, $project);
if(dao::isError()) return false;
}
}
return true;
}
/**
* 构造创建执行的数据。
* Build the data for create execution.
*
* @access protected
* @return object|false
*/
protected function buildExecutionForCreate(): object|false
{
$now = helper::now();
$project = $this->loadModel('project')->fetchByID((int)$_POST['project']);
$type = 'sprint';
if($project) $type = zget($this->config->execution->modelList, $project->model, 'sprint');
$fields = $this->config->execution->form->create;
foreach(explode(',', trim($this->config->execution->create->requiredFields, ',')) as $field) $fields[$field]['required'] = true;
if(!isset($_POST['code'])) $fields['code']['required'] = false;
if(!isset($_POST['percent'])) $fields['percent']['required'] = false;
$this->config->execution->create->requiredFields = implode(',', array_keys(array_filter(array_map(function($config){return $config['required'] == true;}, $fields))));
$this->correctErrorLang();
$execution = form::data($fields)
->setDefault('openedBy', $this->app->user->account)
->setDefault('openedDate', $now)
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', $now)
->setDefault('type', $type)
->setDefault('team', $this->post->name)
->setDefault('parent', $this->post->project)
->setDefault('isTpl', $project->isTpl)
->setIF($this->post->parent, 'parent', $this->post->parent)
->setIF($this->post->heightType == 'auto', 'displayCards', 0)
->setIF($this->post->acl == 'open', 'whitelist', '')
->setIF($this->post->type, 'type', $this->post->type)
->get();
if(!empty($execution->parent) && ($execution->project == $execution->parent)) $execution->hasProduct = $project->hasProduct;
if(!empty($execution->parent) && empty($execution->attribute))
{
/* 子阶段与父阶段同类型。 */
$parent = $this->execution->fetchByID((int)$execution->parent);
$execution->attribute = $parent->attribute;
}
if($this->post->heightType == 'custom' && !$this->loadModel('kanban')->checkDisplayCards($execution->displayCards)) return false;
if(!$this->checkPostForCreate()) return false;
/* Set planDuration and realDuration. */
if(in_array($this->config->edition, array('max', 'ipd')))
{
$execution->planDuration = $this->loadModel('programplan')->getDuration($execution->begin, $execution->end);
if(!empty($execution->realBegan) && !empty($execution->realEnd)) $execution->realDuration = $this->programplan->getDuration($execution->realBegan, $execution->realEnd);
}
return $this->loadModel('file')->processImgURL($execution, $this->config->execution->editor->create['id'], $this->post->uid);
}
/**
* 检查累积流图的日期。
* Check Cumulative flow diagram date.
*
* @param string $begin
* @param string $end
* @param string $minDate
* @param string $maxDate
* @access protected
* @return bool
*/
protected function checkCFDDate(string $begin, string $end, string $minDate, string $maxDate): bool
{
$dateError = array();
if(empty($begin)) $dateError[] = sprintf($this->lang->error->notempty, $this->lang->execution->charts->cfd->begin);
if(empty($end)) $dateError[] = sprintf($this->lang->error->notempty, $this->lang->execution->charts->cfd->end);
if(empty($dateError))
{
if($begin < $minDate) $dateError[] = sprintf($this->lang->error->gt, $this->lang->execution->charts->cfd->begin, $minDate);
if($begin > $maxDate) $dateError[] = sprintf($this->lang->error->lt, $this->lang->execution->charts->cfd->begin, $maxDate);
if($end < $minDate) $dateError[] = sprintf($this->lang->error->gt, $this->lang->execution->charts->cfd->end, $minDate);
if($end > $maxDate) $dateError[] = sprintf($this->lang->error->lt, $this->lang->execution->charts->cfd->end, $maxDate);
}
foreach($dateError as $index => $error)
{
dao::$errors = str_replace(array('。', '.'), array('', ''), $error);
return false;
}
if($begin >= $end)
{
dao::$errors = $this->lang->execution->charts->cfd->errorBegin;
return false;
}
if(date("Y-m-d", strtotime("-3 months", strtotime($end))) > $begin)
{
dao::$errors = $this->lang->execution->charts->cfd->errorDateRange;
return false;
}
return true;
}
/**
* 处理版本列表展示数据。
* Process build list display data.
*
* @param array $buildList
* @param string $executionID
* @access protected
* @return object[]
*/
protected function processBuildListData(array $buildList, int $executionID = 0): array
{
$this->loadModel('build');
$productIdList = array();
foreach($buildList as $build) $productIdList[$build->product] = $build->product;
/* Get branch name. */
$showBranch = false;
$branchGroups = $this->loadModel('branch')->getByProducts($productIdList);
$builds = array();
foreach($buildList as $build)
{
$build->branchName = '';
if(isset($branchGroups[$build->product]))
{
$showBranch = true;
$branchPairs = $branchGroups[$build->product];
foreach(explode(',', trim($build->branch, ',')) as $branchID)
{
if(isset($branchPairs[$branchID])) $build->branchName .= "{$branchPairs[$branchID]},";
}
$build->branchName = trim($build->branchName, ',');
}
if($build->scmPath && $build->filePath)
{
$build->rowspan = 2;
$buildInfo = clone $build;
$buildInfo->pathType = 'scmPath';
$buildInfo->path = $build->scmPath;
$builds[] = $buildInfo;
$buildInfo = clone $build;
$buildInfo->pathType = 'filePath';
$buildInfo->path = $build->filePath;
$builds[] = $buildInfo;
}
else
{
$build->pathType = empty($build->scmPath) ? 'filePath' : 'scmPath';
$build->path = empty($build->scmPath) ? $build->filePath : $build->scmPath;
$builds[] = $build;
}
if(!$build->system) $build->system = '';
}
if(!$showBranch) unset($this->config->build->dtable->fieldList['branch']);
unset($this->config->build->dtable->fieldList['execution']);
return $builds;
}
/**
* 构建产品下拉选择数据。
* Build product drop-down select data.
*
* @param int $executionID
* @param int $productID
* @param object[] $products
* @access protected
* @return array
*/
protected function buildProductSwitcher(int $executionID, int $productID, array $products)
{
$productOption = array();
$branchOption = array();
$programIdList = array();
if(count($products) > 1) $productOption[0] = $this->lang->product->all;
foreach($products as $productData) $programIdList[$productData->program] = $productData->program;
$programPairs = $this->loadModel('program')->getPairsByList($programIdList);
$linePairs = $this->loadModel('product')->getLinePairs($programIdList);
foreach($products as $productData)
{
$programName = isset($programPairs[$productData->program]) ? $programPairs[$productData->program] . ' / ' : '';
$lineName = isset($linePairs[$productData->line]) ? $linePairs[$productData->line] . ' / ' : '';
$productOption[$productData->id] = $programName . $lineName . $productData->name;
}
$product = $this->product->getById((int)$productID);
if($product and $product->type != 'normal')
{
/* Display status of branch. */
$branches = $this->loadModel('branch')->getList($productID, $executionID, 'all');
foreach($branches as $branchInfo)
{
$branchOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
}
return array($productOption, $branchOption);
}
/**
* 构建执行团队成员信息。
* Build execution team member information.
*
* @param array $currentMembers
* @param array $members2Import
* @param array $deptUsers
* @param int $days
* @access protected
* @return array
*/
protected function buildMembers(array $currentMembers, array $members2Import, array $deptUsers, int $days): array
{
$teamMembers = array();
foreach($currentMembers as $account => $member)
{
$member->memberType = 'default';
$teamMembers[$account] = $member;
}
foreach($members2Import as $account => $member2Import)
{
$member2Import->memberType = 'import';
$member2Import->days = $days;
$member2Import->limited = 'no';
$teamMembers[$account] = $member2Import;
}
$roles = $this->loadModel('user')->getUserRoles(array_keys($deptUsers));
foreach($deptUsers as $deptAccount => $userName)
{
if(isset($currentMembers[$deptAccount]) || isset($members2Import[$deptAccount])) continue;
$deptMember = new stdclass();
$deptMember->memberType = 'dept';
$deptMember->account = $deptAccount;
$deptMember->role = zget($roles, $deptAccount, '');
$deptMember->days = $days;
$deptMember->hours = $this->config->execution->defaultWorkhours;
$deptMember->limited = 'no';
$teamMembers[$deptAccount] = $deptMember;
}
for($j = 0; $j < 5; $j ++)
{
$newMember = new stdclass();
$newMember->memberType = 'add';
$newMember->account = '';
$newMember->role = '';
$newMember->days = $days;
$newMember->hours = $this->config->execution->defaultWorkhours;
$newMember->limited = 'no';
$teamMembers[] = $newMember;
}
return $teamMembers;
}
/**
* 构造待更新的团队成员数据。
* Construct the team member data to be updated.
*
* @param object $execution
* @access protected
* @return array
*/
protected function buildMembersForManageMembers(object $execution)
{
$members = form::batchData()->get();
$oldMemberDays = $this->dao->select('`account`, `days`')->from(TABLE_TEAM)->where('root')->eq($execution->id)->andWhere('type')->eq('execution')->fetchPairs('account', 'days');
foreach($members as $rowIndex => $member)
{
$member->root = $execution->id;
if($member->hours > 24) dao::$errors["hours[$rowIndex]"] = $this->lang->execution->errorHours;
if(!empty($execution->days))
{
if(isset($oldMemberDays[$member->account]) && $member->days == $oldMemberDays[$member->account]) continue;
if($member->days > $execution->days) dao::$errors["days[$rowIndex]"] = sprintf($this->lang->execution->daysGreaterProject, $execution->days);
}
}
return $members;
}
/**
* 根据过滤规则,筛选任务分组数据。
* Filter task group data based on the filter rules.
*
* @param array $groupTasks
* @param string $groupBy
* @param string $filter
* @param int $allCount
* @param array $tasks
* @access protected
* @return array
*/
protected function filterGroupTasks(array $groupTasks, string $groupBy, string $filter, int $allCount, array $tasks): array
{
if($filter == 'all') return array($groupTasks, $allCount);
if($groupBy == 'story' && $filter == 'linked' && isset($groupTasks[0]))
{
$allCount -= count($groupTasks[0]);
unset($groupTasks[0]);
}
elseif($groupBy == 'pri' && $filter == 'noset')
{
foreach($groupTasks as $pri => $tasks)
{
if($pri)
{
$allCount -= count($tasks);
unset($groupTasks[$pri]);
}
}
}
elseif($groupBy == 'assignedTo' && $filter == 'undone')
{
$multiTaskCount = array();
foreach($groupTasks as $assignedTo => $tasks)
{
foreach($tasks as $i => $task)
{
if($task->status != 'wait' && $task->status != 'doing')
{
if($task->mode == 'multi' && !isset($multiTaskCount[$task->id]))
{
$multiTaskCount[$task->id] = true;
$allCount -= 1;
}
elseif($task->mode != 'multi')
{
$allCount -= 1;
}
unset($groupTasks[$assignedTo][$i]);
}
}
}
}
elseif(($groupBy == 'finishedBy' || $groupBy == 'closedBy') && isset($tasks['']))
{
$allCount -= count($tasks['']);
unset($tasks['']);
}
return array($groupTasks, $allCount);
}
/**
* 设置最近五次执行。
* Set the recent five executions.
*
* @param int $executionID
* @access protected
* @return void
*/
protected function setRecentExecutions(int $executionID)
{
if($this->session->multiple)
{
$recentExecutions = isset($this->config->execution->recentExecutions) ? explode(',', $this->config->execution->recentExecutions) : array();
array_unshift($recentExecutions, $executionID);
$recentExecutions = array_slice(array_unique($recentExecutions), 0, 5);
$recentExecutions = implode(',', $recentExecutions);
$this->loadModel('setting');
if(empty($this->config->execution->recentExecutions) || $this->config->execution->recentExecutions != $recentExecutions) $this->setting->updateItem($this->app->user->account . 'common.execution.recentExecutions', $recentExecutions);
if(empty($this->config->execution->lastExecution) || $this->config->execution->lastExecution != $executionID) $this->setting->updateItem($this->app->user->account . 'common.execution.lastExecution', $executionID);
}
}
/**
* 设置任务页面的Cookie和Session。
* Set task page storage.
*
* @access protected
* @return void
*/
protected function setTaskPageStorage(int $executionID, string $orderBy, string $browseType, int $param = 0)
{
helper::setcookie('preExecutionID', (string)$executionID);
helper::setcookie('executionTaskOrder', $orderBy);
if($this->cookie->preExecutionID != $executionID)
{
helper::setcookie('moduleBrowseParam', '0');
helper::setcookie('productBrowseParam', '0');
}
if($browseType == 'bymodule')
{
helper::setcookie('moduleBrowseParam', (string)$param);
helper::setcookie('productBrowseParam', '0');
}
elseif($browseType == 'byproduct')
{
helper::setcookie('moduleBrowseParam', '0');
helper::setcookie('productBrowseParam', (string)$param);
}
else
{
$this->session->set('taskBrowseType', $browseType);
}
if($browseType == 'bymodule' && $this->session->taskBrowseType == 'bysearch') $this->session->set('taskBrowseType', 'unclosed');
}
/**
* 构建执行看板的数据。
* Build the data to execution Kanban.
*
* @param array $projectIdList
* @param array $executions
* @access protected
* @return void
*/
protected function buildExecutionKanbanData(array $projectIdList, array $executions)
{
$projectCount = 0;
$statusCount = array();
$myExecutions = array();
$kanbanGroup = array();
$teams = $this->execution->getMembersByIdList(toIntArray($this->app->user->view->sprints));
foreach($projectIdList as $projectID)
{
foreach(array_keys($this->lang->execution->statusList) as $status)
{
if(!isset($statusCount[$status])) $statusCount[$status] = 0;
foreach($executions as $execution)
{
if($execution->status == $status)
{
if(isset($teams[$execution->id][$this->app->user->account])) $myExecutions[$status][$execution->id] = $execution;
if($execution->project == $projectID) $kanbanGroup[$projectID][$status][$execution->id] = $execution;
}
}
$statusCount[$status] += isset($kanbanGroup[$projectID][$status]) ? count($kanbanGroup[$projectID][$status]) : 0;
/* Max 2 closed executions. */
if($status == 'closed')
{
list($myExecutions, $kanbanGroup) = $this->processExecutionKanbanData($myExecutions, $kanbanGroup, $projectID, $status);
}
}
if(empty($kanbanGroup[$projectID])) continue;
$projectCount ++;
}
return array($projectCount, $statusCount, $myExecutions, $kanbanGroup);
}
/**
* 获取可以导入到执行中的Bug。
*
* @param int $executionID
* @param array $productIdList
* @param string $browseType
* @param int $queryID
* @param object $pager
* @access protected
* @return array
*/
protected function getImportBugs(int $executionID, array $productIdList, string $browseType, int $queryID, object $pager): array
{
$this->loadModel('bug');
$bugs = array();
if($browseType != "bysearch")
{
$bugs = $this->bug->getActiveAndPostponedBugs($productIdList, $executionID, $pager);
}
else
{
if($queryID)
{
$query = $this->loadModel('search')->getQuery($queryID);
if($query)
{
$this->session->set('importBugQuery', $query->sql);
$this->session->set('importBugForm', $query->form);
}
else
{
$this->session->set('importBugQuery', ' 1 = 1');
}
}
else
{
if($this->session->importBugQuery === false) $this->session->set('importBugQuery', ' 1 = 1');
}
$bugQuery = str_replace("`product` = 'all'", "`product`" . helper::dbIN($productIdList), $this->session->importBugQuery); // Search all execution.
$bugs = $this->execution->getSearchBugs($productIdList, $executionID, $bugQuery, 'id_desc', $pager);
}
return $bugs;
}
/**
* 获取打印看板的数据。
* Get printed kanban data.
*
* @param int $executionID
* @param array $stories
* @access protected
* @return array
*/
protected function getPrintKanbanData(int $executionID, array $stories): array
{
$kanbanTasks = $this->execution->getKanbanTasks($executionID, "id");
$kanbanBugs = $this->loadModel('bug')->getExecutionBugs($executionID);
$users = array();
$taskAndBugs = array();
foreach($kanbanTasks as $task)
{
$status = $task->status;
$users[] = $task->assignedTo;
$taskAndBugs[$status]["task{$task->id}"] = $task;
}
foreach($kanbanBugs as $bug)
{
$status = $bug->status;
$status = $status == 'active' ? 'wait' : ($status == 'resolved' ? ($bug->resolution == 'postponed' ? 'cancel' : 'done') : $status);
$users[] = $bug->assignedTo;
$taskAndBugs[$status]["bug{$bug->id}"] = $bug;
}
$dataList = array();
$contents = array('story', 'wait', 'doing', 'done', 'cancel');
foreach($contents as $content)
{
if($content != 'story' and !isset($taskAndBugs[$content])) continue;
$dataList[$content] = $content == 'story' ? $stories : $taskAndBugs[$content];
}
return array($dataList, $users);
}
/**
* 处理执行看板数据。
* Process execution kanban data.
*
* @param array $myExecutions
* @param array $kanbanGroup
* @param int $projectID
* @param string $status
* @access protected
* @return array
*/
protected function processExecutionKanbanData(array $myExecutions, array $kanbanGroup, int $projectID, string $status): array
{
if(isset($myExecutions[$status]) and count($myExecutions[$status]) > 2)
{
foreach($myExecutions[$status] as $executionID => $execution)
{
unset($myExecutions[$status][$executionID]);
$myExecutions[$status][$execution->closedDate] = $execution;
}
krsort($myExecutions[$status]);
$myExecutions[$status] = array_slice($myExecutions[$status], 0, 2, true);
}
if(isset($kanbanGroup[$projectID][$status]) and count($kanbanGroup[$projectID][$status]) > 2)
{
foreach($kanbanGroup[$projectID][$status] as $executionID => $execution)
{
unset($kanbanGroup[$projectID][$status][$executionID]);
$kanbanGroup[$projectID][$status][$execution->closedDate] = $execution;
}
krsort($kanbanGroup[$projectID][$status]);
$kanbanGroup[$projectID][$status] = array_slice($kanbanGroup[$projectID][$status], 0, 2);
}
return array($myExecutions, $kanbanGroup);
}
/**
* 处理打印的看板数据。
* Process printed Kanban data.
*
* @param int $executionID
* @param array $dataList
* @access protected
* @return array
*/
protected function processPrintKanbanData(int $executionID, array $dataList): array
{
$prevKanbans = $this->execution->getPrevKanban($executionID);
foreach($dataList as $type => $data)
{
if(isset($prevKanbans[$type]))
{
$prevData = $prevKanbans[$type];
foreach($prevData as $id)
{
if(isset($data[$id])) unset($dataList[$type][$id]);
}
}
}
return $dataList;
}
/**
* Check if the product has multiple branch and check if the execution has a product with multiple branch.
*
* @param int $productID
* @param int $executionID
* @return bool
*/
protected function hasMultipleBranch(int $productID, int $executionID): bool
{
/* Check if the product is multiple branch. */
$multiBranchProduct = false;
if($productID)
{
$product = $this->loadModel('product')->getByID($productID);
if($product->type != 'normal') $multiBranchProduct = true;
}
else
{
$executionProductList = $this->loadModel('product')->getProducts($executionID);
foreach($executionProductList as $executionProduct)
{
if($executionProduct->type != 'normal')
{
$multiBranchProduct = true;
break;
}
}
}
return $multiBranchProduct;
}
/**
* 通过模块,方法和类型生成执行的链接。
* Generate the link of execution by module, method and type.
*
* @param string $module
* @param string $method
* @param mixed $type
* @access protected
* @return string
*/
protected function getLink(string $module, string $method, string $type = ''): string
{
$executionModules = array('task', 'testcase', 'build', 'bug', 'case', 'testtask', 'testreport', 'doc');
if(in_array($module, array('task', 'testcase', 'story', 'testtask')) && in_array($method, array('view', 'edit', 'batchedit', 'create', 'batchcreate', 'report', 'batchrun', 'groupcase'))) $method = $module;
if(in_array($module, $executionModules) && in_array($method, array('view', 'edit', 'create'))) $method = $module;
if(in_array($module, array_merge($executionModules, array('story', 'product')))) $module = 'execution';
if($module == 'execution' && $method == 'create') return '';
if($this->config->edition != 'open')
{
$flow = $this->loadModel('workflow')->getByModule($module);
if(!empty($flow) && in_array($flow->app, array('scrum', 'waterfall', 'kanbanProject'))) $flow->app = 'project';
if(!empty($flow) && $flow->buildin == '0') return helper::createLink('flow', 'ajaxSwitchBelong', "objectID=%s&moduleName=$module") . "#app=$flow->app";
}
$link = helper::createLink($module, $method, "executionID=%s");
if($module == 'execution' && ($method == 'index' || $method == 'all'))
{
$link = helper::createLink($module, 'task', "executionID=%s");
}
elseif($module == 'execution' && $method == 'storyview')
{
$link = helper::createLink($module, 'story', "executionID=%s");
}
elseif($module == 'execution' && in_array($method, array('kanban', 'cfd')))
{
$link = helper::createLink($module, 'view', "executionID=%s");
}
elseif($module == 'execution' && $method == 'autoschedule')
{
$link = helper::createLink('task', 'autoSchedule', "executionID=%s");
}
elseif($module == 'bug' && $method == 'create' && $this->app->tab == 'execution')
{
$link = helper::createLink($module, $method, "productID=0&branch=0&executionID=%s");
}
elseif(in_array($module, array('bug', 'case', 'testtask', 'testreport')) && strpos(',view,edit,', ",$method,") !== false)
{
$link = helper::createLink('execution', $module, "executionID=%s");
}
elseif($module == 'repo' && $method == 'review')
{
$link = helper::createLink('repo', 'review', "repoID=0&browseType=all&executionID=%s") . '#app=execution';
}
elseif($module == 'repo')
{
$link = helper::createLink('repo', 'browse', "repoID=0&branchID=&executionID=%s");
}
elseif($module == 'mr')
{
$link = helper::createLink('mr', 'browse', "repoID=0&mode=status&param=opened&objectID=%s") . '#app=execution';
}
elseif($module == 'doc')
{
$link = helper::createLink('doc', $method, "type=execution&objectID=%s&from=execution");
}
elseif($module == 'pssp')
{
$link = helper::createLink($module, 'browse', "projectID=%s") . "#app={$this->app->tab}";
}
elseif(in_array($module, array('issue', 'risk', 'opportunity', 'auditplan', 'nc', 'meeting')))
{
$link = helper::createLink($module, 'browse', "executionID=%s&from=execution");
}
elseif(($module == 'testreport' && $method == 'create') || ($module == 'execution' && $method == 'cases'))
{
$link = helper::createLink('execution', 'testtask', "executionID=%s");
}
elseif($method == 'zerocase')
{
$link = helper::createLink('execution', 'testcase', "executionID=%s");
}
elseif($method == 'createrelation')
{
$link = helper::createLink('execution', 'relation', "executionID=%s");
}
elseif($module == 'project' && $method == 'deliverablechecklist')
{
$link = helper::createLink('project', 'deliverableChecklist', "projectID=%s") . "#app={$this->app->tab}";
}
if($type != '') $link .= "&type=$type";
return $link;
}
/**
* 设置cookie和session。
* Set the cookie and session.
*
* @param string $executionID
* @param string $type all|byModule|byProduct|byBranch|bySearch
* @param string $param
* @param string $orderBy
* @access protected
* @return int
*/
protected function setStorageForStory(string $executionID, string $type, string $param, string $orderBy): int
{
$productID = 0;
if($this->cookie->storyPreExecutionID != $executionID)
{
$_COOKIE['storyModuleParam'] = $_COOKIE['storyProductParam'] = $_COOKIE['storyBranchParam'] = 0;
helper::setcookie('storyModuleParam', '0');
helper::setcookie('storyProductParam', '0');
helper::setcookie('storyBranchParam', '0');
}
if($type == 'bymodule')
{
$module = $this->loadModel('tree')->getByID((int)$param);
$productID = isset($module->root) ? $module->root : 0;
helper::setcookie('storyModuleParam', $param);
helper::setcookie('storyProductParam', '0');
helper::setcookie('storyBranchParam', '0');
}
elseif($type == 'byproduct')
{
$productID = $param;
helper::setcookie('storyModuleParam', '0');
helper::setcookie('storyProductParam', $param);
helper::setcookie('storyBranchParam', '0');
}
elseif($type == 'bybranch')
{
helper::setcookie('storyModuleParam', '0');
helper::setcookie('storyProductParam', '0');
helper::setcookie('storyBranchParam', $param);
}
$this->session->set('executionStoryBrowseType', $type);
$this->session->set('storyBrowseType', $type, 'execution');
$uri = $this->app->getURI(true);
$this->session->set('storyList', $uri, $this->app->tab);
$this->session->set('executionStoryList', $uri, 'execution');
helper::setcookie('executionStoryOrder', $orderBy);
helper::setcookie('storyPreExecutionID', $executionID);
if($this->app->tab == 'project' && $this->cookie->storyProductParam != $productID) helper::setcookie('storyProductParam', '0');
return (int)$productID;
}
/**
* Set the more link of user and return users with echo role.
*
* @param object[]|object $execution
* @return array
*/
protected function setUserMoreLink(mixed $execution = null): array
{
$appendPo = $appendPm = $appendQd = $appendRd = array();
if(is_array($execution))
{
$appendPo = $appendPm = $appendQd = $appendRd = array();
foreach($execution as $item)
{
$appendPo[$item->PO] = $item->PO;
$appendPm[$item->PM] = $item->PM;
$appendQd[$item->QD] = $item->QD;
$appendRd[$item->RD] = $item->RD;
}
}
elseif(is_object($execution))
{
$appendPo[$execution->PO] = $execution->PO;
$appendPm[$execution->PM] = $execution->PM;
$appendQd[$execution->QD] = $execution->QD;
$appendRd[$execution->RD] = $execution->RD;
}
$this->loadModel('user');
$pmUsers = $this->user->getPairs('noclosed|nodeleted|pmfirst', $appendPm, $this->config->maxCount);
if(!empty($this->config->user->moreLink)) $this->config->moreLinks["PM"] = $this->config->user->moreLink;
$poUsers = $this->user->getPairs('noclosed|nodeleted|pofirst', $appendPo, $this->config->maxCount);
if(!empty($this->config->user->moreLink)) $this->config->moreLinks["PO"] = $this->config->user->moreLink;
$qdUsers = $this->user->getPairs('noclosed|nodeleted|qdfirst', $appendQd, $this->config->maxCount);
if(!empty($this->config->user->moreLink)) $this->config->moreLinks["QD"] = $this->config->user->moreLink;
$rdUsers = $this->user->getPairs('noclosed|nodeleted|devfirst', $appendRd, $this->config->maxCount);
if(!empty($this->config->user->moreLink)) $this->config->moreLinks["RD"] = $this->config->user->moreLink;
return array($pmUsers, $poUsers, $qdUsers, $rdUsers);
}
/**
* 初始化创建执行的字段。
* Init execution fields for create.
*
* @param int $projectID
* @param array $output
* @access protected
* @return object
*/
protected function initFieldsForCreate(int $projectID, array $output = array()): object
{
$execution = new stdclass();
$execution->project = $projectID;
$execution->type = zget($output, 'type', 'sprint');
$execution->name = '';
$execution->code = '';
$execution->team = '';
$execution->acl = 'private';
$execution->whitelist = '';
$this->view->execution = $execution;
return $execution;
}
/**
* 通过复制执行ID设置字段值。
* Set execution fields by copy execution id.
*
* @param object $fields
* @param int $copyExecutionID
* @access protected
* @return object
*/
protected function setFieldsByCopyExecution(object $fields, int $copyExecutionID): object
{
if(empty($copyExecutionID)) return $fields;
$copyExecution = $this->execution->fetchByID($copyExecutionID);
$fields->project = (int)$copyExecution->project;
$fields->type = $copyExecution->type;
$fields->name = $copyExecution->name;
$fields->code = $copyExecution->code;
$fields->team = $copyExecution->team;
$fields->acl = $copyExecution->acl;
$fields->whitelist = $copyExecution->whitelist;
$this->view->execution = $copyExecution;
return $fields;
}
/**
* 获取已关联的产品。
* Get can link products.
*
* @param int $copyExecutionID
* @param int $planID
* @param object|null $project
* @access protected
* @return array
*/
protected function getLinkedProducts(int $copyExecutionID, int $planID, object|null $project): array
{
$products = array();
if($copyExecutionID) $products = $this->loadModel('product')->getProducts($copyExecutionID);
if($planID)
{
$plan = $this->loadModel('productplan')->fetchByID($planID);
if($plan->begin == $this->config->productplan->future) $plan->begin = '';
if($plan->end == $this->config->productplan->future) $plan->end = '';
$products = $this->dao->select('t1.id, t1.name, t1.type, t2.branch')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.product')
->where('t1.id')->eq($plan->product)
->fetchAll('id');
$this->view->plan = $plan;
}
if(isset($project->hasProduct) && empty($project->hasProduct))
{
$product = $this->loadModel('product')->getShadowProductByProject($project->id);
$products = array($product->id => $product->name);
}
return $products;
}
/**
* 获取已关联的分支。
* Set linked branches.
*
* @param array $products
* @param int $copyExecutionID
* @param int $planID
* @param object|null $project
* @access protected
* @return void
*/
protected function setLinkedBranches(array $products, int $copyExecutionID, int $planID, object|null $project)
{
$projectID = empty($project) ? 0 : $project->id;
if(!empty($copyExecutionID))
{
$branches = $this->project->getBranchesByProject($copyExecutionID);
$plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($products), 'skipparent|unexpired');
$branchGroups = $this->execution->getBranchByProduct(array_keys($products), $projectID);
}
if(!empty($project) and $project->stageBy == 'project')
{
$products = $this->loadModel('product')->getProducts($project->id);
$branches = $this->project->getBranchesByProject($projectID);
$plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($products), 'skipparent|unexpired');
}
if($products and isset($branches))
{
$linkedBranches = array();
foreach($products as $productIndex => $product)
{
$productPlans[$productIndex] = array();
foreach($branches[$productIndex] as $branchID => $branch)
{
$linkedBranches[$productIndex][$branchID] = $branchID;
if(isset($plans[$productIndex][$branchID]))
{
foreach($plans[$productIndex][$branchID] as $plan) $productPlans[$productIndex][$branchID][$plan->id] = $plan->title;
}
}
}
$this->view->productPlans = $productPlans;
$this->view->linkedBranches = $linkedBranches;
}
if(!empty($planID))
{
$plan = $this->loadModel('productplan')->fetchByID($planID);
$productPlan = $this->productplan->getPairs($plan->product, $plan->branch, 'unexpired|withMainPlan', true);
$linkedBranches = array();
$linkedBranches[$plan->product][$plan->branch] = $plan->branch;
$this->view->linkedBranches = $linkedBranches;
}
$this->view->productPlan = isset($productPlan) ? $productPlan : array();
$this->view->branchGroups = isset($branchGroups) ? $branchGroups : $this->execution->getBranchByProduct(array_keys($products), $projectID);
if(isset($project->hasProduct) and empty($project->hasProduct))
{
$shadowProduct = $this->loadModel('product')->getShadowProductByProject($project->id);
$this->view->productPlan = $this->loadModel('productplan')->getPairs($shadowProduct->id ?? 0, '0,0', 'noclosed,unexpired', true);
}
}
/**
* 根据项目获取可关联的产品。
* Get all products for create.
*
* @param object|null $project
* @access protected
* @return void
*/
protected function getAllProductsForCreate(object|null $project) : array
{
if(empty($project)) return array();
$allProducts = $this->loadModel('product')->getProductPairsByProject($project->id, 'noclosed');
if(!empty($project->hasProduct)) $allProducts = array(0 => '') + $allProducts;
return $allProducts;
}
/**
* 设置可复制的执行。
* Set copy projects.
*
* @param object|null $project
* @access protected
* @return void
*/
protected function setCopyProjects(object|null $project)
{
$parentProject = 0;
$projectModel = '';
if($project)
{
$parentProject = $project->parent;
$projectModel = $project->model;
if($projectModel == 'agileplus') $projectModel = array('scrum', 'agileplus');
if($projectModel == 'waterfallplus') $projectModel = array('waterfall', 'waterfallplus');
}
$copyProjects = $this->loadModel('project')->getPairsByProgram($parentProject, 'noclosed', false, 'order_asc', '', $projectModel, 'multiple');
$copyProjectID = empty($project) ? key($copyProjects) : $project->id;
$this->view->copyProjects = $copyProjects;
$this->view->copyProjectID = $copyProjectID;
$this->view->copyExecutions = empty($copyProjectID) ? array() : $this->execution->getList($copyProjectID, 'all', 'all', 0, 0, 0, null, false);
}
/**
* 修正executionCommon公共语言项。
* Correct execution common lang.
*
* @param object $project
* @param string $type
* @access protected
* @return bool
*/
protected function correctExecutionCommonLang(object $project, string $type): bool
{
if(empty($project)) return false;
if($project->model == 'kanban')
{
global $lang;
$executionLang = $lang->execution->common;
$executionCommonLang = $lang->executionCommon;
$lang->executionCommon = $lang->execution->kanban;
$lang->execution->common = $lang->execution->kanban;
include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
$lang->execution->common = $executionLang;
$lang->executionCommon = $executionCommonLang;
$lang->execution->typeList['sprint'] = $executionCommonLang;
}
elseif($project->model == 'waterfall' || $project->model == 'waterfallplus')
{
$this->app->loadLang('stage');
global $lang;
$lang->executionCommon = $lang->execution->stage;
include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
}
if(isset($project->hasProduct) and empty($project->hasProduct)) $this->lang->execution->PO = $this->lang->common->story . $this->lang->execution->owner;
return true;
}
/**
* 修正错误提示语言。
* Correct error lang.
*
* @access protected
* @return void
*/
protected function correctErrorLang(): void
{
$this->lang->execution->team = $this->lang->execution->teamName;
$this->lang->error->unique = $this->lang->error->repeat;
/* Redefines the language entries for the fields in the project table. */
foreach(explode(',', $this->config->execution->create->requiredFields) as $field)
{
if(isset($this->lang->execution->$field)) $this->lang->project->$field = $this->lang->execution->$field;
}
/* Replace required language. */
if($this->app->tab == 'project')
{
$this->lang->project->name = $this->lang->execution->name;
$this->lang->project->code = $this->lang->execution->code;
}
else
{
$this->lang->project->name = $this->lang->execution->execName;
$this->lang->project->code = $this->lang->execution->execCode;
}
}
/**
* 创建执行后,显示提示页面。
* Display after created.
*
* @param int $projectID
* @param int $executionID
* @param int $planID
* @param string $confirm
* @access protected
* @return void
*/
protected function displayAfterCreated(int $projectID, int $executionID, int $planID, string $confirm = 'no')
{
$execution = $this->execution->fetchByID($executionID);
if(!empty($planID) and $execution->lifetime != 'ops')
{
if($confirm == 'yes')
{
$this->execution->linkStories($executionID);
}
else
{
$executionProductList = $this->loadModel('product')->getProducts($executionID);
$multiBranchProduct = false;
array_map(function($executionProduct) use(&$multiBranchProduct){if($executionProduct->type != 'normal') $multiBranchProduct = true;}, $executionProductList);
$storyType = '';
$project = $this->loadModel('project')->fetchByID($projectID);
if($project)
{
$this->loadModel('story');
foreach(explode(',', $project->storyType) as $type) $storyType .= $this->lang->story->typeList[$type] . ', ';
}
if(empty($storyType)) $storyType = $this->lang->story->common;
$importPlanStoryTips = sprintf($multiBranchProduct ? $this->lang->execution->importBranchPlanStory : $this->lang->execution->importPlanStory, trim($storyType, ', '));
if($execution->type == 'stage') $importPlanStoryTips = str_replace($this->lang->executionCommon, $this->lang->execution->stage, $importPlanStoryTips);
$confirmURL = inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=$planID&confirm=yes");
$cancelURL = inlink('create', "projectID=$projectID&executionID=$executionID");
return $this->send(array('result' => 'success', 'open' => array('confirm' => $importPlanStoryTips, 'url' => $confirmURL, 'canceled' => $cancelURL)));
}
}
if(!empty($projectID) and $execution->type == 'kanban' and $this->app->tab == 'project') return $this->send(array('result' => 'success', 'load' => $this->createLink('project', 'index', "projectID=$projectID")));
if(!empty($projectID) and $execution->type == 'kanban') return $this->send(array('result' => 'success', 'load' => inlink('kanban', "executionID=$executionID")));
$this->view->title = $this->lang->execution->tips;
$this->view->executionID = $executionID;
$this->view->execution = $this->execution->fetchByID($executionID);
$this->display('execution', 'tips');
}
/**
* 更新迭代关联的计划。
* Update linked plans.
*
* @param int $executionID
* @param string $newPlans
* @param string $confirm
* @return void
*/
protected function updateLinkedPlans(int $executionID, string $newPlans = '', string $confirm = 'no')
{
$projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
if(!empty($newPlans) and $confirm == 'yes')
{
$newPlans = explode(',', $newPlans);
$this->loadModel('productplan')->linkProject($executionID, $newPlans);
$this->productplan->linkProject($projectID, $newPlans);
return $this->send(array('result' => 'success', 'load' => inlink('view', "executionID=$executionID")));
}
elseif(!empty($newPlans))
{
$executionProductList = $this->loadModel('product')->getProducts($executionID); /* 无论是迭代ID还是项目ID都会查到一个与之对应的产品。*/
$multiBranchProduct = false;
foreach($executionProductList as $executionProduct)
{
if($executionProduct->type != 'normal')
{
$multiBranchProduct = true;
break;
}
}
$storyType = '';
$project = $this->loadModel('project')->fetchByID($projectID);
if($project)
{
$this->loadModel('story');
foreach(explode(',', $project->storyType) as $type) $storyType .= $this->lang->story->typeList[$type] . ', ';
}
if(empty($storyType)) $storyType = $this->lang->story->common;
$linkPlanMsg = sprintf($multiBranchProduct ? $this->lang->execution->importBranchEditPlanStory : $this->lang->execution->importEditPlanStory, trim($storyType, ', '));
$confirmURL = inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=yes");
$cancelURL = inlink('view', "executionID=$executionID");
return $this->send(array('result' => 'success', 'load' => array('confirm' => $linkPlanMsg, 'confirmed' => $confirmURL, 'canceled' => $cancelURL)));
}
}
/**
* Check if the execution can be linked to plan stories.
*
* @param int $executionID
* @param array $oldPlans
* @return void
*/
protected function checkLinkPlan(int $executionID, array $oldPlans)
{
$oldPlans = explode(',', implode(',' ,$oldPlans));
$newPlans = array();
if(isset($_POST['plans']))
{
foreach((array)$_POST['plans'] as $plans)
{
foreach($plans as $planID)
{
if(array_search($planID, $oldPlans) === false) $newPlans[$planID] = $planID;
}
}
}
$newPlans = array_filter($newPlans);
if(!empty($newPlans))
{
$newPlans = implode(',', $newPlans);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('edit', "executionID=$executionID&action=edit&extra=&newPlans=$newPlans&confirm=no")));
}
}
/**
* 获取执行关联的对象,如关联的产品、分支、需求等。
* Get linked objects of this execution.
*
* @param object $execution
* @return object
*/
protected function getLinkedObjects(object $execution): object
{
$this->loadModel('project');
$this->loadModel('product');
$this->loadModel('productplan');
$branches = $this->project->getBranchesByProject($execution->id);
$linkedProductIdList = empty($branches) ? '' : array_keys($branches);
$allProducts = $this->product->getProducts($execution->project, 'noclosed', '', false, $linkedProductIdList);
$productPlans = $linkedBranches = $linkedBranchList = array();
$linkedProducts = $this->product->getProducts($execution->id, 'all', '', true, $linkedProductIdList, false);
$plans = $this->productplan->getGroupByProduct(array_keys($linkedProducts), 'skipparent|unexpired');
$executionStories = $this->project->getStoriesByProject($execution->id);
/* If the story of the product which linked the execution, you don't allow to remove the product. */
$unmodifiableProducts = $unmodifiableBranches = $linkedStoryIDList = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->deleted ? $linkedProduct->name . "({$this->lang->product->deleted})" : $linkedProduct->name;
$productPlans[$productID] = array();
foreach($branches[$productID] as $branchID => $branch)
{
if(isset($plans[$productID][$branchID]))
{
foreach($plans[$productID][$branchID] as $plan) $productPlans[$productID][$branchID][$plan->id] = $plan->title;
}
$linkedBranchList[$branchID] = $branchID;
$linkedBranches[$productID][$branchID] = $branchID;
if($branchID != BRANCH_MAIN && isset($plans[$productID][BRANCH_MAIN]))
{
foreach($plans[$productID][BRANCH_MAIN] as $plan) $productPlans[$productID][BRANCH_MAIN][$plan->id] = $plan->title;
}
if(!empty($executionStories[$productID][$branchID]))
{
array_push($unmodifiableProducts, $productID);
array_push($unmodifiableBranches, $branchID);
$linkedStoryIDList[$productID][$branchID] = $executionStories[$productID][$branchID]->storyIDList;
}
}
if(!empty($linkedProduct->plans))
{
foreach($linkedProduct->plans as $linkedPlans)
{
$planList = explode(',', $linkedPlans);
$planList = array_filter($planList);
foreach($planList as $planID)
{
if(isset($plans[$productID][$planID])) continue;
$productPlans[$productID][$branchID][$planID] = '';
}
}
}
}
$productPlan = array();
if(!empty($planID))
{
$plan = $this->productplan->fetchByID((int)$planID);
$productPlan = $this->productplan->getPairs($plan->product, $plan->branch, 'unexpired|withMainPlan', true);
}
$productPlansOrder = array();
foreach($productPlans as $productID => $branchPlans)
{
foreach($branchPlans as $branchID => $plans)
{
$orderPlans = $this->loadModel('productplan')->getByIDList(array_keys($plans));
$orderPlans = $this->productplan->relationBranch($orderPlans);
$orderPlansMap = array_keys($orderPlans);
foreach($orderPlansMap as $planMapID)
{
if(empty($plans[$planMapID])) $productPlans[$productID][$branchID][$planMapID] = $orderPlans[$planMapID]->title;
$productPlansOrder[$productID][$branchID][$planMapID] = $productPlans[$productID][$branchID][$planMapID];
}
}
}
$linkedObjects = new stdclass();
$linkedObjects->currentPlan = !empty($planID) ? $planID : 0;
$linkedObjects->productPlan = $productPlan;
$linkedObjects->allProducts = $allProducts;
$linkedObjects->linkedProducts = $linkedProducts;
$linkedObjects->productPlans = array_filter($productPlansOrder);
$linkedObjects->linkedBranches = $linkedBranches;
$linkedObjects->linkedBranchList = $linkedBranchList;
$linkedObjects->linkedStoryIDList = $linkedStoryIDList;
$linkedObjects->unmodifiableProducts = $unmodifiableProducts;
$linkedObjects->unmodifiableBranches = $unmodifiableBranches;
return $linkedObjects;
}
/**
* 获取创建执行后的跳转地址。
* Get location after create.
*
* @param int $projectID
* @param int $executionID
* @param string $model
* @access protected
* @return string
*/
protected function getAfterCreateLocation(int $projectID, int $executionID, string $model = ''): string
{
if($this->app->tab == 'doc') return $this->createLink('doc', 'projectSpace', "objectID=$executionID");
if(!empty($_POST['plans'])) return inlink('create', "projectID=$projectID&executionID=$executionID&copyExecutionID=&planID=1&confirm=no");
if(!empty($projectID) and $model == 'kanban')
{
if($this->app->tab == 'project') return $this->config->vision != 'lite' ? $this->createLink('project', 'index', "projectID=$projectID") : $this->createLink('project', 'execution', "status=all&projectID=$projectID");
return inlink('kanban', "executionID=$executionID");
}
$execution = $this->execution->fetchByID($executionID);
if(!empty($execution->isTpl)) return inlink('task', "executionID=$executionID");
return inlink('create', "projectID=$projectID&executionID=$executionID");
}
}