This commit is contained in:
liyang
2023-04-28 14:26:12 +08:00
27 changed files with 757 additions and 466 deletions
+12 -4
View File
@@ -24,7 +24,7 @@ class trace
public function getRequestInfo()
{
$this->trace['Request'] = array(
$this->trace['request'] = array(
'start' => date('Y-m-d H:i:s', (int)$this->app->startTime),
'url' => $this->app->getURI(true),
'protocol' => $this->app->server->server_protocol,
@@ -40,7 +40,7 @@ class trace
public function getRequestFiles()
{
$this->trace['Files'] = get_included_files();
$this->trace['files'] = get_included_files();
}
public function getRequestSqls()
@@ -52,8 +52,15 @@ class trace
$explain[] = $this->dao->explain($query, false);
}
*/
$this->trace['SQL Query'] = dao::$querys;
$this->trace['SQL Explain'] = $explain;
$this->trace['sqlQuery'] = dao::$querys;
$this->trace['sqlExplain'] = $explain;
}
public function getSQLProfile()
{
$profiling = $this->dao->dbh->query('SHOW PROFILES')->fetchAll(PDO::FETCH_ASSOC);
$this->trace['profiles'] = $profiling;
}
public function getTrace()
@@ -61,6 +68,7 @@ class trace
$this->getRequestInfo();
$this->getRequestFiles();
$this->getRequestSqls();
$this->getSQLProfile();
return $this->trace;
}
+16 -16
View File
@@ -115,25 +115,25 @@ $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['productStatic'] = clone $config->block->params['default'];
$config->block->params['productStatic']->type['name'] = $lang->block->type;
$config->block->params['productStatic']->type['options'] = $lang->block->typeList->product;
$config->block->params['productStatic']->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['projectStatic'] = clone $config->block->params['default'];
$config->block->params['projectStatic']->type['name'] = $lang->block->type;
$config->block->params['projectStatic']->type['options'] = $lang->block->typeList->project;
$config->block->params['projectStatic']->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['executionStatic'] = clone $config->block->params['default'];
$config->block->params['executionStatic']->type['name'] = $lang->block->type;
$config->block->params['executionStatic']->type['options'] = $lang->block->typeList->execution;
$config->block->params['executionStatic']->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['qaStatic'] = clone $config->block->params['default'];
$config->block->params['qaStatic']->type['name'] = $lang->block->type;
$config->block->params['qaStatic']->type['options'] = $lang->block->typeList->product;
$config->block->params['qaStatic']->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;
+38 -38
View File
@@ -134,23 +134,23 @@ $lang->block->titleList['case'] = '指派给我的用例';
$lang->block->titleList['testtask'] = '待测版本列表';
$lang->block->default['waterfall']['project']['3']['title'] = "{$lang->projectCommon}计划";
$lang->block->default['waterfall']['project']['3']['block'] = 'waterfallgantt';
$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['waterfall']['project']['6']['title'] = '最新动态';
$lang->block->default['waterfall']['project']['6']['block'] = 'projectdynamic';
$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['waterfallplus'] = $lang->block->default['waterfall'];
$lang->block->default['scrum']['project']['1']['title'] = $lang->projectCommon . '概况';
$lang->block->default['scrum']['project']['1']['block'] = 'scrumoverview';
$lang->block->default['scrum']['project']['1']['code'] = 'scrumoverview';
$lang->block->default['scrum']['project']['1']['grid'] = 8;
$lang->block->default['scrum']['project']['2']['title'] = $lang->executionCommon . '列表';
$lang->block->default['scrum']['project']['2']['block'] = 'scrumlist';
$lang->block->default['scrum']['project']['2']['code'] = 'scrumlist';
$lang->block->default['scrum']['project']['2']['grid'] = 8;
$lang->block->default['scrum']['project']['2']['params']['type'] = 'undone';
@@ -158,7 +158,7 @@ $lang->block->default['scrum']['project']['2']['params']['count'] = '20';
$lang->block->default['scrum']['project']['2']['params']['orderBy'] = 'id_desc';
$lang->block->default['scrum']['project']['3']['title'] = '待测版本';
$lang->block->default['scrum']['project']['3']['block'] = 'scrumtest';
$lang->block->default['scrum']['project']['3']['code'] = 'scrumtest';
$lang->block->default['scrum']['project']['3']['grid'] = 8;
$lang->block->default['scrum']['project']['3']['params']['type'] = 'wait';
@@ -166,35 +166,35 @@ $lang->block->default['scrum']['project']['3']['params']['count'] = '15';
$lang->block->default['scrum']['project']['3']['params']['orderBy'] = 'id_desc';
$lang->block->default['scrum']['project']['4']['title'] = $lang->executionCommon . '总览';
$lang->block->default['scrum']['project']['4']['block'] = 'sprint';
$lang->block->default['scrum']['project']['4']['code'] = 'sprint';
$lang->block->default['scrum']['project']['4']['grid'] = 4;
$lang->block->default['scrum']['project']['5']['title'] = '最新动态';
$lang->block->default['scrum']['project']['5']['block'] = 'projectdynamic';
$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['product']['1']['title'] = $lang->productCommon . '统计';
$lang->block->default['product']['1']['block'] = 'statistic';
$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']['block'] = 'overview';
$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']['block'] = 'list';
$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']['block'] = 'story';
$lang->block->default['product']['4']['code'] = 'story';
$lang->block->default['product']['4']['grid'] = 4;
$lang->block->default['product']['4']['params']['count'] = 15;
@@ -202,18 +202,18 @@ $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']['block'] = 'statistic';
$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']['block'] = 'overview';
$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']['block'] = 'list';
$lang->block->default['execution']['3']['code'] = 'list';
$lang->block->default['execution']['3']['grid'] = 8;
$lang->block->default['execution']['3']['params']['count'] = 15;
@@ -221,7 +221,7 @@ $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']['block'] = 'task';
$lang->block->default['execution']['4']['code'] = 'task';
$lang->block->default['execution']['4']['grid'] = 4;
$lang->block->default['execution']['4']['params']['count'] = 15;
@@ -229,14 +229,14 @@ $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']['block'] = 'build';
$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']['block'] = 'statistic';
$lang->block->default['qa']['1']['code'] = 'statistic';
$lang->block->default['qa']['1']['grid'] = 8;
$lang->block->default['qa']['1']['params']['type'] = 'noclosed';
@@ -247,7 +247,7 @@ $lang->block->default['qa']['1']['params']['count'] = '20';
//$lang->block->default['qa']['2']['grid'] = 4;
$lang->block->default['qa']['2']['title'] = '指派给我的Bug';
$lang->block->default['qa']['2']['block'] = 'bug';
$lang->block->default['qa']['2']['code'] = 'bug';
$lang->block->default['qa']['2']['grid'] = 4;
$lang->block->default['qa']['2']['params']['count'] = 15;
@@ -255,7 +255,7 @@ $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']['block'] = 'case';
$lang->block->default['qa']['3']['code'] = 'case';
$lang->block->default['qa']['3']['grid'] = 4;
$lang->block->default['qa']['3']['params']['count'] = 15;
@@ -263,7 +263,7 @@ $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']['block'] = 'testtask';
$lang->block->default['qa']['4']['code'] = 'testtask';
$lang->block->default['qa']['4']['grid'] = 8;
$lang->block->default['qa']['4']['params']['count'] = 15;
@@ -271,44 +271,44 @@ $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']['block'] = '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']['block'] = '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']['block'] = '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']['block'] = 'list';
$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']['params']['count'] = '20';
$lang->block->default['full']['my']['5']['title'] = "{$lang->projectCommon}统计";
$lang->block->default['full']['my']['5']['block'] = 'statistic';
$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']['params']['count'] = '20';
$lang->block->default['full']['my']['6']['title'] = '我的贡献';
$lang->block->default['full']['my']['6']['block'] = 'contribute';
$lang->block->default['full']['my']['6']['code'] = 'contribute';
$lang->block->default['full']['my']['6']['source'] = '';
$lang->block->default['full']['my']['6']['grid'] = 4;
$lang->block->default['full']['my']['7']['title'] = "我近期参与的{$lang->projectCommon}";
$lang->block->default['full']['my']['7']['block'] = 'recentproject';
$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']['block'] = 'assigntome';
$lang->block->default['full']['my']['8']['code'] = 'assigntome';
$lang->block->default['full']['my']['8']['source'] = '';
$lang->block->default['full']['my']['8']['grid'] = 8;
@@ -323,12 +323,12 @@ $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']['block'] = 'projectteam';
$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']['block'] = '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;
@@ -337,40 +337,40 @@ $lang->block->default['full']['my']['10']['params']['count'] = '15';
/* Doc module block. */
$lang->block->default['doc']['1']['title'] = '文档统计';
$lang->block->default['doc']['1']['block'] = 'docstatistic';
$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']['block'] = 'docdynamic';
$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']['block'] = 'docmycollection';
$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']['block'] = 'docrecentupdate';
$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']['block'] = 'docviewlist';
$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']['block'] = 'productdoc';
$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']['block'] = 'doccollectlist';
$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']['block'] = 'projectdoc';
$lang->block->default['doc']['8']['code'] = 'projectdoc';
$lang->block->default['doc']['8']['grid'] = 8;
$lang->block->default['doc']['8']['params']['count'] = '20';
+11 -3
View File
@@ -187,10 +187,18 @@ class blockModel extends model
* @access public
* @return string
*/
public function getParams(string $block, string $module): string
public function getParams(string $code, string $module): string
{
$block = $block == 'todo' ? $module : $block;
$params = zget($this->config->block->params, $block, '');
if($code == 'todo' || $code == 'list')
{
$code = $module;
}
elseif($code == 'statistic')
{
$code = $module . 'Statistic';
}
$params = zget($this->config->block->params, $code, '');
return json_encode($params);
}
+1
View File
@@ -72,6 +72,7 @@ form
(
set::label($lang->block->name),
set::name('title'),
set::value(zget($modules, $module, '') . zget($codes, $code, '')),
set::class('form-row'),
set::control('input')
) : null,
+7 -5
View File
@@ -139,7 +139,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);
$this->getBlockMoreLink($block, $project);
}
return $blocks;
@@ -149,13 +149,15 @@ class blockZen extends block
* 获取区块的更多链接。
* Get the more link of the block.
*
* @param object $block
* @param object $block
* @param object|null $project
* @return void
*/
private function getBlockMoreLink(object $block): void
private function getBlockMoreLink(object $block, object|null $project): void
{
$code = $block->code;
$source = empty($block->source) ? 'common' : $block->source;
$code = $block->code;
$source = empty($block->source) ? 'common' : $block->source;
$projectID = isset($project->id) ? $project->id : 0;
$block->blockLink = $this->createLink('block', 'printBlock', "id=$block->id&module=$block->module");
$block->moreLink = '';
-1
View File
@@ -1,6 +1,5 @@
<?php
$config->product = new stdclass();
$config->product->orderBy = 'isClosed,program_asc,order_asc';
$config->product->showAllProjects = 0;
$config->product->customBatchEditFields = 'PO,QD,RD,status,type,acl';
+7 -9
View File
@@ -319,8 +319,8 @@ class productModel extends model
/**
* Get product pairs.
*
* @param string $mode nocode
* @param string $programID
* @param string $mode all|noclosed
* @param int $programID
* @param string|array $append
* @param string|int $shadow all | 0 | 1
* @return array
@@ -329,21 +329,19 @@ class productModel extends model
{
if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getProductPairs();
if(!empty($append) and is_array($append)) $append = implode(',', $append);
/* Add other products in has priv product id list. */
$append = $this->productTao->formatAppendParam($append);
$views = $this->app->user->view->products . (empty($append) ? '' : ",{$append}");
$views = empty($append) ? $this->app->user->view->products : $this->app->user->view->products . ",$append";
$orderBy = !empty($this->config->product->orderBy) ? $this->config->product->orderBy : 'isClosed';
/* Order by program. */
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('1 = 1')
->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()
->andWhere('t1.vision')->eq($this->config->vision)
->orderBy("$orderBy, t2.order_asc, t1.line_desc, t1.order_asc")
->orderBy("isClosed,t1.program_asc,t2.order_asc,t1.line_desc,t1.order_asc")
->fetchPairs('id', 'name');
}
+21 -1
View File
@@ -238,7 +238,6 @@ class productTao extends productModel
return $closedBugs;
}
/* TODO move to bug module. */
protected function getThisWeekBugsTODO(array $productIDs): array
{
@@ -313,4 +312,25 @@ class productTao extends productModel
return $products;
}
/**
* 格式化append参数,保证输出用逗号间隔的id列表。
* Format append param.
*
* @param string|array append
* @access protected
* @return string
*/
protected function formatAppendParam(string|array $append = ''): string
{
if(empty($append)) return '';
if(is_string($append)) $append = explode(',', $append);
$append = array_map(function($item){return (int)$item;}, $append);
$append = array_unique(array_filter($append));
sort($append);
return implode(',', $append);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ function initData()
$module->root->range('1-5'); //产品线所属项目集
$module->name->prefix("产品线")->range('1-1000');
$module->type->range("line");
$module->parent->range("0");
$module->parent->range('`0`');
$module->gen(10);
}
+3 -15
View File
@@ -3,30 +3,18 @@
include dirname(__FILE__, 5) . "/test/lib/init.php";
include dirname(__FILE__, 2) . '/product.class.php';
zdTable('product')->gen(50);
/**
title=测试productModel->getPairs();
cid=1
pid=1
测试项目集10下的11号产品 >> 正常产品11
测试项目集10下的55号产品 >> 多分支产品55
测试项目集10下的99号产品 >> 多平台产品99
测试不存在的项目集 >> 没有数据
返回所有产品的数量 >> 120
返回项目集10下的所有产品 >> 10
测试项目集10下的未关闭产品5 >> 正常产品6
返回项目集10下的未关闭产品的数量 >> 6
*/
$product = new productTest('admin');
$t_peoduct10 = array('programID'=>'10');
$t_noproduct = array('programID'=>'11');
$t_alproduct = array('programID'=>'0');
$t_notclose5 = array('programID'=>'5');
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号产品
@@ -34,4 +22,4 @@ 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->getNoclosedPairsCount($t_peoduct10['programID'])) && p() && e('6'); // 返回项目集10下的未关闭产品的数量
+6 -3
View File
@@ -223,13 +223,16 @@ class productTest
/**
* Test get product pairs.
*
* @param int $programID
* @param string $mode
* @param int $programID
* @param string|array $append
* @param string|int $shadow
* @access public
* @return array
*/
public function getProductPairs($programID)
public function getProductPairs(string $mode = '', int $programID = 0, string|array $append = '', string|int $shadow = 0): array|string
{
$pairs = $this->objectModel->getPairs('', $programID);
$pairs = $this->objectModel->getPairs($mode, $programID, $append, $shadow);
if($pairs == array()) return '没有数据';
return $pairs;
}
@@ -0,0 +1,30 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
/**
title=productTao->formatAppendParam();
cid=1
pid=1
*/
global $tester;
$productTao = $tester->loadModel('product');
r($productTao->formatAppendParam('')) && p() && e('0');
r($productTao->formatAppendParam(array())) && p() && e('0');
r($productTao->formatAppendParam('1')) && p() && e('1');
r($productTao->formatAppendParam('1,')) && p() && e('1');
r($productTao->formatAppendParam('1,1')) && p() && e('1');
r($productTao->formatAppendParam('1,2')) && p() && e('1,2');
r($productTao->formatAppendParam('1,a')) && p() && e('1');
r($productTao->formatAppendParam(array('1'))) && p() && e('1');
r($productTao->formatAppendParam(array('1', '1'))) && p() && e('1');
r($productTao->formatAppendParam(array('1', '2'))) && p() && e('1,2');
r($productTao->formatAppendParam(array('1', 'a'))) && p() && e('1');
+9 -9
View File
@@ -13,8 +13,8 @@ declare(strict_types=1);
class productZen extends product
{
/**
* Set shared environment data for all function of control layer.
* 为控制层的all函数设置共享环境数据。
* Set shared environment data for all function of control layer.
*
* @access protected
* @return void
@@ -33,8 +33,8 @@ class productZen extends product
}
/**
* Set menu for create product page.
* 为创建产品设置导航数据,主要是替换占位符。
* Set menu for create product page.
*
* @param int $programID
* @access protected
@@ -55,8 +55,8 @@ class productZen extends product
}
/**
* Get goback link for create by extra.
* 通过extra获取返回链接。
* Get goback link for create by extra.
*
* @param string $extra
* @access private
@@ -75,8 +75,8 @@ class productZen extends product
}
/**
* Get form fields for create.
* 获取创建产品页面的表单配置。
* Get form fields for create.
*
* @param int $programID
* @access private
@@ -134,8 +134,8 @@ class productZen extends product
}
/**
* Build form fields for create.
* 构建创建产品页面数据。
* Build form fields for create.
*
* @param int $programID
* @param string $extra
@@ -154,8 +154,8 @@ class productZen extends product
}
/**
* Prepare data for create.
* 追加创建信息,处理白名单、评审者、项目集字段,还有富文本内容处理。
* Prepare data for create.
*
* @param int $programID
* @param string $extra
@@ -181,8 +181,8 @@ class productZen extends product
}
/**
* Process after create product.
* 成功插入产品数据后,其他的额外操作。
* Process after create product.
*
* @param int $productID
* @param object $product
@@ -222,8 +222,8 @@ class productZen extends product
}
/**
* Locate after create product.
* 创建完成后,做页面跳转。
* Locate after create product.
*
* @param int $productID
* @param int $programID
@@ -243,8 +243,8 @@ class productZen extends product
}
/**
* Send error for create.
* 输出创建产品产生的错误。
* Send error for create.
*
* @access protected
* @return void
+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');
+15 -26
View File
@@ -487,6 +487,7 @@ class project extends control
*/
public function create($model = 'scrum', $programID = 0, $copyProjectID = 0, $extra = '')
{
$this->loadModel('execution');
$this->session->set('projectModel', $model);
if($model == 'kanban') unset($this->lang->project->authList['reset']);
@@ -521,8 +522,8 @@ class project extends control
if($model == 'waterfall' or $model == 'waterfallplus')
{
$productID = $this->loadModel('product')->getProductIDByProject($projectID, true);
$this->session->set('projectPlanList', $this->createLink('programplan', 'browse', "projectID=$projectID&productID=$productID&type=lists", '', '', $projectID), 'project');
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('programplan', 'create', "projectID=$projectID", '', '', $projectID)));
$this->session->set('projectPlanList', $this->createLink('programplan', 'browse', "projectID=$projectID&productID=$productID&type=lists", '', false, $projectID), 'project');
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('programplan', 'create', "projectID=$projectID", '', false, $projectID)));
}
$parent = isset($_POST['parent']) ? $_POST['parent'] : 0;
@@ -1835,46 +1836,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);
}
/**
+33 -151
View File
@@ -179,8 +179,8 @@ class projectModel extends model
}
/**
* Get a project by id.
* 根据项目ID获取项目信息。
* Get a project by id.
*
* @param int $projectID
* @access public
@@ -198,17 +198,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();
@@ -1262,7 +1263,7 @@ class projectModel extends model
* @access public
* @return int|bool
*/
public function create(object $project, object $postData)
public function create(object $project, object $postData): int|bool
{
$requiredFields = $this->config->project->create->requiredFields;
if($postData->rawdata->delta == 999) $requiredFields = trim(str_replace(',end,', ',', ",{$requiredFields},"), ',');
@@ -1278,159 +1279,40 @@ class projectModel extends model
->checkFlow()
->exec();
/* Add the creater to the team. */
if(!dao::isError())
{
$projectID = $this->dao->lastInsertId();
if(dao::isError()) return false;
/* Set team of project. */
$members = isset($_POST['teamMembers']) ? $_POST['teamMembers'] : array();
array_push($members, $project->PM, $project->openedBy);
$members = array_unique($members);
$roles = $this->loadModel('user')->getUserRoles(array_values($members));
$projectID = (int)$this->dao->lastInsertId();
$teamMembers = array();
foreach($members as $account)
{
if(empty($account)) continue;
/* Manage team members. */
$teamMembers = $this->projectTao->setProjectTeam($projectID, $project, $postData);
$this->loadModel('execution')->addProjectMembers($projectID, $teamMembers);
$member = new stdClass();
$member->root = $projectID;
$member->type = 'project';
$member->account = $account;
$member->role = zget($roles, $account, '');
$member->join = helper::now();
$member->days = zget($project, 'days', 0);
$member->hours = $this->config->execution->defaultWorkhours;
$this->dao->insert(TABLE_TEAM)->data($member)->exec();
$teamMembers[$account] = $member;
}
$this->loadModel('execution')->addProjectMembers($projectID, $teamMembers);
/* Add project whitelist. */
$whitelist = explode(',', $project->whitelist);
$this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID);
$whitelist = explode(',', $project->whitelist);
$this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID);
$program = $project->parent ? $this->getByID((int)$project->parent) : new stdclass();
$this->projectTao->createDocLib($projectID, $project, $postData, $program);
/* Create doc lib. */
$this->app->loadLang('doc');
$authorizedUsers = array();
if($project->hasProduct) $this->updateProducts($projectID);
if($project->parent and $project->acl == 'program')
{
$stakeHolders = $this->loadModel('stakeholder')->getStakeHolderPairs($project->parent);
$authorizedUsers = array_keys($stakeHolders);
/* If $_POST has product name, create it. */
$linkedProductsCount = $this->projectTao->getLinkedProductsCount($project, $postData->rawdata);
if(!$project->hasProduct or isset($postData->rawdata->newProduct) or (!$project->parent and empty($linkedProductsCount))) $this->createProduct($projectID, $project, $postData, $program);
foreach(explode(',', $project->whitelist) as $user)
{
if(empty($user)) continue;
$authorizedUsers[$user] = $user;
}
/* Save order. */
$this->dao->update(TABLE_PROJECT)->set('`order`')->eq($projectID * 5)->where('id')->eq($projectID)->exec();
$this->file->updateObjectID($postData->rawdata->uid, $projectID, 'project');
$this->loadModel('program')->setTreePath($projectID);
$authorizedUsers[$project->PM] = $project->PM;
$authorizedUsers[$project->openedBy] = $project->openedBy;
$authorizedUsers[$program->PM] = $program->PM;
$authorizedUsers[$program->openedBy] = $program->openedBy;
}
/* Add project admin. */
$this->projectTao->addProjectAdmin($projectID);
$lib = new stdclass();
$lib->project = $projectID;
$lib->name = $this->lang->doclib->main['project'];
$lib->type = 'project';
$lib->main = '1';
$lib->acl = 'default';
$lib->users = ',' . implode(',', array_filter($authorizedUsers)) . ',';
$lib->vision = zget($project, 'vision', 'rnd');
$lib->addedBy = $this->app->user->account;
$lib->addedDate = helper::now();
$this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
if($project->acl != 'open') $this->loadModel('user')->updateUserView($projectID, 'project');
if($project->hasProduct) $this->updateProducts($projectID);
if(empty($project->multiple) and $project->model != 'waterfall' and $project->model != 'waterfallplus') $this->loadModel('execution')->createDefaultSprint($projectID);
if(!$project->hasProduct or isset($_POST['newProduct']) or (!$project->parent and empty($linkedProductsCount)))
{
/* If parent not empty, link products or create products. */
$product = new stdclass();
$product->name = $project->hasProduct && $postData->rawdata->productName ? $postData->rawdata->productName : $project->name;
$product->shadow = zget($project, 'vision', 'rnd') == 'rnd' ? (int)empty($project->hasProduct) : 1;
$product->bind = $postData->rawdata->parent ? 0 : 1;
$product->program = $project->parent ? current(array_filter(explode(',', $program->path))) : 0;
$product->acl = $project->acl == 'open' ? 'open' : 'private';
$product->PO = $project->PM;
$product->QD = '';
$product->RD = '';
$product->whitelist = '';
$product->createdBy = $this->app->user->account;
$product->createdDate = helper::now();
$product->status = 'normal';
$product->line = 0;
$product->desc = '';
$product->createdVersion = $this->config->version;
$product->vision = zget($project, 'vision', 'rnd');
$this->dao->insert(TABLE_PRODUCT)->data($product)->exec();
$productID = $this->dao->lastInsertId();
if(!$project->hasProduct) $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', $productID);
$this->loadModel('action')->create('product', $productID, 'opened');
$this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($productID * 5)->where('id')->eq($productID)->exec();
if($product->acl != 'open') $this->loadModel('user')->updateUserView($productID, 'product');
$projectProduct = new stdclass();
$projectProduct->project = $projectID;
$projectProduct->product = $productID;
$projectProduct->branch = 0;
$projectProduct->plan = 0;
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($projectProduct)->exec();
if($project->hasProduct)
{
/* Create doc lib. */
$this->app->loadLang('doc');
$lib = new stdclass();
$lib->product = $productID;
$lib->name = $this->lang->doclib->main['product'];
$lib->type = 'product';
$lib->main = '1';
$lib->acl = 'default';
$lib->addedBy = $this->app->user->account;
$lib->addedDate = helper::now();
$this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
}
}
/* Save order. */
$this->dao->update(TABLE_PROJECT)->set('`order`')->eq($projectID * 5)->where('id')->eq($projectID)->exec();
$this->file->updateObjectID($postData->rawdata->uid, $projectID, 'project');
$this->loadModel('program')->setTreePath($projectID);
/* Add project admin. */
$groupPriv = $this->dao->select('t1.*')->from(TABLE_USERGROUP)->alias('t1')
->leftJoin(TABLE_GROUP)->alias('t2')->on('t1.`group` = t2.id')
->where('t1.account')->eq($this->app->user->account)
->andWhere('t2.role')->eq('projectAdmin')
->fetch();
if(!empty($groupPriv))
{
$newProject = $groupPriv->project . ",$projectID";
$this->dao->update(TABLE_USERGROUP)->set('project')->eq($newProject)->where('account')->eq($groupPriv->account)->andWhere('`group`')->eq($groupPriv->group)->exec();
}
else
{
$projectAdminID = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->eq('projectAdmin')->fetch('id');
$groupPriv = new stdclass();
$groupPriv->account = $this->app->user->account;
$groupPriv->group = $projectAdminID;
$groupPriv->project = $projectID;
$this->dao->replace(TABLE_USERGROUP)->data($groupPriv)->exec();
}
if($project->acl != 'open') $this->loadModel('user')->updateUserView($projectID, 'project');
if(empty($project->multiple) and $project->model != 'waterfall' and $project->model != 'waterfallplus') $this->loadModel('execution')->createDefaultSprint($projectID);
return $projectID;
}
return $projectID;
}
/**
@@ -2286,9 +2168,9 @@ class projectModel extends model
/**
* Convert budget unit.
*
* @param int $budget
* @param int $budget
* @access public
* @return void
* @return int|string $projectBudget
*/
public function getBudgetWithUnit($budget)
{
@@ -2408,7 +2290,7 @@ class projectModel extends model
$data->product = $productID;
$data->branch = $branchID;
$data->plan = isset($plans[$productID]) ? implode(',', $plans[$productID]) : $oldPlan;
$data->plan = trim($data->plan, ',');
$data->plan = trim((string)$data->plan, ',');
$data->plan = empty($data->plan) ? 0 : ",$data->plan,";
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
+211
View File
@@ -172,4 +172,215 @@ class projectTao extends projectModel
if($project and helper::isZeroDate($project->end)) $project->end = '';
return $project;
}
/**
* 创建项目后,将团队成员插入到TEAM表.
* Insert into zt_team after create a project.
*
* @param int $projectID
* @param object $project
* @param object $postData
* @access protected
* @return array
*/
protected function setProjectTeam(int $projectID, object $project, object $postData): array
{
/* Set team of project. */
$members = isset($postData->rawdata->teamMembers) ? $postData->rawdata->teamMembers : array();
array_push($members, $project->PM, $project->openedBy);
$members = array_unique($members);
$roles = $this->loadModel('user')->getUserRoles(array_values($members));
$teamMembers = array();
foreach($members as $account)
{
if(empty($account)) continue;
$member = new stdClass();
$member->root = $projectID;
$member->type = 'project';
$member->account = $account;
$member->role = zget($roles, $account, '');
$member->join = helper::now();
$member->days = zget($project, 'days', 0);
$member->hours = $this->config->execution->defaultWorkhours;
$this->dao->insert(TABLE_TEAM)->data($member)->exec();
$teamMembers[$account] = $member;
}
return $teamMembers;
}
/**
* 创建项目后,创建默认的项目主库.
* Create doclib after create a project.
*
* @param int $projectID
* @param object $project
* @param object $postData
* @param object $program
* @access protected
* @return bool
*/
protected function createDocLib(int $projectID, object $project, object $postData, object $program): bool
{
/* Create doc lib. */
$this->app->loadLang('doc');
$authorizedUsers = array();
if($project->parent and $project->acl == 'program')
{
$stakeHolders = $this->loadModel('stakeholder')->getStakeHolderPairs($project->parent);
$authorizedUsers = array_keys($stakeHolders);
foreach(explode(',', $project->whitelist) as $user)
{
if(empty($user)) continue;
$authorizedUsers[$user] = $user;
}
$authorizedUsers[$project->PM] = $project->PM;
$authorizedUsers[$project->openedBy] = $project->openedBy;
$authorizedUsers[$program->PM] = $program->PM;
$authorizedUsers[$program->openedBy] = $program->openedBy;
}
$lib = new stdclass();
$lib->project = $projectID;
$lib->name = $this->lang->doclib->main['project'];
$lib->type = 'project';
$lib->main = '1';
$lib->acl = 'default';
$lib->users = ',' . implode(',', array_filter($authorizedUsers)) . ',';
$lib->vision = zget($project, 'vision', 'rnd');
$lib->addedBy = $this->app->user->account;
$lib->addedDate = helper::now();
$this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
return !dao::isError();
}
/**
* 创建项目时,如果直接输入了产品名,则创建产品并与项目关联.
* Create doclib after create a project.
*
* @param int $projectID
* @param object $project
* @param object $postData
* @param object $program
* @access protected
* @return bool
*/
protected function createProduct(int $projectID, object $project, object $postData, object $program): bool
{
/* If parent not empty, link products or create products. */
$product = new stdclass();
$product->name = $project->hasProduct && $postData->rawdata->productName ? $postData->rawdata->productName : $project->name;
$product->shadow = zget($project, 'vision', 'rnd') == 'rnd' ? (int)empty($project->hasProduct) : 1;
$product->bind = $postData->rawdata->parent ? 0 : 1;
$product->program = $project->parent ? current(array_filter(explode(',', $program->path))) : 0;
$product->acl = $project->acl == 'open' ? 'open' : 'private';
$product->PO = $project->PM;
$product->QD = '';
$product->RD = '';
$product->whitelist = '';
$product->createdBy = $this->app->user->account;
$product->createdDate = helper::now();
$product->status = 'normal';
$product->line = 0;
$product->desc = '';
$product->createdVersion = $this->config->version;
$product->vision = zget($project, 'vision', 'rnd');
$this->dao->insert(TABLE_PRODUCT)->data($product)->exec();
$productID = $this->dao->lastInsertId();
if(!$project->hasProduct) $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', $productID);
$this->loadModel('action')->create('product', $productID, 'opened');
$this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($productID * 5)->where('id')->eq($productID)->exec();
if($product->acl != 'open') $this->loadModel('user')->updateUserView($productID, 'product');
$projectProduct = new stdclass();
$projectProduct->project = $projectID;
$projectProduct->product = $productID;
$projectProduct->branch = 0;
$projectProduct->plan = 0;
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($projectProduct)->exec();
if($project->hasProduct)
{
/* Create doc lib. */
$this->app->loadLang('doc');
$lib = new stdclass();
$lib->product = $productID;
$lib->name = $this->lang->doclib->main['product'];
$lib->type = 'product';
$lib->main = '1';
$lib->acl = 'default';
$lib->addedBy = $this->app->user->account;
$lib->addedDate = helper::now();
$this->dao->insert(TABLE_DOCLIB)->data($lib)->exec();
}
return !dao::isError();
}
/**
* 获取创建项目时选择的产品数量.
* Get products count from post.
*
* @param object $project
* @param object $rawdata
* @param object $program
* @access protected
* @return bool
*/
protected function getLinkedProductsCount(object $project, object $rawdata): int
{
$linkedProductsCount = 0;
if($project->hasProduct && isset($rawdata->products))
{
foreach($rawdata->products as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
}
return $linkedProductsCount;
}
/**
* 创建项目后,将项目创建者加到项目管理员分组.
* Create project admin after create a project.
*
* @param int $projectID
* @access protected
* @return bool
*/
protected function addProjectAdmin(int $projectID): bool
{
$groupPriv = $this->dao->select('t1.*')->from(TABLE_USERGROUP)->alias('t1')
->leftJoin(TABLE_GROUP)->alias('t2')->on('t1.`group` = t2.id')
->where('t1.account')->eq($this->app->user->account)
->andWhere('t2.role')->eq('projectAdmin')
->fetch();
if(!empty($groupPriv))
{
$newProject = $groupPriv->project . ",$projectID";
$this->dao->update(TABLE_USERGROUP)->set('project')->eq($newProject)->where('account')->eq($groupPriv->account)->andWhere('`group`')->eq($groupPriv->group)->exec();
}
else
{
$projectAdminID = $this->dao->select('id')->from(TABLE_GROUP)->where('role')->eq('projectAdmin')->fetch('id');
$groupPriv = new stdclass();
$groupPriv->account = $this->app->user->account;
$groupPriv->group = $projectAdminID;
$groupPriv->project = $projectID;
$this->dao->replace(TABLE_USERGROUP)->data($groupPriv)->exec();
}
return !dao::isError();
}
}
+11 -10
View File
@@ -18,14 +18,15 @@ title=测试 projectModel->create();
timeout=0
cid=1
- 执行projectClass模块的cr方法,参数是$normalProject, $postData,属性name @测试新增项目一
- 执行projectClass模块的cr方法,参数是$emptyNameProject, $postData,属性name @『项目名称』不能为空。
- 执行projectClass模块的cr方法,参数是$normalProject, $postData属性name @测试新增项目一
- 执行projectClass模块的cr方法,参数是$emptyEndProject, $postData,属性end @『计划完成』不能为空。
- 执行projectClass模块的cr方法,参数是$emptyNameProject, $postData第name条的0属性 @『项目名称』不能为空。
- 执行projectClass模块的cr方法,参数是$beginGtEndProject, $postData,属性end @『计划完成』应当大于『2022-02-07』。
- 执行projectClass模块的cr方法,参数是$emptyEndProject, $postData第end条的0属性 @『计划完成』不能为空。
- 执行projectClass模块的cr方法,参数是$emptyBeginProject, $postData,属性begin @『计划开始』不能为空。
- 执行projectClass模块的cr方法,参数是$beginGtEndProject, $postData第end条的0属性 @『计划完成』应当大于『2022-02-07』。
- 执行projectClass模块的cr方法,参数是$emptyBeginProject, $postData第begin条的0属性 @『计划开始』不能为空。
@@ -51,10 +52,10 @@ $project->multiple = 1;
$project->hasProduct = 1;
$postData = new stdclass();
$postData->rowdata = clone $project;
$postData->rowdata->uid = '64dda2xc';
$postData->rowdata->delta = 0;
$postData->rowdata->products = array(1);
$postData->rawdata = clone $project;
$postData->rawdata->uid = '64dda2xc';
$postData->rawdata->delta = 0;
$postData->rawdata->products = array(1);
$normalProject = clone $project;
@@ -77,4 +78,4 @@ r($projectClass->create($normalProject, $postData)) && p('name')
r($projectClass->create($emptyNameProject, $postData)) && p('message[name]:0') && e('『项目名称』不能为空。');
r($projectClass->create($emptyEndProject, $postData)) && p('message[end]:0') && e('『计划完成』不能为空。');
r($projectClass->create($beginGtEndProject, $postData)) && p('message[end]:0') && e('『计划完成』应当大于『2022-02-07』。');
r($projectClass->create($emptyBeginProject, $postData)) && p('message[begin]:0') && e('『计划开始』不能为空。');
r($projectClass->create($emptyBeginProject, $postData)) && p('message[begin]:0') && e('『计划开始』不能为空。');
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env php
<?php
include dirname(__FILE__, 5) . "/test/lib/init.php";
su('admin');
global $tester;
$tester->loadModel('project');
r($oriject->getBudgetWithUnit(99)) && p() && e('99');
@@ -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为字符串的产品的项目
+25 -1
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.
*
@@ -161,7 +173,7 @@ class Project
}
/**
* doActivate a project.
* Do activate a project.
*
* @param int $projectID
* @param object $project
@@ -172,4 +184,16 @@ class Project
{
return $this->project->doActivate($projectID, $project);
}
/**
* Get budget with unit.
*
* @param int $budget
* @access public
* @return int|string $projectBudget
*/
public function getBudgetWithUnit($budget)
{
return $this->project->getBudgetWithUnit($budget);
}
}
+103 -15
View File
@@ -43,23 +43,24 @@ class projectZen extends project
/* Lean mode relation defaultProgram. */
if($this->config->systemMode == 'light') $project->parent = $this->config->global->defaultProgram;
if(!$this->checkProductAndBranch($rawdata, $project)) return false;
if(!$this->checkDaysAndBudget($rawdata, $project)) return false;
if(!$this->checkProductNameUnqiue($rawdata, $project)) return false;
if(!$this->checkProductAndBranch($project, $rawdata)) return false;
if(!$this->checkDaysAndBudget($project, $rawdata)) return false;
if(!$this->checkProductNameUnqiue($project, $rawdata)) return false;
return $project;
}
private function checkProductAndBranch(object $rawdata, object $project): bool
/**
* 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 = 0;
if($project->hasProduct && isset($rawdata->products))
{
foreach($rawdata->products as $product)
{
if(!empty($product)) $linkedProductsCount++;
}
}
$linkedProductsCount = $this->project->getLinkedProductsCount($project, $rawdata);
if($rawdata->products)
{
@@ -78,7 +79,7 @@ class projectZen extends project
$program = new stdClass();
if($project->parent)
{
$program = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($project->parent)->fetch();
$program = $this->project->getByID((int)$project->parent);
/* Judge products not empty. */
if($project->hasProduct && empty($linkedProductsCount) and !isset($rawdata->newProduct))
@@ -91,7 +92,15 @@ class projectZen extends project
return true;
}
private function checkDaysAndBudget(object $rawdata, object $project): bool
/**
* 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. */
$workdays = helper::diffDate($project->end, $project->begin) + 1;
@@ -122,7 +131,15 @@ class projectZen extends project
return true;
}
private function checkProductNameUnqiue(object $rawdata, object $project): bool
/**
* 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. */
if($project->hasProduct && isset($rawdata->newProduct))
@@ -454,4 +471,75 @@ class projectZen extends project
$this->view->actions = $this->loadModel('action')->getList('project', $projectID);
$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();
}
}
+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:
+98 -72
View File
@@ -18,7 +18,7 @@ define('RUN_MODE', 'test');
if($argc > 1 && $argv[1] == '-extract')
{
parseScript();
printSteps();
exit;
}
@@ -110,7 +110,7 @@ function p($keys = '', $delimiter = ',')
if(is_array($_result) and isset($_result['code']) and $_result['code'] == 'fail') return print((string) $_result['message'] . "\n");
/* Print $_result. */
if(!$keys and is_array($_result)) return print((string)$_result[''] . "\n");
if(!$keys and is_array($_result)) return print(json_encode($_result['']) . "\n");
if(!$keys or !is_array($_result) and !is_object($_result)) return print((string) $_result . "\n");
$parts = explode(';', $keys);
@@ -124,77 +124,101 @@ function p($keys = '', $delimiter = ',')
}
/**
* 生成用例步骤描述
* Generate step desc.
*
* @param string $userStep
* @param string $moduleName
* @param string $methodName
* @param string $methodParam
* @param string $isGrup
* @access public
* @return void
*/
function genStepDesc($userStep, $moduleName, $methodName, $methodParam, $isGrup)
{
if($userStep) return '- ' . $userStep . ($isGrup ? "\n" : '');
if ($methodName === 0 && $methodParam === 0)
{
$stepDesc = "- 执行{$moduleName}" . ($isGrup ? "\n" : '');
}
elseif($methodParam)
{
$stepDesc = "- 执行{$moduleName}模块的{$methodName}方法,参数是{$methodParam}" . ($isGrup ? "\n" : '');
}
else
{
$stepDesc = "- 执行{$moduleName}模块的{$methodName}方法" . ($isGrup ? "\n" : '');
}
return $stepDesc;
}
/**
* 生成用例步骤并输出(ztf使用)
* extract ZTF note.
*
* @param string $key
* @access public
* @return void
*/
function parseScript()
function printSteps()
{
$debugInfo = debug_backtrace();
if(!empty($debugInfo))
if(empty($debugInfo)) return;
$file = $debugInfo[count($debugInfo)-1]['file'];
$contents = file_get_contents($file);
$rpeList = genParamsByRPE($contents);
foreach($rpeList as $rpe)
{
$file = $debugInfo[count($debugInfo)-1]['file'];
$contents = file_get_contents($file);
$rpeList = genParamsByRPE($contents);
list($moduleName, $methodName, $methodParam) = $rpe[0];
$expectStr = trim($rpe[1], '"\'');
$pParam = $rpe[2];
$userStep = trim(str_replace('//', '', $rpe[3]));
$keys = $pParam[0];
$delimiter = $pParam[1] ? $pParam[1] : ',';
$isGrup = false;
$stepDesc = '';
$expects = '' === $expectStr ? array() : explode($delimiter, $expectStr);
foreach($rpeList as $rpe)
$rowIndex = -1;
$pos = strpos($keys, ':');
if($pos)
{
list($moduleName, $methodName, $methodParam) = $rpe[0];
$expectStr = trim($rpe[1], '"\'');
$pParam = $rpe[2];
$keys = $pParam[0];
$delimiter = $pParam[1] ? $pParam[1] : ',';
$isGrup = false;
$stepDesc = '';
$expects = '' === $expectStr ? array() : explode($delimiter, $expectStr);
$arrKey = substr($keys, 0, $pos);
$keys = substr($keys, $pos + 1);
$rowIndex = $arrKey;
}
$keys = empty($keys) ? array() : explode($delimiter, $keys);
if(count($keys) > 1) $isGrup = true;
$rowIndex = -1;
$pos = strpos($keys, ':');
if($pos)
$stepDesc = genStepDesc($userStep, $moduleName, $methodName, $methodParam, $isGrup);
if(empty($keys)) $stepDesc .= " @{$expectStr}\n";
foreach($keys as $index => $row)
{
$stepExpect = isset($expects[$index]) ? $expects[$index] : '';
if(count($expects) == 1) $stepExpect = $expectStr;
if($rowIndex == -1)
{
$arrKey = substr($keys, 0, $pos);
$keys = substr($keys, $pos + 1);
$pos = strpos($arrKey, '[');
$rowIndex = $pos ? trim(substr($arrKey, $pos + 1), ']') : $arrKey;
}
$keys = explode($delimiter, $keys);
if(count($keys) > 1) $isGrup = true;
if ($methodName === 0 && $methodParam === 0)
{
$stepDesc = "- 执行{$moduleName}" . ($isGrup ? "\n" : '');
$stepDesc .= ($isGrup ? ' - ' : '') . ($row ? "属性{$row}" : '') . " @{$stepExpect}\n";
}
else
{
$stepDesc = "- 执行{$moduleName}模块的{$methodName}方法,参数是{$methodParam}" . ($isGrup ? "\n" : '');
$stepDesc .= ($isGrup ? ' - ' : '') . "第{$rowIndex}条的{$row}属性 @{$stepExpect}\n";
}
if(empty($keys)) $stepDesc .= " @{$expects[0]}\n";
foreach($keys as $index => $row)
{
$stepExpect = isset($expects[$index]) ? $expects[$index] : '';
if(count($expects) == 1) $stepExpect = $expects[0];
if($rowIndex == -1)
{
$stepDesc .= ($isGrup ? ' - ' : '') . "属性{$row} @{$stepExpect}\n";
}
else
{
$stepDesc .= ($isGrup ? ' - ' : '') . "第{$rowIndex}条的{$row}属性 @{$stepExpect}\n";
}
}
echo $stepDesc . ($isGrup ? "\n" : "\n");
}
echo $stepDesc . ($isGrup ? "\n" : "\n");
}
}
/**
* Split function params.
* 从p()函数提取传入参数
* Split function params.
*
* @param array $params
* @return array
@@ -206,13 +230,13 @@ function splitParam($params)
{
$param = trim($param);
$firstSymbol = substr($param, 0, 1);
$paramArray = str_split($param);
$paramList = str_split($param);
$delimiterIndex = -1;
foreach($paramArray as $i => $p)
foreach($paramList as $i => $p)
{
if($i == 0) continue;
if($p === $firstSymbol && (!isset($paramArray[$i-1]) || '\\' != $paramArray[$i-1]))
if($p === $firstSymbol && (!isset($paramList[$i-1]) || '\\' != $paramList[$i-1]))
{
$delimiterIndex = $i + 1;
break;
@@ -236,8 +260,8 @@ function splitParam($params)
}
/**
* Generate module,method,param from r function.
* 从r()函数提取调用的moduleName,methodName,methodParam
* Generate module,method,param from r function.
*
* @param array $rParams
* @return array
@@ -247,7 +271,9 @@ function genModuleAndMethod($rParams)
$newParams = array();
foreach($rParams as $index => $param)
{
$param = trim($param, "'");
$param = trim($param, "'");
if($param[0] != '$') $param = trim(strchr($param, '$'), ')');
$objArrowCount = substr_count($param, '->');
$rParamsStructureList = explode('->', $param);
@@ -255,17 +281,16 @@ function genModuleAndMethod($rParams)
{
$moduleName = substr($rParamsStructureList[0], 1);
$method = $rParamsStructureList[1];
$methodName = substr(explode('(', $method)[0], 0, -4);
$methodParam = substr(explode('(', $method)[1], 0, -1);
$methodParam = trim($methodParam, "'");
$methodName = substr(explode('(', $method)[0], 0);
$methodParam = substr(explode('(', $method)[1], 0);
$methodParam = trim($methodParam, "'()");
}
elseif($objArrowCount == 2)
{
$moduleName = $rParamsStructureList[1];
$method = $rParamsStructureList[2];
$methodName = explode('(', $method)[0];
$methodParam = trim(substr(explode('(', $method)[1], 0, -1), ")");
$methodParam = trim($methodParam, "'");
$methodParam = trim(substr(explode('(', $method)[1], 0), "'()");
}
else
{
@@ -281,27 +306,28 @@ function genModuleAndMethod($rParams)
}
/**
* Generate method,module,params from rpe.
* 从RPE中获取模块,方法以及参数
* Generate method,module,params from rpe.
*
* @param string $rpe
* @return array
*/
function genParamsByRPE($rpe)
{
preg_match_all("/r\((.*?)\)\s*&&\s*p\((.*?)\)\s*&&\s*e\((.*?)\);/", $rpe, $matches);
$rParams = !empty($matches[1]) ? $matches[1] : array();
$pParams = !empty($matches[2]) ? $matches[2] : array();
$eParams = !empty($matches[3]) ? $matches[3] : array();
$rParams = is_array($rParams) ? $rParams : array($rParams);
$pParams = is_array($pParams) ? $pParams : array($pParams);
$eParams = is_array($eParams) ? $eParams : array($eParams);
preg_match_all("/r\((.*?)\)\s*&&\s*p\((.*?)\)\s*&&\s*e\((.*?)\);(.*)/", $rpe, $matches);
$rParams = !empty($matches[1]) ? $matches[1] : array();
$pParams = !empty($matches[2]) ? $matches[2] : array();
$eParams = !empty($matches[3]) ? $matches[3] : array();
$stepList = !empty($matches[4]) ? $matches[4] : array();
$rParams = is_array($rParams) ? $rParams : array($rParams);
$pParams = is_array($pParams) ? $pParams : array($pParams);
$eParams = is_array($eParams) ? $eParams : array($eParams);
$pParamsArray = splitParam($pParams);
$rpeList = array();
$rParamArray = genModuleAndMethod($rParams);
$pParamsList = splitParam($pParams);
$rpeList = array();
$rParamList = genModuleAndMethod($rParams);
foreach($rParamArray as $index => $param) $rpeList[] = array($param, $eParams[$index], $pParamsArray[$index]);
foreach($rParamList as $index => $param) $rpeList[] = array($param, $eParams[$index], $pParamsList[$index], $stepList[$index]);
return $rpeList;
}
+27 -78
View File
@@ -6,8 +6,8 @@
const currentCode = window.name.substring(4);
const isInAppTab = parent.window !== window;
const fetchTasks = new Map();
const phpErrTypes = {'1': 'error', '2': 'warning', '4': 'parse', '8': 'notice', '16': 'core-error', '32': 'core-warning', '64': 'compile-error', '128': 'compile-warning', '256': 'user-error', '512': 'user-warning', '1024': 'user-notice', '2048': 'strict', '4096': 'recoverable-error', '8192': 'deprecated', '16384': 'user-deprecated', '32767': 'all'};
let currentAppUrl = '';
let currentAppUrl = isInAppTab ? '' : location.href;
let zinbar = null;
$.apps = $.extend(
{
@@ -17,7 +17,7 @@
const state = typeof code === 'object' ? code : {url: url, title: title};
const oldState = window.history.state;
if(title) document.title = title;
if(title) document.title = title;
if(oldState && oldState.url === url) return;
@@ -44,6 +44,7 @@
activeMenu: activeNav,
table: updateTable,
fatal: showFatalError,
zinDebug: (data, info, options) => showZinDebugInfo(data, options),
zinErrors: (data, info, options) => showErrors(data, options.id === 'page'),
};
@@ -58,86 +59,34 @@
let $bar = $('#zinbar');
if($bar.length) return;
$bar = $(
[
'<div id="zinbar" class="shadow-xl h-5 bg-inverse text-canvas fixed row items-center text-base" style="right:0;bottom:0;z-index: 99999;cursor: pointer">',
'<div id="zinErrors" class="mono row items-center"></div>',
'<div id="pagePerf" class="row items-center"></div>',
'<div id="partPerf" class="row items-center"></div>',
'<div id="zinErrorList" class="mono absolute shadow-xl ring rounded fade-from-right" style="bottom:100%;right:0;margin-bottom:1px"></div>',
'</div>'
].join('')).insertAfter('body');
$bar = $('<div id="zinbar"></div>').insertAfter('body');
zinbar = new zui.Zinbar($bar[0]);
}
$('#zinErrorList').on('click', '.zin-error-item', function(event)
{
const error = $(this).data('error');
navigator.clipboard.writeText(`vim +${error.line} ${error.file}`).then(() => {zui.Messager.show({content: 'Copied vim command to clipboard.', type: 'success'});});
event.stopPropagation();
});
$('#zinbar').on('click', () => $('#zinErrorList').toggleClass('in'));
function updateZinbar(perf, errors)
{
if(!DEBUG) return;
if(zinbar && zinbar.$) zinbar.$.update(perf, errors);
else requestAnimationFrame(() => zinbar.$.update(perf, errors));
}
function updatePerfInfo(options, stage, info)
{
if(!DEBUG || isIndexPage) return;
options[stage] = performance.now();
const $perf = options.id === 'page' ? $('#pagePerf') : $('#partPerf');
if(stage === 'requestBegin')
{
$perf.html(`<div class="opacity-50 pl-2">${options.id === 'page' ? 'PAGE' : (options.id === '#dtable' ? 'TABLE' : 'PART')}</div>`)
.append($('<div class="px-2 zin-perf-load">loading...</div>'))
.attr('title', `Loading from ${options.url}`);
if(options.id === 'page') $('#partPerf').empty();
}
else if(stage === 'requestEnd')
{
const loadTime = options.requestEnd - options.requestBegin;
$perf.find('.zin-perf-load')
.addClass('font-bold')
.html(`<i class="icon icon-arrow-down"></i>${loadTime.toFixed(2)}ms`)
.addClass(loadTime > 400 ? 'text-danger' : (loadTime > 100 ? 'text-warning' : 'text-success'))
.attr('title', `Load time for ${options.url}`);
if(info && info.dataLength)
{
$perf.append(`<div title="Load size"><i class="icon icon-cube"></i> ${zui.formatBytes(info.dataLength)}</div>`)
.append(`<div title="load speed" class="ml-1"><i class="icon icon-run"></i> ${zui.formatBytes(info.dataLength / (loadTime / 1000))}/s</div>`);
}
if(info && info.error) showErrors([{message: info.error.message}]);
}
else if(stage === 'renderBegin')
{
$perf.append($('<div class="px-2 zin-perf-render">rendering...</div>')
.attr('title', `Renderring ${options.id}`));
}
else if(stage === 'renderEnd')
{
const renderTime = options.renderEnd - options.renderBegin;
$perf.find('.zin-perf-render').addClass('font-bold').html(`<i class="icon icon-${renderTime > 200 ? 'frown' : (renderTime > 50 ? 'meh' : 'smile')}"></i> ${renderTime.toFixed(2)}ms`).addClass(renderTime > 200 ? 'text-danger' : (renderTime > 50 ? 'text-warning' : 'text-success')).attr('title', `Render time for ${options.id}`);
}
if(!DEBUG) return;
const perf = {id: options.id, url: options.url || currentAppUrl};
perf[stage] = performance.now();
if(stage === 'requestBegin') $.extend(perf, {requestEnd: undefined, renderBegin: undefined, renderEnd: undefined});
if(info && info.dataSize) perf.dataSize = info.dataSize;
updateZinbar(perf);
}
function showErrors(data, clear)
function showZinDebugInfo(data, options)
{
if(!DEBUG) return;
const $count = $('#zinErrors').empty();
const $list = $('#zinErrorList');
if(clear) $list.empty();
data.forEach((error) =>
{
const phpErrType = phpErrTypes[error.level] || 'info';
const errType = phpErrType.includes('error') ? 'error' : (phpErrType.includes('warning') ? 'warning' : 'info');
$(`<div class="zin-error-item ${errType}-pale text-fore px-2 py-1 ring ring-darker" style=""><div class="zin-error-msg font-bold"><strong class="text-${errType}" style="text-transform: uppercase;">${phpErrType}</strong> ${error.message}</div><div class="zin-error-info ellipsis text-sm opacity-60"><strong>vim +${error.line}</strong> ${error.file}</div></div>`).data('error', error).appendTo($list);
});
const warnings = $list.find('.zin-error-item.warning-pale').length;
const errors = $list.find('.zin-error-item.error-pale').length;
const infos = $list.find('.zin-error-item.info-pale').length;
if(errors) $count.append(`<span class="error font-bold px-2">ERRO ${errors}</span>`);
if(warnings) $count.append(`<span class="warning font-bold px-2">WARN ${warnings}</span>`);
if(infos) $count.append(`<span class="info font-bold px-2">INFO ${infos}</span>`);
if(clear && (warnings || errors || infos)) $list.addClass('in');
updateZinbar({id: options.id, trace: data.trace}, data.errors);
}
function updatePageWithHtml(data)
@@ -267,8 +216,8 @@
const url = options.url;
if(DEBUG) console.log('[APP]', 'request', options);
if(DEBUG && !selectors.includes('zinErrors()')) selectors.push('zinErrors()');
const isDebugRequest = DEBUG && selectors.length !== 1 || selectors[0] !== 'zinErrors()';
if(DEBUG && !selectors.includes('zinDebug()')) selectors.push('zinDebug()');
const isDebugRequest = DEBUG && selectors.length === 1 || selectors[0] === 'zinDebug()';
return $.ajax(
{
url: url,
@@ -276,12 +225,12 @@
beforeSend: () =>
{
updatePerfInfo(options, 'requestBegin');
if(!isDebugRequest) return;
if(isDebugRequest) return;
toggleLoading();
},
success: (data) =>
{
updatePerfInfo(options, 'requestEnd', {dataLength: data.length});
updatePerfInfo(options, 'requestEnd', {dataSize: data.length});
options.result = 'success';
try{data = JSON.parse(data);}catch(e){data = [{name: data.includes('Fatal error') ? 'fatal' : 'html', data: data}];}
if(options.updateUrl !== false) currentAppUrl = url;
@@ -536,7 +485,7 @@
initZinbar();
if(window.defaultAppUrl) loadPage(window.defaultAppUrl);
else if(DEBUG) loadCurrentPage('zinErrors()');
else if(DEBUG) loadCurrentPage();
/* Compatible with old version */
if(DEBUG && typeof window.zin !== 'object' && isInAppTab)
+10 -1
View File
@@ -13,6 +13,15 @@ namespace zin;
require_once 'zin.class.php';
/**
* Render page content with a widget to HTML.
* 将视图页面声明的所有内容通过一个部件进行渲染,并输出 HTML。
*
* @access public
* @param string $wgName
* @param string|array $options
* @return void
*/
function render(string $wgName = 'page', string|array $options = NULL)
{
$args = array();
@@ -38,7 +47,7 @@ function render(string $wgName = 'page', string|array $options = NULL)
}
global $app;
data('zinErrors', isset($app->zinErrors) ? $app->zinErrors : array());
data('zinDebug.errors', isset($app->zinErrors) ? $app->zinErrors : array());
$wg = createWg($wgName, $args);
if(!$isFullPage) $wg = fragment($wg);