Merge branch 'master' of git.zcorp.cc:easycorp/zentaopms

This commit is contained in:
zhaoke
2023-04-28 17:08:44 +08:00
43 changed files with 974 additions and 712 deletions
+1 -1
View File
@@ -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
Vendored
+2 -5
View File
@@ -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"
+1 -1
View File
@@ -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`;
+9 -2
View File
@@ -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;
}
+4 -3
View File
@@ -1,9 +1,10 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);
/**
* The form class file of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.easycorp.cn)
* @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 Lu Fei <lufei@easycorp.ltd>
* @package form
* @link https://www.zentao.net
+53 -26
View File
@@ -1,4 +1,14 @@
<?php
declare(strict_types=1);
/**
* The trace class file of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Lu Fei <lufei@easycorp.ltd>
* @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();
+26 -26
View File
@@ -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;
+10 -18
View File
@@ -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();
}
+147 -123
View File
@@ -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'] = '进行中';
+25 -38
View File
@@ -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');
}
}
+2 -2
View File
@@ -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();
@@ -2,7 +2,6 @@
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/block.class.php';
su('admin');
/**
@@ -16,8 +15,8 @@ pid=1
*/
$block = new blockTest();
$data[0] = $block->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列表,用例列表,版本列表'); // 通过获取测试的可用区块
r($data[1]) && p('statistic,bug,case,testtask') && e('测试统计,Bug列表,用例列表,版本列表'); // 通过获取测试的可用区块
+5 -1
View File
@@ -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()
{
+3
View File
@@ -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
+3 -1
View File
@@ -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',
+12 -22
View File
@@ -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 = '';
+21 -29
View File
@@ -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;
}
/**
-3
View File
@@ -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();
}
+32 -62
View File
@@ -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);
+67
View File
@@ -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];
}
}
+9 -8
View File
@@ -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('没有数据'); // 测试不存在的项目集
-14
View File
@@ -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.
*
View File
+5 -4
View File
@@ -1,9 +1,10 @@
<?php
$config->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');
+12 -24
View File
@@ -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);
}
/**
+9 -8
View File
@@ -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));
}
+23 -16
View File
@@ -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();
}
}
@@ -0,0 +1,45 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/project.class.php';
su('admin');
function initData()
{
$project = zdTable('project');
$project->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为字符串的产品的项目
+49 -13
View File
@@ -1,29 +1,65 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
su('admin');
function initData()
{
$team = zdTable('team');
$team->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');
+12
View File
@@ -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.
*
+95
View File
@@ -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();
}
}
+1 -1
View File
@@ -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');
}
}
+5
View File
@@ -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');
+75 -168
View File
@@ -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;
+44 -47
View File
@@ -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
+16 -17
View File
@@ -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();
}
}
-1
View File
@@ -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');
/**
+4 -5
View File
@@ -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 <liuyue@easycorp.ltd>
* @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);
+136 -14
View File
@@ -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);
}
/**
+1 -1
View File
@@ -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/*
sonar.exclusions=**/*.bak,**/*.sql,**/*.js,**/*.css,**/*.yaml,**/*.zip,**/*.out,**/lang/*,**/test/**
+3 -3
View File
@@ -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: <div>
<p><h1>一、禅道项目管理软件是做什么的?</h1>
@@ -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:
+4 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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)