2703 lines
107 KiB
PHP
2703 lines
107 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
/**
|
||
* The model file of tree module of ZenTaoPMS.
|
||
*
|
||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
|
||
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
|
||
* @package tree
|
||
* @version $Id: model.php 5149 2013-07-16 01:47:01Z zhujinyonging@gmail.com $
|
||
* @link https://www.zentao.net
|
||
*/
|
||
class treeModel extends model
|
||
{
|
||
/**
|
||
* 通过ID获取模块信息。
|
||
* Get module by ID.
|
||
*
|
||
* @param int $moduleID
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function getByID(int $moduleID)
|
||
{
|
||
return $this->dao->findById((int)$moduleID)->from(TABLE_MODULE)->fetch();
|
||
}
|
||
|
||
/**
|
||
* 获取模块列表(全路径名称)
|
||
* Get all module pairs with path.
|
||
*
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function getAllModulePairs(string $type = 'task')
|
||
{
|
||
$modules = $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('(type')->eq('story')
|
||
->beginIF($type == 'task')->orWhere('type')->eq('task')->fi()
|
||
->beginIF($type == 'bug')->orWhere('type')->eq('bug')->fi()
|
||
->beginIF($type == 'case')->orWhere('type')->eq('case')->fi()
|
||
->beginIF($type == 'ticket')->orWhere('type')->eq('ticket')->fi()
|
||
->beginIF($type == 'feedback')->orWhere('type')->eq('feedback')->fi()
|
||
->markRight(1)
|
||
->andWhere('deleted')->eq('0')
|
||
->orderBy('grade asc')
|
||
->fetchAll();
|
||
|
||
$pairs = array();
|
||
$pairs[0] = '/';
|
||
foreach($modules as $module)
|
||
{
|
||
if($module->grade == 1)
|
||
{
|
||
$pairs[$module->id] = '/' . $module->name;
|
||
continue;
|
||
}
|
||
|
||
$moduleName = '/' . $module->name;
|
||
$pairs[$module->id] = isset($pairs[$module->parent]) ? $pairs[$module->parent] . $moduleName : $moduleName;
|
||
}
|
||
|
||
return $pairs;
|
||
}
|
||
|
||
/**
|
||
* 构建SQL查询语句。
|
||
* Build the sql query.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @param int $startModule
|
||
* @param string $branch
|
||
* @param string $param
|
||
* @param int $grade
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function buildMenuQuery(int $rootID, string $type, int $startModule = 0, string $branch = 'all', string $param = 'nodeleted', int $grade = 0): string
|
||
{
|
||
/* Set the start module. */
|
||
$startModulePath = '';
|
||
if($startModule > 0)
|
||
{
|
||
$startModule = $this->getById($startModule);
|
||
if($startModule) $startModulePath = $startModule->path . '%';
|
||
}
|
||
if($this->isMergeModule($rootID, $type))
|
||
{
|
||
return $this->dao->select('id, name, root, branch, grade, path, parent, owner, type, extra')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$rootID)
|
||
->beginIF($type == 'task')->andWhere('type')->eq('task')->fi()
|
||
->beginIF($type != 'task')->andWhere('type')->in("story,$type")->fi()
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->beginIF($branch !== 'all' && $branch !== '' && strpos($param, 'noMainBranch') === false)
|
||
->andWhere("(branch")->eq(0)
|
||
->orWhere('branch')->in($branch)
|
||
->markRight(1)
|
||
->fi()
|
||
->beginIF($branch !== 'all' && $branch !== '' && strpos($param, 'noMainBranch') !== false)
|
||
->andWhere('branch')->eq($branch)
|
||
->fi()
|
||
->beginIF(strpos($param, 'nodeleted') !== false)->andWhere('deleted')->eq(0)->fi()
|
||
->orderBy('grade desc, `order`, type desc')
|
||
->get();
|
||
}
|
||
|
||
/* If feedback module is merge add story module.*/
|
||
$syncConfig = $this->getSyncConfig($type);
|
||
$storyCondition = array();
|
||
if(($type == 'feedback' || $type == 'ticket') && strpos($param, 'noproduct') === false && !empty($syncConfig))
|
||
{
|
||
if(isset($syncConfig[$rootID]))
|
||
{
|
||
$storyCondition[] = "`type` = 'story' AND `grade` <= " . (int)zget($syncConfig, $rootID, 0);
|
||
}
|
||
elseif(empty($rootID))
|
||
{
|
||
foreach($syncConfig as $productID => $storyGrade) $storyCondition[] = "(`type` = 'story' AND `root` = '$productID' AND `grade` <= '$storyGrade')";
|
||
}
|
||
}
|
||
$storyCondition = implode(' OR ', array_unique($storyCondition));
|
||
|
||
/* $createdVersion < 4.1 or $type == 'story'. */
|
||
return $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('1=1')
|
||
->beginIF($type != 'feedback' || !empty($rootID))->andwhere('root')->eq((int)$rootID)->fi()
|
||
->andWhere('type', true)->in($type)
|
||
->beginIF($storyCondition)->orWhere('(' . $storyCondition . ')')->fi()
|
||
->markRight(1)
|
||
->beginIF($grade)->andWhere('grade')->le($grade)->fi()
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->beginIF($branch !== 'all' && $branch !== '' && strpos($param, 'noMainBranch') === false)
|
||
->andWhere('(branch')->eq(0)
|
||
->orWhere('branch')->eq($branch)
|
||
->markRight(1)
|
||
->fi()
|
||
->beginIF($branch !== 'all' && $branch !== '' && strpos($param, 'noMainBranch') !== false)
|
||
->andWhere('branch')->eq($branch)
|
||
->fi()
|
||
->beginIF(strpos($param, 'nodeleted') !== false)->andWhere('deleted')->eq(0)->fi()
|
||
->orderBy('root, grade desc, `order`')
|
||
->get();
|
||
}
|
||
|
||
/**
|
||
* 获取某个分支模块的下拉菜单。
|
||
* Get option menu by branch.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @param int $startModule
|
||
* @param string $branch
|
||
* @param string $param
|
||
* @param string $grade
|
||
* @param string $divide /|>
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
protected function getOptionMenuByBranch(int $rootID, string $type = 'story', int $startModule = 0, string $branch = 'all', string $param = 'nodeleted', string $grade = 'all', string $divide = '/'): array
|
||
{
|
||
$branches = array($branch => '');
|
||
if($branch != 'all' && strpos('story|bug|case', $type) !== false)
|
||
{
|
||
$product = $this->loadModel('product')->getById($rootID);
|
||
if($product && $product->type == 'normal') $branches = array('0' => '');
|
||
if($product && $product->type != 'normal')
|
||
{
|
||
$branchPairs = $this->loadModel('branch')->getPairs($rootID, 'all');
|
||
foreach(explode(',', (string)$branch) as $branchID)
|
||
{
|
||
$branchID = (int)$branchID;
|
||
$branches[$branchID] = $branchPairs[$branchID];
|
||
}
|
||
}
|
||
}
|
||
|
||
$syncConfig = $this->getSyncConfig($type); // If feedback or ticket module is merge add story module.
|
||
|
||
$treeMenu = array();
|
||
foreach($branches as $branchID => $branch)
|
||
{
|
||
$stmt = $this->app->dbQuery($this->buildMenuQuery($rootID, $type, $startModule, (string)$branchID, $param));
|
||
$modules = array();
|
||
while($module = $stmt->fetch())
|
||
{
|
||
if(($type == 'feedback' || $type == 'ticket') && $module->type == 'story') // If is feedback or ticket filter story module by grade.
|
||
{
|
||
if(isset($syncConfig[$module->root]) && $module->grade > $syncConfig[$module->root]) continue;
|
||
}
|
||
if($grade != 'all' && $module->grade > $grade) continue;
|
||
$modules[$module->id] = $module;
|
||
}
|
||
|
||
foreach($modules as $module)
|
||
{
|
||
$branchName = (isset($product) && $product->type != 'normal' && (string)$module->branch === BRANCH_MAIN) ? $this->lang->branch->main : $branch;
|
||
$this->buildTreeArray($treeMenu, $modules, $module, (empty($branchName)) ? '/' : "/$branchName/", $divide);
|
||
}
|
||
}
|
||
|
||
ksort($treeMenu);
|
||
$topMenu = explode("\n", trim((string)array_shift($treeMenu)));
|
||
$lastMenu = array(0 => '/');
|
||
foreach($topMenu as $menu)
|
||
{
|
||
if(!strpos($menu, '|')) continue;
|
||
list($label, $moduleID) = explode('|', $menu);
|
||
$lastMenu[$moduleID] = $label;
|
||
}
|
||
|
||
return $lastMenu;
|
||
}
|
||
|
||
/**
|
||
* 获取模块的下拉菜单。
|
||
* Get option menu.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @param int $startModule
|
||
* @param string|array $branch
|
||
* @param string $param
|
||
* @param string $grade
|
||
* @param string $divide /|>
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getOptionMenu(int $rootID, string $type = 'story', int $startModule = 0, string|array $branch = 'all', string $param = 'nodeleted', string $grade = 'all', string $divide = '/'): array
|
||
{
|
||
/* 新手引导。 Tutorial. */
|
||
if(commonModel::isTutorialMode())
|
||
{
|
||
$modulePairs = $this->loadModel('tutorial')->getModulePairs();
|
||
if(!is_array($branch)) return $modulePairs;
|
||
|
||
$modules = array();
|
||
foreach($branch as $branchID) $modules[$branchID] = $modulePairs;
|
||
return $modules;
|
||
}
|
||
|
||
if($type == 'line') $rootID = 0;
|
||
|
||
if(is_array($branch))
|
||
{
|
||
$modules = array();
|
||
foreach($branch as $branchID) $modules[$branchID] = $this->getOptionMenuByBranch($rootID, $type, $startModule, (string)$branchID, $param, $grade, $divide);
|
||
|
||
return $modules;
|
||
}
|
||
|
||
return $this->getOptionMenuByBranch($rootID, $type, $startModule, $branch, $param, $grade, $divide);
|
||
}
|
||
|
||
/**
|
||
* 获取模块对列表。
|
||
* Get module pairs.
|
||
*
|
||
* @param int|array $rootID
|
||
* @param string $viewType
|
||
* @param string $showModule
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getModulePairs(int|array $rootID, string $viewType = 'story', string $showModule = 'end', string $extra = '')
|
||
{
|
||
if(common::isTutorialMode()) $modulePairs = $this->loadModel('tutorial')->getModulePairs();
|
||
|
||
if($viewType == 'task')
|
||
{
|
||
$products = array_keys($this->loadModel('product')->getProductPairsByProject($rootID));
|
||
if(!$this->isMergeModule($rootID, $viewType) || !$products)
|
||
{
|
||
$modules = $this->dao->select('id,name,path,short')->from(TABLE_MODULE)->where('root')->in($rootID)->andWhere('type')->in($viewType)->andWhere('deleted')->eq(0)->fetchAll('id');
|
||
}
|
||
else
|
||
{
|
||
$modules = $this->dao->select('id,name,path,short')->from(TABLE_MODULE)
|
||
->where("((root = '" . (int)$rootID . "' AND type = 'task')")
|
||
->orWhere('(root')->in($products)->andWhere('type')->eq('story')
|
||
->markRight(2)
|
||
->andWhere('deleted')->eq(0)
|
||
->fetchAll('id');
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* When case with libIdList then append lib modules. */
|
||
$modules = array();
|
||
if($this->isMergeModule($rootID, $viewType) || !$rootID) $viewType .= ',story';
|
||
$modules += $this->dao->select('id,name,path,short')->from(TABLE_MODULE)
|
||
->where('type')->in($viewType)
|
||
->beginIF($rootID)->andWhere('root')->in($rootID)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->fetchAll('id');
|
||
}
|
||
|
||
$modulePairs = array();
|
||
foreach($modules as $moduleID => $module)
|
||
{
|
||
list($baseModule) = explode(',', trim($module->path, ','));
|
||
if($showModule == 'base' && isset($modules[$baseModule])) $module = $modules[$baseModule];
|
||
$modulePairs[$moduleID] = $module->short ? $module->short : $module->name;
|
||
}
|
||
|
||
return $modulePairs;
|
||
}
|
||
|
||
/**
|
||
* 获取项目的模块下拉菜单。
|
||
* Get an option menu of task in html.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $startModule
|
||
* @param string $extra
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTaskOptionMenu(int $rootID, int $startModule = 0, string $extra = ''): array
|
||
{
|
||
/* If createdVersion <= 4.1, go to getOptionMenu(). */
|
||
$products = $this->loadModel('product')->getProductPairsByProject($rootID);
|
||
$branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products), 'noclosed');
|
||
|
||
if(!$this->isMergeModule($rootID, 'task') || !$products) return $this->getOptionMenu($rootID, 'task', $startModule);
|
||
|
||
/* createdVersion > 4.1. */
|
||
$startModulePath = '';
|
||
if($startModule > 0)
|
||
{
|
||
$startModule = $this->getById($startModule);
|
||
if($startModule) $startModulePath = $startModule->path . '%';
|
||
}
|
||
$treeMenu = array();
|
||
$lastMenu[] = '/';
|
||
$executionModules = $this->getTaskTreeModules($rootID, true);
|
||
$noProductModules = $this->dao->select('*')->from(TABLE_MODULE)->where('root')->eq($rootID)->andWhere('type')->eq('task')->andWhere('parent')->eq(0)->andWhere('deleted')->eq(0)->orderBy('grade desc, branch, `order`, type')->fetchPairs('id', 'name');
|
||
|
||
/* Fix for not in product modules. */
|
||
$productNum = count($products);
|
||
foreach(array('product' => $products, 'noProduct' => $noProductModules) as $type => $rootModules)
|
||
{
|
||
foreach($rootModules as $id => $rootModule)
|
||
{
|
||
$activeBranch = isset($branchGroups[$id]) ? array_keys($branchGroups[$id]) : array();
|
||
if($type == 'product')
|
||
{
|
||
$modules = $this->dao->select('*')->from(TABLE_MODULE)->where("((root = '" . (int)$rootID . "' AND type = 'task' AND parent != 0) OR (root = $id AND type = 'story'))")
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->beginIF($activeBranch)->andWhere('branch')->in($activeBranch)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, branch, `order`, type')
|
||
->fetchAll('id');
|
||
}
|
||
else
|
||
{
|
||
$modules = $this->dao->select('*')->from(TABLE_MODULE)->where('root')->eq((int)$rootID)
|
||
->andWhere('type')->eq('task')
|
||
->andWhere('path')->like("%,$id,%")
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->fetchAll('id');
|
||
}
|
||
|
||
foreach($modules as $module)
|
||
{
|
||
$parentModules = explode(',', trim($module->path, ','));
|
||
if($type == 'product' && isset($noProductModules[$parentModules[0]])) continue;
|
||
/* Fix bug #2007. */
|
||
if($type == 'product' && $module->type == 'task' && !isset($modules[$parentModules[0]])) continue;
|
||
$rootName = ($productNum > 1 && $type == 'product') ? "/$rootModule/" : '/';
|
||
if($type == 'product' && $module->branch && isset($branchGroups[$id][$module->branch])) $rootName .= $branchGroups[$id][$module->branch] . '/';
|
||
$this->buildTreeArray($treeMenu, $modules, $module, $rootName);
|
||
}
|
||
|
||
ksort($treeMenu);
|
||
$topMenu = array_shift($treeMenu);
|
||
$topMenu = explode("\n", trim((string)$topMenu));
|
||
foreach($topMenu as $menu)
|
||
{
|
||
if(!strpos($menu, '|')) continue;
|
||
list($label, $moduleID) = explode('|', $menu);
|
||
if(isset($executionModules[$moduleID]) || strpos($extra, 'allModule') !== false) $lastMenu[$moduleID] = $label;
|
||
}
|
||
foreach($topMenu as $moduleID => $moduleName)
|
||
{
|
||
if(!isset($executionModules[$moduleID])) unset($treeMenu[$moduleID]);
|
||
}
|
||
}
|
||
}
|
||
return $lastMenu;
|
||
}
|
||
|
||
/**
|
||
* 构建模块树数组。
|
||
* Build tree array.
|
||
*
|
||
* @param array $&treeMenu
|
||
* @param array $modules
|
||
* @param object $module
|
||
* @param string $moduleName
|
||
* @param string $divide
|
||
* @access protected
|
||
* @return void
|
||
*/
|
||
protected function buildTreeArray(array & $treeMenu, array $modules, object $module, string $moduleName = '/', string $divide = '/'): void
|
||
{
|
||
$parentModules = array_filter(explode(',', $module->path));
|
||
foreach($parentModules as $parentModuleID)
|
||
{
|
||
if(empty($parentModuleID)) continue;
|
||
if(empty($modules[$parentModuleID])) continue;
|
||
$moduleName .= $modules[$parentModuleID]->name . $divide;
|
||
}
|
||
$moduleName = rtrim($moduleName, $divide);
|
||
$moduleName .= "|$module->id\n";
|
||
|
||
if(isset($treeMenu[$module->id]) && !empty($treeMenu[$module->id]))
|
||
{
|
||
if(isset($treeMenu[$module->parent]))
|
||
{
|
||
$treeMenu[$module->parent] .= $moduleName;
|
||
}
|
||
else
|
||
{
|
||
$treeMenu[$module->parent] = $moduleName;
|
||
}
|
||
$treeMenu[$module->parent] .= $treeMenu[$module->id];
|
||
}
|
||
else
|
||
{
|
||
if(isset($treeMenu[$module->parent]) && !empty($treeMenu[$module->parent]))
|
||
{
|
||
$treeMenu[$module->parent] .= $moduleName;
|
||
}
|
||
else
|
||
{
|
||
$treeMenu[$module->parent] = $moduleName;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取模块树菜单。
|
||
* Get the tree menu.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @param int $startModule
|
||
* @param array $userFunc the function used to create link
|
||
* @param array $extra extra params
|
||
* @param string $branch product branch
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTreeMenu(int $rootID, string $type = 'root', int $startModule = 0, array $userFunc = array(), array|string $extra = array(), string $branch = 'all'): array
|
||
{
|
||
if($type == 'line') $rootID = 0;
|
||
|
||
$this->loadModel('branch');
|
||
$projectID = zget($extra, 'projectID', 0);
|
||
$branches = array($branch => '');
|
||
$executionModules = array();
|
||
if($branch && empty($projectID))
|
||
{
|
||
$branchName = $this->branch->getByID($branch);
|
||
$branches = array($branch => $branchName);
|
||
$extra = $userFunc[1] == 'createTestTaskLink' ? $extra : array('rootID' => $rootID, 'branch' => $branch);
|
||
}
|
||
|
||
$product = $this->loadModel('product')->getByID($rootID);
|
||
|
||
$onlyGetLinked = ($projectID && $this->config->vision != 'lite');
|
||
if(strpos('story|bug|case', $type) !== false && $branch === 'all' && empty($projectID))
|
||
{
|
||
if($product->type != 'normal') $branches = array(BRANCH_MAIN => $this->lang->branch->main) + $this->loadModel('branch')->getPairs($rootID, 'noempty');
|
||
}
|
||
elseif(strpos(',case,bug,', ",$type,") !== false && $this->app->tab == 'execution')
|
||
{
|
||
if($product->type != 'normal' && $projectID) $branches += $this->branch->getPairs($product->id, 'noempty', $projectID);
|
||
if($onlyGetLinked) $executionModules = $this->getTaskTreeModules($projectID, true, $type, array('branchID' => $branch));
|
||
}
|
||
elseif(($type == 'story' && $this->app->rawModule == 'projectstory') || (strpos(',case,bug,', ",$type,") !== false && $this->app->tab == 'project'))
|
||
{
|
||
if($product->type != 'normal' && $projectID) $branches += $this->branch->getPairs((int)$product->id, 'noempty', $projectID);
|
||
if($onlyGetLinked) $executionModules = $this->getTaskTreeModules($projectID, true, $type, $type == 'story' ? array() : array('branchID' => $branch));
|
||
}
|
||
|
||
/* Add for task #1945. check the module has case or no. */
|
||
if($type == 'case' && !empty($extra)) $this->loadModel('testtask');
|
||
|
||
$modules = array();
|
||
$stmt = $this->app->dbQuery($this->buildMenuQuery($rootID, $type, $startModule, $branch));
|
||
while($module = $stmt->fetch())
|
||
{
|
||
if($onlyGetLinked && !isset($executionModules[$module->id]) && empty($product->shadow)) continue;
|
||
|
||
$data = $this->buildTree($module, $type, '0', $userFunc, $extra, $branch);
|
||
if($data) $modules[] = $data;
|
||
}
|
||
|
||
return $modules;
|
||
}
|
||
|
||
/**
|
||
* 获取任务模块树菜单。
|
||
* Get the tree menu of task.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $productID
|
||
* @param int $startModule
|
||
* @param array $userFunc
|
||
* @param string $extra
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTaskTreeMenu(int $rootID, int $productID = 0, int $startModule = 0, array $userFunc = array(), string $extra = ''): array
|
||
{
|
||
$extra = array('tip' => true, 'extra' => $extra, 'executionID' => $rootID, 'productID' => $productID);
|
||
|
||
/* If createdVersion <= 4.1, go to getTreeMenu(). */
|
||
$products = $this->loadModel('product')->getProductPairsByProject($rootID);
|
||
$branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
|
||
|
||
if(!$this->isMergeModule($rootID, 'task') || !$products)
|
||
{
|
||
$extra['tip'] = false;
|
||
return (array)$this->getTreeMenu($rootID, 'task', $startModule, $userFunc, $extra);
|
||
}
|
||
|
||
/* createdVersion > 4.1. */
|
||
$menu = array();
|
||
|
||
/* Set the start module. */
|
||
$startModulePath = '';
|
||
if($startModule > 0)
|
||
{
|
||
$startModule = $this->getById($startModule);
|
||
if($startModule) $startModulePath = $startModule->path . '%';
|
||
}
|
||
|
||
$executionModules = $this->getTaskTreeModules($rootID, true);
|
||
|
||
/* Get module according to product. */
|
||
$productNum = count($products);
|
||
foreach($products as $id => $product)
|
||
{
|
||
/* 产品名也要放在树状菜单上。Show the product in tree menu. */
|
||
$menuItem = new stdclass();
|
||
$menuItem->id = "product-$id";
|
||
$menuItem->name = $product;
|
||
$menuItem->parent = 0;
|
||
$menuItem->url = helper::createLink('execution', 'task', "executionID=$rootID&status=byProduct&praram=$id");
|
||
$menu["product-$id"] = $menuItem;
|
||
|
||
/* tree menu. */
|
||
if(empty($branchGroups[$id])) $branchGroups[$id]['0'] = '';
|
||
foreach($branchGroups[$id] as $branch => $branchName)
|
||
{
|
||
$query = $this->dao->select('*')->from(TABLE_MODULE)->where("((root = '" . (int)$rootID . "' AND type = 'task' AND parent != 0) OR (root = $id AND type = 'story' AND branch ='$branch'))")
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
while($module = $stmt->fetch())
|
||
{
|
||
if(!isset($executionModules[$module->id]) && strpos($extra['extra'], 'allModule') === false) continue;
|
||
|
||
if($module->type == 'story' && $module->root > 0 && $module->parent == 0) $module->parent = "product-{$module->root}";
|
||
$module->url = helper::createLink('execution', 'task', "executionID={$rootID}&type=byModule¶m={$module->id}");
|
||
$menu[$module->id] = $module;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Get execution module. */
|
||
if($startModule == 0)
|
||
{
|
||
$query = $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$rootID)
|
||
->andWhere('type')->eq('task')
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
while($module = $stmt->fetch())
|
||
{
|
||
$module->url = helper::createLink('execution', 'task', "executionID={$rootID}&type=byModule¶m={$module->id}");
|
||
$menu[$module->id] = $module;
|
||
}
|
||
}
|
||
|
||
return $menu;
|
||
}
|
||
|
||
/**
|
||
* 获取 bug 的模块。
|
||
* Get the modules of bug.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $productID
|
||
* @param int $startModule
|
||
* @param array $userFunc
|
||
* @param array $extra
|
||
* @access public
|
||
* @return void
|
||
*/
|
||
public function getBugTreeMenu(int $rootID, int $productID = 0, int $startModule = 0, array $userFunc = array(), array $extra = array()): array
|
||
{
|
||
$extra += array('executionID' => $rootID, 'projectID' => $rootID, 'productID' => $productID, 'tip' => true);
|
||
$tab = $this->app->tab;
|
||
|
||
/* If createdVersion <= 4.1, go to getTreeMenu(). */
|
||
$products = $tab == 'execution' ? $this->loadModel('product')->getProducts($rootID) : $this->loadModel('product')->getProductPairsByProject($rootID);
|
||
$branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
|
||
|
||
/* Set the start module. */
|
||
$startModulePath = '';
|
||
if($startModule > 0)
|
||
{
|
||
$startModule = $this->getByID($startModule);
|
||
if($startModule) $startModulePath = $startModule->path . '%';
|
||
}
|
||
|
||
$executionModules = $this->getTaskTreeModules($rootID, true, 'bug');
|
||
|
||
/* Get module according to product. */
|
||
$moduleName = strpos(',project,execution,', ",$tab,") !== false ? $this->app->tab : 'bug';
|
||
$methodName = strpos(',project,execution,', ",$tab,") !== false ? 'bug' : 'browse';
|
||
$param = strpos(',project,execution,', ",$tab,") !== false ? "{$tab}ID={$rootID}&" : '';
|
||
|
||
$modules = array();
|
||
foreach($products as $productID => $product)
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = uniqid();
|
||
$data->parent = '0';
|
||
$data->name = is_object($product) ? $product->name : $product;
|
||
$data->url = helper::createLink($moduleName, $methodName, "{$param}productID=$productID");
|
||
$modules[] = $data;
|
||
|
||
if(empty($branchGroups[$productID])) $branchGroups[$productID]['0'] = '';
|
||
foreach($branchGroups[$productID] as $branch => $branchName)
|
||
{
|
||
$query = $this->dao->select('*')->from(TABLE_MODULE)->where("((root = $productID AND type = 'bug' AND branch = '$branch') OR (root = $productID AND type = 'story' AND branch = '$branch'))")
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
while($module = $stmt->fetch())
|
||
{
|
||
if(isset($executionModules[$module->id])) $modules[] = $this->buildTree($module, 'bug', $data->id, $userFunc, $extra, (string)$branch);
|
||
}
|
||
}
|
||
}
|
||
|
||
return $modules;
|
||
}
|
||
|
||
/**
|
||
* 获取用例模块树菜单。
|
||
* Get the tree menu of case in html.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $productID
|
||
* @param int $startModule
|
||
* @param array $userFunc
|
||
* @param string $extra
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getCaseTreeMenu(int $rootID, int $productID = 0, int $startModule = 0, array $userFunc = array(), string $extra = ''): array
|
||
{
|
||
$extra = array('projectID' => $rootID, 'executionID' => $rootID, 'productID' => $productID, 'tip' => true, 'extra' => $extra);
|
||
|
||
/* If createdVersion <= 4.1, go to getTreeMenu(). */
|
||
$products = $this->app->tab != 'execution' ? $this->loadModel('product')->getProductPairsByProject($rootID) : $this->loadModel('product')->getProducts($rootID);
|
||
$branchGroups = $this->dao->select('t1.product as product,branch,t3.name')->from(TABLE_PROJECTPRODUCT)->alias('t1')
|
||
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product=t2.id')
|
||
->leftJoin(TABLE_BRANCH)->alias('t3')->on('t1.branch=t3.id')
|
||
->where('t1.project')->eq($rootID)
|
||
->andWhere('t2.type')->ne('normal')
|
||
->andWhere('t1.product')->in(array_keys($products))
|
||
->andWhere('t2.deleted')->eq('0')
|
||
->fetchGroup('product', 'branch');
|
||
|
||
$this->app->loadLang('branch');
|
||
foreach($branchGroups as $productID => $branches)
|
||
{
|
||
$branchGroups[$productID][0] = $this->lang->branch->main;
|
||
foreach($branches as $branchID => $branchInfo)
|
||
{
|
||
$branchGroups[$productID][$branchID] = $branchID == BRANCH_MAIN ? $this->lang->branch->main : $branchInfo->name;
|
||
}
|
||
}
|
||
|
||
/* Set the start module. */
|
||
$startModulePath = '';
|
||
if($startModule > 0)
|
||
{
|
||
$startModule = $this->getById($startModule);
|
||
if($startModule) $startModulePath = $startModule->path . '%';
|
||
}
|
||
|
||
$executionModules = $this->getTaskTreeModules($rootID, true, 'case');
|
||
|
||
/* Get module according to product. */
|
||
$productNum = count($products);
|
||
$moduleName = strpos(',project,execution,', ",{$this->app->tab},") !== false ? $this->app->tab : 'testcase';
|
||
$methodName = strpos(',project,execution,', ",{$this->app->tab},") !== false ? 'testcase' : 'browse';
|
||
$param = $this->app->tab == 'project' ? "projectID={$this->session->project}&" : '';
|
||
$param = $this->app->tab == 'execution' ? "executionID={$rootID}&" : $param;
|
||
|
||
$modules = array();
|
||
foreach($products as $productID => $product)
|
||
{
|
||
$extra['productID'] = $productID;
|
||
|
||
$data = new stdclass();
|
||
$data->id = uniqid();
|
||
$data->parent = 0;
|
||
$data->name = is_object($product) ? $product->name : $product;
|
||
$data->url = helper::createLink($moduleName, $methodName, $param . "productID=$productID");
|
||
$modules[] = $data;
|
||
|
||
if(empty($branchGroups[$productID])) $branchGroups[$productID]['0'] = '';
|
||
foreach($branchGroups[$productID] as $branch => $branchName)
|
||
{
|
||
$query = $this->dao->select('*')->from(TABLE_MODULE)->where("((root = $productID AND type = 'case' AND branch = '$branch') OR (root = $productID AND type = 'story' AND branch = '$branch'))")
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
while($module = $stmt->fetch())
|
||
{
|
||
if(isset($executionModules[$module->id])) $modules[] = $this->buildTree($module, 'case', $data->id, $userFunc, $extra, (string)$branch);
|
||
}
|
||
}
|
||
}
|
||
return $modules;
|
||
}
|
||
|
||
/**
|
||
* 获取项目关联的story。
|
||
* Get project story tree menu.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $startModule
|
||
* @param string|array $userFunc
|
||
* @param array $extra
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getProjectStoryTreeMenu(int $rootID, int $startModule = 0, string|array$userFunc = '', array $extra = array()): array
|
||
{
|
||
$this->app->loadLang('branch');
|
||
|
||
$menu = array();
|
||
|
||
if($this->app->rawModule == 'projectstory') $extra['projectID'] = $rootID;
|
||
if($this->app->rawModule == 'execution') $extra['executionID'] = $rootID;
|
||
$startModulePath = '';
|
||
if($startModule > 0)
|
||
{
|
||
$startModule = $this->getById($startModule);
|
||
if($startModule) $startModulePath = $startModule->path . '%';
|
||
}
|
||
|
||
$executionModules = $this->getTaskTreeModules($rootID, true);
|
||
|
||
/* Get module according to product. */
|
||
$products = $this->loadModel('product')->getProductPairsByProject($rootID);
|
||
$branchGroups = $this->loadModel('execution')->getBranchByProduct(array_keys($products), $rootID);
|
||
|
||
$productNum = count($products);
|
||
$storyType = zget($extra, 'storyType', 'story');
|
||
foreach($products as $id => $product)
|
||
{
|
||
$extra['productID'] = $id;
|
||
$projectProductLink = helper::createLink('projectstory', 'story', "projectID=$rootID&productID=$id&branch=all&browseType=¶m=0&storyType={$storyType}");
|
||
$executionProductLink = helper::createLink('execution', 'story', "executionID=$rootID&storyType={$storyType}&orderBy=&type=byProduct&praram=$id");
|
||
$link = $this->app->rawModule == 'projectstory' ? $projectProductLink : $executionProductLink;
|
||
if($productNum > 1)
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = "p_{$id}";
|
||
$data->parent = '0';
|
||
$data->name = $product;
|
||
$data->url = $link;
|
||
|
||
$menu[] = $data;
|
||
}
|
||
|
||
/* tree menu. */
|
||
$branchGroups[$id][BRANCH_MAIN] = empty($branchGroups[$id]) ? '' : $this->lang->branch->main;
|
||
foreach($branchGroups[$id] as $branch => $branchName)
|
||
{
|
||
$treeMenu = array();
|
||
$query = $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$id)
|
||
->andWhere('type')->eq('story')
|
||
->beginIF(count($branchGroups[$id]) > 1)->andWhere('branch')->eq($branch)->fi()
|
||
->beginIF($startModulePath)->andWhere('path')->like($startModulePath)->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, branch, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
while($module = $stmt->fetch())
|
||
{
|
||
/* Ignore unused modules. */
|
||
if(!isset($executionModules[$module->id]) && $this->config->vision != 'lite') continue;
|
||
|
||
$treeMenu = $this->buildTree($module, 'story', '0', $userFunc, $extra);
|
||
if($productNum > 1 && $module->parent == 0) $treeMenu->parent = "p_{$module->root}";
|
||
$menu[] = $treeMenu;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $menu;
|
||
}
|
||
|
||
/**
|
||
* 获取主机模块树菜单。
|
||
* Get host tree menu.
|
||
*
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getHostTreeMenu(): array
|
||
{
|
||
$menu = array();
|
||
/* tree menu. */
|
||
$treeMenu = array();
|
||
$stmt = $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('type')->eq('host')
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade_desc,id_asc')
|
||
->query();
|
||
|
||
while($module = $stmt->fetch())
|
||
{
|
||
$treeMenu = $this->buildTree($module, '', '0', array('treeModel', 'createHostLink'));
|
||
if($module->parent == 0) $treeMenu->parent = $module->root;
|
||
|
||
$menu[] = $treeMenu;
|
||
}
|
||
|
||
return $menu;
|
||
}
|
||
|
||
/**
|
||
* 获取模块树。
|
||
* Get tree structure.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTreeStructure(int $rootID, string $type): array
|
||
{
|
||
$stmt = $this->app->dbQuery($this->buildMenuQuery($rootID, $type, $startModule = 0));
|
||
return $this->getDataStructure($stmt, $type, $rootID);
|
||
}
|
||
|
||
/**
|
||
* 获取某个项目的所有任务模块树。
|
||
* Get full task tree
|
||
* @param int $executionID, common value is execution id
|
||
* @param int $productID
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTaskStructure(int $rootID, int $productID = 0): array
|
||
{
|
||
$extra = array('executionID' => $rootID, 'productID' => $productID, 'tip' => true);
|
||
|
||
/* If createdVersion <= 4.1, go to getTreeMenu(). */
|
||
$products = $this->loadModel('product')->getProductPairsByProject($rootID);
|
||
$branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
|
||
|
||
if(!$this->isMergeModule($rootID, 'task') || !$products)
|
||
{
|
||
$extra['tip'] = false;
|
||
$stmt = $this->app->dbQuery($this->buildMenuQuery($rootID, 'task', $startModule = 0));
|
||
if(empty($products))
|
||
{
|
||
$this->app->loadConfig('execution');
|
||
$this->config->execution->task->allModule = 1;
|
||
}
|
||
return $this->getDataStructure($stmt, 'task', $rootID);
|
||
}
|
||
|
||
/* only get linked modules && ignore others. */
|
||
$executionModules = $this->getTaskTreeModules($rootID, true);
|
||
|
||
/* Get module according to product. */
|
||
$fullTrees = array();
|
||
foreach($products as $id => $product)
|
||
{
|
||
$productInfo = $this->product->getById($id);
|
||
/* tree menu. */
|
||
$productTree = array();
|
||
$branchTrees = array();
|
||
if(empty($branchGroups[$id])) $branchGroups[$id]['0'] = '';
|
||
foreach($branchGroups[$id] as $branch => $branchName)
|
||
{
|
||
$query = $this->dao->select('id, name, type, grade, path, parent, root')->from(TABLE_MODULE)->where("((root = '" . (int)$rootID . "' AND type = 'task' AND parent != 0) OR (root = $id AND type = 'story' AND branch ='$branch'))")
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
if($branch == 0) $productTree = $this->getDataStructure($stmt, 'task', $rootID, $executionModules);
|
||
if($branch != 0)
|
||
{
|
||
$children = $this->getDataStructure($stmt, 'task', $rootID, $executionModules);
|
||
if($children) $branchTrees[] = array('name' => $branchName, 'root' => $id, 'type' => 'branch', 'actions' => false, 'children' => $children);
|
||
}
|
||
}
|
||
if($branchTrees) $productTree[] = array('name' => $this->lang->product->branchName[$productInfo->type], 'root' => $id, 'type' => 'branch', 'actions' => false, 'children' => $branchTrees);
|
||
$fullTrees[] = array('name' => $productInfo->name, 'root' => $id, 'type' => 'product', 'actions' => false, 'children' => $productTree);
|
||
}
|
||
|
||
/* Get execution module. */
|
||
$query = $this->dao->select('id,name,type,grade,path,parent')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$rootID)
|
||
->andWhere('type')->eq('task')
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('grade desc, `order`, type')
|
||
->get();
|
||
$stmt = $this->app->dbQuery($query);
|
||
$taskTrees = $this->getDataStructure($stmt, 'task', $rootID, $executionModules);
|
||
foreach($taskTrees as $taskModule) $fullTrees[] = $taskModule;
|
||
|
||
return $fullTrees;
|
||
}
|
||
|
||
/**
|
||
* 构造模块树。
|
||
* Build tree.
|
||
*
|
||
* @param object $module
|
||
* @param string $type
|
||
* @param string $parent
|
||
* @param array $userFunc
|
||
* @param array $extra
|
||
* @param int $branch
|
||
* @access protected
|
||
* @return object|false
|
||
*/
|
||
protected function buildTree(object $module, string $type, string $parent = '0', array $userFunc = array(), array|string $extra = array(), string $branch = 'all'): object|false
|
||
{
|
||
/* Add for task #1945. check the module has case or no. */
|
||
if((isset($extra['rootID']) && isset($extra['branch']) && $branch === 'null') || ($type == 'case' && is_numeric($extra)))
|
||
{
|
||
static $objects = array();
|
||
if(empty($objects))
|
||
{
|
||
if(is_array($extra))
|
||
{
|
||
$table = $this->config->objectTables[$type];
|
||
$objects = $this->dao->select('module')->from($table)->where('product')->eq((int)$extra['rootID'])->andWhere('branch')->eq($extra['branch'])->fetchAll('module');
|
||
}
|
||
else
|
||
{
|
||
$objects = $this->dao->select('t1.*,t2.module')->from(TABLE_TESTRUN)->alias('t1')
|
||
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
|
||
->where('t1.task')->eq((int)$extra)
|
||
->fetchAll('module');
|
||
}
|
||
}
|
||
static $modules = array();
|
||
if(empty($modules))
|
||
{
|
||
$typeCondition = "type='story'";
|
||
if($type != 'story') $typeCondition .= " OR type='{$type}'";
|
||
$modules = $this->dao->select('id,path')->from(TABLE_MODULE)->where('root')->eq($module->root)->andWhere("({$typeCondition})")->fetchPairs('id', 'path');
|
||
}
|
||
$childModules = array();
|
||
foreach($modules as $moduleID => $modulePath)
|
||
{
|
||
if(strpos($modulePath, $module->path) === 0) $childModules[$moduleID] = $moduleID;
|
||
}
|
||
$hasObjects = false;
|
||
foreach($childModules as $moduleID)
|
||
{
|
||
if(isset($objects[$moduleID]))
|
||
{
|
||
$hasObjects = true;
|
||
break;
|
||
}
|
||
}
|
||
if(!$hasObjects) return false;
|
||
}
|
||
|
||
if(is_array($extra)) $extra['branchID'] = $branch;
|
||
if(empty($extra))
|
||
{
|
||
$extra = array();
|
||
$extra['branchID'] = $branch;
|
||
}
|
||
|
||
return call_user_func($userFunc, $type, $module, $parent, $extra);
|
||
}
|
||
|
||
/**
|
||
* 获取执行的模块。
|
||
* Get execution modules.
|
||
*
|
||
* @param int $executionID
|
||
* @param bool $parent
|
||
* @param string $linkObject
|
||
* @param array $extra
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTaskTreeModules(int $executionID, bool $parent = false, string $linkObject = 'story', array $extra = array()): array
|
||
{
|
||
$field = $parent ? 'path' : 'id';
|
||
$paths = array();
|
||
|
||
if($linkObject)
|
||
{
|
||
$branch = zget($extra, 'branchID', 0);
|
||
|
||
/* 获取执行下关联Bug、Story、Case的模块。 Get object paths of this execution. */
|
||
if($linkObject == 'story' || $linkObject == 'case') // Get story or case from projectStory and projectCase, get bug from zt_bug
|
||
{
|
||
$table1 = TABLE_PROJECTSTORY;
|
||
$table2 = TABLE_STORY;
|
||
if($linkObject == 'case')
|
||
{
|
||
$table1 = TABLE_PROJECTCASE;
|
||
$table2 = TABLE_CASE;
|
||
}
|
||
|
||
$paths = $this->dao->select("t4.{$field}")->from(TABLE_PROJECT)->alias('t1')
|
||
->leftJoin($table1)->alias('t2')->on('t1.id=t2.project')
|
||
->leftJoin($table2)->alias('t3')->on("t2.{$linkObject}=t3.id")
|
||
->leftJoin(TABLE_MODULE)->alias('t4')->on('t3.module=t4.id')
|
||
->where('t3.deleted')->eq(0)
|
||
->andWhere('t4.deleted')->eq(0)
|
||
->andWhere('t1.project', true)->eq($executionID)->orWhere('t2.project')->eq($executionID)->markRight(1)
|
||
->beginIF(isset($extra['branchID']) && $branch !== 'all')->andWhere('t3.branch')->eq($branch)->fi()
|
||
->fetchPairs();
|
||
}
|
||
elseif($linkObject == 'bug' && str_contains(',project,execution,', ",{$this->app->tab},"))
|
||
{
|
||
$paths = $this->dao->select("t2.{$field}")->from(TABLE_BUG)->alias('t1')
|
||
->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module = t2.id')
|
||
->where('t1.deleted')->eq(0)
|
||
->andWhere('t2.deleted')->eq(0)
|
||
->beginIF(isset($extra['branchID']) && $branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
|
||
->andWhere("t1.{$this->app->tab}")->eq($executionID)
|
||
->fetchPairs();
|
||
}
|
||
else
|
||
{
|
||
return $paths;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
$productGroups = $this->dao->select('product,branch')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->fetchGroup('product', 'branch');
|
||
$modules = $this->dao->select('id,root,branch')->from(TABLE_MODULE)->where('root')->in(array_keys($productGroups))->andWhere('type')->eq('story')->andWhere('deleted')->eq(0)->fetchAll();
|
||
|
||
foreach($modules as $module)
|
||
{
|
||
if(empty($module->branch)) $paths[$module->id] = $module->id;
|
||
if(isset($productGroups[$module->root][0]) || isset($productGroups[$module->root][$module->branch])) $paths[$module->id] = $module->id;
|
||
}
|
||
}
|
||
|
||
if(!str_contains(',case,bug,', ",$linkObject,"))
|
||
{
|
||
/* Add task paths of this execution.*/
|
||
$paths += $this->dao->select($field)->from(TABLE_MODULE)->where('root')->eq($executionID)->andWhere('type')->eq('task')->andWhere('deleted')->eq(0)->fetchPairs($field, $field);
|
||
|
||
/* Add task paths of this execution for has existed. */
|
||
$paths += $this->dao->select("t1.{$field}")->from(TABLE_MODULE)->alias('t1')
|
||
->leftJoin(TABLE_TASK)->alias('t2')->on('t1.id=t2.module')
|
||
->where('t2.module')->ne(0)
|
||
->andWhere('t2.execution')->eq($executionID)
|
||
->andWhere('t2.deleted')->eq(0)
|
||
->andWhere('t1.type')->eq('story')
|
||
->andWhere('t1.deleted')->eq(0)
|
||
->fetchPairs($field, $field);
|
||
}
|
||
|
||
/* Get all modules from paths. */
|
||
$executionModules = array();
|
||
foreach($paths as $path)
|
||
{
|
||
foreach(explode(',', (string)$path) as $module) $executionModules[$module] = $module;
|
||
}
|
||
|
||
return array_filter($executionModules);
|
||
}
|
||
|
||
/**
|
||
* 生成需求链接。
|
||
* Create link of a story.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createStoryLink(string $type, object $module, string $parent = '0', array $extra = array()): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $parent ? uniqid() : (string)$module->id;
|
||
$data->parent = $parent ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
|
||
if(isset($extra['projectID']) && !empty($extra['projectID']))
|
||
{
|
||
$productID = zget($extra, 'productID', 0);
|
||
$projectID = $extra['projectID'];
|
||
$data->url = helper::createLink('projectstory', 'story', "projectID=$projectID&productID=$productID&branch=&browseType=byModule¶m={$module->id}&storyType=story");
|
||
}
|
||
elseif(isset($extra['executionID']) && !empty($extra['executionID']))
|
||
{
|
||
$executionID = $extra['executionID'];
|
||
$data->url = helper::createLink('execution', 'story', "executionID=$executionID&storyType=story&orderBy=order_desc&type=byModule¶m={$module->id}");
|
||
}
|
||
else
|
||
{
|
||
$data->url = helper::createLink('product', 'browse', "root={$module->root}&branch={$extra['branchID']}&type=byModule¶m={$module->id}&storyType=story");
|
||
}
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成用户需求链接。
|
||
* Create link of requirement for waterfall.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createRequirementLink(string $type, object $module, string $parent = '0', array $extra = array()): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $parent ? uniqid() : (string)$module->id;
|
||
$data->parent = $parent ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
|
||
if(isset($extra['projectID']) && !empty($extra['projectID']))
|
||
{
|
||
$productID = zget($extra, 'productID', 0);
|
||
$projectID = $extra['projectID'];
|
||
$data->url = helper::createLink('projectstory', 'story', "projectID=$projectID&productID=$productID&branch=&browseType=byModule¶m={$module->id}&storyType=requirement");
|
||
}
|
||
elseif(isset($extra['executionID']) && !empty($extra['executionID']))
|
||
{
|
||
$executionID = $extra['executionID'];
|
||
$data->url = helper::createLink('execution', 'story', "executionID=$executionID&storyType=requirement&orderBy=order_desc&type=byModule¶m={$module->id}");
|
||
}
|
||
else
|
||
{
|
||
$branch = isset($extra['branchID']) ? $extra['branchID'] : 'all';
|
||
$data->url = helper::createLink('product', 'browse', "root={$module->root}&branch=$branch&type=byModule¶m={$module->id}&storyType=requirement");
|
||
}
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成业务需求链接。
|
||
* Create link of epic for waterfall.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createEpicLink(string $type, object $module, string $parent = '0', array $extra = array()): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $parent ? uniqid() : (string)$module->id;
|
||
$data->parent = $parent ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
|
||
if(isset($extra['projectID']) && !empty($extra['projectID']))
|
||
{
|
||
$productID = zget($extra, 'productID', 0);
|
||
$projectID = $extra['projectID'];
|
||
$data->url = helper::createLink('projectstory', 'story', "projectID=$projectID&productID=$productID&branch=&browseType=byModule¶m={$module->id}&storyType=epic");
|
||
}
|
||
elseif(isset($extra['executionID']) && !empty($extra['executionID']))
|
||
{
|
||
$executionID = $extra['executionID'];
|
||
$data->url = helper::createLink('execution', 'story', "executionID=$executionID&storyType=epic&orderBy=order_desc&type=byModule¶m={$module->id}");
|
||
}
|
||
else
|
||
{
|
||
$branch = isset($extra['branchID']) ? $extra['branchID'] : 'all';
|
||
$data->url = helper::createLink('product', 'browse', "root={$module->root}&branch=$branch&type=byModule¶m={$module->id}&storyType=epic");
|
||
}
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成任务链接。
|
||
* Create link of a task.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createTaskLink(string $type, object $module): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('execution', 'task', "executionID={$module->root}&type=byModule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成交付物链接。
|
||
* Create link of a deliverable.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param string $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createDeliverableLink(string $type, object $module, string $parent, string $extra = ''): object
|
||
{
|
||
if(!$extra) $extra = 'all';
|
||
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('deliverable', 'browse', "groupID={$module->root}&browseType={$extra}¶m={$module->id}");
|
||
$data->extra = $module->extra;
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成项目交付物链接。
|
||
* Create link of a project deliverable.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param string $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createProjectDeliverableLink(string $type, object $module, string $parent, string $extra = ''): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('project', 'deliverable', "projectID={$extra}&browseType=bymodule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成文档链接。
|
||
* Create link of a doc.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createDocLink(string $type, object $module): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $parent ? uniqid() : (string)$module->id;
|
||
$data->parent = $parent ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('doc', 'browse', "libID={$module->root}&browseType=byModule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* Create the manage link of a module.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createManageLink(string $type, object $module): object
|
||
{
|
||
static $users;
|
||
if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter');
|
||
|
||
$name = $module->name;
|
||
if(strpos('bug,case', $type) !== false) $name .= '[' . strtoupper(substr($type, 0, 1)) . ']';
|
||
if($type == 'bug' && $module->owner) $name .= '[' . $users[$module->owner] . ']';
|
||
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = (string)$module->parent;
|
||
$data->name = $name;
|
||
$data->url = helper::createLink('subject', 'browse', "currentModuleID={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 设置Bug模块树的点击链接。
|
||
* Click link to set Bug module tree.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createBugLink(string $type, object $module, string $parent, array $extra = array()): object
|
||
{
|
||
$moduleName = strpos(',project,execution,', ",{$this->app->tab},") !== false ? $this->app->tab : 'bug';
|
||
$methodName = strpos(',project,execution,', ",{$this->app->tab},") !== false ? 'bug' : 'browse';
|
||
$param = "root={$module->root}&branch=&type=byModule¶m={$module->id}";
|
||
|
||
$extraType = (isset($extra['type']) && $extra['type'] != 'bysearch') ? $extra['type'] : 'all';
|
||
$projectID = zget($extra, 'projectID', 0);
|
||
$branchID = zget($extra, 'branchID', 0);
|
||
$orderBy = zget($extra, 'orderBy', '');
|
||
$build = zget($extra, 'build', 0);
|
||
if($this->app->tab == 'execution') $param = "executionID={$projectID}&productID={$module->root}&branch={$branchID}&orderBy={$orderBy}&build={$build}&type={$extraType}¶m={$module->id}";
|
||
if($this->app->tab == 'project') $param = "projectID={$projectID}&productID={$module->root}&branch={$branchID}&orderBy={$orderBy}&build={$build}&type={$extraType}¶m={$module->id}";
|
||
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = $parent && empty($module->parent) ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink($moduleName, $methodName, $param);
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成测试用例的链接。
|
||
* Create module of a test case.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array|string $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createCaseLink(string $type, object $module, string $parent, array|string $extra = array()): object
|
||
{
|
||
$moduleName = strpos(',project,execution,', ",{$this->app->tab},") !== false ? $this->app->tab : 'testcase';
|
||
$methodName = strpos(',project,execution,', ",{$this->app->tab},") !== false ? 'testcase' : 'browse';
|
||
$param = $this->app->tab == 'project' ? "projectID={$this->session->project}&" : "";
|
||
$param = $this->app->tab == 'execution' ? "executionID={$extra['projectID']}&" : $param;
|
||
$branch = isset($extra['branchID']) ? $extra['branchID'] : 'all';
|
||
$moduleParam = $this->app->tab == 'execution' ? "¶m=0&moduleID={$module->id}" : "¶m={$module->id}";
|
||
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = $parent && empty($module->parent) ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink($moduleName, $methodName, $param . "productID={$module->root}&branch=$branch&browseType=byModule" . $moduleParam);
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成测试场景的链接。
|
||
* Create module of a scene.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array|string $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createSceneLink(string $type, object $module, string $parent, array|string $extra = array()): object
|
||
{
|
||
$branch = $extra['branchID'] ?? '';
|
||
$orderBy = $extra['orderBy'] ?? 'id_desc';
|
||
|
||
$data = new stdclass();
|
||
$data->id = (string)$module->id;
|
||
$data->parent = $parent && empty($module->parent) ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('testcase', 'browseScene', "productID={$module->root}&branch=$branch&moduleID={$module->id}&orderBy=$orderBy");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成测试任务的链接。
|
||
* Create link of a test task.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array|string $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createTestTaskLink(string $type, object $module, string $parent, array|string $extra = array()): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $parent ? uniqid() : (string)$module->id;
|
||
$data->parent = $parent ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('testtask', 'cases', "taskID=$extra&type=byModule&module={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成用例库的链接。
|
||
* Create case lib link.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createCaseLibLink($type, $module): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $module->id;
|
||
$data->parent = $module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('caselib', 'browse', "root={$module->root}&type=byModule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成分支的链接。
|
||
* Create branch link
|
||
*
|
||
* @param string $type
|
||
* @param int $rootID
|
||
* @param int $branchID
|
||
* @param string $branch
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function createBranchLink($type, $rootID, $branchID, $branch): string
|
||
{
|
||
if($type == 'story') return html::a(helper::createLink('product', 'browse', "productID={$rootID}&branch=$branchID"), $branch, '_self', "id='branch{$branchID}'");
|
||
if($type == 'bug') return html::a(helper::createLink('bug', 'browse', "productID={$rootID}&branch=$branchID"), $branch, '_self', "id='branch{$branchID}'");
|
||
if($type == 'case') return html::a(helper::createLink('testcase', 'browse', "productID={$rootID}&branch=$branchID"), $branch, '_self', "id='branch{$branchID}'");
|
||
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* 生成反馈的链接。
|
||
* Create link of feedback.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createFeedbackLink(string $type, object $module): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $module->id;
|
||
$data->parent = $module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('feedback', $this->app->methodName, "type=byModule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 生成工单的链接。
|
||
* Create link of ticket.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createTicketLink(string $type, object $module): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $module->id;
|
||
$data->parent = $module->parent;
|
||
$data->name = $module->name;
|
||
$data->url = helper::createLink('ticket', $this->app->methodName, "type=byModule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* Create link of practice.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function createPracticeLink(string $type, object $module): string
|
||
{
|
||
return html::a(helper::createLink('traincourse', 'practicebrowse', "moduleID={$module->id}"), $module->name, '_self', "id='module{$module->id}' title='{$module->name}'");
|
||
}
|
||
|
||
/**
|
||
* 生成培训技能的链接。
|
||
* Create link of trainskill.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $extra
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function createTrainSkillLink(string $type, object $module, string $extra = ''): string
|
||
{
|
||
return html::a(helper::createLink('trainskill', 'browse', "type=byModule¶m={$module->id}"), $module->name, '', "id='module{$module->id}' title='{$module->name}'");
|
||
}
|
||
|
||
/**
|
||
* 生成培训课程的链接。
|
||
* Create link of traincourse.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $extra
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function createTrainCourseLink(string $type, object $module, string $extra = ''): string
|
||
{
|
||
return html::a(helper::createLink('traincourse', 'browse', "type=byModule¶m={$module->id}"), $module->name, '', "id='module{$module->id}' title='{$module->name}'");
|
||
}
|
||
|
||
/**
|
||
* 生成培训评论的链接。
|
||
* Create link of trainpost.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $extra
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function createTrainPostLink(string $type, object $module, string $extra = ''): string
|
||
{
|
||
return html::a(helper::createLink('trainpost', 'browse', "type=byModule¶m={$module->id}"), $module->name, '', "id='module{$module->id}' title='{$module->name}'");
|
||
}
|
||
|
||
/**
|
||
* 生成报告的链接。
|
||
* Create report link.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param array $extra
|
||
* @access public
|
||
* @return string
|
||
*/
|
||
public function createReportLink(string $type, object $module, string $extra): string
|
||
{
|
||
$dimension = zget($extra, 'dimension', 0);
|
||
return html::a(helper::createLink('report', 'browsereport', "dimension={$dimension}&module={$module->id}"), $module->name, '', "id='module{$module->id}' title='{$module->name}'");
|
||
}
|
||
|
||
/**
|
||
* 生成主机的链接。
|
||
* Create link of a host.
|
||
*
|
||
* @param string $type
|
||
* @param object $module
|
||
* @param string $parent
|
||
* @param array $extra
|
||
* @access public
|
||
* @return object
|
||
*/
|
||
public function createHostLink(string $type, object $module, string $parent = '0', array $extra = array()): object
|
||
{
|
||
$data = new stdclass();
|
||
$data->id = $parent ? uniqid() : (string)$module->id;
|
||
$data->parent = $parent ? $parent : (string)$module->parent;
|
||
$data->name = $module->name;
|
||
|
||
$data->url = helper::createLink('host', 'browse', "browseType=bymodule¶m={$module->id}");
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 获取一个模块的子模块。
|
||
* Get sons of a module.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $moduleID
|
||
* @param string $type
|
||
* @param string $branch
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getSons(int $rootID, int $moduleID, string $type = 'root', string $branch = '0'): array
|
||
{
|
||
if(common::isTutorialMode()) return array();
|
||
|
||
$syncConfig = $this->getSyncConfig($type);
|
||
|
||
if($type == 'line') $rootID = 0;
|
||
|
||
/* if createVersion <= 4.1 or type == 'story', only get modules of its type. */
|
||
if(!$this->isMergeModule($rootID, $type) || $type == 'story')
|
||
{
|
||
return $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$rootID)
|
||
->andWhere('parent')->eq((int)$moduleID)
|
||
->andWhere('type')->in($type)
|
||
->beginIF($branch !== 'all')
|
||
->andWhere("(branch")->eq(0)
|
||
->orWhere("branch")->eq((int)$branch)
|
||
->markRight(1)
|
||
->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('`order`')
|
||
->fetchAll();
|
||
}
|
||
|
||
/* else get modules of its type && story type. */
|
||
if(strpos('task|case|bug', $type) !== false) $type = "$type,story";
|
||
|
||
return $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$rootID)
|
||
->andWhere('parent')->eq((int)$moduleID)
|
||
->andWhere('type', true)->in($type)
|
||
->beginIF(($type == 'feedback' || $type == 'ticket') && isset($syncConfig[$rootID]))
|
||
->orWhere('(type')->eq('story')->andWhere('grade')->le(zget($syncConfig, $rootID, 0))->markRight(1)
|
||
->fi()
|
||
->markRight(1)
|
||
->beginIF($branch !== 'all')
|
||
->andWhere("(branch")->eq(0)
|
||
->orWhere("branch")->eq((int)$branch)
|
||
->markRight(1)
|
||
->fi()
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('`order`, type desc')
|
||
->fetchAll();
|
||
}
|
||
|
||
/**
|
||
* 获取一个任务模块的子模块。
|
||
* Get sons of a task module.
|
||
*
|
||
* @param int $rootID
|
||
* @param int $productID
|
||
* @param int $moduleID
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getTaskSons(int $rootID, int $productID, int $moduleID): array
|
||
{
|
||
if($moduleID)
|
||
{
|
||
return $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('root')->eq((int)$rootID)
|
||
->andWhere('parent')->eq((int)$moduleID)
|
||
->andWhere('type')->in("task,story")
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('`order`, type')
|
||
->fetchAll();
|
||
}
|
||
else
|
||
{
|
||
return $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('parent')->eq(0)
|
||
->andWhere('deleted')->eq(0)
|
||
->andWhere("(root = '" . (int)$rootID . "' AND type = 'task'")
|
||
->orWhere("root = '" . (int)$productID . "' AND type = 'story')")
|
||
->orderBy('`order`, type')
|
||
->fetchAll();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取所有子模块的ID列表。
|
||
* Get id list of a module's children.
|
||
*
|
||
* @param int $moduleID
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getAllChildId(int $moduleID): array
|
||
{
|
||
if($moduleID == 0) return array();
|
||
|
||
$module = $this->getById((int)$moduleID);
|
||
if(empty($module)) return array();
|
||
|
||
return $this->dao->select('id')->from(TABLE_MODULE)->where("CONCAT(',', path, ',')")->like("%$module->path%")->andWhere('deleted')->eq(0)->fetchPairs();
|
||
}
|
||
|
||
/**
|
||
* 获取一个模块的所有父模块。
|
||
* Get parents of a module.
|
||
*
|
||
* @param int $moduleID
|
||
* @param bool $queryAll
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getParents(int $moduleID, bool $queryAll = false): array
|
||
{
|
||
if($moduleID == 0 || common::isTutorialMode()) return array();
|
||
$path = $this->dao->select('path')->from(TABLE_MODULE)->where('id')->eq((int)$moduleID)->fetch('path');
|
||
$path = trim($path, ',');
|
||
if(!$path) return array();
|
||
return $this->dao->select('*')->from(TABLE_MODULE)
|
||
->where('id')->in($path)
|
||
->beginIF(!$queryAll)->andWhere('deleted')->eq(0)->fi()
|
||
->orderBy('grade')
|
||
->fetchAll();
|
||
}
|
||
|
||
/**
|
||
* 获取一个模块所属的产品。
|
||
* Get product by moduleID.
|
||
*
|
||
* @param int $moduleID
|
||
* @access public
|
||
* @return object|false
|
||
*/
|
||
public function getProduct(int $moduleID): object|false
|
||
{
|
||
if($moduleID == 0) return false;
|
||
$path = $this->dao->select('path')->from(TABLE_MODULE)->where('id')->eq($moduleID)->fetch('path');
|
||
$paths = explode(',', trim($path, ','));
|
||
if(!$path) return false;
|
||
|
||
$moduleID = $paths[0];
|
||
$module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($moduleID)->fetch();
|
||
if($module->type != 'story' || !$module->root) return false;
|
||
return $this->dao->select('id,name')->from(TABLE_PRODUCT)->where('id')->eq($module->root)->fetch();
|
||
}
|
||
|
||
/**
|
||
* 获取一个需求模块。
|
||
* Get the module that its type == 'story'.
|
||
*
|
||
* @param int $moduleID
|
||
* @access public
|
||
* @return int
|
||
*/
|
||
public function getStoryModule(int $moduleID): int
|
||
{
|
||
$module = $this->dao->select('id,type,parent')->from(TABLE_MODULE)->where('id')->eq((int)$moduleID)->fetch();
|
||
if(empty($module)) return 0;
|
||
|
||
while(!empty($module) && $module->id && $module->type != 'story')
|
||
{
|
||
$module = $this->dao->select('id,type,parent')->from(TABLE_MODULE)->where('id')->eq($module->parent)->fetch();
|
||
}
|
||
|
||
return empty($module) ? 0 : $module->id;
|
||
}
|
||
|
||
/**
|
||
* 获取一些模块的名称。
|
||
* Get modules name.
|
||
*
|
||
* @param array $moduleIdList
|
||
* @param bool $allPath
|
||
* @param bool $branchPath
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getModulesName(array $moduleIdList, bool $allPath = true, bool $branchPath = false): array
|
||
{
|
||
if(!$allPath) return $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in($moduleIdList)->andWhere('deleted')->eq(0)->fetchPairs('id', 'name');
|
||
|
||
/* Get modules && submodules through id list. */
|
||
$modules = $this->dao->select('id, name, path, branch')->from(TABLE_MODULE)->where('id')->in($moduleIdList)->andWhere('deleted')->eq(0)->fetchAll('path');
|
||
$allModules = $this->dao->select('id, name')->from(TABLE_MODULE)->where('id')->in(join(array_keys($modules)))->andWhere('deleted')->eq(0)->fetchPairs('id', 'name');
|
||
|
||
/* Constructs a key-value pair for the module name:id. */
|
||
$branchIDList = array();
|
||
$modulePairs = array();
|
||
foreach($modules as $module)
|
||
{
|
||
$paths = explode(',', trim($module->path, ','));
|
||
$moduleName = '';
|
||
foreach($paths as $path) $moduleName .= '/' . zget($allModules, $path);
|
||
$modulePairs[$module->id] = $moduleName;
|
||
|
||
if($module->branch) $branchIDList[$module->branch] = $module->branch;
|
||
}
|
||
|
||
if(!$branchPath) return $modulePairs;
|
||
|
||
/* Prefixes the module name with the branch name. */
|
||
$branchs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($branchIDList)->andWhere('deleted')->eq(0)->fetchALL('id');
|
||
foreach($modules as $module)
|
||
{
|
||
if(!isset($modulePairs[$module->id])) continue;
|
||
|
||
$branchName = isset($branchs[$module->branch]) ? '/' . $branchs[$module->branch]->name : '';
|
||
$modulePairs[$module->id] = $branchName . $modulePairs[$module->id];
|
||
}
|
||
|
||
return $modulePairs;
|
||
}
|
||
|
||
/**
|
||
* 更新模块排序。
|
||
* Update modules' order.
|
||
*
|
||
* @param array $orders
|
||
* @access public
|
||
* @return void
|
||
*/
|
||
public function updateOrder(array $orders): void
|
||
{
|
||
asort($orders);
|
||
$orderInfo = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->in(array_keys($orders))->andWhere('deleted')->eq(0)->fetchAll('id');
|
||
$newOrders = array();
|
||
foreach(array_keys($orders) as $moduleID)
|
||
{
|
||
$parent = $orderInfo[$moduleID]->parent;
|
||
$grade = $orderInfo[$moduleID]->grade;
|
||
$branch = $orderInfo[$moduleID]->branch;
|
||
|
||
if(!isset($newOrders[$parent][$grade][$branch]))
|
||
{
|
||
$newOrders[$parent][$grade][$branch] = 1;
|
||
}
|
||
else
|
||
{
|
||
$newOrders[$parent][$grade][$branch] ++;
|
||
}
|
||
|
||
$newOrder = $newOrders[$parent][$grade][$branch] * 10;
|
||
$this->dao->update(TABLE_MODULE)->set('`order`')->eq($newOrder)->where('id')->eq((int)$moduleID)->exec();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 修改模块的所有子模块。
|
||
* Manage childs of a module.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @access public
|
||
* @return array|false
|
||
*/
|
||
public function manageChild(int $rootID, string $type): array|false
|
||
{
|
||
if($type == 'line') $rootID = 0;
|
||
|
||
$data = fixer::input('post')->get();
|
||
$childs = $data->modules;
|
||
$parentModuleID = (int)$data->parentModuleID;
|
||
|
||
foreach($childs as $moduleID => $moduleName)
|
||
{
|
||
if(preg_match('/(^\s+$)/', $moduleName))
|
||
{
|
||
dao::$errors['root'] = $this->lang->tree->shouldNotBlank;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$module = new stdClass();
|
||
$module->root = $rootID;
|
||
$module->type = $type;
|
||
$module->parent = $parentModuleID;
|
||
$repeatName = $this->checkUnique($module, $childs);
|
||
if($repeatName)
|
||
{
|
||
dao::$errors['name'] = sprintf($this->lang->tree->repeatName, $repeatName);
|
||
return false;
|
||
}
|
||
|
||
$parentModule = $this->getByID($parentModuleID);
|
||
|
||
$branches = isset($data->branch) ? $data->branch : array();
|
||
$orders = isset($data->order) ? $data->order : array();
|
||
$shorts = $data->shorts;
|
||
if($parentModule)
|
||
{
|
||
$grade = $parentModule->grade + 1;
|
||
$parentPath = $parentModule->path;
|
||
}
|
||
else
|
||
{
|
||
$grade = 1;
|
||
$parentPath = ',';
|
||
}
|
||
|
||
$this->setModuleLang();
|
||
|
||
$i = 1;
|
||
$oldModules = $this->getOptionMenu($rootID, 'story', 0, 'all');
|
||
$createIdList = array();
|
||
$editIdList = array();
|
||
foreach($childs as $moduleID => $moduleName)
|
||
{
|
||
if(empty($moduleName)) continue;
|
||
|
||
/* The new modules. */
|
||
if(is_numeric($moduleID))
|
||
{
|
||
if(isset($orders[$moduleID]) && !empty($orders[$moduleID]))
|
||
{
|
||
$order = $orders[$moduleID];
|
||
}
|
||
else
|
||
{
|
||
$order = (int)$this->post->maxOrder + $i * 10;
|
||
$i ++;
|
||
}
|
||
|
||
$module = new stdClass();
|
||
$module->root = $rootID;
|
||
$module->name = strip_tags(trim($moduleName));
|
||
$module->parent = $parentModuleID;
|
||
$module->branch = isset($branches[$moduleID]) ? $branches[$moduleID] : 0;
|
||
$module->short = $shorts[$moduleID];
|
||
$module->grade = $grade;
|
||
$module->type = $type;
|
||
$module->order = $order;
|
||
$this->dao->insert(TABLE_MODULE)->data($module)->autoCheck()->exec();
|
||
if(dao::isError()) return false;
|
||
|
||
$moduleID = $this->dao->lastInsertID();
|
||
$createIdList[] = $moduleID;
|
||
$childPath = $parentPath . "$moduleID,";
|
||
$this->dao->update(TABLE_MODULE)->set('path')->eq($childPath)->where('id')->eq($moduleID)->exec();
|
||
if(dao::isError()) return false;
|
||
}
|
||
else
|
||
{
|
||
$originID = $moduleID;
|
||
$short = $shorts[$moduleID];
|
||
$order = $orders[$moduleID];
|
||
$moduleID = (int)str_replace('id', '', $moduleID);
|
||
|
||
$oldModule = $this->getByID($moduleID);
|
||
|
||
$data = new stdClass();
|
||
$data->name = strip_tags(trim($moduleName));
|
||
$data->short = $short;
|
||
$data->order = $order;
|
||
$data->branch = isset($branches[$originID]) ? $branches[$originID] : 0;
|
||
|
||
$this->dao->update(TABLE_MODULE)->data($data)->autoCheck()->where('id')->eq($moduleID)->exec();
|
||
if(dao::isError()) return false;
|
||
|
||
$newModule = $this->getByID($moduleID);
|
||
if(common::createChanges($oldModule, $newModule))
|
||
{
|
||
$editIdList[] = $moduleID;
|
||
$moduleChanges[$moduleID] = common::createChanges($oldModule, $newModule);
|
||
}
|
||
}
|
||
}
|
||
|
||
if($type == 'story' || strpos($this->config->tree->groupTypes, ",$type,") !== false)
|
||
{
|
||
$objectType = $type == 'story' ? 'module' : 'chartgroup';
|
||
$this->loadModel('action');
|
||
if(!empty($createIdList)) $actionID = $this->action->create($objectType, $rootID, 'created', '', implode(',', $createIdList));
|
||
|
||
if(!empty($editIdList))
|
||
{
|
||
$changes = array();
|
||
$newModules = $this->getOptionMenu($rootID, 'story', 0, 'all');
|
||
foreach($moduleChanges as $moduleID => $moduleChange)
|
||
{
|
||
foreach($moduleChange as $change)
|
||
{
|
||
if($change['field'] == 'name')
|
||
{
|
||
$change['old'] = zget($oldModules, $moduleID);
|
||
$change['new'] = zget($newModules, $moduleID);
|
||
$change['diff'] = '';
|
||
}
|
||
$changes[] = $change;
|
||
}
|
||
}
|
||
$actionID = $this->action->create($objectType, $rootID, 'edited', '', implode(',', $editIdList));
|
||
if(!empty($changes)) $this->action->logHistory($actionID, $changes);
|
||
}
|
||
}
|
||
return $createIdList;
|
||
}
|
||
|
||
/**
|
||
* 更新模块信息。
|
||
* Update a module.
|
||
*
|
||
* @param int $moduleID
|
||
* @param string $type
|
||
* @access public
|
||
* @return bool
|
||
*/
|
||
public function update(int $moduleID, string $type = ''): bool
|
||
{
|
||
$module = fixer::input('post')->cleanInt('branch')->setDefault('parent', 0)->get();
|
||
$self = $this->getById($moduleID);
|
||
$changes = common::createChanges($self, $module);
|
||
if(!isset($_POST['branch'])) $module->branch = $self->branch;
|
||
|
||
if($self)
|
||
{
|
||
if($type == 'ticket' || $type == 'feedback') $module->root = $self->root;
|
||
if($type == 'host' || !isset($module->root)) $module->root = 0;
|
||
if(strpos($this->config->tree->groupTypes, ",$type,") !== false) $module->root = $self->root;
|
||
if($self->root && !$module->root) $module->root = $self->root;
|
||
if($self->parent != $module->parent || $self->root != $module->root)
|
||
{
|
||
$maxOrder = $this->dao->select('MAX(`order`) AS `order`')->from(TABLE_MODULE)->where('parent')->eq($module->parent)->andWhere('root')->eq($module->root)->fetch('order');
|
||
$module->order = $maxOrder ? ++ $maxOrder : $self->order;
|
||
}
|
||
|
||
if($module->parent) $self->parent = $module->parent;
|
||
}
|
||
|
||
$repeatName = $this->checkUnique($self, array("id{$self->id}" => $module->name), array("id{$self->id}" => $module->branch));
|
||
if($repeatName)
|
||
{
|
||
$tips = in_array($self->type, array('doc', 'api')) ? $this->lang->tree->repeatDirName : $this->lang->tree->repeatName;
|
||
dao::$errors['name'] = sprintf($tips, $repeatName);
|
||
return false;
|
||
}
|
||
|
||
if((empty($module->root) || empty($module->name)) && in_array($self->type, array('doc', 'api')))
|
||
{
|
||
$this->app->loadLang('doc');
|
||
if(empty($module->root))
|
||
{
|
||
dao::$errors['root'] = sprintf($this->lang->error->notempty, $this->lang->doc->lib);
|
||
return false;
|
||
}
|
||
if(empty($module->name))
|
||
{
|
||
dao::$errors['name'] = sprintf($this->lang->error->notempty, $this->lang->doc->catalogName);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$module->name = strip_tags(trim($module->name));
|
||
if(empty($module->name))
|
||
{
|
||
dao::$errors['name'] = sprintf($this->lang->error->notempty, $this->lang->tree->name);
|
||
return false;
|
||
}
|
||
|
||
$this->setModuleLang();
|
||
|
||
$modules = $self->type == 'story' ? $this->getOptionMenu($self->root, 'story', 0, 'all') : array();
|
||
$parent = $this->getById((int)$this->post->parent);
|
||
$childs = $this->getAllChildId($moduleID);
|
||
$module->grade = $parent ? $parent->grade + 1 : 1;
|
||
$module->path = $parent ? $parent->path . $moduleID . ',' : ',' . $moduleID . ',';
|
||
$this->dao->update(TABLE_MODULE)->data($module)->autoCheck()->check('name', 'notempty')->where('id')->eq($moduleID)->exec();
|
||
if(dao::isError()) return false;
|
||
|
||
$this->dao->update(TABLE_MODULE)->set('grade = grade + 1')->where('id')->in($childs)->andWhere('id')->ne($moduleID)->exec();
|
||
$this->dao->update(TABLE_MODULE)->set('owner')->eq($this->post->owner)->where('id')->in($childs)->andWhere('owner')->eq('')->exec();
|
||
$this->dao->update(TABLE_MODULE)->set('owner')->eq($this->post->owner)->where('id')->in($childs)->andWhere('owner')->eq($self->owner)->exec();
|
||
|
||
if($self->type == 'story' || strpos($this->config->tree->groupTypes, ",$self->type,") !== false)
|
||
{
|
||
$objectType = $self->type == 'story' ? 'module' : 'chartgroup';
|
||
$rootID = isset($module->root) ? (int)$module->root : $self->root;
|
||
$newModules = $this->getOptionMenu($rootID, 'story', 0, 'all');
|
||
|
||
foreach($changes as $id => $change)
|
||
{
|
||
if($change['field'] == 'name')
|
||
{
|
||
$changes[$id]['old'] = zget($modules, $moduleID);
|
||
$changes[$id]['new'] = zget($newModules, $moduleID);
|
||
$changes[$id]['diff'] = '';
|
||
break;
|
||
}
|
||
}
|
||
$actionID = $this->loadModel('action')->create($objectType, $self->root, 'edited', '', $moduleID);
|
||
if(!empty($changes)) $this->action->logHistory($actionID, $changes);
|
||
if(isset($module->root) && $module->root != $self->root)
|
||
{
|
||
$actionID = $this->action->create($objectType, $rootID, 'edited', '', $moduleID);
|
||
if(!empty($changes)) $this->action->logHistory($actionID, $changes);
|
||
}
|
||
}
|
||
|
||
if(isset($module->root) && $module->root != $self->root)
|
||
{
|
||
$this->dao->update(TABLE_MODULE)->set('root')->eq($module->root)->where('id')->in($childs)->exec();
|
||
$this->dao->update(TABLE_MODULE)->set('branch')->eq($module->branch)->where('id')->in($childs)->exec();
|
||
if($self->type == 'doc')
|
||
{
|
||
$this->dao->update(TABLE_DOC)->set('`lib`')->eq($module->root)
|
||
->where('module')->eq($moduleID)
|
||
->orWhere('module')->in($childs)
|
||
->exec();
|
||
}
|
||
}
|
||
$this->fixModulePath(isset($module->root) ? (int)$module->root : $self->root, $self->type);
|
||
if(isset($module->root) && $module->root != $self->root) $this->changeRoot($moduleID, $self->root, (int)$module->root, $self->type);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 修改模块下关联事项的root,注意: 修改module本身的root不在该逻辑中。
|
||
* Change root of items in the module.
|
||
*
|
||
* @param int $moduleID
|
||
* @param int $oldRoot
|
||
* @param int $newRoot
|
||
* @param string $type
|
||
* @access public
|
||
* @return void
|
||
*/
|
||
public function changeRoot(int $moduleID, int $oldRoot, int $newRoot, string $type): void
|
||
{
|
||
/* Get all children id list. */
|
||
$childIdList = $this->dao->select('id')->from(TABLE_MODULE)->where('path')->like("%,$moduleID,%")->andWhere('deleted')->eq(0)->fetchPairs('id');
|
||
|
||
/* Update product field for stories, bugs, cases under this module. */
|
||
$this->dao->update(TABLE_STORY)->set('product')->eq($newRoot)->where('module')->in($childIdList)->exec();
|
||
$this->dao->update(TABLE_BUG)->set('product')->eq($newRoot)->where('module')->in($childIdList)->exec();
|
||
$this->dao->update(TABLE_CASE)->set('product')->eq($newRoot)->where('module')->in($childIdList)->exec();
|
||
$this->dao->update(TABLE_DOC)->set('lib')->eq($newRoot)->where('module')->in($childIdList)->exec();
|
||
|
||
if($type != 'story') return;
|
||
|
||
/* If the type if story, check it's releated projects. */
|
||
$projectStories = $this->dao->select('DISTINCT t1.id,t1.version,t2.project')
|
||
->from(TABLE_STORY)->alias('t1')
|
||
->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story')
|
||
->where('t1.module')->in($childIdList)
|
||
->andWhere('t2.product')->eq($oldRoot)
|
||
->fetchAll('id');
|
||
$executions = array();
|
||
foreach($projectStories as $story)
|
||
{
|
||
$this->dao->update(TABLE_PROJECTSTORY)
|
||
->set('product')->eq($newRoot)
|
||
->where('project')->eq($story->project)
|
||
->andWhere('story')->eq($story->id)
|
||
->andWhere('version')->eq($story->version)
|
||
->exec();
|
||
$executions[$story->project] = $story->project;
|
||
}
|
||
|
||
if($executions)
|
||
{
|
||
$projectProduct = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->in($executions)->fetchGroup('project', 'product');
|
||
$linkedProduct = $this->dao->select('DISTINCT project,product')->from(TABLE_PROJECTSTORY)->where('project')->in($executions)->fetchGroup('project', 'product');
|
||
foreach($executions as $executionID)
|
||
{
|
||
if(!isset($projectProduct[$executionID]) || !in_array($newRoot, array_keys($projectProduct[$executionID]))) $this->dao->insert(TABLE_PROJECTPRODUCT)->set('project')->eq($executionID)->set('product')->eq($newRoot)->exec();
|
||
if(isset($linkedProduct[$executionID]) && !in_array($oldRoot, array_keys($linkedProduct[$executionID])))
|
||
{
|
||
$this->dao->delete()->from(TABLE_PROJECTPRODUCT)->where('project')->eq($project)->andWhere('product')->eq($oldRoot)->exec();
|
||
$this->dao->update(TABLE_BUILD)->set('product')->eq($newRoot)->where('product')->eq($oldRoot)->andWhere('execution')->eq($executionID)->exec();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除一个模块。
|
||
* Delete a module.
|
||
*
|
||
* @param int $moduleID
|
||
* @access public
|
||
* @return bool
|
||
*/
|
||
public function remove(int $moduleID): bool
|
||
{
|
||
$module = $this->getById($moduleID);
|
||
if(empty($module)) return false;
|
||
|
||
if($module->type == 'doc')
|
||
{
|
||
$childs = array();
|
||
}
|
||
else
|
||
{
|
||
$childs = $this->getAllChildId($moduleID);
|
||
}
|
||
$childs[$moduleID] = $moduleID;
|
||
|
||
$objectType = (!empty($module->type) && strpos($this->config->tree->groupTypes, ",$module->type,") !== false) ? 'chartgroup' : 'module';
|
||
/* Mark deletion when delete a module. */
|
||
$this->dao->update(TABLE_MODULE)->set('deleted')->eq(1)->where('id')->in($childs)->exec();
|
||
|
||
if($module->type != 'deliverable')
|
||
{
|
||
foreach($childs as $childID) $this->loadModel('action')->create($objectType, $childID, 'deleted', '', actionModel::CAN_UNDELETED);
|
||
}
|
||
|
||
$this->fixModulePath($module->root, $module->type);
|
||
$cookieName = '';
|
||
switch ($module->type)
|
||
{
|
||
case 'line':
|
||
$this->dao->update(TABLE_PRODUCT)->set('line')->eq('0')->where('line')->eq($moduleID)->exec();
|
||
break;
|
||
case 'task':
|
||
$this->dao->update(TABLE_TASK)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$cookieName = 'moduleBrowseParam';
|
||
break;
|
||
case 'bug':
|
||
$this->dao->update(TABLE_BUG)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$cookieName = 'bugModule';
|
||
break;
|
||
case 'case':
|
||
$this->dao->update(TABLE_CASE)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$cookieName = 'caseModule';
|
||
break;
|
||
case 'story':
|
||
$this->dao->update(TABLE_STORY)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$this->dao->update(TABLE_TASK)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$this->dao->update(TABLE_BUG)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$this->dao->update(TABLE_CASE)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$this->dao->update(TABLE_FEEDBACK)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$this->dao->update(TABLE_TICKET)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$cookieName = 'storyModule';
|
||
break;
|
||
case 'chart':
|
||
$this->dao->update(TABLE_CHART)->set('`group`')->eq($module->parent)->where('`group`')->in($childs)->exec();
|
||
break;
|
||
case 'report':
|
||
$this->dao->update(TABLE_REPORT)->set('`module`')->eq($module->parent)->where('`module`')->in($childs)->exec();
|
||
break;
|
||
case 'dataview':
|
||
$this->dao->update(TABLE_DATAVIEW)->set('`group`')->eq($module->parent)->where('`group`')->in($childs)->exec();
|
||
break;
|
||
case 'feedback':
|
||
$this->dao->update(TABLE_FEEDBACK)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$cookieName = 'feedbackModule';
|
||
break;
|
||
case 'ticket':
|
||
$this->dao->update(TABLE_TICKET)->set('module')->eq($module->parent)->where('module')->in($childs)->exec();
|
||
$cookieName = 'ticketModule';
|
||
break;
|
||
}
|
||
$sessionValue = $this->session->{$module->type . 'List'};
|
||
if($sessionValue && strpos($sessionValue, 'param=' . $moduleID)) $this->session->set($module->type . 'List', str_replace('param=' . $moduleID, 'param=0', $sessionValue));
|
||
if($cookieName) helper::setcookie($cookieName, 0, time() - 3600, $this->config->webRoot, '', $this->config->cookieSecure, true);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 修复模块的Path和Grade。
|
||
* Fix the path, grade fields according to the id && parent fields.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $type
|
||
* @access public
|
||
* @return void
|
||
*/
|
||
public function fixModulePath(int $rootID, string $type): void
|
||
{
|
||
/* Get all modules grouped by parent. */
|
||
if($type == 'bug' || $type == 'case') $type = 'story,' . $type;
|
||
$groupModules = $this->dao->select('id, parent, branch')->from(TABLE_MODULE)->where('root')->eq($rootID)->andWhere('type')->in($type)->andWhere('deleted')->eq(0)->fetchGroup('parent', 'id');
|
||
$modules = array();
|
||
|
||
/* Cycle the groupModules until it has no item any more. */
|
||
while(count($groupModules) > 0)
|
||
{
|
||
$oldCounts = count($groupModules); // Record the counts before processing.
|
||
foreach($groupModules as $parentModuleID => $childModules)
|
||
{
|
||
/* If the parentModule doesn't exsit in the modules, skip it. If exists, compute it's child modules. */
|
||
if(!isset($modules[$parentModuleID]) && $parentModuleID != 0) continue;
|
||
if($parentModuleID == 0)
|
||
{
|
||
$parentModule = new stdclass();
|
||
$parentModule->grade = 0;
|
||
$parentModule->path = ',';
|
||
}
|
||
else
|
||
{
|
||
$parentModule = $modules[$parentModuleID];
|
||
}
|
||
|
||
/* Compute it's child modules. */
|
||
foreach($childModules as $childModuleID => $childModule)
|
||
{
|
||
$childModule->grade = $parentModule->grade + 1;
|
||
$childModule->path = $parentModule->path . $childModule->id . ',';
|
||
if(isset($parentModule->branch))$childModule->branch = $parentModule->branch;
|
||
$modules[$childModuleID] = $childModule; // Save child module to modules, thus the child of child can compute it's grade && path.
|
||
}
|
||
unset($groupModules[$parentModuleID]); // Remove it from the groupModules.
|
||
}
|
||
if(count($groupModules) == $oldCounts) break; // If after processing, no module processed, break the cycle.
|
||
}
|
||
|
||
/* Save modules to database. */
|
||
foreach($modules as $module) $this->dao->update(TABLE_MODULE)->data($module)->where('id')->eq($module->id)->exec();
|
||
}
|
||
|
||
/**
|
||
* 检查模块是否重名。
|
||
* Check unique module name.
|
||
*
|
||
* @param object $module 检查范围,包括root,type,parent
|
||
* @param array $modules
|
||
* @param array $branches 检查范围
|
||
* @access public
|
||
* @return string|false
|
||
*/
|
||
public function checkUnique(object $module, $modules = array(), $branches = array()): string|false
|
||
{
|
||
if(empty($branches)) $branches = $this->post->branch;
|
||
if(empty($branches) && isset($module->branch)) $branches = array($module->branch);
|
||
if(empty($branches)) $branches = array(0);
|
||
|
||
/* If no modules to check, check the name of module. */
|
||
if(empty($modules) && isset($module->name)) $modules = array($module->name);
|
||
|
||
$branches = array_unique($branches);
|
||
$rootID = $module->root;
|
||
$viewType = $module->type;
|
||
$parentModuleID = $module->parent;
|
||
|
||
if($this->isMergeModule($rootID, $viewType) && $viewType != 'task') $viewType .= ',story';
|
||
|
||
$existsModules = $this->dao->select('id,branch,name')->from(TABLE_MODULE)->where('root')->eq($rootID)->andWhere('type')->in($viewType)->andWhere('parent')->eq($parentModuleID)->andWhere('branch')->in($branches)->andWhere('deleted')->eq(0)->fetchAll();
|
||
$checkedModules = ',';
|
||
$repeatName = '';
|
||
foreach($modules as $id => $name)
|
||
{
|
||
$existed = false;
|
||
if(!is_numeric($id) && strpos($id, 'id') === 0)
|
||
{
|
||
$existed = true;
|
||
$moduleID = substr($id, 2);
|
||
}
|
||
|
||
if(strpos($checkedModules, ",$name,") !== false) return $name;
|
||
|
||
foreach($existsModules as $existsModule)
|
||
{
|
||
if($name == $existsModule->name && (!$existed || $moduleID != $existsModule->id) && (!isset($branches[$id]) || $branches[$id] == $existsModule->branch)) return $name;
|
||
}
|
||
$checkedModules .= "$name,";
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 检查是否是已合并模块。4.1后task,case,bug也会使用story的模块。
|
||
* Check merge module version.
|
||
*
|
||
* @param int|array $rootID
|
||
* @param string $viewType
|
||
* @access public
|
||
* @return bool
|
||
*/
|
||
public function isMergeModule(int|array $rootID, string $viewType): bool
|
||
{
|
||
if(!in_array($viewType, array('bug', 'case', 'task'))) return false;
|
||
|
||
/* Get createdVersion. */
|
||
$table = $viewType == 'task' ? TABLE_PROJECT : TABLE_PRODUCT;
|
||
$versionField = $viewType == 'task' ? 'openedVersion' : 'createdVersion';
|
||
$createdVersion = $this->dao->select($versionField)->from($table)->where('id')->in($rootID)->fetch($versionField);
|
||
if(!$createdVersion) return true;
|
||
|
||
if(is_numeric($createdVersion[0]) && version_compare($createdVersion, '4.1', '<=')) return false;
|
||
return true;
|
||
|
||
}
|
||
|
||
/**
|
||
* 获取产品的模块树。
|
||
* Get full trees.
|
||
*
|
||
* @param int $rootID
|
||
* @param string $viewType
|
||
* @param string|int $branchID
|
||
* @param int $currentModuleID
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getProductStructure(int $rootID, string $viewType, string $branchID = 'all', int $currentModuleID = 0): array
|
||
{
|
||
if(common::isTutorialMode()) return array();
|
||
|
||
if($viewType == 'line') $rootID = 0;
|
||
$stmt = $this->app->dbQuery($this->buildMenuQuery($rootID, $viewType, $currentModuleID, $branchID));
|
||
$trees = $this->getDataStructure($stmt, $viewType, $rootID, array(), $branchID);
|
||
|
||
return $trees;
|
||
}
|
||
|
||
/**
|
||
* 获取模块树。
|
||
* Get full modules tree.
|
||
*
|
||
* @param object $stmt
|
||
* @param string $viewType
|
||
* @param int $rootID
|
||
* @param array $keepModules
|
||
* @param string $branch
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getDataStructure(object $stmt, string $viewType, int $rootID, array $keepModules = array(), string $branch = 'all'): array
|
||
{
|
||
$parent = array();
|
||
|
||
/* If feedback || ticket module is merge add story module.*/
|
||
$syncConfig = $this->getSyncConfig($viewType);
|
||
|
||
while($module = $stmt->fetch())
|
||
{
|
||
/* If is feedback || ticket filter story module by grade.*/
|
||
if(($viewType == 'feedback' || $viewType == 'ticket') && $module->type == 'story')
|
||
{
|
||
if(isset($syncConfig[$module->root]) && $module->grade > $syncConfig[$module->root]) continue;
|
||
}
|
||
|
||
/* Ignore useless module for task. */
|
||
$allModule = (isset($this->config->execution->task->allModule) && ($this->config->execution->task->allModule == 1));
|
||
if($keepModules && !isset($keepModules[$module->id]) && !$allModule) continue;
|
||
if($viewType == 'task' && empty($keepModules) && !$allModule) continue;
|
||
if(isset($parent[$module->id]))
|
||
{
|
||
$module->children = $parent[$module->id]->children;
|
||
unset($parent[$module->id]);
|
||
}
|
||
if(!isset($parent[$module->parent])) $parent[$module->parent] = new stdclass();
|
||
|
||
if($viewType == 'task')
|
||
{
|
||
$module->url = helper::createLink('tree', 'browsetask', "rootID=$rootID&productID=0&moduleID=$module->id");
|
||
}
|
||
else if(in_array($viewType, array('pivot', 'chart')))
|
||
{
|
||
$module->url = helper::createLink('tree', 'browsegroup', "dimensionID=$rootID&groupID=$module->id&type=$viewType");
|
||
}
|
||
else
|
||
{
|
||
$module->url = helper::createLink('tree', 'browse', "rootID=$rootID&view=$viewType¤tModuleID=$module->id&branchID=$branch");
|
||
}
|
||
$parent[$module->parent]->children[] = $module;
|
||
}
|
||
|
||
if($viewType == 'task') $parentTypePairs = $this->dao->select('id,type')->from(TABLE_MODULE)->where('id')->in(array_keys($parent))->andWhere('deleted')->eq(0)->fetchPairs('id', 'type');
|
||
|
||
$tree = array();
|
||
foreach($parent as $module)
|
||
{
|
||
foreach($module->children as $item)
|
||
{
|
||
if($viewType == 'task' && isset($parentTypePairs[$item->parent]) && $parentTypePairs[$item->parent] != 'task') continue;
|
||
if($item->parent != 0) continue; // Filter project children modules.
|
||
$tree[] = $item;
|
||
}
|
||
}
|
||
return $tree;
|
||
}
|
||
|
||
/**
|
||
* 获取文档模块树。
|
||
* Get all doc structure.
|
||
*
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getDocStructure(): array
|
||
{
|
||
$stmt = $this->app->dbQuery($this->dao->select('*')->from(TABLE_MODULE)->where('type')->eq('doc')->andWhere('deleted')->eq(0)->orderBy('`grade`_desc, `order`')->get());
|
||
$parent = array();
|
||
while($module = $stmt->fetch())
|
||
{
|
||
if(!isset($parent[$module->root])) $parent[$module->root] = array();
|
||
|
||
if(isset($parent[$module->root][$module->id]))
|
||
{
|
||
$module->children = $parent[$module->root][$module->id]->children;
|
||
unset($parent[$module->root][$module->id]);
|
||
}
|
||
if(!isset($parent[$module->root][$module->parent])) $parent[$module->root][$module->parent] = new stdclass();
|
||
$parent[$module->root][$module->parent]->children[] = $module;
|
||
}
|
||
|
||
$tree = array();
|
||
foreach($parent as $root => $modules)
|
||
{
|
||
foreach($modules as $module)
|
||
{
|
||
foreach($module->children as $children)
|
||
{
|
||
if($children->parent != 0 && !empty($tree[$root]))
|
||
{
|
||
foreach($tree[$root] as $firstChildren)
|
||
{
|
||
if($firstChildren->id == $children->parent) $firstChildren->children[] = $children;
|
||
}
|
||
}
|
||
$tree[$root][] = $children;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $tree;
|
||
}
|
||
|
||
/**
|
||
* 获取反馈和工单是否已跟产品同步的信息。
|
||
* Get syncProduct module config.
|
||
*
|
||
* @param string $type feedback|ticket
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getSyncConfig(string $type = ''): array
|
||
{
|
||
if(!isset($this->config->global->syncProduct)) return array();
|
||
|
||
/* If feedback or ticket module is merge add story module.*/
|
||
$syncConfig = json_decode($this->config->global->syncProduct, true);
|
||
$syncConfig = isset($syncConfig[$type]) ? $syncConfig[$type] : array();
|
||
return $syncConfig;
|
||
}
|
||
|
||
/**
|
||
* 加载模块的语言项。
|
||
* Load module language.
|
||
*
|
||
* @access public
|
||
* @return void
|
||
*/
|
||
public function setModuleLang(): void
|
||
{
|
||
$this->lang->module = new stdclass();
|
||
$this->lang->module->name = $this->lang->tree->wordName;
|
||
$this->lang->module->short = $this->lang->tree->short;
|
||
}
|
||
|
||
/**
|
||
* 创建模块。
|
||
* Create module.
|
||
*
|
||
* @access public
|
||
* @return object|false
|
||
*/
|
||
public function createModule(): object|false
|
||
{
|
||
$data = fixer::input('post')
|
||
->setDefault('name', '')
|
||
->setDefault('createType', 'child')
|
||
->setDefault('objectID', 0)
|
||
->setDefault('order', 10)
|
||
->cleanInt('libID,parentID,objectID')
|
||
->get();
|
||
|
||
$module = new stdClass();
|
||
$module->root = zget($data, 'libID', 0);
|
||
$module->type = zget($data, 'moduleType', 'doc');
|
||
$module->parent = zget($data, 'parentID', 0);
|
||
$module->name = strip_tags(trim($data->name));
|
||
$module->branch = 0;
|
||
$module->short = '';
|
||
$module->order = $data->order;
|
||
|
||
if(empty($module->name))
|
||
{
|
||
dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->tree->dir);
|
||
return false;
|
||
}
|
||
|
||
if($data->createType == 'same')
|
||
{
|
||
$baseModule = $this->getByID($data->objectID);
|
||
if(!empty($baseModule))
|
||
{
|
||
$module->parent = $baseModule->parent;
|
||
$module->order = $baseModule->order;
|
||
}
|
||
}
|
||
elseif($data->createType == 'child')
|
||
{
|
||
$maxOrder = $this->dao->select('`order`')->from(TABLE_MODULE)
|
||
->where('root')->eq($module->root)
|
||
->andWhere('parent')->eq($module->parent)
|
||
->andWhere('type')->eq($module->type)
|
||
->orderBy('order_desc')
|
||
->limit(1)
|
||
->fetch('order');
|
||
|
||
$module->order = (int)$maxOrder + 10;
|
||
}
|
||
|
||
$repeatName = $this->checkUnique($module);
|
||
if($repeatName)
|
||
{
|
||
dao::$errors[] = sprintf($this->lang->tree->repeatDirName, $repeatName);
|
||
return false;
|
||
}
|
||
|
||
$parent = $this->getByID($module->parent);
|
||
$module->grade = $module->parent ? $parent->grade + 1 : 1;
|
||
$this->dao->insert(TABLE_MODULE)->data($module)->exec();
|
||
$moduleID = $this->dao->lastInsertID();
|
||
|
||
if($data->createType == 'same')
|
||
{
|
||
$this->dao->update(TABLE_MODULE)
|
||
->set('`order` = `order` + 10')
|
||
->where('`root`')->eq($module->root)
|
||
->andWhere('`parent`')->eq($module->parent)
|
||
->andWhere('`type`')->eq($module->type)
|
||
->andWhere('`order`')->gt($module->order)
|
||
->exec();
|
||
|
||
$module->order += 10;
|
||
}
|
||
|
||
$modulePath = "$moduleID,";
|
||
if($module->parent) $modulePath = $parent->path . $modulePath;
|
||
$this->dao->update(TABLE_MODULE)->set('`path`')->eq($modulePath)->set('`order`')->eq($module->order)->where('id')->eq($moduleID)->exec();
|
||
|
||
return $this->getByID($moduleID);
|
||
}
|
||
|
||
/**
|
||
* 获取分组模块对列表。
|
||
* Get group pairs.
|
||
*
|
||
* @param int $dimensionID
|
||
* @param int $parentGroup
|
||
* @param int $grade
|
||
* @param string $type
|
||
* @access public
|
||
* @return array
|
||
*/
|
||
public function getGroupPairs(int $dimensionID = 0, int $parentGroup = 0, int $grade = 2, string $type = 'chart'): array
|
||
{
|
||
$groups = $this->dao->select('id,name,grade,parent')->from(TABLE_MODULE)
|
||
->where('root')->eq($dimensionID)
|
||
->beginIF(!empty($parentGroup))->andWhere('root')->eq($dimensionID)->fi()
|
||
->andWhere('type')->eq($type)
|
||
->andWhere('deleted')->eq(0)
|
||
->orderBy('order')
|
||
->fetchGroup('grade', 'id');
|
||
|
||
$groupPairs = array();
|
||
if(!empty($groups[1]))
|
||
{
|
||
foreach($groups[1] as $parentGroup)
|
||
{
|
||
if($grade == 1) $groupPairs[$parentGroup->id] = $parentGroup->name;
|
||
if($grade == 2 && !empty($groups[2]))
|
||
{
|
||
foreach($groups[2] as $childGroup)
|
||
{
|
||
if($parentGroup->id == $childGroup->parent) $groupPairs[$childGroup->id] = '/' . $parentGroup->name . '/' . $childGroup->name;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $groupPairs;
|
||
}
|
||
}
|