Merge branch release/21.7.3 of zentao/zentaopms (#10478)

This commit is contained in:
刘刚
2025-07-21 15:10:08 +08:00
committed by Gitfox
422 changed files with 12478 additions and 2607 deletions
+1 -1
View File
@@ -1 +1 @@
21.7.2
21.7.3
+3 -3
View File
@@ -6,13 +6,13 @@
},
"zentaoext": {
"gitRepo": "zentao/zentaoext",
"gitVersion": "release/12.1"
"gitVersion": "release/12.2"
},
"zentaomax": {
"gitVersion": "release/7.1"
"gitVersion": "release/7.2"
},
"zentaoipd": {
"gitVersion": "release/4.1"
"gitVersion": "release/4.2"
},
"devops": {
"gitRepo": "zentao/devops"
+1 -1
View File
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
if(!function_exists('getWebRoot')){function getWebRoot(){}}
/* 基本设置。Basic settings. */
$config->version = '21.7.2'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->version = '21.7.3'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->liteVersion = '1.2'; // 迅捷版版本。 The version of Lite.
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
+1
View File
@@ -363,6 +363,7 @@ $config->logonMethods[] = 'user.deny';
$config->logonMethods[] = 'user.logout';
$config->logonMethods[] = 'zanode.nodelist';
$config->logonMethods[] = 'screen.viewold';
$config->logonMethods[] = 'system.backupview';
$config->openModules = array();
$config->openModules[] = 'install';
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
ALTER TABLE `zt_project` ADD `tplAcl` char(30) NOT NULL DEFAULT 'open' AFTER `whitelist`;
ALTER TABLE `zt_project` ADD `tplWhiteList` text NULL AFTER `tplAcl`;
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项级别', 'charterLevel', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterLevel', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterLevel');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项类型', 'charterCategory', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterCategory', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterCategory');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项适用市场', 'charterMarket', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterMarket', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterMarket');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项状态', 'charterStatus', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterStatus', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterStatus');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项关闭原因', 'charterCloseReason', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterCloseReason', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterCloseReason');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项审批结果', 'charterReviewResult', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterReviewResult', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterReviewResult');
INSERT INTO `zt_workflowdatasource` (`type`, `name`, `code`, `buildin`, `vision`, `createdBy`, `createdDate`, `datasource`, `view`, `keyField`, `valueField`)
SELECT 'lang', '立项审批状态', 'charterReviewStatus', '1', 'rnd', 'admin', '1970-01-01 00:00:01', 'charterReviewStatus', '', '', '' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM `zt_workflowdatasource` WHERE `type` = 'lang' AND `code` = 'charterReviewStatus');
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterLevel' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'level';
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterCategory' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'category';
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterMarket' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'market';
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterStatus' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'status';
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterCloseReason' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'closedReason';
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterReviewResult' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'reviewedResult';
UPDATE `zt_workflowfield` SET `options` = (SELECT `id` FROM `zt_workflowdatasource` WHERE `code` = 'charterReviewStatus' LIMIT 1) WHERE `module` = 'charter' AND `field` = 'reviewStatus';
INSERT INTO `zt_grouppriv` SELECT `group`, `module`, 'browsescene' AS `method` FROM `zt_grouppriv` WHERE `module` = 'testcase' AND `method` = 'browse';
+13
View File
@@ -1615,6 +1615,8 @@ CREATE TABLE IF NOT EXISTS `zt_project` (
`team` varchar(90) NOT NULL DEFAULT '',
`acl` char(30) NOT NULL DEFAULT 'open',
`whitelist` text NULL,
`tplAcl` char(30) NOT NULL DEFAULT 'open',
`tplWhiteList` text NULL,
`order` mediumint(8) unsigned NOT NULL DEFAULT '0',
`vision` varchar(10) NOT NULL DEFAULT 'rnd',
`stageBy` enum('project','product') NOT NULL DEFAULT 'product',
@@ -4447,6 +4449,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(1, 'testcase', 'batchEdit'),
(1, 'testcase', 'batchReview'),
(1, 'testcase', 'browse'),
(1, 'testcase', 'browseScene'),
(1, 'testcase', 'bugs'),
(1, 'testcase', 'confirmChange'),
(1, 'testcase', 'confirmLibcaseChange'),
@@ -5392,6 +5395,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(2, 'task', 'start'),
(2, 'task', 'view'),
(2, 'testcase', 'browse'),
(2, 'testcase', 'browseScene'),
(2, 'testcase', 'bugs'),
(2, 'testcase', 'createBug'),
(2, 'testcase', 'export'),
@@ -6057,6 +6061,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(3, 'testcase', 'batchEdit'),
(3, 'testcase', 'batchReview'),
(3, 'testcase', 'browse'),
(3, 'testcase', 'browseScene'),
(3, 'testcase', 'bugs'),
(3, 'testcase', 'confirmChange'),
(3, 'testcase', 'confirmLibcaseChange'),
@@ -7064,6 +7069,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(4, 'testcase', 'batchEdit'),
(4, 'testcase', 'batchReview'),
(4, 'testcase', 'browse'),
(4, 'testcase', 'browseScene'),
(4, 'testcase', 'bugs'),
(4, 'testcase', 'confirmChange'),
(4, 'testcase', 'confirmLibcaseChange'),
@@ -8089,6 +8095,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(5, 'task', 'start'),
(5, 'task', 'view'),
(5, 'testcase', 'browse'),
(5, 'testcase', 'browseScene'),
(5, 'testcase', 'bugs'),
(5, 'testcase', 'createBug'),
(5, 'testcase', 'export'),
@@ -8872,6 +8879,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(6, 'task', 'start'),
(6, 'task', 'view'),
(6, 'testcase', 'browse'),
(6, 'testcase', 'browseScene'),
(6, 'testcase', 'bugs'),
(6, 'testcase', 'export'),
(6, 'testcase', 'groupCase'),
@@ -9727,6 +9735,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(7, 'task', 'start'),
(7, 'task', 'view'),
(7, 'testcase', 'browse'),
(7, 'testcase', 'browseScene'),
(7, 'testcase', 'bugs'),
(7, 'testcase', 'createBug'),
(7, 'testcase', 'export'),
@@ -10445,6 +10454,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(8, 'testcase', 'batchEdit'),
(8, 'testcase', 'batchReview'),
(8, 'testcase', 'browse'),
(8, 'testcase', 'browseScene'),
(8, 'testcase', 'bugs'),
(8, 'testcase', 'confirmChange'),
(8, 'testcase', 'confirmLibcaseChange'),
@@ -11324,6 +11334,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(9, 'task', 'start'),
(9, 'task', 'view'),
(9, 'testcase', 'browse'),
(9, 'testcase', 'browseScene'),
(9, 'testcase', 'bugs'),
(9, 'testcase', 'export'),
(9, 'testcase', 'groupCase'),
@@ -11827,6 +11838,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(10, 'task', 'start'),
(10, 'task', 'view'),
(10, 'testcase', 'browse'),
(10, 'testcase', 'browseScene'),
(10, 'testcase', 'bugs'),
(10, 'testcase', 'export'),
(10, 'testcase', 'groupCase'),
@@ -12300,6 +12312,7 @@ REPLACE INTO `zt_grouppriv` (`group`, `module`, `method`) VALUES
(11, 'task', 'start'),
(11, 'task', 'view'),
(11, 'testcase', 'browse'),
(11, 'testcase', 'browseScene'),
(11, 'testcase', 'bugs'),
(11, 'testcase', 'export'),
(11, 'testcase', 'groupCase'),
+32
View File
@@ -1,3 +1,35 @@
2025-07-03 21.7.3
完成的需求
开源版:
75768 优化编辑器固定工具栏中常显的按钮
74527 在文档顶部工具栏的更多下拉菜单中打印插入禅道数据按钮
74092 文档新编辑器中插入的简单表格支持拆分单元格
74080 文档新编辑器中插入的简单表格支持合并单元格
企业版:
75451 甘特图组件支持通过插件的形式来一次性配置禅道定制样式
75138 支持导出文档中插入的看板视图和表格视图中的标题
70456 新编辑器文档支持前端直接导出 Word
70053 导出文档时可以导出文档中的关键字
70050 blocksuite编辑器中/命令插入的附件支持导出
70017 文档导出Word支持代码块完整显示及自动分页
旗舰版:
75810 实现项目模板下甘特图维护任务依赖关系的功能
75809 项目模板增加权限设置
75320 复制项目的下一步页面重构
75305 创建项目页面只展示使用中的项目模板
75209 复制项目新增数据逻辑
75035 隐藏执行的功能
75019 从已有项目创建页面优化
74203 由模板创建项目新增数据逻辑
74194 实现项目模板下文档的功能
74158 实现项目模板下审批的功能
73893 实现项目模板的停用功能
73892 实现项目模板的发布功能
73889 实现项目模板的编辑功能
修复的Bug
旗舰版:
63839 报告模板的二级导航名称错误应该是【报告模板】
2025-06-27 21.7.2
完成的需求
开源版:
@@ -3,7 +3,6 @@
.kanban-card .panel {margin: 0 0; border: 1px solid #DCDCDC; border-radius: 4px; box-shadow: none; cursor: pointer; height: 160px; margin-bottom: 5px;}
.kanban-card .panel:hover {border-color: #006AF1; box-shadow: 0 0 10px 0 rgba(0,0,100,.25);}
.kanban-card .panel:hover .kanban-actions {visibility: unset;}
.kanban-card .kanban-actions {position: absolute; top: 4px; right: 4px;}
.kanban-card .kanban-actions .dropdown-menu.pull-right {top:31px; right: -84px; left: auto;}
.kanban-card .kanban-actions .dropdown-menu.pull-left {top:31px; right:-1px; left: auto;}
.kanban-card .kanban-desc {color: #838a9d; word-break:break-all; height: 85px; overflow: hidden; display: -webkit-box;}
@@ -72,8 +72,7 @@ $fnBuildSingleCard = function($kanban) use ($executionActions, $lang, $kanbanvie
return cell
(
setID("kanban-{$kanban->id}"),
setClass('kanban-card'),
set::width('1/4'),
setClass('kanban-card flex-none w-1/4'),
set('data-url', createLink('execution', $kanbanview, "kanbanID=$kanban->id")),
div
(
@@ -82,16 +81,20 @@ $fnBuildSingleCard = function($kanban) use ($executionActions, $lang, $kanbanvie
(
setClass('panel-heading justify-start'),
set::style(array('position' => 'relative')),
span(setClass("label kanban-status-{$kanban->status}"), zget($lang->execution->statusList, $kanban->status)),
h::strong(setClass('kanban-name'), set::title($kanban->name), $kanban->name),
span(setClass("label kanban-status-{$kanban->status} flex-none"), zget($lang->execution->statusList, $kanban->status)),
h::strong(setClass('kanban-name flex-auto text-clip'), set::title($kanban->name), $kanban->name),
$canActions ? div
(
setClass("kanban-actions kanban-actions{$kanban->id}"),
dropdown
(
icon('ellipsis-v'),
set::caret(false),
btn
(
setClass('ghost square open-url not-open-url'),
set::icon('ellipsis-v')
),
set::items($actionItems),
set::hasIcons(true)
)
) : null
),
@@ -101,7 +104,7 @@ $fnBuildSingleCard = function($kanban) use ($executionActions, $lang, $kanbanvie
div(setClass('kanban-desc'), html($kanban->desc)),
row
(
setClass('kanban-footer bottom-2'),
setClass('kanban-footer bottom-6'),
set::style(array('position' => 'relative')),
cell
(
@@ -129,7 +132,7 @@ $fnShowCards = function($kanbanList) use ($fnBuildSingleCard)
$cards = array();
foreach($kanbanList as $kanban) $cards[] = $fnBuildSingleCard($kanban);
return row(setClass('kanban-cards'), $cards);
return row(setClass('kanban-cards flex flex-wrap'), $cards);
};
data('recTotal', count($kanbanList));
@@ -0,0 +1,24 @@
<?php
helper::importControl('story');
class myStory extends story
{
/**
* 关闭需求。
* Close the story.
*
* @param int $storyID
* @param string $from
* @param string $storyType
* @access public
* @return void
*/
public function close(int $storyID, string $from = '', string $storyType = 'story')
{
foreach($this->lang->{$storyType}->reasonList as $key => $value)
{
if(!in_array($key, array('done', 'duplicate', 'cancel'))) unset($this->lang->{$storyType}->reasonList[$key]);
}
return parent::close($storyID, $from, $storyType);
}
}
@@ -1,5 +0,0 @@
<?php
foreach($reasonList as $key => $value)
{
if(!in_array($key, array('done', 'duplicate', 'cancel'))) unset($reasonList[$key]);
}
+49 -2
View File
@@ -57,7 +57,7 @@ class baseHelper
if(!is_object(${$objName}) or empty($key)) return false;
$key = str_replace('.', '->', $key);
$value = serialize($value);
$code = ("\$${objName}->{$key}=unserialize(<<<EOT\n$value\nEOT\n);");
$code = ("\${$objName}->{$key}=unserialize(<<<EOT\n$value\nEOT\n);");
eval($code);
return true;
}
@@ -832,6 +832,13 @@ class baseHelper
if(empty($sessionID)) $sessionID = sha1((string)mt_rand(0, mt_getrandmax()));
session_write_close();
if(ini_get('session.save_handler') == 'user')
{
$ztSessionHandler = new ztSessionHandler();
session_set_save_handler($ztSessionHandler, true);
}
session_id($sessionID);
session_start();
@@ -885,7 +892,7 @@ class baseHelper
* @access public
* @return bool
*/
public static function setcookie(string $name, string|int|bool $value = '', int $expire = null, string $path = null, string $domain = '', bool $secure = null, bool $httponly = true)
public static function setcookie(string $name, string|int|bool $value = '', ?int $expire = null, ?string $path = null, string $domain = '', ?bool $secure = null, bool $httponly = true)
{
if(defined('RUN_MODE') && RUN_MODE == 'test')
{
@@ -1488,3 +1495,43 @@ function arrayUnion(...$args): array
}
return $result;
}
/**
* 压缩 ID 列表。
* Compress ID list.
*
* @param string|array $idList
* @access public
* @return string
*/
function compress(string|array $idList): string
{
if(is_string($idList)) $idList = array_filter(explode(',', $idList));
if(!is_array($idList)) return $idList;
$firstID = reset($idList);
if(!is_numeric($firstID) || (float)$firstID != $firstID) return $idList; // If the first ID is not numeric, return the original array.
$idList = array_values($idList);
asort($idList);
$encoded = [$idList[0]];
for($i = 1; $i < count($idList); $i++) $encoded[] = $idList[$i] - $idList[$i-1];
return gzcompress(implode(',', $encoded));
}
/**
* 解压缩 ID 列表。
* Uncompress ID list.
*
* @param string $encoded
* @access public
* @return array
*/
function uncompress(string $encoded): array
{
$decoded = explode(',', gzuncompress($encoded));
for($i = 1; $i < count($decoded); $i++) $decoded[$i] += $decoded[$i-1];
return $decoded;
}
+48 -38
View File
@@ -853,7 +853,7 @@ class baseRouter
*/
public function setDebug()
{
if(!empty($this->config->debug)) error_reporting(E_ALL & ~ E_STRICT);
if(!empty($this->config->debug)) error_reporting(E_ALL);
}
/**
@@ -949,11 +949,8 @@ class baseRouter
if(empty($account) and isset($_COOKIE['za'])) $account = $_COOKIE['za'];
$vision = '';
$sql = new sql();
if($this->config->installed and validater::checkAccount($account) and !$this->upgrading)
{
$account = $sql->quote($account);
if(!empty($_COOKIE['vision']))
{
$vision = $_COOKIE['vision'];
@@ -1229,15 +1226,9 @@ class baseRouter
if(is_writable($savePath))
{
session_save_path($this->getTmpRoot() . 'session');
$ztSessionHandler = new ztSessionHandler();
session_set_save_handler(
array($ztSessionHandler, 'open'),
array($ztSessionHandler, 'close'),
array($ztSessionHandler, 'read'),
array($ztSessionHandler, 'write'),
array($ztSessionHandler, 'destroy'),
array($ztSessionHandler, 'gc')
);
session_set_save_handler($ztSessionHandler, true);
}
}
@@ -3359,7 +3350,7 @@ class baseRouter
}
/* Show non-serious errors to classic page. */
if($level == E_NOTICE or $level == E_WARNING or $level == E_STRICT or $level == 8192)
if($level == E_NOTICE or $level == E_WARNING or $level == 8192)
{
$cmd = "vim +$line $file";
$size = strlen($cmd);
@@ -3548,31 +3539,45 @@ class baseRouter
}
}
/**
* config类。
* The config class.
*
* @package framework
*/
class config
if(version_compare(PHP_VERSION, '8.2.0', '>='))
{
#[\AllowDynamicProperties] // https://www.php.net/manual/zh/class.allowdynamicproperties.php
class config
{
public function set(string $key, $value)
{
helper::setMember('config', $key, $value);
}
}
}
else
{
/**
* 设置成员变量,成员可以是'db.user'类似的格式。
* Set the value of a member. the member can be the format like db.user.
* config类。
* The config class.
*
* <code>
* <?php
* $config->set('db.user', 'wwccss');
* ?>
* </code>
* @param string $key the key of the member
* @param mixed $value the value
* @access public
* @return void
* @package framework
*/
public function set(string $key, $value)
class config
{
helper::setMember('config', $key, $value);
/**
* 设置成员变量,成员可以是'db.user'类似的格式。
* Set the value of a member. the member can be the format like db.user.
*
* <code>
* <?php
* $config->set('db.user', 'wwccss');
* ?>
* </code>
* @param string $key the key of the member
* @param mixed $value the value
* @access public
* @return void
*/
public function set(string $key, $value)
{
helper::setMember('config', $key, $value);
}
}
}
@@ -3582,6 +3587,7 @@ class config
*
* @package framework
*/
#[AllowDynamicProperties]
class language
{
/**
@@ -3626,6 +3632,7 @@ class language
*
* @package framework
*/
#[AllowDynamicProperties]
class super
{
/**
@@ -3797,7 +3804,7 @@ class EndResponseException extends \Exception
*
* @package framework
*/
class ztSessionHandler
class ztSessionHandler implements SessionHandlerInterface
{
public $sessSavePath;
public $sessionFile;
@@ -3853,6 +3860,7 @@ class ztSessionHandler
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function open($savePath, $sessionName): bool
{
$this->sessSavePath = $savePath;
@@ -3866,6 +3874,7 @@ class ztSessionHandler
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function close(): bool
{
return true;
@@ -3878,6 +3887,7 @@ class ztSessionHandler
* @access public
* @return string|false
*/
#[\ReturnTypeWillChange]
public function read($id): string|false
{
$sessFile = $this->getSessionFile($id);
@@ -3894,6 +3904,7 @@ class ztSessionHandler
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function write($id, $sessData): bool
{
$sessFile = $this->getSessionFile($id);
@@ -3913,6 +3924,7 @@ class ztSessionHandler
* @access public
* @return bool
*/
#[\ReturnTypeWillChange]
public function destroy($id): bool
{
$sessFile = $this->getSessionFile($id);
@@ -3928,16 +3940,14 @@ class ztSessionHandler
* @access public
* @return int|false
*/
#[\ReturnTypeWillChange]
public function gc($maxlifeTime): int|false
{
$time = time();
$count = 0;
foreach(glob("{$this->sessSavePath}/sess_*") as $fileName)
{
if(filemtime($fileName) + $maxlifeTime < $time)
{
if(unlink($fileName)) $count++;
}
if(filemtime($fileName) + $maxlifeTime < $time && unlink($fileName)) $count++;
}
return $count;
+15 -3
View File
@@ -18,6 +18,8 @@
* @package framework
*/
include __DIR__ . '/base/control.class.php';
#[AllowDynamicProperties]
class control extends baseControl
{
/**
@@ -515,7 +517,7 @@ class control extends baseControl
* @access public
* @return zin\fieldList
*/
public function appendExtendFields(zin\fieldList $fields, string $moduleName = '', string $methodName = '', object $object = null): zin\fieldList
public function appendExtendFields(zin\fieldList $fields, string $moduleName = '', string $methodName = '', ?object $object = null): zin\fieldList
{
if($this->config->edition == 'open') return $fields;
if(!empty($this->app->installing) || !empty($this->app->upgrading)) return $fields;
@@ -534,6 +536,16 @@ class control extends baseControl
$uiID = $this->loadModel('workflowlayout')->getUIByData($flow->module, $action->action, $object);
$fieldList = $this->workflowaction->getPageFields($flow->module, $action->action, true, null, $uiID, $groupID);
/* 复制项目时显示被复制项目的工作流字段值。*/
if($moduleName == 'project' && $methodName == 'create')
{
foreach($fieldList as $field)
{
$objectValue = zget($object, $field->field, '');
if($objectValue) $field->default = $objectValue;
}
}
return $this->loadModel('flow')->buildFormFields($fields, $fieldList, array(), $object);
}
@@ -547,7 +559,7 @@ class control extends baseControl
* @access public
* @return string
*/
public function appendExtendCssAndJS(string $moduleName = '', string $methodName = '', object $object = null): string
public function appendExtendCssAndJS(string $moduleName = '', string $methodName = '', ?object $object = null): string
{
if($this->config->edition == 'open') return '';
if(!empty($this->app->installing) || !empty($this->app->upgrading)) return '';
@@ -588,7 +600,7 @@ class control extends baseControl
* @access public
* @return array
*/
public function appendExtendForm(string $position = 'info', object $object = null, string $moduleName = '', string $methodName = ''): array
public function appendExtendForm(string $position = 'info', ?object $object = null, string $moduleName = '', string $methodName = ''): array
{
if($this->config->edition == 'open') return array();
if(!empty($this->app->installing) || !empty($this->app->upgrading)) return array();
+1 -1
View File
@@ -509,7 +509,7 @@ function initPageEntity(object $object): array
* @access public
* @return array
*/
function initTableData(array $items, array &$fieldList, object $model = null, string $moduleName = ''): array
function initTableData(array $items, array &$fieldList, ?object $model = null, string $moduleName = ''): array
{
if(!empty($_GET['orderBy']) && strpos($_GET['orderBy'], '-') !== false) list($orderField, $orderValue) = explode('_', $_GET['orderBy']);
if(!empty($orderField) && !empty($orderValue) && !empty($fieldList[$orderField]))
+2
View File
@@ -18,6 +18,8 @@
* @package framework
*/
include __DIR__ . '/base/model.class.php';
#[AllowDynamicProperties]
class model extends baseModel
{
/**
+3
View File
@@ -18,6 +18,9 @@
* @package framework
*/
include __DIR__ . '/base/router.class.php';
#[AllowDynamicProperties]
class router extends baseRouter
{
/**
+1 -1
View File
@@ -441,7 +441,7 @@ class baseDAO
* @access public
* @return void
*/
public function setCache($key, $sql = '', $value = null, int $ttl = null)
public function setCache($key, $sql = '', $value = null, ?int $ttl = null)
{
if(!$this->app->isServing() || empty($this->cache)) return false;
+9
View File
@@ -50,6 +50,15 @@ class cache
*/
private $dao;
/**
* 全局数据库操作句柄。
* Global database operation handler.
*
* @access private
* @var object
*/
private $dbh;
/**
* 全局配置对象。
* Global configuration object.
+1
View File
@@ -225,6 +225,7 @@ class dao extends baseDAO
$method = $this->app->getMethodName();
if($module == 'story' && $this->app->rawModule == 'requirement') $module = 'requirement';
if($module == 'story' && $this->app->rawModule == 'epic') $module = 'epic';
if($module == 'project' && $method == 'createtemplate') $method = 'create';
$flowAction = $this->dbh->query("SELECT * FROM " . TABLE_WORKFLOWACTION . " WHERE `module` = '{$module}' AND `action` = '{$method}' AND `buildin` = '1' AND `extensionType` = 'extend' AND `vision` = '{$this->config->vision}'")->fetch(PDO::FETCH_OBJ);
if(!$flowAction) return $this;
+6 -2
View File
@@ -68,7 +68,7 @@ class form extends fixer
* @param int $objectID
* @return form
*/
public static function data(array $configObject = null, int $objectID = 0): form
public static function data(?array $configObject = null, int $objectID = 0): form
{
global $app, $config;
@@ -85,7 +85,7 @@ class form extends fixer
* @param array|null $configObject
* @return form
*/
public static function batchData(array $configObject = null): form
public static function batchData(?array $configObject = null): form
{
global $app, $config;
@@ -119,6 +119,10 @@ class form extends fixer
if($moduleName == 'projectrelease') $moduleName = 'release';
if($moduleName == 'projectbuild') $moduleName = 'build';
/* 项目复制用项目创建的工作流。 */
if($moduleName == 'project' && $methodName == 'copyconfirm') $methodName = 'create';
if($moduleName == 'project' && $methodName == 'edittemplate') $methodName = 'edit';
/* 用户需求和业务需求用自己的工作流。*/
if($moduleName == 'story' && $app->rawModule == 'requirement') $moduleName = 'requirement';
if($moduleName == 'story' && $app->rawModule == 'epic') $moduleName = 'epic';
+1 -1
View File
@@ -651,7 +651,7 @@ class mobile
* from the $headers array instead.
*/
public function __construct(
array $headers = null,
?array $headers = null,
$userAgent = null
) {
$this->setHttpHeaders($headers);
+7
View File
@@ -7,6 +7,13 @@
*/
class pinyin
{
/**
* Segments.
* @var array
*
*/
protected array $segments = array();
/**
* Constructor.
*
+2 -2
View File
@@ -61,9 +61,9 @@ class scm
* @param int $pageID
* @return array
*/
public function branch(string $showDetail = '', string $orderBy = '', int $limit = 0, int $pageID = 1)
public function branch(string $showDetail = '', string $orderBy = '', int $limit = 0, int $pageID = 1, string $label = '')
{
return $this->engine->branch($showDetail, $orderBy, $limit, $pageID);
return $this->engine->branch($showDetail, $orderBy, $limit, $pageID, $label);
}
/**
-2
View File
@@ -17,8 +17,6 @@ require_once __DIR__ . DS . 'zin.func.php';
class item extends node
{
public bool $notRenderInGlobal = true;
public function build(): array|node|directive
{
if($this->parent instanceof node && method_exists($this->parent, 'onBuildItem'))
+8
View File
@@ -47,6 +47,14 @@ class jsCallback extends jsHelper
*/
public bool $isArrowFunc = false;
/**
* Parent node
*
* @access public
* @var node
*/
public $parent = null;
/**
* 构造函数。
*
+2
View File
@@ -57,6 +57,8 @@ class node implements \JsonSerializable
public array $eventBindings = array();
public ?bool $notRenderInGlobal = null;
public function __construct(mixed ...$args)
{
$this->gid = static::nextGid();
+1 -1
View File
@@ -109,7 +109,7 @@ class props extends \zin\utils\dataset
if($value) $this->setVal($name, $value);
}
public function bindEvent(string|array $name, string|array $handler = null)
public function bindEvent(string|array $name, mixed $handler = null)
{
if(is_array($name))
{
+1 -1
View File
@@ -25,7 +25,7 @@ class set extends setting implements iDirective
* @param array|object|string $data Properties list array.
* @param mixed $value Property value.
*/
public function __construct(array|string $data = null, mixed $value = null)
public function __construct(mixed $data = null, mixed $value = null)
{
parent::__construct($data, $value);
+1 -1
View File
@@ -136,7 +136,7 @@ class setting extends \zin\utils\dataset
* @param mixed $value - Setting value.
* @return setting
*/
function setting(array|string $setting = null, mixed $value = null): setting
function setting(mixed $setting = null, mixed $value = null): setting
{
return new setting($setting, $value);
}
+10 -2
View File
@@ -25,6 +25,14 @@ class dataset implements \JsonSerializable
*/
protected array $storedData = array();
/**
* Parent node
*
* @access public
* @var node
*/
public $parent = null;
/**
* Create an instance, the initialed data can be passed.
*
@@ -32,7 +40,7 @@ class dataset implements \JsonSerializable
* @param array|object|string $data Properties list array.
* @param mixed $value Property value.
*/
public function __construct(array|string $data = null, mixed $value = null)
public function __construct($data = null, $value = null)
{
if($data !== null) $this->set($data, $value);
}
@@ -191,7 +199,7 @@ class dataset implements \JsonSerializable
* @param mixed $defaultValue Optional default value if actual value is null.
* @return mixed
*/
public function get(string|array $prop = null, mixed $defaultValue = null): mixed
public function get($prop = null, mixed $defaultValue = null): mixed
{
if(is_null($prop)) return $this->storedData;
+2 -2
View File
@@ -46,7 +46,7 @@ class backBtn extends btn
'task' => 'execution-task,my-work,my-contribute,execution-tree,execution-grouptask,project-execution,product-track,repo-view,story-change,execution-kanban,execution-taskkanban,my-index,feedback-adminview,projectstory-track,execution-calendar,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,task-view',
'story' => 'product-browse,projectstory-story,execution-story,my-work,my-contribute,productplan-view,build-view,projectbuild-view,product-track,repo-view,testcase-zerocase,my-index,feedback-adminview,projectstory-track,task-view,my-effort,company-effort,company-calendar,demandpool-track,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,ticket-browse,ticket-view,roadmap-view,charter-view,demand-view,review-assess,review-audit,action-trash',
'bug' => 'bug-browse,project-bug,my-work,my-contribute,execution-bug,bug-view,qa-index,execution-task,product-track,execution-task,task-view,repo-view,story-change,repo-review,feedback-adminview,my-index,projectstory-track,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,ticket-browse,ticket-view,productplan-view',
'testcase' => 'testcase-browse,project-testcase,my-work,my-contribute,execution-testcase,testtask-cases,testsuite-view,product-browse,testcase-view,qa-index,caselib-browse,product-track,story-change,my-index,projectstory-track,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,testreport-view,bug-browse,project-bug,bug-view',
'testcase' => 'testcase-browse,testcase-browseScene,project-testcase,my-work,my-contribute,execution-testcase,testtask-cases,testsuite-view,product-browse,testcase-view,qa-index,caselib-browse,product-track,story-change,my-index,projectstory-track,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,testreport-view,bug-browse,project-bug,bug-view',
'testsuite' => 'testsuite-browse,testsuite-view,testtask-cases,my-index,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view',
'testtask' => 'testtask-browse,testtask-cases,qa-index,testcase-browse,execution-build,my-index,my-effort,company-effort,company-calendar,product-dynamic,project-dynamic,execution-dynamic,project-view,execution-view,project-testcase',
'testreport' => 'testreport-browse,project-testreport,execution-testreport,execution-testtask',
@@ -61,7 +61,7 @@ class backBtn extends btn
'build' => 'execution-build,build-view,project-index,kanban-view,testtask-browse,projectbuild-browse,my-effort,company-effort,company-calendar,my-index,project-dynamic,execution-dynamic,project-view,execution-view',
'projectbuild' => 'projectbuild-browse,projectbuild-view,project-index',
'repo' => 'repo-maintain,repo-log,repo-browse,repo-view,repo-review,mr-view,repo-browsewebhooks,repo-browserule',
'mr' => 'pullreq-browse,mr-browse',
'mr' => 'pullreq-browse,mr-browse,my-audit',
'pullreq' => 'pullreq-browse',
'compile' => 'compile-browse',
'store' => 'store-browse',
+27
View File
@@ -61,6 +61,33 @@ window.setZentaoSlashMenu = function(menus, title, vision, position)
zui.Editor.setGlobalConfig({slashMenuStyles: [`link:${$.libRoot}zui.zentao.css`]});
}
/**
* 更多菜单操作。
* More menu action.
*
* @param {MoreMenuItem} menu
*/
window.moreMenuAction = function(menu)
{
if (menu.module && menu.method && menu.params)
{
if(menu.key == 'gantt' && !menu.isModal)
{
const doc = getDocApp().doc;
$.ajaxSubmit({
url: $.createLink(menu.module, menu.method, menu.params),
data: {templateID: doc.data.id}
});
}
else {
zui.Modal.open({
size: menu.modalSize ? menu.modalSize : 'lg',
url: $.createLink(menu.module, menu.method, menu.params)
});
}
}
}
/* 获取 ZentaoList 块属性。 */
function getZentaoListProps(type, blockID, props)
{
+48 -1
View File
@@ -125,6 +125,42 @@ class docApp extends wg
return $menus;
}
/**
* 转换禅道数据菜单格式
* Convert zentao list menu format.
*
* @access public
* @return array
*/
protected function convertZentaoListMenu(array $list): array
{
$menus = array();
foreach($list as $item)
{
$menu = array();
if(isset($item['key'])) $menu['key'] = $item['key'];
if(isset($item['name'])) $menu['text'] = $item['name'];
if(isset($item['icon'])) $menu['icon'] = $item['icon'];
if(isset($item['module'])) $menu['module'] = $item['module'];
if(isset($item['method'])) $menu['method'] = $item['method'];
if(isset($item['params'])) $menu['params'] = $item['params'];
if(isset($item['priv'])) $menu['priv'] = $item['priv'];
if(isset($item['isModal'])) $menu['isModal'] = $item['isModal'];
if(isset($item['modalSize'])) $menu['modalSize'] = $item['modalSize'];
if(isset($item['subMenu']))
{
$menu['items'] = $this->convertZentaoListMenu($item['subMenu']);
}
$menus[] = $menu;
}
return $menus;
}
protected function build()
{
global $app, $lang, $config;
@@ -278,6 +314,15 @@ class docApp extends wg
$historyPanelProps = array('fileListProps' => $fileListProps);
$canPreviewOffice = $canDownload && isset($config->file->libreOfficeTurnon) and $config->file->libreOfficeTurnon == 1;
$zentaoListMenu = $hasZentaoSlashMenu ? $this->getZentaoListMenu() : array();
$moreMenus = $zentaoListMenu
? array_merge([array(
'text' => $lang->doc->zentaoData,
'type' => 'heading',
)], $this->convertZentaoListMenu($zentaoListMenu))
: array();
return zui::docApp
(
set::_class('shadow rounded ring canvas'),
@@ -321,9 +366,11 @@ class docApp extends wg
set::langData($langData),
set::historyPanel($historyPanelProps),
set::showToolbar(true),
set::moreMenu($moreMenus),
set::moreMenuAction(jsRaw('window.moreMenuAction')),
set::canPreviewOffice($canPreviewOffice),
set::fileInfoUrl($fileInfoUrl),
$hasZentaoSlashMenu ? jsCall('setZentaoSlashMenu', $this->getZentaoListMenu(), $lang->doc->zentaoData, $config->vision, $config->doc->zentaoListMenuPosition) : null
$hasZentaoSlashMenu ? jsCall('setZentaoSlashMenu', $zentaoListMenu, $lang->doc->zentaoData, $config->vision, $config->doc->zentaoListMenuPosition) : null
);
}
}
+1
View File
@@ -3,3 +3,4 @@
#pick-pop-admin-menu .dropmenu-item + .dropmenu-item {margin-top: 4px}
#pick-pop-admin-menu .dropmenu-item.active {background-color: unset;}
#pick-pop-admin-menu .dropmenu-item:hover {color: rgba(var(--color-primary-500-rgb),var(--tw-text-opacity)); background-color: rgba(var(--color-primary-50-rgb),var(--tw-bg-opacity));}
.dropmenu-item .item-content .label {flex-shrink: 0;}
+7 -5
View File
@@ -31,14 +31,15 @@ class editor extends wg
);
protected static string $css = <<<CSS
.editor {border: unset; border-radius: unset;}
.editor {border: unset; border-radius: unset; color: var(--color-fore)}
.editor.size-auto {min-height: 0;}
zen-editor-menu-item > .menu-item {color: #9ea3b0!important;}
zen-editor-menu-item > .menu-item:hover {color: var(--color-primary-400)!important; background-color: var(--color-gray-200)!important;}
zen-editor-menu-item {display: inline-flex; align-items: center;}
zen-editor-menu-item > .menu-item {color: #64758B!important; display: inline-flex; align-items: center;}
zen-editor-menu-item > .menu-item:hover {color: var(--color-primary-400)!important; background-color: var(--color-gray-100)!important;}
zen-editor-menu-item > .menu-item.is-active {color: var(--color-primary-400)!important; background-color: transparent!important; box-shadow: inset 0 0 0 1px var(--color-primary-300);}
zen-editor-menu-item > .menu-item.is-active:hover {background-color: var(--color-gray-200)!important;}
zen-editor-menu-item > .menu-item.is-active:not(:hover) {background-color: transparent!important;}
zen-editor-menu-item > .menu-item:has(.color):hover, zen-editor-menu-item > .menu-item:has(.color).is-active {background-color: transparent!important; box-shadow: inset 0 0 0 1px var(--color-primary-300)!important;}
zen-editor-menu-item > .menu-item:has(.color):hover, zen-editor-menu-item > .menu-item:has(.color).is-active {background-color: ransparent!important; box-shadow: inset 0 0 0 1px var(--color-primary-300)!important;}
.menubar {border-bottom: 1px solid #d8dbde!important; padding: 0.125rem;}
.tippy-content > div {border: 1px solid #d8dbde!important;}
.tippy-content zen-editor-menu-item {line-height: normal;}
@@ -94,7 +95,7 @@ class editor extends wg
protected function build()
{
global $lang;
global $lang, $app;
$editor = new h
(
@@ -115,6 +116,7 @@ class editor extends wg
$editor->add(set($customProps));
$editor->add(set('css', self::$css)); // Inject CSS into editor.
$editor->add(set('css-src', $app->getWebRoot() . 'js/zui3/zen-editor/zui-inject-style.css')); // Inject CSS on page, for tippy menus.
$editor->add(h('article', set('slot', 'content'), html($this->prop('value')), $this->children())); // Set initial content.
$templateType = $this->prop('templateType');
+2
View File
@@ -4,6 +4,8 @@ namespace zin;
class floatToolbar extends wg
{
protected ?object $object = null;
protected static array $defineProps = array(
'prefix?:array',
'main?:array',
+5 -1
View File
@@ -77,12 +77,13 @@ class formPanel extends panel
{
global $app;
$moduleName = $app->getModuleName();
$methodName = $app->getMethodName();
if($moduleName == 'caselib') return data('lib');
if($moduleName == 'flow') return data('data');
if($moduleName == 'productplan') return data('plan');
if($moduleName == 'projectrelease') return data('release');
if($moduleName == 'projectbuild') return data('build');
if($moduleName == 'project' && $app->getMethodName() == 'create') return data('copyProject');
if($moduleName == 'project' && ($methodName == 'create' or $methodName == 'createtemplate')) return data('copyProject') ? data('copyProject') : null;
return data($moduleName);
}
@@ -105,6 +106,9 @@ class formPanel extends panel
}
}
if($moduleName == 'project' && $methodName == 'createtemplate') $methodName = 'create';
if($moduleName == 'project' && $methodName == 'edittemplate') $methodName = 'edit';
/* 反馈转化。 */
if($moduleName == 'feedback')
{
+2 -1
View File
@@ -195,7 +195,8 @@ class relatedObjectList extends relatedList
set::icon('treemap'),
set::size('sm'),
set::type('secondary'),
setClass('my-2 pull-right'),
setClass('my-2'),
setClass($btn ? 'pull-right' : ''),
setData(array('toggle' => 'modal', 'size' => 'lg')),
setID('graphButton'),
$app->loadLang('custom')->custom->relationGraph
+5 -7
View File
@@ -22,13 +22,11 @@ class field extends setting
{
public ?fieldList $fieldList;
public ?field $parent;
public ?string $dataType;
public mixed $default;
public function __construct(string|object|array|null $nameOrProps = null, ?fieldList $fieldList = null, ?field $parent = null)
public function __construct(mixed $nameOrProps = null, ?fieldList $fieldList = null, ?field $parent = null)
{
$this->fieldList = $fieldList;
$this->parent = $parent;
@@ -120,7 +118,7 @@ class field extends setting
return $this->setVal('strong', $strong);
}
public function label(bool|string|null $label, string|object|array $classOrProps = null): field
public function label(mixed $label, mixed $classOrProps = null): field
{
$this->setVal('label', $label);
if (is_string($classOrProps)) $this->labelClass($classOrProps);
@@ -148,7 +146,7 @@ class field extends setting
return $this->setVal('labelWidth', $width);
}
public function labelHint(?string $hint, string|object|array $classOrProps = null): field
public function labelHint(?string $hint, mixed $classOrProps = null): field
{
$this->setVal('labelHint', $hint);
if (is_string($classOrProps)) $this->labelHintClass($classOrProps);
@@ -171,7 +169,7 @@ class field extends setting
return $this->setVal('labelHintIcon', $icon);
}
public function labelActions(array|false|null $actions, bool $reset = false, ?string $key = null): field
public function labelActions(mixed $actions, bool $reset = false, ?string $key = null): field
{
if($actions === false) return $this->remove('actions');
if($reset) return $this->setVal('actions', $actions);
@@ -251,7 +249,7 @@ class field extends setting
return $item;
}
public function control(string|array|object|false|null $control, array|object|null $props = null): field
public function control(string|array|object|false|null $control, mixed $props = null): field
{
if($control === false) return $this->remove('control');
+1 -1
View File
@@ -334,7 +334,7 @@ class fieldList
return isset(static::$map[$listName]) ? static::$map[$listName] : null;
}
public static function getListFields(string $listName, string $fieldNames = null): array
public static function getListFields(string $listName, ?string $fieldNames = null): array
{
if(is_null($fieldNames)) list($listName, $fieldNames) = explode('/', $listName);
if(is_null($fieldNames)) return null;
+1 -1
View File
@@ -130,7 +130,7 @@ function isAjaxRequest(?string $type = null): bool
* @param array $options
* @return directive|set
*/
function bind(string $name, bool|string|array $callback, array|string $options = null): directive|set
function bind(string $name, bool|string|array $callback, mixed $options = null): directive|set
{
if(is_string($options) && is_string($callback))
{
+12 -21
View File
@@ -20,8 +20,6 @@ class actionModel extends model
const BE_HIDDEN = 2; // The deleted object has been hidden.
const MAXCOUNT = 1000;
public $productAlias = 't2';
/**
* 创建一个操作记录。
* Create a action.
@@ -287,7 +285,7 @@ class actionModel extends model
* @access public
* @return object
*/
public function processHistory(object $history = null): object
public function processHistory(?object $history = null): object
{
if(empty($history)) return $history;
$users = $this->loadModel('user')->getPairs('noletter');
@@ -435,7 +433,7 @@ class actionModel extends model
* @access public
* @return array
*/
public function getTrashes(string $objectType, string $type, string $orderBy, object $pager = null): array
public function getTrashes(string $objectType, string $type, string $orderBy, ?object $pager = null): array
{
$noMultipleExecutions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('multiple')->eq('0')->andWhere('type')->in('sprint,kanban')->fetchPairs();
@@ -536,7 +534,7 @@ class actionModel extends model
* @access public
* @return array
*/
public function getTrashesBySearch(string $objectType, string $type, string|int $queryID, string $orderBy, object $pager = null): array
public function getTrashesBySearch(string $objectType, string $type, string|int $queryID, string $orderBy, ?object $pager = null): array
{
if($objectType == 'all') return array();
if($queryID && $queryID != 'myQueryID')
@@ -957,7 +955,7 @@ class actionModel extends model
* @access public
* @return array
*/
public function buildActionList(array $actions, array|null $users = null, bool $commentEditable = true): array
public function buildActionList(array $actions, ?array $users = null, bool $commentEditable = true): array
{
if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter');
@@ -1082,9 +1080,6 @@ class actionModel extends model
*/
public function getDynamicByProduct(int $productID, string $account = 'all', string $period = 'all', string $orderBy = 'date_desc', int $limit = 50, string $date = '', string $direction = 'next'): array
{
$count = $this->dao->select('COUNT(1) AS count')->from(TABLE_ACTIONPRODUCT)->where('product')->eq($productID)->fetch('count');
if($count > 10000) $this->productAlias = 't2 FORCE INDEX (action_product)';
return $this->getDynamic($account, $period, $orderBy, $limit, (int)$productID, 'all', 'all', $date, $direction);
}
@@ -1214,12 +1209,7 @@ class actionModel extends model
/* If the query condition include all executions, no limit execution. */
if(strpos($actionQuery, $allExecutions) !== false) $actionQuery = str_replace($allExecutions, '1 = 1', $actionQuery);
if($productID)
{
$actionQuery = str_replace(" `product` = '{$productID}'", " t2.`product` = '{$productID}'", $actionQuery);
$count = $this->dao->select('count(1) as count')->from(TABLE_ACTIONPRODUCT)->where('product')->eq($productID)->fetch('count');
if($count > 10000) $this->productAlias = 't2 FORCE INDEX (action_product)';
}
if($productID) $actionQuery = str_replace(" `product` = '{$productID}'", " t2.`product` = '{$productID}'", $actionQuery);
if($date) $actionQuery = "({$actionQuery}) AND " . ('date' . ($direction == 'next' ? '<' : '>') . "'{$date}'");
/* 如果当前版本为lite,则过滤掉产品相关的动态。 */
@@ -1257,7 +1247,7 @@ class actionModel extends model
if($noMultipleExecutions) $condition = "({$condition}) AND (`objectType` != 'execution' OR (`objectType` = 'execution' AND `objectID`" . (count($noMultipleExecutions) == 1 ? ' != ' . reset($noMultipleExecutions) : ' NOT ' . helper::dbIN($noMultipleExecutions)) . "))";
return $this->dao->select('action.*')->from(TABLE_ACTION)->alias('action')
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias($this->productAlias)->on('action.id=t2.action')->fi()
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
->where('objectType')->notIN($this->config->action->ignoreObjectType4Dynamic)
->andWhere('action.action')->notIN($this->config->action->ignoreActions4Dynamic)
->andWhere("({$sql})")
@@ -1956,7 +1946,7 @@ class actionModel extends model
$hasProduct = preg_match('/t2\.(`?)product/', $condition);
$condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
$actions = $this->dao->select('action.id')->from(TABLE_ACTION)->alias('action')
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias($this->productAlias)->on('action.id=t2.action')->fi()
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
->where($condition)
->andWhere('`date`' . ($direction == 'next' ? '<' : '>') . "'{$date}'")
->beginIF($direction == 'next' && $beginAndEnd['begin'] != EPOCH_DATE)->andWhere('date')->ge($beginAndEnd['begin'])->fi()
@@ -2568,11 +2558,12 @@ class actionModel extends model
$table = $this->actionTao->getActionTable($period);
$hasProduct = preg_match('/t2\.(`?)product/', $condition);
$useIndex = $hasProduct && strpos('mysql,oceanbase', $this->config->db->driver) !== false ? ' USE INDEX (`vision_date`)' : ''; // 关联产品时指定索引以提高查询性能。Specify index to improve query performance when associated with product.
$beginAndEnd = $this->computeBeginAndEnd($period, '', 'next');
$condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
$actions = $this->dao->select('action.id')->from($table)->alias('action')
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias($this->productAlias)->on('action.id=t2.action')->fi()
$actions = $this->dao->select('action.id')->from($table)->alias('action' . $useIndex)
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
->where($condition)
->beginIF($beginAndEnd['begin'] != EPOCH_DATE)->andWhere('date')->ge($beginAndEnd['begin'])->fi()
->beginIF($beginAndEnd['end'] != FUTURE_DATE)->andWhere('date')->le($beginAndEnd['end'])->fi()
@@ -2630,7 +2621,7 @@ class actionModel extends model
$lastDate = substr($lastAction->originalDate, 0, 10);
$condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
$actions = $this->dao->select('action.id')->from(TABLE_ACTION)->alias('action')
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias($this->productAlias)->on('action.id=t2.action')->fi()
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
->where($condition)
->andWhere("`date`")->ge($lastDate)
->andWhere("`date`")->lt($lastDate . ' 23:59:59')
@@ -2659,7 +2650,7 @@ class actionModel extends model
$lastAction = $this->dao->select('*')->from(TABLE_ACTION)->where('id')->eq($lastActionID)->fetch();
$lastDate = substr($lastAction->date, 0, 10);
$actions = $this->dao->select('action.*')->from(TABLE_ACTION)->alias('action')
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias($this->productAlias)->on('action.id=t2.action')->fi()
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
->where($condition)
->andWhere("`date`")->ge($lastDate)
->andWhere("`date`")->lt($lastDate . ' 23:59:59')
+1 -1
View File
@@ -703,7 +703,7 @@ class actionTao extends actionModel
if($productID === 'notzero') $hasProduct = true;
return $this->dao->select('action.*')->from($actionTable)->alias('action')
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias($this->productAlias)->on('action.id=t2.action')->fi()
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
->where('objectType')->notIN($this->config->action->ignoreObjectType4Dynamic)
->andWhere('action.action')->notIN($this->config->action->ignoreActions4Dynamic)
->andWhere('vision')->eq($this->config->vision)
@@ -22,9 +22,8 @@ cid=1
- 查找用户admin动态 @12
- 查找用户dev17动态 @12
- 查找用户test18动态 @11
- 查找今天的动态 @0
- 查找昨天的动态 @1
- 查找上周的动态 @3
- 查找今天的动态 @1
- 查找昨天的动态 @0
- 查找今天的动态 @0
- 查找用户admin动态 @12
- 查找用户dev17动态 @12
@@ -50,9 +49,8 @@ r($action->getDynamicByAccountTest($accountList[3])) && p() && e('11'); // 查
zenData('action')->loadYaml('action_week')->gen(35, true, false);
zenData('actionrecent')->loadYaml('action_week')->gen(35, true, false);
r($action->getDynamicByAccountTest($accountList[1], $typeList[1])) && p() && e('0'); // 查找今天的动态
r($action->getDynamicByAccountTest($accountList[1], $typeList[2])) && p() && e('1'); // 查找昨天的动态
r($action->getDynamicByAccountTest($accountList[1], $typeList[3])) && p() && e('3'); // 查找上周的动态
r($action->getDynamicByAccountTest($accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
r($action->getDynamicByAccountTest($accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
zenData('action')->loadYaml('action_year')->gen(35, true, false);
zenData('actionrecent')->loadYaml('action_year')->gen(35, true, false);
@@ -27,9 +27,8 @@ cid=1
- 查找用户admin动态 @12
- 查找用户dev17动态 @12
- 查找用户test18动态 @11
- 查找今天的动态 @0
- 查找昨天的动态 @1
- 查找上周的动态 @3
- 查找今天的动态 @1
- 查找昨天的动态 @0
- 查找今天的动态 @5
*/
@@ -53,9 +52,8 @@ $action->gen(35, true, false);
$actionrecent = zenData('actionrecent')->loadYaml('action_week');
$actionrecent->execution->range('101');
$actionrecent->gen(35, true, false);
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[1])) && p() && e('0'); // 查找今天的动态
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[2])) && p() && e('1'); // 查找昨天的动态
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[3])) && p() && e('3'); // 查找上周的动态
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
$action = zenData('action')->loadYaml('action_year');
$action->execution->range('101');
@@ -63,4 +61,4 @@ $action->gen(35, true, false);
$actionrecent = zenData('actionrecent')->loadYaml('action_year');
$actionrecent->execution->range('101');
$actionrecent->gen(35, true, false);
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[0], $typeList[1])) && p() && e('5'); // 查找今天的动态
r($actionTest->getDynamicByExecutionTest($executionID, $accountList[0], $typeList[1])) && p() && e('5'); // 查找今天的动态
@@ -23,9 +23,8 @@ cid=1
- 查找用户admin动态 @3
- 查找用户dev17动态 @2
- 查找用户test18动态 @2
- 查找今天的动态 @0
- 查找昨天的动态 @1
- 查找上周的动态 @0
- 查找今天的动态 @1
- 查找昨天的动态 @0
- 查找今天的动态 @1
*/
@@ -45,10 +44,9 @@ r($actionTest->getDynamicByProductTest($productID, $accountList[3])) && p() && e
zenData('action')->loadYaml('action_week')->gen(35, true, false);
zenData('actionrecent')->loadYaml('action_week')->gen(35, true, false);
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[1])) && p() && e('0'); // 查找今天的动态
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[2])) && p() && e('1'); // 查找昨天的动态
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[3])) && p() && e('0'); // 查找上周的动态
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
r($actionTest->getDynamicByProductTest($productID, $accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
zenData('action')->loadYaml('action_year')->gen(35, true, false);
zenData('actionrecent')->loadYaml('action_year')->gen(35, true, false);
r($actionTest->getDynamicByProductTest($productID, $accountList[0], $typeList[1])) && p() && e('1'); // 查找今天的动态
r($actionTest->getDynamicByProductTest($productID, $accountList[0], $typeList[1])) && p() && e('1'); // 查找今天的动态
@@ -27,9 +27,8 @@ cid=1
- 查找用户admin动态 @12
- 查找用户dev17动态 @12
- 查找用户test18动态 @11
- 查找今天的动态 @0
- 查找昨天的动态 @1
- 查找上周的动态 @3
- 查找今天的动态 @1
- 查找昨天的动态 @0
- 查找今天的动态 @5
*/
@@ -53,9 +52,8 @@ $action->gen(35, true, false);
$actionrecent = zenData('actionrecent')->loadYaml('action_week');
$actionrecent->project->range('101');
$actionrecent->gen(35, true, false);
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[1])) && p() && e('0'); // 查找今天的动态
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[2])) && p() && e('1'); // 查找昨天的动态
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[3])) && p() && e('3'); // 查找上周的动态
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[1])) && p() && e('1'); // 查找今天的动态
r($actionTest->getDynamicByProjectTest($projectID, $accountList[1], $typeList[2])) && p() && e('0'); // 查找昨天的动态
$action = zenData('action')->loadYaml('action_year');
$action->project->range('101');
@@ -63,4 +61,4 @@ $action->gen(35, true, false);
$actionrecent = zenData('actionrecent')->loadYaml('action_year');
$actionrecent->project->range('101');
$actionrecent->gen(35, true, false);
r($actionTest->getDynamicByProjectTest($projectID, $accountList[0], $typeList[1])) && p() && e('5'); // 查找今天的动态
r($actionTest->getDynamicByProjectTest($projectID, $accountList[0], $typeList[1])) && p() && e('5'); // 查找今天的动态
+21 -15
View File
@@ -19,19 +19,25 @@ timeout=0
cid=1
- 获取排序为date倒序的所有动态
- 第46条的id属性 @46
- 第46条的objectType属性 @review
- 第78条的id属性 @78
- 第78条的objectType属性 @testcase
- 第0条的id属性 @62
- 第0条的objectType属性 @branch
- 第1条的id属性 @31
- 第1条的objectType属性 @bug
- 获取排序为date正序的所有动态
- 第1条的id属性 @1
- 第1条的objectType属性 @product
- 第0条的id属性 @63
- 第0条的objectType属性 @module
- 第1条的id属性 @32
- 第1条的objectType属性 @testcase
- 获取排序为date倒序的今年之后的动态
- 第46条的id属性 @46
- 第46条的objectType属性 @review
- 第0条的id属性 @62
- 第0条的objectType属性 @branch
- 第1条的id属性 @31
- 第1条的objectType属性 @bug
- 获取排序为date正序的所有动态
- 第2条的id属性 @2
- 第2条的objectType属性 @story
- 第0条的id属性 @61
- 第0条的objectType属性 @todo
- 第1条的id属性 @30
- 第1条的objectType属性 @build
- 获取排序为date倒序的今年之后的动态
- 第0条的id属性 @31
- 第0条的objectType属性 @bug
@@ -53,8 +59,8 @@ $directionList = array('next', 'pre');
$action = new actionTest();
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('64,testsuite;32,testcase'); // 获取排序为date倒序的所有动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[1], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('65,caselib;33,case'); // 获取排序为date正序的所有动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType;1:id,objectType') && e('64,testsuite;32,testcase'); // 获取排序为date倒序的今年之后的动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[1], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('63,module;31,bug'); // 获取排序为date倒序的今天之前的动态
r(array_values($action->getDynamicBySearchTest($queryID[1], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType') && e('31,bug'); // 获取排序为date倒序的今年之后的动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('62,branch;31,bug'); // 获取排序为date倒序的所有动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[1], $limit, $dateList[0], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('63,module;32,testcase'); // 获取排序为date正序的所有动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType;1:id,objectType') && e('62,branch;31,bug'); // 获取排序为date倒序的今年之后的动态
r(array_values($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[1], $directionList[0]))) && p('0:id,objectType;1:id,objectType') && e('61,todo;30,build'); // 获取排序为date倒序的今天之前的动态
r(array_values($action->getDynamicBySearchTest($queryID[1], $orderByList[0], $limit, $dateList[0], $directionList[1]))) && p('0:id,objectType') && e('31,bug'); // 获取排序为date倒序的今年之后的动态
+1 -1
View File
@@ -5,7 +5,7 @@ $config->admin->log->saveDays = 30;
if(!isset($config->safe)) $config->safe = new stdclass();
if(!isset($config->safe->weak)) $config->safe->weak = '123456,password,12345,12345678,qwerty,123456789,1234,1234567,abc123,111111,123123';
$config->admin->menuGroup['system'] = array('custom|mode', 'backup', 'cron', 'action|trash', 'admin|xuanxuan', 'setting|xuanxuan', 'admin|license', 'admin|checkweak', 'admin|resetpwdsetting', 'admin|safe', 'cache|setting', 'custom|timezone', 'search|buildindex', 'admin|tableengine', 'ldap', 'custom|libreoffice', 'conference', 'watermark', 'client', 'system|browsebackup', 'system|restorebackup');
$config->admin->menuGroup['system'] = array('custom|mode', 'backup', 'cron', 'action|trash', 'admin|xuanxuan', 'setting|xuanxuan', 'admin|license', 'admin|checkweak', 'admin|resetpwdsetting', 'admin|safe', 'cache|setting', 'custom|timezone', 'search|buildindex', 'admin|tableengine', 'admin|metriclib', 'ldap', 'custom|libreoffice', 'conference', 'watermark', 'client', 'system|browsebackup', 'system|restorebackup');
$config->admin->menuGroup['company'] = array('dept', 'company', 'user', 'group', 'tutorial');
$config->admin->menuGroup['switch'] = array('admin|setmodule');
$config->admin->menuGroup['model'] = array('auditcl', 'stage', 'deliverable', 'design', 'cmcl', 'reviewcl', 'custom|required', 'custom|set', 'custom|flow', 'custom|code', 'custom|percent','custom|estimate', 'custom|hours', 'subject', 'process', 'activity', 'zoutput', 'classify', 'holiday', 'reviewsetting', 'custom|project');
+12
View File
@@ -0,0 +1,12 @@
<?php
$config->admin->metricLib = new stdClass();
$config->admin->metricLib->updateSQLs[1] = 'CREATE INDEX `metricCode_system_date` ON ' . TABLE_METRICLIB . ' (`metricCode`, `system`, `date`)';
$config->admin->metricLib->updateSQLs[2] = 'CREATE INDEX `metricCode_program_date` ON ' . TABLE_METRICLIB . ' (`metricCode`, `program`, `date`)';
$config->admin->metricLib->updateSQLs[3] = 'CREATE INDEX `metricCode_project_date` ON ' . TABLE_METRICLIB . ' (`metricCode`, `project`, `date`)';
$config->admin->metricLib->updateSQLs[4] = 'CREATE INDEX `metricCode_product_date` ON ' . TABLE_METRICLIB . ' (`metricCode`, `product`, `date`)';
$config->admin->metricLib->updateSQLs[5] = 'CREATE INDEX `metricCode_execution_date` ON ' . TABLE_METRICLIB . ' (`metricCode`, `execution`, `date`)';
$config->admin->metricLib->updateSQLs[6] = 'CREATE INDEX `metricCode_user_date` ON ' . TABLE_METRICLIB . ' (`metricCode`, `user`(30), `date`)';
$config->admin->metricLib->updateSQLs[7] = 'ALTER TABLE ' . TABLE_METRICLIB . ' DROP INDEX `metricID`';
$config->admin->metricLib->updateSQLs[8] = 'ALTER TABLE ' . TABLE_METRICLIB . ' DROP INDEX `metricCode`';
$config->admin->metricLib->updateSQLs[9] = 'ALTER TABLE ' . TABLE_METRICLIB . ' DROP INDEX `date`';
$config->admin->metricLib->updateSQLs[10] = 'ALTER TABLE ' . TABLE_METRICLIB . ' DROP INDEX `deleted`';
+32
View File
@@ -354,6 +354,38 @@ class admin extends control
$this->display();
}
/**
* 更新度量库表的索引。
* Upgrade metriclib table index.
*
* @param int $key
* @access public
* @return void
*/
public function metriclib(int $key = 0)
{
$sql = $this->config->admin->metricLib->updateSQLs[$key] ?? '';
if($sql)
{
set_time_limit(0);
session_write_close();
try
{
$this->dbh->exec($sql);
}
catch(PDOException $e)
{
$this->app->triggerError($e->getMessage(), __FILE__, __LINE__);
}
if(isset($this->config->admin->metricLib->updateSQLs[++$key])) return $this->send(['result' => 'success', 'key' => $key]);
return $this->send(['result' => 'success']);
}
$this->view->title = $this->lang->metriclib->common;
$this->display();
}
/**
* 更换表引擎为InnoDB。
* Ajax change table engine.
+28
View File
@@ -0,0 +1,28 @@
/**
* Update metriclib table structure.
*/
function updateMetriclib(key)
{
$('#updateSQLs > #sql' + key).removeClass('hidden');
$('#startUpdate').remove();
$.getJSON($.createLink('admin', 'metriclib', 'key=' + key), function(resp)
{
if(resp.result == 'success')
{
$('#sql' + key).addClass('text-success').find('.icon').toggleClass('animate-spin icon-spinner-indicator icon-check');
if(resp.key)
{
updateMetriclib(resp.key);
}
else
{
$('#updated').removeClass('hidden');
}
}
else
{
zui.Modal.alert(resp.message).then(() => {loadCurrentPage()});
}
});
}
+19 -4
View File
@@ -26,10 +26,10 @@ $lang->admin->setModuleIndex = 'Set Module';
$lang->admin->mon = 'month';
$lang->admin->day = 'day';
$lang->admin->updateDynamics = 'updateDynamics';
$lang->admin->updatePatch = 'updatePatch';
$lang->admin->upgradeRecommend = 'upgradeRecommend';
$lang->admin->zentaoUsed = '';
$lang->admin->updateDynamics = 'Update Dynamics';
$lang->admin->updatePatch = 'Patch Update';
$lang->admin->upgradeRecommend = 'Upgrade Recommend';
$lang->admin->zentaoUsed = 'You have been using ZenTao';
$lang->admin->api = 'API';
$lang->admin->log = 'Log';
@@ -300,4 +300,19 @@ $lang->admin->community->uxPlan->cancel = 'Canceled';
$lang->admin->community->unBind = new stdclass();
$lang->admin->community->unBind->success = 'Disconnect';
$lang->admin->nickname = 'Nickname';
$lang->admin->position = 'Position';
$lang->admin->company = 'Company Name';
$lang->admin->solvedProblems = 'Project Management Problems';
$lang->admin->mobile = 'Mobile Phone';
$lang->admin->code = 'SMS Verification Code';
$lang->admin->agreeUX = 'User Experience Program';
$lang->admin->metricLib = new stdclass();
$lang->admin->metricLib->startUpdate = 'Start Update';
$lang->admin->metricLib->updating = 'Updating';
$lang->admin->metricLib->updated = 'Update Completed';
$lang->admin->metricLib->tips = "Due to the large amount of data in the metric library table, updating indexes may take a long time. Please operate during low business hours. During this time, you can perform other operations, but please do not close the current page or browser.";
include dirname(__FILE__) . '/menu.php';
+1
View File
@@ -48,6 +48,7 @@ $lang->admin->menuList->system['subMenu']['cron'] = array('link' => "{$la
$lang->admin->menuList->system['subMenu']['timezone'] = array('link' => "{$lang->timezone}|custom|timezone|");
$lang->admin->menuList->system['subMenu']['buildindex'] = array('link' => "{$lang->admin->buildIndex}|search|buildindex|");
$lang->admin->menuList->system['subMenu']['tableengine'] = array('link' => "{$lang->admin->tableEngine}|admin|tableengine|");
if($config->edition != 'open' && $config->vision == 'rnd') $lang->admin->menuList->system['subMenu']['metriclib'] = array('link' => "{$lang->metriclib->common}|admin|metriclib|");
if(in_array($this->config->db->driver, $this->config->mysqlDriverList)) $lang->admin->menuList->system['subMenu']['backup'] = array('link' => "{$lang->backup->common}|backup|index|");
$lang->admin->menuList->system['menuOrder']['5'] = 'mode';
+6
View File
@@ -309,4 +309,10 @@ $lang->admin->mobile = '手机号';
$lang->admin->code = '短信验证码';
$lang->admin->agreeUX = '用户体验计划';
$lang->admin->metricLib = new stdclass();
$lang->admin->metricLib->startUpdate = '开始更新';
$lang->admin->metricLib->updating = '正在更新';
$lang->admin->metricLib->updated = '更新完成';
$lang->admin->metricLib->tips = "由于度量库表数据量较大,更新索引可能需要较长时间,请在业务低峰期操作。在此期间您可以进行其他操作,但请勿关闭当前页面或浏览器。";
include dirname(__FILE__) . '/menu.php';
+55
View File
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/**
* The metric library view file of admin 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)
* @author Gang Liu <liugang@easycorp.ltd>
* @package admin
* @link https://www.zentao.net
*/
namespace zin;
$sqls = [];
foreach($config->admin->metricLib->updateSQLs as $key => $sql)
{
$sqls[] = div
(
setID('sql' . $key),
setClass('hidden'),
icon(setClass('icon icon-spinner-indicator mr-2 animate-spin')),
$sql
);
}
panel
(
setClass('m-auto w-2/3'),
set::title($lang->metriclib->common),
set::headingClass('justify-start border-b'),
div
(
setClass('mb-4'),
$lang->admin->metricLib->tips
),
div
(
setID('updateSQLs'),
setClass('mb-4'),
$sqls,
div
(
setID('updated'),
setClass('text-success hidden'),
icon(setClass('icon icon-check mr-2')),
$lang->admin->metricLib->updated
)
),
a
(
setID('startUpdate'),
setClass('btn primary'),
on::click('updateMetriclib(1)'),
$lang->admin->metricLib->startUpdate
)
);
+4 -4
View File
@@ -390,7 +390,7 @@ class apiModel extends model
* @param object $pager
* @return array
*/
public function getListByModuleID(int $libID = 0, int $moduleID = 0, int $releaseID = 0, object $pager = null): array
public function getListByModuleID(int $libID = 0, int $moduleID = 0, int $releaseID = 0, ?object $pager = null): array
{
/* Get release info. */
if($releaseID > 0)
@@ -472,7 +472,7 @@ class apiModel extends model
* @access public
* @return array
*/
public function getStructByQuery(int $libID, object $pager = null, string $orderBy = ''): array
public function getStructByQuery(int $libID, ?object $pager = null, string $orderBy = ''): array
{
return $this->dao->select('t1.*,t2.realname as addedName')->from(TABLE_APISTRUCT)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')->on('t2.account = t1.addedBy')
@@ -494,7 +494,7 @@ class apiModel extends model
* @access public
* @return array
*/
public function getStructListByRelease(object $release, string $where = '1 = 1 ', object $pager = null, string $orderBy = 'id'): array
public function getStructListByRelease(object $release, string $where = '1 = 1 ', ?object $pager = null, string $orderBy = 'id'): array
{
$strJoin = array();
if(isset($release->snap['structs']))
@@ -527,7 +527,7 @@ class apiModel extends model
* @access public
* @return array
*/
public function getReleaseByQuery(array $libID, object $pager = null, string $orderBy = ''): array
public function getReleaseByQuery(array $libID, ?object $pager = null, string $orderBy = ''): array
{
return $this->dao->select('*')->from(TABLE_API_LIB_RELEASE)
->where('lib')->in($libID)
+2
View File
@@ -138,6 +138,8 @@ if($config->inQuickon)
if($this->config->inQuickon)
{
$data = array();
$config->system->dtable->backup->fieldList['name']['link'] = array('module' => 'system', 'method' => 'backupView', 'params' => 'name={id}');
$config->system->dtable->backup->fieldList['name']['data-toggle'] = 'modal';
if(!empty($backups)) $data = initTableData($backups, $config->system->dtable->backup->fieldList, $this->system);
foreach($data as &$backup)
+5 -3
View File
@@ -330,9 +330,11 @@ $config->block->size['scrumproject']['projectdynamic'] = array(1 => 8, 2 => 8);
$config->block->size['waterfallproject']['waterfallgantt'] = array(2 => 8, 1 => 8);
$config->block->size['waterfallproject']['projectdynamic'] = array(1 => 8, 2 => 8);
$config->block->size['agileplusproject'] = $config->block->size['scrumproject'];
$config->block->size['waterfallplusproject'] = $config->block->size['waterfallproject'];
$config->block->size['ipdproject'] = $config->block->size['waterfallproject'];
/* 旗舰版和IPD版本,敏捷项目和瀑布项目都新增了区块,融合敏捷项目的区块和敏捷项目的区块保持一致,融合瀑布项目和IPD项目的区块和瀑布项目的区块保持一致 。*/
/* In the max and IPD versions, new blocks are added for scrum project and waterfall project. The blocks for agile plus project are consistent with scrum project, and the blocks for waterfall plus and IPD project are consistent with waterfall project. */
$config->block->size['agileplusproject'] = &$config->block->size['scrumproject'];
$config->block->size['waterfallplusproject'] = &$config->block->size['waterfallproject'];
$config->block->size['ipdproject'] = &$config->block->size['waterfallproject'];
$config->block->size['execution']['overview'] = array(1 => 3);
$config->block->size['execution']['statistic'] = array(2 => 5, 1 => 8);
+1
View File
@@ -1,6 +1,7 @@
$(function()
{
if($('#expiredModal').length) zui.Modal.open({id: 'expiredModal'});
if($('#metriclibModal').length) zui.Modal.open({id: 'metriclibModal'});
if($('#annualModal').length) zui.Modal.open({id: 'annualModal'});
if($('#upgradeModal').length) zui.Modal.open({id: 'upgradeModal'});
});
+7
View File
@@ -50,6 +50,13 @@ $remind ? modal
html($remind)
) : null;
$metriclibRemind = $this->misc->getMetriclibRemind();
$metriclibRemind ? modal
(
setID('metriclibModal'),
html($metriclibRemind)
) : null;
$upgradeRemind = $this->misc->getUpgradeRemind();
if($upgradeRemind)
{
+2 -2
View File
@@ -2471,7 +2471,7 @@ class blockZen extends block
$involveds = $this->product->getOrderedProducts('involved', 0, 0, 'all');
$productIdList = array_merge(array_keys($products), array_keys($involveds));
$stmt = $this->dao->select('id,product,lib,title,type,addedBy,addedDate,editedDate,status,acl,groups,readGroups,users,readUsers,deleted')->from(TABLE_DOC)->alias('t1')
$stmt = $this->dao->select('id,product,lib,title,type,addedBy,addedDate,editedDate,status,acl,`groups`,readGroups,users,readUsers,deleted')->from(TABLE_DOC)->alias('t1')
->where('deleted')->eq(0)
->andWhere('product')->in($productIdList)
->beginIF($this->config->doc->notArticleType)->andWhere('t1.type')->notIN($this->config->doc->notArticleType)->fi()
@@ -3499,7 +3499,7 @@ class blockZen extends block
* @access protected
* @return object
*/
protected function buildProjectStatistic(object $project, array $data, object $pager = null): object
protected function buildProjectStatistic(object $project, array $data, ?object $pager = null): object
{
extract($data);
$projectID = $project->id;
+1 -1
View File
@@ -53,7 +53,7 @@ class branchModel extends model
* @access public
* @return array
*/
public function getList(int $productID, int $executionID = 0, string $browseType = 'active', string $orderBy = 'order', object|null $pager = null, bool $withMainBranch = true): array
public function getList(int $productID, int $executionID = 0, string $browseType = 'active', string $orderBy = 'order', ?object $pager = null, bool $withMainBranch = true): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBranches();
+16 -6
View File
@@ -34,12 +34,17 @@ class manageTester extends tester
{
$managePage = $this->initForm('branch', 'manage', $productID, 'appIframe-product');
$managePage->dom->allTab->click();
$managePage->wait(2);
$managePage->dom->closeBtn->click();
$managePage->wait(1);
$managePage->wait(2);
$managePage->dom->confirmBtn->click();
$managePage->wait(1);
$managePage->wait(2);
$managePage->dom->allTab->click();
$managePage->wait(2);
$status = $managePage->dom->secStatus->getText();
return ($status == $this->lang->branch->statusList->closed) ? $this->success("关闭分支成功") : $this->failed("关闭分支失败");
return ($status == $this->lang->branch->statusList->closed)
? $this->success("关闭分支成功")
: $this->failed("关闭分支失败");
}
/**
@@ -53,11 +58,16 @@ class manageTester extends tester
{
$managePage = $this->initForm('branch', 'manage', $productID, 'appIframe-product');
$managePage->dom->allTab->click();
$managePage->wait(2);
$managePage->dom->activateBtn->click();
$managePage->wait(1);
$managePage->wait(2);
$managePage->dom->confirmBtn->click();
$managePage->wait(1);
$managePage->wait(2);
$managePage->dom->allTab->click();
$managePage->wait(2);
$status = $managePage->dom->secStatus->getText();
return ($status == $this->lang->branch->statusList->active) ? $this->success("激活分支成功") : $this->failed("激活分支失败");
return ($status == $this->lang->branch->statusList->active)
? $this->success("激活分支成功")
: $this->failed("激活分支失败");
}
}
+11 -11
View File
@@ -97,7 +97,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getList(string $browseType, array $productIdList, int $projectID, array $executionIdList, int|string $branch = 'all', int $moduleID = 0, int $queryID = 0, string $orderBy = 'id_desc', object $pager = null): array
public function getList(string $browseType, array $productIdList, int $projectID, array $executionIdList, int|string $branch = 'all', int $moduleID = 0, int $queryID = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
if($browseType == 'bymodule' && $this->session->bugBrowseType && $this->session->bugBrowseType != 'bysearch') $browseType = $this->session->bugBrowseType;
@@ -125,7 +125,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getPlanBugs(int $planID, string $status = 'all', string $orderBy = 'id_desc', object $pager = null): array
public function getPlanBugs(int $planID, string $status = 'all', string $orderBy = 'id_desc', ?object $pager = null): array
{
if(common::isTutorialMode()) return array();
@@ -216,7 +216,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getActiveBugs(array|int $products, int|string $branch, string $executions, array $excludeBugs, object $pager = null, string $orderBy = 'id desc'): array
public function getActiveBugs(array|int $products, int|string $branch, string $executions, array $excludeBugs, ?object $pager = null, string $orderBy = 'id desc'): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
@@ -244,7 +244,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getActiveAndPostponedBugs(array $products, int $executionID, object $pager = null): array
public function getActiveAndPostponedBugs(array $products, int $executionID, ?object $pager = null): array
{
return $this->dao->select('t1.*')->from(TABLE_BUG)->alias('t1')
->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.product = t2.product')
@@ -670,7 +670,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getBugs2Link(int $bugID, bool $bySearch = false, string $excludeBugs = '', int $queryID = 0, object $pager = null): array
public function getBugs2Link(int $bugID, bool $bySearch = false, string $excludeBugs = '', int $queryID = 0, ?object $pager = null): array
{
$bug = $this->getByID($bugID);
@@ -861,7 +861,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getUserBugs(string $account, string $type = 'assignedTo', string $orderBy = 'id_desc', int $limit = 0, object $pager = null, int $executionID = 0, int $queryID = 0): array
public function getUserBugs(string $account, string $type = 'assignedTo', string $orderBy = 'id_desc', int $limit = 0, ?object $pager = null, int $executionID = 0, int $queryID = 0): array
{
if($type != 'bySearch' and !$this->loadModel('common')->checkField(TABLE_BUG, $type)) return array();
@@ -974,7 +974,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getProjectBugs(int $projectID, int $productID = 0, int|string $branchID = 0, int $build = 0, string $type = '', int $param = 0, string $orderBy = 'id_desc', string $excludeBugs = '', object $pager = null): array
public function getProjectBugs(int $projectID, int $productID = 0, int|string $branchID = 0, int $build = 0, string $type = '', int $param = 0, string $orderBy = 'id_desc', string $excludeBugs = '', ?object $pager = null): array
{
if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
@@ -1026,7 +1026,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getExecutionBugs(int $executionID, int $productID = 0, string|int $branchID = 'all', string|array $builds = '0', string $type = '', int $param = 0, string $orderBy = 'id_desc', string $excludeBugs = '', object $pager = null): array
public function getExecutionBugs(int $executionID, int $productID = 0, string|int $branchID = 'all', string|array $builds = '0', string $type = '', int $param = 0, string $orderBy = 'id_desc', string $excludeBugs = '', ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
@@ -1088,7 +1088,7 @@ class bugModel extends model
* @access public
* @return array|null
*/
public function getProductLeftBugs(array $buildIdList, int $productID, int|string $branch = '', string $linkedBugs = '', object $pager = null): array|null
public function getProductLeftBugs(array $buildIdList, int $productID, int|string $branch = '', string $linkedBugs = '', ?object $pager = null): array|null
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
@@ -1202,7 +1202,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getReleaseBugs(array $buildIdList, int $productID, int|string $branch = 0, string $linkedBugs = '', object $pager = null): array
public function getReleaseBugs(array $buildIdList, int $productID, int|string $branch = 0, string $linkedBugs = '', ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
@@ -2143,7 +2143,7 @@ class bugModel extends model
* @access public
* @return array
*/
public function getBySearch(string $object = 'bug', array|int $productIdList = array(), int|string $branch = 0, int $projectID = 0, int $executionID = 0, int $queryID = 0, string $excludeBugs = '', string $orderBy = '', object $pager = null): array
public function getBySearch(string $object = 'bug', array|int $productIdList = array(), int|string $branch = 0, int $projectID = 0, int $executionID = 0, int $queryID = 0, string $excludeBugs = '', string $orderBy = '', ?object $pager = null): array
{
$bugQuery = $this->processSearchQuery($object, $queryID, $productIdList, (string)$branch);
+2 -2
View File
@@ -38,7 +38,7 @@ class bugTao extends bugModel
* @access protected
* @return array
*/
protected function getListByBrowseType(string $browseType, array $productIdList, int $projectID, array $executionIdList, int|string $branch, array $moduleIdList, int $queryID, string $orderBy, object $pager = null): array
protected function getListByBrowseType(string $browseType, array $productIdList, int $projectID, array $executionIdList, int|string $branch, array $moduleIdList, int $queryID, string $orderBy, ?object $pager = null): array
{
$browseType = strtolower($browseType);
@@ -112,7 +112,7 @@ class bugTao extends bugModel
* @access protected
* @return array
*/
protected function getNeedConfirmList(array $productIdList, int $projectID, array $executionIdList, int|string $branch, array $moduleIdList, string $orderBy, object $pager = null): array
protected function getNeedConfirmList(array $productIdList, int $projectID, array $executionIdList, int|string $branch, array $moduleIdList, string $orderBy, ?object $pager = null): array
{
return $this->dao->select("t1.*, t2.title AS storyTitle, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) AS priOrder, IF(t1.`severity` = 0, {$this->config->maxPriValue}, t1.`severity`) AS severityOrder")->from(TABLE_BUG)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
+5 -4
View File
@@ -1411,10 +1411,11 @@ class bugTest
*/
public function getActivatedBugsTest(string $productIdList, string $begin = '', string $end = '', string $buildIdList = ''): string
{
if($begin == 'lastweek') $begin = date('Y-m-d', strtotime('-7 days'));
if($begin == 'lastmonth') $begin = date('Y-m-d', strtotime('-30 days'));
if($end == 'nextweek') $end = date('Y-m-d', strtotime('+7 days'));
if($end == 'nextmonth') $end = date('Y-m-d', strtotime('+30 days'));
$date = '2025-05-01';
if($begin == 'lastweek') $begin = date('Y-m-d', strtotime($date . '-7 days'));
if($begin == 'lastmonth') $begin = date('Y-m-d', strtotime($date . '-30 days'));
if($end == 'nextweek') $end = date('Y-m-d', strtotime($date . '+7 days'));
if($end == 'nextmonth') $end = date('Y-m-d', strtotime($date . '+30 days'));
$bugs = $this->objectModel->getActivatedBugs(explode(',', $productIdList), $begin, $end, explode(',', $buildIdList));
if(dao::isError()) return dao::getError();
return implode(',', array_column($bugs, 'id'));
+7 -5
View File
@@ -4,7 +4,9 @@ include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/bug.unittest.class.php';
zenData('bug')->gen(30);
zenData('action')->loadYaml('action')->gen(100);
$action = zenData('action')->loadYaml('action');
$action->date->range("20250401 000000-20250601 000000:1D")->type('timestamp')->format('YY-MM-DD hh:mm:ss');
$action->gen(100);
zenData('history')->loadYaml('history')->gen(100);
zenData('user')->gen(1);
@@ -16,10 +18,10 @@ title=bugModel->getActivatedBugs();
timeout=0
cid=1
- 测试获取产品 1 2 3 类型 空 开始日期 上月 结束日期 下月 的bug @2,5,6,9
- 测试获取产品 1 2 3 类型 空 开始日期 上月 结束日期 下月 的bug @1,2,5,6,9
- 测试获取产品 1 2 3 类型 空 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 1 2 3 类型 resolved 开始日期 上月 结束日期 下月 的bug @5,6,9
- 测试获取产品 1 2 3 类型 resolved 开始日期 上月 结束日期 下月 的bug @1,5,6,9
- 测试获取产品 1 2 3 类型 resolved 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 1 2 3 类型 opened 开始日期 上月 结束日期 下月 的bug @2,5,6,9
@@ -62,9 +64,9 @@ $buildIdList = array('1,0,trunk', '1,trunk', '0,trunk');
$bug = new bugTest();
r($bug->getActivatedBugsTest($productIdList[0], $beginList[0], $endList[0], $buildIdList[0])) && p() && e('2,5,6,9'); // 测试获取产品 1 2 3 类型 空 开始日期 上月 结束日期 下月 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[0], $endList[0], $buildIdList[0])) && p() && e('1,2,5,6,9'); // 测试获取产品 1 2 3 类型 空 开始日期 上月 结束日期 下月 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[1], $endList[1], $buildIdList[0])) && p() && e('0'); // 测试获取产品 1 2 3 类型 空 开始日期 上周 结束日期 下周 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[0], $endList[0], $buildIdList[1])) && p() && e('5,6,9'); // 测试获取产品 1 2 3 类型 resolved 开始日期 上月 结束日期 下月 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[0], $endList[0], $buildIdList[1])) && p() && e('1,5,6,9'); // 测试获取产品 1 2 3 类型 resolved 开始日期 上月 结束日期 下月 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[1], $endList[1], $buildIdList[1])) && p() && e('0'); // 测试获取产品 1 2 3 类型 resolved 开始日期 上周 结束日期 下周 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[0], $endList[0], $buildIdList[2])) && p() && e('2,5,6,9'); // 测试获取产品 1 2 3 类型 opened 开始日期 上月 结束日期 下月 的bug
r($bug->getActivatedBugsTest($productIdList[0], $beginList[1], $endList[1], $buildIdList[2])) && p() && e('0'); // 测试获取产品 1 2 3 类型 opened 开始日期 上周 结束日期 下周 的bug
+12 -27
View File
@@ -15,42 +15,27 @@ title=bugModel->getActiveBugs();
timeout=0
cid=1
- 测试获取产品 1 2 3 类型 空 开始日期 上月 结束日期 下月 的bug @2,5,6,9
- 查询产品1 2 3 不存在的产品1000001下 且不排除bug的bug @BUG9,bug8,缺陷!()(){}|+=%^&*$#测试bug名称到底可以有多长!#¥%&*":.<>。?/();7,BUG6,BUG5,BUG4,BUG3,BUG2,BUG1
- 测试获取产品 1 2 3 类型 空 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 1 2 3 类型 resolved 开始日期 上月 结束日期 下月 的bug @5,6,9
- 查询产品1 2 3 不存在的产品1000001下 且排除bug2的bug @BUG9,bug8,缺陷!()(){}|+=%^&*$#测试bug名称到底可以有多长!#¥%&*":.<>。?/();7,BUG6,BUG5,BUG4,BUG3,BUG1
- 测试获取产品 1 2 3 类型 resolved 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 1 2 3 类型 opened 开始日期 上月 结束日期 下月 的bug @2,5,6,9
- 查询产品1 2 3 不存在的产品1000001下 且排除bug3 8的bug @BUG9,缺陷!()(){}|+=%^&*$#测试bug名称到底可以有多长!#¥%&*":.<>。?/();7,BUG6,BUG5,BUG4,BUG2,BUG1
- 测试获取产品 1 2 3 类型 opened 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 4 5 6 类型 空 开始日期 上月 结束日期 下月 的bug @10,13,14,17,18
- 查询产品1 3下 且不排除bug的bug @BUG9,bug8,缺陷!()(){}|+=%^&*$#测试bug名称到底可以有多长!#¥%&*":.<>。?/();7,BUG3,BUG2,BUG1
- 测试获取产品 4 5 6 类型 空 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 4 5 6 类型 resolved 开始日期 上月 结束日期 下月 的bug @10,13,14,17,18
- 查询产品1 3下 且排除bug2的bug @BUG9,bug8,缺陷!()(){}|+=%^&*$#测试bug名称到底可以有多长!#¥%&*":.<>。?/();7,BUG3,BUG1
- 测试获取产品 4 5 6 类型 resolved 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 4 5 6 类型 opened 开始日期 上月 结束日期 下月 的bug @10,13,14,17,18
- 查询产品1 3下 且排除bug3 8的bug @BUG9,缺陷!()(){}|+=%^&*$#测试bug名称到底可以有多长!#¥%&*":.<>。?/();7,BUG2,BUG1
- 测试获取产品 4 5 6 类型 opened 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 7 8 9 类型 空 开始日期 上月 结束日期 下月 的bug @21,22,25,26
- 查询产品1下 且不排除bug的bug @BUG3,BUG2,BUG1
- 测试获取产品 7 8 9 类型 空 开始日期 上周 结束日期 下周 的bug @25,26
- 查询产品1下 且排除bug2的bug @BUG3,BUG1
- 测试获取产品 7 8 9 类型 resolved 开始日期 上月 结束日期 下月 的bug @21,22,25,26
- 查询产品1下 且排除bug3 8的bug @BUG2,BUG1
- 测试获取产品 7 8 9 类型 resolved 开始日期 上周 结束日期 下周 的bug @25,26
- 测试获取产品 7 8 9 类型 opened 开始日期 上月 结束日期 下月 的bug @21,22,25,26
- 测试获取产品 7 8 9 类型 opened 开始日期 上周 结束日期 下周 的bug @25,26
- 测试获取产品 空 类型 空 开始日期 上月 结束日期 下月 的bug @0
- 测试获取产品 空 类型 空 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 空 类型 resolved 开始日期 上月 结束日期 下月 的bug @0
- 测试获取产品 空 类型 resolved 开始日期 上周 结束日期 下周 的bug @0
- 测试获取产品 空 类型 opened 开始日期 上月 结束日期 下月 的bug @0
- 测试获取产品 空 类型 opened 开始日期 上周 结束日期 下周 的bug @0
- 查询不存在的产品1000001下 且不排除bug的bug @0
- 查询不存在的产品1000001下 且排除bug2的bug @0
- 查询不存在的产品1000001下 且排除bug3 8的bug @0
*/
+1 -1
View File
@@ -1241,7 +1241,7 @@ class bugZen extends bug
if($bug->assignedTo && !isset($assignedToList[$bug->assignedTo]) && $bug->assignedTo != 'closed')
{
$assignedTo = $this->user->getById($bug->assignedTo);
$assignedToList[$bug->assignedTo] = $assignedTo->realname;
$assignedToList[$bug->assignedTo] = isset($assignedTo->realname) ? $assignedTo->realname : $bug->assignedTo;
}
if($bug->status == 'closed') $assignedToList['closed'] = 'Closed';
+6 -6
View File
@@ -74,7 +74,7 @@ class buildModel extends model
* @access public
* @return array
*/
public function getProjectBuilds(int $projectID = 0, string $type = 'all', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', object $pager = null): array
public function getProjectBuilds(int $projectID = 0, string $type = 'all', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', ?object $pager = null): array
{
$shadows = $this->dao->select('shadow')->from(TABLE_RELEASE)->where("FIND_IN_SET({$projectID}, project)")->fetchPairs('shadow', 'shadow');
$builds = $this->dao->select('t1.*, t2.name as productName')
@@ -129,7 +129,7 @@ class buildModel extends model
* @access public
* @return array
*/
public function getProjectBuildsBySearch(int $projectID, int $queryID, string $orderBy = 't1.date_desc,t1.id_desc', object $pager = null): array
public function getProjectBuildsBySearch(int $projectID, int $queryID, string $orderBy = 't1.date_desc,t1.id_desc', ?object $pager = null): array
{
/* If there are saved query conditions, reset the session. */
if((int)$queryID)
@@ -170,7 +170,7 @@ class buildModel extends model
* @access public
* @return array
*/
public function getExecutionBuilds(int $executionID, string $type = '', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', object $pager = null): array
public function getExecutionBuilds(int $executionID, string $type = '', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBuilds();
@@ -198,7 +198,7 @@ class buildModel extends model
* @access public
* @return object[]
*/
public function getExecutionBuildsBySearch(int $executionID, int $queryID, object $pager = null): array
public function getExecutionBuildsBySearch(int $executionID, int $queryID, ?object $pager = null): array
{
/* If there are saved query conditions, reset the session. */
if($queryID)
@@ -896,7 +896,7 @@ class buildModel extends model
* @access public
* @return array
*/
public function getBugList(string $bugIdList, string $orderBy = '', object $pager = null): array
public function getBugList(string $bugIdList, string $orderBy = '', ?object $pager = null): array
{
return $this->dao->select('*')->from(TABLE_BUG)
->where('id')->in($bugIdList)
@@ -916,7 +916,7 @@ class buildModel extends model
* @access public
* @return array
*/
public function getStoryList(string $storyIdList, int $branch = 0, string $orderBy = '', object $pager = null): array
public function getStoryList(string $storyIdList, int $branch = 0, string $orderBy = '', ?object $pager = null): array
{
$stories = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)
->where('id')->in($storyIdList)
+3 -3
View File
@@ -152,7 +152,7 @@ class caselibModel extends model
* @access public
* @return array
*/
public function getList(string $type = 'all', string $orderBy = 'id_desc', object $pager = null): array
public function getList(string $type = 'all', string $orderBy = 'id_desc', ?object $pager = null): array
{
return $this->dao->select('*')->from(TABLE_TESTSUITE)
->where('product')->eq(0)
@@ -174,7 +174,7 @@ class caselibModel extends model
* @access public
* @return array
*/
public function getPairs(string $type = 'all', string $orderBy = 'id_desc', object $pager = null): array
public function getPairs(string $type = 'all', string $orderBy = 'id_desc', ?object $pager = null): array
{
return $this->dao->select('id,name')->from(TABLE_TESTSUITE)
->where('product')->eq(0)
@@ -227,7 +227,7 @@ class caselibModel extends model
* @access public
* @return array
*/
public function getLibCases(int $libID, string $browseType, int $queryID = 0, int $moduleID = 0, string $sort = 'id_desc', object $pager = null, string $from = 'qa'): array
public function getLibCases(int $libID, string $browseType, int $queryID = 0, int $moduleID = 0, string $sort = 'id_desc', ?object $pager = null, string $from = 'qa'): array
{
$browseType = $browseType == 'bymodule' && $this->session->libBrowseType && $this->session->libBrowseType != 'bysearch' ? $this->session->libBrowseType : $browseType;
+23 -1
View File
@@ -47,7 +47,7 @@ class cneModel extends model
* @access public
* @return bool
*/
public function updateConfig(object $instance, object $settings = null): bool
public function updateConfig(object $instance, ?object $settings = null): bool
{
$apiParams = array();
$apiParams['cluster'] = '';
@@ -1136,4 +1136,26 @@ class cneModel extends model
$apiUrl = "/api/cne/system/update";
return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers);
}
/**
* 获取备份的详细信息。
* Get backup detail.
*
* @param object $instance
* @param string $backupName
* @access public
* @return object|false
*/
public function backupDetail(object $instance, string $backupName): object|false
{
if(empty($instance->k8name) || empty($instance->spaceData)) return false;
$apiUrl = "/api/cne/app/backup/detail";
$apiParams = array('name' => $instance->k8name, 'namespace' => $instance->spaceData->k8space, 'backup_name' => $backupName);
$result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers);
if(empty($result) || $result->code != 200 || empty($result->data)) return false;
return $result->data;
}
}
@@ -382,4 +382,27 @@ class cneTest
{
return $this->objectModel->tryAllocate($resources);
}
/**
* Test backupDetail method.
*
* @param object $instance
* @param int $count
* @access public
* @return object|bool|string
*/
public function backupDetailTest(object $instance, int $count): object|bool|string
{
$name = '';
if($count > 0)
{
$backupList = $this->objectModel->getBackupList($instance);
if(empty($backupList->data)) return false;
if(!empty($backupList->data[$count - 1]->name)) $name = $backupList->data[$count - 1]->name;
}
$result = $this->objectModel->backupDetail($instance, $name);
if(!$result) return $result;
if(!empty($result->message)) return $result->message;
return $result->backup_details;
}
}
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env php
<?php
/**
title=测试 cneModel->backupDetail();
timeout=0
cid=1
- 空的数据 @0
- 错误的空间 @0
- 备份的数据库
- 第0条的db_type属性 @mysql
- 第0条的status属性 @completed
- 备份的数据
- 第0条的volume属性 @data
- 第0条的status属性 @completed
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/cne.unittest.class.php';
$cneModel = new cneTest();
$instance = new stdClass();
r($cneModel->backupDetailTest($instance, 0)) && p() && e('0'); // 空的数据
$instance->spaceData = new stdClass();
$instance->spaceData->k8space = '';
r($cneModel->backupDetailTest($instance, 0)) && p() && e('0'); // 错误的空间
$instance->spaceData->k8space = 'quickon-system';
$instance->k8name = 'zentaopaas';
r($cneModel->backupDetailTest($instance, 1)->db) && p('0:db_type,status') && e('mysql,completed'); // 备份的数据库
r($cneModel->backupDetailTest($instance, 2)->volume) && p('0:volume,status') && e('data,completed'); // 备份的数据
+1 -1
View File
@@ -25,4 +25,4 @@ $certName = 'fail-name';
r($cneModel->certInfoTest($certName)) && p() && e('0'); // 证书名称错误
$certName = 'tls-haogs-cn';
r($cneModel->certInfoTest($certName)) && p('sans:0') && e('*.devops.corp.cc'); // 证书名称正确
r($cneModel->certInfoTest($certName)) && p('sans:0') && e('devops.corp.cc'); // 证书名称正确
+7 -7
View File
@@ -347,7 +347,7 @@ class commonModel extends model
*/
public function setApproval()
{
$this->config->openedApproval = (in_array($this->config->edition, array('biz', 'max', 'ipd'))) && ($this->config->vision == 'rnd');
$this->config->openedApproval = in_array($this->config->edition, array('biz', 'max', 'ipd')) && $this->config->vision != 'lite';
}
/**
@@ -1303,7 +1303,7 @@ eof;
* @access public
* @return bool
*/
public static function hasPriv(string $module, string $method, mixed $object = null, string $vars = '')
public static function hasPriv(string $module, string $method, $object = null, string $vars = '')
{
/* If the user is doing a tutorial, have all privileges. */
if(commonModel::isTutorialMode()) return true;
@@ -1347,7 +1347,7 @@ eof;
* @access public
* @return bool
*/
public static function getUserPriv(string $module, string $method, mixed $object = null, string $vars = ''): bool
public static function getUserPriv(string $module, string $method, $object = null, string $vars = ''): bool
{
global $app,$config;
$module = strtolower($module);
@@ -1846,7 +1846,7 @@ eof;
* @access public
* @return bool
*/
public static function canBeChanged(string $module, mixed $object = null): bool
public static function canBeChanged(string $module, $object = null): bool
{
if(defined('RUN_MODE') && RUN_MODE == 'api') return true;
@@ -1980,7 +1980,7 @@ eof;
* @access public
* @return string|array|bool
*/
public static function http(string $url, string|array|object|null $data = null, array $options = array(), array $headers = array(), string $dataType = 'data', string $method = 'POST', int $timeout = 30, bool $httpCode = false, bool $log = true): string|array|bool
public static function http(string $url, mixed $data = null, array $options = array(), array $headers = array(), string $dataType = 'data', string $method = 'POST', int $timeout = 30, bool $httpCode = false, bool $log = true): string|array|bool
{
global $lang, $app;
@@ -2469,7 +2469,7 @@ eof;
* @access public
* @return void
*/
public static function buildActionItem(string $module, string $method, string $params, object|null $object = null, array $attrs = array()): array
public static function buildActionItem(string $module, string $method, string $params, ?object $object = null, array $attrs = array()): array
{
if(!commonModel::hasPriv($module, $method, $object)) return array();
@@ -3819,7 +3819,7 @@ eof;
* @access public
* @return mixed
*/
public static function printCommentIcon(string $commentFormLink, object $object = null)
public static function printCommentIcon(string $commentFormLink, ?object $object = null)
{
global $lang;
+8 -2
View File
@@ -14,7 +14,8 @@ cid=1
- 查看企业版是否开启了审批流程 @1
- 查看旗舰版是否开启了审批流程 @1
- 查看IPD版本是否开启了审批流程 @1
- 查看OR界面下的IPD版本是否开启了审批流程 @0
- 查看OR界面下的IPD版本是否开启了审批流程 @1
- 查看运营界面下的IPD版本是否开启了审批流程 @0
*/
@@ -38,4 +39,9 @@ r($config->openedApproval) && p('') && e('1'); // 查看IPD版本是否开启了
$config->vision = 'or';
$config->edition = 'ipd';
$tester->loadModel('common')->setApproval();
r($config->openedApproval) && p('') && e('0'); // 查看OR界面下的IPD版本是否开启了审批流程
r($config->openedApproval) && p('') && e('1'); // 查看OR界面下的IPD版本是否开启了审批流程
$config->vision = 'lite';
$config->edition = 'ipd';
$tester->loadModel('common')->setApproval();
r($config->openedApproval) && p('') && e('0'); // 查看运营界面下的IPD版本是否开启了审批流程
+1 -1
View File
@@ -51,7 +51,7 @@ class companyModel extends model
* @access public
* @return array
*/
public function getUsers(string $browseType = 'inside', string $type = '', string|int $queryID = 0, int $deptID = 0, string $sort = '', object $pager = null): array
public function getUsers(string $browseType = 'inside', string $type = '', string|int $queryID = 0, int $deptID = 0, string $sort = '', ?object $pager = null): array
{
if($type == 'bydept')
{
+1 -1
View File
@@ -54,7 +54,7 @@ class compileModel extends model
* @access public
* @return array
*/
public function getList(int $repoID, int $jobID, string $browseType = '', int $queryID = 0, string $orderBy = 'id_desc', object $pager = null): array
public function getList(int $repoID, int $jobID, string $browseType = '', int $queryID = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
$compileQuery = '';
if($browseType == 'bySearch')
+1 -1
View File
@@ -10,7 +10,7 @@ su('admin');
/**
title=测试 customModel->getFormFields();
timeout=1
timeout=0
cid=1
- 测试moduleName值为product,method为空 @0
+6
View File
@@ -211,6 +211,12 @@ class datatable extends control
if(!$execution->hasProduct) unset($cols['branch']);
}
if($module == 'project' && $method == 'execution')
{
$project = $this->datatable->fetchByID($this->session->project, 'project');
if(!empty($project->isTpl)) unset($cols['deliverable']);
}
if($extra == 'unsetStory' && isset($cols['story'])) unset($cols['story']);
if($this->config->edition == 'ipd' && $module == 'product' && $method == 'browse' && $extra == 'story') unset($cols['roadmap']);
+11 -3
View File
@@ -40,9 +40,17 @@ cid=1
*/
global $lang;
$lang->SRCommon = '研发需求';
$lang->URCommon = '用户需求';
global $lang, $app, $config;
$lang->SRCommon = '研发需求';
$lang->URCommon = '用户需求';
$config->edition = 'open';
$app::$loadedLangs = array();
include($app->getModuleRoot() . '/story/control.php');
$app->control = new story();
$app->loadLang('custom');
$app->control->loadModel('task');
$app->control->loadModel('story');
$datatable = new datatableTest();
r($datatable->getSettingTest('product', 'browse')) && p('id:title;id:width;title:title;title:width') && e('ID,80,研发需求名称,0.44'); //获取产品模块browse方法自定义列
+1 -1
View File
@@ -34,7 +34,7 @@ function query(callback) {
$('.query').addClass('disabled');
$('#querying').removeClass('hidden');
var sql = $('#sql').val();
sql = btoa(sql);
sql = latin1ToBase64(sql);
$.post(createLink('dataview', 'ajaxQuery'), {sql: sql, driver: DataStorage.driver, filters: filters, recPerPage: DataStorage.recPerPage, pageID: DataStorage.pageID}, function(resp)
{
resp = JSON.parse(resp);
+1 -1
View File
@@ -335,7 +335,7 @@ class deptModel extends model
* @access public
* @return array
*/
public function getUsers(string $browseType = 'inside', array $depts = array(), string $orderBy = 'id', object $pager = null): array
public function getUsers(string $browseType = 'inside', array $depts = array(), string $orderBy = 'id', ?object $pager = null): array
{
return $this->dao->select('*')->from(TABLE_USER)
->where('deleted')->eq(0)
+6 -6
View File
@@ -91,7 +91,7 @@ class designModel extends model
* @access public
* @return bool|array
*/
public function update(int $designID = 0, object $design = null): bool|array
public function update(int $designID = 0, ?object $design = null): bool|array
{
$oldDesign = $this->getByID($designID);
if(!$oldDesign) return false;
@@ -135,7 +135,7 @@ class designModel extends model
* @access public
* @return array|bool
*/
public function assign(int $designID = 0, object $design = null): array|bool
public function assign(int $designID = 0, ?object $design = null): array|bool
{
$oldDesign = $this->getByID($designID);
if(!$oldDesign) return false;
@@ -302,7 +302,7 @@ class designModel extends model
* @access public
* @return object
*/
public function getAffectedScope(object $design = null): object
public function getAffectedScope(?object $design = null): object
{
if(!isset($design->id)) return $design;
@@ -329,7 +329,7 @@ class designModel extends model
* @access public
* @return object[]
*/
public function getList(int|array $projectID = 0, int|array $productID = 0, string $type = 'all', int $param = 0, string $orderBy = 'id_desc', object $pager = null): array
public function getList(int|array $projectID = 0, int|array $productID = 0, string $type = 'all', int $param = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDesigns();
@@ -370,7 +370,7 @@ class designModel extends model
* @access public
* @return object|bool
*/
public function getCommit(int $designID = 0, object $pager = null): object|bool
public function getCommit(int $designID = 0, ?object $pager = null): object|bool
{
$design = $this->dao->select('*')->from(TABLE_DESIGN)->where('id')->eq($designID)->fetch();
if(!$design) return false;
@@ -401,7 +401,7 @@ class designModel extends model
* @access public
* @return object[]
*/
public function getBySearch(int $projectID = 0, int $productID = 0, int $queryID = 0, string $orderBy = 'id_desc', object $pager = null): array
public function getBySearch(int $projectID = 0, int $productID = 0, int $queryID = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
if($queryID)
{
+16 -7
View File
@@ -368,6 +368,11 @@ class doc extends control
{
$this->app->loadLang('api');
/* 项目模板下setMenu会修改common语言项,所以执行文档要先执行setAclForCreateLib。 */
if($type != 'project') $this->docZen->setAclForCreateLib($type);
$this->doc->setMenuByType($type, (int)$objectID, (int)$libID);
if($type == 'project') $this->docZen->setAclForCreateLib($type);
if(!empty($_POST))
{
$lib = $this->docZen->buildLibForCreateLib();
@@ -386,10 +391,10 @@ class doc extends control
}
$objects = array();
if($type == 'project' && $this->app->tab == 'doc')
if($type == 'project')
{
$this->view->executionPairs = $this->execution->getPairs($objectID, 'all', 'multiple,leaf,noprefix');
$this->view->project = $this->project->getById($objectID);
$this->view->project = $this->project->getById($objectID);
if($this->app->tab == 'doc') $this->view->executionPairs = $this->execution->getPairs($objectID, 'all', 'multiple,leaf,noprefix');
}
if($type == 'execution')
@@ -397,6 +402,7 @@ class doc extends control
$objects = $this->execution->getPairs(0, 'all', 'multiple,leaf,noprefix,withobject');
$execution = $this->execution->getByID($objectID);
if($execution->type == 'stage') $this->lang->doc->execution = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doc->execution);
$this->view->execution = $this->doc->fetchByID($objectID, 'execution');
}
if($type == 'custom' || $type == 'mine' || $type == 'doctemplate')
@@ -406,8 +412,6 @@ class doc extends control
$this->view->spaceID = !empty($lib->parent) ? $lib->parent : $objectID;
}
$this->docZen->setAclForCreateLib($type);
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->user->getPairs('nocode|noclosed');
$this->view->objects = $objects;
@@ -456,6 +460,12 @@ class doc extends control
{
if(!common::hasPriv('doc', 'editSpace') && !common::hasPriv('doc', 'editLib')) return;
$lib = $this->doc->getLibByID($libID);
/* 项目模板下setMenu会修改common语言项,所以执行文档要先执行setAclForCreateLib。 */
if($lib->type != 'project') $this->docZen->setAclForEditLib($lib);
$this->doc->setMenuByType($lib->type, (int)zget($lib, $lib->type, 0), (int)$libID);
if($lib->type == 'project') $this->docZen->setAclForEditLib($lib);
if(!empty($_POST))
{
$this->lang->doc->name = $this->lang->nameAB;
@@ -499,8 +509,6 @@ class doc extends control
$this->view->object = $execution;
}
$this->docZen->setAclForEditLib($lib);
$this->view->lib = $lib;
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->user->getPairs('noletter|noclosed', $lib->users);
@@ -2208,6 +2216,7 @@ class doc extends control
$this->doc->setMenuByType($type, $spaceID, $libID);
$objectKey = $type . 'ID';
$this->view->$objectKey = $spaceID;
$this->view->$type = $this->doc->fetchByID($spaceID, $type);
}
if($type == 'mine') $menuType = 'my';
+12 -12
View File
@@ -373,7 +373,7 @@ class docModel extends model
* @access public
* @return bool|int
*/
public function createApiLib(object $formData = null): bool|int
public function createApiLib(?object $formData = null): bool|int
{
$this->app->loadLang('api');
@@ -411,7 +411,7 @@ class docModel extends model
* @access public
* @return array|false
*/
public function updateApiLib(int $id, object $formData = null): array|bool
public function updateApiLib(int $id, ?object $formData = null): array|bool
{
$oldLib = $this->getLibByID($id);
@@ -495,7 +495,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getDocsByBrowseType(string $browseType, int $queryID, int $moduleID, string $sort, object $pager = null)
public function getDocsByBrowseType(string $browseType, int $queryID, int $moduleID, string $sort, ?object $pager = null)
{
if($browseType == 'all') return $this->getDocs(0, 0, $browseType, $sort, $pager);
@@ -558,7 +558,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getMyDocListBySearch(int $queryID, array $hasPrivDocIdList, array $allLibIDList, string $sort, object $pager = null): array
public function getMyDocListBySearch(int $queryID, array $hasPrivDocIdList, array $allLibIDList, string $sort, ?object $pager = null): array
{
if($queryID)
{
@@ -664,7 +664,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getDocTemplateList(int $libID = 0, string $type = 'all', string $orderBy = 'id_desc', object $pager = null, string $searchName = ''): array
public function getDocTemplateList(int $libID = 0, string $type = 'all', string $orderBy = 'id_desc', ?object $pager = null, string $searchName = ''): array
{
return $this->dao->select('*')->from(TABLE_DOC)
->where('templateType')->ne('')
@@ -725,7 +725,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getDocs(int $libID, int $moduleID, string $browseType, string $orderBy, object $pager = null): array
public function getDocs(int $libID, int $moduleID, string $browseType, string $orderBy, ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getDocs();
@@ -915,7 +915,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getMineList(string $type, string $browseType, int $queryID = 0, string $orderBy = 'id_desc', object $pager = null): array
public function getMineList(string $type, string $browseType, int $queryID = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
$query = '';
if($browseType == 'bysearch')
@@ -978,7 +978,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getMySpaceDocs(string $type, string $browseType, string $query = '', string $orderBy = 'id_desc', object $pager = null, string $appendDocs = '', string $filterDocs = ''): array
public function getMySpaceDocs(string $type, string $browseType, string $query = '', string $orderBy = 'id_desc', ?object $pager = null, string $appendDocs = '', string $filterDocs = ''): array
{
if(!in_array($type, array('all', 'view', 'collect', 'createdby', 'editedby'))) return array();
@@ -2421,7 +2421,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getLibFiles(string $type, int $objectID, string $browseType = '', int $param = 0, string $orderBy = 'id_desc', object $pager = null): array
public function getLibFiles(string $type, int $objectID, string $browseType = '', int $param = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
if(!in_array($type, array('execution', 'project', 'product'))) return array();
@@ -3064,7 +3064,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getDocsBySearch(string $type, int $objectID, int $libID, int $queryID, string $orderBy = 'id_desc', object $pager = null): array
public function getDocsBySearch(string $type, int $objectID, int $libID, int $queryID, string $orderBy = 'id_desc', ?object $pager = null): array
{
$query = $this->buildQuery($type, $queryID);
$libs = $this->getLibsByObject($type, $objectID, $libID);
@@ -3311,7 +3311,7 @@ class docModel extends model
* @access public
* @return object
*/
public function buildLibItem(int $libID, object $lib, string $type, int $moduleID = 0, int $objectID = 0, string $browseType = '', int $docID = 0, object|null|bool $release = null): object
public function buildLibItem(int $libID, object $lib, string $type, int $moduleID = 0, int $objectID = 0, string $browseType = '', int $docID = 0, mixed $release = null): object
{
$releaseModule = array();
if($release && $release->lib == $lib->id)
@@ -3597,7 +3597,7 @@ class docModel extends model
* @access public
* @return array
*/
public function getDynamic(object $pager = null): array
public function getDynamic(?object $pager = null): array
{
$allLibs = $this->getLibs('hasApi');
$hasPrivDocIdList = $this->getPrivDocs(array(), 0, 'all');
+6 -6
View File
@@ -27,9 +27,9 @@ $recPerPages = array(5, 10, 20);
$pagerIds = array(1, 2);
$docTester = new docTest();
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[0])) && p() && e('31;30;29;28;27;'); // 获取每页5条,第1页动态数据
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[1])) && p() && e('26;25;24;23;22;'); // 获取每页5条,第2页动态数据
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[0])) && p() && e('31;30;29;28;27;26;25;24;23;22;'); // 获取每页10条,第1页动态数据
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[1])) && p() && e('21;20;19;50;18;49;17;48;16;47;'); // 获取每页10条,第2页动态数据
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[0])) && p() && e('31;30;29;28;27;26;25;24;23;22;21;20;19;50;18;49;17;48;16;47;'); // 获取每页20条,第1页动态数据
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[1])) && p() && e('15;46;14;45;13;44;12;43;11;42;10;41;9;40;8;39;7;38;6;37;'); // 获取每页20条,第2页动态数据
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[0])) && p() && e('27;28;29;30;31'); // 获取每页5条,第1页动态数据
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[1])) && p() && e('22;23;24;25;26'); // 获取每页5条,第2页动态数据
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[0])) && p() && e('22;23;24;25;26;27;28;29;30;31'); // 获取每页10条,第1页动态数据
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[1])) && p() && e('16;17;18;19;20;21;47;48;49;50'); // 获取每页10条,第2页动态数据
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[0])) && p() && e('16;17;18;19;20;21;22;23;24;25;26;27;28;29;30;31;47;48;49;50'); // 获取每页20条,第1页动态数据
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[1])) && p() && e('6;7;8;9;10;11;12;13;14;15;37;38;39;40;41;42;43;44;45;46'); // 获取每页20条,第2页动态数据
+3 -3
View File
@@ -13,7 +13,7 @@ cid=1
- 属性myDocs @50
- 获取登录用户为admin时,用户浏览跟收藏的文档数
- 第myDoc条的docViews属性 @0
- 第myDoc条的docCollects属性 @0
- 第myDoc条的docCollects属性 @100
- 获取登录用户为user1时,文档总数、今日编辑文档数、用户编辑过的文档数、用户创建的文档数
- 属性totalDocs @50
- 属性todayEditedDocs @0
@@ -38,8 +38,8 @@ $user1Info = $docTester->getStatisticInfoTest('user1');
/* Admin statistic information. */
r($adminInfo) && p('totalDocs,todayEditedDocs,myEditedDocs,myDocs') && e('50,0,10,50'); // 获取登录用户为admin时,文档总数、今日编辑文档数、用户编辑过的文档数、用户创建的文档数
r($adminInfo) && p('myDoc:docViews,docCollects') && e('0,0'); // 获取登录用户为admin时,用户浏览跟收藏的文档数
r($adminInfo) && p('myDoc:docViews,docCollects') && e('0,100'); // 获取登录用户为admin时,用户浏览跟收藏的文档数
/* User1 statistic information.*/
r($user1Info) && p('totalDocs,todayEditedDocs,myEditedDocs,myDocs') && e('50,0,10,0'); // 获取登录用户为user1时,文档总数、今日编辑文档数、用户编辑过的文档数、用户创建的文档数
r($user1Info) && p('myDoc:docViews,docCollects') && e('~~,~~'); // 获取登录用户为user1时,用户浏览跟收藏的文档数
r($user1Info) && p('myDoc:docViews,docCollects') && e('~~,~~'); // 获取登录用户为user1时,用户浏览跟收藏的文档数
+10 -9
View File
@@ -25,26 +25,27 @@ if($type === 'project')
* 定义文档界面上的权限。
* Define the privs of doc app.
*/
$hasCustomSpace = $type == 'mine' || $type == 'custom';
$hasCustomSpace = $type == 'mine' || $type == 'custom';
$projectTemplate = ($type === 'project' && !empty($project->isTpl)) || ($type == 'execution' && !empty($execution->isTpl));
$privs = array();
$privs['create'] = hasPriv('doc', 'create');
$privs['edit'] = hasPriv('doc', 'edit');
$privs['delete'] = hasPriv('doc', 'delete');
$privs['create'] = hasPriv('doc', 'create') && !$projectTemplate;
$privs['edit'] = hasPriv('doc', 'edit') && !$projectTemplate;
$privs['delete'] = hasPriv('doc', 'delete') && !$projectTemplate;
$privs['view'] = hasPriv('doc', 'view');
$privs['effort'] = $this->config->edition != 'open' && hasPriv('effort', 'createForObject');
$privs['exportDoc'] = $this->config->edition != 'open' && hasPriv('doc', $type . '2export');
$privs['exportDoc'] = $this->config->edition != 'open' && hasPriv('doc', $type . '2export') && !$projectTemplate;
$privs['exportApi'] = $this->config->edition != 'open' && hasPriv('api', 'export');
$privs['moveDoc'] = hasPriv('doc', 'moveDoc');
$privs['moveDoc'] = hasPriv('doc', 'moveDoc') && !$projectTemplate;
$privs['collect'] = hasPriv('doc', 'collect');
$privs['createLib'] = hasPriv('doc', 'createLib');
$privs['editLib'] = hasPriv('doc', 'editLib');
$privs['moveLib'] = hasPriv('doc', 'moveLib');
$privs['moveLib'] = hasPriv('doc', 'moveLib') && !$projectTemplate;
$privs['deleteLib'] = hasPriv('doc', 'deleteLib');
$privs['sortDocLib'] = hasPriv('doc', 'sortDocLib');
$privs['uploadFile'] = hasPriv('doc', 'create');
$privs['uploadFile'] = hasPriv('doc', 'create') && !$projectTemplate;
$privs['editFile'] = hasPriv('file', 'edit');
$privs['deleteFile'] = hasPriv('doc', 'deleteFile');
$privs['exportFiles'] = hasPriv('doc', 'exportFiles');
$privs['exportFiles'] = hasPriv('doc', 'exportFiles') && !$projectTemplate;
$privs['createSpace'] = $hasCustomSpace && hasPriv('doc', 'createSpace');
$privs['deleteSpace'] = $hasCustomSpace && hasPriv('doc', 'deleteSpace');
$privs['editSpace'] = $hasCustomSpace && hasPriv('doc', 'editSpace');
+1
View File
@@ -26,6 +26,7 @@ $renderObjectBox = function(string $type) use ($lang, $objects, $objectID)
return formRow(
setClass('objectBox'),
formGroup(
set::id('object'),
set::width('5/6'),
set::label($lang->doc->{$type}),
set::name($type),
+2 -2
View File
@@ -60,7 +60,7 @@ class entryModel extends model
* @access public
* @return array
*/
public function getList(string $orderBy = 'id_desc', object $pager = null): array
public function getList(string $orderBy = 'id_desc', ?object $pager = null): array
{
if(strpos($orderBy, 'desc_') !== false) $orderBy = str_replace('desc_', '`desc`_', $orderBy);
return $this->dao->select('*')->from(TABLE_ENTRY)->where('deleted')->eq('0')->orderBy($orderBy)->page($pager)->fetchAll('id');
@@ -76,7 +76,7 @@ class entryModel extends model
* @access public
* @return array
*/
public function getLogs(int $id, string $orderBy = 'date_desc', object $pager = null): array
public function getLogs(int $id, string $orderBy = 'date_desc', ?object $pager = null): array
{
return $this->dao->select('*')->from(TABLE_LOG)
->where('objectType')->eq('entry')
+1 -1
View File
@@ -2738,7 +2738,7 @@ class execution extends control
$executionGroups = $this->dao->select('*')->from(TABLE_EXECUTION) /* 按照项目分组,获取有权限访问的执行列表。*/
->where('deleted')->eq('0')
->andWhere('multiple')->eq('1')
->andWhere('type')->in('sprint,stage,kanban')
->andWhere('type')->in(!empty($execution->isTpl) ? 'sprint,stage' : 'sprint,stage,kanban')
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
->beginIF(!empty($execution->isTpl))->andWhere('isTpl')->eq('1')->fi()
->andWhere('project')->in(array_keys($projects))
+4
View File
@@ -114,6 +114,10 @@ window.onRenderCell = function(result, {row, col})
{
result.push({outer: false, style: {alignItems: 'center', justifyContent: 'start'}})
}
if(col.name == 'status' && result)
{
result[0] = {html: `<span class='status-${row.data.rawStatus}'>` + row.data.status + "</span>"};
}
return result;
}
+31 -13
View File
@@ -153,7 +153,6 @@ class executionModel extends model
unset($this->lang->execution->menu->burn);
unset($this->lang->execution->menu->kanban);
unset($this->lang->execution->menu->view);
unset($this->lang->execution->menu->story);
unset($this->lang->execution->menu->qa);
unset($this->lang->execution->menu->devops);
@@ -162,6 +161,22 @@ class executionModel extends model
unset($this->lang->execution->menu->effort);
unset($this->lang->execution->menu->more);
if(!empty($this->lang->execution->menu->view['subMenu']->gantt))
{
$this->lang->execution->menu->gantt = $this->lang->execution->menu->view['subMenu']->gantt;
$taskOrder = 0;
foreach($this->lang->execution->menuOrder as $order => $menu) if($menu == 'task') $taskOrder = $order;
$this->lang->execution->menuOrder[$taskOrder + 1] = 'gantt';
if(!empty($this->lang->project->menuOrder))
{
$taskOrder = 0;
foreach($this->lang->project->menuOrder as $order => $menu) if($menu == 'task') $taskOrder = $order;
$this->lang->project->menuOrder[$taskOrder + 1] = 'gantt';
}
}
if(!empty($this->lang->execution->menu->other['dropMenu']->pssp))
{
$this->lang->execution->menu->pssp = $this->lang->execution->menu->other['dropMenu']->pssp;
@@ -181,6 +196,7 @@ class executionModel extends model
}
}
unset($this->lang->execution->menu->view);
unset($this->lang->execution->menu->other);
if(isset($this->lang->execution->menu->settings['subMenu']->products)) unset($this->lang->execution->menu->settings['subMenu']->products); // 模板下隐藏产品
if(isset($this->lang->execution->menu->settings['subMenu']->whitelist)) unset($this->lang->execution->menu->settings['subMenu']->whitelist); // 模板下隐藏白名单
@@ -1035,7 +1051,7 @@ class executionModel extends model
* @access public
* @return bool
*/
public function checkWorkload(string $type = '', float $percent = 0, object $oldExecution = null): bool
public function checkWorkload(string $type = '', float $percent = 0, ?object $oldExecution = null): bool
{
/* Check whether the workload is positive. */
if(!preg_match("/^[0-9]+(.[0-9]{1,3})?$/", (string)$percent))
@@ -1214,7 +1230,7 @@ class executionModel extends model
* @access public
* @return array
*/
public function getList(int $projectID = 0, string $type = 'all',string $status = 'all', int $limit = 0, int $productID = 0, int $branch = 0, object|null $pager = null, bool $withChildren = true)
public function getList(int $projectID = 0, string $type = 'all',string $status = 'all', int $limit = 0, int $productID = 0, int $branch = 0, ?object $pager = null, bool $withChildren = true)
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getExecutionStats($type);
@@ -1230,8 +1246,9 @@ class executionModel extends model
->beginIF($type == 'all')->andWhere('t2.type')->in('sprint,stage,kanban')->fi()
->beginIF($type != 'all')->andWhere('t2.type')->eq($type)->fi()
->beginIF($status == 'undone')->andWhere('t2.status')->notIN('done,closed')->fi()
->beginIF($status == 'delayed')->andWhere('t2.end')->gt('1970-1-1')->andWhere('t2.end')->lt(date(DT_DATE1))->andWhere('t2.status')->notin('done,closed,suspended')->fi()
->beginIF($branch)->andWhere('t1.branch')->eq($branch)->fi()
->beginIF($status != 'all' and $status != 'undone')->andWhere('status')->in($status)->fi()
->beginIF($status != 'all' && $status != 'undone' && $status != 'delayed')->andWhere('t2.status')->in($status)->fi()
->beginIF(!$this->app->user->admin and isset($this->app->user->view))->andWhere('t2.id')->in($this->app->user->view->sprints)->fi()
->beginIF(!$withChildren)->andWhere('grade')->eq(1)->fi()
->orderBy('order_desc')
@@ -1247,7 +1264,8 @@ class executionModel extends model
->beginIF($type == 'all')->andWhere('type')->in('sprint,stage,kanban')->fi()
->beginIF($type != 'all')->andWhere('type')->eq($type)->fi()
->beginIF($status == 'undone')->andWhere('status')->notIN('done,closed')->fi()
->beginIF($status != 'all' and $status != 'undone')->andWhere('status')->in($status)->fi()
->beginIF($status == 'delayed')->andWhere('end')->gt('1970-1-1')->andWhere('end')->lt(date(DT_DATE1))->andWhere('status')->notin('done,closed,suspended')->fi()
->beginIF($status != 'all' && $status != 'undone' && $status != 'delayed')->andWhere('status')->in($status)->fi()
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->beginIF(!$this->app->user->admin and isset($this->app->user->view))->andWhere('id')->in($this->app->user->view->sprints)->fi()
->beginIF(!$withChildren)->andWhere('grade')->eq(1)->fi()
@@ -1384,7 +1402,7 @@ class executionModel extends model
* @access public
* @return array
*/
public function getStatData(int $projectID = 0, string $browseType = 'undone', int $productID = 0, int $branch = 0, bool $withTasks = false, string|int $param = '', string $orderBy = 'id_asc', object|null $pager = null): array
public function getStatData(int $projectID = 0, string $browseType = 'undone', int $productID = 0, int $branch = 0, bool $withTasks = false, string|int $param = '', string $orderBy = 'id_asc', ?object $pager = null): array
{
if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getExecutionStats($browseType);
@@ -1485,7 +1503,7 @@ class executionModel extends model
* @access public
* @return array
*/
public function fetchExecutionList(int $projectID = 0, string $browseType = 'undone', int $productID = 0, int $param = 0, string $orderBy = 'id_asc', object|null $pager = null): array
public function fetchExecutionList(int $projectID = 0, string $browseType = 'undone', int $productID = 0, int $param = 0, string $orderBy = 'id_asc', ?object $pager = null): array
{
/* Construct the query SQL at search executions. */
$executionQuery = $browseType == 'bySearch' ? $this->getExecutionQuery($param) : '';
@@ -1957,7 +1975,7 @@ class executionModel extends model
* @access public
* @return array
*/
public function getTasks(int $productID, int|array $executionID, array $executions, string $browseType, int $queryID, int $moduleID, string $sort, object $pager = null): array
public function getTasks(int $productID, int|array $executionID, array $executions, string $browseType, int $queryID, int $moduleID, string $sort, ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTasks();
@@ -2072,7 +2090,7 @@ class executionModel extends model
/* Story changed or not. */
$task->storyVersion = zget($storyVersionPairs, $task->id, $task->storyVersion);
$task->needConfirm = false;
if(!empty($task->storyStatus) && $task->storyStatus == 'active' && $task->latestStoryVersion > $task->storyVersion)
if(!empty($task->storyStatus) && $task->storyStatus == 'active' && !in_array($task->status, array('cancel', 'closed')) && $task->latestStoryVersion > $task->storyVersion)
{
$task->needConfirm = true;
$task->status = 'changed';
@@ -2263,7 +2281,7 @@ class executionModel extends model
* @access public
* @return void
*/
public function buildStorySearchForm(array $products, array $branchGroups, array $modules, int $queryID, string $actionURL, string $type = 'executionStory', object $execution = null): void
public function buildStorySearchForm(array $products, array $branchGroups, array $modules, int $queryID, string $actionURL, string $type = 'executionStory', ?object $execution = null): void
{
$this->loadModel('productplan');
$this->app->loadLang('branch');
@@ -3747,7 +3765,7 @@ class executionModel extends model
* @access public
* @return array
*/
public function getSearchTasks(string $condition, string $orderBy, object $pager = null, string $queryKey = 'task'): array
public function getSearchTasks(string $condition, string $orderBy, ?object $pager = null, string $queryKey = 'task'): array
{
/* 按指派人搜索的时候,可以搜索到参与的多人任务。 */
if(strpos($condition, '`assignedTo`') !== false)
@@ -4193,7 +4211,7 @@ class executionModel extends model
* @access public
* @return array
*/
public function getKanbanTasks(int $executionID, string $orderBy = 'status_asc, id_desc', array $excludeTasks = array(), object|null $pager = null): array
public function getKanbanTasks(int $executionID, string $orderBy = 'status_asc, id_desc', array $excludeTasks = array(), ?object $pager = null): array
{
$excludeTasks = array_filter($excludeTasks);
$tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName')
@@ -4997,7 +5015,7 @@ class executionModel extends model
}
$task->needConfirm = false;
if(!empty($task->storyStatus) && $task->storyStatus == 'active' && $task->latestStoryVersion > $task->storyVersion)
if(!empty($task->storyStatus) && $task->storyStatus == 'active' && !in_array($task->status, array('closed', 'cancel')) && $task->latestStoryVersion > $task->storyVersion)
{
$task->needConfirm = true;
$task->rawStatus = $task->status;
+1 -1
View File
@@ -421,7 +421,7 @@ class executionTao extends executionModel
* @access public
* @return object[]
*/
public function getSearchBugs(array $productIdList, int $executionID, string $sql = '1=1', string $orderBy = 'id_desc', object|null $pager = null): array
public function getSearchBugs(array $productIdList, int $executionID, string $sql = '1=1', string $orderBy = 'id_desc', ?object $pager = null): array
{
return $this->dao->select('*')->from(TABLE_BUG)
->where($sql)
@@ -0,0 +1,96 @@
<?php
include dirname(__FILE__, 5) . '/test/lib/ui.php';
class activateExecutionTester extends tester
{
/**
* 输入激活看板表单字段。
* Input fields.
*
* @param string $end
* @param string $executionId
* @access public
*/
public function inputFields($end, $executionId)
{
$form = $this->initForm('execution', 'kanban', array('execution' => $executionId), 'appIframe-project');
$form->wait(1);
$form->dom->kanbanSettingInLite->click();
$form->wait(1);
$this->loadPage();
$form->wait(1);
$form->dom->btn($this->lang->execution->activate)->click();
$form->wait(1);
$this->loadPage();
$form->wait(1);
if(isset($end)) $form->dom->end->datePicker($end);
$form->dom->activateSubmitInLite->click();
$form->wait(1);
}
/**
* 激活看板。
* Activate execution.
*
* @param string $end
* @param string $executionId
* @access public
* @return bool
*/
public function activate($end, $executionId)
{
$this->inputFields($end, $executionId);
$form = $this->loadPage();
$form->wait(1);
if(is_object($form->dom->activateSubmitInLite)) return $this->failed('激活看板失败');
$viewForm = $this->initForm('execution', 'view', array('execution' => $executionId), 'appIframe-project');
$viewForm->wait(1);
if($viewForm->dom->plannedEnd->getText() != $end) return $this->failed('计划完成时间错误');
return $this->success('激活看板成功');
}
/**
* 激活看板时计划完成日期小于计划开始日期。
* Activate execution with end less than begin.
*
* @param string $end
* @param string $executionId
* @access public
* @return bool
*/
public function activateWithLessEnd($end, $executionId)
{
$this->switchVision('lite', 5);
$this->inputFields($end, $executionId);
$form = $this->loadPage();
$form->wait(1);
$info = sprintf($this->lang->execution->errorLesserPlan, $end, date('Y-m-d'));
if($form->dom->endTip->getText() == $info) return $this->success('激活看板表单页提示信息正确');
return $this->failed('激活看板表单页提示信息不正确');
}
/**
* 激活看板时计划完成日期大于项目计划完成日期。
* Activate execution with end greater than begin.
*
* @param string $end
* @param string $executionId
* @access public
* @return bool
*/
public function activateWithGreaterEnd($end, $executionId)
{
$this->inputFields($end, $executionId);
$form = $this->loadPage();
$form->wait(1);
$info = sprintf($this->lang->execution->errorGreaterParent, '');
$text = $form->dom->endTip->getText();
/* 获取页面返回信息中除日期外的内容 */
preg_match_all('/(\d{4}-\d{2}-\d{2})/', $text, $matches); ~
$date = $matches[0][0]; ~
$params = str_replace($date, '', $text); ~
$params = trim($params);
if($params == $info) return $this->success('激活看板表单页提示信息正确');
return $this->failed('激活看板表单页提示信息不正确');
}
}

Some files were not shown because too many files have changed in this diff Show More