* @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 = '