Merge branch release/22.0.alpha1 of zentao/zentaopms (#13051)

This commit is contained in:
刘刚
2025-12-29 16:05:39 +08:00
committed by Gitfox
269 changed files with 2653 additions and 1694 deletions
+18
View File
@@ -0,0 +1,18 @@
:80 {
root www/
file_server
php_server {
env PATH_INFO {http.matchers.file.remainder}
env SCRIPT_NAME {path}
try_files {path} index.php{path}
worker {
file www/index.php
}
}
log {
output file www/tmp/caddy.log
level INFO
}
}
+31 -17
View File
@@ -20,33 +20,47 @@ class tokensEntry extends baseEntry
public function post()
{
$account = $this->request('account');
$password = $this->request('password');
$password = $this->request('password', '');
$authKey = $this->request('authKey', '');
$addAction = $this->request('addAction', false);
if($this->loadModel('user')->checkLocked($account)) return $this->sendError(400, sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes));
if($password)
{
if($this->loadModel('user')->checkLocked($account)) return $this->sendError(400, sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes));
$user = $this->user->identify($account, $password);
}
else
{
$user = $this->loadModel('im')->userIdentifyWithToken($account, $authKey);
if(is_object($user))
{
$user->admin = strpos($this->app->company->admins, ",{$user->account},") !== false;
}
else
{
$user = null;
}
}
$user = $this->user->identify($account, $password);
if($user)
{
$this->user->login($user, $addAction);
return $this->send(201, array('token' => session_id()));
}
else
{
$fails = $this->user->failPlus($account);
$remainTimes = $this->config->user->failTimes - $fails;
if($remainTimes <= 0)
{
return $this->sendError(400, sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes));
}
else if($remainTimes <= 3)
{
return $this->sendError(400, sprintf($this->lang->user->lockWarning, $remainTimes));
}
return $this->sendError(400, $this->lang->user->loginFailed);
$fails = $this->user->failPlus($account);
$remainTimes = $this->config->user->failTimes - $fails;
if($remainTimes <= 0)
{
return $this->sendError(400, sprintf($this->lang->user->loginLocked, $this->config->user->lockMinutes));
}
$this->sendError(400, $this->app->lang->user->loginFailed);
if($remainTimes <= 3)
{
return $this->sendError(400, sprintf($this->lang->user->lockWarning, $remainTimes));
}
return $this->sendError(400, $this->lang->user->loginFailed);
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"pkg": {
"xuanxuan": {
"gitVersion": "dfd57fb979d1943926940e595f7a7259d5fe3517",
"version": "9.5"
"gitVersion": "927c59b18be6964df50606a524d703c256ccd22b",
"version": "9.6"
},
"zentaoext": {
"gitRepo": "zentao/zentaoext",
+10 -4
View File
@@ -16,8 +16,14 @@ $routes['/projects/:projectID/stories'] = array('redirect' => '/projectstori
$routes['/executions/:executionID/stories'] = array('redirect' => '/executions/story?executionID=:executionID');
$routes['/stories/:storyID'] = array('response' => 'story,actions(array)');
$routes['/products/:productID/epics'] = array('redirect' => '/products/browse?productID=:productID&storyType=epic', 'response' => 'stories(array)|epics,pager');
$routes['/epics/:storyID'] = array('response' => 'story|epic,actions(array)');
$routes['/products/:productID/requirements'] = array('redirect' => '/products/browse?productID=:productID&storyType=requirement', 'response' => 'stories(array)|requirements,pager');
$routes['/requirements/:storyID'] = array('response' => 'story|requirement,actions(array)');
$routes['/products/:productID/productplans'] = array('redirect' => '/productplans?productID=:productID', 'response' => 'plans(array)|productplans,pager');
$routes['/productplans/:planID'] = array('response' => 'plan|productplan,actions(array)');
$routes['/productplans/:planID'] = array('response' => 'plan|productplan,actions(array)');
$routes['/products/:productID/releases'] = array('redirect' => '/releases?productID=:productID', 'response' => 'releases,pager');
$routes['/projects/:projectID/releases'] = array('redirect' => '/projectreleases?projectID=:projectID', 'response' => 'releases,pager');
@@ -95,11 +101,11 @@ $routes['/projects/:projectID/auditplans'] = array('redirect' => '/auditpla
$routes['/executions/:executionID/auditplans'] = array('redirect' => '/auditplans?executionID=:executionID&from=execution');
$routes['/feedbacks'] = array('response' => 'feedbacks(array),pager');
$routes['/products/:productID/feedbacks'] = array('redirect' => '/feedbacks?productID=:productID');
$routes['/products/:productID/feedbacks'] = array('redirect' => '/feedbacks?param=:productID');
$routes['/feedbacks/:feedbackID'] = array('response' => 'feedback,actions(array)');
$routes['/tickets'] = array('response' => 'tickets(array),pager');
$routes['/products/:productID/tickets'] = array('redirect' => '/tickets?browseType=byProduct&param=:productID');
$routes['/products/:productID/tickets'] = array('redirect' => '/tickets?param=:productID');
$routes['/tickets/:ticketID'] = array('response' => 'ticket,actions(array)');
$routes['/depts'] = array('response' => 'sons|depts');
@@ -107,4 +113,4 @@ $routes['/depts/browse'] = array();
$routes['/depts/:deptID'] = array('redirect' => '/depts/browse?deptID=:deptID', 'response' => 'sons');
$routes['/users'] = array('redirect' => '/companies/browse', 'response' => 'users,pager');
$routes['/users/:userID'] = array('redirect' => '/users/profile', 'response' => 'user');
$routes['/users/:userID'] = array('redirect' => '/users/:userID/profile', 'response' => 'user');
+1 -1
View File
@@ -756,7 +756,7 @@ $config->closedFeatures = '';
$config->pipelineTypeList = array('gitlab', 'gogs', 'gitea', 'jenkins', 'sonarqube');
$config->mysqlDriverList = array('mysql', 'oceanbase');
$config->pgsqlDriverList = array('postgres', 'highgo');
$config->pgsqlDriverList = array('postgres', 'highgo', 'kingbase');
/* Program privs.*/
$config->programPriv = new stdclass();
+22
View File
@@ -45,3 +45,25 @@ BEGIN
-- 返回res
RETURN res;
END FIND_IN_SET;
/
CREATE OR REPLACE FUNCTION "IF"(
p_condition BOOLEAN, -- 判断条件
p_true_val ANYTYPE, -- true分支返回值(任意类型)
p_false_val ANYTYPE -- false分支返回值(任意类型)
) RETURNS ANYTYPE
AS
BEGIN
-- 仅校验条件非空,不校验类型
IF p_condition IS NULL THEN
RAISE_APPLICATION_ERROR(-20001, '判断条件不能为NULL');
END IF;
-- 核心逻辑:直接返回不同类型值
IF p_condition THEN
RETURN p_true_val; -- 如:字符串
ELSE
RETURN p_false_val; -- 如:数值
END IF;
END;
/
+54 -6
View File
@@ -256,7 +256,7 @@ END;
$$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION IF(
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val BOOLEAN,
false_val BOOLEAN
@@ -272,7 +272,7 @@ $$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION IF(
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val DOUBLE PRECISION,
false_val INTEGER
@@ -288,7 +288,55 @@ $$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION IF(
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val DOUBLE PRECISION,
false_val DOUBLE PRECISION
) RETURNS DOUBLE PRECISION AS $$
BEGIN
IF condition THEN
RETURN true_val;
ELSE
RETURN false_val;
END IF;
END;
$$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val TEXT,
false_val TEXT
) RETURNS TEXT AS $$
BEGIN
IF condition THEN
RETURN true_val;
ELSE
RETURN false_val;
END IF;
END;
$$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val INTEGER,
false_val INTEGER
) RETURNS INTEGER AS $$
BEGIN
IF condition THEN
RETURN true_val;
ELSE
RETURN false_val;
END IF;
END;
$$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val TEXT,
false_val DATE
@@ -304,7 +352,7 @@ $$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION IF(
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val DATE,
false_val DATE
@@ -320,7 +368,7 @@ $$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION IF(
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val timestamp without time zone,
false_val DATE
@@ -336,7 +384,7 @@ $$ LANGUAGE plpgsql;
--
CREATE OR REPLACE FUNCTION IF(
CREATE OR REPLACE FUNCTION "IF"(
condition BOOLEAN,
true_val timestamp without time zone,
false_val timestamp without time zone
+1 -3
View File
@@ -1209,8 +1209,6 @@ CREATE TABLE `zt_dept` (
`path` varchar(255) NOT NULL DEFAULT '',
`grade` tinyint unsigned NOT NULL DEFAULT 0,
`order` int unsigned NOT NULL DEFAULT 0,
`position` varchar(30) NOT NULL DEFAULT '',
`function` varchar(255) NOT NULL DEFAULT '',
`manager` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `parent` (`parent`),
@@ -4008,7 +4006,7 @@ CREATE TABLE `zt_user` (
`visits` int unsigned NOT NULL DEFAULT 0,
`visions` varchar(20) NOT NULL DEFAULT 'rnd,lite',
`ip` varchar(255) NOT NULL DEFAULT '',
`last` int unsigned NOT NULL DEFAULT 0,
`last` datetime DEFAULT NULL,
`fails` tinyint unsigned NOT NULL DEFAULT 0,
`locked` datetime DEFAULT NULL,
`feedback` tinyint unsigned NOT NULL DEFAULT 0,
+3
View File
@@ -134,3 +134,6 @@ ALTER TABLE `zt_taskspec` ADD COLUMN `id` int unsigned NOT NULL AUTO_INCREMENT P
ALTER TABLE `zt_trainrecords` ADD COLUMN `id` int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY;
ALTER TABLE `zt_usergroup` ADD COLUMN `id` int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY;
ALTER TABLE `zt_workflowlinkdata` ADD COLUMN `id` int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY;
ALTER TABLE `zt_dept` DROP COLUMN `position`;
ALTER TABLE `zt_dept` DROP COLUMN `function`;
-16
View File
@@ -527,8 +527,6 @@ ALTER TABLE `zt_dept`
MODIFY COLUMN `parent` int unsigned NOT NULL DEFAULT 0,
MODIFY COLUMN `path` varchar(255) NOT NULL DEFAULT '',
MODIFY COLUMN `order` int unsigned NOT NULL DEFAULT 0,
MODIFY COLUMN `position` varchar(30) NOT NULL DEFAULT '',
MODIFY COLUMN `function` varchar(255) NOT NULL DEFAULT '',
MODIFY COLUMN `manager` varchar(30) NOT NULL DEFAULT '';
ALTER TABLE `zt_design`
MODIFY COLUMN `id` int unsigned NOT NULL AUTO_INCREMENT,
@@ -974,20 +972,6 @@ ALTER TABLE `zt_metric`
ALTER TABLE `zt_metric`
MODIFY COLUMN `builtin` tinyint unsigned NOT NULL DEFAULT 0,
MODIFY COLUMN `deleted` tinyint unsigned NOT NULL DEFAULT 0;
ALTER TABLE `zt_metriclib`
MODIFY COLUMN `code` varchar(30) NOT NULL DEFAULT '',
MODIFY COLUMN `pipeline` varchar(30) NOT NULL DEFAULT '',
MODIFY COLUMN `repo` varchar(30) NOT NULL DEFAULT '',
MODIFY COLUMN `dept` varchar(30) NOT NULL DEFAULT '',
MODIFY COLUMN `year` char(4) NOT NULL DEFAULT '',
MODIFY COLUMN `month` char(2) NOT NULL DEFAULT '',
MODIFY COLUMN `week` char(2) NOT NULL DEFAULT '',
MODIFY COLUMN `day` char(2) NOT NULL DEFAULT '',
MODIFY COLUMN `value` varchar(100) NOT NULL DEFAULT '',
MODIFY COLUMN `calcType` varchar(10) NOT NULL DEFAULT 'cron',
MODIFY COLUMN `deleted` char(1) NOT NULL DEFAULT '0';
ALTER TABLE `zt_metriclib`
MODIFY COLUMN `deleted` tinyint unsigned NOT NULL DEFAULT 0;
ALTER TABLE `zt_module`
MODIFY COLUMN `id` int unsigned NOT NULL AUTO_INCREMENT,
MODIFY COLUMN `root` int unsigned NOT NULL DEFAULT 0,
+1 -3
View File
@@ -776,8 +776,6 @@ CREATE TABLE IF NOT EXISTS `zt_dept` (
`path` varchar(255) NOT NULL DEFAULT '',
`grade` tinyint unsigned NOT NULL DEFAULT 0,
`order` int unsigned NOT NULL DEFAULT 0,
`position` varchar(30) NOT NULL DEFAULT '',
`function` varchar(255) NOT NULL DEFAULT '',
`manager` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
@@ -2471,7 +2469,7 @@ CREATE TABLE IF NOT EXISTS `zt_user` (
`visits` int unsigned NOT NULL DEFAULT 0,
`visions` varchar(20) NOT NULL DEFAULT 'rnd,lite',
`ip` varchar(255) NOT NULL DEFAULT '',
`last` int unsigned NOT NULL DEFAULT 0,
`last` datetime DEFAULT NULL,
`fails` tinyint unsigned NOT NULL DEFAULT 0,
`locked` datetime DEFAULT NULL,
`feedback` tinyint unsigned NOT NULL DEFAULT 0,
@@ -870,41 +870,6 @@ $lang->webhook->methodOrder[25] = 'log';
$lang->webhook->methodOrder[30] = 'bind';
$lang->webhook->methodOrder[35] = 'chooseDept';
/* AI methods. */
$lang->resource->ai = new stdclass();
$lang->resource->ai->models = 'modelBrowse';
$lang->resource->ai->modelView = 'modelView';
$lang->resource->ai->modelCreate = 'modelCreate';
$lang->resource->ai->modelEdit = 'modelEdit';
$lang->resource->ai->modelEnable = 'modelEnable';
$lang->resource->ai->modelDisable = 'modelDisable';
$lang->resource->ai->modelDelete = 'modelDelete';
$lang->resource->ai->modelTestConnection = 'modelTestConnection';
$lang->resource->ai->promptPublish = 'promptPublish';
$lang->resource->ai->promptUnpublish = 'promptUnpublish';
$lang->resource->ai->prompts = 'promptBrowse';
$lang->resource->ai->promptView = 'promptView';
$lang->resource->ai->promptExecute = 'promptExecute';
$lang->resource->ai->promptExecutionReset = 'promptExecutionReset';
$lang->resource->ai->chat = 'chat';
$lang->resource->ai->createMiniProgram = 'createMiniProgram';
$lang->resource->ai->editMiniProgram = 'editMiniProgram';
$lang->resource->ai->testMiniProgram = 'testMiniProgram';
$lang->resource->ai->miniPrograms = 'miniProgramList';
$lang->resource->ai->miniProgramView = 'miniProgramView';
$lang->resource->ai->publishMiniProgram = 'publishMiniProgram';
$lang->resource->ai->unpublishMiniProgram = 'unpublishMiniProgram';
$lang->resource->ai->deleteMiniProgram = 'deleteMiniProgram';
$lang->resource->ai->exportMiniProgram = 'exportMiniProgram';
$lang->resource->ai->importMiniProgram = 'importMiniProgram';
$lang->resource->ai->editMiniProgramCategory = 'editMiniProgramCategory';
$lang->resource->aiapp = new stdclass();
$lang->resource->aiapp->square = 'miniProgramSquare';
$lang->resource->aiapp->view = 'view';
$lang->resource->aiapp->miniProgramChat = 'miniProgramChat';
$lang->resource->aiapp->collectMiniProgram = 'collectMiniProgram';
/* Others. */
$lang->resource->file = new stdclass();
$lang->resource->file->download = 'download';
+1 -1
View File
@@ -207,7 +207,7 @@ class helper extends baseHelper
/* The requestTypes are: GET, PATH_INFO2, PATH_INFO */
if($config->requestType == 'GET')
{
$link = $config->webRoot . (string) substr($link, 2);
$link = $config->webRoot . (string) substr($link, 1);
}
elseif($config->requestType == 'PATH_INFO2')
{
+89 -13
View File
@@ -78,11 +78,6 @@ class api extends router
*/
public function __construct(string $appName = 'api', string $appRoot = '')
{
parent::__construct($appName, $appRoot);
$this->viewType = 'json';
$this->httpMethod = strtolower((string) $_SERVER['REQUEST_METHOD']);
$this->path = trim(substr((string) $_SERVER['REQUEST_URI'], strpos((string) $_SERVER['REQUEST_URI'], 'api.php') + 7), '/');
if(strpos($this->path, '?') > 0) $this->path = strstr($this->path, '?', true);
@@ -90,6 +85,10 @@ class api extends router
$this->apiVersion = $subPos !== false ? substr($this->path, 0, $subPos) : '';
$this->path = $subPos !== false ? substr($this->path, $subPos) : '';
parent::__construct($appName, $appRoot);
$this->viewType = 'json';
$this->httpMethod = strtolower((string) $_SERVER['REQUEST_METHOD']);
$this->loadApiLang();
}
@@ -278,7 +277,7 @@ class api extends router
if(isset($info['method'])) $methodName = $info['method'];
}
if(isset($info['response'])) $this->responseExtractor = $info['response'];
if(isset($info['response']) && $this->responseExtractor == '*') $this->responseExtractor = $info['response'];
}
foreach($paramValues as $key => $value)
@@ -387,6 +386,64 @@ class api extends router
}
}
/**
* 检查传入的对象是否存在
*
* Check object exists.
*
* @access public
* @return void
*/
public function checkObjectExists()
{
$objectMap = [
'program' => TABLE_PROJECT,
'programID' => TABLE_PROJECT,
'product' => TABLE_PRODUCT,
'productID' => TABLE_PRODUCT,
'project' => TABLE_PROJECT,
'projectID' => TABLE_PROJECT,
'productplan' => TABLE_PRODUCTPLAN,
'productplanID' => TABLE_PRODUCTPLAN,
'plan' => TABLE_PRODUCTPLAN,
'planID' => TABLE_PRODUCTPLAN,
'execution' => TABLE_PROJECT,
'executionID' => TABLE_PROJECT,
'story' => TABLE_STORY,
'storyID' => TABLE_STORY,
'epic' => TABLE_STORY,
'epicID' => TABLE_STORY,
'requirement' => TABLE_STORY,
'requirementID' => TABLE_STORY,
'task' => TABLE_TASK,
'taskID' => TABLE_TASK,
'bug' => TABLE_BUG,
'bugID' => TABLE_BUG,
'feedback' => TABLE_FEEDBACK,
'feedbackID' => TABLE_FEEDBACK,
'build' => TABLE_BUILD,
'buildID' => TABLE_BUILD,
'case' => TABLE_CASE,
'caseID' => TABLE_CASE,
'testcase' => TABLE_CASE,
'testcaseID' => TABLE_CASE,
'user' => TABLE_USER,
'userID' => TABLE_USER,
'ticket' => TABLE_TICKET,
'ticketID' => TABLE_TICKET,
];
$params = array_merge($this->params, $_POST);
foreach($params as $key => $value)
{
if(isset($objectMap[$key]) && $value != 0)
{
$id = $this->dao->select('id')->from($objectMap[$key])->where('id')->eq($value)->andWhere('deleted')->eq('0')->fetch('id');
if(!$id) return $this->control->sendError(ucfirst(str_replace('ID', '', $key)) . ' does not exist.');
}
}
}
/**
* 执行对应模块
*
@@ -407,6 +464,9 @@ class api extends router
{
$this->setFormData();
}
$this->checkObjectExists();
return parent::loadModule();
}
elseif(!$this->apiVersion)
@@ -453,7 +513,15 @@ class api extends router
$_POST = json_decode($requestBody, true);
/* Avoid empty post body. */
$_POST['verifyPassword'] = '1';
if(in_array($this->control->moduleName, ['feedback', 'ticket']))
{
$_POST['uid'] = '1';
}
else
{
$_POST['verifyPassword'] = '1';
}
/* 以POST的值为准。 Set GET value from POST data. */
foreach($_POST as $key => $value)
@@ -463,7 +531,7 @@ class api extends router
/* 其他方法不需要从GET页面获取post data。Other request directly. */
if(!in_array($this->methodName, ['create', 'edit'])) return;
/* 更新操作的表单需要拼接原始的值。 Merge original values. */
/* Get form data by get request. */
$postData = $_POST;
@@ -473,14 +541,18 @@ class api extends router
$this->control->getFormData = true;
$zen = $this->control->moduleName . 'Zen';
$this->control->$zen->getFormData = true;
if(isset($this->control->$zen)) $this->control->$zen->getFormData = true;
$method = $this->control->methodName;
$control = $this->control; // fetch method will change control.
$method = $this->control->methodName;
call_user_func_array(array($this->control, $method), $this->params);
/* Clean the output in get method. */
ob_clean();
$this->control->getFormData = false;
$this->control->$zen->getFormData = false;
$this->control->viewType = 'json';
$this->control = $control;
$_POST = $postData;
foreach($this->control->formData as $key => $value)
@@ -488,9 +560,13 @@ class api extends router
if(!isset($_POST[$key])) $_POST[$key] = $value;
}
foreach($this->control->$zen->formData as $key => $value)
if(isset($this->control->$zen))
{
if(!isset($_POST[$key])) $_POST[$key] = $value;
$this->control->$zen->getFormData = false;
foreach($this->control->$zen->formData as $key => $value)
{
if(!isset($_POST[$key])) $_POST[$key] = $value;
}
}
}
+3 -3
View File
@@ -806,7 +806,7 @@ class baseRouter
if(isset($_SERVER['REQUEST_SCHEME']) and strtolower((string) $_SERVER['REQUEST_SCHEME']) == 'https') $httpType = 'https';
$httpHost = zget($_SERVER, 'HTTP_HOST', '');
$apiMode = (defined('RUN_MODE') && RUN_MODE == 'api') || isset($_GET[$this->config->sessionVar]);
$apiMode = $this->apiVersion || isset($_GET[$this->config->sessionVar]);
if(!$apiMode && (empty($httpHost) or !str_starts_with((string) $this->server->http_referer, "$httpType://$httpHost"))) $_FILES = $_POST = array();
}
@@ -3907,8 +3907,8 @@ class ztSessionHandler implements SessionHandlerInterface
public function gc($maxlifeTime): int|false
{
/* API session never expires. */
if(!isset($this->config->sessionVar)) return 0;
if((defined('RUN_MODE') && RUN_MODE == 'api') || isset($_GET[$this->config->sessionVar])) return 0;
global $config;
if((defined('RUN_MODE') && RUN_MODE == 'api') || isset($_GET[$config->sessionVar])) return 0;
$time = time();
$count = 0;
-20
View File
@@ -2829,26 +2829,6 @@ class baseSQL
*/
public function notin($ids)
{
if((is_string($ids) && $ids === '') || (is_array($ids) && empty($ids)))
{
$pattern = '/\s+(?:`([^`]+)`|"([^"]+)"|(\w+))\s*$/i';
$replacement = ' 1=1 ';
$this->sql = preg_replace($pattern, $replacement, $this->sql);
return $this;
}
if($this->inCondition and !$this->conditionIsTrue) return $this;
if((is_string($ids) && $ids === '') || (is_array($ids) && empty($ids)))
{
$pattern = '/\s+(?:(?:[a-zA-Z0-9]+\.)?|)(?:`([^`]+)`|"([^"]+)"|(\w+))\s*$/i';
$replacement = ' 1=1 ';
$this->sql = preg_replace($pattern, $replacement, $this->sql);
return $this;
}
if($this->inCondition and !$this->conditionIsTrue) return $this;
if((is_string($ids) && $ids === '') || (is_array($ids) && empty($ids)))
+6
View File
@@ -256,6 +256,12 @@ class dao extends baseDAO
$groupID = !empty($result->workflowGroup) ? $result->workflowGroup : 0;
}
if($groupID)
{
$builtIn = $this->dbh->query("SELECT `main` FROM " . TABLE_WORKFLOWGROUP . " WHERE `id` = '{$groupID}'")->fetch(PDO::FETCH_OBJ);
$groupID = !empty($builtIn->main) ? 0 : $groupID;
}
$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}' AND `group` = '{$groupID}'")->fetch(PDO::FETCH_OBJ);
if(!$flowAction) return $this;
+114 -131
View File
@@ -125,20 +125,16 @@ class dbh
public function __construct($dbConfig, $setSchema = true, $flag = 'MASTER')
{
global $config;
$this->config = $config;
$driverAlias = array('oceanbase' => 'mysql', 'highgo' => 'pgsql', 'postgres' => 'pgsql');
$driver = isset($driverAlias[$dbConfig->driver]) ? $driverAlias[$dbConfig->driver] : $dbConfig->driver;
$this->pdo = $this->pdoInit($driver, $dbConfig, $setSchema);
$this->config = $config;
$this->dbConfig = $dbConfig;
$this->flag = $flag;
$this->pdo = $this->pdoInit($setSchema);
$queries = [];
/* Mysql driver include mysql and oceanbase. */
if($driver == 'mysql')
if(in_array($dbConfig->driver, $config->mysqlDriverList))
{
$queries[] = "SET NAMES {$dbConfig->encoding}" . ($dbConfig->collation ? " COLLATE '{$dbConfig->collation}'" : '');
if($dbConfig->driver == 'mysql') $queries[] = "SET NAMES {$dbConfig->encoding}" . ($dbConfig->collation ? " COLLATE '{$dbConfig->collation}'" : '');
if(isset($dbConfig->strictMode) && empty($dbConfig->strictMode)) $queries[] = "SET @@sql_mode= ''";
}
else
@@ -147,7 +143,15 @@ class dbh
if($setSchema)
{
$queries[] = $driver == 'pgsql' ? "SET SCHEMA 'public'" : "SET SCHEMA {$dbConfig->name}";
if($dbConfig->driver == 'dm')
{
$queries[] = "SET SCHEMA {$dbConfig->name}";
}
elseif(in_array($dbConfig->driver, $config->pgsqlDriverList))
{
$schema = $dbConfig->schema ?? 'public';
$queries[] = "SET SCHEMA '{$schema}'";
}
}
}
if(!empty($queries))
@@ -156,30 +160,45 @@ class dbh
}
}
/**
* 获取PDO驱动名称。
* Get pdo driver name.
*
* @access private
* @return string
*/
private function getPdoDriver()
{
if($this->dbConfig->driver == 'kingbase') return 'kdb';
if(in_array($this->dbConfig->driver, $this->config->mysqlDriverList)) return 'mysql';
if(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList)) return 'pgsql';
return $this->dbConfig->driver;
}
/**
* 初始化PDO对象。
* Init pdo.
*
* @param string $driver
* @param object $dbConfig
* @param bool $setSchema
* @access private
* @return object
*/
private function pdoInit($driver, $dbConfig, $setSchema)
private function pdoInit($setSchema)
{
$dsn = "{$driver}:host={$dbConfig->host};port={$dbConfig->port}";
$driver = $this->getPdoDriver();
$dsn = "{$driver}:host={$this->dbConfig->host};port={$this->dbConfig->port}";
if($setSchema)
{
$dsn .= ";dbname={$dbConfig->name}";
$dsn .= ";dbname={$this->dbConfig->name}";
}
elseif($driver == 'pgsql') // pgsql(postgres,highgo) need database to connect
elseif(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
$dsn .= ";dbname={$dbConfig->driver}"; // default database
$dsn .= ";dbname={$this->dbConfig->driver}"; // default database
}
$password = helper::decryptPassword($dbConfig->password);
$pdo = new PDO($dsn, $dbConfig->user, $password);
$password = helper::decryptPassword($this->dbConfig->password);
$pdo = new PDO($dsn, $this->dbConfig->user, $password);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
@@ -380,33 +399,35 @@ class dbh
*/
public function dbExists()
{
switch($this->dbConfig->driver)
if($this->dbConfig->driver == 'dm')
{
case 'oceanbase':
case 'mysql':
$sql = "SHOW DATABASES like '{$this->dbConfig->name}'";
break;
case 'dm':
$sql = "SELECT * FROM ALL_OBJECTS WHERE object_type='SCH' AND owner='{$this->dbConfig->name}'";
break;
case 'postgres':
case 'highgo':
$sql = "SELECT * FROM pg_database WHERE datname ='{$this->dbConfig->name}'";
break;
default:
$sql = '';
$sql = "SELECT * FROM ALL_OBJECTS WHERE object_type='SCH' AND owner='{$this->dbConfig->name}'";
return $this->rawQuery($sql)->fetch();
}
return $this->rawQuery($sql)->fetch();
if(in_array($this->dbConfig->driver, $this->config->mysqlDriverList))
{
$sql = "SHOW DATABASES like '{$this->dbConfig->name}'";
return $this->rawQuery($sql)->fetch();
}
if(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
$sql = "SELECT * FROM pg_database WHERE datname ='{$this->dbConfig->name}'";
return $this->rawQuery($sql)->fetch();
}
return false;
}
/**
* Check table exits or not.
* Check table exist or not.
*
* @param string $tableName
* @access public
* @return void
*/
public function tableExits($tableName)
public function tableExist($tableName)
{
$tableName = str_replace(array("'", '`'), "", $tableName);
@@ -415,15 +436,21 @@ class dbh
$sql = "SELECT * FROM all_tables WHERE owner='{$this->dbConfig->name}' AND table_name='{$tableName}'";
return $this->rawQuery($sql)->fetch();
}
elseif(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
if(in_array($this->dbConfig->driver, $this->config->mysqlDriverList))
{
$sql = "SHOW TABLES FROM {$this->dbConfig->name} like '{$tableName}'";
return $this->rawQuery($sql)->fetch();
}
if(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
$this->useDB($this->dbConfig->name);
$sql = "SELECT * FROM information_schema.tables WHERE table_catalog = '{$this->dbConfig->name}' AND table_name='{$tableName}'";
return $this->rawQuery($sql)->fetch();
}
$sql = "SHOW TABLES FROM {$this->dbConfig->name} like '{$tableName}'";
return $this->rawQuery($sql)->fetch();
return false;
}
/**
@@ -474,23 +501,26 @@ class dbh
*/
public function createDB($version)
{
switch($this->dbConfig->driver)
if($this->dbConfig->driver == 'dm')
{
case 'mysql':
$result = $this->getServerCharsetAndCollation();
$sql = "CREATE DATABASE `{$this->dbConfig->name}` DEFAULT CHARACTER SET {$result['charset']} COLLATE {$result['collation']}";
return $this->rawQuery($sql);
case 'dm':
$createSchema = "CREATE SCHEMA {$this->dbConfig->name} AUTHORIZATION {$this->dbConfig->user}";
return $this->rawQuery($createSchema);
case 'oceanbase':
case 'postgres':
case 'highgo':
$sql = "CREATE DATABASE `{$this->dbConfig->name}`";
return $this->rawQuery($sql);
default:
return false;
$createSchema = "CREATE SCHEMA {$this->dbConfig->name} AUTHORIZATION {$this->dbConfig->user}";
return $this->rawQuery($createSchema);
}
if($this->dbConfig->driver == 'mysql')
{
$result = $this->getServerCharsetAndCollation();
$sql = "CREATE DATABASE `{$this->dbConfig->name}` DEFAULT CHARACTER SET {$result['charset']} COLLATE {$result['collation']}";
return $this->rawQuery($sql);
}
if($this->dbConfig->driver == 'oceanbase' || in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
$sql = "CREATE DATABASE {$this->dbConfig->name}";
return $this->rawQuery($sql);
}
return false;
}
/**
@@ -502,20 +532,18 @@ class dbh
*/
public function useDB($dbName)
{
switch($this->dbConfig->driver)
if($this->dbConfig->driver == 'dm') return $this->exec("SET SCHEMA {$dbName}");
if(in_array($this->dbConfig->driver, $this->config->mysqlDriverList)) return $this->exec("USE {$dbName}");
if(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
case 'oceanbase':
case 'mysql':
return $this->exec("USE {$this->dbConfig->name}");
case 'dm':
return $this->exec("SET SCHEMA {$this->dbConfig->name}");
case 'postgres':
case 'highgo':
$this->pdo = $this->pdoInit('pgsql', $this->dbConfig, true);
return $this->exec("SET SCHEMA 'public'");
default:
return false;
$this->pdo = $this->pdoInit(true);
$schema = $this->dbConfig->schema ?? 'public';
return $this->exec("SET SCHEMA '{$schema}'");
}
return false;
}
/**
@@ -529,18 +557,11 @@ class dbh
{
$this->sql = $sql;
switch($this->dbConfig->driver)
{
case 'dm':
return $this->formatDmSQL($sql);
if($this->dbConfig->driver == 'dm') return $this->formatDmSQL($sql);
case 'postgres':
case 'highgo':
return $this->formatPgSQL($sql);
if(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList)) return $this->formatPgSQL($sql);
default:
return $sql;
}
return $sql;
}
/**
@@ -585,28 +606,7 @@ class dbh
if(stripos($sql, 'CREATE OR REPLACE VIEW ') === 0)
{
// Modify if function.
$fieldsBegin = stripos($sql, 'select');
$fieldsEnd = stripos($sql, 'from');
$fields = substr($sql, $fieldsBegin+6, $fieldsEnd-$fieldsBegin-6);
$fieldList = preg_split("/,(?![^(]+\))/", $fields);
foreach($fieldList as $key => $field)
{
$aliasPos = stripos($field, ' AS ');
$subField = substr($field, 0, $aliasPos);
if(stripos($field, 'SUM(') === 0) $subField = substr($subField, 4, -1);
$fieldParts = preg_split("/\+(?![^(]+\))/", $subField);
foreach($fieldParts as $pkey => $fieldPart)
{
$originField = trim($fieldPart);
if(stripos($originField, 'if(') === false) continue;
$fieldParts[$pkey] = $this->formatDmIfFunction($originField);
}
$fieldList[$key] = str_replace($subField, implode(' + ', $fieldParts), $field);
}
$fields = implode(',', $fieldList);
$sql = substr($sql, 0, $fieldsBegin+6) . $fields . substr($sql, $fieldsEnd);
$sql = $this->formatField($sql);
return str_replace('CREATE OR REPLACE VIEW ', 'CREATE VIEW ', $sql);
}
elseif(stripos($sql, 'CREATE UNIQUE INDEX') === 0 || stripos($sql, 'CREATE INDEX') === 0)
@@ -809,17 +809,13 @@ class dbh
*/
public function formatField($sql)
{
switch($this->dbConfig->driver)
if($this->dbConfig->driver == 'dm' || in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
case 'dm':
case 'postgres':
case 'highgo':
$sql = str_replace('`', '"', $sql);
return $sql;
default:
return $sql;
$sql = str_replace('`', '"', $sql);
$sql = preg_replace('/(?<!\w)if\(/i', '"IF"(', $sql);
}
return $sql;
}
/**
@@ -831,17 +827,10 @@ class dbh
*/
public function formatFunction($sql)
{
switch($this->dbConfig->driver)
{
case 'dm':
/* DATE convert to TO_CHAR. */
$sql = preg_replace("/\bDATE\(([^)]*)\)/", "TO_CHAR($1, 'yyyy-mm-dd')", $sql, -1);
/* DATE convert to TO_CHAR. */
if($this->dbConfig->driver == 'dm') return preg_replace("/\bDATE\(([^)]*)\)/", "TO_CHAR($1, 'yyyy-mm-dd')", $sql, -1);
return $sql;
default:
return $sql;
}
return $sql;
}
/**
@@ -883,7 +872,7 @@ class dbh
*/
public function formatAttr($sql)
{
if(in_array($this->dbConfig->driver, array('dm', 'postgres', 'highgo')))
if($this->dbConfig->driver == 'dm' || in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
{
$pos = stripos($sql, ' ENGINE');
if($pos > 0) $sql = substr($sql, 0, $pos);
@@ -906,14 +895,14 @@ class dbh
"0000-00-00" => '1970-01-01',
);
if(in_array($this->dbConfig->driver, $this->config->pgsqlDriverList))
if($this->dbConfig->driver == 'dm')
{
$sql = preg_replace('/(\s*`[^`]+`)\s+\K.+AUTO_INCREMENT(,)/i', ' serial,', $sql);
$sql = str_ireplace(' DATETIME', ' TIMESTAMP', $sql);
$sql = str_ireplace(' AUTO_INCREMENT', ' IDENTITY(1, 1)', $sql);
}
else
{
$sql = str_ireplace(' AUTO_INCREMENT', ' IDENTITY(1, 1)', $sql);
$sql = preg_replace('/(\s*`[^`]+`)\s+\K.+AUTO_INCREMENT(,)/i', ' serial,', $sql);
$sql = str_ireplace(' DATETIME', ' TIMESTAMP', $sql);
}
$sql = preg_replace('/ enum[\_0-9a-z\,\'\"\( ]+\)+/i', ' varchar(255) ', $sql);
@@ -1217,18 +1206,12 @@ class dbh
*/
public function getVersion(): string
{
switch($this->dbConfig->driver)
if(in_array($this->dbConfig->driver, $this->config->mysqlDriverList))
{
case 'oceanbase':
case 'mysql':
$sql = "SELECT version() AS version";
break;
case 'dm':
default:
$sql = '';
$sql = "SELECT VERSION() AS version";
return $this->rawQuery($sql)->fetch()->version;
}
if(empty($sql)) return '';
return $this->rawQuery($sql)->fetch()->version;
return '';
}
}
+1 -1
View File
@@ -886,7 +886,7 @@ class Mobile_Detect
* from the $headers array instead.
*/
public function __construct(
array $headers = null,
?array $headers = null,
$userAgent = null
) {
$this->setHttpHeaders($headers);
+1 -1
View File
@@ -352,7 +352,7 @@ class context extends \zin\utils\dataset
{
$rawContent = ob_get_contents();
if(!is_string($rawContent)) $rawContent = '';
ob_end_clean();
if(!empty(ob_get_status(true))) ob_end_clean();
return $rawContent;
}
+6 -4
View File
@@ -19,7 +19,7 @@ function deepGet(object|array &$data, string $namePath, mixed $defaultValue = nu
return $data === null ? $defaultValue : $data;
}
function deepSet(array &$data, string $namePath, mixed $value)
function deepSet(array|object &$data, string $namePath, mixed $value)
{
$names = explode('.', $namePath);
$lastName = array_pop($names);
@@ -27,12 +27,14 @@ function deepSet(array &$data, string $namePath, mixed $value)
{
foreach($names as $name)
{
if(!is_array($data)) return;
if(!is_array($data) && !is_object($data)) return;
if(!isset($data[$name])) $data[$name] = array();
if(is_array($data) && !isset($data[$name])) $data[$name] = array();
if(is_object($data) && !isset($data->$name)) $data->$name = new \stdClass();
$data = &$data[$name];
}
}
$data[$lastName] = $value;
if(is_array($data)) $data[$lastName] = $value;
elseif(is_object($data)) $data->$lastName = $value;
}
+1 -1
View File
@@ -58,7 +58,7 @@ class datalist extends wg
),
div
(
setClass('datalist-item-content', $contentClass),
setClass('datalist-item-content whitespace-pre-wrap', $contentClass),
$content,
$children
)
+1
View File
@@ -214,6 +214,7 @@ CSS;
set::object($object),
set::title($title),
set::titleClass('text-lg text-clip font-bold'),
set::titleProps(array('title' => $title)),
set::type($objectType),
set::color($color),
set::parentTitleClass('text-lg text-clip font-bold'),
+2 -2
View File
@@ -925,10 +925,10 @@ const actionsMap =
if(!isApi && !isTemplate)
{
const canExportWord = hasPriv('exportDoc');
if(doc.contentType === 'doc' || doc.contentType === 'markdown')
if(canExportWord && (doc.contentType === 'doc' || doc.contentType === 'markdown'))
{
const exportItems = [
canExportWord ? {text: lang.exportWord, command: 'exportWord'} : null,
{text: lang.exportWord, command: 'exportWord'},
config.debug > 5 ? {text: lang.exportPdf, command: 'exportDoc/pdf'} : null,
config.debug > 5 ? {text: lang.exportImage, command: 'exportDoc/png'} : null,
{text: lang.exportMarkdown, command: 'exportDoc/markdown'},
+3 -2
View File
@@ -59,8 +59,9 @@ class featureBar extends wg
data('activeFeature', $current);
if(empty($commonLink)) $commonLink = createLink($app->rawModule, $app->rawMethod, $this->prop('linkParams'));
if(empty($searchModule)) $searchModule = data("config.{$currentModule}.search.module") ? data("config.{$currentModule}.search.module") : $currentModule;
if(empty($commonLink)) $commonLink = createLink($app->rawModule, $app->rawMethod, $this->prop('linkParams'));
if(empty($searchModule)) $searchModule = data("config.{$currentModule}.search.module");
if(empty($searchModule)) $searchModule = $currentModule;
foreach($rawItems as $rawItem)
{
+14
View File
@@ -107,6 +107,20 @@ class fileSelector extends wg
}
$this->setProp('defaultFiles', $defaultFiles);
}
if($this->hasProp('accept'))
{
$accept = explode(',', $this->prop('accept'));
$dangers = explode(',', $app->config->file->dangers);
$filteredAccept = array_filter($accept, function($item) use ($dangers)
{
$item = strtolower($item);
$ext = ltrim($item, '.');
return !in_array($ext, $dangers);
});
$newAccept = implode(',', $filteredAccept);
$this->setProp('accept', $newAccept);
}
/* Check file type. */
$acceptFileTypes = $this->prop('accept') ? ',' . str_replace('.', '', $this->prop('accept')) . ',' : '';
+11 -4
View File
@@ -1,6 +1,6 @@
<?php
/**
* PHP 隐式可空类型修复脚本 - 最终版本
* PHP 隐式可空类型修复脚本
* 用于修复 RFC: Deprecate implicitly nullable types 问题
* https://wiki.php.net/rfc/deprecate-implicitly-nullable-types
*
@@ -9,6 +9,7 @@
* - 支持命名空间类型(\Namespace\ClassName)
* - 支持跨行函数参数
* - 自动跳过已有?的参数
* - 自动跳过 mixed 类型的参数
* - 自动跳过联合类型中已包含null的参数
* - 完整日志记录
*/
@@ -123,6 +124,12 @@ class NullableTypesFixer
continue; // 已经有 ? 了
}
// 检查是否是 mixed 类型
if($type == 'mixed')
{
continue;
}
// 检查是否是联合类型且已包含 null
if(stripos($type, '|null') !== false || stripos($type, 'null|') !== false)
{
@@ -247,13 +254,13 @@ class NullableTypesFixer
// 使用示例
if($argc < 2)
{
echo "用法: php fix_nullable_types_final.php <目录路径> [--dry-run]\n";
echo "用法: php fix_nullable_types.php <目录路径> [--dry-run]\n";
echo "参数说明:\n";
echo " <目录路径> 要扫描的PHP文件目录\n";
echo " --dry-run 预览模式,不实际修改文件\n";
echo "\n示例:\n";
echo " php fix_nullable_types_final.php /path/to/project\n";
echo " php fix_nullable_types_final.php /path/to/project --dry-run\n";
echo " php fix_nullable_types.php /path/to/project\n";
echo " php fix_nullable_types.php /path/to/project --dry-run\n";
exit(1);
}
+1 -1
View File
@@ -15,7 +15,7 @@ $config->action->objectNameFields['bug'] = 'title';
$config->action->objectNameFields['testcase'] = 'title';
$config->action->objectNameFields['case'] = 'title';
$config->action->objectNameFields['testtask'] = 'name';
$config->action->objectNameFields['user'] = 'account';
$config->action->objectNameFields['user'] = 'realname';
$config->action->objectNameFields['api'] = 'title';
$config->action->objectNameFields['board'] = 'name';
$config->action->objectNameFields['boardspace'] = 'name';
+2 -1
View File
@@ -632,7 +632,8 @@ class actionTao extends actionModel
$table = $this->config->objectTables[$module];
$field = $this->config->action->objectNameFields[$module];
$name = $this->dao->select($field)->from($table)->where('id')->eq($id)->fetch($field);
if($name) $action->appendLink = html::a(helper::createLink($module, 'view', "id={$id}"), "#{$id} " . $name);
$method = $module == 'story' ? 'storyView' : 'view';
if($name) $action->appendLink = html::a(helper::createLink($module, $method, "id={$id}"), "#{$id} " . $name);
}
$action->extra = $extra;
}
@@ -171,7 +171,7 @@ class actionTest
* @access public
* @return array
*/
public function getTrashesBySearchTest(string $objectType, string $type, string|int $queryID, string $orderBy, object $pager = null): array
public function getTrashesBySearchTest(string $objectType, string $type, string|int $queryID, string $orderBy, ?object $pager = null): array
{
$objects = $this->objectModel->getTrashesBySearch($objectType, $type, $queryID, $orderBy, $pager);
if(dao::isError()) return dao::getError();
@@ -888,7 +888,7 @@ class actionTest
if(dao::isError()) return dao::getError();
return $output;
return str_replace("\n", '', $output);
}
/**
+5 -6
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env php
<?php
/**
title=测试 actionModel::getHistory();
@@ -47,8 +46,8 @@ su('admin');
$actionTest = new actionTest();
r($actionTest->getHistoryTest(1)) && p("0:field,old,new") && e('resolution,1,2'); // 测试步骤1:使用整数actionID查询存在的历史记录
r($actionTest->getHistoryTest('2')) && p("0:field,old,new") && e('resolvedBuild,2,3'); // 测试步骤2:使用字符串actionID查询存在的历史记录
r($actionTest->getHistoryTest(3)) && p("") && e('0'); // 测试步骤3:查询没有历史记录的actionID
r($actionTest->getHistoryTest(10000)) && p("") && e('0'); // 测试步骤4:查询不存在的actionID
r($actionTest->getHistoryTest(0)) && p("") && e('0'); // 测试步骤5:使用无效的actionID
r($actionTest->getHistoryTest(1)[1]) && p("0:field,old,new") && e('resolution,1,2'); // 测试步骤1:使用整数actionID查询存在的历史记录
r($actionTest->getHistoryTest('2')[2]) && p("0:field,old,new") && e('resolvedBuild,2,3'); // 测试步骤2:使用字符串actionID查询存在的历史记录
r($actionTest->getHistoryTest(3)) && p() && e('0'); // 测试步骤3:查询没有历史记录的actionID
r($actionTest->getHistoryTest(10000)) && p() && e('0'); // 测试步骤4:查询不存在的actionID
r($actionTest->getHistoryTest(0)) && p() && e('0'); // 测试步骤5:使用无效的actionID
+10 -10
View File
@@ -7,11 +7,11 @@ title=测试 actionModel::printChanges();
timeout=0
cid=14920
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, array @
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories1 @修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />' . "\n"
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories2 @修改了 <strong><i>指派给</i></strong>,旧值为 "admin",新值为 "user1"。<br />' . "\n" . '修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />' . "\n"
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories3 @修改了 <strong><i>任务描述</i></strong>,区别为:' . "\n" . "<blockquote class='textdiff'></blockquote>" . "\n" . "<blockquote class='original'>&lt;del&gt;旧描述&lt;/del&gt;&lt;ins&gt;新描述&lt;/ins&gt;</blockquote>"
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories3, false @修改了 <strong><i>任务描述</i></strong>,区别为:' . "\n" . "<blockquote class='textdiff'></blockquote>" . "\n" . "<blockquote class='original'>&lt;del&gt;旧描述&lt;/del&gt;&lt;ins&gt;新描述&lt;/ins&gt;</blockquote>"
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, array @0
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories1 @修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories2 @修改了 <strong><i>指派给 </i></strong>,旧值为 "admin",新值为 "user1"。<br />修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories3 @修改了 <strong><i>任务描述</i></strong>,区别为:<blockquote class='textdiff'><del>旧描述</del><ins>新描述</ins></blockquote><blockquote class='original'><del>旧描述</del><ins>新描述</ins></blockquote>
- 执行actionTest模块的printChangesTest方法,参数是'task', 1, $histories3, false @修改了 <strong><i>任务描述</i></strong>,区别为:<blockquote class='textdiff'></blockquote><blockquote class='original'><del>旧描述</del><ins>新描述</ins></blockquote>
*/
@@ -27,26 +27,26 @@ su('admin');
$actionTest = new actionTest();
// 步骤1:空历史记录测试
r($actionTest->printChangesTest('task', 1, array())) && p() && e('');
r($actionTest->printChangesTest('task', 1, array())) && p() && e('0');
// 步骤2:单个字段变更测试
$histories1 = array(
(object)array('field' => 'status', 'old' => '待处理', 'new' => '进行中', 'diff' => '')
);
r($actionTest->printChangesTest('task', 1, $histories1)) && p() && e('修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />' . "\n");
r($actionTest->printChangesTest('task', 1, $histories1)) && p() && e('修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />');
// 步骤3:多个字段变更测试
$histories2 = array(
(object)array('field' => 'assignedTo', 'old' => 'admin', 'new' => 'user1', 'diff' => ''),
(object)array('field' => 'status', 'old' => '待处理', 'new' => '进行中', 'diff' => '')
);
r($actionTest->printChangesTest('task', 1, $histories2)) && p() && e('修改了 <strong><i>指派给</i></strong>,旧值为 "admin",新值为 "user1"。<br />' . "\n" . '修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />' . "\n");
r($actionTest->printChangesTest('task', 1, $histories2)) && p() && e('修改了 <strong><i>指派给 </i></strong>,旧值为 "admin",新值为 "user1"。<br />修改了 <strong><i>任务状态</i></strong>,旧值为 "待处理",新值为 "进行中"。<br />');
// 步骤4:包含diff信息的变更测试
$histories3 = array(
(object)array('field' => 'desc', 'old' => '旧描述', 'new' => '新描述', 'diff' => '<del>旧描述</del><ins>新描述</ins>')
);
r($actionTest->printChangesTest('task', 1, $histories3)) && p() && e('修改了 <strong><i>任务描述</i></strong>,区别为:' . "\n" . "<blockquote class='textdiff'></blockquote>" . "\n" . "<blockquote class='original'>&lt;del&gt;旧描述&lt;/del&gt;&lt;ins&gt;新描述&lt;/ins&gt;</blockquote>");
r($actionTest->printChangesTest('task', 1, $histories3)) && p() && e("修改了 <strong><i>任务描述</i></strong>,区别为:<blockquote class='textdiff'><del>旧描述</del><ins>新描述</ins></blockquote><blockquote class='original'><del>旧描述</del><ins>新描述</ins></blockquote>");
// 步骤5:canChangeTag为false的diff测试
r($actionTest->printChangesTest('task', 1, $histories3, false)) && p() && e('修改了 <strong><i>任务描述</i></strong>,区别为:' . "\n" . "<blockquote class='textdiff'></blockquote>" . "\n" . "<blockquote class='original'>&lt;del&gt;旧描述&lt;/del&gt;&lt;ins&gt;新描述&lt;/ins&gt;</blockquote>");
r($actionTest->printChangesTest('task', 1, $histories3, false)) && p() && e("修改了 <strong><i>任务描述</i></strong>,区别为:<blockquote class='textdiff'></blockquote><blockquote class='original'><del>旧描述</del><ins>新描述</ins></blockquote>");
+30 -14
View File
@@ -5,16 +5,33 @@ declare(strict_types=1);
/**
title=测试 actionTao::getNeedRelatedFields();
timeout=0
cid=0
- 测试story类型对象的相关字段获取 >> 期望返回product数组和project、execution字段@1,0,0
- 测试productplan类型对象的相关字段获取 >> 期望正确返回product字段@0,0,0
- 测试branch类型对象的相关字段获取 >> 期望正确返回product字段@0,0,0
- 测试testcase类型对象的相关字段获取 >> 期望正确返回相关字段@1,1,6
- 测试case类型对象的相关字段获取 >> 期望正确返回相关字段@1,1,6
- 测试task类型对象的相关字段获取 >> 期望正确返回相关字段@1,1,6
- 测试release类型对象的相关字段获取 >> 期望正确返回相关字段@1,1
- 测试不存在的对象类型的相关字段获取 >> 期望正确处理不存在的情况@0,0,0
- 测试story类型对象的相关字段获取第0条的0属性 @1
- 测试productplan类型对象的相关字段获取
- @1
- 属性1 @0
- 属性2 @0
- 测试branch类型对象的相关字段获取
- @1
- 属性1 @0
- 属性2 @0
- 测试testcase类型对象的相关字段获取
- 第0条的0属性 @1
- 第0条的1属性 @~~
- 第0条的2属性 @~~
- 测试case类型对象的相关字段获取
- 第0条的0属性 @1
- 第0条的1属性 @~~
- 第0条的2属性 @~~
- 测试task类型对象的相关字段获取
- 第0条的0属性 @~~
- 第0条的1属性 @1
- 第0条的2属性 @~~
- 测试release类型对象的相关字段获取
- 第0条的0属性 @1
- 第0条的1属性 @~~
*/
@@ -93,11 +110,10 @@ su('admin');
$actionTest = new actionTest();
r($actionTest->getNeedRelatedFieldsTest('story', 1, 'created', '')) && p('0:0;1;2', ';') && e('1;0;0'); // 测试story类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('story', 1, 'created', '')) && p('0:0') && e('1'); // 测试story类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('productplan', 1, '', '')) && p('0,1,2') && e('1,0,0'); // 测试productplan类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('branch', 1, '', '')) && p('0,1,2') && e('1,0,0'); // 测试branch类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('testcase', 1, 'linked2testtask', '1')) && p('0:0;1;2', ';') && e('1;1;6'); // 测试testcase类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('case', 1, 'run', '1')) && p('0:0;1;2', ';') && e('1;1;6'); // 测试case类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('task', 1, '', '')) && p('0:0;1;2', ';') && e('1;1;6'); // 测试task类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('release', 1, '', '')) && p('0:0;1', ';') && e('1;1'); // 测试release类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('unknown', 999, '', '')) && p('0:0;1;2', ';') && e('0;0;0'); // 测试不存在的对象类型的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('testcase', 1, 'linked2testtask', '1')) && p('0:0,1,2') && e('1,~~,~~'); // 测试testcase类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('case', 1, 'run', '1')) && p('0:0,1,2') && e('1,~~,~~'); // 测试case类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('task', 1, '', '')) && p('0:0,1,2') && e('~~,1,~~'); // 测试task类型对象的相关字段获取
r($actionTest->getNeedRelatedFieldsTest('release', 1, '', '')) && p('0:0,1') && e('1,~~'); // 测试release类型对象的相关字段获取
@@ -17,16 +17,16 @@ cid=14957
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/action.unittest.class.php';
zenData('project')->gen(5);
zenData('product')->gen(5);
zenData('doc')->gen(5);
$actionTest = new actionTest();
global $tester;
$tester->loadModel('action');
r($actionTest->getUndeleteParamsByObjectType('project')) && p('0') && e('`zt_project`'); // 查看通过项目获取的字段
r($actionTest->getUndeleteParamsByObjectType('project')) && p('3') && e('id'); // 查看通过项目获取的字段
r($actionTest->getUndeleteParamsByObjectType('product')) && p('2', '|') && e('id, name, code, acl'); // 查看通过产品获取的字段
r($actionTest->getUndeleteParamsByObjectType('product')) && p('3') && e('id'); // 查看通过产品获取的字段
r($actionTest->getUndeleteParamsByObjectType('doc')) && p('1') && e('version desc'); // 查看通过文档获取的字段
r($tester->action->getUndeleteParamsByObjectType('project')) && p('0') && e('`zt_project`'); // 查看通过项目获取的字段
r($tester->action->getUndeleteParamsByObjectType('project')) && p('3') && e('id'); // 查看通过项目获取的字段
r($tester->action->getUndeleteParamsByObjectType('product')) && p('2', '|') && e('id, name, code, acl'); // 查看通过产品获取的字段
r($tester->action->getUndeleteParamsByObjectType('product')) && p('3') && e('id'); // 查看通过产品获取的字段
r($tester->action->getUndeleteParamsByObjectType('doc')) && p('1') && e('version desc'); // 查看通过文档获取的字段
+3 -1
View File
@@ -35,8 +35,10 @@ $trash3 = (object)array('objectType' => 'task', 'objectID' => 1, 'project' => 1
$trash4 = (object)array('objectType' => 'story', 'objectID' => 2, 'project' => 0, 'execution' => 0, 'objectName' => '测试需求');
$trash5 = (object)array('objectType' => 'task', 'objectID' => 3, 'project' => 0, 'execution' => 1, 'objectName' => '测试任务2');
global $config;
$config->requestType = 'PATH_INFO';
r($actionTest->processTrashTest($trash1, array(), array(), array())) && p('objectName') && e('中文名称'); // 步骤1:pivot类型JSON名称处理
r($actionTest->processTrashTest($trash2, array(), array(), array())) && p('objectName') && e("<a href='bug-view-1.html' title='测试Bug' >测试Bug</a>"); // 步骤2:普通对象名称处理(包含HTML链接)
r($actionTest->processTrashTest($trash3, array(1 => (object)array('name' => '测试项目', 'deleted' => 0)), array(), array())) && p('project') && e('~~'); // 步骤3:项目信息为空(ID不匹配)
r($actionTest->processTrashTest($trash4, array(), array(2 => (object)array('productTitle' => '测试产品', 'productDeleted' => 0)), array())) && p('product') && e('测试项目'); // 步骤4:属性累积现象验证
r($actionTest->processTrashTest($trash5, array(), array(), array(1 => (object)array('name' => '测试执行', 'deleted' => 0)))) && p('execution') && e('测试产品'); // 步骤5:属性累积现象验证
r($actionTest->processTrashTest($trash5, array(), array(), array(1 => (object)array('name' => '测试执行', 'deleted' => 0)))) && p('execution') && e('测试产品'); // 步骤5:属性累积现象验证
+1 -1
View File
@@ -93,7 +93,7 @@ class actionZen extends action
else
{
$module = $trash->objectType == 'case' ? 'testcase' : $trash->objectType;
$module = $trash->objectType == 'doctemplate' ? 'doc' : $trash->objectType;
$module = $trash->objectType == 'doctemplate' ? 'doc' : $module;
$params = $trash->objectType == 'user' ? "account={$trash->objectName}" : "id={$trash->objectID}";
$methodName = 'view';
if($module == 'basicmeas')
+2 -4
View File
@@ -15,14 +15,12 @@ cid=14978
- 查看生成的日期使用情况
- 属性year @0
- 属性month @10
- 属性day @1
- 属性hour @0
- 属性minute @0
- 属性secound @0
- 查看生成的日期使用情况
- 属性year @0
- 属性month @1
- 属性day @30
- 属性hour @0
- 属性minute @0
- 属性secound @0
@@ -33,7 +31,7 @@ global $tester;
$tester->loadModel('admin');
$result = $tester->admin->genDateUsed('2025-01-01');
r($result) && p('year,month,day,hour,minute,secound') && e('0,10,1,0,0,0'); // 查看生成的日期使用情况
r($result) && p('year,month,hour,minute,secound') && e('0,10,0,0,0'); // 查看生成的日期使用情况
$result = $tester->admin->genDateUsed('2026-01-01');
r($result) && p('year,month,day,hour,minute,secound') && e('0,1,30,0,0,0'); // 查看生成的日期使用情况
r($result) && p('year,month,hour,minute,secound') && e('0,1,0,0,0'); // 查看生成的日期使用情况
+2 -1
View File
@@ -130,7 +130,7 @@ $config->ai->targetForm['execution']['batchcreatetask'] = (object)array('m' =>
// $config->ai->targetForm['execution']['createrisk'] = (object)array('m' => 'execution', 'f' => 'createRisk');
// $config->ai->targetForm['execution']['createissue'] = (object)array('m' => 'execution', 'f' => 'createIssue');
$config->ai->targetForm['task']['edit'] = (object)array('m' => 'task', 'f' => 'edit', 'for' => 'task');
$config->ai->targetForm['task']['batchcreate'] = (object)array('m' => 'task', 'f' => 'batchcreate', 'for' => 'execution,task');
$config->ai->targetForm['task']['batchcreate'] = (object)array('m' => 'task', 'f' => 'batchcreate', 'for' => 'task');
$config->ai->targetForm['testcase']['edit'] = (object)array('m' => 'testcase', 'f' => 'edit', 'for' => 'case');
// $config->ai->targetForm['testcase']['createscript'] = (object)array('m' => 'testcase', 'f' => 'createScript');
$config->ai->targetForm['bug']['edit'] = (object)array('m' => 'bug', 'f' => 'edit', 'for' => 'bug');
@@ -163,6 +163,7 @@ $config->ai->targetFormVars['story']['batchcreate'] = (object)array('for
$config->ai->targetFormVars['story']['subdivide'] = (object)array('format' => 'productID=%d&branch=&moduleID=0&storyID=%d', 'args' => array('product' => 1, 'story' => 0), 'app' => 'product');
$config->ai->targetFormVars['story']['change'] = (object)array('format' => 'storyID=%d', 'args' => array('story' => 1), 'app' => 'product');
$config->ai->targetFormVars['story']['totask'] = (object)array('format' => 'executionID=%d&storyID=%d', 'args' => array('execution' => 1, 'story' => 0), 'app' => 'execution');
$config->ai->targetFormVars['story']['testcasecreate'] = (object)array('format' => 'productID=%d&branch=&moduleID=%d&from=&param=&storyID=%d', 'args' => array('product' => 1, 'module' => 0, 'story' => 0), 'app' => 'product');
$config->ai->targetFormVars['productplan']['create'] = (object)array('format' => 'productID=%d&branch=%d&parent=%d', 'args' => array('product' => 1, 'branch' => 0, 'productplan' => 0), 'app' => 'product');
$config->ai->targetFormVars['productplan']['edit'] = (object)array('format' => 'planID=%d', 'args' => array('productplan' => 1), 'app' => 'product');
$config->ai->targetFormVars['task']['create'] = (object)array('format' => 'executionID=%d&storyID=%d', 'args' => array('execution' => 1, 'story' => 0), 'app' => 'execution');
+1
View File
@@ -621,6 +621,7 @@ class ai extends control
$response['objectID'] = $objectId;
$response['objectType'] = $prompt->module;
$response['knowledgeLib'] = $prompt->knowledgeLib;
$response['object'] = $objectData;
$response['formLocation'] = $location;
$response['model'] = $prompt->model;
+1
View File
@@ -21,6 +21,7 @@ $lang->prompt->source = 'Data Source';
$lang->prompt->targetForm = 'Target Form';
$lang->prompt->purpose = 'Purpose';
$lang->prompt->elaboration = 'Elaboration';
$lang->prompt->knowledgeLib = 'Knowledge Library';
$lang->prompt->role = 'Role';
$lang->prompt->characterization = 'Characterization';
$lang->prompt->status = 'Status';
+1
View File
@@ -21,6 +21,7 @@ $lang->prompt->source = 'Data Source';
$lang->prompt->targetForm = 'Target Form';
$lang->prompt->purpose = 'Purpose';
$lang->prompt->elaboration = 'Elaboration';
$lang->prompt->knowledgeLib = 'Knowledge Library';
$lang->prompt->role = 'Role';
$lang->prompt->characterization = 'Characterization';
$lang->prompt->status = 'Status';
+1
View File
@@ -21,6 +21,7 @@ $lang->prompt->source = 'Data Source';
$lang->prompt->targetForm = 'Target Form';
$lang->prompt->purpose = 'Purpose';
$lang->prompt->elaboration = 'Elaboration';
$lang->prompt->knowledgeLib = 'Knowledge Library';
$lang->prompt->role = 'Role';
$lang->prompt->characterization = 'Characterization';
$lang->prompt->status = 'Status';
+1
View File
@@ -21,6 +21,7 @@ $lang->prompt->source = '对象数据';
$lang->prompt->targetForm = '目标表单';
$lang->prompt->purpose = '操作';
$lang->prompt->elaboration = '补充要求';
$lang->prompt->knowledgeLib = '知识库';
$lang->prompt->role = '角色';
$lang->prompt->characterization = '角色描述';
$lang->prompt->status = '阶段';
+4 -3
View File
@@ -2126,12 +2126,13 @@ class aiModel extends model
$dataPrompt = $this->serializeDataToPrompt($prompt->module, $prompt->source, $objectData);
if(empty($dataPrompt)) return -3;
$wholePrompt = static::assemblePrompt($prompt, '');
$schema = $this->getFunctionCallSchema($prompt->targetForm);
$role = static::tryPunctuate($prompt->role);
$role .= static::autoPrependNewline(static::tryPunctuate($prompt->characterization, true));
$schema = $this->getFunctionCallSchema($prompt->targetForm);
if(empty($schema)) return -5;
$this->useLanguageModel($prompt->model);
return array('prompt' => $wholePrompt, 'schema' => $schema, 'dataPrompt' => $dataPrompt, 'name' => $prompt->name, 'purpose' => $prompt->purpose, 'status' => $prompt->status, 'targetForm' => $prompt->targetForm, 'promptID' => $prompt->id);
return array('role' => $role, 'schema' => $schema, 'dataPrompt' => $dataPrompt, 'name' => $prompt->name, 'purpose' => $prompt->purpose, 'status' => $prompt->status, 'targetForm' => $prompt->targetForm, 'promptID' => $prompt->id);
}
/**
+13 -1
View File
@@ -41,7 +41,6 @@ $promptMenuInject = function()
$prompts = $this->ai->filterPromptsForExecution($prompts, true);
$btnName = sprintf($this->lang->ai->promptMenu->dropdownTitle, isset($this->lang->ai->dataSource[$module]['common']) ? $this->lang->ai->dataSource[$module]['common'] : '');
if($isDocApp)
{
h::globalJS
@@ -52,6 +51,19 @@ $promptMenuInject = function()
return;
}
if($module === 'productplan' && $method === 'view')
{
/* 子计划不显示“拆分子计划智能体” */
$plan = data('plan');
if(is_object($plan) && $plan->parent > 0)
{
$prompts = array_filter($prompts, function($prompt)
{
return $prompt->module !== 'productplan' || $prompt->targetForm !== 'productplan.create';
});
}
}
if(empty($prompts)) return;
$html = '';
@@ -3250,7 +3250,7 @@ class blockTest
* @access public
* @return object
*/
public function printProjectDynamicBlockTest(object $block = null)
public function printProjectDynamicBlockTest(?object $block = null)
{
global $tester;
@@ -5647,7 +5647,7 @@ class blockTest
* @access public
* @return object
*/
public function organizaExternalDataTest(object $block = null)
public function organizaExternalDataTest(?object $block = null)
{
global $tester;
global $app;
-2
View File
@@ -652,9 +652,7 @@ class blockZenTest extends baseTest
*/
public function printScrumTestBlockTest(object $block)
{
ob_start();
$this->invokeArgs('printScrumTestBlock', array($block));
ob_end_clean();
if(dao::isError()) return dao::getError();
$view = $this->instance->view;
+1 -1
View File
@@ -101,4 +101,4 @@ r($blockTest->printCaseBlockTest($block2)) && p('count') && e('3');
su('admin');
r($blockTest->printCaseBlockTest($block3)) && p('count') && e('5');
su('user1');
r($blockTest->printCaseBlockTest($block4)) && p('count') && e('2');
r($blockTest->printCaseBlockTest($block4)) && p('count') && e('2');
@@ -11,6 +11,7 @@ cid=15289
- 步骤2:验证项目对象存在属性hasProject @1
- 步骤3:验证故事点统计默认值属性storyPoints @0
- 步骤4:验证任务数统计默认值属性tasks @0
- 步骤5:切换到不同项目ID属性projectID @12
*/
@@ -88,4 +89,5 @@ r($blockTest->printScrumOverviewBlockTest()) && p('projectID') && e('11'); //
r($blockTest->printScrumOverviewBlockTest()) && p('hasProject') && e('1'); // 步骤2:验证项目对象存在
r($blockTest->printScrumOverviewBlockTest()) && p('storyPoints') && e('0'); // 步骤3:验证故事点统计默认值
r($blockTest->printScrumOverviewBlockTest()) && p('tasks') && e('0'); // 步骤4:验证任务数统计默认值
$app->session->set('project', 12); r($blockTest->printScrumOverviewBlockTest()) && p('projectID') && e('12'); // 步骤5:切换到不同项目ID
$app->session->set('project', 12);
r($blockTest->printScrumOverviewBlockTest()) && p('projectID') && e('12'); // 步骤5:切换到不同项目ID
+28 -14
View File
@@ -7,23 +7,34 @@ title=测试 blockZen::printScrumTestBlock();
timeout=0
cid=15301
- 执行blockTest模块的printScrumTestBlockTest方法,参数type=all 属性blockType @all
- 执行blockTest模块的printScrumTestBlockTest方法,参数type=wait 属性testtaskCount @3
- 执行blockTest模块的printScrumTestBlockTest方法,参数type=doing 属性testtaskCount @5
- 执行blockTest模块的printScrumTestBlockTest方法,参数type=done 属性testtaskCount @7
- 执行blockTest模块的printScrumTestBlockTest方法,参数count=10 属性blockCount @10
- 测试type=all属性count @0
- 测试type=wait属性count @1
- 测试type=doing属性count @0
- 测试type=done属性count @0
- 测试count限制属性count @0
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/block.unittest.class.php';
include dirname(__FILE__, 2) . '/lib/zen.class.php';
$testtask = zenData('testtask');
$testtask->id->range('1-5');
$testtask->name->range('测试单`1-5`');
$testtask->status->range('wait{3},doing{2}');
$testtask->deleted->range('0');
$testtask->gen(5);
zenData('product')->loadYaml('product')->gen(3);
zenData('project')->loadYaml('project')->gen(5);
zenData('user')->loadYaml('user')->gen(5);
su('admin');
global $tester;
$tester->session->set('project', 1);
global $tester, $app;
$tester->session->set('project', 11);
$blockTest = new blockTest();
$blockTest = new blockZenTest();
// 测试参数 type=all
$block1 = new stdClass();
@@ -60,8 +71,11 @@ $block5->params->type = 'all';
$block5->params->orderBy = 'id_desc';
$block5->params->count = 10;
r($blockTest->printScrumTestBlockTest($block1)) && p('blockType') && e('all'); // 测试type=all
r($blockTest->printScrumTestBlockTest($block2)) && p('testtaskCount') && e('3'); // 测试type=wait
r($blockTest->printScrumTestBlockTest($block3)) && p('testtaskCount') && e('5'); // 测试type=doing
r($blockTest->printScrumTestBlockTest($block4)) && p('testtaskCount') && e('7'); // 测试type=done
r($blockTest->printScrumTestBlockTest($block5)) && p('blockCount') && e('10'); // 测试count限制
$app->rawModule = 'block';
$app->rawMethod = 'dashboard';
r($blockTest->printScrumTestBlockTest($block1)) && p('count') && e('0'); // 测试type=all
r($blockTest->printScrumTestBlockTest($block2)) && p('count') && e('1'); // 测试type=wait
r($blockTest->printScrumTestBlockTest($block3)) && p('count') && e('0'); // 测试type=doing
r($blockTest->printScrumTestBlockTest($block4)) && p('count') && e('0'); // 测试type=done
r($blockTest->printScrumTestBlockTest($block5)) && p('count') && e('0'); // 测试count限制
+16 -12
View File
@@ -7,20 +7,21 @@ title=测试 blockZen::printTesttaskBlock();
timeout=0
cid=15300
- 执行blockTest模块的printTesttaskBlockTest方法,参数type=all 属性hasValidation @1
- 执行blockTest模块的printTesttaskBlockTest方法,参数type=wait 属性type @wait
- 执行blockTest模块的printTesttaskBlockTest方法,参数type=doing 属性type @doing
- 执行blockTest模块的printTesttaskBlockTest方法,参数type=done 属性type @done
- 执行blockTest模块的printTesttaskBlockTest方法,参数count=3 属性count @3
- 测试type=all属性type @all
- 测试type=wait属性type @wait
- 测试type=doing属性type @doing
- 测试type=done属性type @done
- 测试count限制属性count @3
*/
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/block.unittest.class.php';
include dirname(__FILE__, 2) . '/lib/zen.class.php';
su('admin');
$blockTest = new blockTest();
global $tester, $app;
$blockTest = new blockZenTest();
// 测试参数 type=all
$block1 = new stdClass();
@@ -57,8 +58,11 @@ $block5->params->type = 'all';
$block5->params->orderBy = 'id_desc';
$block5->params->count = 3;
r($blockTest->printTesttaskBlockTest($block1)) && p('hasValidation') && e('1'); // 测试type=all, 验证参数有效性
r($blockTest->printTesttaskBlockTest($block2)) && p('type') && e('wait'); // 测试type=wait
r($blockTest->printTesttaskBlockTest($block3)) && p('type') && e('doing'); // 测试type=doing
r($blockTest->printTesttaskBlockTest($block4)) && p('type') && e('done'); // 测试type=done
r($blockTest->printTesttaskBlockTest($block5)) && p('count') && e('3'); // 测试count限制
$app->rawModule = 'block';
$app->rawMethod = 'dashboard';
r($blockTest->printTesttaskBlockTest($block1)) && p('type') && e('all'); // 测试type=all
r($blockTest->printTesttaskBlockTest($block2)) && p('type') && e('wait'); // 测试type=wait
r($blockTest->printTesttaskBlockTest($block3)) && p('type') && e('doing'); // 测试type=doing
r($blockTest->printTesttaskBlockTest($block4)) && p('type') && e('done'); // 测试type=done
r($blockTest->printTesttaskBlockTest($block5)) && p('count') && e('3'); // 测试count限制
+22 -3
View File
@@ -75,6 +75,25 @@ if(($longBlock && $count > 5) || (!$longBlock && $count > 1))
);
}
$processProjectField = function($review, $projects) use($lang)
{
if($review->project && strpos($review->project, ',') !== false)
{
$projectIdList = explode(',', $review->project);
$review->project = '';
foreach($projectIdList as $projectID)
{
if(empty($projectID)) continue;
$review->project .= zget($projects, $projectID, '') . $lang->comma;
}
$review->project = trim($review->project, $lang->comma);
}
else
{
$review->project = zget($projects, $review->project, '');
}
};
$contents = array();
foreach($hasViewPriv as $type => $bool)
{
@@ -126,8 +145,9 @@ foreach($hasViewPriv as $type => $bool)
}
$review->type = $typeName;
if(isset($review->project) && $review->project == 0) $review->project = '';
if(isset($review->product) && $review->product == 0) $review->product = '';
if(isset($review->project) && empty($review->project)) $review->project = '';
if(isset($review->product) && empty($review->product)) $review->product = '';
$processProjectField($review, $projects);
}
$config->block->review->dtable->fieldList['status']['statusMap'] = $statusList;
}
@@ -139,7 +159,6 @@ foreach($hasViewPriv as $type => $bool)
if($type == 'meeting') $config->block->meeting->dtable->fieldList['dept']['map'] = $depts;
$config->block->review->dtable->fieldList['product']['map'] = $products;
$config->block->review->dtable->fieldList['project']['map'] = $projects;
$selected = key($hasViewPriv);
$contents[] = div
+1 -1
View File
@@ -11,7 +11,7 @@ namespace zin;
* @param string $code
* @return array
*/
function buildParamsRows(object $block = null, ?array $params = null, string $module = '', string $code = ''): array
function buildParamsRows(?object $block = null, ?array $params = null, string $module = '', string $code = ''): array
{
global $lang;
+1 -1
View File
@@ -2154,7 +2154,7 @@ class blockZen extends block
->beginIF($objectType == 'demand')->andWhere('t2.deleted')->eq(0)->fi()
->orderBy($orderBy)
->beginIF($limitCount)->limit($limitCount)->fi()
->fetchAll();
->fetchAll('id', false);
if($objectType == 'todo')
{
+9
View File
@@ -310,6 +310,8 @@ class bug extends control
public function edit(int $bugID, bool $comment = false)
{
$oldBug = $this->bug->getByID($bugID);
if(!$oldBug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
if(!empty($_POST))
{
$formData = form::data($this->config->bug->form->edit, $bugID);
@@ -352,6 +354,7 @@ class bug extends control
/* 获取更新前的 bug,并且检查所属执行的权限。*/
/* Get old bug and check privilege of the execution. */
$oldBug = $this->bug->getByID($bugID);
if(!$oldBug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
$this->bugZen->checkBugExecutionPriv($oldBug);
if(!empty($_POST))
@@ -404,6 +407,7 @@ class bug extends control
public function confirm(int $bugID, string $kanbanParams = '', string $from = '')
{
$oldBug = $this->bug->getByID($bugID);
if(!$oldBug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
/* 检查 bug 所属执行的权限。*/
/* Check privilege for execution of the bug. */
@@ -454,6 +458,7 @@ class bug extends control
/* 获取 bug 信息,并检查 bug 所属执行的权限。*/
/* Get bug info, and check privilege of bug 所属执行的权限。*/
$oldBug = $this->bug->getById($bugID);
if(!$oldBug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
$this->bugZen->checkBugExecutionPriv($oldBug);
if(!empty($_POST))
@@ -529,6 +534,7 @@ class bug extends control
/* 获取 bug 信息,并检查 bug 所属执行的权限。*/
/* Get bug info, and check privilege of bug 所属执行的权限。*/
$oldBug = $this->bug->getByID($bugID);
if(!$oldBug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
$this->bugZen->checkBugExecutionPriv($oldBug);
if(!empty($_POST))
@@ -586,6 +592,7 @@ class bug extends control
/* 获取 bug 信息,并检查 bug 所属执行的权限。*/
/* Get bug info, and check privilege of bug 所属执行的权限。*/
$oldBug = $this->bug->getByID($bugID);
if(!$oldBug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
$this->bugZen->checkBugExecutionPriv($oldBug);
if(!empty($_POST))
@@ -629,6 +636,8 @@ class bug extends control
/* 删除 bug。 */
/* Delete bug. */
$bug = $this->bug->getByID($bugID);
if(!$bug) return $this->send(array('result' => 'fail', 'message' => $this->lang->bug->error->notExist));
$this->bug->delete(TABLE_BUG, $bugID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
+1 -1
View File
@@ -2416,7 +2416,7 @@ class bugTest
* @access public
* @return mixed
*/
public function assignBatchCreateVarsTest(int $executionID = 0, object $product = null, string $branch = '', array $output = array(), array $bugImagesFile = array())
public function assignBatchCreateVarsTest(int $executionID = 0, ?object $product = null, string $branch = '', array $output = array(), array $bugImagesFile = array())
{
global $tester;
@@ -113,7 +113,7 @@ class confirmBugTester extends tester
* @access public
* @return object
*/
public function bugAssert(string $bugTitle = '', object $list = null)
public function bugAssert(string $bugTitle = '', ?object $list = null)
{
if(empty($bugTitle) || !is_object($list)) return $this->failed('获取bug标题失败');
+21 -5
View File
@@ -11,6 +11,22 @@ title=bugTao->processSearchQuery();
timeout=0
cid=15420
- 处理 bug 产品 1 分支 all 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5)
- 处理 bug 产品 2 分支 all 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5)
- 处理 bug 产品 1 分支 0 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'
- 处理 bug 产品 2 分支 0 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'
- 处理 story 产品 1 分支 all 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5)
- 处理 story 产品 2 分支 all 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5)
- 处理 story 产品 1 分支 0 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'
- 处理 story 产品 2 分支 0 的查询语句 @`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'
*/
$object = array('bug', 'story');
@@ -25,12 +41,12 @@ $bug = $tester->loadModel('bug');
r($bug->processSearchQuery($object[0], 0, array($productIdList[0]), $branch[0])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5)"); // 处理 bug 产品 1 分支 all 的查询语句
r($bug->processSearchQuery($object[0], 0, array($productIdList[1]), $branch[0])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5)"); // 处理 bug 产品 2 分支 all 的查询语句
r($bug->processSearchQuery($object[0], 0, array($productIdList[0]), $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` in('0','0')"); // 处理 bug 产品 1 分支 0 的查询语句
r($bug->processSearchQuery($object[0], 0, array($productIdList[1]), $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` in('0','0')"); // 处理 bug 产品 2 分支 0 的查询语句
r($bug->processSearchQuery($object[0], 0, array($productIdList[0]), $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'"); // 处理 bug 产品 1 分支 0 的查询语句
r($bug->processSearchQuery($object[0], 0, array($productIdList[1]), $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'"); // 处理 bug 产品 2 分支 0 的查询语句
r($bug->processSearchQuery($object[1], 0, $productIdList[0], $branch[0])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5)"); // 处理 story 产品 1 分支 all 的查询语句
r($bug->processSearchQuery($object[1], 0, $productIdList[1], $branch[0])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5)"); // 处理 story 产品 2 分支 all 的查询语句
r($bug->processSearchQuery($object[1], 0, $productIdList[0], $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` in('0','0')"); // 处理 story 产品 1 分支 0 的查询语句
r($bug->processSearchQuery($object[1], 0, $productIdList[1], $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` in('0','0')"); // 处理 story 产品 2 分支 0 的查询语句
r($bug->processSearchQuery($object[1], 0, $productIdList[0], $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'"); // 处理 story 产品 1 分支 0 的查询语句
r($bug->processSearchQuery($object[1], 0, $productIdList[1], $branch[1])) && p() && e("`product` != '0' AND `product` IN (1,2,3,4,5) AND `branch` = '0'"); // 处理 story 产品 2 分支 0 的查询语句
unset($_SESSION['bugQuery']);
unset($_SESSION['storyBugQuery']);
unset($_SESSION['storyBugQuery']);
+5 -5
View File
@@ -7,22 +7,22 @@ title=测试 bugZen::buildBrowseView();
timeout=0
cid=0
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs1, $product1, '0', 'all', 0, $executions, 0, 'id_desc', $pager
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs1, $product1, '0', 'all', 0, $executions, 0, 'id_desc', $pager
- 属性product @1
- 属性browseType @all
- 属性bugsCount @5
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs2, $product2, '1', 'bymodule', 1, $executions, 0, 'id_asc', $pager
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs2, $product2, '1', 'bymodule', 1, $executions, 0, 'id_asc', $pager
- 属性product @3
- 属性branch @1
- 属性currentModuleID @1
- 属性bugsCount @2
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs3, $product1, '0', 'assignedto', 0, $executions, 1, 'pri_desc', $pager
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs3, $product1, '0', 'assignedto', 0, $executions, 1, 'pri_desc', $pager
- 属性browseType @assignedto
- 属性param @1
- 属性stories @2
- 属性tasks @1
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs4, $product1, '0', 'all', 0, $executions, 0, 'id_desc', $pager 属性bugsCount @0
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs5, $product1, '0', 'resolved', 0, $executions, 0, 'status_desc', $pager
- 执行bugTest模块的buildBrowseViewTest方法,参数是$bugs5, $product1, '0', 'resolved', 0, $executions, 0, 'status_desc', $pager
- 属性browseType @resolved
- 属性orderBy @status_desc
- 属性bugsCount @3
@@ -68,4 +68,4 @@ r($bugTest->buildBrowseViewTest($bugs1, $product1, '0', 'all', 0, $executions, 0
r($bugTest->buildBrowseViewTest($bugs2, $product2, '1', 'bymodule', 1, $executions, 0, 'id_asc', $pager)) && p('product;branch;currentModuleID;bugsCount') && e('3,1,1,2');
r($bugTest->buildBrowseViewTest($bugs3, $product1, '0', 'assignedto', 0, $executions, 1, 'pri_desc', $pager)) && p('browseType;param;stories;tasks') && e('assignedto,1,2,1');
r($bugTest->buildBrowseViewTest($bugs4, $product1, '0', 'all', 0, $executions, 0, 'id_desc', $pager)) && p('bugsCount') && e('0');
r($bugTest->buildBrowseViewTest($bugs5, $product1, '0', 'resolved', 0, $executions, 0, 'status_desc', $pager)) && p('browseType;orderBy;bugsCount') && e('resolved,status_desc,3');
r($bugTest->buildBrowseViewTest($bugs5, $product1, '0', 'resolved', 0, $executions, 0, 'status_desc', $pager)) && p('browseType;orderBy;bugsCount') && e('resolved,status_desc,3');
+12 -11
View File
@@ -17,10 +17,10 @@ cid=15467
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData1, $oldBug1] 属性id @1
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData2, $oldBug2] @0
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData3, $oldBug3] 属性assignedTo @user1
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData4, $oldBug4]
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData4, $oldBug4]
- 属性status @resolved
- 属性confirmed @1
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData5, $oldBug5]
- 执行invokeArgs($zen模块的newInstance方法,参数是, [$formData5, $oldBug5]
- 属性status @closed
- 属性assignedTo @closed
@@ -33,7 +33,7 @@ $app->rawMethod = 'edit';
// 创建测试用的form对象
function createFormMock($assignedTo, $resolution = '', $resolvedBy = '', $closedBy = '', $closedDate = '') {
global $tester;
// 设置POST数据模拟表单提交
$_POST['assignedTo'] = $assignedTo;
$_POST['resolution'] = $resolution;
@@ -49,21 +49,22 @@ function createFormMock($assignedTo, $resolution = '', $resolvedBy = '', $closed
$_POST['pri'] = 3;
$_POST['severity'] = 3;
$_POST['steps'] = 'Test steps';
// 使用真实的form类创建对象
$formData = form::data($tester->config->bug->form->edit);
return $formData;
}
// 准备基础Bug数据
$baseBug = (object)array(
'id' => 1,
'product' => 1,
'assignedTo' => 'admin',
'status' => 'active',
'id' => 1,
'product' => 1,
'assignedTo' => 'admin',
'status' => 'active',
'lastEditedDate' => '2023-05-04 14:00:00',
'openedBy' => 'admin'
'openedBy' => 'admin',
'resolvedBy' => ''
);
$zen = initReference('bug');
@@ -99,4 +100,4 @@ r($func->invokeArgs($zen->newInstance(), [$formData4, $oldBug4])) && p('status,c
$_POST['lastEditedDate'] = '2023-05-04 14:00:00';
$formData5 = createFormMock('user1', 'fixed', 'admin', 'admin', '2023-05-10 10:00:00');
$oldBug5 = clone $baseBug;
r($func->invokeArgs($zen->newInstance(), [$formData5, $oldBug5])) && p('status,assignedTo') && e('closed,closed');
r($func->invokeArgs($zen->newInstance(), [$formData5, $oldBug5])) && p('status,assignedTo') && e('closed,closed');
+4 -2
View File
@@ -640,7 +640,7 @@ class bugZen extends bug
->setIF($formData->data->assignedTo != '', 'assignedDate', helper::now())
->setIF($formData->data->story !== false, 'storyVersion', $this->loadModel('story')->getVersion((int)$formData->data->story))
->setIF($this->post->project, 'project', $this->post->project)
->setIF($this->post->project, 'execution', $this->post->execution)
->setIF($this->post->execution, 'execution', $this->post->execution)
->get();
if($this->post->fromCase && $this->post->fromCase != $formData->data->case)
@@ -700,6 +700,8 @@ class bugZen extends bug
->stripTags($this->config->bug->editor->edit['id'], $this->config->allowedTags)
->get();
if($oldBug->resolvedBy == $bug->resolvedBy && !$this->post->resolvedDate) unset($bug->resolvedDate);
$bug = $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->edit['id'], $bug->uid);
return $bug;
@@ -1967,7 +1969,7 @@ class bugZen extends bug
$this->loadModel('kanban');
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'bug', (string)$bugID);
if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'bug');
if(empty($laneID) or empty($columnID)) $this->kanban->updateLane((int)$executionID, 'bug');
}
/* Callback the callable method to process the related data for object that is transfered to bug. */
@@ -55,7 +55,7 @@ class buildTest
* @access public
* @return array|int
*/
public function getProjectBuildsTest(int $count, int $projectID, string $type = 'all', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', object $pager = null): array|int
public function getProjectBuildsTest(int $count, int $projectID, string $type = 'all', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', ?object $pager = null): array|int
{
$objects = $this->objectModel->getProjectBuilds($projectID, $type, $param, $orderBy, $pager);
@@ -96,7 +96,7 @@ class buildTest
* @access public
* @return array|int
*/
public function getExecutionBuildsTest(int $count, int $executionID, string $type = '', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', object $pager = null): array|int
public function getExecutionBuildsTest(int $count, int $executionID, string $type = '', string $param = '', string $orderBy = 't1.date_desc,t1.id_desc', ?object $pager = null): array|int
{
$objects = $this->objectModel->getExecutionBuilds($executionID, $type, $param, $orderBy, $pager);
+12 -16
View File
@@ -7,16 +7,12 @@ title=测试 buildModel::batchUnlinkStory();
timeout=0
cid=15486
- 步骤1:正常批量移除多个需求
- 第1条的stories属性 @1
- 步骤2:移除单个需求
- 第2条的stories属性 @1
- 步骤3:传入空数组测试 @rue
- 步骤4:移除不存在的需求ID
- 第4条的stories属性 @1
- 步骤5:对不存在的版本ID操作 @alse
- 步骤6:重复移除已移除的需求
- 第5条的stories属性 @1
- 步骤1:正常批量移除多个需求第1条的stories属性 @1,3,5
- 步骤2:移除单个需求第2条的stories属性 @1,3,4,5
- 步骤3:传入空数组测试 @1
- 步骤4:移除不存在的需求ID第4条的stories属性 @1,2,3,4,5
- 步骤5:对不存在的版本ID操作 @0
- 步骤6:重复移除已移除的需求第5条的stories属性 @1,3,4,5
*/
@@ -41,9 +37,9 @@ su('admin');
$build = new buildTest();
r($build->batchUnlinkStoryTest(1, array('2', '4', '6'))) && p('1:stories') && e('1,3,5'); // 步骤1:正常批量移除多个需求
r($build->batchUnlinkStoryTest(2, array('2'))) && p('2:stories') && e('1,3,4,5'); // 步骤2:移除单个需求
r($build->batchUnlinkStoryTest(3, array())) && p() && e(true); // 步骤3:传入空数组测试
r($build->batchUnlinkStoryTest(4, array('999'))) && p('4:stories') && e('1,2,3,4,5'); // 步骤4:移除不存在的需求ID
r($build->batchUnlinkStoryTest(999, array('2'))) && p() && e(false); // 步骤5:对不存在的版本ID操作
r($build->batchUnlinkStoryTest(5, array('2', '2'))) && p('5:stories') && e('1,3,4,5'); // 步骤6:重复移除已移除的需求
r($build->batchUnlinkStoryTest(1, array('2', '4', '6'))) && p('1:stories', '|') && e('1,3,5'); // 步骤1:正常批量移除多个需求
r($build->batchUnlinkStoryTest(2, array('2'))) && p('2:stories', '|') && e('1,3,4,5'); // 步骤2:移除单个需求
r($build->batchUnlinkStoryTest(3, array())) && p() && e(1); // 步骤3:传入空数组测试
r($build->batchUnlinkStoryTest(4, array('999'))) && p('4:stories', '|') && e('1,2,3,4,5'); // 步骤4:移除不存在的需求ID
r($build->batchUnlinkStoryTest(999, array('2'))) && p() && e(0); // 步骤5:对不存在的版本ID操作
r($build->batchUnlinkStoryTest(5, array('2', '2'))) && p('5:stories', '|') && e('1,3,4,5'); // 步骤6:重复移除已移除的需求
+1 -1
View File
@@ -148,7 +148,7 @@ class caselib extends control
$libraries = $this->caselib->getLibraries();
if(empty($libraries))
{
if($from == 'doc')
if($from == 'doc' || $from == 'ai')
{
$this->app->loadLang('doc');
return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->tips->noCaselib));
+1 -1
View File
@@ -132,7 +132,7 @@ if($canExportTemplate)
toolbar
(
setClass(array('hidden' => $isFromDoc)),
setClass(array('hidden' => $isFromDoc || $isFromAI)),
$canView ? a
(
setClass('toolbar-item ghost btn btn-default'),
+5 -5
View File
@@ -97,7 +97,7 @@ class cneTest
* @access public
* @return array
*/
public function __constructTest(string $appName = '', bool $switchChannel = null): array
public function __constructTest(string $appName = '', ?bool $switchChannel = null): array
{
global $config, $app;
@@ -378,7 +378,7 @@ class cneTest
* @access public
* @return object|null
*/
public function startAppTest(object $apiParams = null): object|null
public function startAppTest(?object $apiParams = null): object|null
{
if($apiParams === null)
{
@@ -527,7 +527,7 @@ class cneTest
* @access public
* @return object|null
*/
public function stopAppTest(object $apiParams = null): object|null
public function stopAppTest(?object $apiParams = null): object|null
{
if($apiParams === null)
{
@@ -1364,7 +1364,7 @@ class cneTest
* @access public
* @return object
*/
public function uploadCertTest(object $cert = null, string $channel = ''): object
public function uploadCertTest(?object $cert = null, string $channel = ''): object
{
// 模拟uploadCert方法的行为,避免实际API调用
if($cert === null)
@@ -1871,7 +1871,7 @@ class cneTest
* @access public
* @return object|null
*/
public function installAppTest(object $apiParams = null): object|null
public function installAppTest(?object $apiParams = null): object|null
{
// 模拟测试,避免实际API调用
if($apiParams === null)
+1 -1
View File
@@ -1772,7 +1772,7 @@ eof;
if(!$user) $this->response('INVALID_ACCOUNT');
$this->loadModel('user');
$user->last = time();
$user->last = helper::now();
$user->rights = $this->user->authorize($user->account);
$user->groups = $this->user->getGroups($user->account);
$user->view = $this->user->grantUserView($user->account, $user->rights['acls']);
+10 -10
View File
@@ -7,11 +7,11 @@ title=测试 commonModel::buildMoreButton();
timeout=0
cid=15647
- 步骤1:不存在的executionID @
- 步骤2:无效ID为0 @
- 步骤3:负数ID @
- 步骤4:Tutorial模式或正常模式 @
- 步骤5:执行记录但同项目无其他执行 @
- 步骤1:不存在的executionID @0
- 步骤2:无效ID为0 @0
- 步骤3:负数ID @0
- 步骤4:Tutorial模式或正常模式 @0
- 步骤5:执行记录但同项目无其他执行 @0
*/
@@ -40,8 +40,8 @@ $commonTest = new commonTest();
$_SESSION['tutorialMode'] = true;
// 测试用例(5个测试步骤)
r($commonTest->buildMoreButtonTest(999, false)) && p() && e(''); // 步骤1:不存在的executionID
r($commonTest->buildMoreButtonTest(0, false)) && p() && e(''); // 步骤2:无效ID为0
r($commonTest->buildMoreButtonTest(-1, false)) && p() && e(''); // 步骤3:负数ID
r($commonTest->buildMoreButtonTest(1, false)) && p() && e(''); // 步骤4:Tutorial模式或正常模式
r($commonTest->buildMoreButtonTest(6, false)) && p() && e(''); // 步骤5:执行记录但同项目无其他执行
r($commonTest->buildMoreButtonTest(999, false)) && p() && e('0'); // 步骤1:不存在的executionID
r($commonTest->buildMoreButtonTest(0, false)) && p() && e('0'); // 步骤2:无效ID为0
r($commonTest->buildMoreButtonTest(-1, false)) && p() && e('0'); // 步骤3:负数ID
r($commonTest->buildMoreButtonTest(1, false)) && p() && e('0'); // 步骤4:Tutorial模式或正常模式
r($commonTest->buildMoreButtonTest(6, false)) && p() && e('0'); // 步骤5:执行记录但同项目无其他执行
+5 -5
View File
@@ -14,8 +14,8 @@ cid=15666
- 查看任务1和任务2的差异字段数量 @14
- 查看任务1和任务2的差异字段8
- 第8条的field属性 @estimate
- 第8条的old属性 @0
- 第8条的new属性 @1
- 第8条的old属性 @0.00
- 第8条的new属性 @1.00
- 查看任务1和任务2的差异字段12
- 第12条的field属性 @status
- 第12条的old属性 @wait
@@ -31,6 +31,6 @@ $task2 = $tester->task->fetchById(2);
$changes = common::createChanges($task1, $task2);
r(count($changes)) && p() && e('14'); // 查看任务1和任务2的差异字段数量
r($changes) && p('8:field,old,new') && e('estimate,0,1'); // 查看任务1和任务2的差异字段8
r($changes) && p('12:field,old,new') && e('status,wait,doing'); // 查看任务1和任务2的差异字段12
r(count($changes)) && p() && e('14'); // 查看任务1和任务2的差异字段数量
r($changes) && p('8:field,old,new') && e('estimate,0.00,1.00'); // 查看任务1和任务2的差异字段8
r($changes) && p('12:field,old,new') && e('status,wait,doing'); // 查看任务1和任务2的差异字段12
+9 -11
View File
@@ -8,13 +8,10 @@ title=测试 commonModel::setUserConfig();
timeout=0
cid=15715
- 没有登录的用户,账号和姓名都是guest
- 属性account @guest
- 属性realname @guest
- 登录admin账号,账号和姓名都是admin
- 属性account @admin
- 属性realname @admin
- 查看设置的公司名称 @易软天创网络科技有限公司
- 没有登录的用户,账号和姓名都是guest属性account @guest
- 没有登录的用户,账号和姓名都是guest属性realname @guest
- 登录admin账号,账号和姓名都是admin属性account @admin
- 登录admin账号,账号和姓名都是admin属性realname @admin
- 查看设置的公司ID @1
*/
@@ -23,10 +20,11 @@ global $tester;
$tester->loadModel('common')->setUserConfig();
global $app;
r($app->user) && p('account,realname') && e('guest,guest'); // 没有登录的用户,账号和姓名都是guest
r($app->user) && p('account') && e('guest'); // 没有登录的用户,账号和姓名都是guest
r($app->user) && p('realname') && e('guest'); // 没有登录的用户,账号和姓名都是guest
su('admin');
r($app->user) && p('account,realname') && e('admin,admin'); // 登录admin账号,账号和姓名都是admin
r($app->user) && p('account') && e('admin'); // 登录admin账号,账号和姓名都是admin
r($app->user) && p('realname') && e('admin'); // 登录admin账号,账号和姓名都是admin
r($app->company->name) && p('') && e('易软天创网络科技有限公司'); // 查看设置的公司名称
r($app->company->id) && p('') && e('1'); // 查看设置的公司ID
r($app->company->id) && p('') && e('1'); // 查看设置的公司ID
@@ -171,7 +171,7 @@ class compileTest
* Test buildSearchForm method.
*
* @param int $repoID
* @param int $jobID
* @param int $jobID
* @param int $queryID
* @access public
* @return array
@@ -179,10 +179,10 @@ class compileTest
public function buildSearchFormTest($repoID = 0, $jobID = 0, $queryID = 0)
{
global $tester;
// 模拟compile zen的buildSearchForm逻辑
$actionURL = "compile-browse-{$repoID}-{$jobID}-bySearch-myQueryID.html";
// 初始化config结构
if(!isset($tester->config->compile->search))
{
@@ -190,11 +190,11 @@ class compileTest
$tester->config->compile->search['fields'] = array();
$tester->config->compile->search['params'] = array();
}
// 设置默认的repo字段
$tester->config->compile->search['fields']['repo'] = '代码库';
$tester->config->compile->search['params']['repo'] = array('values' => array());
// 根据repoID或jobID参数决定是否移除repo字段
if($repoID || $jobID)
{
@@ -206,17 +206,17 @@ class compileTest
// 模拟从repo模块获取仓库对列表
$tester->config->compile->search['params']['repo']['values'] = array('1' => '仓库1', '2' => '仓库2');
}
$tester->config->compile->search['actionURL'] = $actionURL;
$tester->config->compile->search['queryID'] = $queryID;
if(dao::isError()) return dao::getError();
$result = array();
$result['actionURL'] = $tester->config->compile->search['actionURL'];
$result['queryID'] = $tester->config->compile->search['queryID'];
$result['hasRepoField'] = isset($tester->config->compile->search['fields']['repo']) ? '1' : '0';
return $result;
}
+16 -17
View File
@@ -8,25 +8,24 @@ title=测试 compileModel::createByJob();
timeout=0
cid=15743
- 执行compileTest模块的createByJobTest方法,参数是1, 'v1.0.0', 'tag'
- 属性name @Job1
- 根据ID为1的job生成compile
- 属性name @这是一个Job1
- 属性job @1
- 属性tag @v1.0.0
- 属性createdBy @admin
- 执行compileTest模块的createByJobTest方法,参数是2, 'abc123', 'commit'
- 属性name @Job2
- 根据ID为2的job生成compile
- 属性name @abc123
- 属性job @2
- 属性commit @abc123
- 属性createdBy @admin
- 执行compileTest模块的createByJobTest方法,参数是3, '', 'tag'
- 属性name @Job3
- 根据ID为3的job生成compile
- 属性name @这是一个Job3
- 属性job @3
- 属性tag @
- 执行compileTest模块的createByJobTest方法,参数是999, 'test', 'tag' @alse
- 执行compileTest模块的createByJobTest方法,参数是4, 'branch-dev', 'branch'
- 属性name @Job4
- 属性tag @~~
- 根据ID为999的job生成compile @0
- 根据ID为4的job生成compile
- 属性name @这是一个Job4
- 属性job @4
- 属性branch @branch-dev
- 属性branch @dev
*/
@@ -43,8 +42,8 @@ su('admin');
$compileTest = new compileTest();
r($compileTest->createByJobTest(1, 'v1.0.0', 'tag')) && p('name,job,tag,createdBy') && e('Job1,1,v1.0.0,admin');
r($compileTest->createByJobTest(2, 'abc123', 'commit')) && p('name,job,commit,createdBy') && e('Job2,2,abc123,admin');
r($compileTest->createByJobTest(3, '', 'tag')) && p('name,job,tag') && e('Job3,3,');
r($compileTest->createByJobTest(999, 'test', 'tag')) && p() && e(false);
r($compileTest->createByJobTest(4, 'branch-dev', 'branch')) && p('name,job,branch') && e('Job4,4,branch-dev');
r($compileTest->createByJobTest(1, 'v1.0.0', 'tag')) && p('name,job,tag,createdBy') && e('这是一个Job1,1,v1.0.0,admin'); // 根据ID为1的job生成compile
r($compileTest->createByJobTest(2, 'abc123', 'name')) && p('name,job,createdBy') && e('abc123,2,admin'); // 根据ID为2的job生成compile
r($compileTest->createByJobTest(3, '', 'tag')) && p('name,job,tag') && e('这是一个Job3,3,~~'); // 根据ID为3的job生成compile
r($compileTest->createByJobTest(999, 'test', 'tag')) && p() && e(0); // 根据ID为999的job生成compile
r($compileTest->createByJobTest(4, 'dev', 'branch')) && p('name,job,branch') && e('这是一个Job4,4,dev'); // 根据ID为4的job生成compile
+10 -10
View File
@@ -9,9 +9,9 @@ cid=15747
- 测试步骤1:正常jobID查询最新编译结果属性job @1
- 测试步骤2:查询有多条记录的jobID属性job @2
- 测试步骤3:查询不存在的jobID @alse
- 测试步骤4:查询无状态记录的jobID @alse
- 测试步骤5:边界值测试(jobID为0) @alse
- 测试步骤3:查询不存在的jobID @0
- 测试步骤4:查询无状态记录的jobID @0
- 测试步骤5:边界值测试(jobID为0) @0
*/
@@ -23,9 +23,9 @@ $compile->id->range('1-10');
$compile->name->range('Build1,Build2,Build3,Compile4,Deploy5,Test6,Release7,Fix8,Update9,Package10');
$compile->job->range('1{3},2{2},3{1},4{1},5{3}');
$compile->queue->range('1-10');
$compile->status->range('success,failure,running,,done,success,failure,running,done,pending');
$compile->status->range('success,failure,running,failure,done,success,``,running,done,pending');
$compile->createdBy->range('admin,user1,user2,admin,user1,user2,admin,user1,user2,admin');
$compile->createdDate->range('2024-01-01 10:00:00,2024-01-01 11:00:00,2024-01-01 12:00:00,2024-01-01 13:00:00,2024-01-01 14:00:00,2024-01-01 15:00:00,2024-01-01 16:00:00,2024-01-01 17:00:00,2024-01-01 18:00:00,2024-01-01 19:00:00');
$compile->createdDate->range('`2024-01-01 10:00:00`,`2024-01-01 11:00:00`,`2024-01-01 12:00:00`,`2024-01-01 13:00:00`,`2024-01-01 14:00:00`,`2024-01-01 15:00:00`,`2024-01-01 16:00:00`,`2024-01-01 17:00:00`,`2024-01-01 18:00:00`,`2024-01-01 19:00:00`');
$compile->deleted->range('0');
$compile->gen(10);
@@ -34,8 +34,8 @@ su('admin');
$compileTest = new compileTest();
r($compileTest->getLastResultTest(1)) && p('job') && e('1'); // 测试步骤1:正常jobID查询最新编译结果
r($compileTest->getLastResultTest(2)) && p('job') && e('2'); // 测试步骤2:查询有多条记录的jobID
r($compileTest->getLastResultTest(999)) && p() && e(false); // 测试步骤3:查询不存在的jobID
r($compileTest->getLastResultTest(4)) && p() && e(false); // 测试步骤4:查询无状态记录的jobID
r($compileTest->getLastResultTest(0)) && p() && e(false); // 测试步骤5:边界值测试(jobID为0)
r($compileTest->getLastResultTest(1)) && p('job') && e('1'); // 测试步骤1:正常jobID查询最新编译结果
r($compileTest->getLastResultTest(2)) && p('job') && e('2'); // 测试步骤2:查询有多条记录的jobID
r($compileTest->getLastResultTest(999)) && p() && e('0'); // 测试步骤3:查询不存在的jobID
r($compileTest->getLastResultTest(4)) && p() && e('0'); // 测试步骤4:查询无状态记录的jobID
r($compileTest->getLastResultTest(0)) && p() && e('0'); // 测试步骤5:边界值测试(jobID为0)
+2 -2
View File
@@ -65,8 +65,8 @@ class convertModel extends model
public function dbExists(string $dbName = ''): object|false
{
if(!$this->checkDBName($dbName)) return false;
return $this->dbh->execute('SHOW DATABASES like ?', array($dbName))->fetch();
$quotedDbName = $this->dbh->quote($dbName);
return $this->dbh->query("SHOW DATABASES like {$quotedDbName}")->fetch();
}
/**
@@ -3451,13 +3451,13 @@ class convertTest
public function getJiraAccount(string $userKey): string
{
if(empty($userKey)) return '';
// 模拟用户映射
$userMap = array(
'jira_user_key' => 'jira_user',
'reporter_key' => 'reporter_user'
);
return isset($userMap[$userKey]) ? $userMap[$userKey] : $userKey;
}
};
@@ -3552,7 +3552,7 @@ class convertTest
try {
// Start output buffering to capture any output
ob_start();
// Set up necessary session data for jira conversion
$originalJiraMethod = isset($this->objectTao->app->session->jiraMethod) ? $this->objectTao->app->session->jiraMethod : null;
$this->objectTao->app->session->jiraMethod = 'jira';
@@ -3560,26 +3560,26 @@ class convertTest
$reflection = new ReflectionClass($this->objectTao);
$method = $reflection->getMethod('createTask');
$method->setAccessible(true);
$result = $method->invoke($this->objectTao, $projectID, $executionID, $data, $relations);
// Clean output buffer
ob_end_clean();
// Restore original session data
if($originalJiraMethod !== null) {
$this->objectTao->app->session->jiraMethod = $originalJiraMethod;
} else {
unset($this->objectTao->app->session->jiraMethod);
}
if(dao::isError()) return dao::getError();
return $result;
} catch (Exception | Error $e) {
// Clean output buffer even on exception
if(ob_get_level()) ob_end_clean();
// Restore session data even on exception
if(isset($originalJiraMethod)) {
if($originalJiraMethod !== null) {
@@ -3762,25 +3762,25 @@ class convertTest
try {
// Mock the createRelease functionality instead of calling the real method
// This avoids dependency issues in testing environment
// Validate input parameters
if($build === null || $data === null) {
return 0;
}
// Basic validation mimicking the actual method logic
if(empty($build->id) || empty($build->product) || empty($build->project)) {
return 0;
}
// Mock the creation process
$status = 'normal';
if(empty($data->released)) $status = 'wait';
if(!empty($data->archived)) $status = 'terminate';
// Simulate successful creation
return 1;
} catch (Exception $e) {
return 0;
} catch (Error $e) {
@@ -3801,12 +3801,12 @@ class convertTest
public function createBuildinFieldTest($module, $resolutions, $priList, $buildin = false)
{
global $tester;
if(!isset($this->objectTao->workflowfield))
{
$this->objectTao->workflowfield = $this->createMockWorkflowField();
}
$reflection = new ReflectionClass($this->objectTao);
$method = $reflection->getMethod('createBuildinField');
$method->setAccessible(true);
@@ -3814,7 +3814,7 @@ class convertTest
if(dao::isError()) return dao::getError();
return $result;
}
private function createMockWorkflowField()
{
return new MockWorkflowField();
@@ -4099,10 +4099,10 @@ class convertTest
// 简化测试:由于createGroup方法依赖复杂的环境,我们直接验证参数处理逻辑
if(empty($name)) $name = '默认组名';
if(strlen($name) > 80) $name = substr($name, 0, 80);
$validTypes = array('project', 'product');
if(!in_array($type, $validTypes)) return 'invalid type';
// 验证参数类型
if(!is_array($objectList)) return 'invalid objectList';
if(!is_int($jiraProjectID)) return 'invalid jiraProjectID';
@@ -4110,7 +4110,7 @@ class convertTest
if(!is_array($productRelations)) return 'invalid productRelations';
if(!is_array($projectFieldList)) return 'invalid projectFieldList';
if(!is_array($archivedProject)) return 'invalid archivedProject';
// 模拟成功创建
return 'true';
}
@@ -4129,18 +4129,18 @@ class convertTest
public function createWorkflowGroupTest($relations = array(), $projectRelations = array(), $productRelations = array(), $edition = 'open', $existingGroups = array())
{
global $config;
// 模拟版本配置
$originalEdition = isset($config->edition) ? $config->edition : 'open';
$config->edition = $edition;
// 如果是开源版,直接返回原始relations
if($edition == 'open')
{
$config->edition = $originalEdition;
return serialize($relations);
}
// 模拟企业版逻辑
// 如果没有项目关系,返回原始relations
if(empty($projectRelations))
@@ -4148,20 +4148,20 @@ class convertTest
$config->edition = $originalEdition;
return serialize($relations);
}
// 模拟处理项目关系的逻辑
foreach($projectRelations as $jiraProjectID => $zentaoProjectID)
{
// 如果已存在工作流组关系则跳过
if(!empty($existingGroups[$jiraProjectID])) continue;
// 模拟创建工作流组的过程
// 实际方法会调用createGroup来创建project和product类型的工作流组
}
// 恢复原始配置
$config->edition = $originalEdition;
return serialize($relations);
}
@@ -4179,19 +4179,19 @@ class convertTest
{
return 0;
}
// 其他测试情况返回mock数组表示测试通过
if($testType == 'bug_resolution' || $testType == 'story_reason' || $testType == 'ticket_closed_reason')
{
// 模拟方法成功执行的情况
return 'array';
}
if($testType == 'invalid_key' || $testType == 'no_resolution')
{
return 0;
}
return 0;
}
@@ -4208,7 +4208,7 @@ class convertTest
$reflection = new ReflectionClass($this->objectTao);
$method = $reflection->getMethod('updateSubStory');
$method->setAccessible(true);
$result = $method->invoke($this->objectTao, $storyLink, $issueList);
if(dao::isError()) return dao::getError();
@@ -4228,7 +4228,7 @@ class convertTest
$reflection = new ReflectionClass($this->objectTao);
$method = $reflection->getMethod('updateSubTask');
$method->setAccessible(true);
$result = $method->invoke($this->objectTao, $taskLink, $issueList);
if(dao::isError()) return dao::getError();
@@ -4248,7 +4248,7 @@ class convertTest
$reflection = new ReflectionClass($this->objectTao);
$method = $reflection->getMethod('updateDuplicateStoryAndBug');
$method->setAccessible(true);
$result = $method->invoke($this->objectTao, $duplicateLink, $issueList);
if(dao::isError()) return dao::getError();
@@ -4268,7 +4268,7 @@ class convertTest
$reflection = new ReflectionClass($this->objectTao);
$method = $reflection->getMethod('updateRelatesObject');
$method->setAccessible(true);
$result = $method->invoke($this->objectTao, $relatesLink, $issueList);
if(dao::isError()) return dao::getError();
+10 -8
View File
@@ -21,15 +21,17 @@ cid=0
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/model.class.php';
zenData('workflowfield')->gen(0);
su('admin');
$convertTest = new convertModelTest();
r(count($convertTest->getZentaoFieldsTest('epic'))) && p() && e('6');
r(count($convertTest->getZentaoFieldsTest('story'))) && p() && e('6');
r(count($convertTest->getZentaoFieldsTest('bug'))) && p() && e('13');
r(count($convertTest->getZentaoFieldsTest('task'))) && p() && e('5');
r(count($convertTest->getZentaoFieldsTest('testcase'))) && p() && e('9');
r(count($convertTest->getZentaoFieldsTest('requirement'))) && p() && e('6');
r(count($convertTest->getZentaoFieldsTest('notexist'))) && p() && e('0');
r(count($convertTest->getZentaoFieldsTest(''))) && p() && e('0');
r(count($convertTest->getZentaoFieldsTest('epic'))) && p() && e('7');
r(count($convertTest->getZentaoFieldsTest('story'))) && p() && e('7');
r(count($convertTest->getZentaoFieldsTest('bug'))) && p() && e('14');
r(count($convertTest->getZentaoFieldsTest('task'))) && p() && e('6');
r(count($convertTest->getZentaoFieldsTest('testcase'))) && p() && e('10');
r(count($convertTest->getZentaoFieldsTest('requirement'))) && p() && e('7');
r(count($convertTest->getZentaoFieldsTest('notexist'))) && p() && e('1');
r(count($convertTest->getZentaoFieldsTest(''))) && p() && e('1');
@@ -45,15 +45,6 @@ try {
// 表可能已存在,忽略错误
}
// 3. 准备测试数据
$jiraRelationTable = zenData('jiratmprelation');
$jiraRelationTable->AType->range('jissue{3},jchangeitem{1}');
$jiraRelationTable->AID->range('1,2,3,1');
$jiraRelationTable->BType->range('zstory,ztask,zbug,zaction');
$jiraRelationTable->BID->range('1,2,3,101');
$jiraRelationTable->extra->range('issue,issue,issue,action');
$jiraRelationTable->gen(4);
$actionTable = zenData('action');
$actionTable->objectType->range('story,task,bug');
$actionTable->objectID->range('1-3');
@@ -183,4 +174,4 @@ $testData8 = array(
'newstring' => 'This is an English title'
)
);
r($convertTest->importJiraChangeItemTest($testData8)) && p() && e('true'); // 步骤9:导入多语言数据测试
r($convertTest->importJiraChangeItemTest($testData8)) && p() && e('true'); // 步骤9:导入多语言数据测试
@@ -45,4 +45,4 @@ r($convertTest->processJiraContentTest('This is a test !screenshot.png|thumbnail
r($convertTest->processJiraContentTest('Two images: !screenshot.png|thumb! and !image.jpg|width=100!', $fileList2)) && p() && e('Two images: <img src="{1.png}" alt="processjiracontent.php?m=file&f=read&t=png&fileID=1"/> and <img src="{2.jpg}" alt="processjiracontent.php?m=file&f=read&t=jpg&fileID=2"/>');
r($convertTest->processJiraContentTest('Missing file: !notfound.png|thumb!', $fileList3)) && p() && e('Missing file: !notfound.png|thumb!');
r($convertTest->processJiraContentTest('No image markers here', $fileList1)) && p() && e('0');
r($convertTest->processJiraContentTest('Image with options !screenshot.png|width=100,height=80! here', $fileList1)) && p() && e('Image with options <img src="{1.png}" alt="processjiracontent.php?m=file&f=read&t=png&fileID=1"/> here');
r($convertTest->processJiraContentTest('Image with options !screenshot.png|width=100,height=80! here', $fileList1)) && p() && e('Image with options <img src="{1.png}" alt="processjiracontent.php?m=file&f=read&t=png&fileID=1"/> here');
+3 -3
View File
@@ -45,15 +45,15 @@ $config->custom->fieldList['project']['create'] = 'budget,PM,desc';
$config->custom->fieldList['project']['edit'] = 'budget,PM,desc';
$config->custom->fieldList['product']['create'] = 'PO,QD,RD,type,desc';
$config->custom->fieldList['product']['edit'] = 'PO,QD,RD,type,desc,status';
$config->custom->fieldList['epic']['create'] = 'module,plan,source,pri,estimate,keywords,spec,verify';
$config->custom->fieldList['epic']['create'] = 'module,plan,source,pri,assignedTo,estimate,keywords,spec,verify,files';
$config->custom->fieldList['epic']['change'] = 'comment,spec,verify';
$config->custom->fieldList['epic']['close'] = 'comment';
$config->custom->fieldList['epic']['review'] = 'reviewedDate,comment';
$config->custom->fieldList['story']['create'] = 'module,plan,source,pri,estimate,keywords,spec,verify';
$config->custom->fieldList['story']['create'] = 'module,plan,source,pri,assignedTo,estimate,keywords,spec,verify,files';
$config->custom->fieldList['story']['change'] = 'comment,spec,verify';
$config->custom->fieldList['story']['close'] = 'comment';
$config->custom->fieldList['story']['review'] = 'reviewedDate,comment';
$config->custom->fieldList['requirement']['create'] = 'module,plan,source,pri,estimate,keywords,spec,verify';
$config->custom->fieldList['requirement']['create'] = 'module,plan,source,pri,assignedTo,estimate,keywords,spec,verify,files';
$config->custom->fieldList['requirement']['change'] = 'comment,spec,verify';
$config->custom->fieldList['requirement']['close'] = 'comment';
$config->custom->fieldList['requirement']['review'] = 'reviewedDate,comment';
+5 -2
View File
@@ -525,10 +525,13 @@ class customModel extends model
*/
public static function mergeFeatureBar(string $module, string $method): void
{
global $lang, $app;
global $lang, $app, $config;
if(!isset($lang->$module->featureBar[$method])) return;
$queryModule = ($module == 'execution' && $method == 'task') ? 'task' : ($module == 'product' ? 'story' : $module);
$queryModule = $module == 'execution' && $method == 'story' ? 'executionStory' : $queryModule;
if(isset($config->$module->queryModule[$method])) $queryModule = $config->$module->queryModule[$method];
if($module == 'execution' && $method == 'story') $queryModule = 'executionStory';
$shortcuts = $app->dbQuery('select id, title from ' . TABLE_USERQUERY . " where (`account` = '{$app->user->account}' or `common` = '1') AND `module` = '{$queryModule}' AND `shortcut` = '1' order by id")->fetchAll();
if($shortcuts)
@@ -8,11 +8,10 @@ timeout=0
cid=15894
- 测试步骤1:全生命周期管理模式 @0
- 测试步骤2:轻量级管理模式 @productER,waterfall,waterfallplus,scrumMeasrecord,agileplusMeasrecord,productTrack,productRoadmap
- 测试步骤2:轻量级管理模式 @1
- 测试步骤3:无效模式参数 @0
- 测试步骤4:空字符串模式参数 @0
- 测试步骤5:验证URAndSR和enableER配置 @productER,waterfall,waterfallplus,scrumMeasrecord,agileplusMeasrecord,productTrack,productRoadmap|1|0
- 测试步骤5:验证URAndSR和enableER配置 @1
*/
@@ -40,7 +39,9 @@ su('admin');
$customTester = new customTest();
r($customTester->disableFeaturesByModeTest('ALM')) && p() && e('0'); // 测试步骤1:全生命周期管理模式
r($customTester->disableFeaturesByModeTest('light')) && p() && e('productER,waterfall,waterfallplus,scrumMeasrecord,agileplusMeasrecord,productTrack,productRoadmap'); // 测试步骤2:轻量级管理模式
$light = $customTester->disableFeaturesByModeTest('light');
r(strpos($light, 'productTrack') !== false) && p() && e('1'); // 测试步骤2:轻量级管理模式
r($customTester->disableFeaturesByModeTest('invalid')) && p() && e('0'); // 测试步骤3:无效模式参数
r($customTester->disableFeaturesByModeTest('')) && p() && e('0'); // 测试步骤4:空字符串模式参数
r($customTester->disableFeaturesByModeTestWithURAndSR('light')) && p() && e('productER,waterfall,waterfallplus,scrumMeasrecord,agileplusMeasrecord,productTrack,productRoadmap|1|0'); // 测试步骤5:验证URAndSR和enableER配置
$light = $customTester->disableFeaturesByModeTestWithURAndSR('light');
r(strpos($light, 'agileplusMeasrecord') !== false) && p() && e('1'); // 测试步骤5:验证URAndSR和enableER配置
@@ -10,10 +10,8 @@ cid=15901
- 测试步骤1:空配置对象 @0
- 测试步骤2:单个方法配置属性create @name
- 测试步骤3:需求配置方法属性edit @title
- 测试步骤4:执行配置方法
- 属性batchedit @name
- 测试步骤5:空格处理
- 属性create @name
- 测试步骤4:执行配置方法属性batchedit @name,code,begin,end
- 测试步骤5:空格处理属性create @name,type,status
*/
@@ -67,8 +65,8 @@ $complexConfig->batchcreate = new stdclass();
$complexConfig->batchcreate->requiredFields = 'name,type,pri,estimate,assignedTo,deadline';
$customTester = new customTest();
r($customTester->getRequiredFieldsTest($emptyConfig)) && p() && e('0'); // 测试步骤1:空配置对象
r($customTester->getRequiredFieldsTest($taskConfig)) && p('create') && e('name'); // 测试步骤2:单个方法配置
r($customTester->getRequiredFieldsTest($storyConfig)) && p('edit') && e('title'); // 测试步骤3:需求配置方法
r($customTester->getRequiredFieldsTest($executionConfig)) && p('batchedit') && e('name,code,begin,end'); // 测试步骤4:执行配置方法
r($customTester->getRequiredFieldsTest($spaceConfig)) && p('create') && e('name,type,status'); // 测试步骤5:空格处理
r($customTester->getRequiredFieldsTest($emptyConfig)) && p() && e('0'); // 测试步骤1:空配置对象
r($customTester->getRequiredFieldsTest($taskConfig)) && p('create', ';') && e('name'); // 测试步骤2:单个方法配置
r($customTester->getRequiredFieldsTest($storyConfig)) && p('edit', ';') && e('title'); // 测试步骤3:需求配置方法
r($customTester->getRequiredFieldsTest($executionConfig)) && p('batchedit', ';') && e('name,code,begin,end'); // 测试步骤4:执行配置方法
r($customTester->getRequiredFieldsTest($spaceConfig)) && p('create', ';') && e('name,type,status'); // 测试步骤5:空格处理
+1 -3
View File
@@ -29,8 +29,6 @@ $table->parent->range('0{5},1{5}');
$table->path->range(',1,,1,2,,1,3,,1,4,,1,5,,6,,7,,8,,9,,10,');
$table->grade->range('1{5},2{5}');
$table->order->range('1-10:1');
$table->position->range('总经理,人事经理,财务经理,研发经理,测试经理,销售经理,市场经理,客服经理,运维经理,产品经理');
$table->function->range('公司管理,人员管理,财务管理,产品研发,质量保证,销售业务,市场推广,客户服务,系统运维,产品规划');
$table->manager->range('admin,manager1,manager2,manager3,manager4,manager5,manager6,manager7,manager8,manager9');
$table->gen(10);
@@ -52,4 +50,4 @@ $table = zenData('dept');
$table->gen(0);
r($deptTest->getDeptPairsTest('empty')) && p() && e('1'); // 步骤6:空数据库情况
r($deptTest->getDeptPairsTest('count')) && p() && e('0'); // 步骤7:空数据库数量验证
r($deptTest->getDeptPairsTest('count')) && p() && e('0'); // 步骤7:空数据库数量验证
+1 -3
View File
@@ -32,8 +32,6 @@ for($i = 1; $i <= 10; $i++)
$dept->path = $i <= 5 ? ",{$i}," : ",1,{$i},";
$dept->grade = $i <= 5 ? 1 : 2;
$dept->order = $i;
$dept->position = '';
$dept->function = '';
$dept->manager = '';
$tester->dao->insert(TABLE_DEPT)->data($dept)->exec();
}
@@ -44,4 +42,4 @@ r($deptTest->updateOrderTest(array('3', '1', '5', '2', '4'))) && p() && e('1');
r($deptTest->updateOrderTest(array('6'))) && p() && e('1'); // 测试步骤2:单个部门排序更新
r($deptTest->updateOrderTest(array())) && p() && e('1'); // 测试步骤3:空数组输入测试
r($deptTest->updateOrderTest(array('999', '888'))) && p() && e('1'); // 测试步骤4:不存在部门ID测试
r($deptTest->updateOrderSimpleTest(array('7', '8', '9'))) && p() && e('1'); // 测试步骤5:验证排序结果正确性
r($deptTest->updateOrderSimpleTest(array('7', '8', '9'))) && p() && e('1'); // 测试步骤5:验证排序结果正确性
@@ -47,23 +47,10 @@ fields:
postfix: ""
loop: 0
format: ""
- field: position
note: 职位
prefix: ""
postfix: ""
loop: 0
format: ""
- field: function
note: 部门职能
range: '软件开发,产品规划,质量保证,系统运维,市场推广,人力资源,财务管理,行政管理,销售业务,客户服务'
prefix: ""
postfix: ""
loop: 0
format: ""
- field: manager
note: 部门经理
range: 'admin,user1,user2,user3,user4,user5,user6,user7,user8,user9'
prefix: ""
postfix: ""
loop: 0
format: ""
format: ""
@@ -25,12 +25,6 @@ fields:
- field: order
note: 排序
range: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
- field: position
note: 职位
range: ["", "", "", "", "", "", "", "", "", ""]
- field: function
note: 职能
range: ["", "", "", "", "", "", "", "", "", ""]
- field: manager
note: 负责人
range: ["admin", "", "", "", "", "", "", "", "", ""]
range: ["admin", "", "", "", "", "", "", "", "", ""]
@@ -47,24 +47,10 @@ fields:
postfix: ""
loop: 0
format: ""
- field: position
note: 职位
range: '总经理,人事经理,财务经理,研发经理,测试经理,销售经理,市场经理,客服经理,运维经理,产品经理,设计经理,法务经理,行政经理,IT经理,培训经理'
prefix: ""
postfix: ""
loop: 0
format: ""
- field: function
note: 部门职能
range: '公司管理,人员管理,财务管理,产品研发,质量保证,销售业务,市场推广,客户服务,系统运维,产品规划,UI设计,法律事务,行政管理,技术支持,员工培训'
prefix: ""
postfix: ""
loop: 0
format: ""
- field: manager
note: 部门经理
range: 'admin,manager1,manager2,manager3,manager4,manager5,manager6,manager7,manager8,manager9,manager10,manager11,manager12,manager13,manager14'
prefix: ""
postfix: ""
loop: 0
format: ""
format: ""
@@ -32,15 +32,7 @@ fields:
note: 显示顺序
range: 10-150:10
format: ""
- field: position
note: 岗位
range: 部长{5},主管{6},专员{4}
format: ""
- field: function
note: 部门职能
range: 技术开发,产品规划,运营管理,人力资源管理,财务管理,市场推广,客户服务,行政管理,研发创新,软件测试,UI设计,销售管理,法律事务,采购管理,质量管理
format: ""
- field: manager
note: 部门负责人
range: admin,user1,user2,user3,user4,user5,user6,user7,user8,user9,user10,user11,user12,user13,user14
format: ""
format: ""
+1 -1
View File
@@ -152,7 +152,7 @@ $config->doc->zentaoList['bug'] = array('key' => 'bug', 'name' => $lang->doc
$config->doc->zentaoList['more'] = array('key' => 'more', 'name' => $lang->doc->zentaoList['more'] . $lang->doc->list, 'icon' => 'ellipsis-v', 'subMenu' => array());
$config->doc->zentaoList['story']['subMenu'][] = array('key' => 'productStory', 'name' => $lang->doc->zentaoList['productStory'] . $lang->doc->list, 'icon' => 'lightbulb-alt', 'module' => 'product', 'method' => 'browse', 'params' => 'productID=0&branch=all&browseType=&param=0&storyType=story&orderBy=&recTotal=0&recPerPage=20&pageID=1&projectID=0&from=doc', 'priv' => 'productStory');
$config->doc->zentaoList['story']['subMenu'][] = array('key' => 'projectStory', 'name' => $lang->doc->zentaoList['projectStory'] . $lang->doc->list, 'icon' => 'project', 'module' => 'projectStory', 'method' => 'story', 'params' => 'projectID=0&productID=0&branch=&browseTyp=&param=0&storyType=story&orderBy=&recTotal=0&recPerPage=20&pageID=1&from=doc', 'priv' => 'projectStory');
$config->doc->zentaoList['story']['subMenu'][] = array('key' => 'projectStory', 'name' => $lang->doc->zentaoList['projectStory'] . $lang->doc->list, 'icon' => 'project', 'module' => 'projectStory', 'method' => 'story', 'params' => 'projectID=0&productID=0&branch=&browseTyp=&param=0&storyType=story&orderBy=id_desc&recTotal=0&recPerPage=20&pageID=1&from=doc', 'priv' => 'projectStory');
$config->doc->zentaoList['story']['subMenu'][] = array('key' => 'executionStory', 'name' => $lang->doc->zentaoList['executionStory'] . $lang->doc->list, 'icon' => 'run', 'module' => 'execution', 'method' => 'story', 'params' => 'executionID=0&storyType=story&orderBy=&type=all&param=0&recTotal=0&recPerPage=20&pageID=1&from=doc', 'priv' => 'executionStory');
$config->doc->zentaoList['story']['subMenu'][] = array('key' => 'planStory', 'name' => $lang->doc->zentaoList['planStory'] . $lang->doc->list, 'icon' => 'productplan', 'module' => 'productplan', 'method' => 'story', 'params' => 'productID=0&planID=0&blockID=0', 'priv' => 'productplanView');
+6 -4
View File
@@ -1629,11 +1629,12 @@ class doc extends control
$params = helper::safe64Decode($params);
parse_str($params, $params);
$this->view->params = $params;
$this->view->params = $params;
$this->view->objectType = $objectType;
$this->view->spaceList = $spaceList;
$this->view->typeList = $typeList;
$this->view->spaceList = $spaceList;
$this->view->typeList = $typeList;
$this->view->from = $from;
$this->display();
}
@@ -1997,7 +1998,7 @@ class doc extends control
$this->view->spaceType = $spaceType;
$this->view->space = $space;
$this->view->doc = $doc;
$this->view->spaces = $this->doc->getAllSubSpaces();
$this->view->spaces = $this->doc->getAllSubSpaces($this->app->tab != 'doc' ? $this->app->tab : 'all');
$this->view->libPairs = $libPairs;
$this->view->optionMenu = $chapterAndDocs;
$this->view->groups = $this->loadModel('group')->getPairs();
@@ -2255,6 +2256,7 @@ class doc extends control
}
if($isNotDocTab && in_array($type, array('product', 'project', 'execution')))
{
if($type == 'product' && $spaceID == 0) $spaceID = (int)$this->cookie->preProductID;
$this->doc->setMenuByType($type, $spaceID, $libID);
$objectKey = $type . 'ID';
$this->view->$objectKey = $spaceID;
+9 -9
View File
@@ -12,7 +12,7 @@ window.getSpaceType = function()
window.changeSpace = function()
{
const objectType = getSpaceType();
if(objectType) loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}`));
if(objectType) loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=&from=${pageFrom}`));
}
/**
@@ -28,7 +28,7 @@ window.reloadMineAndCustom = function()
const libID = $('.modal-body input[name=lib]').val();
const params = window.btoa('objectID=' + objectID + '&libID=' + libID);
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}`));
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}&from=${pageFrom}`));
}
/**
@@ -45,7 +45,7 @@ window.reloadProduct = function()
const libID = $('.modal-body input[name=lib]').val();
const params = window.btoa('docType=' + docType + '&objectID=' + objectID + '&libID=' + libID);
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}`));
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}&from=${pageFrom}`));
}
/**
@@ -63,7 +63,7 @@ window.reloadProject = function()
const libID = $('.modal-body input[name=lib]').val();
const params = window.btoa('docType=' + docType + '&objectID=' + objectID + '&executionID=' + executionID + '&libID=' + libID);
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}`));
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}&from=${pageFrom}`));
}
/**
@@ -77,7 +77,7 @@ window.reloadApiByApiType = function()
const objectType = getSpaceType();
const apiType = $('.modal-body input[name=apiType]').val();
const params = window.btoa('apiType=' + apiType);
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}`));
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}&from=${pageFrom}`));
}
/**
@@ -109,7 +109,7 @@ window.reloadApi = function()
params = window.btoa('apiType=' + apiType + '&libID=' + libID);
}
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}`));
loadModal($.createLink('doc', 'selectLibType', `objectType=${objectType}&params=${params}&from=${pageFrom}`));
}
/**
@@ -125,9 +125,9 @@ window.reloadApi = function()
*/
window.redirectParentWindow = function(link, from, spaceID, libID, moduleID)
{
if(from === 'ai' && sessionStorage.getItem('aiResult'))
if(from === 'ai' && localStorage.getItem('aiResult'))
{
const aiResult = JSON.parse(sessionStorage.getItem('aiResult'));
const aiResult = JSON.parse(localStorage.getItem('aiResult'));
zui.DocApp.storeNextCreatingDoc({
content: aiResult.content || '',
contentType: 'markdown',
@@ -135,7 +135,7 @@ window.redirectParentWindow = function(link, from, spaceID, libID, moduleID)
lib: Number(libID),
module: Number(moduleID),
});
sessionStorage.removeItem('aiResult');
localStorage.removeItem('aiResult');
}
openUrl(link, 'doc');
}
+115 -17
View File
@@ -1396,23 +1396,28 @@ class docModel extends model
* 获取所有子空间。
* Get all sub spaces.
*
* @param string $spaceType
* @access public
* @return array
*/
public function getAllSubSpaces()
public function getAllSubSpaces(string $spaceType = 'all')
{
$productList = $this->config->vision == 'rnd' ? $this->loadModel('product')->getPairs('nocode') : array();
$projectList = ($this->config->vision == 'rnd' || $this->config->vision == 'lite') ? $this->loadModel('project')->getPairsByProgram() : array();
$productList = $this->config->vision == 'rnd' && in_array($spaceType, array('all', 'product')) ? $this->loadModel('product')->getPairs('nocode') : array();
$projectList = ($this->config->vision == 'rnd' || $this->config->vision == 'lite') && in_array($spaceType, array('all', 'project', 'execution')) ? $this->loadModel('project')->getPairsByProgram() : array();
$spaceList = $this->dao->select('*')->from(TABLE_DOCLIB)
->where('deleted')->eq(0)
->andWhere('parent')->eq(0)
->andWhere('vision')->eq($this->config->vision)
->andWhere('type', true)->eq('custom')
->orWhere('(type')->eq('mine')->andWhere('addedBy')->eq($this->app->user->account)
->markRight(2)
->orderBy('type_desc')
->fetchAll('', false);
$spaceList = array();
if($spaceType == 'all')
{
$spaceList = $this->dao->select('*')->from(TABLE_DOCLIB)
->where('deleted')->eq(0)
->andWhere('parent')->eq(0)
->andWhere('vision')->eq($this->config->vision)
->andWhere('type', true)->eq('custom')
->orWhere('(type')->eq('mine')->andWhere('addedBy')->eq($this->app->user->account)
->markRight(2)
->orderBy('type_desc')
->fetchAll('', false);
}
$productPairs = $projectPairs = $spacePairs = array();
foreach($productList as $productID => $productName) $productPairs["product.{$productID}"] = $this->lang->doc->spaceList['product'] . '/' . $productName;
@@ -2586,11 +2591,9 @@ class docModel extends model
{
$project = $this->loadModel('project')->getByID($projectID);
$storyIdList = $issueIdList = $meetingIdList = $reviewIdList = $designIdList = $executionIdList = $taskIdList = $buildIdList = 0;
if($project && !$project->hasProduct)
{
$projectIDList = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->orWhere('project')->eq($projectID)->fetchPairs('id', 'id');
$storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in($projectIDList)->fetchPairs('story', 'story');
}
$projectIDList = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->orWhere('project')->eq($projectID)->fetchPairs('id', 'id');
$storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in($projectIDList)->fetchPairs('story', 'story');
if(in_array($this->config->edition, array('max', 'ipd')))
{
@@ -4835,4 +4838,99 @@ class docModel extends model
/* Record the time of upgrade doc template. */
$this->setting->setItem("system.doc.upgradeTime", helper::now());
}
/**
* 遍历文档的区块。
* For each doc block.
*
* 下面为一个遍历文档中所有附件,并获取所有附件 sourceId 例子:
*
* ```php
* // 定义遍历回调函数:
* $callback = function($block, $data, $level)
* {
* if(!empty($block['props']['sourceId'])) $data[] = $block['props']['sourceId'];
* return $data;
* };
*
* // 遍历文档,并获取最终的 sourceId 列表:
* $sourceIdList = static::forEachDocBlock($rawContent, $callback, array(), 'affine:attachment');
* ```
*
* @param array $rawContent 文档的区块内容
* @param callable $callback 回调函数,用于处理每个区块,参数包括区块内容、传递的数据、区块级别、区块索引
* @param mixed $data 用于在遍历过程中需要传递的数据,此参数可选,默认为 null
* @param string $flavours 区块的 flavour,例如 affine:attachment,可以用英文逗号匹配多个 flavour,如果为空则匹配所有 flavour
* @param string $types 区块的 type,包括页面(page)和块(block),默认仅匹配块(block),如果为空则匹配所有类型
* @param ?array $props 区块的 props,例如 array('type' => 'h1'),可以指定多个属性值
* @param int $level 区块的级别,用于递归遍历,如果要指定起始级别,则可以使用此参数指定,默认为 0
* @param int $index 区块的索引,用于递归遍历,如果要指定起始索引,则可以使用此参数指定,默认为 0
* @access public
* @return void
*/
public static function forEachDocBlock(array $rawContent, callable $callback, mixed $data = null, string $flavours = '', string $types = 'block', ?array $props = null, int $level = 0, int $index = 0): mixed
{
/* 如果内容是列表,则遍历列表。 */
if(array_is_list($rawContent))
{
foreach($rawContent as $idx => $block)
{
$data = static::forEachDocBlock($block, $callback, $data, $flavours, $types, $props, $level, $idx);
}
return $data;
}
/* 获取内容类型。 */
if(!isset($rawContent['type'])) return $data;
$type = $rawContent['type'];
/* 判断内容是否匹配类型。 */
$blockMatched = empty($types) || str_contains(',' . $types . ',', ',' . $type . ',');
/* 判断内容是否匹配 flavour。 */
if($blockMatched && !empty($flavours))
{
$flavour = empty($rawContent['flavour']) ? '' : $rawContent['flavour'];
$blockMatched = !empty($flavour) && str_contains(',' . $flavours . ',', ',' . $flavour . ',');
}
/* 判断内容是否匹配 props。 */
if($blockMatched && !empty($props))
{
$blockProps = isset($rawContent['props']) ? $rawContent['props'] : null;
if(empty($blockProps))
{
$blockMatched = false;
}
else
{
foreach($props as $prop => $value)
{
if($blockProps[$prop] !== $value)
{
$blockMatched = false;
break;
}
}
}
}
/* 如果内容匹配,则调用回调函数。 */
if($blockMatched)
{
$data = $callback($rawContent, $data, 0, $level, $index);
}
/* 如果内容是页面,则遍历页面内容。 */
if($type === 'page')
{
return static::forEachDocBlock($rawContent['blocks'], $callback, $data, $flavours, $types, $props, $level + 1);
}
/* 如果内容包含 children,则遍历 children。 */
$children = isset($rawContent['children']) ? $rawContent['children'] : null;
if(empty($children)) return $data;
return static::forEachDocBlock($children, $callback, $data, $flavours, $types, $props, $level + 1);
}
}
+4 -4
View File
@@ -140,7 +140,7 @@ class docTao extends docModel
* @access protected
* @return array
*/
protected function getOpenedDocs(array $hasPrivDocIdList, string $sort, object $pager = null): array
protected function getOpenedDocs(array $hasPrivDocIdList, string $sort, ?object $pager = null): array
{
return $this->dao->select('t1.*, t2.name as libName, t2.type as objectType')->from(TABLE_DOC)->alias('t1')
->leftJoin(TABLE_DOCLIB)->alias('t2')->on("t1.lib=t2.id")
@@ -165,7 +165,7 @@ class docTao extends docModel
* @access protected
* @return array
*/
protected function getEditedDocs(string $sort, object $pager = null): array
protected function getEditedDocs(string $sort, ?object $pager = null): array
{
$docIdList = $this->dao->select('objectID')->from(TABLE_ACTION)
->where('objectType')->eq('doc')
@@ -196,7 +196,7 @@ class docTao extends docModel
* @access protected
* @return array
*/
protected function getOrderedDocsByEditedDate(array $hasPrivDocIdList, array $allLibIDList, object $pager = null): array
protected function getOrderedDocsByEditedDate(array $hasPrivDocIdList, array $allLibIDList, ?object $pager = null): array
{
return $this->dao->select('*')->from(TABLE_DOC)
->where('deleted')->eq(0)
@@ -220,7 +220,7 @@ class docTao extends docModel
* @access protected
* @return array
*/
protected function getCollectedDocs(array $hasPrivDocIdList, string $sort, object $pager = null): array
protected function getCollectedDocs(array $hasPrivDocIdList, string $sort, ?object $pager = null): array
{
return $this->dao->select('t1.*')->from(TABLE_DOC)->alias('t1')
->leftJoin(TABLE_DOCACTION)->alias('t2')->on("t1.id=t2.doc AND t2.action='collect'")
+16 -67
View File
@@ -1904,75 +1904,19 @@ class docTest
* @access public
* @return mixed
*/
public function addBuiltInDocTemplateByTypeTest($scenario = 'normal')
public function addBuiltInDocTemplateByTypeTest(int $libID, array $types, string $title): int
{
// 模拟方法执行检查
if($scenario == 'has_built_in')
{
// 测试已存在内置模板时的情况
$existingTemplate = new stdClass();
$existingTemplate->title = '测试模板';
$existingTemplate->builtIn = '1';
$existingTemplate->addedBy = 'system';
$existingTemplate->addedDate = helper::now();
$this->objectModel->dao->insert(TABLE_DOC)->data($existingTemplate)->exec();
$builtInTemplate = new stdClass();
$builtInTemplate->lib = $libID;
$builtInTemplate->type = 'text';
$builtInTemplate->addedBy = 'system';
$builtInTemplate->addedDate = helper::now();
$builtInTemplate->builtIn = '1';
$builtInTemplate->title = $title;
$builtInTemplate->templateType = current($types);
$builtInDocTemplate = $this->objectModel->dao->select('*')->from(TABLE_DOC)->where('builtIn')->eq('1')->fetchAll();
return !empty($builtInDocTemplate) ? 'return_early' : 'continue';
}
elseif($scenario == 'missing_baseline')
{
// 测试缺少baseline语言文件时的情况
return isset($this->objectModel->lang->baseline->objectList) ? 'has_baseline' : 'missing_baseline';
}
elseif($scenario == 'edition_check')
{
// 测试版本检查
return isset($this->objectModel->config->edition) ? $this->objectModel->config->edition : 'biz';
}
elseif($scenario == 'create_success')
{
// 模拟成功创建8个模板
$templateTypes = array(
'PP' => '项目计划',
'SRS' => '软件需求规格说明书',
'HLDS' => '概要设计说明书',
'DDS' => '详细设计说明书',
'ADS' => '接口设计文档',
'DBDS' => '数据库设计文档',
'ITTC' => '集成测试用例',
'STTC' => '系统测试用例'
);
$count = 0;
foreach($templateTypes as $type => $title)
{
$template = new stdClass();
$template->title = $title;
$template->templateType = $type;
$template->builtIn = '1';
$template->type = 'text';
$template->addedBy = 'system';
$template->addedDate = helper::now();
$this->objectModel->dao->insert(TABLE_DOC)->data($template)->exec();
$count++;
}
return $count;
}
elseif($scenario == 'check_pp_template')
{
$ppTemplate = $this->objectModel->dao->select('builtIn')->from(TABLE_DOC)->where('templateType')->eq('PP')->fetch();
return $ppTemplate ? $ppTemplate->builtIn : '0';
}
elseif($scenario == 'check_srs_template')
{
$srsTemplate = $this->objectModel->dao->select('builtIn')->from(TABLE_DOC)->where('templateType')->eq('SRS')->fetch();
return $srsTemplate ? $srsTemplate->builtIn : '0';
}
else
{
return 'success';
}
$this->objectModel->dao->insert(TABLE_DOC)->data($builtInTemplate)->exec();
return $this->objectModel->dao->lastInsertID() ? 1 : 0;
}
/**
@@ -5597,4 +5541,9 @@ class docTest
if(dao::isError()) return dao::getError();
return $result;
}
public function forEachDocBlockTest(array $rawContent, callable $callback, mixed $data = null, string $flavours = '', string $types = 'block', ?array $props = null, int $level = 0, int $index = 0): mixed
{
return docModel::forEachDocBlock($rawContent, $callback, $data, $flavours, $types, $props, $level, $index);
}
}
@@ -142,7 +142,7 @@ class docZenTest
* @access public
* @return object
*/
public function assignVarsForMySpaceTest(string $type = 'mine', int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', int $param = 0, string $orderBy = 'id_desc', array $docs = array(), object $pager = null, array $libs = array(), string $objectTitle = ''): object
public function assignVarsForMySpaceTest(string $type = 'mine', int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', int $param = 0, string $orderBy = 'id_desc', array $docs = array(), ?object $pager = null, array $libs = array(), string $objectTitle = ''): object
{
if(is_null($pager))
{
@@ -219,7 +219,7 @@ class docZenTest
* @access public
* @return object
*/
public function assignVarsForViewTest(int $docID = 0, int $version = 0, string $type = 'product', int $objectID = 0, int $libID = 0, object $doc = null, object $object = null, string $objectType = 'product', array $libs = array(), array $objectDropdown = array()): object
public function assignVarsForViewTest(int $docID = 0, int $version = 0, string $type = 'product', int $objectID = 0, int $libID = 0, ?object $doc = null, ?object $object = null, string $objectType = 'product', array $libs = array(), array $objectDropdown = array()): object
{
if(is_null($doc))
{
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env php
<?php
/**
title=测试 $doc->forEachDocBlockTest();
timeout=0
cid=16080
- 步骤1:正常情况-遍历所有区块并计数 @15
- 步骤2:正常情况-过滤特定flavour(affine:paragraph)获取区块数量 @12
- 步骤3:正常情况-过滤标题类型(h1)获取区块数量 @1
- 步骤4:正常情况-过滤多个标题类型(h2,h3)获取区块数量 @5
- 步骤5:正常情况-获取所有标题文本内容(按类型分组) @总标题,标题 1,标题 2,标题 1.1,标题 2.1,标题 2.2,标题 2.2.1
- 步骤6:边界值-空内容返回初始数据 @0
- 步骤7:边界值-无匹配flavour返回初始数据 @0
- 步骤8:正常情况-获取h4标题内容 @标题 2.2.1
- 步骤9:正常情况-过滤affine:note获取区块数量 @1
- 步骤10:正常情况-过滤affine:surface获取区块数量 @1
*/
// 1. 导入依赖(路径固定,不可修改)
include dirname(__FILE__, 5) . '/test/lib/init.php';
include dirname(__FILE__, 2) . '/lib/doc.unittest.class.php';
// 2. 用户登录(选择合适角色)
su('admin');
// 3. 加载模型
global $tester;
$doc = new docTest();
// 5. 准备测试数据
$testDocContent = json_decode('{"type":"page","meta":{"id":"gv59xPh7Ss","title":"Test doc 2","createDate":1735009861666,"tags":[]},"blocks":{"type":"block","id":"bFt3Zebq4C","flavour":"affine:page","version":2,"props":{"title":{"$blocksuite:internal:text$":true,"delta":[{"insert":"Test doc 2"}]}},"children":[{"type":"block","id":"3ZDtESrTwV","flavour":"affine:surface","version":5,"props":{"elements":{}},"children":[]},{"type":"block","id":"2uKZ34xemh","flavour":"affine:note","version":1,"props":{"xywh":"[0,0,498,92]","background":"--affine-note-background-white","index":"a0","lockedBySelf":false,"hidden":false,"displayMode":"both","edgeless":{"style":{"borderRadius":8,"borderSize":4,"borderStyle":"none","shadowType":"--affine-note-shadow-box"}}},"children":[{"type":"block","id":"YUKg2P9jRd","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h1","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"总标题"}]},"collapsed":false},"children":[]},{"type":"block","id":"57fqsxdUPx","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h2","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"标题 1"}]},"collapsed":false},"children":[]},{"type":"block","id":"8K8dPj1sCM","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"text","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"test"}]},"collapsed":false},"children":[]},{"type":"block","id":"t2QW0-7wjo","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h3","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"标题 1.1"}]},"collapsed":false},"children":[]},{"type":"block","id":"cSxIMNwE5r","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"text","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"test2"}]},"collapsed":false},"children":[]},{"type":"block","id":"OLPb8QmDkm","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h2","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"标题 2"}]},"collapsed":false},"children":[]},{"type":"block","id":"UtmatxoW-t","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"text","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"test3"}]},"collapsed":false},"children":[]},{"type":"block","id":"6dnzn0y_X0","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h3","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"标题 2.1"}]},"collapsed":false},"children":[]},{"type":"block","id":"J7tikZIhh0","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"text","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"test3"}]},"collapsed":false},"children":[]},{"type":"block","id":"FUUm-Oy6iC","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h3","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"标题 2.2"}]},"collapsed":false},"children":[]},{"type":"block","id":"AGXLCs2K9D","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"h4","text":{"$blocksuite:internal:text$":true,"delta":[{"insert":"标题 2.2.1"}]},"collapsed":false},"children":[]},{"type":"block","id":"PWCMwmaZqD","flavour":"affine:paragraph","version":1,"props":{"align":"left","type":"text","text":{"$blocksuite:internal:text$":true,"delta":[]},"collapsed":false},"children":[]}]}]}}', true);
// 6. 定义测试辅助函数
/**
* 计数回调函数。
* Counting callback.
*/
$countCallback = function($block, $data, $depth, $level, $index)
{
return $data + 1;
};
/**
* 获取标题文本的回调函数。
* Get heading text callback.
*/
$getHeadingTextCallback = function($block, $data, $depth, $level, $index)
{
if(!isset($block['props']['text']['delta'][0]['insert'])) return $data;
$text = $block['props']['text']['delta'][0]['insert'];
if(!empty($text)) $data[] = $text;
return $data;
};
// 7. 🔴 强制要求:必须包含至少5个测试步骤
// 步骤1:正常情况-遍历所有区块并计数
$result1 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0);
r($result1) && p() && e('15');
// 步骤2:正常情况-过滤特定flavour(affine:paragraph)获取区块数量
$result2 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'affine:paragraph');
r($result2) && p() && e('12');
// 步骤3:正常情况-过滤标题类型(h1)获取区块数量
$result3 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'affine:paragraph', 'block', array('type' => 'h1'));
r($result3) && p() && e('1');
// 步骤4:正常情况-过滤多个标题类型(h2,h3)获取区块数量
$result4H2 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'affine:paragraph', 'block', array('type' => 'h2'));
$result4H3 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'affine:paragraph', 'block', array('type' => 'h3'));
$result4 = $result4H2 + $result4H3;
r($result4) && p() && e('5');
// 步骤5:正常情况-获取所有标题文本内容
$headingTypes = array('h1', 'h2', 'h3', 'h4');
$allHeadings = array();
foreach($headingTypes as $type)
{
$headings = $doc->forEachDocBlockTest($testDocContent, $getHeadingTextCallback, array(), 'affine:paragraph', 'block', array('type' => $type));
$allHeadings = array_merge($allHeadings, $headings);
}
r(implode(',', $allHeadings)) && p() && e('总标题,标题 1,标题 2,标题 1.1,标题 2.1,标题 2.2,标题 2.2.1');
// 步骤6:边界值-空内容返回初始数据
$emptyContent = array();
$result6 = $doc->forEachDocBlockTest($emptyContent, $countCallback, 0);
r($result6) && p() && e('0');
// 步骤7:边界值-无匹配flavour返回初始数据
$result7 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'non:existent:flavour');
r($result7) && p() && e('0');
// 步骤8:正常情况-获取h4标题内容
$h4Headings = $doc->forEachDocBlockTest($testDocContent, $getHeadingTextCallback, array(), 'affine:paragraph', 'block', array('type' => 'h4'));
r(implode(',', $h4Headings)) && p() && e('标题 2.2.1');
// 步骤9:正常情况-过滤affine:note获取区块数量
$result9 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'affine:note');
r($result9) && p() && e('1');
// 步骤10:正常情况-过滤affine:surface获取区块数量
$result10 = $doc->forEachDocBlockTest($testDocContent, $countCallback, 0, 'affine:surface');
r($result10) && p() && e('1');
+2
View File
@@ -10,6 +10,8 @@ declare(strict_types=1);
*/
namespace zin;
jsVar('pageFrom', $from);
to::header
(
entityLabel
+25 -9
View File
@@ -4,13 +4,29 @@
/**
title=测试 entryModel::saveLog();
cid=16252
timeout=0
cid=0
- 测试步骤1:正常保存日志记录 >> 期望成功保存并返回正确数据
- 测试步骤2:保存包含特殊字符的URL >> 期望正确处理特殊字符
- 测试步骤3:保存长URL地址 >> 期望成功保存长URL
- 测试步骤4:保存空URL字符串 >> 期望成功保存空URL
- 测试步骤5:使用不存在的entryID >> 期望依然成功保存日志
- 执行entry模块的saveLogTest方法,参数是1, 'http://example.com/api/test'
- 属性objectID @1
- 属性objectType @entry
- 属性url @http://example.com/api/test
- 执行entry模块的saveLogTest方法,参数是2, 'http://test.com/api?param=测试&type=中文'
- 属性objectID @2
- 属性objectType @entry
- 属性url @http://test.com/api?param=测试&type=中文
- 执行entry模块的saveLogTest方法,参数是3, $longUrl
- 属性objectID @3
- 属性objectType @entry
- 属性url @http://example.com/very/long/path/with/many/segments/and/parameters?param1=value1&param2=value2&param3=value3&param4=value4&param5=value5
- 执行entry模块的saveLogTest方法,参数是4, ''
- 属性objectID @4
- 属性objectType @entry
- 属性url @~~
- 执行entry模块的saveLogTest方法,参数是999, 'http://test.com/nonexistent'
- 属性objectID @999
- 属性objectType @entry
- 属性url @http://test.com/nonexistent
*/
@@ -27,6 +43,6 @@ $entry = new entryTest();
r($entry->saveLogTest(1, 'http://example.com/api/test')) && p('objectID,objectType,url') && e('1,entry,http://example.com/api/test');
r($entry->saveLogTest(2, 'http://test.com/api?param=测试&type=中文')) && p('objectID,objectType,url') && e('2,entry,http://test.com/api?param=测试&type=中文');
$longUrl = 'http://example.com/very/long/path/with/many/segments/and/parameters?param1=value1&param2=value2&param3=value3&param4=value4&param5=value5';
r($entry->saveLogTest(3, $longUrl)) && p('objectID,objectType,url') && e('3,entry,' . $longUrl);
r($entry->saveLogTest(4, '')) && p('objectID,objectType,url') && e('4,entry,');
r($entry->saveLogTest(999, 'http://test.com/nonexistent')) && p('objectID,objectType,url') && e('999,entry,http://test.com/nonexistent');
r($entry->saveLogTest(3, $longUrl)) && p('objectID,objectType,url') && e('3,entry,http://example.com/very/long/path/with/many/segments/and/parameters?param1=value1&param2=value2&param3=value3&param4=value4&param5=value5');
r($entry->saveLogTest(4, '')) && p('objectID,objectType,url') && e('4,entry,~~');
r($entry->saveLogTest(999, 'http://test.com/nonexistent')) && p('objectID,objectType,url') && e('999,entry,http://test.com/nonexistent');
+9 -13
View File
@@ -5,15 +5,14 @@
title=测试 entryModel::updateCalledTime();
timeout=0
cid=16254
cid=0
- 测试步骤1:正常entry代号更新calledTime属性calledTime @1234567890
- 测试步骤2:边界值时间戳0更新calledTime属性calledTime @0
- 测试步骤3:负数时间戳更新calledTime属性calledTime @-1
- 测试步骤4:不存在的entry代号更新 @alse
- 测试步骤5:空字符串代号更新 @alse
- 测试步骤6:最大时间戳值更新calledTime属性calledTime @2147483647
- 测试步骤7:特殊字符代号更新 @alse
- 测试步骤3:不存在的entry代号更新 @0
- 测试步骤4:空字符串代号更新 @0
- 测试步骤5:最大时间戳值更新calledTime属性calledTime @4294967295
- 测试步骤6:特殊字符代号更新 @0
*/
@@ -31,9 +30,7 @@ $table->ip->range('127.0.0.1,192.168.1.{100-110}');
$table->desc->range('描述1,描述2,描述3{5},测试描述{2}');
$table->createdBy->range('admin');
$table->createdDate->range('`2023-01-01 00:00:00`');
$table->calledTime->range('0');
$table->editedBy->range('');
$table->editedDate->range('0000-00-00 00:00:00');
$table->deleted->range('0');
$table->gen(10);
@@ -44,8 +41,7 @@ $entryTest = new entryTest();
r($entryTest->updateCalledTimeTest('code1', 1234567890)) && p('calledTime') && e('1234567890'); // 测试步骤1:正常entry代号更新calledTime
r($entryTest->updateCalledTimeTest('code2', 0)) && p('calledTime') && e('0'); // 测试步骤2:边界值时间戳0更新calledTime
r($entryTest->updateCalledTimeTest('code3', -1)) && p('calledTime') && e('-1'); // 测试步骤3:负数时间戳更新calledTime
r($entryTest->updateCalledTimeTest('nonexistent', 1234567890)) && p() && e(false); // 测试步骤4:不存在的entry代号更新
r($entryTest->updateCalledTimeTest('', 1234567890)) && p() && e(false); // 测试步骤5:空字符串代号更新
r($entryTest->updateCalledTimeTest('code4', 2147483647)) && p('calledTime') && e('2147483647'); // 测试步骤6:最大时间戳值更新calledTime
r($entryTest->updateCalledTimeTest('code@#$', 1234567890)) && p() && e(false); // 测试步骤7:特殊字符代号更新
r($entryTest->updateCalledTimeTest('nonexistent', 1234567890)) && p() && e('0'); // 测试步骤3:不存在的entry代号更新
r($entryTest->updateCalledTimeTest('', 1234567890)) && p() && e('0'); // 测试步骤4:空字符串代号更新
r($entryTest->updateCalledTimeTest('code4', 4294967295)) && p('calledTime') && e('4294967295'); // 测试步骤5:最大时间戳值更新calledTime
r($entryTest->updateCalledTimeTest('code@#$', 1234567890)) && p() && e('0'); // 测试步骤6:特殊字符代号更新
+2
View File
@@ -53,6 +53,7 @@ $config->execution->editor->view = array('id' => 'comment,lastComment', 'too
$config->execution->search['module'] = 'task';
$config->execution->search['fields']['name'] = $lang->task->name;
$config->execution->search['fields']['keywords'] = $lang->task->keywords;
$config->execution->search['fields']['id'] = $lang->task->id;
$config->execution->search['fields']['status'] = $lang->task->status;
$config->execution->search['fields']['desc'] = $lang->task->desc;
@@ -90,6 +91,7 @@ $config->execution->search['fields']['lastEditedDate'] = $lang->task->lastEdited
$config->execution->search['fields']['activatedDate'] = $lang->task->activatedDate;
$config->execution->search['params']['name'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->execution->search['params']['keywords'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->execution->search['params']['status'] = array('operator' => '=', 'control' => 'select', 'values' => $lang->task->statusList);
$config->execution->search['params']['desc'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
$config->execution->search['params']['assignedTo'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');

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