* @package testcase
* @link https://www.zentao.net
*/
class testcaseZen extends testcase
{
/**
* 检查是否有产品,如果没有则跳转到创建产品的页面。
* Check products.
*
* @access protected
* @return void
*/
protected function checkProducts(): void
{
$objectID = 0;
$tab = $this->app->tab == 'project' || $this->app->tab == 'execution' ? $this->app->tab : 'qa';
if($this->app->tab == 'project')
{
$objectID = $this->session->project;
}
elseif($this->app->tab == 'execution')
{
$objectID = $this->session->execution;
}
if(empty($this->products) && (helper::isAjaxRequest('zin') || helper::isAjaxRequest('fetch'))) $this->locate($this->createLink('product', 'showErrorNone', "moduleName=$tab&activeMenu=testcase&objectID=$objectID"));
}
/**
* 设置列表页面的 cookie。
* Set browse cookie.
*
* @param int $productID
* @param string|bool $branch
* @param string $browseType
* @param int $param
* @access protected
* @return void
*/
protected function setBrowseCookie(int $productID, string|bool $branch, string $browseType = '', string $param = ''): void
{
helper::setcookie('preProductID', $productID);
helper::setcookie('preBranch', $branch);
$productChanged = $this->cookie->preProductID != $productID;
$branchChanged = $this->cookie->preBranch != $branch;
if($productChanged || $branchChanged || $browseType == 'bysearch')
{
$_COOKIE['caseModule'] = 0;
helper::setcookie('caseModule', '0');
}
if($browseType == 'bymodule') helper::setcookie('caseModule', $param);
if($browseType == 'bysuite') helper::setcookie('caseSuite', $param);
}
/**
* 获取列表页面的 branch 参数。
* Get browse branch param.
*
* @param string $branch
* @access protected
* @return string
*/
protected function getBrowseBranch(string $branch): string
{
if($branch === '') $branch = $this->cookie->preBranch;
if($branch === '' || $branch === false) $branch = '0';
return $branch;
}
/**
* 设置列表页面的 session。
* Set Browse session.
*
* @param int $productID
* @param string|bool $branch
* @param int $moduleID
* @param string $browseType
* @param string $orderBy
* @access protected
* @return void
*/
protected function setBrowseSession(int $productID, string|bool $branch, int $moduleID, string $browseType = '', string $orderBy = ''): void
{
if($browseType != 'bymodule') $this->session->set('caseBrowseType', $browseType);
$this->session->set('caseList', $this->app->getURI(true), $this->app->tab);
$this->session->set('productID', $productID);
$this->session->set('branch', $branch, 'qa');
$this->session->set('moduleID', $moduleID);
$this->session->set('browseType', $browseType);
$this->session->set('orderBy', $orderBy);
$this->session->set('testcaseOrderBy', '`sort` asc', $this->app->tab);
$this->session->set('testcaseOrderBy', '`sort` asc');
}
/**
* 设置列表页面的导航。
* Set menu in browse.
*
* @param int $productID
* @param string|bool $branch
* @param int $projectID
* @access protected
* @return array
*/
protected function setBrowseMenu(int $productID, string|bool $branch, int $projectID = 0): array
{
/* 在不同的应用中,设置不同的导航。 */
/* Set menu, save session. */
if($this->app->tab == 'project')
{
if(empty($projectID)) $projectID = (int)$this->session->project;
if(empty($projectID)) return array($productID, $branch);
$linkedProducts = $this->product->getProducts($projectID, 'all', '', false);
$this->products = count($linkedProducts) > 1 ? array('0' => $this->lang->product->all) + $linkedProducts : $linkedProducts;
$productID = count($linkedProducts) > 1 ? $productID : key($linkedProducts);
$hasProduct = $this->dao->findById($projectID)->from(TABLE_PROJECT)->fetch('hasProduct');
if(!$hasProduct) unset($this->config->testcase->search['fields']['product']);
$branch = intval($branch) > 0 ? $branch : 'all';
$this->loadModel('project')->setMenu($projectID);
$this->view->products = $this->products;
$this->view->projectID = $projectID;
$this->view->hasProduct = $hasProduct;
}
else
{
$this->loadModel('qa')->setMenu($productID, $branch);
}
return array($productID, $branch);
}
/**
* 设置菜单。
* Set menu.
*
* @param int $projectID
* @param int $executionID
* @param int $productID
* @param string|int $branch
* @access protected
* @return void
*/
protected function setMenu(int $projectID = 0, int $executionID = 0, int $productID = 0, string|int $branch = '')
{
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($projectID);
if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($executionID);
if($this->app->tab == 'qa') $this->testcase->setMenu($productID, $branch);
$this->view->projectID = $projectID;
$this->view->executionID = $executionID;
}
/**
* 构建搜索表单。
* Build the search form.
*
* @param int $productID
* @param int $queryID
* @param int $projectID
* @access protected
* @return void
*/
protected function buildBrowseSearchForm(int $productID, int $queryID, int $projectID, string $actionURL): void
{
if($this->app->rawModule == 'testcase') $this->config->testcase->search['onMenuBar'] = 'yes';
$searchProducts = $this->product->getPairs('', 0, '', 'all');
$this->testcase->buildSearchForm($productID, $searchProducts, $queryID, $actionURL, $projectID);
}
/**
* 展示从用例库导入的用例。
* Assign the cases imported from the library.
*
* @param int $productID
* @param string $branch
* @param int $libID
* @param string $orderBy
* @param int $queryID
* @param array $libraries
* @param array $cases
* @access protected
* @return void
*/
protected function assignForImportFromLib(int $productID, string $branch, int $libID, string $orderBy, int $queryID, array $libraries, int $projectID, array $cases): void
{
$product = $this->loadModel('product')->getById($productID);
$branches = array();
if($product->type != 'normal')
{
$this->loadModel('branch');
$branches = array(BRANCH_MAIN => $this->lang->branch->main) + $this->branch->getPairs($productID, 'active', $projectID);
}
foreach($branches as $branchID => $branchName) $canImportModules[$branchID] = $this->testcase->getCanImportedModules($productID, $libID, $branchID, 'items', $cases);
if(empty($branches)) $canImportModules[0] = $this->testcase->getCanImportedModules($productID, $libID, 0, 'items', $cases);
/* Build the search form. */
$actionURL = $this->createLink('testcase', 'importFromLib', "productID={$productID}&branch={$branch}&libID={$libID}&orderBy={$orderBy}&browseType=bySearch&queryID=myQueryID");
$this->config->testcase->search['module'] = 'testsuite';
$this->config->testcase->search['onMenuBar'] = 'no';
$this->config->testcase->search['actionURL'] = $actionURL;
$this->config->testcase->search['queryID'] = $queryID;
$this->config->testcase->search['fields']['lib'] = $this->lang->testcase->lib;
$this->config->testcase->search['params']['lib'] = array('operator' => '=', 'control' => 'select', 'values' => array('' => '', $libID => $libraries[$libID], 'all' => $this->lang->caselib->all));
$this->config->testcase->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($libID, 'caselib');
if(!$this->config->testcase->needReview) unset($this->config->testcase->search['params']['status']['values']['wait']);
unset($this->config->testcase->search['fields']['product']);
unset($this->config->testcase->search['fields']['branch']);
$this->loadModel('search')->setSearchParams($this->config->testcase->search);
$this->view->productID = $productID;
$this->view->branch = $branch;
$this->view->libID = $libID;
$this->view->orderBy = $orderBy;
$this->view->queryID = $queryID;
$this->view->product = $product;
$this->view->branches = $branches;
$this->view->canImportModules = $canImportModules;
}
/**
* 展示创建 testcase 的相关变量。
* Show the variables associated with the creation testcase.
*
* @param int $productID
* @param string $branch
* @param int $moduleID
* @param string $from
* @param int $param
* @param int $storyID
* @access protected
* @return void
*/
protected function assignCreateVars(int $productID, string $branch = '', int $moduleID = 0, string $from = '', int $param = 0, int $storyID = 0)
{
$product = $this->product->getById($productID);
if(!isset($this->products[$productID])) $this->products[$productID] = $product->name;
$projectID = $from == 'project' ? $param : $this->session->project;
$executionID = $from == 'execution' ? $param : 0;
$testcaseID = $from && strpos('testcase|work|contribute', $from) !== false ? $param : 0;
/* 设置分支。 */
/* Set branches. */
if($this->app->tab == 'project' || $this->app->tab == 'execution')
{
$objectID = $this->app->tab == 'project' ? $this->session->project : $executionID;
$productBranches = isset($product->type) && $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct(array($productID), $objectID, 'noclosed|withMain') : array();
$branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array('');
$branch = empty($branch) ? (string)key($branches) : $branch;
}
else
{
$branches = isset($product->type) && $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array();
}
/* 设置菜单。 */
/* Set menu. */
$executionID = $executionID ? $executionID : (int)$this->session->execution;
$this->setMenu((int)$this->session->project, $executionID, $productID, $branch);
/* 初始化用例数据。 */
/* Initialize the testcase. */
$case = $this->initTestcase($storyID, $testcaseID, $from == 'bug' ? $param : 0);
if($case->story && $this->loadModel('story')->fetchByID($case->story)->product != $productID) $case->story = 0;
/* 设置模块和需求。 */
/* Set modules. */
$this->assignModulesForCreate($productID, $moduleID, $branch, $case->story, $branches);
$this->view->product = $product;
$this->view->projectID = isset($projectID) ? $projectID : 0;
$this->view->currentSceneID = $testcaseID > 0 ? $case->scene : (int)$this->cookie->lastCaseScene;
$this->view->case = $case;
$this->view->executionID = $executionID;
$this->view->branch = $branch;
$this->view->branches = $branches;
$this->view->from = $from;
$this->view->param = $param;
}
/**
* 展示创建场景的相关变量。
* Show the variables associated with the creation scene.
*
* @param int $productID
* @param string $branch
* @param int $moduleID
* @access protected
* @return void
*/
protected function assignCreateSceneVars(int $productID, string $branch = '', int $moduleID = 0): void
{
$product = $this->product->getById($productID);
if(!isset($this->products[$productID])) $this->products[$productID] = $product->name;
/* 获取分支键值对及当前分支。*/
/* Get branch pairs and set current branch. */
if($this->app->tab == 'project' || $this->app->tab == 'execution')
{
/* 在项目或执行中显示时只显示项目或执行关联的分支键值对。*/
/* When showing in a project or execution, only the branch key-value pairs associated with the project or execution are shown. */
$objectID = $this->app->tab == 'project' ? $this->session->project : 0;
$productBranches = isset($product->type) && $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct(array($productID), $objectID, 'noclosed|withMain') : array();
$branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array();
if(!empty($branches)) $branch = (string)key($branches);
}
else
{
$branches = isset($product->type) && $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array();
}
/* 设置菜单。 */
/* Set menu. */
$this->setMenu((int)$this->session->project, (int)$this->session->execution, $productID, $branch);
$this->view->title = $this->products[$productID] . $this->lang->hyphen . $this->lang->testcase->newScene;
$this->view->modules = $this->tree->getOptionMenu($productID, 'case', 0, ($branch === 'all' || !isset($branches[$branch])) ? 'all' : (string)$branch);
$this->view->scenes = $this->testcase->getSceneMenu($productID, $moduleID, ($branch === 'all' || !isset($branches[$branch])) ? 'all' : (string)$branch);
$this->view->moduleID = $moduleID ? (int)$moduleID : (int)$this->cookie->lastCaseModule;
$this->view->parent = (int)$this->cookie->lastCaseScene;
$this->view->product = $product;
$this->view->branch = $branch;
$this->view->branches = $branches;
}
/**
* 处理编辑场景相关的变量。
* Assign variables of edit a scene.
*
* @param object $oldScene
* @access protected
* @return void
*/
protected function assignEditSceneVars(object $oldScene): void
{
$productID = $oldScene->product;
$branchID = (string)$oldScene->branch;
$moduleID = $oldScene->module;
$parentID = $oldScene->parent;
/* 设置菜单。 */
/* Set menu. */
$this->setMenu((int)$this->session->project, (int)$this->session->execution, $productID, $branchID);
$product = $this->product->getByID($productID);
if(!isset($this->products[$productID])) $this->products[$productID] = $product->name;
/* Display status of branch. */
$branches = array();
$branchList = $this->loadModel('branch')->getList($productID, 0, 'all');
foreach($branchList as $branch) $branches[$branch->id] = $branch->name . ($branch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
if(!isset($branches[$branchID]))
{
$sceneBranch = $this->branch->getByID($branchID, $productID, '');
if($sceneBranch) $branches[$branchID] = $sceneBranch->name . ($sceneBranch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
$modules = $this->tree->getOptionMenu($productID, 'case', 0, $branchID);
if(!isset($modules[$moduleID])) $modules += $this->tree->getModulesName($moduleID);
$scenes = $this->testcase->getSceneMenu($productID, $moduleID, $branchID, 0, $oldScene->id);
if(!isset($scenes[$parentID])) $scenes += $this->testcase->getScenesName((array)$parentID);
$this->view->title = $this->products[$productID] . $this->lang->hyphen . $this->lang->testcase->editScene;
$this->view->products = $this->products;
$this->view->product = $product;
$this->view->branches = $branches;
$this->view->modules = $modules;
$this->view->scenes = $scenes;
$this->view->scene = $oldScene;
}
/**
* 展示创建 testcase 的模块变量。
* Show the modules associated with the creation testcase.
*
* @param int $productID
* @param int $moduleID
* @param string $branch
* @param int $storyID
* @param array $branches
* @access protected
* @return void
*/
protected function assignModulesForCreate(int $productID, int $moduleID, string $branch, int $storyID, array $branches)
{
if($storyID)
{
$story = $this->loadModel('story')->getByID($storyID);
if(empty($moduleID)) $moduleID = $story->module;
}
$currentModuleID = !$moduleID && $productID == (int)$this->cookie->lastCaseProduct ? (int)$this->cookie->lastCaseModule : $moduleID;
$this->view->currentModuleID = $currentModuleID;
$this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, 'case', 0, $branch === 'all' || !isset($branches[$branch]) ? '0' : $branch);
$this->view->sceneOptionMenu = $this->testcase->getSceneMenu($productID, $moduleID, $branch === 'all' || !isset($branches[$branch]) ? '0' : $branch);
}
/**
* 处理浏览页面的用例列表。
* Process cases for browse page.
*
* @param array $cases
* @access public
* @return array
*/
public function processCasesForBrowse(array $cases): array
{
if(!$cases) return [];
foreach($cases as $case)
{
$case->caseID = $case->id;
$case->id = 'case_' . $case->id; // 给用例 ID 加前缀以防止和场景 ID 重复。Add prefix to case ID to prevent it from conflicting with scene ID.
$case->parent = 0;
$case->isScene = false;
$case->title = htmlspecialchars_decode($case->title);
}
$caseScenes = array_unique(array_filter(array_column($cases, 'scene'))); // 获取用例的场景 ID。Get unique scene IDs from cases.
if(!$caseScenes) return $cases;
$pathList = $this->dao->select('path')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in($caseScenes)->fetchPairs(); // 获取场景的路径列表。Get path list of scenes.
$idList = array_unique(array_filter(explode(',', implode(',', $pathList))));
$scenes = $this->dao->select('*')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in($idList)->orderBy('grade_desc, sort_asc')->fetchAll('id'); // 获取场景列表。Get scene list.
foreach($cases as $case)
{
if(!isset($scenes[$case->scene])) continue;
$scene = $scenes[$case->scene];
$scene->hasCase = true;
$case->parent = $scene->id;
$case->grade = $scene->grade + 1;
$case->path = $scene->path . $case->id . ',';
}
$this->dao->setTable(TABLE_CASE);
$fieldTypes = $this->dao->getFieldsType();
foreach($scenes as $scene)
{
$scene = $this->testcase->buildSceneBaseOnCase($scene, $fieldTypes);
$scene->scene = $scene->parent;
if(isset($scene->hasCase))
{
if (isset($scenes[$scene->parent])) $scenes[$scene->parent]->hasCase = true; // 如果子场景有用例,那么父场景也有用例。If the child scene has a use case, the parent scene also has a use case.
}
else
{
$scene->hasCase = false; // hasCase 为 false 时在页面上显示“暂无用例”。If hasCase is false, show "No use case" on the page.
}
}
return array_merge($scenes, $cases);
}
/**
* 指定浏览用例页面的用例列表。
* Assign cases for browse page.
*
* @param int $productID
* @param string $branch
* @param string $browseType
* @param int $queryID
* @param int $moduleID
* @param string $caseType
* @param string $orderBy
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access protected
* @return void
*/
protected function assignCasesForBrowse(int $productID, string $branch, string $browseType, int $queryID, int $moduleID, string $caseType, string $orderBy, int $recTotal, int $recPerPage, int $pageID, string $from = 'testcase'): void
{
$this->app->loadClass('pager', $static = true);
$pager = new pager($recTotal, $recPerPage, $pageID);
$sort = common::appendOrder($orderBy);
if(strpos($sort, 'caseID') !== false) $sort = str_replace('caseID', 'id', $sort);
$cases = $this->testcase->getTestCases($productID, $branch, $browseType, $queryID, $moduleID, $caseType, 'no', $sort, $pager, $from);
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false);
$cases = $this->loadModel('story')->checkNeedConfirm($cases);
$cases = $this->testcase->appendData($cases);
if($this->config->edition != 'open')
{
$caseRelatedObjectList = $this->loadModel('custom')->getRelatedObjectList(array_keys($cases), 'testcase', 'byRelation', true);
foreach($cases as $caseID => $case) $case->relatedObject = zget($caseRelatedObjectList, $caseID, 0);
}
$this->view->cases = $this->processCasesForBrowse($cases);
$this->view->orderBy = $orderBy;
$this->view->pager = $pager;
}
/**
*
* 指定模块树。
* Assign module tree in browse page.
*
* @param int $productID
* @param string $branch
* @param int $projectID
* @access protected
* @return void
*/
protected function assignModuleTreeForBrowse(int $productID, string $branch, int $projectID): void
{
if($projectID && empty($productID))
{
$this->view->moduleTree = $this->tree->getCaseTreeMenu($projectID, $productID, 0, array('treeModel', 'createCaseLink'));
}
else
{
$this->view->moduleTree = $this->tree->getTreeMenu($productID, 'case', 0, array('treeModel', 'createCaseLink'), array('projectID' => $projectID, 'productID' => $productID), $branch);
}
}
/**
*
* 指定产品和分支。
* Assign product and branch.
*
* @param int $productID
* @param string $branch
* @param int $projectID
* @access protected
* @return void
*/
protected function assignProductAndBranchForBrowse(int $productID, string $branch, int $projectID = 0): void
{
/* 根据产品类型判断是否展示分支,获取分支选项信息和带标签的分支选项信息。*/
/* Judge whether to show branch according to the type of product, get branch option and branch tag option. */
$product = $this->product->getByID($productID);
$showBranch = false;
$branchOption = array();
$branchTagOption = array();
if($product && $product->type != 'normal')
{
/* Display of branch label. */
$showBranch = $this->loadModel('branch')->showBranch($productID);
/* Display status of branch. */
$branches = $this->loadModel('branch')->getList($productID, $projectID, 'all');
foreach($branches as $branchInfo)
{
$branchOption[$branchInfo->id] = $branchInfo->name;
$branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
}
$this->view->productID = $productID;
$this->view->productName = zget($this->products, $productID, '');
$this->view->product = $product;
$this->view->branch = (!empty($product) and $product->type != 'normal') ? $branch : 0;
$this->view->branchOption = $branchOption;
$this->view->branchTagOption = $branchTagOption;
}
/**
* 指定变量。
* Assign variables.
*
* @param int $productID
* @param string $branch
* @param string $browseType
* @param int $projectID
* @param int $param
* @param int $moduleID
* @param int $suiteID
* @param string $caseType
* @access protected
* @return void
*/
protected function assignForBrowse(int $productID, string $branch, string $browseType, int $projectID, int $param, int $moduleID, int $suiteID, string $caseType): void
{
$showModule = !empty($this->config->testcase->browse->showModule) ? $this->config->testcase->browse->showModule : '';
$tree = $moduleID ? $this->tree->getByID($moduleID) : '';
$this->view->title = zget($this->products, $productID, '') . $this->lang->hyphen . $this->lang->testcase->common;
$this->view->projectID = $projectID;
$this->view->projectType = !empty($projectID) ? $this->dao->select('model')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('model') : '';
$this->view->browseType = $browseType;
$this->view->param = $param;
$this->view->moduleID = $moduleID;
$this->view->moduleName = $moduleID ? $tree->name : $this->lang->tree->all;
$this->view->suiteID = $suiteID;
$this->view->caseType = $caseType;
$this->view->users = $this->user->getPairs('noletter');
$this->view->modules = $this->tree->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0, $branch == 'all' ? '0' : $branch);
$this->view->iscenes = $this->testcase->getSceneMenu($productID, $moduleID);
$this->view->suiteList = $this->loadModel('testsuite')->getSuites($productID);
$this->view->modulePairs = $showModule ? $this->tree->getModulePairs($productID, 'case', $showModule) : array();
$this->view->libraries = $this->loadModel('caselib')->getLibraries();
$this->view->stories = array('') + $this->loadModel('story')->getPairs($productID);
$this->view->automation = $this->loadModel('zanode')->getAutomationByProduct($productID);
if($projectID)
{
$productPairs = $this->product->getProductPairsByProject($projectID);
$this->view->switcherParams = "projectID={$projectID}&productID={$productID}¤tMethod=testcase";
$this->view->switcherText = zget($productPairs, $productID, $this->lang->product->all);
$this->view->project = $this->loadModel('project')->getByID($projectID);
$this->view->switcherObjectID = $productID;
}
}
/**
* 处理更新请求数据。
* Processing request data.
*
* @param form $formData
* @param object $oldBug
* @access protected
* @return object|false
*/
protected function prepareEditExtras(form $formData, object $oldCase): object|false
{
foreach($formData->data->expects as $key => $value)
{
if(!empty($value) && empty($formData->data->steps[$key]))
{
dao::$errors['message'][] = sprintf($this->lang->testcase->stepsEmpty, $key);
return false;
}
}
if(!empty($_FILES['scriptFile'])) unset($_FILES['scriptFile']);
$result = $this->getStatusForUpdate($oldCase);
if(!$result || !is_array($result)) return $result;
list($stepChanged, $status) = $result;
$case = $formData->add('id', $oldCase->id)
->add('version', $stepChanged ? (int)$oldCase->version + 1 : (int)$oldCase->version)
->add('lastEditedBy', $this->app->user->account)
->add('lastEditedDate', helper::now())
->add('stepChanged', $stepChanged)
->setForce('status', $status)
->setDefault('story,branch', 0)
->setDefault('stage', '')
->setDefault('deleteFiles', array())
->setDefault('storyVersion', $oldCase->storyVersion)
->setDefault('script', $oldCase->script)
->setIF($formData->data->story != false && $formData->data->story != $oldCase->story, 'storyVersion', $this->loadModel('story')->getVersion((int)$formData->data->story))
->setIF(!$formData->data->linkCase, 'linkCase', '')
->setIF($formData->data->auto == 'auto' && $formData->data->script, 'script', htmlentities($formData->data->script))
->setIF($formData->data->auto == 'no', 'script', '')
->join('stage', ',')
->join('linkCase', ',')
->cleanInt('story,product,branch,module')
->skipSpecial('script')
->remove('files,labels,scriptFile,scriptName')
->removeIF($formData->data->auto == 'auto' && !$formData->data->script, 'script')
->get();
if(!empty($oldCase->lib) && empty($oldCase->product) && !empty($_POST['lib'])) $case->lib = $this->post->lib;
return !empty($this->config->testcase->editor->edit['id']) ? $this->loadModel('file')->processImgURL($case, $this->config->testcase->editor->edit['id'], $this->post->uid) : $case;
}
/**
* 提前处理用例数据。
* Preprocess case.
*
* @param object $case
* @access protected
* @return object
*/
protected function preProcessForEdit(object $case): object
{
/* 初始化用例步骤。*/
/* Unit the steps of case. */
if(empty($case->steps))
{
$step = new stdclass();
$step->type = 'step';
$step->desc = '';
$step->name = '';
$step->expect = '';
$case->steps[] = $step;
}
return $case;
}
/**
* 设置编辑用例库用例的导航。
* Set menu for editing lib case.
*
* @param object $case
* @param array $libraries
* @access protected
* @return void
*/
protected function setMenuForLibCaseEdit(object $case, array $libraries): void
{
if($this->app->tab == 'project')
{
$this->loadModel('project')->setMenu($this->session->project);
}
else
{
$this->loadModel('caselib')->setLibMenu($libraries, $case->lib);
}
}
/**
* 设置编辑用例的导航。
* Set menu for editing case.
*
* @param object $case
* @param int $executionID
* @access protected
* @return void
*/
protected function setMenuForCaseEdit(object $case, int $executionID): void
{
if($this->app->tab == 'project')
{
$this->loadModel('project')->setMenu($case->project);
$this->view->projectID = $case->project;
}
if($this->app->tab == 'execution')
{
if(!$executionID) $executionID = $case->execution;
$this->loadModel('execution')->setMenu($executionID);
$this->view->executionID = $executionID;
}
if($this->app->tab == 'qa') $this->testcase->setMenu($case->product, $case->branch);
}
/**
* 指定编辑用例库用例的数据。
* Assign data for editint lib case.
*
* @param object $case
* @param array $libraries
* @access protected
* @return void
*/
protected function assignForEditLibCase(object $case, array $libraries): void
{
$this->view->title = "CASE #$case->id $case->title - " . $libraries[$case->lib];
$this->view->isLibCase = true;
$this->view->libraries = $libraries;
$this->view->moduleOptionMenu = $this->tree->getOptionMenu($case->lib, 'caselib', 0);
$this->view->libName = $libraries[$case->lib];
$this->view->libID = $case->lib;
}
/**
* 指定编辑用例的数据。
* Assign for editing case.
*
* @param object $case
* @param int $executionID
* @access protected
* @return void
*/
protected function assignForEditCase(object $case, int $executionID): void
{
$product = $this->product->getByID($case->product);
if(!isset($this->products[$case->product])) $this->products[$case->product] = $product->name;
$this->view->title = $this->products[$case->product] . $this->lang->hyphen . $this->lang->testcase->edit;
$this->view->isLibCase = false;
$this->view->product = $product;
$this->view->products = $this->products;
$this->view->branch = $this->cookie->preBranch;
$this->assignBranchForEdit($case, $executionID);
$this->assignModuleOptionMenuForEdit($case);
}
/**
* 指定编辑用例的分支。
* Assign branch data for editing case.
*
* @param object $case
* @param int $executionID
* @access private
* @return void
*/
private function assignBranchForEdit(object $case, int $executionID): void
{
$objectID = 0;
if($this->app->tab == 'execution') $objectID = $executionID;
if($this->app->tab == 'project') $objectID = $case->project;
$branches = $this->loadModel('branch')->getList($case->product, $objectID, 'all');
$branchTagOption = array();
foreach($branches as $branchInfo) $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
if(!isset($branchTagOption[$case->branch]))
{
$caseBranch = $this->branch->getByID($case->branch, $case->product, '');
$branchTagOption[$case->branch] = $case->branch == BRANCH_MAIN ? $caseBranch : ($caseBranch->name . ($caseBranch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''));
}
$this->view->branchTagOption = $branchTagOption;
}
/**
* 指定编辑用的模块选项。
* Assign module option menu for editing case.
*
* @param object $case
* @access private
* @return void
*/
private function assignModuleOptionMenuForEdit(object $case): void
{
$moduleOptionMenu = $this->tree->getOptionMenu($case->product, 'case', 0, (string)$case->branch);
if($case->lib && $case->fromCaseID)
{
$lib = $this->loadModel('caselib')->getByID($case->lib);
$libModules = $this->tree->getOptionMenu($case->lib, 'caselib');
foreach($libModules as $moduleID => $moduleName)
{
if($moduleID == 0) continue;
$moduleOptionMenu[$moduleID] = $lib->name . $moduleName;
}
}
if(!isset($moduleOptionMenu[$case->module])) $moduleOptionMenu += $this->tree->getModulesName((array)$case->module);
$this->view->moduleOptionMenu = $moduleOptionMenu;
}
/**
* 指定编辑用例的其他数据。
* Assign other data for editing case.
*
* @param int $productID
* @param object $case
* @param array $testtasks
* @access protected
* @return void
*/
protected function assignForEdit(int $productID, object $case, array $testtasks): void
{
$sceneOptionMenu = $this->testcase->getSceneMenu($productID, $case->module);
if(!isset($sceneOptionMenu[$case->scene])) $sceneOptionMenu += $this->testcase->getScenesName((array)$case->scene);
$forceNotReview = $this->testcase->forceNotReview();
if($forceNotReview) unset($this->lang->testcase->statusList['wait']);
$this->view->case = $case;
$this->view->testtasks = $testtasks;
$this->view->forceNotReview = $forceNotReview;
$this->view->sceneOptionMenu = $sceneOptionMenu;
$this->view->users = $this->user->getPairs('noletter');
$this->view->actions = $this->loadModel('action')->getList('case', $case->id);
}
/**
* 指定批量编辑用例的数据。
* Assign for editing case.
*
* @param int $productID
* @param string $branch
* @param string $type
* @param array $cases
* @access protected
* @return void
*/
protected function assignForBatchEdit(int $productID, string $branch, string $type, array $cases): void
{
list($productIdList, $libIdList) = $this->assignTitleForBatchEdit($productID, $branch, $type, $cases);
/* 设置模块。 */
/* Set modules. */
$modules = array();
$branchProduct = false;
$branchTagOption = array();
$products = $this->product->getByIdList($productIdList);
$branches = array(0 => 0);
foreach($products as $product)
{
if($product->type != 'normal')
{
$branches = $this->loadModel('branch')->getList($product->id, 0, 'all');
foreach($branches as $branchInfo) $branchTagOption[$product->id][$branchInfo->id] = "/{$product->name}/{$branchInfo->name}" . ($branchInfo->status == 'closed' ? " ({$this->lang->branch->statusList['closed']})" : '');
$branchProduct = true;
}
$modulePairs = $this->tree->getOptionMenu($product->id, 'case', 0, array_keys($branches));
foreach($modulePairs as $branchID => $branchModules)
{
$modules['case'][$product->id][$branchID] = array();
foreach($branchModules as $moduleID => $module) $modules['case'][$product->id][$branchID][] = array('text' => $module, 'value' => $moduleID);
}
}
foreach($libIdList as $libID)
{
$libModules = $this->tree->getOptionMenu($libID, 'caselib');
$modules['lib'][$libID][0] = array();
foreach($libModules as $moduleID => $module) $modules['lib'][$libID][0][] = array('text' => $module, 'value' => $moduleID);
}
if($this->app->tab == 'project') $branchTagOption = $this->loadModel('branch')->getPairsByProjectProduct($this->session->project, $productID);
/* 指派模块和场景。 */
/* Assign modules and scenes. */
$this->assignModuleAndSceneForBatchEdit($productID, $branch, $branches, $cases, $modules);
/* 设置自定义字段。 */
/* Set custom fields. */
foreach(explode(',', $this->config->testcase->list->customBatchEditFields) as $field) $customFields[$field] = $this->lang->testcase->$field;
$this->view->customFields = $customFields;
$this->view->showFields = $this->config->testcase->custom->batchEditFields;
$this->view->branchTagOption = $branchTagOption;
$this->view->products = $products;
$this->view->branchProduct = $branchProduct;
}
/**
* 指派用例库下批量编辑用例的数据。
* Assign for batch editing case in caselib.
*
* @param int $libID
* @access protected
* @return void
*/
protected function assignLibForBatchEdit(int $libID): void
{
$libraries = $this->loadModel('caselib')->getLibraries();
$this->loadModel('caselib')->setLibMenu($libraries, $libID);
$this->view->libID = $libID;
}
/**
* 指定批量编辑用例的页面标题。
* Assign title for editing case.
*
* @param int $productID
* @param string $branch
* @param string $type
* @param array $cases
* @access protected
* @return array
*/
protected function assignTitleForBatchEdit(int $productID, string $branch, string $type, array $cases): array
{
$productIdList = array();
$libIdList = array();
/* 指派用例库用例。 */
/* Assign lib cases. */
if($type == 'lib')
{
$libID = $productID;
$libIdList = array($libID);
$libraries = $this->loadModel('caselib')->getLibraries();
/* Remove story custom fields from caselib */
$this->config->testcase->list->customBatchEditFields = str_replace(',story', '', $this->config->testcase->list->customBatchEditFields);
$this->config->testcase->custom->batchEditFields = str_replace(',story', '', $this->config->testcase->custom->batchEditFields);
/* Set caselib menu. */
$this->caselib->setLibMenu($libraries, $libID);
$this->view->title = $libraries[$libID] . $this->lang->hyphen . $this->lang->testcase->batchEdit;
}
/* 指派测试用例。 */
/* Assign test cases. */
elseif($productID)
{
$product = $this->product->getByID($productID);
$productIdList = array($productID);
$this->setMenu((int)$this->session->project, (int)$this->session->execution, $productID, $branch);
$this->view->title = $product->name . $this->lang->hyphen . $this->lang->testcase->batchEdit;
}
/* 指派地盘标签下的用例。 */
/* Assign cases of my tab. */
else
{
foreach($cases as $case)
{
if($case->lib == 0) $productIdList[$case->product] = $case->product;
if($case->lib > 0) $libIdList[$case->lib] = $case->lib;
}
$this->view->title = $this->lang->testcase->batchEdit;
}
return array($productIdList, $libIdList);
}
/**
* 指定批量编辑用例的模块和场景标题。
* Assign modules and scenes for editing case.
*
* @param int $productID
* @param string $branch
* @param array $branches
* @param array $cases
* @param array $modules
* @access protected
* @return void
*/
protected function assignModuleAndSceneForBatchEdit(int $productID, string $branch, array $branches, array $cases, array $modules): void
{
$moduleScenes = array();
$moduleScenesPairs = array();
$modulePairs = array();
$scenePairs = array();
foreach($cases as $case)
{
$case->story = $case->story ? $case->story : '';
/* 设置用例模块。 */
/* Set case module. */
$objectID = $case->lib > 0 ? $case->lib : $case->product;
$objectType = $case->lib > 0 ? 'lib' : 'case';
if(isset($modules[$objectType][$objectID][$case->branch]))
{
$modulePairs[$case->id] = $modules[$objectType][$objectID][$case->branch];
}
elseif(isset($modules[$objectType][$objectID]))
{
$modulePairs[$case->id] = $modules[$objectType][$objectID][0];
}
else
{
$module = $this->tree->getByID($case->module);
$modulePairs[$case->id][] = array('text' => zget($module, 'name', ''), 'value' => $case->module);
}
/* 设置用例场景。 */
/* Set case 场景. */
if(!isset($moduleScenesPairs[$case->module]))
{
$moduleScenesPairs[$case->module] = array();
$moduleScenes = $this->testcase->getSceneMenu($productID, $case->module, $branch !== 'all' && !isset($branches[$branch]) ? '0' : $branch);
foreach($moduleScenes as $sceneID => $scene) $moduleScenesPairs[$case->module][] = array('text' => $scene, 'value' => $sceneID);
}
$scenePairs[$case->id][] = $moduleScenesPairs[$case->module];
if(!isset($scenes[$case->scene])) $scenePairs[$case->id][] = array('text' => '/' . $this->testcase->fetchSceneName($case->scene), 'value' => $case->scene);
}
$this->view->scenePairs = $scenePairs;
$this->view->modulePairs = $modulePairs;
}
/**
* 指派导入的数据。
* Assign show imported data.
*
* @param int $productID
* @param string $branch
* @param array $caseData
* @param int $stepVars
* @param int $pagerID
* @param int $maxImport
* @access protected
* @return void
*/
protected function assignShowImportVars(int $productID, string $branch, array $caseData, int $stepVars, int $pagerID, int $maxImport)
{
if(empty($caseData)) return $this->sendError($this->lang->error->noData, $this->createLink('testcase', 'browse', "productID={$productID}&branch={$branch}"));
/* 设置模块。 */
/* Set modules. */
$modules = array();
$branches = $this->loadModel('branch')->getPairs($productID, 'active');
$branchModules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, empty($branches) ? array(0) : array_keys($branches));
foreach($branchModules as $branchID => $moduleList)
{
$modules[$branchID] = array();
foreach($moduleList as $moduleID => $moduleName)
{
$modules[$branchID][$moduleID] = $moduleName;
}
}
/* 如果导入的用例数大于最大导入数,则在最大导入时截取。 */
/* If the number of imported cases is greater than max import, intercept at max import. */
$allCount = count($caseData);
$allPager = 1;
if($allCount > $this->config->file->maxImport)
{
if(empty($maxImport))
{
$this->view->allCount = $allCount;
$this->view->maxImport = $maxImport;
$this->view->productID = $productID;
$this->view->branch = $branch;
$this->display();
exit;
}
$allPager = ceil($allCount / $maxImport);
$caseData = array_slice($caseData, ($pagerID - 1) * $maxImport, $maxImport, true);
}
if(empty($caseData)) return $this->sendError($this->lang->error->noData, $this->createLink('testcase', 'browse', "productID={$productID}&branch={$branch}"));
/* 判断输入的用例是否超出限制,并设定session。 */
/* Judge whether the imported cases is too large and set session. */
$countInputVars = count($caseData) * 12 + $stepVars;
$showSuhosinInfo = common::judgeSuhosinSetting($countInputVars);
if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars);
$this->view->modules = $modules;
$this->view->caseData = $caseData;
$this->view->branches = $branches;
$this->view->allCount = $allCount;
$this->view->allPager = $allPager;
$this->view->isEndPage = $pagerID >= $allPager;
$this->view->pagerID = $pagerID;
}
/**
* Add edit action.
*
* @param int $caseID
* @param string $oldStatus
* @param string $status
* @param array $changes
* @param string $comment
* @access protected
* @return void
*/
protected function addEditAction(int $caseID, string $oldStatus, string $status, array $changes = array(), string $comment = ''): void
{
$this->loadModel('action');
$action = !empty($changes) ? 'Edited' : 'Commented';
$actionID = $this->action->create('case', $caseID, $action, $comment);
$this->action->logHistory($actionID, $changes);
if($oldStatus != 'wait' && $status == 'wait') $this->action->create('case', $caseID, 'submitReview');
}
/**
* 指定 testcase 查看的关联的变量。
* Assign testcase related variables.
*
* @param object $case
* @param string $from
* @param int $taskID
* @access protected
* @return void
*/
protected function assignCaseForView(object $case, string $from, int $taskID)
{
$case = $this->loadModel('story')->checkNeedConfirm($case);
if($from == 'testtask')
{
$run = $this->loadModel('testtask')->getRunByCase($taskID, $case->id);
$case->assignedTo = $run->assignedTo;
$case->lastRunner = $run->lastRunner;
$case->lastRunDate = $run->lastRunDate;
$case->lastRunResult = $run->lastRunResult;
$case->caseStatus = $case->status;
$case->status = $run->status;
$results = $this->testtask->getResults($run->id);
$result = array_shift($results);
if($result)
{
$case->xml = $result->xml;
$case->duration = $result->duration;
}
}
$case = $this->testcase->appendCaseFails($case, $from, $taskID);
$case = $this->processStepsForMindMap($case);
$sceneOptionMenu = $this->testcase->getSceneMenu($case->product, $case->module);
if(!isset($sceneOptionMenu[$case->scene])) $sceneOptionMenu += $this->testcase->getScenesName((array)$case->scene);
$this->view->from = $from;
$this->view->taskID = $taskID;
$this->view->runID = $from == 'testcase' || $from == 'testsuite' ? 0 : $run->id;
$this->view->case = $case;
$this->view->caseFails = $case->caseFails;
$this->view->modulePath = $this->tree->getParents($case->module);
$this->view->caseModule = empty($case->module) ? '' : $this->tree->getById($case->module);
$this->view->preAndNext = !isOnlybody() ? $this->loadModel('common')->getPreAndNextObject('testcase', $case->id) : '';
$this->view->users = $this->user->getPairs('noletter');
$this->view->actions = $this->loadModel('action')->getList('case', $case->id);
$this->view->scenes = $sceneOptionMenu;
}
/**
* 指定批量创建用例的相关变量。
* Assign for batch creating case.
*
* @param int $productID
* @param string $branch
* @param int $moduleID
* @param int $storyID
* @access protected
* @return void
*/
protected function assignForBatchCreate(int $productID, string $branch = '', int $moduleID = 0, int $storyID = 0)
{
$product = $this->product->getById($productID);
/* 设置分支。 */
/* Set branches. */
if($this->app->tab == 'project' and $product->type != 'normal')
{
$this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
$productBranches = $this->loadModel('execution')->getBranchByProduct(array($productID), $this->session->project, 'noclosed|withMain');
$branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array();
$branch = key($branches);
}
else
{
$branches = $this->loadModel('branch')->getPairs($productID, 'active');
}
/* 设置自定义字段和展示字段。 */
/* Set custom fields and show fields. */
foreach(explode(',', $this->config->testcase->list->customBatchCreateFields) as $field)
{
if($product->type != 'normal') $customFields[$product->type] = $this->lang->product->branchName[$product->type];
$customFields[$field] = $this->lang->testcase->$field;
}
$showFields = sprintf($this->config->testcase->custom->batchCreateFields, $product->type);
if($product->type == 'normal')
{
$showFields = str_replace(array(",branch,", ",platform,"), '', ",$showFields,");
$showFields = trim($showFields, ',');
}
/* 设置需求键对。 */
/* Set story pairs. */
$storyPairs = $this->loadModel('story')->getProductStoryPairs($productID, $branch === 'all' ? 0 : $branch, array(), 'active,reviewing', 'id_desc', 50, '', 'story', false);
$story = $storyID ? $this->story->fetchByID($storyID) : '';
$storyPairs += $storyID ? array($storyID => $story->id . ':' . $story->title) : array();
if($storyID && empty($moduleID)) $moduleID = $story->module;
$this->view->product = $product;
$this->view->branches = $branches;
$this->view->customFields = $customFields;
$this->view->showFields = $showFields;
$this->view->story = $story;
$this->view->storyPairs = $this->story->addGradeLabel($storyPairs);
$this->view->sceneOptionMenu = $this->testcase->getSceneMenu($productID, $moduleID, $branch === 'all' || !isset($branches[$branch]) ? '0' : $branch);
$this->view->currentModuleID = $moduleID;
}
/**
* 构建创建 testcase 页面数据。
* Build form fields for creating testcase.
*
* @param string $from
* @param int $param
* @access protected
* @return void
*/
protected function buildCaseForCreate(string $from, int $param): object
{
$status = $this->getStatusForCreate();
$case = form::data($this->config->testcase->form->create)
->add('status', $status)
->setIF($from == 'bug', 'fromBug', $param)
->setIF($from == 'project' && $param, 'project', $param)
->setIF($from == 'execution' && $param, 'execution', $param)
->setIF($from != 'project' && $this->app->tab == 'project', 'project', $this->session->project)
->setIF($from != 'execution' && $this->app->tab == 'execution', 'execution', $this->session->execution)
->setIF($this->post->project, 'project', $this->post->project) // API will post project param
->setIF($this->post->execution, 'execution', $this->post->execution) // API will post execution param
->setIF($this->post->auto, 'auto', 'auto')
->setIF($this->post->auto && $this->post->script, 'script', $this->post->script ? htmlentities($this->post->script) : '')
->setIF($this->post->story, 'storyVersion', $this->loadModel('story')->getVersion((int)$this->post->story))
->get();
/* 如果用例产品是影子产品,同步用例到项目中。 */
$product = $this->loadModel('product')->getById($case->product);
if($product->shadow && empty($case->project))
{
$project = $this->loadModel('project')->getByShadowProduct($case->product);
$case->project = $project->id;
}
/* 如果用例的项目不为空,且不启用迭代,同步用例到影子迭代中。 */
if(!empty($case->project))
{
if(!isset($project)) $project = $this->loadModel('project')->fetchByID($case->project);
if(empty($project->multiple)) $case->execution = $this->loadModel('execution')->getNoMultipleID($case->project);
}
return !empty($this->config->testcase->editor->create['id']) ? $this->loadModel('file')->processImgURL($case, $this->config->testcase->editor->create['id'], $this->post->uid) : $case;
}
/**
* 构建批量创建用例的数据。
* Build cases for batch creating.
*
* @param int $productID
* @access protected
* @return array
*/
protected function buildCasesForBathcCreate(int $productID): array
{
$now = helper::now();
$account = $this->app->user->account;
$forceNotReview = $this->testcase->forceNotReview();
$storyVersions = array();
$testcases = form::batchData($this->config->testcase->form->batchCreate)->get();
$projectID = $this->app->tab == 'project' ? $this->session->project : 0;
$executionID = 0;
if($projectID)
{
$project = $this->loadModel('project')->getByID($projectID);
if(!$project->multiple) $executionID = $this->dao->select('id')->from(TABLE_PROJECT)->where('parent')->eq($projectID)->fetch('id');
}
foreach($testcases as $testcase)
{
$testcase->product = $productID;
$testcase->project = $projectID;
$testcase->execution = $executionID;
$testcase->openedBy = $account;
$testcase->openedDate = $now;
$testcase->status = $forceNotReview || $testcase->review == 0 ? 'normal' : 'wait';
$testcase->version = 1;
$testcase->storyVersion = isset($storyVersions[$testcase->story]) ? $storyVersions[$testcase->story] : 0;
$testcase->stepType = array();
if($testcase->story && !isset($storyVersions[$testcase->story]))
{
if(count($storyVersions) == 0) $this->loadModel('story');
$testcase->storyVersion = $this->story->getVersion((int)$testcase->story);
$storyVersions[$testcase->story] = $testcase->storyVersion;
}
unset($testcase->review);
list($testcase->steps, $testcase->stepType) = $this->testcase->processStepsOrExpects($testcase->steps);
list($testcase->expects) = $this->testcase->processStepsOrExpects($testcase->expects);
}
return $testcases;
}
/**
* 构建批量编辑用例的数据。
* Build cases for batch editing.
*
* @param array $oldCases
* @access protected
* @return array
*/
protected function buildCasesForBathcEdit(array $oldCases, array $oldSteps): array
{
$caseIdList = array_keys($oldCases);
if(empty($caseIdList)) return array();
$now = helper::now();
$account = $this->app->user->account;
$cases = form::batchData($this->config->testcase->form->batchEdit)->get();
$forceNotReview = $this->testcase->forceNotReview();
foreach($cases as $caseID => $case)
{
$oldCase = $oldCases[$caseID];
$case->id = $caseID;
$case->product = $oldCase->product;
$case->lastEditedBy = $account;
$case->lastEditedDate = $now;
if(!isset($case->precondition)) $case->precondition = $oldCase->precondition;
$versionChanged = false;
if($case->title && $case->title != $oldCase->title) $versionChanged = true;
if($case->precondition && $case->precondition != $oldCase->precondition) $versionChanged = true;
list($case->steps, $case->stepType) = $this->testcase->processStepsOrExpects($case->steps);
list($case->expects) = $this->testcase->processStepsOrExpects($case->expects);
$oldStep = zget($oldSteps, $caseID, array());
$stepChanged = $this->testcase->processStepsChanged($case, $oldStep);
if($stepChanged && !$forceNotReview) $case->status = 'wait';
if($stepChanged) $versionChanged = true;
$case->version = $versionChanged ? (int)$oldCase->version + 1 : (int)$oldCase->version;
$case->stepChanged = $stepChanged;
}
return $cases;
}
/**
* 构建导入用例的数据。
* Build cases for showing imported.
*
* @param int $productID
* @access protected
* @return array
*/
protected function buildCasesForShowImport(int $productID): array
{
/* 初始化变量。 */
/* Initialize variables. */
$now = helper::now();
$account = $this->app->user->account;
$cases = form::batchData($this->config->testcase->form->showImport)->get();
$forceNotReview = $this->testcase->forceNotReview();
$insert = $this->post->insert;
$caseIdList = !$insert ? array_keys($this->post->title) : array();
$oldCases = $this->testcase->getByList($caseIdList);
$oldSteps = $this->testcase->fetchStepsByList($caseIdList);
$storyVersionPairs = $this->loadModel('story')->getVersions($this->post->story);
foreach($cases as $case)
{
list($case->steps, $case->stepType) = $this->testcase->processStepsOrExpects($case->steps);
list($case->expects) = $this->testcase->processStepsOrExpects($case->expects);
/* 构建更新的用例. */
/* Build updated case. */
if(!empty($case->rawID) && !$insert)
{
$oldCase = zget($oldCases, $case->rawID, new stdclass());
$oldStep = zget($oldSteps, $case->rawID, array());
$stepChanged = $this->testcase->processStepsChanged($case, $oldStep);
$case->id = $case->rawID;
$case->product = $productID;
$case->lastEditedBy = $account;
$case->lastEditedDate = $now;
$case->version = $stepChanged ? $oldCase->version + 1 : $oldCase->version;
if($case->story != $oldCase->story) $case->storyVersion = zget($storyVersionPairs, $case->story, 1);
$changes = common::createChanges($oldCase, $case);
if(!$changes && !$stepChanged) unset($cases[$case->rawID]);
}
/* 构建插入的用例. */
/* Build inserted case. */
else
{
$case->product = $productID;
$case->version = 1;
$case->openedBy = $this->app->user->account;
$case->openedDate = $now;
$case->status = !$forceNotReview ? 'wait' : 'normal';
if($this->app->tab == 'project') $case->project = $this->session->project;
if($case->story) $case->storyVersion = zget($storyVersionPairs, $case->story, 1);
}
$case->frequency = 1;
unset($case->rawID);
}
return $cases;
}
/**
* 根据 xmind 构建导入用例的数据。
* Build imported cases by xmind data.
*
* @param int $productID
* @param string $branch
* @param array $caseList
* @param bool $isInsert
* @access protected
* @return array
*/
protected function buildCasesByXmind(int $productID, string $branch, array $caseList, bool $isInsert): array
{
$caseIdList = array_filter(array_map(function($case){return zget($case, 'id', 0);}, $caseList));
$now = helper::now();
$forceNotReview = $this->testcase->forceNotReview();
$account = $this->app->user->account;
$oldCases = $this->testcase->getByList($caseIdList);
$oldSteps = $this->testcase->fetchStepsByList($caseIdList);
$branch = (int)$branch;
$modules = $this->loadModel('tree')->getOptionMenu($productID, 'case');
$cases = array();
foreach($caseList as $caseData)
{
$moduleID = (int)$caseData['module'];
$case = new stdclass();
$case->module = isset($modules[$moduleID]) ? $moduleID : 0;
$case->product = $productID;
$case->branch = $branch;
$case->title = $caseData['name'];
$case->pri = $caseData['pri'];
$case->precondition = $caseData['precondition'];
$case->tmpPId = $caseData['tmpPId'];
$case->version = 1;
$case = $this->testcase->processCaseSteps($case, (object)$caseData);
$caseID = (int)zget($caseData, 'id', 0);
$oldCase = zget($oldCases, $caseID, null);
if(empty($oldCase) || $isInsert)
{
$case->type = 'feature';
$case->status = !$forceNotReview ? 'wait' : 'normal';
$case->openedBy = $account;
$case->openedDate = $now;
}
else
{
$oldStep = zget($oldSteps, $caseID, array());
$stepChanged = $this->testcase->processStepsChanged($case, $oldStep);
$case->version = $stepChanged ? (int)$oldCase->version + 1 : (int)$oldCase->version;
$case->stepChanged = $stepChanged;
if($stepChanged && !$forceNotReview) $case->status = 'wait';
$case->id = $caseID;
$case->lastEditedBy = $account;
$case->lastEditedDate = $now;
}
$cases[] = $case;
}
return $cases;
}
/**
* 构建从用例库导入的数据。
* Build data for importing from lib.
*
* @param int $productID
* @param string $branch
* @param int $libID
* @access protected
* @return array
*/
protected function buildDataForImportFromLib(int $productID, string $branch, int $libID): array
{
/* 处理模块。 */
/* Process modules. */
$preModule = 0;
$modules = $this->post->module ? $this->post->module : array();
foreach($modules as $caseID => $module)
{
if($module != 'ditto') $preModule = $module;
if($module == 'ditto') $modules[$caseID] = $preModule;
}
/* 处理分支。 */
/* Process branches. */
$preBranch = 0;
$caseModules = array();
$branches = $this->post->branch;
$caseIdList = $this->post->caseIdList;
if(!empty($branches))
{
foreach($branches as $caseID => $branch)
{
if($branch != 'ditto') $prevBranch = $branch;
if($branch == 'ditto') $branches[$caseID] = $prevBranch;
if(!isset($caseModules[$branch]) && in_array($caseID, $caseIdList) !== false) $caseModules[$branch] = $this->testcase->getCanImportedModules($productID, $libID, $branch, 'pairs', $caseIdList);
}
}
else
{
$caseModules[$branch] = $this->testcase->getCanImportedModules($productID, $libID, $branch, 'pairs', $caseIdList);
}
/* 构建用例。 */
/* Build cases. */
$hasImported = '';
$cases = array();
$steps = array();
$files = array();
$fromCases = $this->testcase->getByList($caseIdList);
$fromSteps = $this->testcase->getRelatedSteps($caseIdList);
$fromFiles = $this->testcase->getRelatedFiles($caseIdList);
foreach($caseIdList as $caseID)
{
if(!isset($fromCases[$caseID])) continue;
$case = clone $fromCases[$caseID];
$case->lib = 0;
$case->steps = array();
$case->expects = array();
$case->stepType = array();
$case->product = $productID;
$case->fromCaseID = $case->id;
$case->fromCaseVersion = $case->version;
if(isset($modules[$case->id])) $case->module = $modules[$case->id];
if(isset($branches[$case->id])) $case->branch = $branches[$case->id];
unset($case->id);
if(!$case->scriptedDate) unset($case->scriptedDate);
if(!$case->reviewedDate) unset($case->reviewedDate);
if(!$case->lastEditedDate) unset($case->lastEditedDate);
if(!$case->lastRunDate) unset($case->lastRunDate);
$caseBranch = zget($case, 'branch', $branch);
if(empty($caseModules[$caseBranch][$case->fromCaseID][$case->module]))
{
$hasImported .= "$case->fromCaseID,";
}
else
{
$cases[$caseID] = $case;
$steps[$caseID] = zget($fromSteps, $caseID, array());
$files[$caseID] = zget($fromFiles, $caseID, array());
foreach($files[$caseID] as $file) $file->oldpathname = $file->pathname;
}
}
return array($cases, $steps, $files, $hasImported);
}
/**
* 构建导入到用例库的数据。
* Build data for import to lib.
*
* @param int $caseID
* @param int $libID
* @access protected
* @return array
*/
protected function buildDataForImportToLib(int $caseID, int $libID): array
{
$cases = array();
$steps = array();
$files = array();
$caseIdList = !empty($caseID) ? $caseID : $this->post->caseIdList;
$caseIdList = str_replace('case_' , '', (string)$caseIdList);
$caseIdList = explode(',' , $caseIdList);
$fromCases = $this->testcase->getByList($caseIdList);
$fromSteps = $this->testcase->fetchStepsByList($caseIdList);
$fromFiles = $this->testcase->getRelatedFiles($caseIdList);
$libCases = $this->dao->select('*')->from(TABLE_CASE)->where('lib')->eq($libID)->andWhere('product')->eq(0)->andWhere('deleted')->eq('0')->fetchGroup('fromCaseID', 'id');
$maxOrder = $this->dao->select('max(`order`) as maxOrder')->from(TABLE_CASE)->where('deleted')->eq(0)->fetch('maxOrder');
$maxModuleOrder = $this->dao->select('max(`order`) as maxOrder')->from(TABLE_MODULE)->where('deleted')->eq(0)->fetch('maxOrder');
foreach($fromCases as $caseID => $case)
{
/* 初始化用例。 */
/* Init case. */
$libCase = $this->initLibCase($case, $libID, ++ $maxOrder, $maxModuleOrder, $libCases);
/* 设置用例步骤。 */
/* Set case steps. */
$steps[$caseID] = array();
if(!isset($fromSteps[$caseID])) $fromSteps[$caseID] = array();
foreach($fromSteps[$caseID] as $step)
{
$stepID = $step->id;
$step->version = zget($libCase, 'version', 1);
unset($step->id);
$steps[$caseID][$stepID] = $step;
}
/* 设置用例文件。 */
/* Set case files. */
$files[$caseID] = array();
if(!isset($fromFiles[$caseID])) $fromFiles[$caseID] = array();
foreach($fromFiles[$caseID] as $file)
{
$file->oldpathname = $file->pathname;
$files[$caseID][$file->id] = $file;
}
$cases[$caseID] = $libCase;
}
return array($cases, $steps, $files);
}
/**
* 为展示导入用例构建更新的用例。
* Build update case for showing import.
*
* @param object $case
* @param object $oldCase
* @param array $oldStep
* @param bool $forceNotReview
* @access protected
* @return bool
*/
protected function buildUpdateCaseForShowImport(object $case, object $oldCase, array $oldStep, bool $forceNotReview): bool
{
$stepChanged = (count($oldStep) != count($case->desc));
if(!$stepChanged)
{
$desc = array_values($case->desc);
$expect = array_values($case->expect);
$stepType = array_values($case->stepType);
foreach($oldStep as $index => $step)
{
if(!isset($desc[$index]) || !isset($expect[$index]) || $step->desc != $desc[$index] || $step->expect != $expect[$index] || $step->type != $stepType[$index])
{
$stepChanged = true;
break;
}
}
}
$case->version = $stepChanged ? (int)$oldCase->version + 1 : (int)$oldCase->version;
$case->stepChanged = $stepChanged;
if($stepChanged && !$forceNotReview) $case->status = 'wait';
return $stepChanged;
}
/**
* 创建测试用例前检验表单数据是否正确。
* check from data for create case.
*
* @param object $case
* @access protected
* @return bool
*/
protected function checkCreateFormData(object $case): bool
{
$steps = $case->steps;
$expects = $case->expects;
foreach($expects as $key => $value)
{
if(!empty($value) && empty($steps[$key])) dao::$errors[] = sprintf($this->lang->testcase->stepsEmpty, $key);
}
foreach(explode(',', $this->config->testcase->create->requiredFields) as $field)
{
$field = trim($field);
if($field && empty($case->{$field}))
{
$fieldName = $this->config->testcase->form->create[$field]['type'] != 'array' ? "{$field}" : "{$field}[]";
dao::$errors[$fieldName] = sprintf($this->lang->error->notempty, $this->lang->testcase->{$field});
}
}
if(dao::isError()) return false;
return true;
}
/**
* 批量创建测试用例前检验数据是否正确。
* Check testcases for batch creating.
*
* @param array $testcases
* @access protected
* @return array
*/
protected function checkTestcasesForBatchCreate(array $testcases): array
{
$this->loadModel('common');
$requiredErrors = array();
foreach($testcases as $i => $testcase)
{
/* 检验必填项。 */
/* Check reuqired. */
foreach(explode(',', $this->config->testcase->create->requiredFields) as $field)
{
$field = trim($field);
if($field && empty($testcases[$i]->{$field}))
{
$fieldName = $this->config->testcase->form->batchCreate[$field]['type'] != 'array' ? "{$field}[{$i}]" : "{$field}[{$i}][]";
$requiredErrors[$fieldName] = sprintf($this->lang->error->notempty, $this->lang->testcase->{$field});
}
}
}
if(!empty($requiredErrors)) dao::$errors = $requiredErrors;
return $testcases;
}
/**
* 批量创建测试用例前检验数据是否正确。
* Check testcases for batch editing.
*
* @param array $cases
* @access protected
* @return array
*/
protected function checkCasesForBatchEdit(array $cases): array
{
$this->loadModel('common');
$requiredErrors = array();
foreach($cases as $i => $testcase)
{
/* 检验必填项。 */
/* Check reuqired. */
foreach(explode(',', $this->config->testcase->edit->requiredFields) as $field)
{
$field = trim($field);
if($field && empty($cases[$i]->{$field}))
{
$fieldName = $this->config->testcase->form->batchEdit[$field]['type'] != 'array' ? "{$field}[{$i}]" : "{$field}[{$i}][]";
$requiredErrors[$fieldName] = sprintf($this->lang->error->notempty, $this->lang->testcase->{$field});
}
}
}
if(!empty($requiredErrors)) dao::$errors = $requiredErrors;
return $cases;
}
/**
* 导入测试用例前检验数据是否正确。
* Check testcases for importing.
*
* @param array $cases
* @access protected
* @return array
*/
protected function checkCasesForShowImport(array $cases): array
{
if($this->config->edition != 'open')
{
$extendFields = $this->testcase->getFlowExtendFields();
$notEmptyRule = $this->loadModel('workflowrule')->getByTypeAndRule('system', 'notempty');
foreach($extendFields as $extendField)
{
if(strpos(",$extendField->rules,", ",$notEmptyRule->id,") !== false)
{
$this->config->testcase->create->requiredFields .= ',' . $extendField->field;
}
}
}
foreach($cases as $i => $case)
{
/* 检查期望对应的步骤是否填写。 */
/* Check the step which has expect. */
foreach($case->expects as $exportID => $value)
{
if(!empty($value) && empty($case->steps[$exportID]))
{
$caseErrors["steps[{$i}]"][] = sprintf($this->lang->testcase->stepsEmpty, $exportID);
}
}
/* 检验必填项。 */
/* Check reuqired. */
foreach(explode(',', $this->config->testcase->edit->requiredFields) as $field)
{
$field = trim($field);
if($field && empty($case->{$field}))
{
$fieldName = $this->config->testcase->form->showImport[$field]['type'] != 'array' ? "{$field}[{$i}]" : "{$field}[{$i}][]";
$caseErrors[$fieldName][] = sprintf($this->lang->error->notempty, $this->lang->testcase->{$field});
}
}
}
if(!empty($caseErrors)) dao::$errors = $caseErrors;
return $cases;
}
/**
* 检查 mind 配置。
* Build mind config.
*
* @access protected
* @return array|bool
*/
protected function buildMindConfig(string $type): array|bool
{
$configList = array();
$module = $this->post->module;
if(!empty($module)) $configList[] = array('key' => 'module', 'value' => $module);
$scene = $this->post->scene;
if(!empty($scene)) $configList[] = array('key' => 'scene', 'value' => $scene);
$case = $this->post->case;
if(!empty($case)) $configList[] = array('key' => 'case', 'value' => $case);
$precondition = $this->post->precondition;
if(!empty($precondition)) $configList[] = array('key' => 'precondition', 'value' => $precondition);
$pri = $this->post->pri;
if(!empty($pri)) $configList[] = array('key' => 'pri', 'value' => $pri);
$group = $this->post->group;
if(!empty($group)) $configList[] = array('key' => 'group', 'value' => $group);
$configErrors = array();
foreach($configList as $config)
{
$key = $config['key'];
$value = $config['value'];
if(!preg_match("/^[a-zA-Z]{1,10}$/", $value)) $configErrors[$key][] = sprintf($this->lang->testcase->errorMindConfig, $this->lang->testcase->{$key});
}
if(!empty($configErrors)) dao::$errors = $configErrors;
$map = array();
$map[strtolower($module)] = true;
$map[strtolower($scene)] = true;
$map[strtolower($case)] = true;
$map[strtolower($precondition)] = true;
$map[strtolower($pri)] = true;
$map[strtolower($group)] = true;
if(count($map) < 5 && count($map) > 0) dao::$errors['message'][] = '特征字符串不能重复';
return !dao::isError() ? $configList : false;
}
/**
* 初始化用例数据。
* Initialize the testcase.
*
* @param int $storyID
* @param int $testcaseID
* @param int $bugID
* @access protected
* @return object
*/
protected function initTestcase(int $storyID, int $testcaseID, int $bugID): object
{
/* 初始化用例。 */
/* Initialize the testcase. */
$case = new stdclass();
$case->type = 'feature';
$case->pri = 3;
$case->scene = 0;
$case->story = $storyID;
$case->stage = $case->title = $case->precondition = $case->keywords = $case->color = $case->auto = $case->script = '';
$case->steps = array();
/* 如果用例 id 大于 0,使用这个用例数据作为模板。 */
/* If testcaseID large than 0, use this testcase as template. */
if($testcaseID > 0)
{
$testcase = $this->testcase->getById($testcaseID);
$case->product = $testcase->product;
$case->type = $testcase->type ? $testcase->type : 'feature';
$case->stage = $testcase->stage;
$case->pri = $testcase->pri;
$case->scene = $testcase->scene;
$case->story = $testcase->story;
$case->title = $testcase->title;
$case->precondition = $testcase->precondition;
$case->keywords = $testcase->keywords;
$case->steps = $testcase->steps;
$case->color = $testcase->color;
$case->auto = $testcase->auto;
$case->script = $testcase->script;
$case->files = $testcase->files;
foreach($case->files as $file)
{
$file->name = $file->title;
$file->url = $this->createLink('file', 'download', "fileID={$file->id}");
}
if($this->config->edition != 'open')
{
$extendFields = $this->loadModel('flow')->getExtendFields('testcase', 'create');
foreach($extendFields as $field) $case->{$field->field} = $testcase->{$field->field};
}
}
/* 如果 bug id 大于 0,使用这个 bug 数据作为模板。 */
/* If bugID large than 0, use this bug as template. */
if($bugID > 0)
{
$bug = $this->loadModel('bug')->getById($bugID);
$case->type = 'feature';
$case->pri = $bug->pri ? $bug->pri : $bug->severity;
$case->story = $bug->story;
$case->title = $bug->title;
$case->keywords = $bug->keywords;
$case->steps = $this->testcase->createStepsFromBug($bug->steps);
}
/* 追加步骤到默认的步骤数。 */
/* Append the steps to the default steps count. */
$case->steps = $this->testcase->appendSteps(!empty($case->steps) ? $case->steps : array());
return $case;
}
/**
* 初始化要导入到用例库的用例
* Init case to import to lib.
*
* @param object $case
* @param int $libID
* @param int $maxOrder
* @param int $maxModuleOrder
* @param array $libCases
* @access protected
* @return object
*/
protected function initLibCase(object $case, int $libID, int $maxOrder, int $maxModuleOrder, array $libCases): object
{
$libCase = new stdclass();
$libCase->lib = $libID;
$libCase->title = $case->title;
$libCase->precondition = $case->precondition;
$libCase->keywords = $case->keywords;
$libCase->pri = $case->pri;
$libCase->type = $case->type;
$libCase->stage = $case->stage;
$libCase->status = $case->status;
$libCase->fromCaseID = $case->id;
$libCase->fromCaseVersion = $case->version;
$libCase->color = $case->color;
$libCase->order = $maxOrder;
$libCase->module = empty($case->module) ? 0 : $this->testcase->importCaseRelatedModules($libID, $case->module, $maxModuleOrder);
if(!isset($libCases[$case->id]))
{
$libCase->openedBy = $this->app->user->account;
$libCase->openedDate = helper::now();
}
else
{
$libCaseList = array_keys($libCases[$case->id]);
$libCaseID = $libCaseList[0];
$libCase->id = $libCaseID;
$libCase->lastEditedBy = $this->app->user->account;
$libCase->lastEditedDate = helper::now();
$libCase->version = (int)$libCases[$case->id][$libCaseID]->version + 1;
}
return $libCase;
}
/**
* 导入用例。
* Import cases.
*
* @param array $cases
* @access protected
* @return array
*/
protected function importCases(array $cases): array
{
$this->loadModel('action');
$daoErrors = array();
foreach($cases as $case)
{
if(isset($case->id))
{
$oldCase = $this->testcase->getByID($case->id);
if($oldCase->product != $case->product) continue;
$changes = $this->testcase->update($case, $oldCase);
$actionID = $this->action->create('case', $case->id, 'Edited');
$this->action->logHistory($actionID, $changes);
$this->testcase->updateCase2Project($oldCase, $case);
}
else
{
$caseID = $this->testcase->create($case);
$case->id = $caseID;
dao::isError() ? $daoErrors = array_merge($daoErrors, dao::getError()) : $this->testcase->syncCase2Project($case, $caseID);
}
}
if(!empty($daoErrors)) dao::$errors = $daoErrors;
return $cases;
}
/**
* 处理xmind数据
* Process scene data.
*
* @param array $result
* @access protected
* @return array
*/
protected function processScene(array $result): array
{
$scenes['id'] = $result['id'];
$scenes['text'] = $result['title'];
$scenes['type'] = 'root';
if(!empty($result['children'] && !empty($result['children']['attached']))) $scenes['children'] = $this->processChildScene($result['children']['attached'], $result['id'], 'sub');
if(!empty($result['children'] && !empty($result['children']['topics']['topic']))) $scenes['children'] = $this->processChildScene($result['children']['topics']['topic'], $result['id'], 'sub');
return $scenes;
}
/**
* 处理xmind的节点数据
* process scene child data.
*
* @param array $results
* @param string $parent
* @param string $type
* @access protected
* @return void
*/
protected function processChildScene(array $results, string $parent, string $type)
{
$scenes = array();
foreach($results as $result)
{
if(!isset($result['id'])) continue;
$scene['id'] = $result['id'];
$scene['text'] = $result['title'];
$scene['parent'] = $parent;
$scene['type'] = $type;
if(!empty($result['children']) && !empty($result['children']['attached']))
{
$scene['children'] = $this->processChildScene($result['children']['attached'], $result['id'], 'node');
}
elseif(!empty($result['children']) && !empty($result['children']['topics']))
{
if(!isset($result['children']['topics']['type']) || empty($result['children']['topics']['topic'])) continue;
$topic = $result['children']['topics']['topic'];
if(isset($topic['id']))
{
$sceneChild = array();
$sceneChild['id'] = $topic['id'];
$sceneChild['text'] = $topic['title'];
$sceneChild['parent'] = $result['id'];
$sceneChild['type'] = 'node';
$grandChild = array();
if(!empty($topic['children']) && !empty($topic['children']['topics']['topic'])) $grandChild = $topic['children']['topics']['topic'];
if(isset($topic['nodeType'])) $grandChild = $topic;
if(isset($grandChild['id'])) $grandChild = array($grandChild);
$sceneChild['children'] = $this->processChildScene($grandChild, $topic['id'], 'node');
$scene['children'][] = $sceneChild;
}
else
{
$scene['children'] = $this->processChildScene($topic, $result['id'], 'node');
}
}
$scenes[] = $scene;
}
return $scenes;
}
/**
* 为展示脑图计算步骤数据。
* Process steps for mindmap.
*
* @param object $case
* @access protected
* @return object
*/
protected function processStepsForMindMap(object $case): object
{
$mindMapSteps = array();
$mindMapSteps['id'] = 'case_' . $case->id;
$mindMapSteps['text'] = $case->title;
$mindMapSteps['type'] = 'root';
$stepItem['subSide'] = 'right';
$reverseSteps = array_reverse($case->steps);
$stepList = array();
$parentSteps = array();
foreach($reverseSteps as $step)
{
if(empty($step->id)) continue;
if($step->type != 'group')
{
$descItem = array();
$descItem['id'] = 'desc_' . $step->id;
$descItem['text'] = $step->expect;
$descItem['type'] = 'node';
$descItem['parent'] = $step->id;
$descItem['subSide'] = 'right';
$parentSteps[$step->id][] = $descItem;
}
$stepItem = array();
$stepItem['id'] = $step->id;
$stepItem['text'] = $step->step;
$stepItem['type'] = $step->grade == 1 ? 'sub' : 'node';
$stepItem['parent'] = $step->parent > 0 ? $step->parent : 'case_' . $case->id;
$stepItem['subSide'] = 'right';
if(isset($parentSteps[$step->id])) $stepItem['children'] = array_reverse($parentSteps[$step->id]);
if($step->parent > 0)
{
$parentSteps[$step->parent][] = $stepItem;
}
else
{
$stepList[] = $stepItem;
}
}
$mindMapSteps['children'] = array_reverse($stepList);
$case->mindMapSteps = $mindMapSteps;
return $case;
}
/**
* 计算导入列的键值。
* Process import column key.
*
* @param string $fileName
* @param array $fields
* @access protected
* @return void
*/
protected function processImportColumnKey(string $fileName, array $fields): array
{
/* 获取文件内的行,设置头。 */
/* Get rows and set header. */
$rows = $this->loadModel('file')->parseCSV($fileName);
$header = array();
foreach($rows[0] as $i => $rowValue)
{
if(empty($rowValue)) break;
$header[$i] = $rowValue;
}
unset($rows[0]);
/* 设置列的键值。 */
/* Set column key. */
$columnKey = array();
foreach($header as $title)
{
if(isset($fields[$title])) $columnKey[] = $fields[$title];
}
return $columnKey;
}
/**
* 获取分组用例。
* Get group cases.
*
* @param int $productID
* @param string $branch
* @param string $groupBy
* @param string $caseType
* @param string $browseType
* @access protected
* @return array
*/
protected function getGroupCases(int $productID, string $branch, string $groupBy, string $caseType, string $browseType = ''): array
{
/* 获取用例。 */
/* Get cases. */
$cases = $this->testcase->getModuleCases($productID, $branch, 0, $browseType, 'no', $caseType, $groupBy);
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false);
$cases = $this->loadModel('story')->checkNeedConfirm($cases);
$cases = $this->testcase->appendData($cases);
foreach($cases as $case) $case->caseID = $case->id;
/* 获取用例的需求分组。 */
/* Get story groups of cases. */
$groupCases = array();
if($groupBy == 'story')
{
foreach($cases as $case) $groupCases[$case->story][] = $case;
}
/* 设置用例的需求分组的行占比。 */
/* Set row span of story group in cases. */
$story = null;
foreach($cases as $index => $case)
{
if($case->storyDeleted)
{
unset($cases[$index]);
continue;
}
$case->rowspan = 0;
if($story !== $case->story)
{
$story = $case->story;
if(!empty($groupCases[$case->story])) $case->rowspan = count($groupCases[$case->story]);
}
}
return $cases;
}
/**
* 创建完 testcase 后的相关处理。
* Relevant processing after create testcase.
*
* @param object $case
* @param int $caseID
* @access protected
* @return void
*/
protected function afterCreate(object $case, int $caseID)
{
/* 设置 cookie。 */
/* Set cookie. */
helper::setcookie('lastCaseModule', (string)$case->module);
helper::setcookie('lastCaseProduct', (string)$case->product);
helper::setcookie('lastCaseScene', (string)$case->scene);
helper::setcookie('caseModule', '0');
if(!empty($_POST['fileList']))
{
$fileList = $this->post->fileList;
if($fileList) $fileList = json_decode($fileList, true);
$this->loadModel('file')->saveDefaultFiles($fileList, 'testcase', $caseID);
}
/* 如果需求关联到项目,把用例也关联到项目。 */
/* If the story is linked project, make the case link the project. */
$this->testcase->syncCase2Project($case, $caseID);
}
/**
* 返回创建 testcase 的结果。
* Respond after creating testcase.
*
* @param int $caseID
* @access protected
* @return array|int
*/
protected function responseAfterCreate(int $caseID, int $moduleID = 0): array|int
{
$message = $this->executeHooks($caseID);
if(!$message) $message = $this->lang->saveSuccess;
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message, 'id' => $caseID));
/* If link from no head then reload. */
if(isInModal() || helper::isAjaxRequest('modal')) return $this->send(array('result' => 'success', 'message' => $message, 'load' => true, 'closeModal' => true));
/* 判断是否当前一级菜单不是 QA,并且 caseList session 存在,并且 caseList 不是动态页面。 */
/* Use this session link, when the tab is not QA, a session of the case list exists, and the session is not from the Dynamic page. */
$useSession = ($this->app->tab != 'qa' and $this->session->caseList and strpos($this->session->caseList, 'dynamic') === false);
if($this->app->tab == 'project')
{
$locateLink = $this->createLink('project', 'testcase', "projectID={$this->session->project}");
}
else
{
$params = "productID={$this->post->product}&branch={$this->post->branch}";
if($moduleID) $params .= "&browseType=byModule¶m=$moduleID";
$locateLink = $this->createLink('testcase', 'browse', $params);
}
return $this->send(array('result' => 'success', 'message' => $message, 'load' => $useSession ? $this->session->caseList : $locateLink));
}
/**
* 返回批量创建 testcase 的结果。
* Respond after batch creating testcase.
*
* @param int $productID
* @param string|int $branch
* @access protected
* @return void
*/
protected function responseAfterBatchCreate(int $productID, string|int $branch): array
{
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
if(helper::isAjaxRequest('modal')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => true));
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $caseIdList));
helper::setcookie('caseModule', '0');
$currentModule = $this->app->tab == 'qa' ? 'testcase' : $this->app->tab;
$currentMethod = $this->app->tab == 'qa' ? 'browse' : 'testcase';
$projectParam = $this->app->tab == 'qa' ? '' : "{$this->app->tab}ID=" . zget($_SESSION, $this->app->tab, 0) . '&';
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $this->createLink($currentModule, $currentMethod, "{$projectParam}productID={$productID}&branch={$branch}")));
}
/**
* 返回展示导入 testcase 的结果。
* Respond after showing imported testcase.
*
* @param int $productID
* @param string $branch
* @param int $maxImport
* @param string $tmpFile
* @param string $message
* @access protected
* @return void
*/
protected function responseAfterShowImport(int $productID, string $branch = '0', int $maxImport = 0, string $tmpFile = '', string $message = ''): array
{
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
if($this->post->isEndPage)
{
if(!empty($_SESSION['fileImport']) && file_exists($_SESSION['fileImport'])) unlink($tmpFile);
if(!empty($_SESSION['fileImport'])) unset($_SESSION['fileImport']);
$locateLink = $this->app->tab == 'project' ? $this->createLink('project', 'testcase', "projectID={$this->session->project}&productID={$productID}") : inlink('browse', "productID={$productID}");
}
else
{
$locateLink = inlink('showImport', "productID={$productID}&branch={$branch}&pagerID=" . ((int)$this->post->pagerID + 1) . "&maxImport={$maxImport}&insert=" . zget($_POST, 'insert', ''));
}
return $this->send(array('result' => 'success', 'message' => $message ? $message : $this->lang->saveSuccess, 'load' => $locateLink));
}
/**
* 处理评审数据。
* Prepare review data.
*
* @param int $caseID
* @param object $oldCase
* @access protected
* @return bool|object
*/
protected function prepareReviewData(int $caseID, object $oldCase): bool|object
{
$now = helper::now();
$status = $this->getStatusForReview($oldCase);
$case = form::data($this->config->testcase->form->review, $caseID)->add('id', $caseID)
->setForce('status', $status)
->setDefault('reviewedDate', substr($now, 0, 10))
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', $now)
->join('reviewedBy', ',')
->stripTags($this->config->testcase->editor->review['id'], $this->config->allowedTags)
->get();
if(!$case->result)
{
dao::$errors['result'] = $this->lang->testcase->mustChooseResult;
return false;
}
return $this->loadModel('file')->processImgURL($case, $this->config->testcase->editor->review['id'], $this->post->uid);
}
/**
* 构建关联用例页面的搜索表单。
* Build search form for link cases.
*
* @param object $case
* @param int $queryID
* @access protected
* @return void
*/
protected function buildLinkCasesSearchForm(object $case, int $queryID): void
{
$actionURL = $this->createLink('testcase', 'linkCases', "caseID={$case->id}&browseType=bySearch&queryID=myQueryID", '', true);
$objectID = 0;
if($this->app->tab == 'project') $objectID = $case->project;
if($this->app->tab == 'execution') $objectID = $case->execution;
unset($this->config->testcase->search['fields']['product']);
$this->testcase->buildSearchForm($case->product, $this->products, $queryID, $actionURL, $objectID);
}
/**
* 构建关联 bug 页面的搜索表单。
* Build search form for link bugs.
*
* @param object $case
* @param int $queryID
* @access protected
* @return void
*/
protected function buildLinkBugsSearchForm(object $case, int $queryID): void
{
$actionURL = $this->createLink('testcase', 'linkBugs', "caseID={$case->id}&browseType=bySearch&queryID=myQueryID", '', true);
$objectID = 0;
if($this->app->tab == 'project') $objectID = $case->project;
if($this->app->tab == 'execution') $objectID = $case->execution;
/* 删除单一项目的计划字段。*/
/* Unset search field 'plan' in single project. */
unset($this->config->bug->search['fields']['product']);
if($case->project && ($this->app->tab == 'project' || $this->app->tab == 'execution'))
{
$project = $this->loadModel('project')->getByID($case->project);
if(!$project->hasProduct && $project->model == 'waterfall') unset($this->config->bug->search['fields']['plan']);
}
$this->loadModel('bug')->buildSearchForm($case->product, $this->products, $queryID, $actionURL, (string)$objectID);
}
/**
* 获取导出的字段列表。
* Get the export fields.
*
* @param string $productType
* @access protected
* @return array
*/
protected function getExportFields(string $productType): array
{
$fields = $this->post->exportFields ? $this->post->exportFields : explode(',', $this->config->testcase->exportFields);
foreach($fields as $key => $fieldName)
{
$fieldName = trim($fieldName);
if($productType != 'normal' || $fieldName != 'branch') $fields[$fieldName] = zget($this->lang->testcase, $fieldName);
unset($fields[$key]);
}
return $fields;
}
/**
* 获取 mind 导出的数据。
* Get export data.
*
* @param int $productID
* @param int $moduleID
* @param string $branch
* @access protected
* @return array
*/
protected function getMindExport(string $type, int $productID, int $moduleID, string $branch): array
{
$caseList = $this->testcase->getCaseListForXmindExport($productID, $moduleID, $branch);
$stepList = $this->testcase->getStepByProductAndModule($productID, $moduleID);
$moduleList = $this->getModuleListForXmindExport($productID, $moduleID, $branch);
$sceneInfo = $this->testcase->getSceneByProductAndModule($productID, $moduleID);
$config = $this->testcase->getMindConfig($type);
return array('caseList' => $caseList, 'stepList' => $stepList, 'sceneMaps' => $sceneInfo['sceneMaps'], 'topScenes' => $sceneInfo['topScenes'], 'moduleList' => $moduleList, 'config' => $config);
}
/**
* 导出xmind格式用例时获取模块列表。
* Get module list for xmind export.
*
* @param int $productID
* @param int $moduleID
* @param string $branch
* @access public
* @return array
*/
private function getModuleListForXmindExport(int $productID, int $moduleID, string $branch): array
{
if($moduleID)
{
$module = $this->loadModel('tree')->getByID($moduleID);
if(!$module) return array();
return array($module->id => $module->name);
}
$moduleList = $this->loadModel('tree')->getOptionMenu($productID, $viewType = 'case', $startModuleID = 0, ($branch === 'all' || !isset($branches[$branch])) ? 'all' : $branch);
unset($moduleList['0']);
return $moduleList;
}
/**
* 获取导入的数据。
* Get imported data.
*
* @param int $productID
* @param string $file
* @access protected
* @return void
*/
protected function getImportedData(int $productID, string $file): array
{
$rows = $this->loadModel('file')->parseCSV($file);
$header = array();
foreach($rows[0] as $i => $rowValue)
{
if(empty($rowValue)) break;
$header[$i] = $rowValue;
}
unset($rows[0]);
$fields = $this->testcase->getImportFields($productID);
$fields = array_flip($fields);
$caseData = array();
$stepVars = 0;
foreach($rows as $row => $data)
{
$case = new stdclass();
foreach($header as $key => $title)
{
if(!isset($fields[$title]) || !isset($data[$key])) continue;
$field = $fields[$title];
$cellValue = $data[$key];
$case = $this->getImportField($field, $cellValue, $case);
}
if(empty($case->title)) continue;
$caseData[$row] = $case;
unset($case);
}
return array(array('caseData' => $caseData), $stepVars);
}
/**
* 获取导入的用例字段。
* Get imported field.
*
* @param string $field
* @param string $cellValue
* @param object $case
* @access protected
* @return object
*/
protected function getImportField(string $field, string $cellValue, object $case): object
{
if($field == 'story' || $field == 'module' || $field == 'branch')
{
$case->{$field} = 0;
if(strrpos($cellValue, '(#') !== false)
{
$id = trim(substr($cellValue, strrpos($cellValue,'(#') + 2), ')');
$case->{$field} = $id;
}
}
elseif($field == 'stepDesc')
{
$case->steps = $cellValue;
}
elseif($field == 'stepExpect')
{
$case->expects = $cellValue;
}
elseif(in_array($field, $this->config->testcase->export->listFields))
{
if($field == 'stage')
{
$stages = explode("\n", $cellValue);
foreach($stages as $stage) $case->stage[] = array_search($stage, $this->lang->testcase->{$field . 'List'});
$case->stage = join(',', $case->stage);
}
else
{
$case->{$field} = array_search($cellValue, $this->lang->testcase->{$field . 'List'});
}
}
else
{
$case->{$field} = $cellValue;
}
return $case;
}
/**
* 获取导入的用例步骤。
* Get imported steps.
*
* @param string $field
* @param array $steps
* @param array $stepData
* @param int $row
* @access protected
* @return array
*/
protected function getImportSteps(string $field, array $steps, array $stepData, int $row): array
{
$caseSteps = array();
foreach($steps as $step)
{
$step = trim($step);
if(empty($step)) continue;
preg_match('/^((([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
if(!$out) preg_match('/^(([0-9]+)[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
if(!$out) preg_match('/^([0-9]+)[.、](.*)$/Uu', $step, $out);
if($out)
{
$count = count($out);
$num = $out[1];
$parent = $count > 4 ? $out[2] : '0';
$grand = $count > 6 ? $out[3] : '0';
$step = trim($out[2]);
if($count > 4) $step = $count > 6 ? trim($out[6]) : trim($out[4]);
$caseSteps[$num]['content'] = $step;
$caseSteps[$num]['number'] = $num;
$caseSteps[$num]['type'] = $count > 4 ? 'item' : 'step';
if(!empty($parent)) $caseSteps[$parent]['type'] = 'group';
if(!empty($grand)) $caseSteps[$grand]['type'] = 'group';
}
elseif(isset($num))
{
$caseSteps[$num]['content'] = isset($caseSteps[$num]['content']) ? "{$caseSteps[$num]['content']}\n{$step}" : "\n{$step}";
}
elseif($field == 'stepDesc')
{
$num = 1;
$caseSteps[$num]['content'] = $step;
$caseSteps[$num]['type'] = 'step';
$caseSteps[$num]['number'] = $num;
}
elseif($field == 'stepExpect' && isset($stepData[$row]['desc']))
{
end($stepData[$row]['desc']);
$num = key($stepData[$row]['desc']);
$caseSteps[$num]['content'] = $step;
$caseSteps[$num]['number'] = $num;
}
}
return $caseSteps;
}
/**
* 处理导出的用例数据。
* Process export cases.
*
* @param array $cases
* @param int $productID
* @param int $taskID
* @access protected
* @return array
*/
protected function processCasesForExport(array $cases, int $productID, int $taskID): array
{
$products = $this->product->getPairs('', 0, '', 'all');
$branches = $this->loadModel('branch')->getPairs($productID);
$users = $this->loadModel('user')->getPairs('noletter');
$results = $this->testcase->getCaseResultsForExport(array_keys($cases), $taskID);
$relatedModules = $this->loadModel('tree')->getAllModulePairs('case');
$relatedStories = $this->testcase->getRelatedStories($cases);
$relatedCases = $this->testcase->getRelatedCases($cases);
$relatedSteps = $this->testcase->getRelatedSteps(array_keys($cases));
$relatedFiles = $this->testcase->getRelatedFiles(array_keys($cases));
$relatedScenes = $this->testcase->getSceneMenu($productID, 0);
if($taskID) $this->app->loadLang('testtask');
$cases = $this->testcase->appendData($cases);
foreach($cases as $case) $this->processCaseForExport($case, $products, $branches, $users, $results, $relatedModules, $relatedStories, $relatedCases, $relatedSteps, $relatedFiles, $relatedScenes);
return $cases;
}
/**
* 处理导出的某个用例。
* Process export case.
*
* @param object $case
* @param array $products
* @param array $branches
* @param array $users
* @param array $results
* @param array $relatedModules
* @param array $relatedStories
* @param array $relatedCases
* @param array $relatedSteps
* @param array $relatedFiles
* @param array $relatedScenes
* @access protected
* @return void
*/
protected function processCaseForExport(object $case, array $products, array $branches, array $users, array $results, array $relatedModules, array $relatedStories, array $relatedCases, array $relatedSteps, array $relatedFiles, array $relatedScenes): void
{
$case->stepDesc = '';
$case->stepExpect = '';
$case->real = '';
$case->openedDate = !helper::isZeroDate($case->openedDate) ? substr($case->openedDate, 0, 10) : '';
$case->lastEditedDate = !helper::isZeroDate($case->lastEditedDate) ? substr($case->lastEditedDate, 0, 10) : '';
$case->lastRunDate = !helper::isZeroDate($case->lastRunDate) ? $case->lastRunDate : '';
$case->product = isset($products[$case->product]) ? $products[$case->product] . "(#$case->product)" : '';
$case->branch = isset($branches[$case->branch]) ? $branches[$case->branch] . "(#$case->branch)" : '';
$case->module = isset($relatedModules[$case->module])? $relatedModules[$case->module] . "(#$case->module)" : '';
$case->story = isset($relatedStories[$case->story]) ? $relatedStories[$case->story] . "(#$case->story)" : '';
$case->scene = isset($relatedScenes[$case->scene]) ? $relatedScenes[$case->scene] . "(#$case->scene)" : '';
$case->pri = zget($this->lang->testcase->priList, $case->pri);
$case->type = zget($this->lang->testcase->typeList, $case->type);
$case->status = $this->processStatus('testcase', $case);
$case->openedBy = zget($users, $case->openedBy);
$case->lastEditedBy = zget($users, $case->lastEditedBy);
$case->lastRunner = zget($users, $case->lastRunner);
$case->lastRunResult = zget($this->lang->testcase->resultList, $case->lastRunResult);
$case->bugsAB = $case->bugs;
$case->resultsAB = $case->results;
$case->stepNumberAB = $case->stepNumber;
unset($case->bugs);
unset($case->results);
unset($case->stepNumber);
unset($case->caseFails);
$this->processStepForExport($case, zget($results, $case->id, array()), $relatedSteps);
$this->processStageForExport($case);
$this->processFileForExport($case, $relatedFiles);
if($case->linkCase) $this->processLinkCaseForExport($case);
}
/**
* 处理导出的用例的步骤。
* Process step of case for export.
*
* @param object $case
* @param array $result
* @param array $relatedSteps
* @access protected
* @return void
*/
protected function processStepForExport(object $case, array $result, array $relatedSteps): void
{
$case->real = '';
if(!empty($result) && !isset($relatedSteps[$case->id]))
{
$firstStep = reset($result);
$case->real = $firstStep['real'];
}
if(!isset($case->stepDesc)) $case->stepDesc = '';
if(!isset($case->stepExpect)) $case->stepExpect = '';
if(!empty($case->id) && !empty($relatedSteps[$case->id]))
{
$preGrade = 1;
$parentSteps = array();
$key = array(0, 0, 0);
foreach($relatedSteps[$case->id] as $step)
{
$grade = 1;
$parentSteps[$step->id] = $step->parent;
if(isset($parentSteps[$step->parent])) $grade = isset($parentSteps[$parentSteps[$step->parent]]) ? 3 : 2;
if($grade > $preGrade)
{
$key[$grade - 1] = 1;
}
else
{
if($grade < $preGrade)
{
if($grade < 2) $key[1] = 0;
if($grade < 3) $key[2] = 0;
}
$key[$grade - 1] ++;
}
$stepID = implode('.', $key);
$stepID = str_replace('.0', '', $stepID);
$stepID = str_replace('.0', '', $stepID);
$sign = (in_array($this->post->fileType, array('html', 'xml'))) ? '
' : "\n";
$case->stepDesc .= $stepID . ". " . htmlspecialchars_decode($step->desc) . $sign;
$case->stepExpect .= $stepID . ". " . htmlspecialchars_decode($step->expect) . $sign;
$case->real .= $stepID . ". " . (isset($result[$step->id]) ? $result[$step->id]['real'] : '') . $sign;
$preGrade = $grade;
}
}
$case->stepDesc = !empty($case->stepDesc) ? trim($case->stepDesc) : '';
$case->stepExpect = !empty($case->stepExpect) ? trim($case->stepExpect) : '';
$case->real = !empty($case->real) ? trim($case->real) : '';
if($this->post->fileType == 'csv')
{
$case->stepDesc = str_replace('"', '""', $case->stepDesc);
$case->stepExpect = str_replace('"', '""', $case->stepExpect);
}
}
/**
* 处理导出的用例的适用阶段。
* Process stage of case for export.
*
* @param object $case
* @access protected
* @return void
*/
protected function processStageForExport(object $case): void
{
$case->stage = explode(',', $case->stage);
foreach($case->stage as $key => $stage) $case->stage[$key] = isset($this->lang->testcase->stageList[$stage]) ? $this->lang->testcase->stageList[$stage] : $stage;
$case->stage = join("\n", $case->stage);
}
/**
* 处理导出用例的相关用例。
* Process link case of the case for export.
*
* @param object $case
* @access protected
* @return void
*/
protected function processLinkCaseForExport(object $case): void
{
$tmpLinkCases = array();
$linkCaseIdList = explode(',', $case->linkCase);
foreach($linkCaseIdList as $linkCaseID)
{
$linkCaseID = trim($linkCaseID);
$tmpLinkCases[] = isset($relatedCases[$linkCaseID]) ? $relatedCases[$linkCaseID] . "(#$linkCaseID)" : $linkCaseID;
}
$case->linkCase = join("; \n", $tmpLinkCases);
}
/**
* 处理导出用例的附件。
* Process file of case for export.
*
* @param object $case
* @param array $relatedFiles
* @access protected
* @return void
*/
protected function processFileForExport(object $case, array $relatedFiles): void
{
$case->files = '';
if(isset($relatedFiles[$case->id]))
{
foreach($relatedFiles[$case->id] as $file)
{
$fileURL = common::getSysURL() . $this->createLink('file', 'download', "fileID={$file->id}");
$case->files .= html::a($fileURL, $file->title, '_blank') . '
';
}
}
}
/**
* 处理批量编辑用例的步骤和预期。
* Process steps and expects for batch edit.
*
* @param array $cases
* @access protected
* @return array
*/
protected function processStepsAndExpectsForBatchEdit(array $cases): array
{
$relatedSteps = $this->testcase->getRelatedSteps(array_column($cases, 'id'));
foreach($cases as $case)
{
$this->processStepForExport($case, array(), $relatedSteps);
$case->steps = $case->stepDesc;
$case->expects = $case->stepExpect;
}
return $cases;
}
/**
* 获取导出模板的字段。
* Get fields for export template.
*
* @param string $productType
* @access protected
* @return array
*/
protected function getFieldsForExportTemplate(string $productType): array
{
$fields = array();
$fields['branch'] = $this->lang->product->branchName[$productType];
$fields['module'] = $this->lang->testcase->module;
$fields['title'] = $this->lang->testcase->title;
$fields['precondition'] = $this->lang->testcase->precondition;
$fields['stepDesc'] = $this->lang->testcase->stepDesc;
$fields['stepExpect'] = $this->lang->testcase->stepExpect;
$fields['keywords'] = $this->lang->testcase->keywords;
$fields['pri'] = $this->lang->testcase->pri;
$fields['type'] = $this->lang->testcase->type;
$fields['stage'] = $this->lang->testcase->stage;
$fields[''] = '';
$fields['typeValue'] = $this->lang->testcase->lblTypeValue;
$fields['stageValue'] = $this->lang->testcase->lblStageValue;
$fields['branchValue'] = $this->lang->product->branchName[$productType];
if($productType == 'normal')
{
unset($fields['branch']);
unset($fields['branchValue']);
}
return $fields;
}
/**
* 获取导出模板的行。
* Get rows for export template.
*
* @param object $product
* @param int $num
* @access protected
* @return array
*/
protected function getRowsForExportTemplate(object $product, int $num): array
{
$this->loadModel('tree');
$projectID = $this->app->tab == 'project' ? $this->session->project : 0;
$branches = $this->loadModel('branch')->getPairs($product->id, '' , $projectID);
$modules = $product->type == 'normal' ? $this->tree->getOptionMenu($product->id, 'case', 0, 'all') : array();
foreach($branches as $branchID => $branchName)
{
$branches[$branchID] = $branchName . "(#$branchID)";
$modules += $this->tree->getOptionMenu($product->id, 'case', 0, (string)$branchID);
}
$rows = array();
for($i = 0; $i < $num; $i++)
{
foreach($modules as $moduleID => $module)
{
$row = new stdclass();
$row->module = $module . "(#$moduleID)";
$row->stepDesc = "1. \n2. \n3.";
$row->stepExpect = "1. \n2. \n3.";
if(empty($rows))
{
unset($this->lang->testcase->typeList['unit']);
$row->typeValue = join("\n", $this->lang->testcase->typeList);
$row->stageValue = join("\n", $this->lang->testcase->stageList);
if($product->type != 'normal') $row->branchValue = join("\n", $branches);
}
$rows[] = $row;
}
}
return $rows;
}
/**
* 获取创建用例时的状态值。
* Get status for create.
*
* @access public
* @return string
*/
public function getStatusForCreate(): string
{
if($this->testcase->forceNotReview() || !$this->post->needReview) return 'normal';
return 'wait';
}
/**
* 获取评审用例时的状态值。
* Get status for review.
*
* @param object $case
* @access private
* @return string
*/
private function getStatusForReview(object $case): string
{
if($this->post->result == 'pass') return 'normal';
return zget($case, 'status', '');
}
/**
* 获取更新的状态。
* Get status for update.
*
* @param object $case
* @access public
* @return bool|array
*/
public function getStatusForUpdate(object $case): bool|array
{
if($this->post->lastEditedDate && $case->lastEditedDate != $this->post->lastEditedDate)
{
dao::$errors['message'][] = $this->lang->error->editedByOther;
return false;
}
/* 判断步骤是否变更。*/
/* Judge steps changed or not. */
$stepChanged = false;
if($this->post->steps)
{
$steps = array();
foreach($this->post->steps as $key => $desc)
{
if(!$desc) continue;
$steps[] = array('desc' => trim($desc), 'type' => trim(zget($this->post->stepType, $key, 'step')), 'expect' => trim(zget($this->post->expects, $key, '')));
}
/* 如果步骤数量发生变化,步骤变更。*/
/* If step count changed, case changed. */
if(count($case->steps) != count($steps))
{
$stepChanged = true;
}
else
{
/* 对比步骤的每一步。*/
/* Compare every step. */
$i = 0;
foreach($case->steps as $key => $oldStep)
{
if(trim($oldStep->desc) != trim($steps[$i]['desc']) || trim($oldStep->expect) != $steps[$i]['expect'] || trim($oldStep->type) != $steps[$i]['type'])
{
$stepChanged = true;
break;
}
$i++;
}
}
}
$status = $this->post->status ? $this->post->status : $case->status;
if(!$this->testcase->forceNotReview() && $stepChanged) $status = 'wait';
if($this->post->title && $case->title != $this->post->title) $stepChanged = true;
if(isset($_POST['precondition']) && $case->precondition != $this->post->precondition) $stepChanged = true;
if(!empty($_FILES['files']['name'][0])) $stepChanged = true;
if(!empty($_POST['deleteFiles'])) $stepChanged = true;
return array($stepChanged, $status);
}
/**
* 创建 freemind 的 xml 文档。
* Create freemind's xml doc.
*
* @param int $productID
* @param string $productName
* @param array $context
* @access protected
* @return object
*/
protected function createFreeMindXmlDoc(int $productID, string $productName, array $context): object
{
$this->classFreeMind = $this->app->loadClass('freemind');
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
$xmlDoc->formatOutput = true;
$versionAttr = $xmlDoc->createAttribute('version');
$versionAttr->value = '1.0.1';
$textAttr = $xmlDoc->createAttribute('TEXT');
$textAttr->value = $this->classFreeMind->toText("$productName", $productID);
$mapNode = $xmlDoc->createElement('map');
$mapNode->appendChild($versionAttr);
$productNode = $xmlDoc->createElement('node');
$productNode->appendChild($textAttr);
$mapNode->appendChild($productNode);
$xmlDoc->appendChild($mapNode);
$sceneNodes = array();
$moduleNodes = array();
$this->classFreeMind->createModuleNode($xmlDoc, $context, $productNode, $moduleNodes);
$this->classFreeMind->createSceneNode($xmlDoc, $context, $productNode, $moduleNodes, $sceneNodes);
$this->classFreeMind->createTestcaseNode($xmlDoc, $context, $productNode, $moduleNodes, $sceneNodes);
return $xmlDoc;
}
/**
* 获取 xmind 导出的数据。
* Get export data of xmind.
*
* @param int $productID
* @param string $productName
* @param array $context
* @access protected
* @return string
*/
protected function getXmindExportData(int $productID, string $productName, array $context): string
{
$this->classXmind = $this->app->loadClass('xmind');
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
$xmlDoc->formatOutput = true;
$titleAttr = $xmlDoc->createElement('title', $this->classXmind->toText("$productName", $productID));
$idAttr = $xmlDoc->createElement('id', $this->classXmind->toText(uniqid(), ''));
$productChildren = $xmlDoc->createElement('children');
$productTopics = $this->classXmind->createTopics($xmlDoc);
$productChildren->appendChild($productTopics);
$class = $xmlDoc->createAttribute('structure-class');
$classValue = $xmlDoc->createTextNode('org.xmind.ui.map.clockwise');
$class->appendChild($classValue);
$productTopic = $xmlDoc->createElement('topic');
$productTopic->appendChild($titleAttr);
$productTopic->appendChild($idAttr);
$productTopic->appendChild($class);
$productTopic->appendChild($productChildren);
$titleAttr = $xmlDoc->createElement('title', 'sheet');
$sheet = $xmlDoc->createElement('sheet');
$theme = $xmlDoc->createAttribute('theme');
$themeValue = $xmlDoc->createTextNode('65q18ujpt3vgdbk1ifknidq03m');
$theme->appendChild($themeValue);
$sheet->appendChild($titleAttr);
$sheet->appendChild($theme);
$sheet->appendChild($productTopic);
$xmapContent = $this->classXmind->initXmapContent($xmlDoc);
$xmapContent->appendChild($sheet);
$xmlDoc->appendChild($xmapContent);
$sceneTopics = array();
$moduleTopics = array();
$this->classXmind->createModuleTopic($xmlDoc, $context, $productTopics, $moduleTopics);
$this->classXmind->createSceneTopic($xmlDoc, $context, $productTopics, $moduleTopics, $sceneTopics);
$this->classXmind->createTestcaseTopic($xmlDoc, $context, $productTopics, $moduleTopics, $sceneTopics);
$this->app->loadClass('pclzip', true);
$zfile = $this->app->loadClass('zfile');
/* Init xmind file. */
$exportPath = $this->app->getCacheRoot() . $this->app->user->account . uniqid() . '/';
if(is_dir($exportPath)) $zfile->removeDir($exportPath);
$zfile->mkdir($exportPath);
file_put_contents($exportPath . 'content.xml', $xmlDoc->saveXML());
/* create style.xml. */
$styleXmlContent = '';
file_put_contents($exportPath . 'style.xml', $styleXmlContent);
/* create mate.xml. */
$metaXmlContent = '';
file_put_contents($exportPath . 'meta.xml', $metaXmlContent);
/* create META_INF/manifest.xml. */
$zfile->mkdir($exportPath . 'META-INF');
$manifestXmlContent = '';
file_put_contents($exportPath . 'META-INF' . DS . 'manifest.xml', $manifestXmlContent);
/* Zip to xmind. */
$fileName = uniqid() . '.xmind';
helper::cd($exportPath);
$files = array('style.xml', 'meta.xml', 'content.xml', 'META-INF/manifest.xml');
$zip = new pclzip($fileName);
$zip->create($files);
$fileData = file_get_contents($exportPath . $fileName);
$zfile->removeDir($exportPath);
return $fileData;
}
/**
* 解析上传的 xmind 文件。
* Parse the upload xmind file.
*
* @param int $productID
* @param int|string $branch
* @access protected
* @return void
*/
protected function parseUploadFile(int $productID, int|string $branch): array|int
{
/* 创建导入目录。*/
/* Create import Directory. */
$importDir = $this->app->getTmpRoot() . 'import';
if(!is_dir($importDir)) mkdir($importDir, 0755, true);
/* 将上传的临时文件移动到指定的临时位置。*/
/* Move the uploaded temporary file to the specified temporary location. */
$fileName = $this->app->user->id . '-xmind';
$filePath = $importDir . '/' . $fileName;
$tmpFile = $filePath . 'tmp';
if(!move_uploaded_file($_FILES['file']['tmp_name'][0], $tmpFile)) return array('result' => 'fail', 'message' => $this->lang->testcase->errorXmindUpload);
/* 删掉已经存在的当前用户的导入目录。*/
/* Remove the file path. */
$this->classFile = $this->app->loadClass('zfile');
if(is_dir($filePath)) $this->classFile->removeDir($filePath);
/* 压缩临时文件。*/
/* Zip the temporary file. */
$this->app->loadClass('pclzip', true);
$zip = new pclzip($tmpFile);
/* 获取 ZIP 文件中的内容列表。*/
/* Get the content list from the zip file. */
$files = $zip->listContent();
$removePath = $files[0]['filename'];
/* 限制解压的文件内容以阻止 ZIP 解压缩的目录穿越漏洞。*/
/* Limit the file content to prevent the directory traversal vulnerability of ZIP decompression. */
$extractFiles = array('content.json', 'content.xml');
if(in_array($removePath, $extractFiles)) $removePath = '';
if($zip->extract(PCLZIP_OPT_PATH, $filePath, PCLZIP_OPT_BY_NAME, $extractFiles, PCLZIP_OPT_REMOVE_PATH, $removePath) == 0) return array('result' => 'fail', 'message' => $this->lang->testcase->errorXmindUpload);
$this->classFile->removeFile($tmpFile);
$this->classXmind = $this->app->loadClass('xmind');
if(file_exists($filePath . '/content.json'))
{
$fetchResult = $this->fetchByJSON($filePath, $productID, $branch);
}
else
{
$fetchResult = $this->fetchByXML($filePath, $productID);
}
if($fetchResult['result'] == 'fail') return $fetchResult;
$this->session->set('xmindImport', $filePath);
$this->session->set('xmindImportType', $fetchResult['type']);
return (int)$fetchResult['pID'];
}
/**
* 获取 content.xml 的内容。
* Fetch content.xml.
*
* @param string $filePath
* @param int $productID
* @access public
* @return array
*/
private function fetchByXML(string $filePath, int $productID): array
{
$file = $filePath . '/content.xml';
$xmlNode = simplexml_load_file($file);
$title = (string)$xmlNode->sheet->topic->title;
if(!is_string($title) || strlen($title) == 0) return array('result' => 'fail', 'message' => $this->lang->testcase->errorXmindUpload);
$pID = $productID;
if($this->classXmind->endsWith($title, ']'))
{
$tmpID = $this->classXmind->getBetween($title, '[', ']');
if(!empty($tmpID))
{
$product = $this->loadModel('product')->getByID((int)$tmpID);
if(!$product || $product->deleted) return array('result' => 'fail', 'message' => $this->lang->testcase->errorImportBadProduct);
$pID = $tmpID;
}
}
return array('result' => 'success', 'pID' => $pID, 'type' => 'xml');
}
/**
* 获取 content.json 的内容。
* Fetch by json.
*
* @param string $filePath
* @param int $productID
* @param int|string $branch
* @access public
* @return array
*/
function fetchByJSON(string $filePath, int $productID, int|string $branch): array
{
$file = $filePath . '/content.json';
$jsonStr = file_get_contents($file);
$jsonDatas = json_decode($jsonStr, true);
$title = $jsonDatas[0]['rootTopic']['title'];
if(strlen($title) == 0) return array('result' => 'fail', 'message' => $this->lang->testcase->errorXmindUpload);
$pID = $productID;
if($this->classXmind->endsWith($title, ']'))
{
$tmpID = $this->classXmind->getBetween($title, '[', ']');
if(!empty($tmpID))
{
$product = $this->loadModel('product')->getByID((int)$tmpID);
if(!$product || $product->deleted) return array('result' => 'fail', 'message' => $this->lang->testcase->errorImportBadProduct);
$pID = $tmpID;
}
}
return array('result' => 'success', 'pID' => $pID, 'type' => 'json');
}
}