diff --git a/Dockerfile.test b/Dockerfile.test index 5dc0041958..27064d4fdc 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,4 +1,4 @@ -FROM hub.qucheng.com/ci/zentao-ztf:latest +FROM hub.qucheng.com/ci/zentao-ztf:php8-2023042805 # Install zentao ARG VERSION diff --git a/Jenkinsfile b/Jenkinsfile index 123ce32c6d..04f8f319c7 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { kubernetes { - inheritFrom "build-docker code-scan xuanim mysql-server" + inheritFrom "build-docker code-scan xuanim" } } @@ -10,10 +10,7 @@ pipeline { returnStdout: true, script: 'cat VERSION' ).trim()}""" - MYSQL_SERVER_HOST = """${sh( - returnStdout: true, - script: 'hostname -I' - ).trim()}""" + MYSQL_SERVER_HOST = 'ci-mysql-0' MYSQL_ROOT_PASSWORD = 'pass4ci' MIDDLE_IMAGE_REPO = "hub.qc.oop.cc/zentao-ztf" diff --git a/db/update20.0.sql b/db/update20.0.sql index 5bdf6af527..ebdf019abe 100644 --- a/db/update20.0.sql +++ b/db/update20.0.sql @@ -6,4 +6,4 @@ ALTER TABLE `zt_todo` CHANGE `idvalue` `objectID` mediumint(8) unsigned default UPDATE `zt_block` SET `dashboard` = `module`; UPDATE `zt_block` SET `module` = IF(`source` != '', `source`, `code`); - +DROP INDEX account_vision_module_type_order ON `zt_block`; diff --git a/framework/base/router.class.php b/framework/base/router.class.php index 019be4448b..d5a31cd0f4 100644 --- a/framework/base/router.class.php +++ b/framework/base/router.class.php @@ -779,7 +779,7 @@ class baseRouter if(empty($this->config->debug) || $this->config->debug < 4 || !extension_loaded('xhprof')) return false; $log = xhprof_disable(); - $xhprofPath = $this->getTmpRoot() . 'xhprof'; + $xhprofPath = $this->getWwwRoot() . 'xhprof'; $libUtilsPath = $xhprofPath . DS . 'xhprof_lib' . DS . 'utils' . DS; $outputDir = ini_get('xhprof.output_dir'); @@ -795,9 +795,16 @@ class baseRouter } $xhprofRuns = new \XHProfRuns_Default($outputDir); - $runID = $xhprofRuns->save_run($log, "{$this->moduleName}_{$this->methodName}"); + $type = "{$this->moduleName}_{$this->methodName}"; + $runID = $xhprofRuns->save_run($log, $type); header("Xhprof-RunID: {$runID}"); + if(class_exists(\zin\zin::class) && isset(\zin\zin::$data['zinDebug'])) + { + $xhprofURL = getWebRoot(true) . "xhprof/xhprof_html/index.php?run={$runID}&source={$type}"; + \zin\zin::$data['zinDebug']['xhprof'] = $xhprofURL; + } + return true; } diff --git a/lib/form/form.class.php b/lib/form/form.class.php index e375f960fb..a59953175b 100644 --- a/lib/form/form.class.php +++ b/lib/form/form.class.php @@ -1,9 +1,10 @@ - * @package form * @link https://www.zentao.net diff --git a/lib/trace/trace.class.php b/lib/trace/trace.class.php index 739db3c27a..a550a7877c 100644 --- a/lib/trace/trace.class.php +++ b/lib/trace/trace.class.php @@ -1,4 +1,14 @@ + * @package trace + * @link https://www.zentao.net + */ class trace { @@ -6,9 +16,11 @@ class trace 'Request' => '请求', 'Files' => '文件', 'SQL Query' => 'SQL 查询', - 'SQL Explain' => 'SQL Explain', ); + /** + * @var array + */ public $trace = array(); protected $app; @@ -38,37 +50,53 @@ class trace ); } + /** + * 获取请求加载的文件。 + * Get request files. + * + * @return void + */ public function getRequestFiles() { $this->trace['files'] = get_included_files(); } + /** + * 获取请求的 SQL 语句。 + * Get request SQLs. + * + * @return void + */ public function getRequestSqls() { - $explain = array(); - /** - foreach(dao::$querys as $query) - { - $explain[] = $this->dao->explain($query, false); - } - */ $this->trace['sqlQuery'] = dao::$querys; - $this->trace['sqlExplain'] = $explain; } - public function getSQLProfile() + /** + * 获取请求的 SQL profiles。 + * Get request SQL profiles. + * + * @return array + */ + public function getSQLProfiles() { $profiling = $this->dao->dbh->query('SHOW PROFILES')->fetchAll(PDO::FETCH_ASSOC); - + $this->trace['profiles'] = $profiling; } + /** + * 生成请求 Trace。 + * Generate request trace. + * + * @return array + */ public function getTrace() { $this->getRequestInfo(); $this->getRequestFiles(); $this->getRequestSqls(); - $this->getSQLProfile(); + $this->getSQLProfiles(); return $this->trace; } @@ -78,7 +106,6 @@ class trace $lines = ''; foreach($this->trace as $type => $content) { - if($type == 'SQL Explain') continue; $lines .= $this->console($type, empty($content) ? array() : $content); } @@ -93,6 +120,14 @@ JS; return $js; } + /** + * 拼接需要输出到 console 的内容。 + * Concat the content to output to console. + * + * @param string $type + * @param array $content + * @return string + */ protected function console(string $type, $content) { $traceTabs = array_keys($this->types); @@ -106,19 +141,6 @@ JS; $msg = str_replace("\n", '\n', addslashes($item)); $style = "color:#009bb4;"; $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; - - $explain = array(); - if(!empty($this->trace['SQL Explain'])) - { - foreach($this->trace['SQL Explain'][$key] as $explainKey => $explainItem) - { - $explain[] = $explainKey . ': ' . $explainItem; - } - } - - $msg = implode(', ', $explain); - $style = "color:red;"; - $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; break; default: $item = is_string($key) ? $key . ' ' . $item : $key + 1 . ' ' . $item; @@ -131,6 +153,11 @@ JS; return implode(PHP_EOL, $line); } + /** + * 拼接 SQL Profile。 + * + * @return string + */ protected function printSQLProfile() { $lines = array(); diff --git a/module/block/config.php b/module/block/config.php index 3bd9860a1b..e9279b4376 100644 --- a/module/block/config.php +++ b/module/block/config.php @@ -100,13 +100,13 @@ $config->block->params['project']->orderBy['name'] = $lang->block->orderBy; $config->block->params['project']->orderBy['options'] = $lang->block->orderByList->product; $config->block->params['project']->orderBy['control'] = 'select'; -$config->block->params['projectTeam'] = clone $config->block->params['default']; -$config->block->params['projectTeam']->type['name'] = $lang->block->type; -$config->block->params['projectTeam']->type['options'] = $lang->block->typeList->projectAll; -$config->block->params['projectTeam']->type['control'] = 'select'; -$config->block->params['projectTeam']->orderBy['name'] = $lang->block->orderBy; -$config->block->params['projectTeam']->orderBy['options'] = $lang->block->orderByList->project; -$config->block->params['projectTeam']->orderBy['control'] = 'select'; +$config->block->params['projectteam'] = clone $config->block->params['default']; +$config->block->params['projectteam']->type['name'] = $lang->block->type; +$config->block->params['projectteam']->type['options'] = $lang->block->typeList->projectAll; +$config->block->params['projectteam']->type['control'] = 'select'; +$config->block->params['projectteam']->orderBy['name'] = $lang->block->orderBy; +$config->block->params['projectteam']->orderBy['options'] = $lang->block->orderByList->project; +$config->block->params['projectteam']->orderBy['control'] = 'select'; $config->block->params['build'] = clone $config->block->params['default']; @@ -115,33 +115,33 @@ $config->block->params['product']->type['name'] = $lang->block->type; $config->block->params['product']->type['options'] = $lang->block->typeList->product; $config->block->params['product']->type['control'] = 'select'; -$config->block->params['productStatistic'] = clone $config->block->params['default']; -$config->block->params['productStatistic']->type['name'] = $lang->block->type; -$config->block->params['productStatistic']->type['options'] = $lang->block->typeList->product; -$config->block->params['productStatistic']->type['control'] = 'select'; +$config->block->params['productstatistic'] = clone $config->block->params['default']; +$config->block->params['productstatistic']->type['name'] = $lang->block->type; +$config->block->params['productstatistic']->type['options'] = $lang->block->typeList->product; +$config->block->params['productstatistic']->type['control'] = 'select'; -$config->block->params['projectStatistic'] = clone $config->block->params['default']; -$config->block->params['projectStatistic']->type['name'] = $lang->block->type; -$config->block->params['projectStatistic']->type['options'] = $lang->block->typeList->project; -$config->block->params['projectStatistic']->type['control'] = 'select'; +$config->block->params['projectstatistic'] = clone $config->block->params['default']; +$config->block->params['projectstatistic']->type['name'] = $lang->block->type; +$config->block->params['projectstatistic']->type['options'] = $lang->block->typeList->project; +$config->block->params['projectstatistic']->type['control'] = 'select'; -$config->block->params['executionStatistic'] = clone $config->block->params['default']; -$config->block->params['executionStatistic']->type['name'] = $lang->block->type; -$config->block->params['executionStatistic']->type['options'] = $lang->block->typeList->execution; -$config->block->params['executionStatistic']->type['control'] = 'select'; +$config->block->params['executionstatistic'] = clone $config->block->params['default']; +$config->block->params['executionstatistic']->type['name'] = $lang->block->type; +$config->block->params['executionstatistic']->type['options'] = $lang->block->typeList->execution; +$config->block->params['executionstatistic']->type['control'] = 'select'; $config->block->params['qaStatistic'] = clone $config->block->params['default']; $config->block->params['qaStatistic']->type['name'] = $lang->block->type; $config->block->params['qaStatistic']->type['options'] = $lang->block->typeList->product; $config->block->params['qaStatistic']->type['control'] = 'select'; -$config->block->params['waterfallIssue'] = clone $config->block->params['default']; -$config->block->params['waterfallIssue']->type['name'] = $lang->block->type; -$config->block->params['waterfallIssue']->type['options'] = $lang->block->typeList->issue; -$config->block->params['waterfallIssue']->type['control'] = 'select'; -$config->block->params['waterfallIssue']->orderBy['name'] = $lang->block->orderBy; -$config->block->params['waterfallIssue']->orderBy['options'] = $lang->block->orderByList->product; -$config->block->params['waterfallIssue']->orderBy['control'] = 'select'; +$config->block->params['waterfallissue'] = clone $config->block->params['default']; +$config->block->params['waterfallissue']->type['name'] = $lang->block->type; +$config->block->params['waterfallissue']->type['options'] = $lang->block->typeList->issue; +$config->block->params['waterfallissue']->type['control'] = 'select'; +$config->block->params['waterfallissue']->orderBy['name'] = $lang->block->orderBy; +$config->block->params['waterfallissue']->orderBy['options'] = $lang->block->orderByList->product; +$config->block->params['waterfallissue']->orderBy['control'] = 'select'; $config->block->params['waterfallRisk'] = clone $config->block->params['default']; $config->block->params['waterfallRisk']->type['name'] = $lang->block->type; diff --git a/module/block/control.php b/module/block/control.php index 4d79f475a9..a409cb9b2d 100755 --- a/module/block/control.php +++ b/module/block/control.php @@ -206,42 +206,34 @@ class block extends control * 展示应用的仪表盘 * Display dashboard for app. * - * @param string $module - * @param int $projectID + * @param string $dashboard + * @param string $projectID * @access public * @return void */ - public function dashboard(string $module, int $projectID = 0) + public function dashboard(string $dashboard, string $projectID = '0') { - if($this->loadModel('user')->isLogon()) $this->session->set('blockModule', $module); - $blocks = $this->block->getMyDashboard($module); - $vision = $this->config->vision; + $blocks = $this->block->getMyDashboard($dashboard); + $projectID = (int)$projectID; - $section = 'common'; - if($module == 'project' and $projectID) - { - $project = $this->loadModel('project')->getByID($projectID); - $section = $project->model . 'common'; - } - - $isInitiated = $this->block->fetchBlockInitStatus($module, $vision, $section); + $isInitiated = $this->block->fetchBlockInitStatus($dashboard); /* Init block when vist index first. */ - if(empty($blocks) and !$isInitiated and !defined('TUTORIAL') and $this->block->initBlock($module)) + if(empty($blocks) and !$isInitiated and !defined('TUTORIAL') and $this->block->initBlock($dashboard)) { return print(js::reload()); } - $blocks = $this->blockZen->processBlockForRender($blocks, isset($project) ? $project : null); + $blocks = $this->blockZen->processBlockForRender($blocks, $projectID); if($this->app->getViewType() == 'json') return print(json_encode($blocks)); list($shortBlocks, $longBlocks) = $this->blockZen->splitBlocksByLen($blocks); - $this->view->title = zget($this->lang->block->dashboard, $module, $this->lang->block->dashboard['default']); + $this->view->title = zget($this->lang->block->dashboard, $dashboard, $this->lang->block->dashboard['default']); $this->view->longBlocks = $longBlocks; $this->view->shortBlocks = $shortBlocks; - $this->view->dashboard = $module; + $this->view->dashboard = $dashboard; $this->render(); } diff --git a/module/block/lang/zh-cn.php b/module/block/lang/zh-cn.php index 4af2fe68e4..764f5c1ac0 100644 --- a/module/block/lang/zh-cn.php +++ b/module/block/lang/zh-cn.php @@ -133,183 +133,200 @@ $lang->block->titleList['bug'] = '指派给我的Bug'; $lang->block->titleList['case'] = '指派给我的用例'; $lang->block->titleList['testtask'] = '待测版本列表'; -$lang->block->default['waterfall']['project']['3']['title'] = "{$lang->projectCommon}计划"; -$lang->block->default['waterfall']['project']['3']['code'] = 'waterfallgantt'; -$lang->block->default['waterfall']['project']['3']['source'] = 'project'; -$lang->block->default['waterfall']['project']['3']['grid'] = 8; +$lang->block->default['waterfallproject']['3']['title'] = "{$lang->projectCommon}计划"; +$lang->block->default['waterfallproject']['3']['module'] = 'waterfallproject'; +$lang->block->default['waterfallproject']['3']['code'] = 'waterfallgantt'; +$lang->block->default['waterfallproject']['3']['grid'] = 8; -$lang->block->default['waterfall']['project']['6']['title'] = '最新动态'; -$lang->block->default['waterfall']['project']['6']['code'] = 'projectdynamic'; -$lang->block->default['waterfall']['project']['6']['grid'] = 4; -$lang->block->default['waterfall']['project']['6']['source'] = 'project'; +$lang->block->default['waterfallproject']['6']['title'] = '最新动态'; +$lang->block->default['waterfallproject']['6']['module'] = 'waterfallproject'; +$lang->block->default['waterfallproject']['6']['code'] = 'projectdynamic'; +$lang->block->default['waterfallproject']['6']['grid'] = 4; -$lang->block->default['waterfallplus'] = $lang->block->default['waterfall']; +$lang->block->default['waterfallplusproject'] = $lang->block->default['waterfallproject']; -$lang->block->default['scrum']['project']['1']['title'] = $lang->projectCommon . '概况'; -$lang->block->default['scrum']['project']['1']['code'] = 'scrumoverview'; -$lang->block->default['scrum']['project']['1']['grid'] = 8; +$lang->block->default['scrumproject']['1']['title'] = $lang->projectCommon . '概况'; +$lang->block->default['scrumproject']['1']['module'] = 'scrumproject'; +$lang->block->default['scrumproject']['1']['code'] = 'scrumoverview'; +$lang->block->default['scrumproject']['1']['grid'] = 8; -$lang->block->default['scrum']['project']['2']['title'] = $lang->executionCommon . '列表'; -$lang->block->default['scrum']['project']['2']['code'] = 'scrumlist'; -$lang->block->default['scrum']['project']['2']['grid'] = 8; +$lang->block->default['scrumproject']['2']['title'] = $lang->executionCommon . '列表'; +$lang->block->default['scrumproject']['2']['module'] = 'scrumproject'; +$lang->block->default['scrumproject']['2']['code'] = 'scrumlist'; +$lang->block->default['scrumproject']['2']['grid'] = 8; -$lang->block->default['scrum']['project']['2']['params']['type'] = 'undone'; -$lang->block->default['scrum']['project']['2']['params']['count'] = '20'; -$lang->block->default['scrum']['project']['2']['params']['orderBy'] = 'id_desc'; +$lang->block->default['scrumproject']['2']['params']['type'] = 'undone'; +$lang->block->default['scrumproject']['2']['params']['count'] = '20'; +$lang->block->default['scrumproject']['2']['params']['orderBy'] = 'id_desc'; -$lang->block->default['scrum']['project']['3']['title'] = '待测版本'; -$lang->block->default['scrum']['project']['3']['code'] = 'scrumtest'; -$lang->block->default['scrum']['project']['3']['grid'] = 8; +$lang->block->default['scrumproject']['3']['title'] = '待测版本'; +$lang->block->default['scrumproject']['3']['module'] = 'scrumproject'; +$lang->block->default['scrumproject']['3']['code'] = 'scrumtest'; +$lang->block->default['scrumproject']['3']['grid'] = 8; -$lang->block->default['scrum']['project']['3']['params']['type'] = 'wait'; -$lang->block->default['scrum']['project']['3']['params']['count'] = '15'; -$lang->block->default['scrum']['project']['3']['params']['orderBy'] = 'id_desc'; +$lang->block->default['scrumproject']['3']['params']['type'] = 'wait'; +$lang->block->default['scrumproject']['3']['params']['count'] = '15'; +$lang->block->default['scrumproject']['3']['params']['orderBy'] = 'id_desc'; -$lang->block->default['scrum']['project']['4']['title'] = $lang->executionCommon . '总览'; -$lang->block->default['scrum']['project']['4']['code'] = 'sprint'; -$lang->block->default['scrum']['project']['4']['grid'] = 4; +$lang->block->default['scrumproject']['4']['title'] = $lang->executionCommon . '总览'; +$lang->block->default['scrumproject']['4']['module'] = 'scrumproject'; +$lang->block->default['scrumproject']['4']['code'] = 'sprint'; +$lang->block->default['scrumproject']['4']['grid'] = 4; -$lang->block->default['scrum']['project']['5']['title'] = '最新动态'; -$lang->block->default['scrum']['project']['5']['code'] = 'projectdynamic'; -$lang->block->default['scrum']['project']['5']['grid'] = 4; -$lang->block->default['kanban'] = $lang->block->default['scrum']; -$lang->block->default['agileplus'] = $lang->block->default['scrum']; +$lang->block->default['scrumproject']['5']['title'] = '最新动态'; +$lang->block->default['scrumproject']['5']['module'] = 'scrumproject'; +$lang->block->default['scrumproject']['5']['code'] = 'projectdynamic'; +$lang->block->default['scrumproject']['5']['grid'] = 4; -$lang->block->default['product']['1']['title'] = $lang->productCommon . '统计'; -$lang->block->default['product']['1']['code'] = 'statistic'; -$lang->block->default['product']['1']['grid'] = 8; +$lang->block->default['kanbanproject'] = $lang->block->default['scrumproject']; +$lang->block->default['agileplusproject'] = $lang->block->default['scrumproject']; + +$lang->block->default['product']['1']['title'] = $lang->productCommon . '统计'; +$lang->block->default['product']['1']['module'] = 'product'; +$lang->block->default['product']['1']['code'] = 'statistic'; +$lang->block->default['product']['1']['grid'] = 8; $lang->block->default['product']['1']['params']['type'] = 'all'; $lang->block->default['product']['1']['params']['count'] = '20'; -$lang->block->default['product']['2']['title'] = $lang->productCommon . '总览'; -$lang->block->default['product']['2']['code'] = 'overview'; -$lang->block->default['product']['2']['grid'] = 4; +$lang->block->default['product']['2']['title'] = $lang->productCommon . '总览'; +$lang->block->default['product']['2']['module'] = 'product'; +$lang->block->default['product']['2']['code'] = 'overview'; +$lang->block->default['product']['2']['grid'] = 4; -$lang->block->default['product']['3']['title'] = '未关闭的' . $lang->productCommon; -$lang->block->default['product']['3']['code'] = 'list'; -$lang->block->default['product']['3']['grid'] = 8; +$lang->block->default['product']['3']['title'] = '未关闭的' . $lang->productCommon; +$lang->block->default['product']['3']['module'] = 'product'; +$lang->block->default['product']['3']['code'] = 'list'; +$lang->block->default['product']['3']['grid'] = 8; $lang->block->default['product']['3']['params']['count'] = 15; $lang->block->default['product']['3']['params']['type'] = 'noclosed'; -$lang->block->default['product']['4']['title'] = "指派给我的{$lang->SRCommon}"; -$lang->block->default['product']['4']['code'] = 'story'; -$lang->block->default['product']['4']['grid'] = 4; +$lang->block->default['product']['4']['title'] = "指派给我的{$lang->SRCommon}"; +$lang->block->default['product']['4']['module'] = 'product'; +$lang->block->default['product']['4']['code'] = 'story'; +$lang->block->default['product']['4']['grid'] = 4; $lang->block->default['product']['4']['params']['count'] = 15; $lang->block->default['product']['4']['params']['orderBy'] = 'id_desc'; $lang->block->default['product']['4']['params']['type'] = 'assignedTo'; -$lang->block->default['execution']['1']['title'] = '执行统计'; -$lang->block->default['execution']['1']['code'] = 'statistic'; -$lang->block->default['execution']['1']['grid'] = 8; +$lang->block->default['execution']['1']['title'] = '执行统计'; +$lang->block->default['execution']['1']['module'] = 'execution'; +$lang->block->default['execution']['1']['code'] = 'statistic'; +$lang->block->default['execution']['1']['grid'] = 8; $lang->block->default['execution']['1']['params']['type'] = 'all'; $lang->block->default['execution']['1']['params']['count'] = '20'; -$lang->block->default['execution']['2']['title'] = '执行总览'; -$lang->block->default['execution']['2']['code'] = 'overview'; -$lang->block->default['execution']['2']['grid'] = 4; +$lang->block->default['execution']['2']['title'] = '执行总览'; +$lang->block->default['execution']['2']['module'] = 'execution'; +$lang->block->default['execution']['2']['code'] = 'overview'; +$lang->block->default['execution']['2']['grid'] = 4; -$lang->block->default['execution']['3']['title'] = '未关闭的执行'; -$lang->block->default['execution']['3']['code'] = 'list'; -$lang->block->default['execution']['3']['grid'] = 8; +$lang->block->default['execution']['3']['title'] = '未关闭的执行'; +$lang->block->default['execution']['3']['module'] = 'execution'; +$lang->block->default['execution']['3']['code'] = 'list'; +$lang->block->default['execution']['3']['grid'] = 8; $lang->block->default['execution']['3']['params']['count'] = 15; $lang->block->default['execution']['3']['params']['orderBy'] = 'id_desc'; $lang->block->default['execution']['3']['params']['type'] = 'undone'; -$lang->block->default['execution']['4']['title'] = '指派给我的任务'; -$lang->block->default['execution']['4']['code'] = 'task'; -$lang->block->default['execution']['4']['grid'] = 4; +$lang->block->default['execution']['4']['title'] = '指派给我的任务'; +$lang->block->default['execution']['4']['module'] = 'execution'; +$lang->block->default['execution']['4']['code'] = 'task'; +$lang->block->default['execution']['4']['grid'] = 4; $lang->block->default['execution']['4']['params']['count'] = 15; $lang->block->default['execution']['4']['params']['orderBy'] = 'id_desc'; $lang->block->default['execution']['4']['params']['type'] = 'assignedTo'; -$lang->block->default['execution']['5']['title'] = '版本列表'; -$lang->block->default['execution']['5']['code'] = 'build'; -$lang->block->default['execution']['5']['grid'] = 8; +$lang->block->default['execution']['5']['title'] = '版本列表'; +$lang->block->default['execution']['5']['module'] = 'execution'; +$lang->block->default['execution']['5']['code'] = 'build'; +$lang->block->default['execution']['5']['grid'] = 8; $lang->block->default['execution']['5']['params']['count'] = 15; $lang->block->default['execution']['5']['params']['orderBy'] = 'id_desc'; -$lang->block->default['qa']['1']['title'] = '测试统计'; -$lang->block->default['qa']['1']['code'] = 'statistic'; -$lang->block->default['qa']['1']['grid'] = 8; +$lang->block->default['qa']['1']['title'] = '测试统计'; +$lang->block->default['qa']['1']['module'] = 'qa'; +$lang->block->default['qa']['1']['code'] = 'statistic'; +$lang->block->default['qa']['1']['grid'] = 8; $lang->block->default['qa']['1']['params']['type'] = 'noclosed'; $lang->block->default['qa']['1']['params']['count'] = '20'; -//$lang->block->default['qa']['2']['title'] = '测试用例总览'; -//$lang->block->default['qa']['2']['block'] = 'overview'; -//$lang->block->default['qa']['2']['grid'] = 4; - -$lang->block->default['qa']['2']['title'] = '指派给我的Bug'; -$lang->block->default['qa']['2']['code'] = 'bug'; -$lang->block->default['qa']['2']['grid'] = 4; +$lang->block->default['qa']['2']['title'] = '指派给我的Bug'; +$lang->block->default['qa']['2']['module'] = 'qa'; +$lang->block->default['qa']['2']['code'] = 'bug'; +$lang->block->default['qa']['2']['grid'] = 4; $lang->block->default['qa']['2']['params']['count'] = 15; $lang->block->default['qa']['2']['params']['orderBy'] = 'id_desc'; $lang->block->default['qa']['2']['params']['type'] = 'assignedTo'; -$lang->block->default['qa']['3']['title'] = '指派给我的用例'; -$lang->block->default['qa']['3']['code'] = 'case'; -$lang->block->default['qa']['3']['grid'] = 4; +$lang->block->default['qa']['3']['title'] = '指派给我的用例'; +$lang->block->default['qa']['3']['module'] = 'qa'; +$lang->block->default['qa']['3']['code'] = 'case'; +$lang->block->default['qa']['3']['grid'] = 4; $lang->block->default['qa']['3']['params']['count'] = 15; $lang->block->default['qa']['3']['params']['orderBy'] = 'id_desc'; $lang->block->default['qa']['3']['params']['type'] = 'assigntome'; -$lang->block->default['qa']['4']['title'] = '待测版本列表'; -$lang->block->default['qa']['4']['code'] = 'testtask'; -$lang->block->default['qa']['4']['grid'] = 8; +$lang->block->default['qa']['4']['title'] = '待测版本列表'; +$lang->block->default['qa']['4']['module'] = 'qa'; +$lang->block->default['qa']['4']['code'] = 'testtask'; +$lang->block->default['qa']['4']['grid'] = 8; $lang->block->default['qa']['4']['params']['count'] = 15; $lang->block->default['qa']['4']['params']['orderBy'] = 'id_desc'; $lang->block->default['qa']['4']['params']['type'] = 'wait'; $lang->block->default['full']['my']['1']['title'] = '欢迎'; +$lang->block->default['full']['my']['1']['module'] = 'welcome'; $lang->block->default['full']['my']['1']['code'] = 'welcome'; $lang->block->default['full']['my']['1']['grid'] = 8; -$lang->block->default['full']['my']['1']['source'] = ''; $lang->block->default['full']['my']['2']['title'] = '最新动态'; +$lang->block->default['full']['my']['2']['module'] = 'dynamic'; $lang->block->default['full']['my']['2']['code'] = 'dynamic'; $lang->block->default['full']['my']['2']['grid'] = 4; -$lang->block->default['full']['my']['2']['source'] = ''; $lang->block->default['full']['my']['3']['title'] = '使用帮助'; +$lang->block->default['full']['my']['3']['module'] = 'guide'; $lang->block->default['full']['my']['3']['code'] = 'guide'; -$lang->block->default['full']['my']['3']['source'] = ''; $lang->block->default['full']['my']['3']['grid'] = 8; -$lang->block->default['full']['my']['4']['title'] = '我的待办'; -$lang->block->default['full']['my']['4']['code'] = 'list'; -$lang->block->default['full']['my']['4']['grid'] = 4; -$lang->block->default['full']['my']['4']['source'] = 'todo'; +$lang->block->default['full']['my']['4']['title'] = '我的待办'; +$lang->block->default['full']['my']['4']['module'] = 'todo'; +$lang->block->default['full']['my']['4']['code'] = 'list'; +$lang->block->default['full']['my']['4']['grid'] = 4; + $lang->block->default['full']['my']['4']['params']['count'] = '20'; -$lang->block->default['full']['my']['5']['title'] = "{$lang->projectCommon}统计"; -$lang->block->default['full']['my']['5']['code'] = 'statistic'; -$lang->block->default['full']['my']['5']['source'] = 'project'; -$lang->block->default['full']['my']['5']['grid'] = 8; +$lang->block->default['full']['my']['5']['title'] = "{$lang->projectCommon}统计"; +$lang->block->default['full']['my']['5']['module'] = 'project'; +$lang->block->default['full']['my']['5']['code'] = 'statistic'; +$lang->block->default['full']['my']['5']['grid'] = 8; + $lang->block->default['full']['my']['5']['params']['count'] = '20'; $lang->block->default['full']['my']['6']['title'] = '我的贡献'; -$lang->block->default['full']['my']['6']['code'] = 'contribute'; -$lang->block->default['full']['my']['6']['source'] = ''; +$lang->block->default['full']['my']['6']['module'] = 'contribute'; +$lang->block->default['full']['my']['6']['code'] = 'contribute'; $lang->block->default['full']['my']['6']['grid'] = 4; $lang->block->default['full']['my']['7']['title'] = "我近期参与的{$lang->projectCommon}"; +$lang->block->default['full']['my']['7']['module'] = 'project'; $lang->block->default['full']['my']['7']['code'] = 'recentproject'; -$lang->block->default['full']['my']['7']['source'] = 'project'; $lang->block->default['full']['my']['7']['grid'] = 8; $lang->block->default['full']['my']['8']['title'] = '我的待处理'; +$lang->block->default['full']['my']['8']['module'] = 'assigntome'; $lang->block->default['full']['my']['8']['code'] = 'assigntome'; -$lang->block->default['full']['my']['8']['source'] = ''; $lang->block->default['full']['my']['8']['grid'] = 8; $lang->block->default['full']['my']['8']['params']['todoCount'] = '20'; @@ -323,55 +340,63 @@ $lang->block->default['full']['my']['8']['params']['meetingCount'] = '20'; $lang->block->default['full']['my']['8']['params']['feedbackCount'] = '20'; $lang->block->default['full']['my']['9']['title'] = "{$lang->projectCommon}人力投入"; +$lang->block->default['full']['my']['9']['module'] = 'project'; $lang->block->default['full']['my']['9']['code'] = 'projectteam'; -$lang->block->default['full']['my']['9']['source'] = 'project'; $lang->block->default['full']['my']['9']['grid'] = 8; $lang->block->default['full']['my']['10']['title'] = "{$lang->projectCommon}列表"; +$lang->block->default['full']['my']['10']['module'] = 'project'; $lang->block->default['full']['my']['10']['code'] = 'project'; -$lang->block->default['full']['my']['10']['source'] = 'project'; $lang->block->default['full']['my']['10']['grid'] = 8; $lang->block->default['full']['my']['10']['params']['orderBy'] = 'id_desc'; $lang->block->default['full']['my']['10']['params']['count'] = '15'; /* Doc module block. */ -$lang->block->default['doc']['1']['title'] = '文档统计'; -$lang->block->default['doc']['1']['code'] = 'docstatistic'; -$lang->block->default['doc']['1']['grid'] = 8; +$lang->block->default['doc']['1']['title'] = '文档统计'; +$lang->block->default['doc']['1']['module'] = 'doc'; +$lang->block->default['doc']['1']['code'] = 'docstatistic'; +$lang->block->default['doc']['1']['grid'] = 8; -$lang->block->default['doc']['2']['title'] = '文档动态'; -$lang->block->default['doc']['2']['code'] = 'docdynamic'; -$lang->block->default['doc']['2']['grid'] = 4; +$lang->block->default['doc']['2']['title'] = '文档动态'; +$lang->block->default['doc']['2']['module'] = 'doc'; +$lang->block->default['doc']['2']['code'] = 'docdynamic'; +$lang->block->default['doc']['2']['grid'] = 4; -$lang->block->default['doc']['3']['title'] = '我收藏的文档'; -$lang->block->default['doc']['3']['code'] = 'docmycollection'; -$lang->block->default['doc']['3']['grid'] = 8; +$lang->block->default['doc']['3']['title'] = '我收藏的文档'; +$lang->block->default['doc']['3']['module'] = 'doc'; +$lang->block->default['doc']['3']['code'] = 'docmycollection'; +$lang->block->default['doc']['3']['grid'] = 8; -$lang->block->default['doc']['4']['title'] = '最近更新的文档'; -$lang->block->default['doc']['4']['code'] = 'docrecentupdate'; -$lang->block->default['doc']['4']['grid'] = 8; +$lang->block->default['doc']['4']['title'] = '最近更新的文档'; +$lang->block->default['doc']['4']['module'] = 'doc'; +$lang->block->default['doc']['4']['code'] = 'docrecentupdate'; +$lang->block->default['doc']['4']['grid'] = 8; -$lang->block->default['doc']['5']['title'] = '浏览排行榜'; -$lang->block->default['doc']['5']['code'] = 'docviewlist'; -$lang->block->default['doc']['5']['grid'] = 4; +$lang->block->default['doc']['5']['title'] = '浏览排行榜'; +$lang->block->default['doc']['5']['module'] = 'doc'; +$lang->block->default['doc']['5']['code'] = 'docviewlist'; +$lang->block->default['doc']['5']['grid'] = 4; if($config->vision == 'rnd') { - $lang->block->default['doc']['6']['title'] = $lang->productCommon . '文档'; - $lang->block->default['doc']['6']['code'] = 'productdoc'; - $lang->block->default['doc']['6']['grid'] = 8; + $lang->block->default['doc']['6']['title'] = $lang->productCommon . '文档'; + $lang->block->default['doc']['6']['module'] = 'doc'; + $lang->block->default['doc']['6']['code'] = 'productdoc'; + $lang->block->default['doc']['6']['grid'] = 8; $lang->block->default['doc']['6']['params']['count'] = '20'; } -$lang->block->default['doc']['7']['title'] = '收藏排行榜'; -$lang->block->default['doc']['7']['code'] = 'doccollectlist'; -$lang->block->default['doc']['7']['grid'] = 4; +$lang->block->default['doc']['7']['title'] = '收藏排行榜'; +$lang->block->default['doc']['7']['module'] = 'doc'; +$lang->block->default['doc']['7']['code'] = 'doccollectlist'; +$lang->block->default['doc']['7']['grid'] = 4; -$lang->block->default['doc']['8']['title'] = $lang->projectCommon . '文档'; -$lang->block->default['doc']['8']['code'] = 'projectdoc'; -$lang->block->default['doc']['8']['grid'] = 8; +$lang->block->default['doc']['8']['title'] = $lang->projectCommon . '文档'; +$lang->block->default['doc']['8']['module'] = 'doc'; +$lang->block->default['doc']['8']['code'] = 'projectdoc'; +$lang->block->default['doc']['8']['grid'] = 8; $lang->block->default['doc']['8']['params']['count'] = '20'; @@ -556,13 +581,12 @@ $lang->block->typeList->project['doing'] = '进行中'; $lang->block->typeList->project['all'] = '全部'; $lang->block->typeList->project['involved'] = '我参与的'; -$lang->block->typeList->projectAll['all'] = '全部'; -$lang->block->typeList->projectAll['undone'] = '未完成'; -$lang->block->typeList->projectAll['wait'] = '未开始'; -$lang->block->typeList->projectAll['doing'] = '进行中'; -$lang->block->typeList->projectAll['exceeded'] = '已逾期'; -$lang->block->typeList->projectAll['risky'] = '有风险'; -$lang->block->typeList->projectAll['more'] = '更多'; +$lang->block->typeList->projectAll['all'] = '全部'; +$lang->block->typeList->projectAll['undone'] = '未完成'; +$lang->block->typeList->projectAll['wait'] = '未开始'; +$lang->block->typeList->projectAll['doing'] = '进行中'; +$lang->block->typeList->projectAll['suspended'] = '已挂起'; +$lang->block->typeList->projectAll['closed'] = '已关闭'; $lang->block->typeList->execution['undone'] = '未完成'; $lang->block->typeList->execution['doing'] = '进行中'; diff --git a/module/block/model.php b/module/block/model.php index 026069eb25..944b20e1de 100644 --- a/module/block/model.php +++ b/module/block/model.php @@ -195,7 +195,7 @@ class blockModel extends model } elseif($code == 'statistic') { - $code = $module . 'Statistic'; + $code = $module . $code; } $params = zget($this->config->block->params, $code, ''); @@ -401,48 +401,37 @@ class blockModel extends model * Init block when account use first. * 用户首次加载时初始化区块数据. * - * @param string $module - * @param string $type + * @param string $dashboard * @access public * @return bool */ - public function initBlock(string $module, string $type = ''): bool + public function initBlock(string $dashboard): bool { - if(!$module) return false; + if(!$dashboard) return false; $flow = isset($this->config->global->flow) ? $this->config->global->flow : 'full'; $account = $this->app->user->account; $vision = $this->config->vision; - if($module == 'project') - { - $blocks = $this->lang->block->default[$type]['project']; + $blocks = $dashboard == 'my' ? $this->lang->block->default[$flow][$dashboard] : $this->lang->block->default[$dashboard]; - /* Mark project block has init. */ - $this->loadModel('setting')->setItem("$account.$module.{$type}common.blockInited@$vision", '1'); - } - else - { - $blocks = $module == 'my' ? $this->lang->block->default[$flow][$module] : $this->lang->block->default[$module]; - - /* Mark this app has init. */ - $this->loadModel('setting')->setItem("$account.$module.common.blockInited@$vision", '1'); - } - - $this->loadModel('setting')->setItem("$account.$module.block.initVersion", $this->config->block->version); foreach($blocks as $index => $block) { - $block['order'] = $index; - $block['module'] = $module; - $block['type'] = $type; - $block['account'] = $account; - $block['params'] = isset($block['params']) ? helper::jsonEncode($block['params']) : ''; - $block['vision'] = $this->config->vision; - if(!isset($block['source'])) $block['source'] = $module; + $block['account'] = $account; + $block['dashboard'] = $dashboard; + $block['order'] = $index; + $block['params'] = isset($block['params']) ? helper::jsonEncode($block['params']) : ''; + $block['vision'] = $this->config->vision; $this->blockTao->insert($block); } - return !dao::isError(); + if(dao::isError()) return false; + + /* Mark this app has init. */ + $this->loadModel('setting')->setItem("$account.$dashboard.common.blockInited@$vision", '1'); + $this->loadModel('setting')->setItem("$account.$dashboard.block.initVersion", $this->config->block->version); + + return true; } /** @@ -483,19 +472,17 @@ class blockModel extends model * 获取区块是否已经初始化. * Fetch block is initiated or not. * - * @param string $module - * @param string $vision - * @param string $section + * @param string $dashboard * @return string */ - public function fetchBlockInitStatus(string $module, string $vision, string $section): string + public function fetchBlockInitStatus(string $dashboard): string { return $this->dao->select('*')->from(TABLE_CONFIG) - ->where('module')->eq($module) - ->andWhere('owner')->eq($this->app->user->account) - ->andWhere('`section`')->eq($section)->fi() - ->andWhere('`key`')->eq('blockInited') - ->andWhere('vision')->eq($vision) - ->fetch('value'); + ->where('module')->eq($dashboard) + ->andWhere('owner')->eq($this->app->user->account) + ->andWhere('`section`')->eq('common') + ->andWhere('`key`')->eq('blockInited') + ->andWhere('vision')->eq($this->config->vision) + ->fetch('value'); } } diff --git a/module/block/test/block.class.php b/module/block/test/block.class.php index 395a02f6a5..09449cc66b 100644 --- a/module/block/test/block.class.php +++ b/module/block/test/block.class.php @@ -205,9 +205,9 @@ class blockTest * @access public * @return string */ - public function getAvailableBlocksTest($module = '', $dashboard = '', $model = '') + public function getAvailableBlocksTest($dashboard = '', $module = '') { - $objects = json_decode($this->objectModel->getAvailableBlocks($module, $dashboard, $model)); + $objects = $this->objectModel->getAvailableBlocks($dashboard,$module); if(dao::isError()) return dao::getError(); diff --git a/module/block/test/model/getavailableblocks.php b/module/block/test/model/getavailableblocks.php index 211d6b9f9c..f2f24d5364 100755 --- a/module/block/test/model/getavailableblocks.php +++ b/module/block/test/model/getavailableblocks.php @@ -2,7 +2,6 @@ getAvailableBlocksTest('project', 'project', 'waterfall'); +$data[0] = $block->getAvailableBlocksTest('waterfallProject'); $data[1] = $block->getAvailableBlocksTest('qa'); r($data[0]) && p('waterfallgantt,projectdynamic') && e('项目计划,最新动态'); // 通过获取项目的可用区块 -r($data[1]) && p('statistic,bug,case,testtask') && e('测试统计,Bug列表,用例列表,版本列表'); // 通过获取测试的可用区块 \ No newline at end of file +r($data[1]) && p('statistic,bug,case,testtask') && e('测试统计,Bug列表,用例列表,版本列表'); // 通过获取测试的可用区块 diff --git a/module/block/test/model/getmydashboard.php b/module/block/test/model/getmydashboard.php index fbf9f48c8d..c8fdf0a052 100644 --- a/module/block/test/model/getmydashboard.php +++ b/module/block/test/model/getmydashboard.php @@ -3,7 +3,11 @@ include dirname(__FILE__, 5) . "/test/lib/init.php"; include dirname(__FILE__, 2) . '/block.class.php'; -su('admin'); +#su('admin'); + +$userModel = new userModel(); +$user = $userModel->identify('admin', 'Asd123456'); +$userModel->login($user); function initData() { diff --git a/module/block/ui/create.html.php b/module/block/ui/create.html.php index b20fca55ea..a1f7aa04c6 100644 --- a/module/block/ui/create.html.php +++ b/module/block/ui/create.html.php @@ -15,6 +15,7 @@ set::title($title); jsVar('dashboard', $dashboard); $paramsRows = array(); +$isMyDashboard = $dashboard == 'my'; foreach($params as $key => $row) { @@ -38,6 +39,8 @@ form on::change('#code', 'getForm'), formGroup ( + set::class($isMyDashboard ? '' : 'hidden'), + set::value($isMyDashboard ? '' : $dashboard), set::label($lang->block->lblModule), set::name('module'), set::control(array diff --git a/module/block/ui/edit.html.php b/module/block/ui/edit.html.php index 726490a3b6..99eee369d3 100644 --- a/module/block/ui/edit.html.php +++ b/module/block/ui/edit.html.php @@ -15,6 +15,7 @@ set::title($title); jsVar('blockID', $block->id); $paramsRows = array(); +$isMyDashboard = $dashboard == 'my'; foreach($params as $key => $row) { @@ -40,7 +41,8 @@ form ( set::label($lang->block->lblModule), set::name('module'), - set::value($block->module), + set::class($isMyDashboard ? '' : 'hidden'), + set::value($isMyDashboard ? $block->module : $dashboard), set::control(array ( 'type' => 'select', diff --git a/module/block/zen.php b/module/block/zen.php index ed6c52ff67..0550be0b9a 100644 --- a/module/block/zen.php +++ b/module/block/zen.php @@ -107,28 +107,19 @@ class blockZen extends block * 处理每个区块以渲染 UI。 * Process each block for render UI. * - * @param object[] $blocks - * @param object|null $project + * @param object[] $blocks + * @param int $projectID * @return object[] */ - protected function processBlockForRender(array $blocks, object $project = null): array + protected function processBlockForRender(array $blocks, int $projectID): array { $acls = $this->app->user->rights['acls']; foreach($blocks as $key => $block) { - if(in_array($block->code, array('waterfallrisk', 'waterfallissue'))) - { - $model = isset($project->model) ? $project->model : 'waterfall'; - if($block->code == 'waterfallrisk' and !helper::hasFeature("{$model}_risk")) continue; - if($block->code == 'waterfallissue' and !helper::hasFeature("{$model}_issue")) continue; - } - - if(in_array($block->code, array('scrumrisk', 'scrumissue'))) - { - $model = isset($project->model) ? $project->model : 'scrum'; - if($block->code == 'scrumrisk' and !helper::hasFeature("{$model}_risk")) continue; - if($block->code == 'scrumissue' and !helper::hasFeature("{$model}_issue")) continue; - } + if($block->code == 'waterfallrisk' and !helper::hasFeature("waterfall_risk")) continue; + if($block->code == 'waterfallissue' and !helper::hasFeature("waterfall_issue")) continue; + if($block->code == 'scrumrisk' and !helper::hasFeature("scrum_risk")) continue; + if($block->code == 'scrumissue' and !helper::hasFeature("scrum_issue")) continue; if(!empty($block->source) and $block->source != 'todo' and !empty($acls['views']) and !isset($acls['views'][$block->source])) { @@ -139,7 +130,7 @@ class blockZen extends block $block->params = json_decode($block->params); if(isset($block->params->num) and !isset($block->params->count)) $block->params->count = $block->params->num; - $this->getBlockMoreLink($block, $project); + $this->getBlockMoreLink($block, $projectID); } return $blocks; @@ -150,14 +141,13 @@ class blockZen extends block * Get the more link of the block. * * @param object $block - * @param object $project + * @param int $projectID * @return void */ - private function getBlockMoreLink(object $block, object $project): void + private function getBlockMoreLink(object $block, int $project): void { - $code = $block->code; - $source = empty($block->source) ? 'common' : $block->source; - $projectID = isset($project->id) ? $project->id : 0; + $code = $block->code; + $source = empty($block->source) ? 'common' : $block->source; $block->blockLink = $this->createLink('block', 'printBlock', "id=$block->id&module=$block->module"); $block->moreLink = ''; diff --git a/module/execution/model.php b/module/execution/model.php index 6895d02cb3..734bce538a 100755 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -290,51 +290,43 @@ class executionModel extends model } /** - * Save the execution id user last visited to session. + * 检查用户是否可以访问当前执行。 + * Check whether access to the current execution is allowed or not. * * @param int $executionID * @param array $executions * @access public * @return int */ - public function saveState($executionID, $executions) + public function checkAccess(int $executionID, array $executions): int { if(defined('TUTORIAL')) return $executionID; - /* When the cookie and session do not exist, get it from the database. */ - if(empty($executionID) and isset($this->config->execution->lastExecution) and isset($executions[$this->config->execution->lastExecution])) + /* When the cookie and session do not exist, get it from the config. */ + if(!$executionID) { - $this->session->set('execution', $this->config->execution->lastExecution, $this->app->tab); - $this->setProjectSession($this->session->execution); - return $this->session->execution; + if(isset($this->cookie->lastExecution)) $executionID = (int)$this->cookie->lastExecution; + if(isset($this->session->execution)) $executionID = (int)$this->session->execution; + if(isset($this->config->execution->lastExecution)) $executionID = (int)$this->config->execution->lastExecution; } - if($executionID == 0 and $this->cookie->lastExecution) + /* If the execution doesn't exist in the list, use the first execution in the list. */ + if(!isset($executions[$executionID])) $executionID = key($executions); + + /* Check execution again. */ + if($executionID) { - /* Execution link is execution-task. */ - $executionID = (int)$this->cookie->lastExecution; - $executionID = in_array($executionID, array_keys($executions)) ? $executionID : key($executions); + $execution = $this->dao->findByID($executionID)->from(TABLE_EXECUTION)->fetch(); + if(empty($execution)) return js::error($this->lang->notFound); + if(strpos(",{$this->app->user->view->sprints},", ",{$executionID},") === false) $this->accessDenied(); } - if($executionID == 0 and $this->session->execution) $executionID = $this->session->execution; - if($executionID == 0) $executionID = key($executions); + /* Save session. */ + $this->session->set('execution', $executionID, $this->app->tab); + $this->setProjectSession($executionID); - $this->session->set('execution', (int)$executionID, $this->app->tab); - - if(!isset($executions[$executionID])) - { - $this->session->set('execution', key($executions), $this->app->tab); - - if($executionID) - { - $execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->andWhere('type')->in('sprint,stage,kanban')->fetch(); - if(empty($execution)) return js::error($this->lang->notFound); - if(strpos(",{$this->app->user->view->sprints},", ",{$executionID},") === false) $this->accessDenied(); - } - } - - $this->setProjectSession($this->session->execution); - return $this->session->execution; + /* Return execution id. */ + return $executionID; } /** diff --git a/module/product/control.php b/module/product/control.php index 95b03d6ee7..7f3cfe4ad2 100755 --- a/module/product/control.php +++ b/module/product/control.php @@ -121,9 +121,6 @@ class product extends control $this->view->users = $this->loadModel('user')->getPairs('noletter'); $this->view->branchID = $branch; $this->view->branchStatus = $this->loadModel('branch')->getByID($branch, 0, 'status'); - $this->view->recTotal = $recTotal; - $this->view->recPerPage = $recPerPage; - $this->view->pageID = $pageID; $this->view->pager = $pager; $this->display(); } diff --git a/module/product/model.php b/module/product/model.php index c378181aec..9ff405ec68 100755 --- a/module/product/model.php +++ b/module/product/model.php @@ -22,6 +22,9 @@ class productModel extends model const OB_PROGRAM = 'program_asc'; const OB_ORDER = 'order_asc'; + const STORY_TYPE_STORY = 'story'; + const STORY_TYPE_REQ = 'requirement'; + /** * Get product module menu. * @@ -323,7 +326,7 @@ class productModel extends model * @param int $programID * @param string|array $append * @param string|int $shadow all | 0 | 1 - * @return array + * @return int[] */ public function getPairs(string $mode = '', int $programID = 0, string|array $append = '', string|int $shadow = 0): array { @@ -333,16 +336,7 @@ class productModel extends model $append = $this->productTao->formatAppendParam($append); $views = $this->app->user->view->products . (empty($append) ? '' : ",{$append}"); - return $this->dao->select("t1.*, IF(INSTR(' closed', t1.status) < 2, 0, 1) AS isClosed")->from(TABLE_PRODUCT)->alias('t1') - ->leftJoin(TABLE_PROGRAM)->alias('t2')->on('t1.program = t2.id') - ->where('t1.vision')->eq($this->config->vision) - ->beginIF(strpos($mode, 'all') === false)->andWhere('t1.deleted')->eq(0)->fi() - ->beginIF($programID)->andWhere('t1.program')->eq($programID)->fi() - ->beginIF(strpos($mode, 'noclosed') !== false)->andWhere('t1.status')->ne('closed')->fi() - ->beginIF(!$this->app->user->admin and $this->config->vision == 'rnd')->andWhere('t1.id')->in($views)->fi() - ->beginIF($shadow !== 'all')->andWhere('t1.shadow')->eq((int)$shadow)->fi() - ->orderBy("isClosed,t1.program_asc,t2.order_asc,t1.line_desc,t1.order_asc") - ->fetchPairs('id', 'name'); + return $this->productTao->fetchPairs($programID, $mode, $views, $shadow); } /** @@ -1753,6 +1747,7 @@ class productModel extends model } /** + * 获取产品统计信息。 * Get product stats. * * @param string $orderBy order_asc|program_asc @@ -1771,64 +1766,39 @@ class productModel extends model $products = strtolower($status) == static::ST_BYSEARCH ? $this->getListBySearch($param) : $this->productTao->getList($programID, $status, 0, $line); if(empty($products)) return array(); - $productKeys = array_keys($products); - $products = $this->productTao->getStatsProducts($productKeys,$programID, $orderBy, $pager); - $stories = $this->productTao->getStoriesTODO($productKeys); - $requirements = $this->productTao->getRequirementsTODO($productKeys); + /* Get stats data. */ + $productKeys = array_keys($products); + $products = $this->productTao->getStatsProducts($productKeys,$programID, $orderBy, $pager); + $finishClosedStory = $this->productTao->getFinishClosedStoryTODO(); + $unclosedStory = $this->productTao->getUnClosedStoryTODO(); + $plans = $this->productTao->getPlansTODO($productKeys); + $releases = $this->productTao->getReleasesTODO($productKeys); + $bugs = $this->productTao->getBugsTODO($productKeys); + $unResolved = $this->productTao->getUnResolvedTODO($productKeys); + $fixedBugs = $this->productTao->getFixedBugsTODO($productKeys); + $closedBugs = $this->productTao->getClosedBugsTODO($productKeys); + $thisWeekBugs = $this->productTao->getThisWeekBugsTODO($productKeys); + $assignToNull = $this->productTao->getAssignToNullTODO($productKeys); + list($stories, $reqs) = $this->productTao->getStatsStoriesAndRequirements($productKeys, $storyType); - /* Padding the stories to sure all products have records. */ - $emptyStory = array_keys($this->lang->story->statusList); - foreach($productKeys as $productID) - { - if(!isset($stories[$productID])) $stories[$productID] = $emptyStory; - if(!isset($requirements[$productID])) $requirements[$productID] = $emptyStory; - } - - /* Padding the stories to sure all status have records. */ - foreach($stories as $key => $story) - { - foreach(array_keys($this->lang->story->statusList) as $status) $story[$status] = isset($story[$status]) ? $story[$status]->count : 0; - $stories[$key] = $story; - } - foreach($requirements as $key => $requirement) - { - foreach(array_keys($this->lang->story->statusList) as $status) $requirement[$status] = isset($requirement[$status]) ? $requirement[$status]->count : 0; - $requirements[$key] = $requirement; - } - - if($storyType == 'requirement') $stories = $requirements; - - $finishClosedStory = $this->productTao->getFinishClosedStoryTODO(); - $unclosedStory = $this->productTao->getUnClosedStoryTODO(); - $plans = $this->productTao->getPlansTODO($productKeys); - $releases = $this->productTao->getReleasesTODO($productKeys); - $bugs = $this->productTao->getBugsTODO($productKeys); - $unResolved = $this->productTao->getUnResolvedTODO($productKeys); - $fixedBugs = $this->productTao->getFixedBugsTODO($productKeys); - $closedBugs = $this->productTao->getClosedBugsTODO($productKeys); - $thisWeekBugs = $this->productTao->getThisWeekBugsTODO($productKeys); - $assignToNull = $this->productTao->getAssignToNullTODO($productKeys); - - - /* Generate statistic array. */ + /* Render statistic result to each product. */ $stats = array(); foreach($products as $key => $product) { $product->stories = $stories[$product->id]; $product->stories['finishClosed'] = isset($finishClosedStory[$product->id]) ? $finishClosedStory[$product->id] : 0; - $product->stories['unclosed'] = isset($unclosedStory[$product->id]) ? $unclosedStory[$product->id] : 0; - - $product->requirements = $requirements[$product->id]; - $product->plans = isset($plans[$product->id]) ? $plans[$product->id] : 0; - $product->releases = isset($releases[$product->id]) ? $releases[$product->id] : 0; - - $product->bugs = isset($bugs[$product->id]) ? $bugs[$product->id] : 0; - $product->unResolved = isset($unResolved[$product->id]) ? $unResolved[$product->id] : 0; - $product->closedBugs = isset($closedBugs[$product->id]) ? $closedBugs[$product->id] : 0; - $product->fixedBugs = isset($fixedBugs[$product->id]) ? $fixedBugs[$product->id] : 0; - $product->thisWeekBugs = isset($thisWeekBugs[$product->id]) ? $thisWeekBugs[$product->id] : 0; - $product->assignToNull = isset($assignToNull[$product->id]) ? $assignToNull[$product->id] : 0; + $product->stories['unclosed'] = isset($unclosedStory[$product->id]) ? $unclosedStory[$product->id] : 0; + $product->requirements = $reqs[$product->id]; + $product->plans = isset($plans[$product->id]) ? $plans[$product->id] : 0; + $product->releases = isset($releases[$product->id]) ? $releases[$product->id] : 0; + $product->bugs = isset($bugs[$product->id]) ? $bugs[$product->id] : 0; + $product->unResolved = isset($unResolved[$product->id]) ? $unResolved[$product->id] : 0; + $product->closedBugs = isset($closedBugs[$product->id]) ? $closedBugs[$product->id] : 0; + $product->fixedBugs = isset($fixedBugs[$product->id]) ? $fixedBugs[$product->id] : 0; + $product->thisWeekBugs = isset($thisWeekBugs[$product->id]) ? $thisWeekBugs[$product->id] : 0; + $product->assignToNull = isset($assignToNull[$product->id]) ? $assignToNull[$product->id] : 0; + /* Calculate product progress. */ $closedTotal = $product->stories['closed'] + $product->requirements['closed']; $allTotal = array_sum($product->stories) + array_sum($product->requirements); $product->progress = empty($closedTotal) ? 0 : round($closedTotal / $allTotal * 100, 1); diff --git a/module/product/tao.php b/module/product/tao.php index 8a84828f27..6999ed408f 100644 --- a/module/product/tao.php +++ b/module/product/tao.php @@ -12,6 +12,31 @@ declare(strict_types=1); class productTao extends productModel { + /** + * 从数据表获取符合条件的id=>name的键值对。 + * Fetch pairs like id=>name. + * + * @param int $programID + * @param string $mode all|noclosed + * @param string $views + * @param string|int $shadow all|0|1 + * @access protected + * @return int[] + */ + protected function fetchPairs(int $programID, string $mode, string $views, string|int $shadow): array + { + return $this->dao->select("t1.*, IF(INSTR(' closed', t1.status) < 2, 0, 1) AS isClosed")->from(TABLE_PRODUCT)->alias('t1') + ->leftJoin(TABLE_PROGRAM)->alias('t2')->on('t1.program = t2.id') + ->where('t1.vision')->eq($this->config->vision) + ->beginIF(strpos($mode, 'all') === false)->andWhere('t1.deleted')->eq(0)->fi() + ->beginIF($programID)->andWhere('t1.program')->eq($programID)->fi() + ->beginIF(strpos($mode, 'noclosed') !== false)->andWhere('t1.status')->ne('closed')->fi() + ->beginIF(!$this->app->user->admin and $this->config->vision == 'rnd')->andWhere('t1.id')->in($views)->fi() + ->beginIF($shadow !== 'all')->andWhere('t1.shadow')->eq((int)$shadow)->fi() + ->orderBy("isClosed,t2.order_asc,t1.line_desc,t1.order_asc") + ->fetchPairs('id', 'name'); + } + /** * 获取产品ID数组中带有项目集信息的产品分页列表。 * Get products with program data that in the ID list. @@ -333,4 +358,46 @@ class productTao extends productModel return implode(',', $append); } + + /** + * 获取用于数据统计的研发需求和用户需求列表。 + * Get dev stories and user requirements for statistics. + * + * @param array $productIDs + * @param string $storyType + * @access protected + * @return [array, array] + */ + protected function getStatsStoriesAndRequirements(array $productIDs, string $storyType): array + { + $stories = $this->getStoriesTODO($productIDs); + $requirements = $this->getRequirementsTODO($productIDs); + + /* Padding the stories to sure all products have records. */ + $emptyStory = array_keys($this->lang->story->statusList); + foreach($productIDs as $productID) + { + if(!isset($stories[$productID])) $stories[$productID] = $emptyStory; + if(!isset($requirements[$productID])) $requirements[$productID] = $emptyStory; + } + + /* Collect count for each status of stories. */ + foreach($stories as $key => $story) + { + foreach(array_keys($this->lang->story->statusList) as $status) $story[$status] = isset($story[$status]) ? $story[$status]->count : 0; + $stories[$key] = $story; + } + + /* Collect count for each status of requirements. */ + foreach($requirements as $key => $requirement) + { + foreach(array_keys($this->lang->story->statusList) as $status) $requirement[$status] = isset($requirement[$status]) ? $requirement[$status]->count : 0; + $requirements[$key] = $requirement; + } + + /* Story type is 'requirement'. */ + if($storyType == static::STORY_TYPE_REQ) $stories = $requirements; + + return [$stories, $requirements]; + } } diff --git a/module/product/test/model/getpairs.php b/module/product/test/model/getpairs.php index 0d68e4706b..20ce6675de 100755 --- a/module/product/test/model/getpairs.php +++ b/module/product/test/model/getpairs.php @@ -15,11 +15,12 @@ pid=1 $product = new productTest('admin'); -r($product->getProductPairs($t_peoduct10['programID'])) && p('11') && e('正常产品11'); // 测试项目集10下的11号产品 -r($product->getProductPairs($t_peoduct10['programID'])) && p('55') && e('多分支产品55'); // 测试项目集10下的55号产品 -r($product->getProductPairs($t_peoduct10['programID'])) && p('99') && e('多平台产品99'); // 测试项目集10下的99号产品 -r($product->getProductPairs($t_noproduct['programID'])) && p() && e('没有数据'); // 测试不存在的项目集 -r($product->getProductPairsCount($t_alproduct['programID'])) && p() && e('120'); // 返回所有产品的数量 -r($product->getProductPairsCount($t_peoduct10['programID'])) && p() && e('10'); // 返回项目集10下的所有产品 -r($product->getNoclosedPairs($t_notclose5['programID'])) && p('6') && e('正常产品6'); // 测试项目集10下的未关闭产品5 -r($product->getNoclosedPairsCount($t_peoduct10['programID'])) && p() && e('6'); // 返回项目集10下的未关闭产品的数量 +r($product->getProductPairs()) && p('11') && e('正常产品11'); // 测试项目集10下的11号产品 +r($product->getProductPairs('all')) && p('55') && e('多分支产品55'); // 测试项目集10下的55号产品 +r($product->getProductPairs('noclosed')) && p('99') && e('多平台产品99'); // 测试项目集10下的99号产品 +r($product->getProductPairs('all', 1)) && p() && e('没有数据'); // 测试不存在的项目集 +r($product->getProductPairs('all', -1)) && p() && e('没有数据'); // 测试不存在的项目集 +r($product->getProductPairs('all', 10001, '1')) && p() && e('没有数据'); // 测试不存在的项目集 +r($product->getProductPairs('noclosed', 1, '22,23')) && p() && e('没有数据'); // 测试不存在的项目集 +r($product->getProductPairs('noclosed', 1, '22,23', 'all')) && p() && e('没有数据'); // 测试不存在的项目集 +r($product->getProductPairs('noclosed', 1, '22,23', '1')) && p() && e('没有数据'); // 测试不存在的项目集 diff --git a/module/product/test/product.class.php b/module/product/test/product.class.php index be624389a4..327760037b 100644 --- a/module/product/test/product.class.php +++ b/module/product/test/product.class.php @@ -237,20 +237,6 @@ class productTest return $pairs; } - /** - * Test get product pairs count. - * - * @param int $programID - * @access public - * @return int - */ - public function getProductPairsCount($programID) - { - $pairsCount = count($this->getProductPairs($programID)); - if($pairsCount == '没有数据') return '0'; - return $pairsCount; - } - /** * Test get all product pairs. * diff --git a/module/product/test/tao/formatappendparam.php b/module/product/test/tao/formatappendparam.php old mode 100644 new mode 100755 diff --git a/module/project/config/form.php b/module/project/config/form.php index 88dad20c42..34fb997981 100644 --- a/module/project/config/form.php +++ b/module/project/config/form.php @@ -1,9 +1,10 @@ project->form = new stdclass(); -$config->project->form->create = array(); -$config->project->form->start = array(); -$config->project->form->close = array(); -$config->project->form->suspend = array(); +$config->project->form->create = array(); +$config->project->form->start = array(); +$config->project->form->close = array(); +$config->project->form->suspend = array(); +$config->project->form->activate = array(); $config->project->form->create['parent'] = array('type' => 'int', 'required' => false, 'default' => ''); $config->project->form->create['name'] = array('type' => 'string', 'required' => true, 'filter' => 'trim'); diff --git a/module/project/control.php b/module/project/control.php index 819857e76a..107da65a74 100755 --- a/module/project/control.php +++ b/module/project/control.php @@ -1803,46 +1803,34 @@ class project extends control } /** + * 激活项目并更新其状态 * Activate a project. * - * @param int $projectID + * @param string $projectID * @access public + * * @return void */ - public function activate($projectID) + public function activate(string $projectID) { - $this->loadModel('action'); - $this->app->loadLang('execution'); $projectID = (int)$projectID; $project = $this->project->getByID($projectID); if(!empty($_POST)) { - $changes = $this->project->activate($projectID); + $postData = form::data($this->config->project->form->activate); + + $postData = $this->projectZen->prepareActivateExtras($projectID, $postData); + + $changes = $this->project->activate($projectID, $postData); if(dao::isError()) return print(js::error(dao::getError())); - if($this->post->comment != '' or !empty($changes)) - { - $actionID = $this->action->create('project', $projectID, 'Activated', $this->post->comment); - $this->action->logHistory($actionID, $changes); - } - $this->executeHooks($projectID); + $comment = strip_tags($this->post->comment, $this->config->allowedTags); + $this->projectZen->responseAfterActivate($projectID, $changes, $comment); return print(js::reload('parent.parent')); } - $newBegin = date('Y-m-d'); - $dateDiff = helper::diffDate($newBegin, $project->begin); - $newEnd = date('Y-m-d', strtotime($project->end) + $dateDiff * 24 * 3600); - - $this->view->title = $this->lang->project->activate; - $this->view->position[] = $this->lang->project->activate; - $this->view->users = $this->loadModel('user')->getPairs('noletter'); - $this->view->actions = $this->action->getList('project', $projectID); - $this->view->newBegin = $newBegin; - $this->view->newEnd = $newEnd; - $this->view->project = $project; - - $this->display(); + $this->projectZen->buildActivateForm($project); } /** diff --git a/module/project/model.php b/module/project/model.php index a3f755b1d8..f9320cb483 100755 --- a/module/project/model.php +++ b/module/project/model.php @@ -198,8 +198,8 @@ class projectModel extends model } /** - * Get a project by id. * 根据项目ID获取项目信息。 + * Get a project by id. * * @param int $projectID * @access public @@ -217,17 +217,18 @@ class projectModel extends model } /** + * 通过影子产品ID获取一条项目记录。 * Get a project by its shadow product. * - * @param int $product + * @param int $productID * @access public - * @return object + * @return object|false */ - public function getByShadowProduct($product) + public function getByShadowProduct(int $productID): object|false { return $this->dao->select('t2.*')->from(TABLE_PROJECTPRODUCT)->alias('t1') ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id') - ->where('t1.product')->eq($product) + ->where('t1.product')->eq($productID) ->andWhere('t2.type')->eq('project') ->limit(1) ->fetch(); @@ -1820,16 +1821,16 @@ class projectModel extends model * @access public * @return void */ - public function unlinkMember($projectID, $account, $removeExecution = 'no') + public function unlinkMember(int $projectID, string $account, string $removeExecution = 'no'): void { - $this->dao->delete()->from(TABLE_TEAM)->where('root')->eq((int)$projectID)->andWhere('type')->eq('project')->andWhere('account')->eq($account)->exec(); + $this->projectTao->unlinkTeamMember($projectID, 'project', $account); $this->loadModel('user')->updateUserView($projectID, 'project', array($account)); if($removeExecution == 'yes') { $executions = $this->loadModel('execution')->getByProject($projectID, 'undone', 0, true); - $this->dao->delete()->from(TABLE_TEAM)->where('root')->in(array_keys($executions))->andWhere('type')->eq('execution')->andWhere('account')->eq($account)->exec(); + $this->projectTao->unlinkTeamMember(array_keys($executions), 'execution', $account); $this->user->updateUserView(array_keys($executions), 'sprint', array($account)); } diff --git a/module/project/tao.php b/module/project/tao.php index f33d0aaf6c..dc0ed93f74 100755 --- a/module/project/tao.php +++ b/module/project/tao.php @@ -220,7 +220,7 @@ class projectTao extends projectModel * @param object $postData * @param object $program * @access protected - * @return bool + * @return bool */ protected function createDocLib(int $projectID, object $project, object $postData, object $program): bool { @@ -269,7 +269,7 @@ class projectTao extends projectModel * @param object $postData * @param object $program * @access protected - * @return bool + * @return bool */ protected function createProduct(int $projectID, object $project, object $postData, object $program): bool { @@ -333,7 +333,7 @@ class projectTao extends projectModel * @param object $rawdata * @param object $program * @access protected - * @return bool + * @return bool */ protected function getLinkedProductsCount(object $project, object $rawdata): int { @@ -355,7 +355,7 @@ class projectTao extends projectModel * * @param int $projectID * @access protected - * @return bool + * @return bool */ protected function addProjectAdmin(int $projectID): bool { @@ -441,16 +441,23 @@ class projectTao extends projectModel return helper::createLink('project', 'testcase', "projectID=%s"); } - protected function buildLinkForTask if($module == 'testtask') - { - if($method == 'browseunits') - { - $link = helper::createLink('project', 'testcase', "projectID=%s"); - } - else - { - $link = helper::createLink('project', 'testtask', "projectID=%s"); - } - } - + /** + * 删除项目团队成员。 + * Delete project team member. + * + * @param array|int $projectIdList + * @param string $type + * @param string $account + * @access protected + * @return bool + */ + protected function unlinkTeamMember(int|array $projectIdList, string $type, string $account): bool + { + $this->dao->delete()->from(TABLE_TEAM) + ->where('root')->in($projectIdList) + ->andWhere('type')->eq($type) + ->andWhere('account')->eq($account) + ->exec(); + return !dao::isError(); + } } diff --git a/module/project/test/model/getbyshadowproduct.php b/module/project/test/model/getbyshadowproduct.php new file mode 100644 index 0000000000..e1c8e471dc --- /dev/null +++ b/module/project/test/model/getbyshadowproduct.php @@ -0,0 +1,45 @@ +#!/usr/bin/env php +id->range('2-5'); + $project->project->range('2-5'); + $project->name->prefix("项目")->range('2-5'); + $project->code->prefix("project")->range('2-5'); + $project->type->range("project"); + $project->gen(4); + + $projectproduct = zdTable('projectproduct'); + $projectproduct->product->range('1-3'); + $projectproduct->project->range('2-4'); + $projectproduct->branch->range("0"); + $projectproduct->gen(3); +} + +/** + +title=测试 projectModel::getByShadowProduct; +timeout=0 +cid=1 + +- 执行projectTester模块的testGetByShadowPro方法,参数是2属性code @project3 + +- 执行projectTester模块的testGetByShadowPro方法,参数是5属性code @0 + +- 执行projectTester模块的testGetByShadowPro方法,参数是aaa属性code @($productID) must be of type int + + + +*/ + +initData(); + +$projectTester = new Project(); +r($projectTester->testGetByShadowProduct(2)) && p('code') && e('project3'); //获取ID为1的产品关联的项目 +r($projectTester->testGetByShadowProduct(5)) && p('code') && e('0'); //获取ID为4的产品关联的项目 +r($projectTester->testGetByShadowProduct('aaa')) && p('code') && e('($productID) must be of type int'); //获取ID为字符串的产品的项目 diff --git a/module/project/test/model/unlinkmember.php b/module/project/test/model/unlinkmember.php index c965e2f87b..82fb13c4a4 100755 --- a/module/project/test/model/unlinkmember.php +++ b/module/project/test/model/unlinkmember.php @@ -1,29 +1,65 @@ #!/usr/bin/env php id->range('2-9'); + $team->root->range('2,7-9{3}'); + $team->type->range('project,execution{7}'); + $team->account->range('admin,user1,user2,admin'); + $team->limited->range('no'); + $team->join->range('2023-04-02'); + $team->days->range('7'); + $team->hours->range('7'); + $team->gen(8); + + $execution = zdTable('project'); + $execution->id->range('2,6,8,9'); + $execution->project->range('2,3'); + $execution->name->prefix('项目')->range('8,9'); + $execution->code->prefix('project')->range('8,9'); + $execution->type->range('project{2},sprint,kanban'); + $execution->status->range('doing,suspended,closed'); + $execution->gen(4); +} /** -title=测试 projectModel->unlinkMember(); +title=测试 projectModel::getByID; +timeout=0 cid=1 pid=1 -查看移除团队成员之前的ID为11的项目团队成员数量 >> 3 -查看移除团队成员之后的ID为11的项目团队成员数量 >> 2 -查看项目ID为11的团队成员的真实姓名 >> 产品经理92 -查看项目ID为11的团队成员的真实姓名 >> 产品经理92 +- 执行$diffMembers @1 >> 1 + >> admin +- 执行$beforeMembers['admin'] @admin >> 0 + >> 1 +- 执行$afterMembers['admin'] @0 >> admin + >> 0 */ global $tester; $tester->loadModel('project'); -$beforeMembers = $tester->project->getTeamMemberPairs(11); -$tester->project->unlinkMember(11, 'admin', false); -$afterMembers = $tester->project->getTeamMemberPairs(11); +initData(); -r(count($beforeMembers)) && p('') && e('3'); // 查看移除团队成员之前的ID为11的项目团队成员数量 -r(count($afterMembers)) && p('') && e('2'); // 查看移除团队成员之后的ID为11的项目团队成员数量 -r($beforeMembers) && p('pm92') && e('产品经理92'); // 查看项目ID为11的团队成员的真实姓名 -r($afterMembers) && p('pm92') && e('产品经理92'); // 查看项目ID为11的团队成员的真实姓名 +$beforeMembers = $tester->project->getTeamMemberPairs(2); +$beforeExecutionMembers = $tester->project->getTeamMemberPairs(8); + +$tester->project->unlinkMember(2, 'admin', 'yes'); + +$afterMembers = $tester->project->getTeamMemberPairs(2); +$afterExecutionMembers = $tester->project->getTeamMemberPairs(6); +$diffMembers = count($beforeMembers) - count($afterMembers); +$diffExecutionMembers = count($beforeExecutionMembers) - count($afterExecutionMembers); + +r($diffMembers) && p() && e('1'); +r($beforeMembers['admin']) && p() && e('admin'); +r($afterMembers['admin']) && p() && e('0'); +r($diffExecutionMembers) && p() && e('1'); +r($beforeExecutionMembers['admin']) && p() && e('admin'); +r($afterExecutionMembers['admin']) && p() && e('0'); diff --git a/module/project/test/project.class.php b/module/project/test/project.class.php index f6c736f0de..537cd498ab 100644 --- a/module/project/test/project.class.php +++ b/module/project/test/project.class.php @@ -61,6 +61,18 @@ class Project return $this->triggerMethod('fetchProjectInfo', array('projectID' => $projectID)); } + /** + * Test getByShadowProduct function. + * + * @param int $productID + * @access public + * @return string|bool|object + */ + public function testGetByShadowProduct($productID) + { + return $this->triggerMethod('getByShadowProduct', array('productID' => $productID)); + } + /** * Test start a project. * diff --git a/module/project/zen.php b/module/project/zen.php index abdd7216ee..b2b342f853 100644 --- a/module/project/zen.php +++ b/module/project/zen.php @@ -50,6 +50,14 @@ class projectZen extends project return $project; } + /** + * Check product and branch not empty. + * + * @param object $project + * @param object $rawdata + * @access protected + * @return bool + */ private function checkProductAndBranch(object $project, object $rawdata): bool { $linkedProductsCount = $this->project->getLinkedProductsCount($project, $rawdata); @@ -84,6 +92,14 @@ class projectZen extends project return true; } + /** + * Check days and budget by rules. + * + * @param object $project + * @param object $rawdata + * @access protected + * @return bool + */ private function checkDaysAndBudget(object $project, object $rawdata): bool { /* Judge workdays is legitimate. */ @@ -115,6 +131,14 @@ class projectZen extends project return true; } + /** + * Check product name unique and not empty. + * + * @param object $project + * @param object $rawdata + * @access protected + * @return bool + */ private function checkProductNameUnqiue(object $project, object $rawdata): bool { /* When select create new product, product name cannot be empty and duplicate. */ @@ -486,4 +510,75 @@ class projectZen extends project $this->display(); } + + /** + * Send variables to activate page. + * + * @param object $project + * @access protected + * + * @return void + */ + protected function buildActivateForm(object $project): void + { + $newBegin = date('Y-m-d'); + $dateDiff = helper::diffDate($newBegin, $project->begin); + $newEnd = date('Y-m-d', strtotime($project->end) + $dateDiff * 24 * 3600); + + $this->view->title = $this->lang->project->activate; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->actions = $this->loadModel('action')->getList('project', $project->id); + $this->view->newBegin = $newBegin; + $this->view->newEnd = $newEnd; + $this->view->project = $project; + $this->display(); + } + + /** + * After activateing the project, do other operations. + * + * @param int $projectID + * @param array $changes + * @param string $comment + * + * @access protected + * @return void + */ + protected function responseAfterActivate(int $projectID, array $changes, string $comment): void + { + if($this->post->comment != '' or !empty($changes)) + { + $actionID = $this->action->create('project', $projectID, 'Activated', $this->post->comment); + $this->action->logHistory($actionID, $changes); + } + + $this->executeHooks($projectID); + } + + /** + * Append extras data to post data. + * + * @param int $iprojectID + * @param object $postData + * + * @access protected + * @return object + */ + protected function prepareActivateExtras(int $projectID, object $postData): object + { + $oldProject = $this->project->getByID($projectID); + + $editorIdList=$this->config->project->editor->activate['id']; + if($this->app->rawModule=='program')$editorIdList=$this->config->program->editor->activate['id']; + + return $postData->add('id',$projectID) + ->setDefault('realEnd','') + ->setDefault('status','doing') + ->setDefault('lastEditedBy',$this->app->user->account) + ->setDefault('lastEditedDate',helper::now()) + ->setIF(!helper::isZeroDate($oldProject->realBegan),'realBegan',helper::today()) + ->stripTags($editorIdList,$this->config->allowedTags) + ->remove('comment,readjustTime,readjustTask') + ->get(); + } } diff --git a/module/qa/control.php b/module/qa/control.php index c34799dce0..597ec3359f 100644 --- a/module/qa/control.php +++ b/module/qa/control.php @@ -30,6 +30,6 @@ class qa extends control $this->view->title = $this->lang->qa->index; $this->view->position[] = $this->lang->qa->index; $this->view->products = $products; - $this->display(); + echo $this->fetch('block', 'dashboard', 'dashboard=qa'); } } diff --git a/module/todo/config/form.php b/module/todo/config/form.php index d4ee2ecc3d..d4db331850 100644 --- a/module/todo/config/form.php +++ b/module/todo/config/form.php @@ -35,3 +35,8 @@ $config->todo->assignTo->form['assignedTo'] = array('required' => true, 'type $config->todo->batchClose = new stdclass; $config->todo->batchClose->form = array(); $config->todo->batchClose->form['todoIDList'] = array('required' => true, 'type' => 'array'); + +$config->todo->batchEdit = new stdClass; +$config->todo->batchEdit->form = array(); +$config->todo->batchEdit->form['todoIDList'] = array('required' => true, 'type' => 'array'); +$config->todo->batchEdit->form['names'] = array('required' => true, 'type' => 'array'); \ No newline at end of file diff --git a/module/todo/control.php b/module/todo/control.php index c03295c8f6..db6aecfe0c 100755 --- a/module/todo/control.php +++ b/module/todo/control.php @@ -2,7 +2,7 @@ declare(strict_types=1); /** - * The control file of example module of ZenTaoPMS. + * The control file of todo module of ZenTaoPMS. * * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net) * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html) @@ -29,15 +29,15 @@ class todo extends control } /** - * 创建待办 + * 创建待办。 * Create a todo. * * @param string $date * @param string $from todo|feedback|block * @access public - * @return int|void + * @return int */ - public function create(string $date = 'today', string $from = 'todo') + public function create(string $date = 'today', string $from = 'todo'): int { if($date == 'today') $date = date::today(); @@ -56,7 +56,7 @@ class todo extends control if($from == 'block') { - $todo = $this->todo->getById($todoID); + $todo = $this->todo->getByID($todoID); $todo->begin = date::formatTime($todo->begin); return $this->send(array('result' => 'success', 'id' => $todoID, 'name' => $todo->name, 'pri' => $todo->pri, 'priName' => $this->lang->todo->priList[$todo->pri], 'time' => date(DT_DATE4, strtotime($todo->date)) . ' ' . $todo->begin)); } @@ -69,12 +69,7 @@ class todo extends control unset($this->lang->todo->typeList['cycle']); - $this->view->title = $this->lang->todo->common . $this->lang->colon . $this->lang->todo->create; - $this->view->date = date('Y-m-d', strtotime($date)); - $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); - $this->view->time = date::now(); - $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noempty'); - $this->display(); + $this->buildCreateForm($date); } /** @@ -128,17 +123,16 @@ class todo extends control } /** - * 编辑待办数据 + * 编辑待办数据。 * Edit a todo. * * @param string $todoID * @access public - * @return void + * @return int */ - public function edit(string $todoID) + public function edit(string $todoID): int { - $todoID = (int)$todoID; - + $todoID = (int)$todoID; if(!empty($_POST)) { $formData = form::data($this->config->todo->edit->form); @@ -164,18 +158,12 @@ class todo extends control } /* Judge a private todo or not, If private, die. */ - $todo = $this->todo->getById($todoID); + $todo = $this->todo->getByID($todoID); if($todo->private and $this->app->user->account != $todo->account) return print('private'); unset($this->lang->todo->typeList['cycle']); - $todo->date = date("Y-m-d", strtotime($todo->date)); - $this->view->title = $this->lang->todo->common . $this->lang->colon . $this->lang->todo->edit; - $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); - $this->view->todo = $todo; - $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noempty'); - - $this->display(); + $this->renderEditData($todoID); } /** @@ -188,113 +176,26 @@ class todo extends control * @access public * @return void */ - public function batchEdit($from = '', $type = 'today', $userID = '', $status = 'all') + public function batchEdit(string $from = '', string $type = 'today', string $userID = '', string $status = 'all') { /* Get form data for my-todo. */ - if($from == 'myTodo') + if($from == 'myTodo') $this->todoZen->batchEditFromMyTodo($type, $userID, $status); + if($from == 'todoBatchEdit') { - /* Initialize vars. */ - $editedTodos = array(); - $todoIDList = array(); - $columns = 7; - - if($userID == '') $userID = $this->app->user->id; - $user = $this->loadModel('user')->getById($userID, 'id'); - $account = $user->account; - - $reviews = array(); - if($this->config->edition == 'max') $reviews = $this->loadModel('review')->getUserReviewPairs($account); - $allTodos = $this->todo->getList($type, $account, $status); - if($this->post->todoIDList) $todoIDList = $this->post->todoIDList; - - /* Initialize todos whose need to edited. */ - foreach($allTodos as $todo) - { - if(in_array($todo->id, $todoIDList)) - { - $editedTodos[$todo->id] = $todo; - if($todo->type != 'custom') - { - if(!isset($objectIDList[$todo->type])) $objectIDList[$todo->type] = array(); - $objectIDList[$todo->type][$todo->objectID] = $todo->objectID; - } - } - } - - $bugs = $this->bug->getUserBugPairs($account, true, 0, '', '', isset($objectIDList['bug']) ? $objectIDList['bug'] : ''); - $tasks = $this->task->getUserTaskPairs($account, 'wait,doing', '', isset($objectIDList['task']) ? $objectIDList['task'] : ''); - $storys = $this->loadModel('story')->getUserStoryPairs($account, 10, 'story', '', isset($objectIDList['story']) ? $objectIDList['story'] : ''); - if($this->config->edition != 'open') $this->view->feedbacks = $this->loadModel('feedback')->getUserFeedbackPairs($account, '', isset($objectIDList['feedback']) ? $objectIDList['feedback'] : ''); - if($this->config->edition == 'max') - { - $issues = $this->loadModel('issue')->getUserIssuePairs($account); - $risks = $this->loadmodel('risk')->getUserRiskPairs($account); - $opportunities = $this->loadmodel('opportunity')->getUserOpportunityPairs($account); - } - $testtasks = $this->loadModel('testtask')->getUserTestTaskPairs($account); - - /* Judge whether the edited todos is too large. */ - $countInputVars = count($editedTodos) * $columns; - $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); - - unset($this->lang->todo->typeList['cycle']); - /* Set Custom*/ - foreach(explode(',', $this->config->todo->list->customBatchEditFields) as $field) $customFields[$field] = $this->lang->todo->$field; - $this->view->customFields = $customFields; - $this->view->showFields = $this->config->todo->custom->batchEditFields; - - /* Assign. */ - $title = $this->lang->todo->common . $this->lang->colon . $this->lang->todo->batchEdit; - $position[] = html::a($this->createLink('my', 'todo'), $this->lang->my->todo); - $position[] = $this->lang->todo->common; - $position[] = $this->lang->todo->batchEdit; - - if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); - $this->view->bugs = $bugs; - $this->view->tasks = $tasks; - $this->view->storys = $storys; - if($this->config->edition == 'max') - { - $this->view->issues = $issues; - $this->view->risks = $risks; - $this->view->opportunities = $opportunities; - } - $this->view->reviews = $reviews; - $this->view->testtasks = $testtasks; - $this->view->editedTodos = $editedTodos; - $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); - $this->view->time = date::now(); - $this->view->title = $title; - $this->view->position = $position; - $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noempty'); - - $this->display(); - } - /* Get form data from todo-batchEdit. */ - elseif($from == 'todoBatchEdit') - { - $allChanges = $this->todo->batchUpdate(); - foreach($allChanges as $todoID => $changes) - { - if(empty($changes)) continue; - - $actionID = $this->loadModel('action')->create('todo', $todoID, 'edited'); - $this->action->logHistory($actionID, $changes); - } - - return print(js::locate($this->session->todoList, 'parent')); + $formData = form::data($this->config->todo->batchEdit->form); + $this->todoZen->batchEditFromTodoBatchEdit($formData); } } /** - * 开启待办事项 + * 开启待办事项。 * Start a todo. * * @param string $todoID * @access public - * @return void + * @return int */ - public function start(string $todoID) + public function start(string $todoID): int { $todoID = (int)$todoID; $todo = $this->todo->getByID($todoID); @@ -303,79 +204,86 @@ class todo extends control if(in_array($todo->type, array('bug', 'task', 'story'))) return $this->todoZen->printConfirm($todo); if(isonlybody()) return print(js::reload('parent.parent')); - echo js::reload('parent'); + return print(js::reload('parent')); } /** - * 激活待办事项 + * 激活待办事项。 * Activated a todo. * * @param string $todoID * @access public - * @return void + * @return int */ - public function activate(string $todoID) + public function activate(string $todoID): int { $todoID = (int)$todoID; $todo = $this->todo->getByID($todoID); - if($todo->status == 'done' or $todo->status == 'closed') $this->todo->activate($todoID); + if($todo->status == 'done' || $todo->status == 'closed') + { + $isActivate = $this->todo->activate($todoID); + if($isActivate === false) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + } if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); if(isonlybody()) return print(js::reload('parent.parent')); - echo js::reload('parent'); + return print(js::reload('parent')); } /** + * 关闭待办。 * Closed todo. * - * @param $todoID - * + * @param string $todoID * @access public - * @return void + * @return int */ - public function close($todoID) + public function close(string $todoID): int { - $todo = $this->todo->getById($todoID); + $todoID = (int)$todoID; + $todo = $this->todo->getByID($todoID); if($todo->status == 'done') $this->todo->close($todoID); if(isonlybody()) return print(js::reload('parent.parent')); - echo js::reload('parent'); + + return print(js::reload('parent')); } /** - * 指派待办. + * 指派待办。 * Assign todo. * * @param string $todoID * @access public - * @return void + * @return int */ - public function assignTo(string $todoID) + public function assignTo(string $todoID): int { + $todoID = (int)$todoID; if(!empty($_POST)) { $formData = form::data($this->config->todo->assignTo->form); $todo = $this->todoZen->beforeAssignTo($formData); - $todo->id = (int)$todoID; - $res = $this->todoZen->doAssignTo($todo); - if(!$res) return print(js::error(dao::getError())); + $todo->id = $todoID; + $isAssigned = $this->todoZen->doAssignTo($todo); + if(!$isAssigned) return print(js::error(dao::getError())); return print(js::reload('parent.parent')); } - $this->view->todo = $this->todo->getById((int)$todoID); + $this->view->todo = $this->todo->getByID($todoID); $this->view->members = $this->loadModel('user')->getPairs('noclosed|noempty|nodeleted'); $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); - $this->view->actions = $this->loadModel('action')->getList('todo', (int)$todoID); + $this->view->actions = $this->loadModel('action')->getList('todo', $todoID); $this->view->users = $this->user->getPairs('noletter'); $this->view->time = date::now(); $this->display(); } /** - * 获取待办的信息. - * Get info of todo . + * 获取待办的信息。 + * Get info of todo. * * @param string $todoID * @param string $from my|company @@ -383,9 +291,9 @@ class todo extends control * @access public * @return void */ - public function view(string $todoID,string $from = 'company') + public function view(string $todoID, string $from = 'company') { - $todo = $this->todo->getById((int)$todoID, true); + $todo = $this->todo->getByID((int)$todoID, true); if(!$todo) { @@ -416,7 +324,6 @@ class todo extends control if(!isset($this->session->project)) $this->session->set('project', (int)key($projects)); $this->view->title = $account == $todo->account ? "{$this->lang->todo->common} #$todo->id $todo->name" : $this->lang->todo->common; - $this->view->position[] = $this->lang->todo->view; $this->view->todo = $todo; $this->view->times = date::buildTimeList((int)$this->config->todo->times->begin, (int)$this->config->todo->times->end, 5); $this->view->from = $from; @@ -427,7 +334,7 @@ class todo extends control } /** - * 删除待办. + * 删除待办。 * Delete a todo. * * @param string $todoID @@ -437,28 +344,23 @@ class todo extends control */ public function delete(string $todoID, string $confirm = 'no') { + $todoID = (int)$todoID; if($confirm == 'no') { - return print(js::confirm($this->lang->todo->confirmDelete, $this->createLink('todo', 'delete', "todoID=(int)$todoID&confirm=yes"))); + return print(js::confirm($this->lang->todo->confirmDelete, $this->createLink('todo', 'delete', "todoID={$todoID}&confirm=yes"))); } else { - $this->todo->delete(TABLE_TODO, (int)$todoID); + $this->todo->delete(TABLE_TODO, $todoID); - /* if ajax request, send result. */ - if($this->server->ajax) + if(helper::isAjaxRequest()) { - $response = array(); + $response = array('result' => 'success', 'message' => ''); if(dao::isError()) { $response['result'] = 'fail'; $response['message'] = dao::getError(); } - else - { - $response['result'] = 'success'; - $response['message'] = ''; - } return $this->send($response); } @@ -480,7 +382,7 @@ class todo extends control */ public function finish(string $todoID) { - $todo = $this->todo->getById((int)$todoID); + $todo = $this->todo->getByID((int)$todoID); if($todo->status != 'done' && $todo->status != 'closed') $this->todo->finish((int)$todoID); if(in_array($todo->type, array('bug', 'task', 'story'))) @@ -488,15 +390,20 @@ class todo extends control $confirmNote = 'confirm' . ucfirst($todo->type); $okTarget = isonlybody() ? 'parent' : 'window.parent.$.apps.open'; $confirmURL = $this->createLink($todo->type, 'view', "id=$todo->objectID"); + if($todo->type == 'bug') $app = 'qa'; if($todo->type == 'task') $app = 'execution'; if($todo->type == 'story') $app = 'product'; - $cancelURL = $this->server->HTTP_REFERER; + $cancelURL = $this->server->http_referer; if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'message' => sprintf($this->lang->todo->$confirmNote, $todo->objectID), 'locate' => $confirmURL)); return print(strpos($cancelURL, 'calendar') ? json_encode(array(sprintf($this->lang->todo->$confirmNote, $todo->objectID), $confirmURL)) : js::confirm(sprintf($this->lang->todo->$confirmNote, $todo->objectID), $confirmURL, $cancelURL, $okTarget, 'parent', $app)); } - if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); + if(defined('RUN_MODE') && RUN_MODE == 'api') + { + $this->send(array('status' => 'success')); + return; + } if(isonlybody()) return print(js::reload('parent.parent')); echo js::reload('parent'); } @@ -518,8 +425,8 @@ class todo extends control if($todo->status == 'done' || $todo->status == 'closed') unset($todoList[$todoID]); } - $res = $this->todo->batchFinish((array)array_keys($todoList)); - if(!$res) return false; + $isBatchFinished = $this->todo->batchFinish(array_keys($todoList)); + if(!$isBatchFinished) return false; return print(js::reload('parent')); } @@ -529,27 +436,27 @@ class todo extends control * Batch close todos. The status of todo which need to close should be done. * * @access public - * @return void + * @return int */ - public function batchClose(): void + public function batchClose(): int { $waitIdList = array(); $todoIdlist = form::data($this->config->todo->batchClose->form)->get('todoIDList'); foreach($todoIdlist as $todoID) { $todoID = (int) $todoID; - $todo = $this->todo->getById($todoID); + $todo = $this->todo->getByID($todoID); if($todo->status == 'done') $this->todo->close($todoID); if($todo->status != 'done' and $todo->status != 'closed') $waitIdList[] = $todoID; } if(!empty($waitIdList)) echo js::alert(sprintf($this->lang->todo->unfinishedTodo, implode(',', $waitIdList))); - echo js::reload('parent'); + return print(js::reload('parent')); } /** * 修改选中待办的日期。 - * Import selected todoes to today. + * Import selected todos to today. * * @param string $todoID * @access public @@ -559,10 +466,10 @@ class todo extends control { $todoIDList = $_POST ? $this->post->todoIDList : array($todoID); $date = !empty($_POST['date']) ? $_POST['date'] : date::today(); - if(!$date || !$todoIDList) $this->locate((string)$this->session->todoList); + if(!$date || !$todoIDList) return $this->locate((string)$this->session->todoList); $this->todo->editDate((array)$todoIDList, (string)$date); - $this->locate((string)$this->session->todoList); + return this->locate((string)$this->session->todoList); } /** @@ -577,7 +484,7 @@ class todo extends control { if($_POST) { - $user = $this->loadModel('user')->getById($userID, 'id'); + $user = $this->loadModel('user')->getByID($userID, 'id'); $account = $user->account; $todoLang = $this->lang->todo; diff --git a/module/todo/model.php b/module/todo/model.php index bc493f7b95..eb136fe9a6 100755 --- a/module/todo/model.php +++ b/module/todo/model.php @@ -2,7 +2,7 @@ declare(strict_types=1); /** - * The model file of example module of ZenTaoPMS. + * The model file of todo module of ZenTaoPMS. * * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net) * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html) @@ -128,7 +128,7 @@ class todoModel extends model } /** - * 更新待办数据 + * 更新待办数据。 * update a todo. * * @param int $todoID @@ -138,7 +138,7 @@ class todoModel extends model */ public function update(int $todoID, object $todo): array|false { - $oldTodo = $this->dao->findById($todoID)->from(TABLE_TODO)->fetch(); + $oldTodo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch(); if(!$this->todoTao->updateRow($todoID, $todo)) return false; @@ -271,8 +271,8 @@ class todoModel extends model { foreach($todoIDList as $todoID) { - $res = $this->dealFinishData($todoID); - if(!$res) return $res; + $finishResult = $this->dealFinishData($todoID); + if(!$finishResult) return $finishResult; } return true; } @@ -288,20 +288,20 @@ class todoModel extends model */ public function getByID(int $todoID, $setImgSize = false): object|false { - $todo = $this->dao->findById($todoID)->from(TABLE_TODO)->fetch(); + $todo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch(); if(!$todo) return false; $todo = $this->loadModel('file')->replaceImgURL((object)$todo, 'desc'); if($setImgSize) $todo->desc = $this->file->setImgSize($todo->desc); - if($todo->type == 'story') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_STORY)->fetch('title'); - if($todo->type == 'task') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_TASK)->fetch('name'); - if($todo->type == 'bug') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_BUG)->fetch('title'); - if($todo->type == 'issue' and $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_ISSUE)->fetch('title'); - if($todo->type == 'risk' and $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_RISK)->fetch('name'); - if($todo->type == 'opportunity' and $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_OPPORTUNITY)->fetch('name'); - if($todo->type == 'review' and $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_REVIEW)->fetch('title'); - if($todo->type == 'testtask') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_TESTTASK)->fetch('name'); - if($todo->type == 'feedback') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_FEEDBACK)->fetch('title'); + if($todo->type == 'story') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_STORY)->fetch('title'); + if($todo->type == 'task') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_TASK)->fetch('name'); + if($todo->type == 'bug') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_BUG)->fetch('title'); + if($todo->type == 'issue' and $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_ISSUE)->fetch('title'); + if($todo->type == 'risk' and $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_RISK)->fetch('name'); + if($todo->type == 'opportunity' and $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_OPPORTUNITY)->fetch('name'); + if($todo->type == 'review' and $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_REVIEW)->fetch('title'); + if($todo->type == 'testtask') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_TESTTASK)->fetch('name'); + if($todo->type == 'feedback') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_FEEDBACK)->fetch('title'); $todo->date = str_replace('-', '', $todo->date); return $todo; @@ -412,15 +412,15 @@ class todoModel extends model while($todo = $stmt->fetch()) { - if($todo->type == 'story') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_STORY)->fetch('title'); - if($todo->type == 'task') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_TASK)->fetch('name'); - if($todo->type == 'bug') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_BUG)->fetch('title'); - if($todo->type == 'testtask') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_TESTTASK)->fetch('name'); - if($todo->type == 'issue' && $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_ISSUE)->fetch('title'); - if($todo->type == 'risk' && $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_RISK)->fetch('name'); - if($todo->type == 'opportunity' && $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_OPPORTUNITY)->fetch('name'); - if($todo->type == 'review' && $this->config->edition == 'max') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_REVIEW)->fetch('title'); - if($todo->type == 'feedback' and $this->config->edition != 'open') $todo->name = $this->dao->findById($todo->objectID)->from(TABLE_FEEDBACK)->fetch('title'); + if($todo->type == 'story') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_STORY)->fetch('title'); + if($todo->type == 'task') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_TASK)->fetch('name'); + if($todo->type == 'bug') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_BUG)->fetch('title'); + if($todo->type == 'testtask') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_TESTTASK)->fetch('name'); + if($todo->type == 'issue' && $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_ISSUE)->fetch('title'); + if($todo->type == 'risk' && $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_RISK)->fetch('name'); + if($todo->type == 'opportunity' && $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_OPPORTUNITY)->fetch('name'); + if($todo->type == 'review' && $this->config->edition == 'max') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_REVIEW)->fetch('title'); + if($todo->type == 'feedback' and $this->config->edition != 'open') $todo->name = $this->dao->findByID($todo->objectID)->from(TABLE_FEEDBACK)->fetch('title'); $todo->begin = date::formatTime($todo->begin); $todo->end = date::formatTime($todo->end); @@ -556,18 +556,16 @@ class todoModel extends model { $isClosed = $this->todoTao->closeTodo($todoID); - if($isClosed) - { - $this->loadModel('action')->create('todo', $todoID, 'closed', '', 'closed'); + if(!$isClosed) return false; - if($this->config->edition == 'biz' || $this->config->edition == 'max') - { - $feedbackID = $this->dao->select('objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->andWhere('type')->eq('feedback')->fetch('objectID'); - if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'closed'); - } - return true; + $this->loadModel('action')->create('todo', $todoID, 'closed', '', 'closed'); + + if($this->config->edition == 'biz' || $this->config->edition == 'max') + { + $feedbackID = $this->dao->select('objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->andWhere('type')->eq('feedback')->fetch('objectID'); + if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'closed'); } - return false; + return true; } /** @@ -656,25 +654,24 @@ class todoModel extends model $todo->finishedBy = $this->app->user->account; $this->todoTao->updateRow($todoID, $todo); - if(!dao::isError()) - { - $this->loadModel('action')->create('todo', $todoID, 'finished', '', 'done'); + if(dao::isError()) return false; - if(($this->config->edition == 'biz' || $this->config->edition == 'max')) - { - $todo = $this->todoTao->fetch($todoID); - $feedbackID = $todo->idvalue ?? '' ; - if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'done'); - } - return true; + $this->loadModel('action')->create('todo', $todoID, 'finished', '', 'done'); + + if(($this->config->edition == 'biz' || $this->config->edition == 'max')) + { + $todo = $this->todoTao->fetch($todoID); + $feedbackID = $todo->idvalue ?? '' ; + if($feedbackID) $this->loadModel('feedback')->updateStatus('todo', $feedbackID, 'done'); } - return false; + return true; } - /** 修改待办事项的时间。 + /** + * 修改待办事项的时间。 * Edit the date of todo. * - * @param array $idList + * @param array $todoIDList * @param string $date * @access public * @return int diff --git a/module/todo/tao.php b/module/todo/tao.php index a6d5091f6b..3a4c3f043b 100644 --- a/module/todo/tao.php +++ b/module/todo/tao.php @@ -75,11 +75,10 @@ class todoTao extends todoModel ->where('id')->eq($todoID) ->exec(); return !dao::isError(); - } - /* - * 处理要创建的todo的数据 + /** + * 处理要创建的todo的数据。 * Process the data for the todo to be created. * * @param object $todoData @@ -118,13 +117,13 @@ class todoTao extends todoModel } if(empty($todoData->cycle)) unset($todoData->config); - $todoData = $this->loadModel('file')->processImgURL($todoData, $this->config->todo->editor->create['id'], $this->post->uid); - return $todoData; + return $this->loadModel('file')->processImgURL($todoData, $this->config->todo->editor->create['id'], $this->post->uid); } /** - * 获取周期待办列表 - * Get cycle list. + * 获取周期待办列表。 + * Get cycle list. + * * @param array $todoList * @param string $orderBy * @access protected @@ -132,18 +131,18 @@ class todoTao extends todoModel */ protected function getCycleList(array $todoList, string $orderBy = 'date_asc'): array { - return $this->dao->select('*') - ->from(TABLE_TODO)->where('type')->eq('cycle') + return $this->dao->select('*')->from(TABLE_TODO) + ->where('type')->eq('cycle') ->andWhere('deleted')->eq('0') ->andWhere('objectID')->in(array_keys($todoList)) ->orderBy($orderBy) ->fetchAll('objectID'); } - /** * 通过待办构建周期待办数据 * Build cycle todo. + * * @param object $todo * @access protected * @return stdclass @@ -168,9 +167,9 @@ class todoTao extends todoModel } /** - * 通过周期待办,获取要生成待办的日期 - * + * 通过周期待办,获取要生成待办的日期。 * Gets the date by the cycle todo. + * * @param object $todo * @param object|string $lastCycle * @param string $today @@ -207,8 +206,7 @@ class todoTao extends todoModel } /** - * 通过周期待办,获取要生成每日待办的日期 - * + * 通过周期待办,获取要生成每日待办的日期。 * Gets the daily todo date by the cycle todo. * * @param object $todo @@ -305,6 +303,7 @@ class todoTao extends todoModel return false; } $todoData->config['beforeDays'] = (int)$todoData->config['beforeDays']; + $todoData->config = json_encode($todoData->config); $todoData->type = 'cycle'; @@ -317,11 +316,11 @@ class todoTao extends todoModel * * @param array $idList * @param string $date - * @return int + * @return bool */ - protected function updateDate(array $todoIdList, string $date): int + protected function updateDate(array $todoIdList, string $date): bool { $this->dao->update(TABLE_TODO)->set('date')->eq($date)->where('id')->in($todoIdList)->exec(); - return dao::isError() ? 0 : 1; + return !dao::isError(); } } diff --git a/module/todo/test/model/close.php b/module/todo/test/model/close.php index b583b0e319..42890cbbcd 100755 --- a/module/todo/test/model/close.php +++ b/module/todo/test/model/close.php @@ -3,7 +3,6 @@ declare(strict_types=1); include dirname(__FILE__, 5) . "/test/lib/init.php"; -include dirname(__FILE__, 2) . '/todo.class.php'; su('admin'); /** diff --git a/module/todo/ui/assignto.html.php b/module/todo/ui/assignto.html.php index 0feb145a36..a77bf61712 100755 --- a/module/todo/ui/assignto.html.php +++ b/module/todo/ui/assignto.html.php @@ -2,14 +2,13 @@ declare(strict_types=1); namespace zin; /** - * The batch create view of todo module of ZenTaoPMS. + * AssignTo view of todo module of ZenTaoPMS. * - * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com) - * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html) + * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net) + * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html) * @author Yue Liu * @package todo - * @version $Id: create.html.php 2741 2012-04-07 07:24:21 $ - * @link http://www.zentao.net + * @link https://www.zentao.net */ set::title($lang->todo->assignedTo); diff --git a/module/todo/zen.php b/module/todo/zen.php index ffd72aab30..a238e91a41 100755 --- a/module/todo/zen.php +++ b/module/todo/zen.php @@ -3,11 +3,39 @@ declare(strict_types=1); class todoZen extends todo { + /** + * 生成创建待办视图数据。 + * Build create form data. + * + * @param string $date + * @access protected + * @return void + */ + protected function buildCreateForm(string $date): void + { + $this->view->title = $this->lang->todo->common . $this->lang->colon . $this->lang->todo->create; + $this->view->date = date('Y-m-d', strtotime($date)); + $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->time = date::now(); + $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noempty'); + $this->display(); + } + + protected function renderEditData(int $todoID) + { + $todo->date = date("Y-m-d", strtotime($todo->date)); + $this->view->title = $this->lang->todo->common . $this->lang->colon . $this->lang->todo->edit; + $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->todo = $todo; + $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noempty'); + + $this->display(); + } + /** * 处理请求数据 * Processing request data. * - * @process protected * @param object $formData * @access protected * @return object @@ -36,12 +64,13 @@ class todoZen extends todo ->stripTags($this->config->todo->editor->create['id'], $this->config->allowedTags) ->remove(implode(',', $this->config->todo->moduleList) . ',uid') ->get(); + return $data; } /** - * Create a todo after data processing - * 创建完成待办后数据处理 + * 创建完成待办后数据处理。 + * Create a todo after data processing. * * @param object $todo * @access protected @@ -65,7 +94,7 @@ class todoZen extends todo } /** - * 处理编辑待办的请求数据 + * 处理编辑待办的请求数据。 * Processing edit request data. * * @param int $todoID @@ -75,7 +104,7 @@ class todoZen extends todo */ protected function beforeEdit(int $todoID, object $formData): object|false { - $oldTodo = $this->dao->findById($todoID)->from(TABLE_TODO)->fetch(); + $oldTodo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch(); $objectID = 0; $rowData = $formData->rawdata; @@ -96,7 +125,7 @@ class todoZen extends todo ->remove(implode(',', $this->config->todo->moduleList) . ',uid') ->get(); - $todo = (object) array_merge((array) $rowData, (array) $todo); + $todo = (object)array_merge((array)$rowData, (array)$todo); if(in_array($todo->type, $this->config->todo->moduleList)) { @@ -114,9 +143,7 @@ class todoZen extends todo if(!empty($oldTodo->cycle)) $this->handleCycleConfig($todo); - $todo = $this->loadModel('file')->processImgURL($todo, $this->config->todo->editor->edit['id'], $rowData->uid); - - return $todo; + return $this->loadModel('file')->processImgURL($todo, $this->config->todo->editor->edit['id'], $rowData->uid); } /** @@ -135,15 +162,109 @@ class todoZen extends todo $this->action->logHistory($actionID, $changes); } + protected function batchEditFromMyTodo($type, $userID, $status) + { + /* Initialize vars. */ + $editedTodos = array(); + $todoIDList = array(); + $reviews = array(); + $columns = 7; + + if($userID == '') $userID = $this->app->user->id; + $user = $this->loadModel('user')->getById($userID, 'id'); + $account = $user->account; + + if($this->config->edition == 'max') $reviews = $this->loadModel('review')->getUserReviewPairs($account); + $allTodos = $this->todo->getList($type, $account, $status); + if($this->post->todoIDList) $todoIDList = $this->post->todoIDList; + + /* Initialize todos whose need to edited. */ + foreach($allTodos as $todo) + { + if(in_array($todo->id, $todoIDList)) + { + $editedTodos[$todo->id] = $todo; + if($todo->type != 'custom') + { + if(!isset($objectIDList[$todo->type])) $objectIDList[$todo->type] = array(); + $objectIDList[$todo->type][$todo->objectID] = $todo->objectID; + } + } + } + + $bugs = $this->bug->getUserBugPairs($account, true, 0, '', '', isset($objectIDList['bug']) ? $objectIDList['bug'] : ''); + $tasks = $this->task->getUserTaskPairs($account, 'wait,doing', '', isset($objectIDList['task']) ? $objectIDList['task'] : ''); + $storys = $this->loadModel('story')->getUserStoryPairs($account, 10, 'story', '', isset($objectIDList['story']) ? $objectIDList['story'] : ''); + if($this->config->edition != 'open') $this->view->feedbacks = $this->loadModel('feedback')->getUserFeedbackPairs($account, '', isset($objectIDList['feedback']) ? $objectIDList['feedback'] : ''); + if($this->config->edition == 'max') + { + $issues = $this->loadModel('issue')->getUserIssuePairs($account); + $risks = $this->loadmodel('risk')->getUserRiskPairs($account); + $opportunities = $this->loadmodel('opportunity')->getUserOpportunityPairs($account); + } + $testtasks = $this->loadModel('testtask')->getUserTestTaskPairs($account); + + /* Judge whether the edited todos is too large. */ + $countInputVars = count($editedTodos) * $columns; + $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); + + unset($this->lang->todo->typeList['cycle']); + /* Set Custom*/ + foreach(explode(',', $this->config->todo->list->customBatchEditFields) as $field) $customFields[$field] = $this->lang->todo->$field; + $this->view->customFields = $customFields; + $this->view->showFields = $this->config->todo->custom->batchEditFields; + + /* Assign. */ + $title = $this->lang->todo->common . $this->lang->colon . $this->lang->todo->batchEdit; + $position[] = html::a($this->createLink('my', 'todo'), $this->lang->my->todo); + $position[] = $this->lang->todo->common; + $position[] = $this->lang->todo->batchEdit; + + if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); + $this->view->bugs = $bugs; + $this->view->tasks = $tasks; + $this->view->storys = $storys; + if($this->config->edition == 'max') + { + $this->view->issues = $issues; + $this->view->risks = $risks; + $this->view->opportunities = $opportunities; + } + $this->view->reviews = $reviews; + $this->view->testtasks = $testtasks; + $this->view->editedTodos = $editedTodos; + $this->view->times = date::buildTimeList($this->config->todo->times->begin, $this->config->todo->times->end, $this->config->todo->times->delta); + $this->view->time = date::now(); + $this->view->title = $title; + $this->view->position = $position; + $this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted|noempty'); + + $this->display(); + } + + protected function batchEditFromTodoBatchEdit($formData) + { + $allChanges = $this->todo->batchUpdate(); + foreach($allChanges as $todoID => $changes) + { + if(empty($changes)) continue; + + $actionID = $this->loadModel('action')->create('todo', $todoID, 'edited'); + $this->action->logHistory($actionID, $changes); + } + + return print(js::locate($this->session->todoList, 'parent')); + } + /** - * 处理循环待办的配置文件 + * 处理周期待办的配置值。 * Handle cycle config. * * @param object $todo * @access private * @return void */ - private function handleCycleConfig(object &$todo): void + private function handleCycleConfig(object $todo): void { $todo->date = date('Y-m-d'); $todo->config['begin'] = $todo->date; @@ -215,7 +336,7 @@ class todoZen extends todo } /** - * 输出确认弹框 + * 输出确认弹框。 * Output confirm alert. * * @param object $todo @@ -230,7 +351,8 @@ class todoZen extends todo if($todo->type == 'bug') $app = 'qa'; if($todo->type == 'task') $app = 'execution'; if($todo->type == 'story') $app = 'product'; - $cancelURL = $this->server->HTTP_REFERER; + $cancelURL = $this->server->http_referer; + return print(js::confirm(sprintf($this->lang->todo->$confirmNote, $todo->objectID), $confirmURL, $cancelURL, $okTarget, 'parent', $app)); } @@ -244,7 +366,7 @@ class todoZen extends todo protected function getProjectPairsByModel(string $model): array { $model = $model == 'opportunity' ? 'waterfall' : 'all'; - return $this->loadModel('project')->getPairsByModel((string)$model); + return $this->loadModel('project')->getPairsByModel($model); } /** diff --git a/sonar-project.properties b/sonar-project.properties index 4ef3e8bad3..e787def4f9 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,4 +3,4 @@ sonar.sourceEncoding=UTF-8 sonar.qualitygate.wait=true sonar.coverage.exclusions=**/*.* sonar.inclusions=**/**.php -sonar.exclusions=**/*.bak,**/*.sql,**/*.js,**/*.css,**/*.yaml,**/*.zip,**/*.out,**/lang/*,**/test/* \ No newline at end of file +sonar.exclusions=**/*.bak,**/*.sql,**/*.js,**/*.css,**/*.yaml,**/*.zip,**/*.out,**/lang/*,**/test/** diff --git a/test/data/product.yaml b/test/data/product.yaml index 3164353337..b882d31011 100644 --- a/test/data/product.yaml +++ b/test/data/product.yaml @@ -12,7 +12,7 @@ fields: note: "产品名称" fields: - field: name1 - range: []{20},已关闭的{20},[]{20},已关闭的{20},[]{20},已关闭的{20},[]{10},已关闭的{10},[]{10},已关闭的{10},[]{10},已关闭的{10},[]{20} + range: "[]{20},已关闭的{20},[]{20},已关闭的{20},[]{20},已关闭的{20},[]{10},已关闭的{10},[]{10},已关闭的{10},[]{10},已关闭的{10},[]{20}" - field: name2 range: 正常产品{40},多分支产品{40},多平台产品{40},独立测试项目{7},独立产品{53},[@#$%%&&*测试名称长度能有多长并且包含特殊字符的一个长长的字符串()!@#¥%……&*]{10},已删除产品{10} - field: name3 @@ -40,7 +40,7 @@ fields: note: "产品描述" fields: - field: desc1 - range: []{30},产品描述{20} + range: "[]{30},产品描述{20}" - field: desc2 range:

一、禅道项目管理软件是做什么的?

@@ -76,7 +76,7 @@ fields: range: open{10},private{10},open{10},private{10},open{10},private{10},open{10},private{10},open{10},private{10},open{10},private{10},open{5},private{5},open{5},private{5},open{5},private{5},open{5},private{5},open{5},private{5},open{5},private{5},open{20} - field: whitelist note: "分组白名单" - range: []{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{20} + range: "[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{15},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{5},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{7},`,test1,dev1,pm1,td1,pd2,qd2,top1,others1`{3},[]{20}" - field: createdBy note: "创建者" fields: diff --git a/test/lib/yaml.class.php b/test/lib/yaml.class.php index c75eab2c3c..0dd9627996 100644 --- a/test/lib/yaml.class.php +++ b/test/lib/yaml.class.php @@ -260,7 +260,10 @@ class yaml $pos = strripos($runFileName, DS); if($pos !== false) $runFileName = mb_substr($runFileName, $pos+1); - $this->configFiles[] = $_SERVER['PWD'] . "/yaml/$runFileName/{$fileName}.yaml"; + $backtrace = debug_backtrace(); + $runPath = $backtrace[count($backtrace)-1]['file']; + + $this->configFiles[] = dirname($runPath) . "/yaml/$runFileName/{$fileName}.yaml"; return $this; } diff --git a/www/js/zui3/zin.js b/www/js/zui3/zin.js index b0cd68eebb..9958c3dff0 100644 --- a/www/js/zui3/zin.js +++ b/www/js/zui3/zin.js @@ -86,7 +86,7 @@ { if(!DEBUG) return; - updateZinbar({id: options.id, trace: data.trace}, data.errors); + updateZinbar({id: options.id, trace: data.trace, xhprof: data.xhprof}, data.errors); } function updatePageWithHtml(data)