Merge remote-tracking branch 'origin/master' into Merge184
This commit is contained in:
@@ -230,7 +230,8 @@ $config->openMethods[] = 'project.createguide';
|
||||
$config->openMethods[] = 'task.editteam';
|
||||
$config->openMethods[] = 'feedback.mergeproductmodule';
|
||||
$config->openMethods[] = 'zanode.nodelist';
|
||||
$config->openMethods[] = 'action.restoreStages';
|
||||
$config->openMethods[] = 'action.restorestages';
|
||||
$config->openMethods[] = 'action.commentzin';
|
||||
$config->openMethods[] = 'execution.browse';
|
||||
$config->openMethods[] = 'testcase.showimport';
|
||||
$config->openMethods[] = 'testsuite.index';
|
||||
@@ -710,7 +711,7 @@ $config->bi->pickerHeight = 150;
|
||||
$config->db->sqliteBlacklist = array('sqlite_queue', 'cron');
|
||||
$config->hasDropmenuApps = array('program', 'project', 'product', 'execution', 'qa', 'admin', 'bi');
|
||||
$config->hasBranchMenuModules = array('product', 'story', 'release', 'bug', 'testcase', 'testtask');
|
||||
$config->excludeDropmenuList = array('program-browse', 'program-productview', 'product-all', 'product-index', 'execution-all', 'execution-executionkanban', 'project-browse', 'project-batchedit', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create', 'execution-batchedit', 'metric-preview', 'qa-index');
|
||||
$config->excludeDropmenuList = array('program-browse', 'program-productview', 'program-kanban', 'product-all', 'product-index', 'execution-all', 'execution-executionkanban', 'project-browse', 'project-batchedit', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create', 'execution-batchedit', 'metric-preview', 'qa-index');
|
||||
$config->hasSwitcherModules = array('design');
|
||||
$config->hasSwitcherMethods = array('project-bug', 'project-testcase', 'execution-bug', 'execution-testcase', 'testtask-cases', 'testtask-view', 'testtask-report', 'testtask-groupcase', 'testtask-linkcase');
|
||||
$config->excludeSwitcherList = array();
|
||||
|
||||
@@ -90,4 +90,3 @@ INSERT INTO `zt_config` ( `vision`, `owner`, `module`, `section`, `key`, `value`
|
||||
|
||||
REPLACE INTO `zt_chart`(`id`, `name`, `dimension`, `type`, `group`, `dataset`, `desc`, `settings`, `filters`, `step`, `fields`, `langs`, `sql`, `stage`, `builtin`, `objects`, `createdBy`, `createdDate`, `editedBy`, `editedDate`, `deleted`) VALUES (1030, '宏观数据-禅道使用时长', 1, 'card', '58', '', '', '{\"value\": {\"type\": \"value\", \"field\": \"period\", \"agg\": \"value\"}, \"title\": {\"type\": \"text\", \"name\": \"\"},\n\"type\": \"value\"\n}', '[]', 0, '', NULL, ' SELECT if(t2.`year` > 0, concat(t2.`year`, \'年\', t2.`day`, \'天\'), concat(t2.`day`, \'天\')) as period from (\r\nSELECT TIMESTAMPDIFF(YEAR,t1.firstDay,t1.today) AS `year`,DATEDIFF(DATE_SUB(t1.today,INTERVAL TIMESTAMPDIFF(YEAR,t1.firstDay,t1.today) YEAR), t1.firstDay) AS `day` \r\nFROM (SELECT `value` AS firstDay, now() AS today FROM zt_config WHERE `owner` = \'system\' AND `key` = \'installedDate\') AS t1\r\n) t2', 'published', 1, '', 'system', '2022-12-07 14:59:41', '', '2022-12-07 14:59:41', 0);
|
||||
REPLACE INTO `zt_chart`(`id`, `name`, `dimension`, `type`, `group`, `dataset`, `desc`, `settings`, `filters`, `step`, `fields`, `langs`, `sql`, `stage`, `builtin`, `objects`, `createdBy`, `createdDate`, `editedBy`, `editedDate`, `deleted`) VALUES (20015, '使用数据分析-上线时间', 1, 'card', '58', '0', ' ', '{\"value\": {\"type\": \"value\", \"field\": \"date\", \"agg\": \"value\"}, \"title\": {\"type\": \"text\", \"name\": \"\"}, \"type\": \"value\"}', '[]', 0, ' ', NULL, 'select `value` as date from zt_config where `owner` = \'system\' and `key` = \'installedDate\'', 'published', 1, ' ', 'system', '2023-08-16 15:32:10', 'admin', '2023-08-16 15:32:17', 0);
|
||||
|
||||
|
||||
@@ -716,4 +716,24 @@ class router extends baseRouter
|
||||
|
||||
return parent::mergeParams($defaultParams, $passedParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载一个模块:
|
||||
*
|
||||
* Load a module.
|
||||
*
|
||||
* @access public
|
||||
* @return bool|object if the module object of die.
|
||||
*/
|
||||
public function loadModule()
|
||||
{
|
||||
/* 不能直接请求基类的方法 Cannot call methods of base control class. */
|
||||
if(method_exists('Control', $this->methodName))
|
||||
{
|
||||
echo 'Cannot call methods of base control class.';
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::loadModule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class formGroup extends wg
|
||||
zui::width($width),
|
||||
set($this->getRestProps()),
|
||||
setCssVar('form-grid-label-width', $labelWidth),
|
||||
empty($label) ? null : new formLabel
|
||||
empty($label) && $label !== '0' ? null : new formLabel
|
||||
(
|
||||
set::className($labelClass, $strong ? 'font-bold' : null),
|
||||
set::required($required),
|
||||
|
||||
+47
-37
@@ -139,38 +139,40 @@ class header extends wg
|
||||
|
||||
if(!isset($app->user)) return;
|
||||
|
||||
$user = $app->user;
|
||||
$isGuest = $user->account == 'guest';
|
||||
$items = array();
|
||||
$user = $app->user;
|
||||
$isGuest = $user->account == 'guest';
|
||||
$items = array();
|
||||
$modalClass = (isset($config->zin->mode) && $config->zin->mode == 'compatible') ? 'open-in-parent' : null;
|
||||
|
||||
if(!$isGuest)
|
||||
{
|
||||
$noRole = empty($user->role) || !isset($lang->user->roleList[$user->role]);
|
||||
$items[] = array
|
||||
(
|
||||
'url' => createLink('my', 'profile', '', '', true),
|
||||
'url' => createLink('my', 'profile', '', '', true),
|
||||
'leadingClass' => 'row items-center gap-2 px-2 py-1 text-inherit',
|
||||
'icon' => ' hidden',
|
||||
'title' => empty($user->realname) ? $user->account : $user->realname,
|
||||
'titleClass' => 'text-lg',
|
||||
'subtitle' => $noRole ? null : $lang->user->roleList[$user->role],
|
||||
// 'style' => array('padding-left' => 0),
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 700,
|
||||
'data-id' => 'profile',
|
||||
'leading' => array('html' => userAvatar(set::user($user))->render(), 'className' => 'center mr-1')
|
||||
'icon' => ' hidden',
|
||||
'title' => empty($user->realname) ? $user->account : $user->realname,
|
||||
'titleClass' => 'text-lg',
|
||||
'subtitle' => $noRole ? null : $lang->user->roleList[$user->role],
|
||||
'innerClass' => $modalClass,
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 700,
|
||||
'data-id' => 'profile',
|
||||
'leading' => array('html' => userAvatar(set::user($user))->render(), 'className' => 'center mr-1')
|
||||
);
|
||||
|
||||
$items[] = array('type' => 'divider');
|
||||
|
||||
$items[] = array
|
||||
(
|
||||
'url' => createLink('my', 'profile', '', '', true),
|
||||
'icon' => 'account',
|
||||
'text' => $lang->profile,
|
||||
'url' => createLink('my', 'profile', '', '', true),
|
||||
'icon' => 'account',
|
||||
'text' => $lang->profile,
|
||||
'innerClass' => $modalClass,
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 700,
|
||||
'data-id' => 'profile'
|
||||
'data-size' => 700,
|
||||
'data-id' => 'profile'
|
||||
);
|
||||
|
||||
if($app->config->vision === 'rnd')
|
||||
@@ -179,26 +181,28 @@ class header extends wg
|
||||
{
|
||||
$items[] = array
|
||||
(
|
||||
'url' => createLink('tutorial', 'start'),
|
||||
'icon' => 'guide',
|
||||
'text' => $lang->tutorialAB,
|
||||
'class' => '800',
|
||||
'outerClass' => 'user-tutorial',
|
||||
'data-width' => 700,
|
||||
'url' => createLink('tutorial', 'start'),
|
||||
'icon' => 'guide',
|
||||
'text' => $lang->tutorialAB,
|
||||
'class' => '800',
|
||||
'outerClass' => 'user-tutorial',
|
||||
'data-width' => 700,
|
||||
'data-class-name' => 'modal-inverse',
|
||||
'data-headerless' => true,
|
||||
'data-backdrop' => true,
|
||||
'data-keyboard' => true,
|
||||
'data-toggle' => 'modal'
|
||||
'data-backdrop' => true,
|
||||
'data-keyboard' => true,
|
||||
'innerClass' => $modalClass,
|
||||
'data-toggle' => 'modal'
|
||||
);
|
||||
}
|
||||
|
||||
$items[] = array
|
||||
(
|
||||
'url' => createLink('my', 'preference', 'showTip=false', '', true),
|
||||
'icon' => 'controls',
|
||||
'text' => $lang->preference,
|
||||
'data-width' => 700,
|
||||
'url' => createLink('my', 'preference', 'showTip=false', '', true),
|
||||
'icon' => 'controls',
|
||||
'text' => $lang->preference,
|
||||
'data-width' => 700,
|
||||
'innerClass' => $modalClass,
|
||||
'data-toggle' => 'modal'
|
||||
);
|
||||
}
|
||||
@@ -207,11 +211,12 @@ class header extends wg
|
||||
{
|
||||
$items[] = array
|
||||
(
|
||||
'url' => createLink('my', 'changepassword', '', '', true),
|
||||
'icon' => 'cog-outline',
|
||||
'text' => $lang->changePassword,
|
||||
'url' => createLink('my', 'changepassword', '', '', true),
|
||||
'icon' => 'cog-outline',
|
||||
'text' => $lang->changePassword,
|
||||
'innerClass' => $modalClass,
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 'sm'
|
||||
'data-size' => 'sm'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -241,12 +246,12 @@ class header extends wg
|
||||
$helpItems = array();
|
||||
$manualUrl = ((!empty($config->isINT)) ? $config->manualUrl['int'] : $config->manualUrl['home']) . '&theme=' . $_COOKIE['theme'];
|
||||
$helpItems[] = array('text' => $lang->manual, 'url' => $manualUrl, 'attrs' => array('data-app' => 'help'));
|
||||
$helpItems[] = array('text' => $lang->changeLog, 'url' => createLink('misc', 'changeLog'), 'data-toggle' => 'modal');
|
||||
$helpItems[] = array('text' => $lang->changeLog, 'url' => createLink('misc', 'changeLog'), 'data-toggle' => 'modal', 'innerClass' => $modalClass);
|
||||
$items[] = array('text' => $lang->help, 'icon' => 'help', 'items' => $helpItems);
|
||||
|
||||
/* printClientLink */
|
||||
|
||||
$items[] = array('text' => $lang->aboutZenTao, 'icon' => 'about', 'url' => createLink('misc', 'about'), 'data-toggle' => 'modal');
|
||||
$items[] = array('text' => $lang->aboutZenTao, 'icon' => 'about', 'url' => createLink('misc', 'about'), 'data-toggle' => 'modal', 'innerClass' => $modalClass);
|
||||
$items[] = array('type' => 'html', 'className' => 'menu-item', 'html' => $lang->designedByAIUX);
|
||||
|
||||
$items[] = array('type' => 'divider');
|
||||
@@ -290,6 +295,7 @@ class header extends wg
|
||||
|
||||
/* Initialize the default values. */
|
||||
$showCreateList = $needPrintDivider = false;
|
||||
$modalClass = (isset($config->zin->mode) && $config->zin->mode == 'compatible') ? 'open-in-parent' : null;
|
||||
|
||||
/* Get default product id. */
|
||||
$productID = isset($_SESSION['product']) ? $_SESSION['product'] : 0;
|
||||
@@ -347,6 +353,7 @@ class header extends wg
|
||||
case 'doc':
|
||||
$params = "objectType=&objectID=0&libID=0";
|
||||
$createMethod = 'selectLibType';
|
||||
$item['innerClass'] = $modalClass;
|
||||
$item['data-toggle'] = 'modal';
|
||||
break;
|
||||
case 'project':
|
||||
@@ -358,6 +365,7 @@ class header extends wg
|
||||
{
|
||||
$params = "programID=0&from=global";
|
||||
$createMethod = 'createGuide';
|
||||
$item['innerClass'] = $modalClass;
|
||||
$item['data-toggle'] = 'modal';
|
||||
}
|
||||
else
|
||||
@@ -411,11 +419,13 @@ class header extends wg
|
||||
break;
|
||||
case 'kanbanspace':
|
||||
$isOnlyBody = true;
|
||||
$item['innerClass'] = $modalClass;
|
||||
$item['data-toggle'] = 'modal';
|
||||
$item['data-width'] = '75%';
|
||||
break;
|
||||
case 'kanban':
|
||||
$isOnlyBody = true;
|
||||
$item['innerClass'] = $modalClass;
|
||||
$item['data-toggle'] = 'modal';
|
||||
$item['data-width'] = '75%';
|
||||
break;
|
||||
|
||||
@@ -20,6 +20,7 @@ class history extends wg
|
||||
'actions?: array', // 操作列表数据。
|
||||
'users?: array', // 用户 Map 数据。
|
||||
'commentUrl?: string', // 备注对话框 URL。
|
||||
'editCommentUrl?: string', // 修改备注对话框 URL。
|
||||
'title?: string|array', // 标题。
|
||||
'commentBtn?: string|array' // 是否允许添加备注。
|
||||
);
|
||||
@@ -74,7 +75,15 @@ class history extends wg
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
list($panel, $objectID, $objectType, $title, $actions, $commentUrl, $commentBtn) = $this->prop(array('panel', 'objectID', 'objectType', 'title', 'actions', 'commentUrl', 'commentBtn'));
|
||||
global $config;
|
||||
|
||||
list($panel, $objectID, $objectType, $title, $actions, $commentUrl, $editCommentUrl, $commentBtn) = $this->prop(array('panel', 'objectID', 'objectType', 'title', 'actions', 'commentUrl', 'editCommentUrl', 'commentBtn'));
|
||||
|
||||
if(isset($config->zin->mode) && $config->zin->mode == 'compatible')
|
||||
{
|
||||
if(is_null($commentUrl)) $commentUrl = createLink('action', 'commentZin', "objectType=$objectType&objectID=$objectID");
|
||||
if(is_null($editCommentUrl)) $editCommentUrl = createLink('action', 'editCommentZin', 'actionID={actionID}');
|
||||
}
|
||||
|
||||
return zui::historyPanel
|
||||
(
|
||||
@@ -84,6 +93,7 @@ class history extends wg
|
||||
set::actions($actions),
|
||||
set::title($title),
|
||||
set::commentUrl($commentUrl),
|
||||
set::editCommentUrl($editCommentUrl),
|
||||
set::commentBtn($commentBtn),
|
||||
set($this->getRestProps())
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#mainNavbar {padding-left: 1rem; padding-right: 1rem;}
|
||||
#mainNavbar .main-navbar-left {position: absolute; z-index: 2;}
|
||||
#mainNavbar .main-navbar-left #switcher {position: relative; top: 5px; border: 1px solid rgb(var(--color-primary-500-rgb));}
|
||||
#mainNavbar .main-navbar-left #switcher > .dropmenu-btn {background: #FFF;}
|
||||
|
||||
@@ -139,7 +139,7 @@ class mainNavbar extends nav
|
||||
|
||||
if(in_array("$moduleName-$methodName", is_array($config->excludeSwitcherList) ? $config->excludeSwitcherList : array())) return null;
|
||||
|
||||
if(in_array($moduleName, is_array($config->hasSwitcherModules) ? $config->hasSwitcherModules : array()) || in_array("$moduleName-$methodName", is_array($config->hasSwitcherMethods) ? $config->hasSwitcherMethods : array()))
|
||||
if(in_array($moduleName, (isset($config->hasSwitcherModules) && is_array($config->hasSwitcherModules)) ? $config->hasSwitcherModules : array()) || in_array("$moduleName-$methodName", (isset($config->hasSwitcherMethods) && is_array($config->hasSwitcherMethods)) ? $config->hasSwitcherMethods : array()))
|
||||
{
|
||||
$ajaxMethod = 'ajaxSwitcherMenu';
|
||||
if($moduleName == 'testcase' && $app->tab == 'project') $moduleName = 'project';
|
||||
|
||||
@@ -146,7 +146,11 @@ class moduleMenu extends wg
|
||||
|
||||
private function buildCloseBtn(): wg|null
|
||||
{
|
||||
$closeLink = $this->prop('closeLink');
|
||||
$closeLink = $this->prop('closeLink');
|
||||
$tab = $this->prop('app');
|
||||
$titleAttrs = array();
|
||||
if($tab) $titleAttrs['app'] = $tab;
|
||||
if(isInModal()) $titleAttrs['load'] = 'modal';
|
||||
if(!$closeLink) return null;
|
||||
|
||||
$activeKey = $this->prop('activeKey');
|
||||
@@ -155,7 +159,7 @@ class moduleMenu extends wg
|
||||
return a
|
||||
(
|
||||
set('href', $closeLink),
|
||||
isInModal() ? set('data-load', 'modal') : null,
|
||||
$titleAttrs ? setData($titleAttrs) : null,
|
||||
icon('close', setStyle('color', 'var(--color-slate-600)'))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ class pageBase extends wg
|
||||
$themeName = $app->cookie->theme;
|
||||
$zuiPath = $config->zin->zuiPath;
|
||||
|
||||
$jsConfig->zin = (isset($config->zin->mode) && $config->zin->mode) ? $config->zin->mode : true;
|
||||
$zinMode = isset($config->zin->mode) ? $config->zin->mode : '';
|
||||
$jsConfig->zin = !empty($zinMode) ? $zinMode : true;
|
||||
|
||||
$headImports = array();
|
||||
if($zui)
|
||||
@@ -74,10 +75,15 @@ class pageBase extends wg
|
||||
$headImports[] = h::importJs($zuiPath . 'zui.zentao.umd.cjs', setID('zuiJS'));
|
||||
$headImports[] = h::jsCall('$.setLibRoot', $zuiPath);
|
||||
|
||||
if(isset($config->zin->extraCSS) && $config->zin->extraCSS) $headImports[] = h::importCss($webRoot . 'js/zui3/' . $config->zin->extraCSS);
|
||||
$extraCSS = isset($config->zin->extraCSS) ? $config->zin->extraCSS : '';
|
||||
if(!empty($extraCSS)) $headImports[] = h::importCss($webRoot . 'js/zui3/' . $extraCSS);
|
||||
}
|
||||
$headImports[] = h::jsVar('window.config', $jsConfig, setID('configJS'));
|
||||
if($zui) $headImports[] = h::importJs($webRoot . 'js/zui3/zin.js', setID('zinJS'));
|
||||
if($zui)
|
||||
{
|
||||
$extraJS = isset($config->zin->extraJS) ? $config->zin->extraJS : 'zin.js';
|
||||
if(!empty($extraJS)) $headImports[] = h::importJs($webRoot . 'js/zui3/' . $extraJS);
|
||||
}
|
||||
|
||||
if($config->debug)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ class searchForm extends wg
|
||||
'simple?: boolean', // 是否为简单模式,不包含保存搜索条件和已保存的查询条件侧边栏。
|
||||
'setting?: array', // 默认配置。
|
||||
'url?: string', // 配置加载地址,默认为 search-buildForm-module。
|
||||
'searchUrl?: string', // 搜索时提交表单的 URL。
|
||||
'searchLoader?: string|array' // 搜索时 loadPage 参数。
|
||||
);
|
||||
|
||||
@@ -44,6 +45,12 @@ class searchForm extends wg
|
||||
*/
|
||||
protected function build(): wg
|
||||
{
|
||||
global $config;
|
||||
if(isset($config->zin->mode) && $config->zin->mode == 'compatible')
|
||||
{
|
||||
if(!$this->hasProp('url')) $this->setProp('url', createLink('search', 'buildZinForm', 'module=' . $this->prop('module')));
|
||||
if(!$this->hasProp('searchUrl')) $this->setProp('searchUrl', createLink('search', 'buildZinQuery'));
|
||||
}
|
||||
return zui::searchForm(inherit($this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ class searchToggle extends wg
|
||||
protected static array $defineProps = array(
|
||||
'target?: string',
|
||||
'open?: bool',
|
||||
'module?: string'
|
||||
'module?: string',
|
||||
'url?: string',
|
||||
'searchUrl?: string'
|
||||
);
|
||||
|
||||
protected function checkErrors()
|
||||
@@ -17,19 +19,26 @@ class searchToggle extends wg
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
global $lang, $app;
|
||||
list($target, $module, $open) = $this->prop(array('target', 'module', 'open'));
|
||||
global $lang, $app, $config;
|
||||
list($target, $module, $open, $url, $searchUrl) = $this->prop(array('target', 'module', 'open', 'url', 'searchUrl'));
|
||||
|
||||
if(is_null($open) && !empty($_GET['browseType'])) $open = $_GET['browseType'] === 'bySearch';
|
||||
if(is_null($module)) $module = $app->rawModule;
|
||||
|
||||
if(isset($config->zin->mode) && $config->zin->mode == 'compatible')
|
||||
{
|
||||
if(is_null($url)) $url = createLink('search', 'buildZinForm', 'module=' . $this->prop('module'));
|
||||
if(is_null($searchUrl)) $searchUrl = createLink('search', 'buildZinQuery');
|
||||
}
|
||||
|
||||
return btn
|
||||
(
|
||||
set::className('ghost search-form-toggle'),
|
||||
set::icon('search'),
|
||||
set::active($open),
|
||||
set::text($lang->searchAB),
|
||||
toggle::searchform(array('module' => $module, 'target' => $target)),
|
||||
$open ? h::jsCall('~zui.toggleSearchForm', array('module' => $module, 'target' => $target, 'show' => true)) : null
|
||||
toggle::searchform(array('module' => $module, 'target' => $target, 'url' => $url, 'searchUrl' => $searchUrl)),
|
||||
$open ? h::jsCall('~zui.toggleSearchForm', array('module' => $module, 'target' => $target, 'show' => true, 'url' => $url, 'searchUrl' => $searchUrl)) : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ class StepsEditor extends zui.Component
|
||||
|
||||
reset(data, skipRender)
|
||||
{
|
||||
this._items = array();
|
||||
this._items = [];
|
||||
this.update(data, skipRender);
|
||||
}
|
||||
|
||||
|
||||
@@ -638,7 +638,7 @@ class actionModel extends model
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function formatActionComment($comment): string
|
||||
public function formatActionComment(string $comment): string
|
||||
{
|
||||
if(str_contains($comment, '<pre class="prettyprint lang-html">'))
|
||||
{
|
||||
@@ -704,10 +704,7 @@ class actionModel extends model
|
||||
public function printAction(object $action, string $desc = '')
|
||||
{
|
||||
$content = $this->renderAction($action, $desc);
|
||||
if(is_string($content))
|
||||
{
|
||||
echo $content;
|
||||
}
|
||||
if(is_string($content)) echo $content;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1551,7 +1548,7 @@ class actionModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function processDynamicForAPI($dynamics): array
|
||||
public function processDynamicForAPI(array $dynamics): array
|
||||
{
|
||||
/* 获取用户列表。 */
|
||||
/* Get user list. */
|
||||
|
||||
@@ -76,7 +76,7 @@ class productEntry extends entry
|
||||
$product->actions = $this->loadModel('action')->processActionForAPI($actions, $users, $this->lang->product);
|
||||
break;
|
||||
case 'lastexecution':
|
||||
$execution = $this->dao->select('t2.id,t2.name,t2.type')->from(TABLE_PROJECTPRODUCT)->alias('t1')
|
||||
$execution = $this->dao->select('t2.id,t2.name,t2.type,t2.progress')->from(TABLE_PROJECTPRODUCT)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
|
||||
->where('t2.deleted')->eq(0)
|
||||
->andWhere('t1.product')->eq($productID)
|
||||
@@ -84,11 +84,6 @@ class productEntry extends entry
|
||||
->orderBy('t2.id desc')
|
||||
->limit(1)
|
||||
->fetch();
|
||||
if($execution)
|
||||
{
|
||||
$workhour = $this->loadModel('project')->computeProgress(array($execution->id => $execution));
|
||||
if(isset($workhour[$execution->id])) $execution->progress = $workhour[$execution->id]->progress;
|
||||
}
|
||||
|
||||
$product->lastExecution = $execution;
|
||||
break;
|
||||
|
||||
@@ -15,10 +15,10 @@ jsVar('delayInfo', $lang->project->delayInfo);
|
||||
|
||||
foreach($executionStats as $scrum)
|
||||
{
|
||||
$scrum->totalEstimate = zget($scrum->hours, 'totalEstimate', 0) . $lang->execution->workHourUnit;
|
||||
$scrum->totalConsumed = zget($scrum->hours, 'totalConsumed', 0) . $lang->execution->workHourUnit;
|
||||
$scrum->totalLeft = zget($scrum->hours, 'totalLeft', 0) . $lang->execution->workHourUnit;
|
||||
$scrum->progress = zget($scrum->hours, 'progress', 0);
|
||||
$scrum->totalEstimate = zget($scrum, 'estimate', 0) . $lang->execution->workHourUnit;
|
||||
$scrum->totalConsumed = zget($scrum, 'consumed', 0) . $lang->execution->workHourUnit;
|
||||
$scrum->totalLeft = zget($scrum, 'left', 0) . $lang->execution->workHourUnit;
|
||||
$scrum->progress = zget($scrum, 'progress', 0);
|
||||
}
|
||||
|
||||
if(!$longBlock)
|
||||
|
||||
+1
-15
@@ -755,21 +755,7 @@ class blockZen extends block
|
||||
$type = isset($block->params->type) ? $block->params->type : 'all';
|
||||
$orderBy = isset($block->params->orderBy) ? $block->params->orderBy : 'id_desc';
|
||||
|
||||
$projects = $this->loadModel('project')->getOverviewList($type, 0, $orderBy, $count);
|
||||
|
||||
/* Get all tasks and compute totalEstimate, totalConsumed, totalLeft, progress according to them. */
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->in(array_keys($projects))
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
$hours = $this->loadModel('program')->computeProjectHours($tasks);
|
||||
|
||||
$projects = $this->program->appendStatToProjects($projects, 'hours', array('hours' => $hours));
|
||||
foreach($projects as $project) $project->progress = $project->hours->progress;
|
||||
|
||||
$this->view->projects = $projects;
|
||||
$this->view->projects = $this->loadModel('project')->getOverviewList($type, 0, $orderBy, $count);
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noletter', '', 0, array_unique(array_column($this->view->projects, 'PM')));
|
||||
}
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ formPanel
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->bug->steps),
|
||||
set::required(strpos(",{$this->config->bug->create->requiredFields},", ",steps,") !== false),
|
||||
editor
|
||||
(
|
||||
set::name('steps'),
|
||||
|
||||
@@ -87,6 +87,7 @@ class caselibModel extends model
|
||||
$oldLib = $this->dao->select('*')->from(TABLE_TESTSUITE)->where('id')->eq($lib->id)->fetch();
|
||||
|
||||
$this->lang->testsuite->name = $this->lang->caselib->name;
|
||||
$this->lang->testsuite->desc = $this->lang->caselib->desc;
|
||||
|
||||
$this->dao->update(TABLE_TESTSUITE)->data($lib, 'uid')
|
||||
->autoCheck()
|
||||
|
||||
@@ -31,10 +31,8 @@ formPanel
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->caselib->desc),
|
||||
editor
|
||||
(
|
||||
set::name('desc')
|
||||
)
|
||||
set::control('editor'),
|
||||
set::name('desc')
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ formPanel
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->caselib->desc),
|
||||
set::required(strpos(",{$this->config->caselib->edit->requiredFields},", ",desc,") !== false),
|
||||
editor
|
||||
(
|
||||
set::name('desc'),
|
||||
|
||||
@@ -5,8 +5,9 @@ $config->cron->edit = new stdclass();
|
||||
$config->cron->create->requiredFields = 'm,h,dom,mon,dow,command';
|
||||
$config->cron->edit->requiredFields = 'm,h,dom,mon,dow,command';
|
||||
|
||||
$config->cron->maxRunDays = 8;
|
||||
$config->cron->maxRunTime = 65;
|
||||
$config->cron->maxRunDays = 8;
|
||||
$config->cron->maxRunTime = 65;
|
||||
$config->cron->maxConsumer = 2;
|
||||
|
||||
global $lang;
|
||||
$config->cron->dtable = new stdclass();
|
||||
|
||||
+2
-60
@@ -115,64 +115,6 @@ class cron extends control
|
||||
return $this->sendSuccess(array('load' => true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule cron task by RoadRunner.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function rrSchedule()
|
||||
{
|
||||
if('cli' !== PHP_SAPI) return;
|
||||
|
||||
set_time_limit(0);
|
||||
session_write_close();
|
||||
|
||||
$this->loadModel('common');
|
||||
|
||||
$execId = mt_rand();
|
||||
while(true)
|
||||
{
|
||||
dao::$cache = array();
|
||||
if(empty($this->config->global->cron) || !$this->canSchedule($execId))
|
||||
{
|
||||
sleep(60);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->schedule($execId);
|
||||
sleep(30);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume cron task by RoadRunner.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function rrConsume()
|
||||
{
|
||||
if('cli' !== PHP_SAPI) return;
|
||||
|
||||
set_time_limit(0);
|
||||
session_write_close();
|
||||
|
||||
$this->loadModel('common');
|
||||
|
||||
while(true)
|
||||
{
|
||||
if(empty($this->config->global->cron))
|
||||
{
|
||||
sleep(60);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->execTasks(mt_rand());
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Ajax请求执行定时任务.
|
||||
* Ajax execute cron.
|
||||
@@ -315,8 +257,8 @@ class cron extends control
|
||||
{
|
||||
if($setting->value > $expirDate)
|
||||
{
|
||||
if($setting->key == strval($execId)) $roles[] = 'consumer';
|
||||
$consumerCount ++;
|
||||
if($consumer < $this->config->cron->maxConsumer && $setting->key == strval($execId)) $roles[] = 'consumer';
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -326,7 +268,7 @@ class cron extends control
|
||||
}
|
||||
|
||||
if(in_array($scheduler['execId'], array(0, $execId)) || $scheduler['lastTime'] < $expirDate) $roles[] = 'scheduler';
|
||||
if($consumerCount < 4) $roles[] = 'consumer';
|
||||
if($consumerCount < $this->config->cron->maxConsumer) $roles[] = 'consumer';
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
+21
-262
@@ -12,277 +12,52 @@
|
||||
class custom extends control
|
||||
{
|
||||
/**
|
||||
* Index
|
||||
* Index.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if($this->config->vision == 'lite') return print(js::locate(inlink('execution')));
|
||||
if($this->config->vision == 'lite') return $this->locate(inlink('execution'));
|
||||
|
||||
if(common::hasPriv('custom', 'set')) return print(js::locate(inlink('set', "module=project&field=" . key($this->lang->custom->project->fields))));
|
||||
if(common::hasPriv('custom', 'product')) return print(js::locate(inlink('product')));
|
||||
if(common::hasPriv('custom', 'execution')) return print(js::locate(inlink('execution')));
|
||||
if(common::hasPriv('custom', 'set')) return $this->locate(inlink('set', "module=project&field=" . key($this->lang->custom->project->fields)));
|
||||
if(common::hasPriv('custom', 'product')) return $this->locate(inlink('product'));
|
||||
if(common::hasPriv('custom', 'execution')) return $this->locate(inlink('execution'));
|
||||
|
||||
foreach($this->lang->custom->system as $sysObject)
|
||||
{
|
||||
if(common::hasPriv('custom', $sysObject)) return print(js::locate(inlink($sysObject)));
|
||||
if(common::hasPriv('custom', $sysObject)) return $this->locate(inlink($sysObject));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom
|
||||
* 设置对象字段的语言项。
|
||||
* Set the language items of the object fields.
|
||||
*
|
||||
* @param string $module
|
||||
* @param string $field
|
||||
* @param string $lang
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @param string $lang all|zh-cn|zh-tw|en|de|fr
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function set(string $module = 'story', string $field = 'priList', string $lang = '')
|
||||
{
|
||||
if(empty($lang)) $lang = $this->app->getClientLang();
|
||||
if($module == 'user' and $field == 'priList') $field = 'statusList';
|
||||
if($module == 'block' and $field == 'priList')$field = 'closed';
|
||||
if($module == 'user' && $field == 'priList') $field = 'statusList';
|
||||
if($module == 'block' && $field == 'priList')$field = 'closed';
|
||||
$currentLang = $this->app->getClientLang();
|
||||
|
||||
$this->app->loadLang($module);
|
||||
if($lang == 'all')
|
||||
{
|
||||
$fieldList = array();
|
||||
$items = $this->custom->getItems("lang=all&module=$module§ion=$field&vision={$this->config->vision}");
|
||||
foreach($items as $key => $item)
|
||||
{
|
||||
$fieldList[$key] = $item->value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$fieldList = zget($this->lang->$module, $field, '');
|
||||
}
|
||||
|
||||
if($module == 'project' and $field == 'unitList')
|
||||
{
|
||||
$this->app->loadConfig($module);
|
||||
$unitList = zget($this->config->$module, 'unitList', '');
|
||||
$this->view->unitList = explode(',', $unitList);
|
||||
$this->view->defaultCurrency = zget($this->config->$module, 'defaultCurrency', 'CNY');
|
||||
}
|
||||
if(($module == 'story' or $module == 'demand') and $field == 'reviewRules')
|
||||
{
|
||||
$this->app->loadConfig($module);
|
||||
$this->view->reviewRule = zget($this->config->$module, 'reviewRules', 'allpass');
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
|
||||
$this->view->superReviewers = zget($this->config->$module, 'superReviewers', '');
|
||||
}
|
||||
if(($module == 'story' or $module == 'testcase' or $module == 'demand') and $field == 'review')
|
||||
{
|
||||
$this->app->loadConfig($module);
|
||||
$this->loadModel('user');
|
||||
|
||||
if($module == 'story' or $module == 'demand')
|
||||
{
|
||||
$this->view->depts = $this->loadModel('dept')->getDeptPairs();
|
||||
|
||||
$this->view->forceReviewRoles = zget($this->config->$module, 'forceReviewRoles', '');
|
||||
$this->view->forceReviewDepts = zget($this->config->$module, 'forceReviewDepts', '');
|
||||
|
||||
$this->view->forceNotReviewRoles = zget($this->config->$module, 'forceNotReviewRoles', '');
|
||||
$this->view->forceNotReviewDepts = zget($this->config->$module, 'forceNotReviewDepts', '');
|
||||
}
|
||||
|
||||
$this->view->users = $module == 'story' ? $this->user->getCanCreateStoryUsers() : $this->user->getPairs('noclosed|nodeleted');
|
||||
$this->view->needReview = zget($this->config->$module, 'needReview', 1);
|
||||
$this->view->forceReview = zget($this->config->$module, 'forceReview', '');
|
||||
$this->view->forceNotReview = zget($this->config->$module, 'forceNotReview', '');
|
||||
}
|
||||
if($module == 'bug' and $field == 'longlife')
|
||||
{
|
||||
$this->app->loadConfig('bug');
|
||||
$this->view->longlife = $this->config->bug->longlife;
|
||||
}
|
||||
if($module == 'block' and $field == 'closed')
|
||||
{
|
||||
$this->loadModel('block');
|
||||
$closedBlock = isset($this->config->block->closed) ? $this->config->block->closed : '';
|
||||
|
||||
$this->view->blockPairs = $this->block->getClosedBlockPairs($closedBlock);
|
||||
$this->view->closedBlock = $closedBlock;
|
||||
}
|
||||
if($module == 'user' and $field == 'deleted')
|
||||
{
|
||||
$this->app->loadConfig('user');
|
||||
$this->view->showDeleted = isset($this->config->user->showDeleted) ? $this->config->user->showDeleted : '0';
|
||||
}
|
||||
$this->customZen->assignVarsForSet($module, $field, $lang, $currentLang);
|
||||
|
||||
if(strtolower($this->server->request_method) == "post")
|
||||
{
|
||||
$postArray = fixer::input('post');
|
||||
$keys = array();
|
||||
if(isset($postArray->data->keys))
|
||||
{
|
||||
foreach($postArray->data->keys as $key)
|
||||
{
|
||||
if($module == 'testtask' and $field == 'typeList' and empty($key)) continue;
|
||||
if($key && in_array($key, $keys)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->custom->notice->repeatKey, $key)));
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if($module == 'project' and $field == 'unitList')
|
||||
{
|
||||
$data = fixer::input('post')->join('unitList', ',')->get();
|
||||
if(empty($data->unitList)) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->currencyNotEmpty));
|
||||
if(empty($data->defaultCurrency)) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->defaultNotEmpty));
|
||||
$this->loadModel('setting')->setItems("system.$module", $data);
|
||||
}
|
||||
elseif(($module == 'story' or $module == 'demand') and $field == 'review')
|
||||
{
|
||||
$data = fixer::input('post')
|
||||
->setDefault('forceReview', '')
|
||||
->setDefault('forceNotReview', '')
|
||||
->setDefault('forceReviewRoles', '')
|
||||
->setDefault('forceNotReviewRoles', '')
|
||||
->setDefault('forceReviewDepts', '')
|
||||
->setDefault('forceNotReviewDepts', '')
|
||||
->join('forceReview', ',')
|
||||
->join('forceReviewRoles', ',')
|
||||
->join('forceReviewDepts', ',')
|
||||
->join('forceNotReview', ',')
|
||||
->join('forceNotReviewRoles', ',')
|
||||
->join('forceNotReviewDepts', ',')
|
||||
->get();
|
||||
|
||||
foreach($data as $key => $value)
|
||||
{
|
||||
if($key == 'needReview') continue;
|
||||
if(strpos($key, 'Not') and $data->needReview == 0) $data->$key = '';
|
||||
if(!strpos($key, 'Not') and $data->needReview == 1) $data->$key = '';
|
||||
}
|
||||
|
||||
$this->loadModel('setting')->setItems("system.$module@{$this->config->vision}", $data);
|
||||
}
|
||||
elseif(($module == 'story' or $module == 'demand') and $field == 'reviewRules')
|
||||
{
|
||||
$data = fixer::input('post')->setDefault('superReviewers', '')->join('superReviewers', ',')->get();
|
||||
$this->loadModel('setting')->setItems("system.$module@{$this->config->vision}", $data);
|
||||
}
|
||||
elseif($module == 'testcase' and $field == 'review')
|
||||
{
|
||||
$review = fixer::input('post')->get();
|
||||
if($review->needReview) $data = fixer::input('post')->setDefault('forceNotReview', '')->join('forceNotReview', ',')->remove('forceReview')->get();
|
||||
if(!$review->needReview) $data = fixer::input('post')->setDefault('forceReview', '')->join('forceReview', ',')->remove('forceNotReview')->get();
|
||||
$this->loadModel('setting')->setItems("system.$module", $data);
|
||||
|
||||
$reviewCase = isset($review->reviewCase) ? $review->reviewCase : 0;
|
||||
if($review->needReview == 0 and $reviewCase)
|
||||
{
|
||||
$waitCases = $this->loadModel('testcase')->getByStatus(0, 0, 'all', 'wait');
|
||||
$this->testcase->batchReview(array_keys($waitCases), 'pass');
|
||||
}
|
||||
}
|
||||
elseif($module == 'bug' and $field == 'longlife')
|
||||
{
|
||||
$this->loadModel('setting')->setItems('system.bug', fixer::input('post')->get());
|
||||
}
|
||||
elseif($module == 'block' and $field == 'closed')
|
||||
{
|
||||
$data = fixer::input('post')->join('closed', ',')->get();
|
||||
$this->loadModel('setting')->setItem('system.block.closed', zget($data, 'closed', ''));
|
||||
}
|
||||
elseif($module == 'user' and $field == 'contactField')
|
||||
{
|
||||
$data = fixer::input('post')->join('contactField', ',')->get();
|
||||
if(!isset($data->contactField)) $data->contactField = '';
|
||||
$this->loadModel('setting')->setItem('system.user.contactField', $data->contactField);
|
||||
}
|
||||
elseif($module == 'user' and $field == 'deleted')
|
||||
{
|
||||
$data = fixer::input('post')->get();
|
||||
$this->loadModel('setting')->setItem('system.user.showDeleted', $data->showDeleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!$this->post->keys) return $this->sendError(sprintf($this->lang->error->notempty, $this->lang->custom->key));
|
||||
$lang = $_POST['lang'];
|
||||
$oldCustoms = $this->custom->getItems("lang=$lang&module=$module§ion=$field");
|
||||
foreach($_POST['keys'] as $index => $key)
|
||||
{
|
||||
if(!empty($key)) $key = trim($key);
|
||||
/* Invalid key. It should be numbers. (It includes severityList in bug module and priList in story, task, bug, testcasea, testtask and todo module.) */
|
||||
if($field == 'priList' or $field == 'severityList')
|
||||
{
|
||||
if(!is_numeric($key) or $key > 255) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidNumberKey));
|
||||
}
|
||||
if(!empty($key) and !isset($oldCustoms[$key]) and $key != 'n/a' and !validater::checkREG($key, '/^[a-z_A-Z_0-9]+$/')) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidStringKey));
|
||||
|
||||
/* The length of roleList in user module is less than 10. check it when saved. */
|
||||
if($module == 'user' and $field == 'roleList' and strlen($key) > 10) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidStrlen['ten']));
|
||||
|
||||
/* The length of typeList in todo module is less than 15. check it when saved. */
|
||||
if($module == 'todo' and $field == 'typeList' and strlen($key) > 15) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidStrlen['fifteen']));
|
||||
|
||||
/* The length of sourceList in story module and typeList in task module is less than 20, check it when saved. */
|
||||
if(($module == 'story' and $field == 'sourceList') or ($module == 'task' and $field == 'typeList'))
|
||||
{
|
||||
if(strlen($key) > 20) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidStrlen['twenty']));
|
||||
}
|
||||
|
||||
/* The length of stageList in testcase module is less than 255, check it when saved. */
|
||||
if($module == 'testcase' and $field == 'stageList' and strlen($key) > 255) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidStrlen['twoHundred']));
|
||||
|
||||
/* The length of field that in bug and testcase module and reasonList in story and task module is less than 30, check it when saved. */
|
||||
if(in_array($module, array('bug', 'testcase')) or (in_array($module, array('story', 'task')) and $field == 'reasonList'))
|
||||
{
|
||||
if(strlen($key) > 30) return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->invalidStrlen['thirty']));
|
||||
}
|
||||
}
|
||||
|
||||
$this->custom->deleteItems("lang=$lang&module=$module§ion=$field&vision={$this->config->vision}");
|
||||
if($lang == 'all') $this->custom->deleteItems("lang=$currentLang&module=$module§ion=$field&vision={$this->config->vision}");
|
||||
|
||||
$data = fixer::input('post')->get();
|
||||
$emptyKey = false;
|
||||
foreach($data->keys as $index => $key)
|
||||
{
|
||||
if(!$key && $emptyKey) continue;
|
||||
|
||||
//if(!$system and (!$value or !$key)) continue; //Fix bug #951.
|
||||
|
||||
$value = $data->values[$index];
|
||||
$system = $data->systems[$index];
|
||||
if($key and trim($value) === '') return $this->send(array('result' => 'fail', 'message' => $this->lang->custom->notice->valueEmpty)); // Fix bug #23538.
|
||||
|
||||
$this->custom->setItem("{$lang}.{$module}.{$field}.{$key}.{$system}", $value);
|
||||
|
||||
if(!$key) $emptyKey = true;
|
||||
}
|
||||
}
|
||||
$this->customZen->setFieldListForSet($module, $field);
|
||||
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
return $this->sendSuccess(array('load' => $this->createLink('custom', 'set', "module=$module&field=$field&lang=" . ($lang == 'all' ? $lang : ''))));
|
||||
}
|
||||
|
||||
/* Check whether the current language has been customized. */
|
||||
$lang = str_replace('_', '-', $lang);
|
||||
$dbFields = $this->custom->getItems("lang=$lang&module=$module§ion=$field&vision={$this->config->vision}");
|
||||
if(empty($dbFields)) $dbFields = $this->custom->getItems("lang=" . ($lang == $currentLang ? 'all' : $currentLang) . "&module=$module§ion=$field");
|
||||
if($dbFields)
|
||||
{
|
||||
$dbField = reset($dbFields);
|
||||
if($lang != $dbField->lang)
|
||||
{
|
||||
$lang = str_replace('-', "_", $dbField->lang);
|
||||
foreach($fieldList as $key => $value)
|
||||
{
|
||||
if(isset($dbFields[$key]) and $value != $dbFields[$key]->value) $fieldList[$key] = $dbFields[$key]->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->title = $this->lang->custom->common . $this->lang->colon . $this->lang->$module->common;
|
||||
$this->view->fieldList = $fieldList;
|
||||
$this->view->dbFields = $dbFields;
|
||||
$this->view->field = $field;
|
||||
$this->view->lang2Set = str_replace('_', '-', $lang);
|
||||
$this->view->module = $module;
|
||||
@@ -293,6 +68,7 @@ class custom extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原默认语言项。删除相关项。
|
||||
* Restore the default lang. Delete the related items.
|
||||
*
|
||||
* @param string $module
|
||||
@@ -302,38 +78,21 @@ class custom extends control
|
||||
*/
|
||||
public function restore(string $module, string $field)
|
||||
{
|
||||
if($module == 'user' and $field == 'contactField')
|
||||
if($module == 'user' && $field == 'contactField')
|
||||
{
|
||||
$this->loadModel('setting')->deleteItems("module=$module&key=$field");
|
||||
$this->loadModel('setting')->deleteItems("module={$module}&key={$field}");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->custom->deleteItems("module=$module§ion=$field");
|
||||
$this->custom->deleteItems("module={$module}§ion={$field}");
|
||||
}
|
||||
|
||||
return $this->sendSuccess(array('load' => true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set working mode function.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function working()
|
||||
{
|
||||
if($_POST)
|
||||
{
|
||||
$this->loadModel('setting')->setItem('system.common.global.flow', $this->post->flow);
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload'));
|
||||
}
|
||||
|
||||
$this->view->title = $this->lang->custom->working;
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Required.
|
||||
* 设置表单必填字段。
|
||||
* Set the required fields.
|
||||
*
|
||||
* @param string $moduleName
|
||||
* @access public
|
||||
|
||||
@@ -30,8 +30,9 @@ window.changeReview = function(e)
|
||||
|
||||
window.changeUnit = function()
|
||||
{
|
||||
const defaultCurrency = $('#defaultCurrency').val();
|
||||
$('#defaultCurrency').empty().append('<option></option>');
|
||||
const defaultCurrency = $('[name=defaultCurrency]').val();
|
||||
let defaultCurrencyItems = new Array();
|
||||
let index = 0;
|
||||
$('[name^=unitList]').each(function()
|
||||
{
|
||||
if($(this).prop('checked'))
|
||||
@@ -40,11 +41,15 @@ window.changeUnit = function()
|
||||
const firstStr = $(this).val() + '">';
|
||||
|
||||
text = text.substring(text.lastIndexOf(firstStr) + firstStr.length, text.lastIndexOf('<'));
|
||||
$('#defaultCurrency').append("<option value='" + $(this).val() + "'>" + text + '</option>');
|
||||
}
|
||||
defaultCurrencyItems[index] = {'value': $(this).val(), 'text': text};
|
||||
|
||||
index ++;
|
||||
}
|
||||
});
|
||||
|
||||
$('#defaultCurrency').val(defaultCurrency);
|
||||
let $defaultCurrencyPicker = $('[name=defaultCurrency]').zui('picker');
|
||||
$defaultCurrencyPicker.render({items: defaultCurrencyItems});
|
||||
$defaultCurrencyPicker.$.setValue(defaultCurrency);
|
||||
}
|
||||
|
||||
window.savaTestcaseReview = function()
|
||||
|
||||
+17
-9
@@ -124,25 +124,28 @@ class customModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义项。
|
||||
* Delete items.
|
||||
*
|
||||
* @param string $paramString see parseItemParam();
|
||||
* @param string $paramString see parseItemParam();
|
||||
* @access public
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteItems($paramString)
|
||||
public function deleteItems($paramString): bool
|
||||
{
|
||||
$this->prepareSQL($this->parseItemParam($paramString), 'delete')->exec();
|
||||
return !dao::isError();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析选择或删除项的参数字符串。
|
||||
* Parse the param string for select or delete items.
|
||||
*
|
||||
* @param string $paramString lang=xxx&module=story§ion=sourceList&key=customer and so on.
|
||||
* @param string $paramString lang=xxx&module=story§ion=sourceList&key=customer and so on.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function parseItemParam($paramString)
|
||||
public function parseItemParam(string $paramString): array
|
||||
{
|
||||
/* Parse the param string into array. */
|
||||
parse_str($paramString, $params);
|
||||
@@ -150,20 +153,25 @@ class customModel extends model
|
||||
/* Init fields not set in the param string. */
|
||||
$fields = 'lang,module,section,key,vision';
|
||||
$fields = explode(',', $fields);
|
||||
foreach($fields as $field) if(!isset($params[$field])) $params[$field] = '';
|
||||
foreach($fields as $field)
|
||||
{
|
||||
if(isset($params[$field])) continue;
|
||||
$params[$field] = '';
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个DAO对象来选择或删除一条或多条记录。
|
||||
* Create a DAO object to select or delete one or more records.
|
||||
*
|
||||
* @param array $params the params parsed by parseItemParam() method.
|
||||
* @param string $method select|delete.
|
||||
* @param array $params the params parsed by parseItemParam() method.
|
||||
* @param string $method select|delete.
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function prepareSQL($params, $method = 'select')
|
||||
public function prepareSQL(array $params, string $method = 'select'): object
|
||||
{
|
||||
return $this->dao->$method('*')->from(TABLE_LANG)->where('1 = 1')
|
||||
->beginIF($params['lang'])->andWhere('lang')->in($params['lang'])->fi()
|
||||
|
||||
@@ -68,46 +68,47 @@ class customTest
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义项。
|
||||
* Test delete items.
|
||||
*
|
||||
* @param string $paramString
|
||||
* @param string $paramString
|
||||
* @access public
|
||||
* @return int
|
||||
* @return array|bool
|
||||
*/
|
||||
public function deleteItemsTest($paramString)
|
||||
public function deleteItemsTest($paramString): array|bool
|
||||
{
|
||||
$objects = $this->objectModel->deleteItems($paramString);
|
||||
$result = $this->objectModel->deleteItems($paramString);
|
||||
|
||||
if(dao::isError()) return dao::getError();
|
||||
|
||||
return $objects;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析选择或删除项的参数字符串。
|
||||
* Test parse the param string for select or delete items.
|
||||
*
|
||||
* @param string $paramString
|
||||
* @param string $paramString lang=xxx&module=story§ion=sourceList&key=customer and so on.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function parseItemParamTest($paramString)
|
||||
public function parseItemParamTest(string $paramString): array
|
||||
{
|
||||
$objects = $this->objectModel->parseItemParam($paramString);
|
||||
$params = $this->objectModel->parseItemParam($paramString);
|
||||
|
||||
if(dao::isError()) return dao::getError();
|
||||
|
||||
return $objects;
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个DAO对象来选择或删除一条或多条记录。
|
||||
* Test create a DAO object to select or delete one or more records.
|
||||
*
|
||||
* @param string $paramString
|
||||
* @param string $paramString
|
||||
* @param string $method
|
||||
* @access public
|
||||
* @return array|int
|
||||
*/
|
||||
public function prepareSQLTest($paramString, $method = 'select')
|
||||
public function prepareSQLTest(string $paramString, string $method = 'select'): array|int
|
||||
{
|
||||
$params = $this->objectModel->parseItemParam($paramString);
|
||||
if($method == 'delete')
|
||||
|
||||
@@ -7,15 +7,8 @@ su('admin');
|
||||
/**
|
||||
|
||||
title=测试 customModel->deleteItems();
|
||||
timeout=0
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
测试参数为空 >> 0
|
||||
测试参数为lang >> 0
|
||||
测试参数为lang,module >> 0
|
||||
测试参数为lang,key,section >> 0
|
||||
测试参数为lang,key,section,module >> 0
|
||||
测试参数为lang,key,section,module,vision >> 0
|
||||
|
||||
*/
|
||||
|
||||
@@ -26,11 +19,11 @@ $key = 'key=1';
|
||||
$vision = 'vision=rnd';
|
||||
$paramString = array('', $lang, $lang.'&'.$module, $lang.'&'.$key.'&'.$section, $lang.'&'.$key.'&'.$section.'&'.$module, $lang.'&'.$key.'&'.$section.'&'.$module.'&'.$vision);
|
||||
|
||||
$custom = new customTest();
|
||||
$customTester = new customTest();
|
||||
|
||||
r($custom->deleteItemsTest($paramString[0])) && p() && e('0'); //测试参数为空
|
||||
r($custom->deleteItemsTest($paramString[1])) && p() && e('0'); //测试参数为lang
|
||||
r($custom->deleteItemsTest($paramString[2])) && p() && e('0'); //测试参数为lang,module
|
||||
r($custom->deleteItemsTest($paramString[3])) && p() && e('0'); //测试参数为lang,key,section
|
||||
r($custom->deleteItemsTest($paramString[4])) && p() && e('0'); //测试参数为lang,key,section,module
|
||||
r($custom->deleteItemsTest($paramString[5])) && p() && e('0'); //测试参数为lang,key,section,module,vision
|
||||
r($customTester->deleteItemsTest($paramString[0])) && p() && e('1'); //测试参数为空
|
||||
r($customTester->deleteItemsTest($paramString[1])) && p() && e('1'); //测试参数为lang
|
||||
r($customTester->deleteItemsTest($paramString[2])) && p() && e('1'); //测试参数为lang,module
|
||||
r($customTester->deleteItemsTest($paramString[3])) && p() && e('1'); //测试参数为lang,key,section
|
||||
r($customTester->deleteItemsTest($paramString[4])) && p() && e('1'); //测试参数为lang,key,section,module
|
||||
r($customTester->deleteItemsTest($paramString[5])) && p() && e('1'); //测试参数为lang,key,section,module,vision
|
||||
|
||||
@@ -7,15 +7,8 @@ su('admin');
|
||||
/**
|
||||
|
||||
title=测试 customModel->parseItemParam();
|
||||
timeout=0
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
测试参数为空 >> ,,,,
|
||||
测试参数为lang >> zh-cn,,,,
|
||||
测试参数为lang,module >> zh-cn,custom,,,
|
||||
测试参数为lang,key,section >> zh-cn,,URSRList,1,
|
||||
测试参数为lang,key,section,module >> zh-cn,custom,URSRList,1,
|
||||
测试参数为lang,key,section,module,vision >> zh-cn,custom,URSRList,1,rnd
|
||||
|
||||
*/
|
||||
|
||||
@@ -26,11 +19,10 @@ $key = 'key=1';
|
||||
$vision = 'vision=rnd';
|
||||
$paramString = array('', $lang, $lang.'&'.$module, $lang.'&'.$key.'&'.$section, $lang.'&'.$key.'&'.$section.'&'.$module, $lang.'&'.$key.'&'.$section.'&'.$module.'&'.$vision);
|
||||
|
||||
$custom = new customTest();
|
||||
|
||||
r($custom->parseItemParamTest($paramString[0])) && p('lang,module,section,key,vision') && e(',,,,'); //测试参数为空
|
||||
r($custom->parseItemParamTest($paramString[1])) && p('lang,module,section,key,vision') && e('zh-cn,,,,'); //测试参数为lang
|
||||
r($custom->parseItemParamTest($paramString[2])) && p('lang,module,section,key,vision') && e('zh-cn,custom,,,'); //测试参数为lang,module
|
||||
r($custom->parseItemParamTest($paramString[3])) && p('lang,module,section,key,vision') && e('zh-cn,,URSRList,1,'); //测试参数为lang,key,section
|
||||
r($custom->parseItemParamTest($paramString[4])) && p('lang,module,section,key,vision') && e('zh-cn,custom,URSRList,1,'); //测试参数为lang,key,section,module
|
||||
r($custom->parseItemParamTest($paramString[5])) && p('lang,module,section,key,vision') && e('zh-cn,custom,URSRList,1,rnd'); //测试参数为lang,key,section,module,vision
|
||||
$customTester = new customTest();
|
||||
r($customTester->parseItemParamTest($paramString[5])) && p('lang,module,section,key,vision') && e('zh-cn,custom,URSRList,1,rnd'); // 测试参数为lang,key,section,module,vision
|
||||
r($customTester->parseItemParamTest($paramString[4])) && p('lang,module,section,key,vision') && e('zh-cn,custom,URSRList,1,~~'); // 测试参数为lang,key,section,module
|
||||
r($customTester->parseItemParamTest($paramString[3])) && p('lang,module,section,key,vision') && e('zh-cn,~~,URSRList,1,~~'); // 测试参数为lang,key,section
|
||||
r($customTester->parseItemParamTest($paramString[2])) && p('lang,module,section,key,vision') && e('zh-cn,custom,~~,~~,~~'); // 测试参数为lang,module
|
||||
r($customTester->parseItemParamTest($paramString[1])) && p('lang,module,section,key,vision') && e('zh-cn,N/A,N/A,N/A,N/A'); // 测试参数为lang
|
||||
r($customTester->parseItemParamTest($paramString[0])) && p('lang,module,section,key,vision') && e('N/A,N/A,N/A,N/A,N/A'); // 测试参数为空
|
||||
|
||||
@@ -7,43 +7,35 @@ su('admin');
|
||||
/**
|
||||
|
||||
title=测试 customModel->prepareSQL();
|
||||
timeout=0
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
测试method为select,paramString参数为空 >> zh-cn,custom
|
||||
测试method为select,paramString参数为lang >> zh-cn,custom
|
||||
测试method为select,paramString参数为lang,module >> zh-cn,custom
|
||||
测试method为select,paramString参数为lang,key,section >> zh-cn,custom
|
||||
测试method为select,paramString参数为lang,key,section,module >> zh-cn,custom
|
||||
测试method为select,paramString参数为lang,key,section,module,vision >> zh-cn,custom
|
||||
测试method为delete,paramString参数为空 >> 14
|
||||
测试method为delete,paramString参数为lang >> 0
|
||||
测试method为delete,paramString参数为lang,module >> 0
|
||||
测试method为delete,paramString参数为lang,key,section >> 0
|
||||
测试method为delete,paramString参数为lang,key,section,module >> 0
|
||||
测试method为delete,paramString参数为lang,key,section,module,vision >> 0
|
||||
|
||||
*/
|
||||
|
||||
$lang = zdTable('lang');
|
||||
$lang->lang->range('zh-cn');
|
||||
$lang->module->range('custom');
|
||||
$lang->vision->range('rnd');
|
||||
$lang->gen(1, true);
|
||||
|
||||
$lang = 'lang=zh-cn';
|
||||
$module = 'module=custom';
|
||||
$section = 'section=URSRList';
|
||||
$key = 'key=1';
|
||||
$section = 'section=section1';
|
||||
$key = 'key=key1';
|
||||
$vision = 'vision=rnd';
|
||||
$paramString = array('', $lang, $lang.'&'.$module, $lang.'&'.$key.'&'.$section, $lang.'&'.$key.'&'.$section.'&'.$module, $lang.'&'.$key.'&'.$section.'&'.$module.'&'.$vision);
|
||||
$method = array('select', 'delete');
|
||||
|
||||
$custom = new customTest();
|
||||
|
||||
r($custom->prepareSQLTest($paramString[0], $method[0])) && p('1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为空
|
||||
r($custom->prepareSQLTest($paramString[1], $method[0])) && p('1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang
|
||||
r($custom->prepareSQLTest($paramString[2], $method[0])) && p('1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,module
|
||||
r($custom->prepareSQLTest($paramString[3], $method[0])) && p('1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,key,section
|
||||
r($custom->prepareSQLTest($paramString[4], $method[0])) && p('1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,key,section,module
|
||||
r($custom->prepareSQLTest($paramString[5], $method[0])) && p('1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,key,section,module,vision
|
||||
r($custom->prepareSQLTest($paramString[0], $method[1])) && p() && e('14'); //测试method为delete,paramString参数为空
|
||||
r($custom->prepareSQLTest($paramString[1], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang
|
||||
r($custom->prepareSQLTest($paramString[2], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,module
|
||||
r($custom->prepareSQLTest($paramString[3], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,key,section
|
||||
r($custom->prepareSQLTest($paramString[4], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,key,section,module
|
||||
r($custom->prepareSQLTest($paramString[5], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,key,section,module,vision
|
||||
$customTester = new customTest();
|
||||
r($customTester->prepareSQLTest($paramString[0], $method[0])) && p('key1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为空
|
||||
r($customTester->prepareSQLTest($paramString[1], $method[0])) && p('key1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang
|
||||
r($customTester->prepareSQLTest($paramString[2], $method[0])) && p('key1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,module
|
||||
r($customTester->prepareSQLTest($paramString[3], $method[0])) && p('key1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,key,section
|
||||
r($customTester->prepareSQLTest($paramString[4], $method[0])) && p('key1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,key,section,module
|
||||
r($customTester->prepareSQLTest($paramString[5], $method[0])) && p('key1:lang,module') && e('zh-cn,custom'); //测试method为select,paramString参数为lang,key,section,module,vision
|
||||
r($customTester->prepareSQLTest($paramString[0], $method[1])) && p() && e('1'); //测试method为delete,paramString参数为空
|
||||
r($customTester->prepareSQLTest($paramString[1], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang
|
||||
r($customTester->prepareSQLTest($paramString[2], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,module
|
||||
r($customTester->prepareSQLTest($paramString[3], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,key,section
|
||||
r($customTester->prepareSQLTest($paramString[4], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,key,section,module
|
||||
r($customTester->prepareSQLTest($paramString[5], $method[1])) && p() && e('0'); //测试method为delete,paramString参数为lang,key,section,module,vision
|
||||
|
||||
@@ -407,7 +407,9 @@ else
|
||||
|
||||
foreach($fieldList as $key => $value)
|
||||
{
|
||||
$system = isset($dbFields[$key]) ? $dbFields[$key]->system : 1;
|
||||
$system = isset($dbFields[$key]) ? $dbFields[$key]->system : 1;
|
||||
$keyLabel = $key === '' ? 'NULL' : $key;
|
||||
if($key == 0) $keyLabel = '0';
|
||||
|
||||
$formItems[] = formRow
|
||||
(
|
||||
@@ -426,7 +428,7 @@ else
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
set::label($key === '' ? 'NULL' : $key),
|
||||
set::label($keyLabel),
|
||||
set::name('values[]'),
|
||||
set::value(isset($dbFields[$key]) ? $dbFields[$key]->value : $value),
|
||||
set::readonly(empty($key))
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The zen file of custom module of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
||||
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @author Shujie Tian <tianshujie@easycorp.ltd>
|
||||
* @package custom
|
||||
* @link https://www.zentao.net
|
||||
*/
|
||||
class customZen extends custom
|
||||
{
|
||||
/**
|
||||
* 设置自定义字段列表。
|
||||
* Set the list of custom fields.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @param string $lang all|zh-cn|zh-tw|en|de|fr
|
||||
* @param string $currentLang all|zh-cn|zh-tw|en|de|fr
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function assignFieldListForSet(string $module = 'story', string $field = 'priList', string $lang = '', string $currentLang = ''): void
|
||||
{
|
||||
$this->app->loadLang($module);
|
||||
if($lang == 'all')
|
||||
{
|
||||
$fieldList = array();
|
||||
$items = $this->custom->getItems("lang=all&module={$module}§ion={$field}&vision={$this->config->vision}");
|
||||
foreach($items as $key => $item)
|
||||
{
|
||||
$fieldList[$key] = $item->value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$fieldList = zget($this->lang->$module, $field, array());
|
||||
}
|
||||
|
||||
/* Check whether the current language has been customized. */
|
||||
$lang = str_replace('_', '-', $lang);
|
||||
$dbFields = $this->custom->getItems("lang={$lang}&module={$module}§ion={$field}&vision={$this->config->vision}");
|
||||
if(empty($dbFields)) $dbFields = $this->custom->getItems("lang=" . ($lang == $currentLang ? 'all' : $currentLang) . "&module={$module}§ion={$field}");
|
||||
if($dbFields)
|
||||
{
|
||||
$dbField = reset($dbFields);
|
||||
if($lang != $dbField->lang)
|
||||
{
|
||||
$lang = str_replace('-', "_", $dbField->lang);
|
||||
foreach($fieldList as $key => $value)
|
||||
{
|
||||
if(isset($dbFields[$key]) && $value != $dbFields[$key]->value) $fieldList[$key] = $dbFields[$key]->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->view->fieldList = $fieldList;
|
||||
$this->view->dbFields = $dbFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义列表变量。
|
||||
* Set the list of custom variables.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @param string $lang all|zh-cn|zh-tw|en|de|fr
|
||||
* @param string $currentLang all|zh-cn|zh-tw|en|de|fr
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function assignVarsForSet(string $module = 'story', string $field = 'priList', string $lang = '', string $currentLang = ''): void
|
||||
{
|
||||
$this->assignFieldListForSet($module, $field, $lang, $currentLang);
|
||||
if($module == 'project' && $field == 'unitList')
|
||||
{
|
||||
$this->app->loadConfig($module);
|
||||
$unitList = zget($this->config->$module, 'unitList', '');
|
||||
$this->view->unitList = explode(',', $unitList);
|
||||
$this->view->defaultCurrency = zget($this->config->$module, 'defaultCurrency', 'CNY');
|
||||
}
|
||||
if(in_array($module, array('story', 'demand')) && $field == 'reviewRules')
|
||||
{
|
||||
$this->app->loadConfig($module);
|
||||
$this->view->reviewRule = zget($this->config->$module, 'reviewRules', 'allpass');
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
|
||||
$this->view->superReviewers = zget($this->config->$module, 'superReviewers', '');
|
||||
}
|
||||
if(in_array($module, array('story', 'testcase', 'demand')) && $field == 'review')
|
||||
{
|
||||
$this->app->loadConfig($module);
|
||||
$this->loadModel('user');
|
||||
if(in_array($module, array('story', 'demand')))
|
||||
{
|
||||
$this->view->depts = $this->loadModel('dept')->getDeptPairs();
|
||||
$this->view->forceReviewRoles = zget($this->config->$module, 'forceReviewRoles', '');
|
||||
$this->view->forceReviewDepts = zget($this->config->$module, 'forceReviewDepts', '');
|
||||
$this->view->forceNotReviewRoles = zget($this->config->$module, 'forceNotReviewRoles', '');
|
||||
$this->view->forceNotReviewDepts = zget($this->config->$module, 'forceNotReviewDepts', '');
|
||||
}
|
||||
$this->view->users = $module == 'story' ? $this->user->getCanCreateStoryUsers() : $this->user->getPairs('noclosed|nodeleted');
|
||||
$this->view->needReview = zget($this->config->$module, 'needReview', 1);
|
||||
$this->view->forceReview = zget($this->config->$module, 'forceReview', '');
|
||||
$this->view->forceNotReview = zget($this->config->$module, 'forceNotReview', '');
|
||||
}
|
||||
if($module == 'bug' && $field == 'longlife')
|
||||
{
|
||||
$this->app->loadConfig('bug');
|
||||
$this->view->longlife = $this->config->bug->longlife;
|
||||
}
|
||||
if($module == 'block' && $field == 'closed')
|
||||
{
|
||||
$this->loadModel('block');
|
||||
$closedBlock = isset($this->config->block->closed) ? $this->config->block->closed : '';
|
||||
$this->view->blockPairs = $this->block->getClosedBlockPairs($closedBlock);
|
||||
$this->view->closedBlock = $closedBlock;
|
||||
}
|
||||
if($module == 'user' && $field == 'deleted')
|
||||
{
|
||||
$this->app->loadConfig('user');
|
||||
$this->view->showDeleted = isset($this->config->user->showDeleted) ? $this->config->user->showDeleted : '0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义字段的值。
|
||||
* Set the value of the custom field.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @access protected
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function setFieldListForSet(string $module = 'story', string $field = 'priList'): string|bool
|
||||
{
|
||||
$data = $_POST;
|
||||
if($module == 'project' && $field == 'unitList')
|
||||
{
|
||||
if(empty($data['unitList'])) return dao::$errors['message'] = $this->lang->custom->currencyNotEmpty;
|
||||
if(empty($data['defaultCurrency'])) return dao::$errors['message'] = $this->lang->custom->defaultNotEmpty;
|
||||
$this->loadModel('setting')->setItems("system.$module", $data);
|
||||
}
|
||||
elseif(($module == 'story' || $module == 'demand') && $field == 'review')
|
||||
{
|
||||
$this->setStoryReview($data);
|
||||
}
|
||||
elseif(($module == 'story' || $module == 'demand') && $field == 'reviewRules')
|
||||
{
|
||||
if(!isset($data['superReviewers'])) $data['superReviewers'] = array();
|
||||
$data['superReviewers'] = implode(',', $data['superReviewers']);
|
||||
$this->loadModel('setting')->setItems("system.$module@{$this->config->vision}", $data);
|
||||
}
|
||||
elseif($module == 'testcase' && $field == 'review')
|
||||
{
|
||||
$this->setTestcaseReview($data);
|
||||
}
|
||||
elseif($module == 'bug' && $field == 'longlife')
|
||||
{
|
||||
$this->loadModel('setting')->setItems('system.bug', $data);
|
||||
}
|
||||
elseif($module == 'block' && $field == 'closed')
|
||||
{
|
||||
$data['closed'] = implode(',', $data['closed']);
|
||||
$this->loadModel('setting')->setItem('system.block.closed', zget($data, 'closed', ''));
|
||||
}
|
||||
elseif($module == 'user' && $field == 'contactField')
|
||||
{
|
||||
if(!isset($data['contactField'])) $data['contactField']= array();
|
||||
$data['contactField'] = implode(',', $data['contactField']);
|
||||
$this->loadModel('setting')->setItem('system.user.contactField', $data['contactField']);
|
||||
}
|
||||
elseif($module == 'user' && $field == 'deleted')
|
||||
{
|
||||
$this->loadModel('setting')->setItem('system.user.showDeleted', $data['showDeleted']);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!$this->post->keys) return dao::$errors['message'] = sprintf($this->lang->error->notempty, $this->lang->custom->key);
|
||||
$this->checkKeysForSet($module, $field);
|
||||
}
|
||||
|
||||
return !dao::isError();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查自定义key值是否合法。
|
||||
* Check whether the key of custom is valid.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @access protected
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkKeysForSet(string $module = 'story', string $field = 'priList'): bool
|
||||
{
|
||||
$this->checkDuplicateKeys($module, $field);
|
||||
if(dao::$errors) return false;
|
||||
|
||||
$this->checkInvalidKeys($module, $field);
|
||||
if(dao::$errors) return false;
|
||||
|
||||
$lang = $_POST['lang'];
|
||||
$this->custom->deleteItems("lang={$lang}&module={$module}§ion={$field}&vision={$this->config->vision}");
|
||||
if($lang == 'all') $this->custom->deleteItems("lang={$currentLang}&module={$module}§ion={$field}&vision={$this->config->vision}");
|
||||
|
||||
$this->checkEmptyKeys($module, $field);
|
||||
return !dao::isError();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查自定义key值是否重复。
|
||||
* Check whether the key of custom is duplicate.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @access protected
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkDuplicateKeys(string $module = 'story', string $field = 'priList'): bool|string
|
||||
{
|
||||
$keys = array();
|
||||
if(isset($_POST['keys']))
|
||||
{
|
||||
foreach($_POST['keys'] as $key)
|
||||
{
|
||||
if($module == 'testtask' && $field == 'typeList' && empty($key)) continue;
|
||||
if($key && in_array($key, $keys)) return dao::$errors['message'] = sprintf($this->lang->custom->notice->repeatKey, $key);
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查自定义key值是否正确。
|
||||
* Check whether the key of custom is valid.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @access protected
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkInvalidKeys(string $module = 'story', string $field = 'priList'): bool|string
|
||||
{
|
||||
$lang = $_POST['lang'];
|
||||
$oldCustoms = $this->custom->getItems("lang={$lang}&module={$module}§ion={$field}");
|
||||
foreach($_POST['keys'] as $index => $key)
|
||||
{
|
||||
if(!empty($key)) $key = trim($key);
|
||||
|
||||
/* Invalid key. It should be numbers. (It includes severityList in bug module && priList in story, task, bug, testcasea, testtask && todo module.) */
|
||||
if(($field == 'priList' || $field == 'severityList') && (!is_numeric($key) || $key > 255)) return dao::$errors['message'] = $this->lang->custom->notice->invalidNumberKey;
|
||||
|
||||
if(!empty($key) && !isset($oldCustoms[$key]) && $key != 'n/a' && !validater::checkREG($key, '/^[a-z_A-Z_0-9]+$/')) return dao::$errors['message'] = $this->lang->custom->notice->invalidStringKey;
|
||||
|
||||
/* The length of roleList in user module is less than 10. check it when saved. */
|
||||
if($module == 'user' && $field == 'roleList' && strlen($key) > 10) return dao::$errors['message'] = $this->lang->custom->notice->invalidStrlen['ten'];
|
||||
|
||||
/* The length of typeList in todo module is less than 15. check it when saved. */
|
||||
if($module == 'todo' && $field == 'typeList' && strlen($key) > 15) return dao::$errors['message'] = $this->lang->custom->notice->invalidStrlen['fifteen'];
|
||||
|
||||
/* The length of sourceList in story module && typeList in task module is less than 20, check it when saved. */
|
||||
if((($module == 'story' && $field == 'sourceList') || ($module == 'task' && $field == 'typeList')) && strlen($key) > 20) return dao::$errors['message'] = $this->lang->custom->notice->invalidStrlen['twenty'];
|
||||
|
||||
/* The length of field that in bug && testcase module && reasonList in story && task module is less than 30, check it when saved. */
|
||||
if((in_array($module, array('bug', 'testcase')) || (in_array($module, array('story', 'task')) && $field == 'reasonList')) && strlen($key) > 30) return dao::$errors['message'] = $this->lang->custom->notice->invalidStrlen['thirty'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查自定义key值是否为空。
|
||||
* Check whether the key of custom is empty.
|
||||
*
|
||||
* @param string $module todo|story|task|bug|testcase|testtask|user|project
|
||||
* @param string $field priList|typeList|statusList|sourceList|reasonList|stageList|reviewRules|reviewResultList|review|severityList|osList|browserList|resolutionList|longlife|resultList|roleList|contactField|deleted|unitList
|
||||
* @access protected
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkEmptyKeys(string $module = 'story', string $field = 'priList'): bool|string
|
||||
{
|
||||
$lang = $_POST['lang'];
|
||||
$emptyKey = false;
|
||||
$keys = array();
|
||||
foreach($_POST['keys'] as $index => $key)
|
||||
{
|
||||
if(!$key && $emptyKey) continue;
|
||||
|
||||
$value = $_POST['values'][$index];
|
||||
$system = $_POST['systems'][$index];
|
||||
if($key && trim($value) === '') return dao::$errors['message'] = $this->lang->custom->notice->valueEmpty; // Fix bug #23538.
|
||||
|
||||
$this->custom->setItem("{$lang}.{$module}.{$field}.{$key}.{$system}", $value);
|
||||
|
||||
if(!$key) $emptyKey = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置需求评审规则。
|
||||
* Set the review rules of story.
|
||||
*
|
||||
* @param array $data
|
||||
* @access protected
|
||||
* @return bool
|
||||
*/
|
||||
protected function setStoryReview(array $data): bool
|
||||
{
|
||||
$forceFields = array('forceReview', 'forceNotReview', 'forceReviewRoles', 'forceNotReviewRoles', 'forceReviewDepts', 'forceNotReviewDepts');
|
||||
foreach($forceFields as $forceField)
|
||||
{
|
||||
if(!isset($data[$forceField])) $data[$forceField] = array();
|
||||
$data[$forceField] = implode(',', $data[$forceField]);
|
||||
}
|
||||
|
||||
foreach($data as $key => $value)
|
||||
{
|
||||
if($key == 'needReview') continue;
|
||||
if(strpos($key, 'Not') && $data['needReview'] == 0) $data[$key] = '';
|
||||
if(!strpos($key, 'Not') && $data['needReview'] == 1) $data[$key] = '';
|
||||
}
|
||||
|
||||
$this->loadModel('setting')->setItems("system.$module@{$this->config->vision}", $data);
|
||||
|
||||
return !dao::isError();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用例评审规则。
|
||||
* Set testcase review rules.
|
||||
*
|
||||
* @param array $data
|
||||
* @access protected
|
||||
* @return bool
|
||||
*/
|
||||
protected function setTestcaseReview(array $data): bool
|
||||
{
|
||||
if($data['needReview'])
|
||||
{
|
||||
unset($data['forceReview']);
|
||||
if(!isset($data['forceNotReview'])) $data['forceNotReview'] = array();
|
||||
$data['forceNotReview'] = implode(',', $data['forceNotReview']);
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($data['forceNotReview']);
|
||||
if(!isset($data['forceReview'])) $data['forceReview'] = array();
|
||||
$data['forceReview'] = implode(',', $data['forceReview']);
|
||||
}
|
||||
$this->loadModel('setting')->setItems("system.testcase", $data);
|
||||
|
||||
$reviewCase = isset($data['reviewCase']) ? $data['reviewCase'] : 0;
|
||||
if($data['needReview'] == 0 && $reviewCase)
|
||||
{
|
||||
$waitCases = $this->loadModel('testcase')->getByStatus(0, 0, 'all', 'wait');
|
||||
$this->testcase->batchReview(array_keys($waitCases), 'pass');
|
||||
}
|
||||
|
||||
return !dao::isError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
$config->dept->newChildCount = 10;
|
||||
+12
-24
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The control file of dept module of ZenTaoPMS.
|
||||
*
|
||||
@@ -11,41 +12,28 @@
|
||||
*/
|
||||
class dept extends control
|
||||
{
|
||||
const NEW_CHILD_COUNT = 10;
|
||||
|
||||
/**
|
||||
* Construct function, set menu.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($moduleName = '', $methodName = '')
|
||||
{
|
||||
parent::__construct($moduleName, $methodName);
|
||||
$this->loadModel('company')->setMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse a department.
|
||||
* 部门结构维护页面。
|
||||
* Department management page.
|
||||
*
|
||||
* @param int $deptID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function browse($deptID = 0)
|
||||
public function browse(int $deptID = 0)
|
||||
{
|
||||
$parentDepts = $this->dept->getParents($deptID);
|
||||
$this->view->title = $this->lang->dept->manage . $this->lang->colon . $this->app->company->name;
|
||||
$this->view->deptID = $deptID;
|
||||
$this->view->depts = $this->dept->getTreeMenu($rootDeptID = 0, array('deptmodel', 'createManageLink'));
|
||||
$this->view->parentDepts = $parentDepts;
|
||||
$this->view->depts = $this->dept->getTreeMenu(0, array('deptmodel', 'createManageLink'));
|
||||
$this->view->sons = $this->dept->getSons($deptID);
|
||||
$this->view->tree = $this->dept->getDataStructure();
|
||||
$this->view->parentDepts = $this->dept->getParents($deptID);
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the departments order.
|
||||
* 部门结构排序。
|
||||
* Sort of departments.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
@@ -55,12 +43,13 @@ class dept extends control
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$this->dept->updateOrder($_POST['orders']);
|
||||
return print(js::reload('parent'));
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage childs.
|
||||
* 修改子部门结构。
|
||||
* Update child departments.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
@@ -70,8 +59,7 @@ class dept extends control
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$deptIDList = $this->dept->manageChild($_POST['parentDeptID'], $_POST['depts']);
|
||||
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $deptIDList));
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true));
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true, 'idList' => $deptIDList));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -471,8 +471,7 @@ class deptModel extends model
|
||||
$node->text = $node->name;
|
||||
if(isset($tree[$deptID]))
|
||||
{
|
||||
$node->items = $tree[$deptID];
|
||||
$node->actions = array('delete' => false);
|
||||
$node->items = $tree[$deptID];
|
||||
unset($tree[$deptID]);
|
||||
}
|
||||
$tree[$node->parent][] = $node;
|
||||
|
||||
@@ -10,48 +10,23 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace zin;
|
||||
|
||||
jsVar('editLinkTemp', createLink('dept', 'edit', "deptID={id}"));
|
||||
jsVar('deleteLinkTemp', createLink('dept', 'delete', "deptID={id}"));
|
||||
jsVar('deleteTip', $lang->dept->confirmDelete);
|
||||
|
||||
$deptActions = array();
|
||||
if(hasPriv('dept', 'edit'))
|
||||
$processTreeAction = function($tree) use (&$processTreeAction)
|
||||
{
|
||||
$deptAction = array();
|
||||
$deptAction['key'] = 'edit';
|
||||
$deptAction['icon'] = 'edit';
|
||||
$deptAction['hint'] = $lang->dept->edit;
|
||||
$deptAction['onClick'] = jsRaw('window.operateDept');
|
||||
$canEditDept = hasPriv('dept', 'edit');
|
||||
$canDeleteDept = hasPriv('dept', 'delete');
|
||||
foreach($tree as $node)
|
||||
{
|
||||
$actions = array();
|
||||
if($canEditDept) $actions[] = array('key' => 'edit', 'icon' => 'edit', 'hint' => $lang->dept->edit, 'onClick' => jsRaw('window.operateDept'));
|
||||
if(empty($node->items) && $canDeleteDept) $actions[] = array('key' => 'delete', 'icon' => 'trash', 'hint' => $lang->dept->delete, 'onClick' => jsRaw('window.operateDept'));
|
||||
if(!empty($node->items)) $node->items = $processTreeAction($node->items);
|
||||
$node->actions = $actions;
|
||||
}
|
||||
|
||||
$deptActions[] = $deptAction;
|
||||
}
|
||||
if(hasPriv('dept', 'delete'))
|
||||
{
|
||||
$deptAction = array();
|
||||
$deptAction['key'] = 'delete';
|
||||
$deptAction['icon'] = 'trash';
|
||||
$deptAction['hint'] = $lang->dept->delete;
|
||||
$deptAction['onClick'] = jsRaw('window.operateDept');
|
||||
return $tree;
|
||||
};
|
||||
|
||||
$deptActions[] = $deptAction;
|
||||
}
|
||||
|
||||
sidebar
|
||||
(
|
||||
width('400px'),
|
||||
set::showToggle(false),
|
||||
panel
|
||||
(
|
||||
set::title($title),
|
||||
tree
|
||||
(
|
||||
set::id('deptTree'),
|
||||
set::items($tree),
|
||||
set::hover(true),
|
||||
set::itemActions($deptActions)
|
||||
)
|
||||
)
|
||||
);
|
||||
$tree = $processTreeAction($tree);
|
||||
|
||||
$parentNames = array();
|
||||
foreach($parentDepts as $dept)
|
||||
@@ -86,8 +61,9 @@ foreach($sons as $dept)
|
||||
set::value($dept->name)
|
||||
);
|
||||
}
|
||||
|
||||
$emptyInputs = array();
|
||||
for($i = 0; $i < \DEPT::NEW_CHILD_COUNT ; $i ++)
|
||||
for($i = 0; $i < $config->dept->newChildCount; $i ++)
|
||||
{
|
||||
$emptyInputs[] = formGroup
|
||||
(
|
||||
@@ -97,6 +73,27 @@ for($i = 0; $i < \DEPT::NEW_CHILD_COUNT ; $i ++)
|
||||
);
|
||||
}
|
||||
|
||||
jsVar('editLinkTemp', createLink('dept', 'edit', "deptID={id}"));
|
||||
jsVar('deleteLinkTemp', createLink('dept', 'delete', "deptID={id}"));
|
||||
jsVar('deleteTip', $lang->dept->confirmDelete);
|
||||
|
||||
sidebar
|
||||
(
|
||||
width('400px'),
|
||||
set::showToggle(false),
|
||||
panel
|
||||
(
|
||||
set::title($title),
|
||||
tree
|
||||
(
|
||||
set::id('deptTree'),
|
||||
set::items($tree),
|
||||
set::hover(true),
|
||||
set::itemActions($deptActions)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
panel
|
||||
(
|
||||
set::title($lang->dept->manageChild),
|
||||
@@ -147,4 +144,3 @@ panel
|
||||
);
|
||||
|
||||
render();
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
if($sonDept->order > $maxOrder) $maxOrder = $sonDept->order;
|
||||
echo html::input("depts[id$sonDept->id]", $sonDept->name, "class='form-control'");
|
||||
}
|
||||
for($i = 0; $i < DEPT::NEW_CHILD_COUNT ; $i ++) echo html::input("depts[]", '', "class='form-control'");
|
||||
for($i = 0; $i < $config->dept->newChildCount; $i ++) echo html::input("depts[]", '', "class='form-control'");
|
||||
?>
|
||||
</td>
|
||||
<td></td>
|
||||
|
||||
@@ -10,7 +10,7 @@ $(function()
|
||||
|
||||
if(requiredFields.indexOf('content') >= 0)
|
||||
{
|
||||
if($('[name="content"]').val() == '')
|
||||
if($('input[name="content"]').val() == '')
|
||||
{
|
||||
zui.Modal.alert(contentNotEmpty);
|
||||
return false;
|
||||
|
||||
@@ -183,10 +183,8 @@ else
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->doc->keywords),
|
||||
input
|
||||
(
|
||||
set::name('keywords')
|
||||
)
|
||||
set::control('input'),
|
||||
set::name('keywords')
|
||||
),
|
||||
formGroup
|
||||
(
|
||||
|
||||
@@ -136,11 +136,9 @@ form
|
||||
(
|
||||
strpos($config->doc->officeTypes, $doc->type) === false ? setClass('hidden') : null,
|
||||
set::label($lang->doc->keywords),
|
||||
input
|
||||
(
|
||||
set::name('keywords'),
|
||||
set::value($doc->keywords)
|
||||
)
|
||||
set::control('input'),
|
||||
set::name('keywords'),
|
||||
set::value($doc->keywords)
|
||||
),
|
||||
formGroup
|
||||
(
|
||||
|
||||
@@ -81,7 +81,7 @@ if($doc->keywords)
|
||||
{
|
||||
$keywordsLabel[] = span
|
||||
(
|
||||
setClass('label secondary-outline'),
|
||||
setClass('label secondary-outline ml-2'),
|
||||
$keywords
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class editor extends control
|
||||
$extension = 'php';
|
||||
if($filePath)
|
||||
{
|
||||
$filePath = helper::safe64Decode($filePath);
|
||||
$filePath = realpath($filePath);
|
||||
if(strpos(strtolower($filePath), strtolower($this->app->getBasePath())) !== 0) return print($this->lang->editor->editFileError);
|
||||
if($action == 'extendOther' and file_exists($filePath)) $this->view->showContent = file_get_contents($filePath);
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ $config->execution->importBug->dtable->fieldList['taskPri']['type'] = 'h
|
||||
$config->execution->importBug->dtable->fieldList['taskPri']['width'] = 68;
|
||||
$config->execution->importBug->dtable->fieldList['taskPri']['sortType'] = false;
|
||||
$config->execution->importBug->dtable->fieldList['taskPri']['type'] = 'control';
|
||||
$config->execution->importBug->dtable->fieldList['taskPri']['control'] = 'select';
|
||||
$config->execution->importBug->dtable->fieldList['taskPri']['control'] = 'picker';
|
||||
$config->execution->importBug->dtable->fieldList['taskPri']['controlItems'] = $lang->task->priList;
|
||||
|
||||
$config->execution->importBug->dtable->fieldList['assignedTo']['title'] = $lang->bug->assignedTo;
|
||||
|
||||
@@ -1635,14 +1635,11 @@ class executionModel extends model
|
||||
$parentList = array();
|
||||
$emptyHour = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0);
|
||||
$today = helper::today();
|
||||
$hours = $this->loadModel('project')->computeProgress($executions);
|
||||
$burns = $this->getBurnData($executions);
|
||||
foreach($executions as $execution)
|
||||
{
|
||||
$execution->productName = isset($productList[$execution->id]) ? trim($productList[$execution->id]->productName, ',') : '';
|
||||
$execution->end = date(DT_DATE1, strtotime($execution->end));
|
||||
$execution->hours = isset($hours[$execution->id]) ? $hours[$execution->id] : (object)$emptyHour;
|
||||
$execution->teamCount = isset($memberGroup[$execution->id]) ? $memberGroup[$execution->id]->teams : 0;
|
||||
|
||||
if(isset($executions[$execution->parent])) $executions[$execution->parent]->isParent = 1;
|
||||
if(empty($productID) && !empty($productList[$execution->id])) $execution->product = trim($productList[$execution->id]->product, ',');
|
||||
@@ -4684,10 +4681,6 @@ class executionModel extends model
|
||||
$execution->project = $execution->projectName;
|
||||
$execution->parent = ($execution->parent && $execution->grade > 1) ? 'pid' . (string)$execution->parent : '';
|
||||
$execution->isParent = !empty($execution->isParent) or !empty($execution->tasks);
|
||||
$execution->progress = $execution->hours->progress;
|
||||
$execution->totalEstimate = $execution->hours->totalEstimate;
|
||||
$execution->totalConsumed = $execution->hours->totalConsumed;
|
||||
$execution->totalLeft = $execution->hours->totalLeft;
|
||||
$execution->actions = array();
|
||||
foreach($this->config->projectExecution->dtable->fieldList['actions'][$execution->projectModel] as $actionKey)
|
||||
{
|
||||
|
||||
@@ -225,7 +225,7 @@ modal
|
||||
set::text($lang->execution->next),
|
||||
set::btnType('submit'),
|
||||
set::type('primary'),
|
||||
setData(array('dismiss' => 'primary'))
|
||||
setData(array('dismiss' => 'modal'))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -13,11 +13,13 @@ function changeEngine(event)
|
||||
|
||||
var repos = engine == 'gitlab' ? gitlabRepos : repoPairs;
|
||||
var repoItems = [];
|
||||
var repoID = 0;
|
||||
for(i in repos)
|
||||
{
|
||||
if(repoID == 0) repoID = i;
|
||||
repoItems.push({'text': repos[i], 'value': i});
|
||||
}
|
||||
zui.Picker.query('#repo').render({items: repoItems});
|
||||
zui.Picker.query('#repo').render({items: repoItems, value: repoID});
|
||||
|
||||
if(engine == 'gitlab')
|
||||
{
|
||||
@@ -36,6 +38,8 @@ function changeEngine(event)
|
||||
if(engine == 'jenkins' || frame != 'sonarqube') items.push({'text': frameList[frame], 'value': frame});
|
||||
}
|
||||
zui.Picker.query('#frame').render({items: items});
|
||||
|
||||
changeRepo();
|
||||
}
|
||||
|
||||
function changeFrame(event)
|
||||
@@ -56,7 +60,7 @@ function changeFrame(event)
|
||||
|
||||
function changeRepo(event)
|
||||
{
|
||||
const repoID = $(event.target).val();
|
||||
const repoID = $('input[name="repo"]').val();
|
||||
if(repoID <= 0) return;
|
||||
|
||||
var link = $.createLink('repo', 'ajaxLoadProducts', 'repoID=' + repoID);
|
||||
|
||||
@@ -1444,10 +1444,6 @@ class kanbanModel extends model
|
||||
->andWhere('action')->eq('opened')
|
||||
->fetchPairs();
|
||||
}
|
||||
elseif($fromType == 'execution')
|
||||
{
|
||||
$executionProgress = $this->loadModel('project')->computeProgress($objects);
|
||||
}
|
||||
|
||||
/* Data for constructing the card. */
|
||||
foreach($objectCards as $objectID => $cardsInfo)
|
||||
@@ -1473,7 +1469,7 @@ class kanbanModel extends model
|
||||
if($delay > 0) $objectCard->delay = $delay;
|
||||
}
|
||||
$objectCard->execType = $object->type;
|
||||
$objectCard->progress = isset($executionProgress[$objectID]->progress) ? $executionProgress[$objectID]->progress : 0;
|
||||
$objectCard->progress = $object->progress;
|
||||
|
||||
$parentExecutions = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('id')->in(trim($object->path, ','))->andWhere('type')->in('stage,kanban,sprint')->orderBy('grade')->fetchPairs();
|
||||
$objectCard->title = implode('/', $parentExecutions);
|
||||
@@ -1743,7 +1739,7 @@ class kanbanModel extends model
|
||||
if($cardIdList)
|
||||
{
|
||||
$cardsData = $this->buildExecutionCards($cardsData, $column, $lane->type, $cardIdList, $objectGroup, $searchValue, $menus);
|
||||
$columnsData[$column->id]['cards'] = count($cardsData[$column->lane][$column->id]);
|
||||
$columnsData[$column->id]['cards'] = empty($cardsData[$column->lane][$column->id]) ? 0 : count($cardsData[$column->lane][$column->id]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ formPanel
|
||||
setClass('primary-pale'),
|
||||
set::icon('copy'),
|
||||
set::url('#copyKanbanModal'),
|
||||
set('data-destoryOnHide', true),
|
||||
set('data-toggle', 'modal'),
|
||||
setData(array('destoryOnHide' => true, 'toggle' => 'modal')),
|
||||
$lang->kanban->copy . $lang->kanban->common
|
||||
)
|
||||
),
|
||||
@@ -48,7 +47,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('WIPCountBox'),
|
||||
setID('WIPCountBox'),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanban->WIPCount),
|
||||
@@ -63,7 +62,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('spaceBox'),
|
||||
setID('spaceBox'),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
@@ -79,7 +78,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('nameBox'),
|
||||
setID('nameBox'),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
@@ -90,8 +89,8 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('ownerBox'),
|
||||
set::className($type == 'private' ? 'hidden' : ''),
|
||||
setID('ownerBox'),
|
||||
setClass($type == 'private' ? 'hidden' : ''),
|
||||
formGroup
|
||||
(
|
||||
set::width('1/2'),
|
||||
@@ -106,10 +105,10 @@ formPanel
|
||||
),
|
||||
span
|
||||
(
|
||||
set('class', 'input-group-addon'),
|
||||
setClass('input-group-addon'),
|
||||
a
|
||||
(
|
||||
set('id', 'allUsers'),
|
||||
setID('allUsers'),
|
||||
set('href', 'javascript:;'),
|
||||
$lang->kanban->allUsers
|
||||
)
|
||||
@@ -119,8 +118,8 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('teamBox'),
|
||||
set::className($type == 'private' ? 'hidden' : ''),
|
||||
setID('teamBox'),
|
||||
setClass($type == 'private' ? 'hidden' : ''),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanban->team),
|
||||
@@ -135,7 +134,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('fixedColBox'),
|
||||
setID('fixedColBox'),
|
||||
formGroup
|
||||
(
|
||||
setClass('items-center'),
|
||||
@@ -149,10 +148,10 @@ formPanel
|
||||
),
|
||||
div
|
||||
(
|
||||
set::className('flex items-center ml-8 py-1'),
|
||||
setClass('flex items-center ml-8 py-1'),
|
||||
set::style(array('padding-left' => '1px')),
|
||||
span($lang->kanban->colWidth),
|
||||
input(set::type('number'), set::min($config->colWidth), set::name('colWidth'), set::className('w-16 size-sm mx-1'), set::value(isset($copyKanban->colWidth) ? $copyKanban->colWidth : $config->colWidth)),
|
||||
input(set::type('number'), set::min($config->colWidth), set::name('colWidth'), setClass('w-16 size-sm mx-1'), set::value(isset($copyKanban->colWidth) ? $copyKanban->colWidth : $config->colWidth)),
|
||||
span('px')
|
||||
),
|
||||
div
|
||||
@@ -165,7 +164,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('autoColBox'),
|
||||
setID('autoColBox'),
|
||||
set::style(array('margin-top' => '0px')),
|
||||
formGroup
|
||||
(
|
||||
@@ -180,12 +179,12 @@ formPanel
|
||||
),
|
||||
div
|
||||
(
|
||||
set::className('flex items-center ml-5 py-1'),
|
||||
setClass('flex items-center ml-5 py-1'),
|
||||
span($lang->kanban->colWidth),
|
||||
input(set::type('number'), set::min($config->minColWidth), set::name('minColWidth'), set::className('w-16 size-sm mx-1'), set::value(isset($copyKanban->minColWidth) ? $copyKanban->minColWidth : $config->minColWidth)),
|
||||
input(set::type('number'), set::min($config->minColWidth), set::name('minColWidth'), setClass('w-16 size-sm mx-1'), set::value(isset($copyKanban->minColWidth) ? $copyKanban->minColWidth : $config->minColWidth)),
|
||||
span('px'),
|
||||
span('~', set::className('mx-1')),
|
||||
input(set::type('number'), set::min($config->maxColWidth), set::name('maxColWidth'), set::className('w-16 size-sm mx-1'), set::value(isset($copyKanban->maxColWidth) ? $copyKanban->maxColWidth : $config->maxColWidth)),
|
||||
span('~', setClass('mx-1')),
|
||||
input(set::type('number'), set::min($config->maxColWidth), set::name('maxColWidth'), setClass('w-16 size-sm mx-1'), set::value(isset($copyKanban->maxColWidth) ? $copyKanban->maxColWidth : $config->maxColWidth)),
|
||||
span('px')
|
||||
),
|
||||
div
|
||||
@@ -228,7 +227,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('archiveBox'),
|
||||
setID('archiveBox'),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanban->archive),
|
||||
@@ -243,7 +242,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('manageProgressBox'),
|
||||
setID('manageProgressBox'),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanban->manageProgress),
|
||||
@@ -258,7 +257,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('alignmentBox'),
|
||||
setID('alignmentBox'),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanban->alignment),
|
||||
@@ -273,7 +272,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('descBox'),
|
||||
setID('descBox'),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanban->desc),
|
||||
@@ -286,8 +285,8 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('whitelistBox'),
|
||||
set::className($type != 'private' ? 'hidden' : ''),
|
||||
setID('whitelistBox'),
|
||||
setClass($type != 'private' ? 'hidden' : ''),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->whitelist),
|
||||
@@ -299,8 +298,8 @@ formPanel
|
||||
set::value(isset($copyKanban->whitelist) ? $copyKanban->whitelist : '')
|
||||
)
|
||||
),
|
||||
input(set::className('hidden'), set::name('copyKanbanID'), set::value($copyKanbanID)),
|
||||
input(set::className('hidden'), set::name('copyRegion'), set::value($copyRegion))
|
||||
input(setClass('hidden'), set::name('copyKanbanID'), set::value($copyKanbanID)),
|
||||
input(setClass('hidden'), set::name('copyRegion'), set::value($copyRegion))
|
||||
)
|
||||
);
|
||||
|
||||
@@ -310,11 +309,11 @@ if($copyKanbanID != 0)
|
||||
$kanbanList[] = cell
|
||||
(
|
||||
set::width('1/3'),
|
||||
set::className('p-2'),
|
||||
setClass('p-2'),
|
||||
div
|
||||
(
|
||||
set::className('copy-card p-2 border rounded-md text-danger'),
|
||||
icon('cancel', set::className('pr-2')),
|
||||
setClass('copy-card p-2 border rounded-md text-danger'),
|
||||
icon('cancel', setClass('pr-2')),
|
||||
$lang->kanban->cancelCopy
|
||||
)
|
||||
);
|
||||
@@ -327,15 +326,12 @@ if(!empty($kanbans))
|
||||
$kanbanList[] = cell
|
||||
(
|
||||
set::width('1/3'),
|
||||
set::className('p-2'),
|
||||
setClass('p-2'),
|
||||
div
|
||||
(
|
||||
set('data-on', 'click'),
|
||||
set('data-call', 'clickCopyCard'),
|
||||
set('data-params', 'event'),
|
||||
set('data-id', $id),
|
||||
set::className('copy-card p-2 border rounded-md'),
|
||||
icon('kanban', set::className('pr-2')),
|
||||
setData(array('on' => 'click', 'call' => 'clickCopyCard', 'params' => 'event', 'id' => $id)),
|
||||
setClass('copy-card p-2 border rounded-md'),
|
||||
icon('kanban', setClass('pr-2')),
|
||||
$name
|
||||
)
|
||||
);
|
||||
@@ -349,7 +345,7 @@ else
|
||||
icon('exclamation-sign icon-2x pl-2 text-warning'),
|
||||
span
|
||||
(
|
||||
set::className('font-bold ml-2'),
|
||||
setClass('font-bold ml-2'),
|
||||
$lang->kanban->copyNoKanban
|
||||
)
|
||||
);
|
||||
@@ -359,12 +355,12 @@ modalTrigger
|
||||
(
|
||||
modal
|
||||
(
|
||||
set::id('copyKanbanModal'),
|
||||
setID('copyKanbanModal'),
|
||||
to::header
|
||||
(
|
||||
span
|
||||
(
|
||||
set::className('copy-title article-h1'),
|
||||
setClass('copy-title article-h1'),
|
||||
$lang->kanban->copyTitle,
|
||||
),
|
||||
span
|
||||
@@ -381,7 +377,7 @@ modalTrigger
|
||||
),
|
||||
div
|
||||
(
|
||||
set::id('copyKanbans'),
|
||||
setID('copyKanbans'),
|
||||
setClass('flex flex-wrap'),
|
||||
$kanbanList
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ formPanel
|
||||
),
|
||||
formRow
|
||||
(
|
||||
set::id('otherLaneBox'),
|
||||
setID('otherLaneBox'),
|
||||
formGroup
|
||||
(
|
||||
set::label($lang->kanbanlane->otherlane),
|
||||
|
||||
@@ -26,9 +26,9 @@ featureBar
|
||||
(
|
||||
inputGroup
|
||||
(
|
||||
span(set::className('input-group-addon'), $lang->kanban->selectedProduct),
|
||||
span(setClass('input-group-addon'), $lang->kanban->selectedProduct),
|
||||
picker(set::name('project'), set::items($projects), set::value($selectedProjectID), set::style(array('width' => '200px')), set('data-on', 'change'), set('data-call', 'changeProject'), set::required(true)),
|
||||
span(set::className('input-group-addon'), $lang->kanban->selectedLane),
|
||||
span(setClass('input-group-addon'), $lang->kanban->selectedLane),
|
||||
picker(set::name('lane'), set::items($lanePairs), set::style(array('width' => '200px')), set::required(true))
|
||||
)
|
||||
);
|
||||
@@ -50,9 +50,9 @@ unset($config->execution->dtable->fieldList['actions']);
|
||||
|
||||
formBase
|
||||
(
|
||||
set::id('linkForm'),
|
||||
setID('linkForm'),
|
||||
set::actions(''),
|
||||
set::className('mt-2'),
|
||||
setClass('mt-2'),
|
||||
dtable
|
||||
(
|
||||
set::fixedLeftWidth('0.33'),
|
||||
|
||||
@@ -35,9 +35,9 @@ featureBar
|
||||
(
|
||||
inputGroup
|
||||
(
|
||||
span(set::className('input-group-addon'), $lang->kanban->selectedProduct),
|
||||
span(setClass('input-group-addon'), $lang->kanban->selectedProduct),
|
||||
picker(set::name('product'), set::items($products), set::value($selectedProductID), set::style(array('width' => '200px')), set('data-on', 'change'), set('data-call', 'changeProduct'), set::required(true)),
|
||||
span(set::className('input-group-addon'), $lang->kanban->selectedLane),
|
||||
span(setClass('input-group-addon'), $lang->kanban->selectedLane),
|
||||
picker(set::name('lane'), set::items($lanePairs), set::style(array('width' => '200px')), set::required(true))
|
||||
)
|
||||
);
|
||||
@@ -49,9 +49,9 @@ unset($config->release->dtable->fieldList['actions']);
|
||||
|
||||
formBase
|
||||
(
|
||||
set::id('linkForm'),
|
||||
setID('linkForm'),
|
||||
set::actions(''),
|
||||
set::className('mt-2'),
|
||||
setClass('mt-2'),
|
||||
dtable
|
||||
(
|
||||
set::fixedLeftWidth('0.33'),
|
||||
|
||||
@@ -67,7 +67,7 @@ class metric extends control
|
||||
$resultHeader = $this->metricZen->getViewTableHeader($metric);
|
||||
$resultData = $this->metricZen->getViewTableData($metric, $result);
|
||||
}
|
||||
|
||||
|
||||
$this->view->metrics = $metrics;
|
||||
$this->view->groupMetrics = $groupMetrics;
|
||||
$this->view->current = $current;
|
||||
@@ -80,6 +80,7 @@ class metric extends control
|
||||
$this->view->filtersBase64 = $filtersBase64;
|
||||
$this->view->resultHeader = $resultHeader;
|
||||
$this->view->resultData = $resultData;
|
||||
$this->view->tableWidth = $this->metricZen->getViewTableWidth($resultHeader);
|
||||
$this->view->chartTypeList = $this->metric->getChartTypeList($resultHeader);
|
||||
$this->view->echartOptions = $this->metric->getEchartsOptions($resultHeader, $resultData);
|
||||
$this->display();
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
i.star-empty {color: var(--color-slate-400);}
|
||||
i.star {color: var(--color-warning-300);}
|
||||
|
||||
.table-and-chart .table-side {flex: 0 0 480px;}
|
||||
.table-and-chart .chart-side {flex: 0 0 calc(100% - 480px);}
|
||||
.table-and-chart .table-side {flex-shrink: 1; max-width: 50%;}
|
||||
.table-and-chart .chart-side {flex-grow: 1;}
|
||||
.table-and-chart .chart-center {display:flex; justify-content: center; align-items: center;}
|
||||
|
||||
.icon-18 {font-size: 18px;}
|
||||
|
||||
@@ -21,7 +21,7 @@ $fnGenerateSide = function() use($groupMetrics, $current, $viewType, $scope, $la
|
||||
|
||||
$metricList[] = li
|
||||
(
|
||||
set::class('metric-group'),
|
||||
set::className('metric-group'),
|
||||
$lang->metric->objectList[$key]
|
||||
);
|
||||
|
||||
@@ -350,6 +350,7 @@ div
|
||||
div
|
||||
(
|
||||
setClass('table-side'),
|
||||
setStyle(array('flex-basis' => $tableWidth . 'px')),
|
||||
div
|
||||
(
|
||||
$resultData ? dtable
|
||||
|
||||
+12
-1
@@ -397,7 +397,7 @@ class metricZen extends metric
|
||||
|
||||
if(!$result) return array
|
||||
(
|
||||
array('name' => 'value', 'title' => $this->lang->metric->value),
|
||||
array('name' => 'value', 'title' => $this->lang->metric->value, 'width' => 96),
|
||||
array('name' => 'calcTime', 'title' => $this->lang->metric->calcTime, 'width' => 150)
|
||||
);
|
||||
|
||||
@@ -415,6 +415,17 @@ class metricZen extends metric
|
||||
return $header;
|
||||
}
|
||||
|
||||
protected function getViewTableWidth($headers)
|
||||
{
|
||||
$width = 0;
|
||||
foreach($headers as $header)
|
||||
{
|
||||
$width += isset($header['width']) ? $header['width'] : 160;
|
||||
}
|
||||
|
||||
return $width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取度量数据表的数据。
|
||||
* Get data of result table.
|
||||
|
||||
@@ -45,7 +45,4 @@ formPanel
|
||||
set::rows('6')
|
||||
)
|
||||
);
|
||||
history();
|
||||
|
||||
render();
|
||||
|
||||
history(set::objectID($MR->id));
|
||||
|
||||
@@ -100,7 +100,7 @@ $lang->pivot->singleColor[] = 'F6BD0F';
|
||||
$lang->pivot->projectDeviation = "{$lang->execution->common}偏差报表";
|
||||
$lang->pivot->productSummary = $lang->productCommon . '汇总表';
|
||||
$lang->pivot->bugCreate = 'Bug创建表';
|
||||
$lang->pivot->bugAssign = 'Bug指派表';
|
||||
$lang->pivot->bugAssign = '未解决Bug指派表';
|
||||
$lang->pivot->workload = '员工负载表';
|
||||
$lang->pivot->workloadAB = '工作负载';
|
||||
$lang->pivot->bugOpenedDate = 'Bug创建时间';
|
||||
@@ -119,7 +119,7 @@ $lang->pivotList->staff = new stdclass();
|
||||
$lang->pivotList->product->lists[10] = $lang->productCommon . '汇总表|pivot|productsummary';
|
||||
$lang->pivotList->project->lists[10] = "{$lang->execution->common}偏差报表|pivot|projectdeviation";
|
||||
$lang->pivotList->test->lists[10] = 'Bug创建表|pivot|bugcreate';
|
||||
$lang->pivotList->test->lists[13] = 'Bug指派表|pivot|bugassign';
|
||||
$lang->pivotList->test->lists[13] = '未解决Bug指派表|pivot|bugassign';
|
||||
$lang->pivotList->staff->lists[10] = '员工负载表|pivot|workload';
|
||||
|
||||
$lang->pivot->id = '编号';
|
||||
|
||||
@@ -41,7 +41,7 @@ $lang->pivot->singleColor[] = 'F6BD0F';
|
||||
$lang->pivot->projectDeviation = "{$lang->execution->common}偏差報表";
|
||||
$lang->pivot->productSummary = $lang->productCommon . '彙總表';
|
||||
$lang->pivot->bugCreate = 'Bug創建表';
|
||||
$lang->pivot->bugAssign = 'Bug指派表';
|
||||
$lang->pivot->bugAssign = '未解决Bug指派表';
|
||||
$lang->pivot->workload = '員工負載表';
|
||||
$lang->pivot->workloadAB = '工作負載';
|
||||
$lang->pivot->bugOpenedDate = 'Bug創建時間';
|
||||
@@ -60,7 +60,7 @@ $lang->pivotList->staff = new stdclass();
|
||||
$lang->pivotList->product->lists[10] = $lang->productCommon . '彙總表|pivot|productsummary';
|
||||
$lang->pivotList->project->lists[10] = "{$lang->execution->common}偏差報表|pivot|projectdeviation";
|
||||
$lang->pivotList->test->lists[10] = 'Bug創建表|pivot|bugcreate';
|
||||
$lang->pivotList->test->lists[13] = 'Bug指派表|pivot|bugassign';
|
||||
$lang->pivotList->test->lists[13] = '未解决Bug指派表|pivot|bugassign';
|
||||
$lang->pivotList->staff->lists[10] = '員工負載表|pivot|workload';
|
||||
|
||||
$lang->pivot->id = '編號';
|
||||
|
||||
@@ -2112,7 +2112,7 @@ class pivotModel extends model
|
||||
switch($type)
|
||||
{
|
||||
case 'user':
|
||||
$options = $this->loadModel('user')->getPairs();
|
||||
$options = $this->loadModel('user')->getPairs('noletter');
|
||||
break;
|
||||
case 'product':
|
||||
$options = $this->loadModel('product')->getPairs();
|
||||
|
||||
@@ -46,11 +46,10 @@ $config->product->report->planLabels = array();
|
||||
$config->product->report->planLabels[] = '';
|
||||
|
||||
$config->product->statisticFields = array();
|
||||
$config->product->statisticFields['requirements'] = array('draftRequirements', 'activeRequirements', 'changingRequirements', 'reviewingRequirements', 'closedRequirements');
|
||||
$config->product->statisticFields['stories'] = array('draftStories', 'activeStories', 'changingStories', 'reviewingStories', 'closedStories', 'finishClosedStories', 'unclosedStories');
|
||||
$config->product->statisticFields['bugs'] = array('unResolvedBugs', 'closedBugs', 'fixedBugs');
|
||||
$config->product->statisticFields['plans'] = array('plans');
|
||||
$config->product->statisticFields['releases'] = array('releases');
|
||||
$config->product->statisticFields['stories'] = array('draftStories', 'activeStories', 'changingStories', 'reviewingStories', 'closedStories', 'finishedStories', 'totalStories');
|
||||
$config->product->statisticFields['bugs'] = array('unresolvedBugs', 'closedBugs', 'fixedBugs');
|
||||
$config->product->statisticFields['plans'] = array('plans');
|
||||
$config->product->statisticFields['releases'] = array('releases');
|
||||
|
||||
$config->product->skipRedirectMethod = ',create,index,showerrornone,ajaxgetdropmenu,kanban,all,manageline,export,ajaxgetplans,';
|
||||
$config->product->memberFields = array('PO', 'QD', 'RD', 'feedback', 'ticket', 'createdBy;');
|
||||
|
||||
@@ -98,12 +98,12 @@ $config->product->dtable->fieldList['testCaseCoverage']['type'] = 'progress'
|
||||
$config->product->dtable->fieldList['testCaseCoverage']['sortType'] = false;
|
||||
$config->product->dtable->fieldList['testCaseCoverage']['border'] = 'right';
|
||||
|
||||
$config->product->dtable->fieldList['unResolvedBugs']['name'] = 'unResolvedBugs';
|
||||
$config->product->dtable->fieldList['unResolvedBugs']['title'] = $lang->product->activatedBug;
|
||||
$config->product->dtable->fieldList['unResolvedBugs']['minWidth'] = 64;
|
||||
$config->product->dtable->fieldList['unResolvedBugs']['type'] = 'number';
|
||||
$config->product->dtable->fieldList['unResolvedBugs']['sortType'] = false;
|
||||
$config->product->dtable->fieldList['unResolvedBugs']['align'] = 'center';
|
||||
$config->product->dtable->fieldList['unresolvedBugs']['name'] = 'unresolvedBugs';
|
||||
$config->product->dtable->fieldList['unresolvedBugs']['title'] = $lang->product->activatedBug;
|
||||
$config->product->dtable->fieldList['unresolvedBugs']['minWidth'] = 64;
|
||||
$config->product->dtable->fieldList['unresolvedBugs']['type'] = 'number';
|
||||
$config->product->dtable->fieldList['unresolvedBugs']['sortType'] = false;
|
||||
$config->product->dtable->fieldList['unresolvedBugs']['align'] = 'center';
|
||||
|
||||
$config->product->dtable->fieldList['bugFixedRate']['name'] = 'bugFixedRate';
|
||||
$config->product->dtable->fieldList['bugFixedRate']['title'] = $lang->product->bugFixedRate;
|
||||
@@ -240,13 +240,13 @@ $config->product->all->dtable->fieldList['plans']['show'] = true;
|
||||
$config->product->all->dtable->fieldList['plans']['sortType'] = false;
|
||||
$config->product->all->dtable->fieldList['plans']['group'] = 'g5';
|
||||
|
||||
$config->product->all->dtable->fieldList['execution']['name'] = 'execution';
|
||||
$config->product->all->dtable->fieldList['execution']['title'] = $lang->execution->common;
|
||||
$config->product->all->dtable->fieldList['execution']['width'] = 64;
|
||||
$config->product->all->dtable->fieldList['execution']['type'] = 'number';
|
||||
$config->product->all->dtable->fieldList['execution']['show'] = true;
|
||||
$config->product->all->dtable->fieldList['execution']['sortType'] = false;
|
||||
$config->product->all->dtable->fieldList['execution']['group'] = 'g6';
|
||||
$config->product->all->dtable->fieldList['executions']['name'] = 'executions';
|
||||
$config->product->all->dtable->fieldList['executions']['title'] = $lang->execution->common;
|
||||
$config->product->all->dtable->fieldList['executions']['width'] = 64;
|
||||
$config->product->all->dtable->fieldList['executions']['type'] = 'number';
|
||||
$config->product->all->dtable->fieldList['executions']['show'] = true;
|
||||
$config->product->all->dtable->fieldList['executions']['sortType'] = false;
|
||||
$config->product->all->dtable->fieldList['executions']['group'] = 'g6';
|
||||
|
||||
$config->product->all->dtable->fieldList['testCaseCoverage']['name'] = 'testCaseCoverage';
|
||||
$config->product->all->dtable->fieldList['testCaseCoverage']['title'] = $lang->product->testCaseCoverage;
|
||||
@@ -257,14 +257,14 @@ $config->product->all->dtable->fieldList['testCaseCoverage']['show'] = true;
|
||||
$config->product->all->dtable->fieldList['testCaseCoverage']['sortType'] = false;
|
||||
$config->product->all->dtable->fieldList['testCaseCoverage']['group'] = 'g7';
|
||||
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['name'] = 'totalActivatedBugs';
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['title'] = $lang->product->activatedBug;
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['width'] = 64;
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['minWidth'] = 86;
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['type'] = 'number';
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['show'] = true;
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['sortType'] = false;
|
||||
$config->product->all->dtable->fieldList['totalActivatedBugs']['group'] = 'g8';
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['name'] = 'unresolvedBugs';
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['title'] = $lang->product->activatedBug;
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['width'] = 64;
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['minWidth'] = 86;
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['type'] = 'number';
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['show'] = true;
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['sortType'] = false;
|
||||
$config->product->all->dtable->fieldList['unresolvedBugs']['group'] = 'g8';
|
||||
|
||||
$config->product->all->dtable->fieldList['totalBugs']['name'] = 'totalBugs';
|
||||
$config->product->all->dtable->fieldList['totalBugs']['title'] = $lang->product->totalBugs;
|
||||
|
||||
@@ -589,6 +589,9 @@ class product extends control
|
||||
|
||||
$this->app->loadClass('pager', true);
|
||||
$pager = new pager($recTotal, $recPerPage, $pageID);
|
||||
|
||||
$this->product->refreshStats(); // Refresh stats fields of products.
|
||||
|
||||
$productStatList = $this->product->getStats(array_keys($products), $orderBy, $pager, 'story', $programID);
|
||||
|
||||
/* Generate root program list. */
|
||||
|
||||
+15
-78
@@ -879,26 +879,7 @@ class productModel extends model
|
||||
$projects = $this->getProjectListByProduct($productID, $browseType, $branch, $involved, $orderBy, $pager);
|
||||
if(empty($projects)) return array();
|
||||
|
||||
$projectKeys = array_keys($projects);
|
||||
|
||||
/* Get all tasks and compute totalEstimate, totalConsumed, totalLeft, progress according to them. */
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->in($projectKeys)
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
$hours = $this->loadModel('program')->computeProjectHours($tasks);
|
||||
|
||||
/* Get the number of project teams. */
|
||||
$teams = $this->dao->select('t1.root,t1.account')->from(TABLE_TEAM)->alias('t1')
|
||||
->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
|
||||
->where('t1.root')->in($projectKeys)
|
||||
->andWhere('t1.type')->eq('project')
|
||||
->andWhere('t2.deleted')->eq(0)
|
||||
->fetchGroup('root', 'account');
|
||||
|
||||
return $this->program->appendStatToProjects($projects, 'hours,teamCount', array('hours' => $hours, 'teams' => $teams));
|
||||
return $this->program->appendStatToProjects($projects);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1065,44 +1046,22 @@ class productModel extends model
|
||||
|
||||
if(empty($productIdList)) return array();
|
||||
|
||||
/* Get stats data. */
|
||||
$products = $this->getStatsProducts($productIdList, $programID == 0, $orderBy, $pager);
|
||||
$unclosedStory = $this->loadModel('story')->getUnClosedTotal();
|
||||
$finishClosedStory = $this->story->getFinishClosedTotal();
|
||||
$stats = $this->getStatsProducts($productIdList, $programID == 0, $orderBy, $pager);
|
||||
|
||||
$modules = array('plans', 'releases', 'latestReleases', 'bugs', 'unResolved', 'activeBugs', 'fixedBugs', 'closedBugs', 'thisWeekBugs', 'assignToNull');
|
||||
foreach($modules as $module) $$module = $this->productTao->getStatisticByType($productIdList, $module);
|
||||
|
||||
list($stories, $requirements) = $this->getStatsStoriesAndRequirements($productIdList, $storyType);
|
||||
$latestReleases = $this->productTao->getStatisticByType($productIdList, 'latestReleases');
|
||||
$projectCountPairs = $this->productTao->getProjectCountPairs($productIdList);
|
||||
$executionCountPairs = $this->productTao->getExecutionCountPairs($productIdList);
|
||||
$coveragePairs = $this->getCaseCoveragePairs($productIdList);
|
||||
$projectsPairs = $this->productTao->getProjectCountPairs($productIdList);
|
||||
|
||||
/* Render statistic result to each product. */
|
||||
$stats = array();
|
||||
foreach($products as $productID => $product)
|
||||
foreach($stats as $product)
|
||||
{
|
||||
foreach($modules as $field) $product->$field = zget($$field, $productID, 0);
|
||||
|
||||
$product->stories = zget($stories, $product->id, array());
|
||||
$product->stories['finishClosed'] = zget($finishClosedStory, $product->id, 0);
|
||||
$product->stories['unclosed'] = zget($unclosedStory, $product->id, 0);
|
||||
$product->activeStories = zget($product->stories, 'active', 0);
|
||||
$product->requirements = zget($requirements, $product->id, array());
|
||||
$product->executions = zget($executionCountPairs, $product->id, 0);
|
||||
$product->coverage = zget($coveragePairs, $product->id, 0);
|
||||
$product->projects = zget($projectsPairs, $product->id, 0);
|
||||
$product->projects = zget($projectCountPairs, $product->id, 0);
|
||||
$product->executions = zget($executionCountPairs, $product->id, 0);
|
||||
$product->coverage = zget($coveragePairs, $product->id, 0);
|
||||
|
||||
$latestRelease = isset($latestReleases[$product->id]) ? $latestReleases[$product->id][0] : null;
|
||||
$product->latestRelease = $latestRelease ? $latestRelease->name : '';
|
||||
$product->latestReleaseDate = $latestRelease ? $latestRelease->date : '';
|
||||
|
||||
/* Calculate product progress. */
|
||||
$closedTotal = $product->stories['closed'] + $product->requirements['closed'];
|
||||
$allTotal = array_sum($product->stories) + array_sum($product->requirements);
|
||||
$product->progress = empty($closedTotal) ? 0 : round($closedTotal / $allTotal * 100, 1);
|
||||
|
||||
$stats[$productID] = $product;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
@@ -1493,35 +1452,14 @@ class productModel extends model
|
||||
*/
|
||||
public function formatDataForList(object $product, array $users): object
|
||||
{
|
||||
$totalStories = $product->stories['closed'] + $product->stories['unclosed'];
|
||||
$totalBugs = $product->unResolved + $product->fixedBugs;
|
||||
$product->type = 'product';
|
||||
$product->productLine = $product->lineName;
|
||||
$product->PO = !empty($product->PO) ? zget($users, $product->PO) : '';
|
||||
$product->testCaseCoverage = $product->coverage;
|
||||
$product->storyCompleteRate = $product->totalStories == 0 ? 0 : round($product->finishedStories / $product->totalStories, 3) * 100;
|
||||
$product->bugFixedRate = ($product->unresolvedBugs + $product->fixedBugs) == 0 ? 0 : round($product->fixedBugs / ($product->unresolvedBugs + $product->fixedBugs), 3) * 100;
|
||||
|
||||
$item = new stdClass();
|
||||
$item->type = 'product';
|
||||
$item->id = $product->id;
|
||||
$item->name = $product->name;
|
||||
$item->productLine = $product->lineName;
|
||||
$item->PO = !empty($product->PO) ? zget($users, $product->PO) : '';
|
||||
$item->createdDate = $product->createdDate;
|
||||
$item->createdBy = $product->createdBy;
|
||||
$item->draftStories = $product->stories['draft'];
|
||||
$item->activeStories = $product->stories['active'];
|
||||
$item->changingStories = $product->stories['changing'];
|
||||
$item->reviewingStories = $product->stories['reviewing'];
|
||||
$item->totalStories = $totalStories;
|
||||
$item->storyCompleteRate = ($totalStories == 0 ? 0 : round($product->stories['finishClosed'] / $totalStories, 3) * 100);
|
||||
$item->plans = $product->plans;
|
||||
$item->execution = $product->executions;
|
||||
$item->testCaseCoverage = $product->coverage;
|
||||
$item->totalActivatedBugs= $product->activeBugs;
|
||||
$item->totalBugs = $product->bugs;
|
||||
$item->bugFixedRate = ($totalBugs == 0 ? 0 : round($product->fixedBugs / $totalBugs, 3) * 100);
|
||||
$item->releases = $product->releases;
|
||||
$item->latestReleaseDate = $product->latestReleaseDate;
|
||||
$item->latestRelease = $product->latestRelease;
|
||||
if(isset($product->actions)) $item->actions = $product->actions;
|
||||
|
||||
return $item;
|
||||
return $product;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2175,5 +2113,4 @@ class productModel extends model
|
||||
/* 4. Clear actions older than 30 days. */
|
||||
$this->loadModel('action')->cleanActions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-32
@@ -804,41 +804,10 @@ class productTao extends productModel
|
||||
$data = $type == 'program' ? $programStructure[$product->program] : $programStructure[$product->program][$product->line];
|
||||
foreach($this->config->product->statisticFields as $key => $fields)
|
||||
{
|
||||
/* Get the total number of requirements and stories. */
|
||||
if(strpos('stories|requirements', $key) !== false)
|
||||
{
|
||||
$totalObjects = 0;
|
||||
foreach($product->$key as $status => $number) if(isset($this->lang->story->statusList[$status])) $totalObjects += $number;
|
||||
|
||||
$fieldType = $key == 'stories' ? 'Stories' : 'Requirements';
|
||||
if(!isset($data['total' . $fieldType])) $data['total' . $fieldType] = 0;
|
||||
$data['total' . $fieldType] += $totalObjects;
|
||||
}
|
||||
elseif($key == 'bugs')
|
||||
{
|
||||
$fieldType = 'Bugs';
|
||||
}
|
||||
|
||||
foreach($fields as $field)
|
||||
{
|
||||
if(!isset($data[$field])) $data[$field] = 0;
|
||||
|
||||
$status = $field;
|
||||
if(strpos($field, 'Requirements') !== false or strpos($field, 'Stories') !== false or $field == 'unResolvedBugs')
|
||||
{
|
||||
$length = strpos($field, $fieldType);
|
||||
$status = substr($field, 0, $length);
|
||||
}
|
||||
|
||||
if(strpos('requirements|stories', $key) !== false)
|
||||
{
|
||||
$objects = $product->$key;
|
||||
$data[$field] += $objects[$status];
|
||||
}
|
||||
else
|
||||
{
|
||||
$data[$field] += $product->$status;
|
||||
}
|
||||
$data[$field] += $product->$field;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -434,15 +434,7 @@ class productZen extends product
|
||||
{
|
||||
$product->line = zget($lines, $product->line, '');
|
||||
$product->manager = zget($users, $product->PO, '');
|
||||
$product->draftStories = (int)$product->stories['draft'];
|
||||
$product->activeStories = (int)$product->stories['active'];
|
||||
$product->changedStories = (int)$product->stories['changing'];
|
||||
$product->reviewingStories = (int)$product->stories['reviewing'];
|
||||
$product->closedStories = (int)$product->stories['closed'];
|
||||
$product->totalStories = $product->activeStories + $product->changedStories + $product->draftStories + $product->closedStories + $product->reviewingStories;
|
||||
$product->storyCompleteRate = ($product->totalStories == 0 ? 0 : round($product->closedStories / $product->totalStories, 3) * 100) . '%';
|
||||
$product->unResolvedBugs = (int)$product->unResolved;
|
||||
$product->assignToNullBugs = (int)$product->assignToNull;
|
||||
$product->bugFixedRate = (($product->unResolved + $product->fixedBugs) == 0 ? 0 : round($product->fixedBugs / ($product->unResolved + $product->fixedBugs), 3) * 100) . '%';
|
||||
$product->program = $product->programName;
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ if($canBatchAction)
|
||||
|
||||
menu
|
||||
(
|
||||
set::id('navStatus'),
|
||||
set::className('dropdown-menu'),
|
||||
setID('navStatus'),
|
||||
setClass('dropdown-menu'),
|
||||
set::items(array_values($items))
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ formPanel
|
||||
set::required(true),
|
||||
select
|
||||
(
|
||||
set::id('closedReasonBox'),
|
||||
setID('closedReasonBox'),
|
||||
set::name('closedReason'),
|
||||
set::strong(false),
|
||||
set::items($lang->productplan->closedReasonList),
|
||||
|
||||
@@ -25,7 +25,7 @@ searchForm
|
||||
|
||||
dtable
|
||||
(
|
||||
set::id('unlinkBugList'),
|
||||
setID('unlinkBugList'),
|
||||
set::userMap($users),
|
||||
set::checkable(true),
|
||||
set::cols($cols),
|
||||
|
||||
@@ -32,7 +32,7 @@ searchForm
|
||||
|
||||
dtable
|
||||
(
|
||||
set::id('unlinkStoryList'),
|
||||
setID('unlinkStoryList'),
|
||||
set::userMap($users),
|
||||
set::cols($cols),
|
||||
set::data(array_values($allStories)),
|
||||
|
||||
@@ -203,7 +203,7 @@ detailBody
|
||||
),
|
||||
dtable
|
||||
(
|
||||
set::id('storyDTable'),
|
||||
setID('storyDTable'),
|
||||
set::userMap($users),
|
||||
set::bordered(true),
|
||||
set::cols($storyCols),
|
||||
@@ -245,7 +245,7 @@ detailBody
|
||||
),
|
||||
dtable
|
||||
(
|
||||
set::id('bugDTable'),
|
||||
setID('bugDTable'),
|
||||
set::userMap($users),
|
||||
set::bordered(true),
|
||||
set::cols($bugCols),
|
||||
|
||||
@@ -55,15 +55,15 @@ class program extends control
|
||||
$this->config->program->search['actionURL'] = $actionURL;
|
||||
$this->loadModel('search')->setSearchParams($this->config->program->search);
|
||||
|
||||
$this->view->title = $this->lang->program->browse;
|
||||
$this->view->programs = $programs;
|
||||
$this->view->status = $status;
|
||||
$this->view->orderBy = $orderBy;
|
||||
$this->view->pager = $pager;
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
||||
$this->view->usersAvatar = $this->user->getAvatarPairs('');
|
||||
$this->view->PMList = $PMList;
|
||||
$this->view->param = $param;
|
||||
$this->view->title = $this->lang->program->browse;
|
||||
$this->view->programs = $programs;
|
||||
$this->view->status = $status;
|
||||
$this->view->orderBy = $orderBy;
|
||||
$this->view->pager = $pager;
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noletter');
|
||||
$this->view->usersAvatar = $this->user->getAvatarPairs('');
|
||||
$this->view->PMList = $PMList;
|
||||
$this->view->param = $param;
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
+51
-208
@@ -287,8 +287,8 @@ class programModel extends model
|
||||
$programs = $this->getTopPairs('noclosed');
|
||||
|
||||
/* Group data by product. */
|
||||
list($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions, $hours, $projectHours) = $this->getKanbanStatisticData($programs);
|
||||
$productGroup = $this->processProductsForKanban($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions, $hours, $projectHours);
|
||||
list($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions) = $this->getKanbanStatisticData($programs);
|
||||
$productGroup = $this->processProductsForKanban($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions);
|
||||
|
||||
/* Group data by program. */
|
||||
$kanbanGroup = array();
|
||||
@@ -359,20 +359,7 @@ class programModel extends model
|
||||
$executionPairs = array();
|
||||
foreach($doingExecutions as $execution) $executionPairs[$execution->id] = $execution->id;
|
||||
|
||||
/* Compute executions and projects progress. */
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('(execution')->in($executionPairs)
|
||||
->orWhere('project')->in($projectIdList)
|
||||
->markRight(1)
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('execution', 'id');
|
||||
|
||||
$hours = $this->computeProjectHours($tasks);
|
||||
$projectHours = $this->getProgressList();
|
||||
|
||||
return array($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions, $hours, $projectHours);
|
||||
return array($productGroup, $planGroup, $releaseGroup, $projectGroup, $doingExecutions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,12 +371,10 @@ class programModel extends model
|
||||
* @param array $releaseGroup
|
||||
* @param array $projectGroup
|
||||
* @param array $doingExecutions
|
||||
* @param array $hours
|
||||
* @param array $projectHours
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function processProductsForKanban(array $productGroup, array $planGroup, array $releaseGroup, array $projectGroup, array $doingExecutions, array $hours, array $projectHours): array
|
||||
public function processProductsForKanban(array $productGroup, array $planGroup, array $releaseGroup, array $projectGroup, array $doingExecutions): array
|
||||
{
|
||||
if(empty($productGroup)) return $productGroup;
|
||||
|
||||
@@ -414,14 +399,10 @@ class programModel extends model
|
||||
$status = $project->status == 'wait' ? 'wait' : 'doing';
|
||||
$execution = zget($doingExecutions, $project->id, array());
|
||||
|
||||
if(!empty($execution))
|
||||
{
|
||||
$execution->hours = zget($hours, $execution->id, array());
|
||||
if(helper::diffDate($today, $execution->end) > 0) $execution->delay = 1;
|
||||
}
|
||||
if(!empty($execution) && helper::diffDate($today, $execution->end) > 0) $execution->delay = 1;
|
||||
|
||||
$project->execution = $execution;
|
||||
$project->hours['progress'] = zget($projectHours, $project->id, array());
|
||||
$project->hours = array('progress' => $project->progress);
|
||||
|
||||
/* Convert predefined HTML entities to characters. */
|
||||
$project->name = htmlspecialchars_decode($project->name, ENT_QUOTES);
|
||||
@@ -527,67 +508,18 @@ class programModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据按照项目分组的任务,统计各个项目的工时和进度
|
||||
* Compute hours and progress for project or execution.
|
||||
*
|
||||
* @param array $tasks
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function computeProjectHours(array $tasks): array
|
||||
{
|
||||
if(empty($tasks)) return array();
|
||||
|
||||
$hours = array();
|
||||
foreach($tasks as $projectID => $projectTasks)
|
||||
{
|
||||
/* Init hour. */
|
||||
$hour = new stdclass();
|
||||
$hour->totalConsumed = 0;
|
||||
$hour->totalEstimate = 0;
|
||||
$hour->totalLeft = 0;
|
||||
|
||||
/* Compute totalEstimate, totalConsumed, totalLeft to them. */
|
||||
foreach($projectTasks as $task)
|
||||
{
|
||||
$hour->totalConsumed += $task->consumed;
|
||||
$hour->totalEstimate += $task->estimate;
|
||||
if(strpos('|cancel|closed|done|', "|{$task->status}|") === false) $hour->totalLeft += (float)$task->left;
|
||||
}
|
||||
$hours[$projectID] = $hour;
|
||||
}
|
||||
|
||||
/* 计算进度。四舍五入totalEstimate, totalConsumed, totalLeft. */
|
||||
$progressList = $this->loadModel('project')->getWaterfallProgress(array_keys($hours));
|
||||
foreach($hours as $projectID => $hour)
|
||||
{
|
||||
$hour->totalEstimate = round($hour->totalEstimate, 1) ;
|
||||
$hour->totalConsumed = round($hour->totalConsumed, 1);
|
||||
$hour->totalLeft = round($hour->totalLeft, 1);
|
||||
$hour->totalReal = $hour->totalConsumed + $hour->totalLeft;
|
||||
$hour->progress = zget($progressList, $projectID, ($hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 2) * 100 : 0)) ;
|
||||
}
|
||||
|
||||
return $hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将工时、团队人数、剩余任务数、团队成员等统计信息,追加到对应的项目中。
|
||||
* 将团队成员等统计信息,追加到对应的项目中。
|
||||
* Append statistics fields to projects.
|
||||
*
|
||||
* @param array $projects
|
||||
* @param string $appendFields hours,teamCount,leftTasks,teamMembers
|
||||
* @param array $data array keys are hours, teams and leftTasks.
|
||||
* @param array $teams array keys are hours, teams and leftTasks.
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function appendStatToProjects(array $projects, string $appendFields = '', array $data = array()): array
|
||||
public function appendStatToProjects(array $projects, array $teams = array()): array
|
||||
{
|
||||
if(empty($projects)) return array();
|
||||
if(empty($appendFields) || empty($data)) return $projects;
|
||||
|
||||
$appendFields = explode(',', $appendFields);
|
||||
$emptyHour = json_decode(json_encode(array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0)));
|
||||
/* Process projects. */
|
||||
$stats = array();
|
||||
foreach($projects as $projectID => $project)
|
||||
@@ -601,20 +533,16 @@ class programModel extends model
|
||||
if($delay > 0) $project->delay = $delay;
|
||||
}
|
||||
|
||||
/* Merge the hours. */
|
||||
if(in_array('hours', $appendFields))
|
||||
{
|
||||
$project->hours = $emptyHour;
|
||||
if(isset($data['hours'])) $project->hours = zget($data['hours'], $project->id, $emptyHour);
|
||||
}
|
||||
|
||||
/* Merge the team and left tasks. */
|
||||
if(in_array('teamCount', $appendFields)) $project->teamCount = isset($data['teams'][$project->id]) ? count($data['teams'][$project->id]) : 0;
|
||||
if(in_array('teamMembers', $appendFields)) $project->teamMembers = isset($data['teams'][$project->id]) ? array_keys($data['teams'][$project->id]) : array();
|
||||
if(in_array('leftTasks', $appendFields)) $project->leftTasks = isset($data['leftTasks'][$project->id]) ? $data['leftTasks'][$project->id]->tasks : '—';
|
||||
/* Merge project team. */
|
||||
if(!empty($teams))
|
||||
{
|
||||
$project->teamCount = isset($teams[$project->id]) ? count($teams[$project->id]) : 0;
|
||||
$project->teamMembers = isset($teams[$project->id]) ? array_keys($teams[$project->id]) : array();
|
||||
}
|
||||
|
||||
$stats[$projectID] = $project;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
@@ -728,59 +656,6 @@ class programModel extends model
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算项目和项目集的进度。
|
||||
* Get program and project progress list.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProgressList(): array
|
||||
{
|
||||
$totalProgress = array();
|
||||
$projectCount = array();
|
||||
$userPRJCount = array();
|
||||
$progressList = array();
|
||||
$programPairs = $this->getPairs();
|
||||
$projectStats = $this->getProjectStats(0, 'all', 0, 'id_desc', '', true);
|
||||
|
||||
/* Add program progress. */
|
||||
foreach(array_keys($programPairs) as $programID)
|
||||
{
|
||||
$totalProgress[$programID] = 0;
|
||||
$projectCount[$programID] = 0;
|
||||
$userPRJCount[$programID] = 0;
|
||||
$progressList[$programID] = 0;
|
||||
|
||||
foreach($projectStats as $project)
|
||||
{
|
||||
if(strpos($project->path, ',' . $programID . ',') === false) continue;
|
||||
|
||||
/* The number of projects under this program that the user can view. */
|
||||
if(strpos(',' . $this->app->user->view->projects . ',', ',' . $project->id . ',') !== false) $userPRJCount[$programID] ++;
|
||||
|
||||
$totalProgress[$programID] += $project->hours->progress;
|
||||
$projectCount[$programID] ++;
|
||||
}
|
||||
|
||||
if(empty($projectCount[$programID])) continue;
|
||||
|
||||
/* Program progress can't see when this user don't have all projects priv. */
|
||||
if(!$this->app->user->admin && $userPRJCount[$programID] != $projectCount[$programID])
|
||||
{
|
||||
unset($progressList[$programID]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$progressList[$programID] = round($totalProgress[$programID] / $projectCount[$programID]);
|
||||
}
|
||||
|
||||
/* Add project progress. */
|
||||
foreach($projectStats as $project) $progressList[$project->id] = $project->hours->progress;
|
||||
|
||||
return $progressList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建项目集。
|
||||
* Create a program.
|
||||
@@ -1324,44 +1199,10 @@ class programModel extends model
|
||||
{
|
||||
if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getProjectStats($browseType);
|
||||
|
||||
/* Init vars. */
|
||||
$projects = $this->getProjectList($programID, $browseType, $queryID, $orderBy, $programTitle, $queryAll, $pager);
|
||||
if(empty($projects)) return array();
|
||||
|
||||
$projectKeys = array_keys($projects);
|
||||
$executions = $this->loadModel('project')->getExecutionList($projectKeys);
|
||||
|
||||
/* Get all tasks and compute totalEstimate, totalConsumed, totalLeft, progress according to them. */
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->in($projectKeys)
|
||||
->andWhere('execution')->in(array_keys($executions))
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
$hours = $this->computeProjectHours($tasks);
|
||||
|
||||
/* Get the number of left tasks. */
|
||||
$leftTasks = array();
|
||||
if($this->cookie->projectType && $this->cookie->projectType == 'bycard')
|
||||
{
|
||||
$leftTasks = $this->dao->select('t2.parent as project, count(*) as tasks')->from(TABLE_TASK)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id')
|
||||
->where('t1.execution')->in(array_keys($executions))
|
||||
->andWhere('t1.status')->notIn('cancel,closed')
|
||||
->groupBy('t2.parent')
|
||||
->fetchAll('project');
|
||||
}
|
||||
|
||||
/* Get the members of project teams. */
|
||||
$teamMembers = $this->dao->select('t1.root,t1.account')->from(TABLE_TEAM)->alias('t1')
|
||||
->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
|
||||
->where('t1.root')->in($projectKeys)
|
||||
->andWhere('t1.type')->eq('project')
|
||||
->andWhere('t2.deleted')->eq(0)
|
||||
->fetchGroup('root', 'account');
|
||||
|
||||
$stats = $this->appendStatToProjects($projects, 'hours,teamCount,teamMembers,leftTasks', array('hours' => $hours, 'teams' => $teamMembers, 'leftTasks' => $leftTasks));
|
||||
$stats = $this->appendStatToProjects($projects);
|
||||
foreach($stats as $project) $project->name = htmlspecialchars_decode($project->name, ENT_QUOTES); // Convert predefined HTML entities to characters.
|
||||
|
||||
return $stats;
|
||||
@@ -1570,13 +1411,30 @@ class programModel extends model
|
||||
}
|
||||
|
||||
/* 1. Get summary and members of executions to be refreshed. */
|
||||
$summary = $this->dao->select('execution, SUM(t1.estimate) AS totalEstimate, SUM(t1.consumed) AS totalConsumed, SUM(t1.`left`) AS totalLeft')->from(TABLE_TASK)->alias('t1')
|
||||
$tasks = $this->dao->select('t1.id, execution, t1.estimate, t1.consumed, t1.`left`, t1.status')->from(TABLE_TASK)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
|
||||
->where('t1.deleted')->eq(0)
|
||||
->andWhere('t1.parent')->le(0) // Ignore child task.
|
||||
->andWhere('t1.parent')->ge(0) // Ignore parent task.
|
||||
->beginIF(!empty($projects))->andWhere('t1.project')->in(array_keys($projects))->fi()
|
||||
->groupBy('execution')
|
||||
->fetchAll();
|
||||
->fetchAll('id');
|
||||
|
||||
$summary = array();
|
||||
foreach($tasks as $task)
|
||||
{
|
||||
if(empty($task->execution)) continue;
|
||||
if(!isset($summary[$task->execution]))
|
||||
{
|
||||
$summary[$task->execution] = new stdclass();
|
||||
$summary[$task->execution]->totalEstimate = 0;
|
||||
$summary[$task->execution]->totalConsumed = 0;
|
||||
$summary[$task->execution]->totalLeft = 0;
|
||||
}
|
||||
|
||||
$summary[$task->execution]->execution = $task->execution;
|
||||
$summary[$task->execution]->totalEstimate += $task->estimate;
|
||||
$summary[$task->execution]->totalConsumed += $task->consumed;
|
||||
$summary[$task->execution]->totalLeft += ($task->status == 'closed' or $task->status == 'cancel') ? 0 : $task->left;
|
||||
}
|
||||
|
||||
$teamMembers = $this->dao->select('t1.root, COUNT(1) AS members')->from(TABLE_TEAM)->alias('t1')
|
||||
->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
|
||||
@@ -1606,6 +1464,8 @@ class programModel extends model
|
||||
$stats[$nodeID]['totalEstimate'] += $execution->totalEstimate;
|
||||
$stats[$nodeID]['totalConsumed'] += $execution->totalConsumed;
|
||||
$stats[$nodeID]['totalLeft'] += $execution->totalLeft;
|
||||
|
||||
// Check $execution->execution and $nodeID(path) is not deleted.
|
||||
if(empty($projectsPairs[$execution->execution]) && empty($projectsPairs[$nodeID]))
|
||||
{
|
||||
$stats[$nodeID]['totalConsumedNotDel'] += $execution->totalConsumed;
|
||||
@@ -1614,22 +1474,6 @@ class programModel extends model
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadModel('project');
|
||||
foreach($projects as $projectID => $project)
|
||||
{
|
||||
if($project->model != 'waterfall') continue;
|
||||
|
||||
$projectStats = $this->project->getWaterfallPVEVAC($projectID);
|
||||
$stats[$projectID]['totalEstimate'] = $projectStats['PV'];
|
||||
$stats[$projectID]['totalConsumed'] = $projectStats['AC'];
|
||||
$stats[$projectID]['totalLeft'] = $projectStats['left'];
|
||||
if(empty($project->deleted))
|
||||
{
|
||||
$stats[$projectID]['totalConsumedNotDel'] = $projectStats['AC'];
|
||||
$stats[$projectID]['totalLeftNotDel'] = $projectStats['left'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach($teamMembers as $projectID => $teamCount)
|
||||
{
|
||||
if(!isset($stats[$projectID])) $stats[$projectID] = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'teamCount' => 0, 'totalConsumedNotDel' => 0, 'totalLeftNotDel' => 0);
|
||||
@@ -1645,14 +1489,14 @@ class programModel extends model
|
||||
->set('progress')->eq($progress)
|
||||
->set('teamCount')->eq($project['teamCount'])
|
||||
->set('estimate')->eq($project['totalEstimate'])
|
||||
->set('consumed')->eq($project['totalConsumed'])
|
||||
->set('left')->eq($project['totalLeft'])
|
||||
->set('consumed')->eq($project['totalConsumedNotDel'])
|
||||
->set('left')->eq($project['totalLeftNotDel'])
|
||||
->where('id')->eq($projectID)
|
||||
->exec();
|
||||
}
|
||||
|
||||
/* 5. Update programStatsTime. */
|
||||
$projectList = $this->dao->select('id,progress,path')->from(TABLE_PROJECT)
|
||||
$projectList = $this->dao->select('id,progress,path,consumed,`left`')->from(TABLE_PROJECT)
|
||||
->where('type')->eq('project')
|
||||
->andWhere('parent')->ne(0)
|
||||
->andWhere('deleted')->eq(0)
|
||||
@@ -1665,19 +1509,18 @@ class programModel extends model
|
||||
foreach($path as $programID)
|
||||
{
|
||||
if($programID == $projectID) continue;
|
||||
$programProgress[$programID][] = $project->progress;
|
||||
|
||||
if(!isset($programProgress[$programID])) $programProgress[$programID] = array('consumed' => 0, 'left' => 0);
|
||||
$programProgress[$programID]['consumed'] += $project->consumed;
|
||||
$programProgress[$programID]['left'] += $project->left;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($programProgress as $programID => $progress)
|
||||
foreach($programProgress as $programID => $hours)
|
||||
{
|
||||
$count = count($progress);
|
||||
$sum = array_sum($progress);
|
||||
$average = round($sum / $count, 2);
|
||||
$this->dao->update(TABLE_PROJECT)
|
||||
->set('progress')->eq($average)
|
||||
->where('id')->eq($programID)
|
||||
->exec();
|
||||
$progress = ($hours['consumed'] + $hours['left']) ? floor($hours['consumed'] / ($hours['consumed'] + $hours['left']) * 1000) / 1000 * 100 : 0;
|
||||
|
||||
$this->dao->update(TABLE_PROJECT)->set('progress')->eq($progress)->where('id')->eq($programID)->exec();
|
||||
}
|
||||
|
||||
/* 6. Update projectStatsTime in config. */
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
|
||||
zdTable('user')->gen(5);
|
||||
su('admin');
|
||||
|
||||
zdTable('project')->gen(50);
|
||||
|
||||
$task = zdTable('task');
|
||||
$task->project->range('1-20');
|
||||
$task->gen(100);
|
||||
|
||||
/**
|
||||
|
||||
title=测试 programModel::computeProjectHours();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('program');
|
||||
$tasks = $tester->program->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->eq('11')
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
|
||||
r($tester->program->computeProjectHours(array())) && p() && e('0');
|
||||
r($tester->program->computeProjectHours($tasks)) && p('11:totalConsumed,totalEstimate,totalLeft,progress') && e('15,30,10,60');
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
include dirname(__FILE__, 2) . '/program.class.php';
|
||||
su('admin');
|
||||
|
||||
$program = zdTable('project');
|
||||
$program->id->range('1-8');
|
||||
$program->name->setFields(array(
|
||||
array('field' => 'name1', 'range' => '项目集{2},项目{3},迭代{3}'),
|
||||
array('field' => 'name2', 'range' => '1-8')
|
||||
));
|
||||
$program->type->range('program{2},project{3},sprint{3}');
|
||||
$program->status->range('doing');
|
||||
$program->parent->range('0,0,1,1,2,3,4,5');
|
||||
$program->grade->range('1{2},2{3},1{3}');
|
||||
$program->project->range('0{5},3-5');
|
||||
$program->path->range('1,2,`1,3`,`1,4`,`2,5`,`3,6`,`4,7`,`5,8`')->prefix(',')->postfix(',');
|
||||
$program->begin->range('20220112 000000:0')->type('timestamp')->format('YYYY-MM-DD');
|
||||
$program->end->range('20230212 000000:0')->type('timestamp')->format('YYYY-MM-DD');
|
||||
$program->gen(8);
|
||||
|
||||
$task = zdTable('task');
|
||||
$task->name->prefix('任务')->range('1-3');
|
||||
$task->project->range('3-5');
|
||||
$task->execution->range('6-8');
|
||||
$task->estimate->range('6-8');
|
||||
$task->consumed->range('1,3,2');
|
||||
$task->left->range('5,4,6');
|
||||
$task->status->range('doing');
|
||||
$task->assignedTo->range('admin');
|
||||
$task->gen(3);
|
||||
|
||||
zdTable('team')->gen(0);
|
||||
|
||||
/**
|
||||
|
||||
title=测试 programModee::getProgressList();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
*/
|
||||
|
||||
$programTester = new programTest();
|
||||
$progressList = $programTester->getProgressListTest();
|
||||
|
||||
r(count($progressList)) && p() && e('5'); // 获取项目和项目集的个数
|
||||
r($progressList) && p('2') && e('25'); // 获取id=2的项目的进度
|
||||
r($progressList) && p('4') && e('43'); // 获取id=4的项目集的进度
|
||||
@@ -293,17 +293,6 @@ class programTest
|
||||
return $this->program->getTeamMemberPairs($programID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress list.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getProgressListTest()
|
||||
{
|
||||
return $this->program->getProgressList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test save state.
|
||||
*
|
||||
|
||||
@@ -51,9 +51,7 @@ foreach($programs as $program)
|
||||
$program->budget = !empty($program->budget) ? zget($lang->project->currencySymbol, $program->budgetUnit) . ' ' . $programBudget : $lang->project->future;
|
||||
$parents[$program->parent][] = $program->id;
|
||||
|
||||
/* Progress. */
|
||||
if(isset($progressList[$program->id])) $program->progress = round($progressList[$program->id]);
|
||||
|
||||
$program->progress = round($program->progress);
|
||||
$program->isParent = false;
|
||||
if($program->parent > 0 and isset($programs[$program->parent])) $programs[$program->parent]->isParent = true;
|
||||
if($program->parent > 0 and !isset($programs[$program->parent]))
|
||||
@@ -78,7 +76,8 @@ foreach($programs as $program)
|
||||
}
|
||||
|
||||
/* Set invested hours. */
|
||||
if(!isset($program->invested)) $program->invested = 0;
|
||||
$program->invested = !empty($this->config->execution->defaultWorkhours) ? round($program->consumed / $this->config->execution->defaultWorkhours, 2) : 0;
|
||||
|
||||
if(!is_null($program->end) && str_contains($program->end, LONG_TIME)) $program->end = $lang->program->longTime;
|
||||
|
||||
/* Actions. */
|
||||
|
||||
@@ -52,7 +52,7 @@ $fnGenerateProgramRowData = function($programID, $program) use ($config, $users)
|
||||
if(!isset($program['programName']) || $config->systemMode != 'ALM') return null;
|
||||
|
||||
/* ALM mode with more data. */
|
||||
$totalStories = $program['finishClosedStories'] + $program['unclosedStories'];
|
||||
$totalStories = $program['totalStories'];
|
||||
$pmName = '';
|
||||
if(!empty($program['programPM']))
|
||||
{
|
||||
@@ -70,9 +70,9 @@ $fnGenerateProgramRowData = function($programID, $program) use ($config, $users)
|
||||
$item->PM = $pmName;
|
||||
$item->createdDate = '';
|
||||
$item->createdBy = '';
|
||||
$item->totalUnclosedStories = $program['unclosedStories'];
|
||||
$item->totalUnclosedStories = $totalStories - $program['closedStories'];
|
||||
$item->totalStories = $totalStories;
|
||||
$item->closedStoryRate = ($totalStories == 0 ? 0 : round($program['finishClosedStories'] / $totalStories, 3) * 100);
|
||||
$item->closedStoryRate = ($totalStories == 0 ? 0 : round($program['closedStories'] / $totalStories, 3) * 100);
|
||||
$item->totalPlans = $program['plans'];
|
||||
$item->totalProjects = 0;
|
||||
$item->totalExecutions = 0;
|
||||
@@ -125,8 +125,6 @@ $fnGenerateLineRowData = function($programID, $lineID, $line) use ($config, &$li
|
||||
/* Closure for generating product row data. */
|
||||
$fnGenerateProductRowData = function($lineID, $product) use ($users)
|
||||
{
|
||||
$totalStories = $product->stories['finishClosed'] + $product->stories['unclosed'];
|
||||
|
||||
$item = new stdClass();
|
||||
$item->type = 'product';
|
||||
$item->id = $product->id;
|
||||
@@ -135,15 +133,15 @@ $fnGenerateProductRowData = function($lineID, $product) use ($users)
|
||||
$item->PM = !empty($product->PO) ? zget($users, $product->PO) : '';
|
||||
$item->createdDate = $product->createdDate;
|
||||
$item->createdBy = $product->createdBy;
|
||||
$item->totalUnclosedStories = $product->stories['unclosed'];
|
||||
$item->totalStories = $totalStories;
|
||||
$item->closedStoryRate = empty($totalStories) ? 0 : round($product->stories['finishClosed'] / $totalStories, 3) * 100;
|
||||
$item->totalUnclosedStories = $product->totalStories - $product->closedStories;
|
||||
$item->totalStories = $product->totalStories;
|
||||
$item->closedStoryRate = empty($product->totalStories) ? 0 : round($product->closedStories / $product->totalStories, 3) * 100;
|
||||
$item->totalPlans = $product->plans;
|
||||
$item->totalProjects = $product->projects;
|
||||
$item->totalExecutions = $product->executions;
|
||||
$item->testCaseCoverage = $product->coverage;
|
||||
$item->totalActivatedBugs = $product->activeBugs;
|
||||
$item->totalBugs = $product->bugs;
|
||||
$item->totalActivatedBugs = $product->unresolvedBugs;
|
||||
$item->totalBugs = $product->totalBugs;
|
||||
$item->fixedRate = empty($item->totalBugs) ? 0 : round($product->fixedBugs / $item->totalBugs, 3) * 100;
|
||||
$item->totalReleases = $product->releases;
|
||||
$item->latestReleaseDate = $product->latestReleaseDate;
|
||||
|
||||
@@ -325,6 +325,8 @@ class project extends control
|
||||
$actionURL = $this->createLink('project', 'browse', "&programID=$programID&browseType=bySearch&queryID=myQueryID");
|
||||
$this->project->buildSearchForm($queryID, $actionURL);
|
||||
|
||||
$this->loadModel('program')->refreshStats(); // Refresh stats fields of projects.
|
||||
|
||||
$programTitle = $this->loadModel('setting')->getItem("owner={$this->app->user->account}&module=project&key=programTitle");
|
||||
$projectStats = $this->program->getProjectStats($programID, $browseType, $queryID, $orderBy, $programTitle, false, $pager);
|
||||
|
||||
@@ -1311,7 +1313,7 @@ class project extends control
|
||||
*/
|
||||
public function activate(int $projectID)
|
||||
{
|
||||
$project = $this->project->getByID($projectID);
|
||||
$project = $this->project->getByID($projectID);
|
||||
|
||||
if(!empty($_POST))
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ window.programMenuOnClick = function(data, url)
|
||||
location.href = url.replace('%d', data.item.key);
|
||||
}
|
||||
|
||||
window.renderReleaseCountCell = function(result, {col, row})
|
||||
window.renderCell = function(result, {col, row})
|
||||
{
|
||||
if(col.name === 'name')
|
||||
{
|
||||
|
||||
+10
-83
@@ -242,7 +242,7 @@ class projectModel extends model
|
||||
* @access public
|
||||
* @return object[]
|
||||
*/
|
||||
public function getOverviewList(string $status = '', int $projectID = 0, string $orderBy = 'id_desc', int $limit = 15, string $excludedModel = ''): array
|
||||
public function getOverviewList(string $status = '', int $projectID = 0, string $orderBy = 'id_desc', int $limit = 10, string $excludedModel = ''): array
|
||||
{
|
||||
/* Get project list by query. */
|
||||
$projects = $this->projectTao->fetchProjectListByQuery($status, $projectID, $orderBy, $limit, $excludedModel);
|
||||
@@ -254,24 +254,15 @@ class projectModel extends model
|
||||
$projects = array($projectID => $projects[$projectID]);
|
||||
}
|
||||
|
||||
/* Get team members under the project. */
|
||||
$projectIdList = array_keys($projects);
|
||||
$teamCount = $this->projectTao->fetchMemberCountByIdList($projectIdList);
|
||||
|
||||
/* Get all consumed and all estimate under the project. */
|
||||
$hours = $this->projectTao->fetchTaskEstimateByIdList($projectIdList, 'consumed,estimate');
|
||||
|
||||
/* Get bug, task and story summary under the project. */
|
||||
$bugSummary = $this->projectTao->getTotalBugByProject($projectIdList);
|
||||
$taskSummary = $this->projectTao->getTotalTaskByProject($projectIdList);
|
||||
$storySummary = $this->projectTao->getTotalStoriesByProject($projectIdList);
|
||||
$projectIdList = array_keys($projects);
|
||||
$bugSummary = $this->projectTao->getTotalBugByProject($projectIdList);
|
||||
$taskSummary = $this->projectTao->getTotalTaskByProject($projectIdList);
|
||||
$storySummary = $this->projectTao->getTotalStoriesByProject($projectIdList);
|
||||
|
||||
/* Set project attribute. */
|
||||
foreach($projects as $projectID => $project)
|
||||
{
|
||||
$project->teamCount = zget($teamCount, $projectID, 0);
|
||||
$project->consumed = isset($hours[$projectID]) ? round((float)$hours[$projectID]->consumed, 1) : 0;
|
||||
$project->estimate = isset($hours[$projectID]) ? round((float)$hours[$projectID]->estimate, 1) : 0;
|
||||
$project->leftBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->leftBugs : 0;
|
||||
$project->allBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->allBugs : 0;
|
||||
$project->doneBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->doneBugs : 0;
|
||||
@@ -2029,70 +2020,6 @@ class projectModel extends model
|
||||
return array($projectsStats, $ongoingExecutions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computer execution progress.
|
||||
*
|
||||
* @param array $executions
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function computeProgress($executions)
|
||||
{
|
||||
$hours = array();
|
||||
$emptyHour = array('totalEstimate' => 0, 'totalConsumed' => 0, 'totalLeft' => 0, 'progress' => 0);
|
||||
$executionIdList = array_keys($executions);
|
||||
|
||||
/* Get all tasks and compute totalEstimate, totalConsumed, totalLeft, progress according to them. */
|
||||
$tasks = $this->loadModel('execution')->getTaskGroupByExecution($executionIdList);
|
||||
$projects = $this->dao->select('t1.id,t2.model')
|
||||
->from(TABLE_PROJECT)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
|
||||
->where('t1.deleted')->eq('0')
|
||||
->andWhere('t1.id')->in($executionIdList)
|
||||
->fetchPairs();
|
||||
|
||||
/* Compute totalEstimate, totalConsumed, totalLeft. */
|
||||
foreach($tasks as $executionID => $executionTasks)
|
||||
{
|
||||
$hour = (object)$emptyHour;
|
||||
foreach($executionTasks as $task)
|
||||
{
|
||||
$hour->totalEstimate += $task->estimate;
|
||||
$hour->totalConsumed += $task->consumed;
|
||||
if($task->status != 'cancel' and $task->status != 'closed') $hour->totalLeft += (float)$task->left;
|
||||
}
|
||||
$hours[$executionID] = $hour;
|
||||
|
||||
if(isset($executions[$executionID]) && $executions[$executionID]->grade > 1 && isset($projects[$executionID]) && in_array($projects[$executionID], array('waterfall', 'waterfallplus')))
|
||||
{
|
||||
$stageParents = $this->dao->select('id')->from(TABLE_EXECUTION)->where('id')->in(trim($executions[$executionID]->path, ','))->andWhere('type')->eq('stage')->andWhere('id')->ne($executions[$executionID]->id)->orderBy('grade')->fetchPairs();
|
||||
foreach($stageParents as $stageParent)
|
||||
{
|
||||
if(!isset($hours[$stageParent]))
|
||||
{
|
||||
$hours[$stageParent] = clone $hour;
|
||||
continue;
|
||||
}
|
||||
|
||||
$hours[$stageParent]->totalEstimate += $hour->totalEstimate;
|
||||
$hours[$stageParent]->totalConsumed += $hour->totalConsumed;
|
||||
$hours[$stageParent]->totalLeft += $hour->totalLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute totalReal and progress. */
|
||||
foreach($hours as $hour)
|
||||
{
|
||||
$hour->totalEstimate = round($hour->totalEstimate, 1) ;
|
||||
$hour->totalConsumed = round($hour->totalConsumed, 1);
|
||||
$hour->totalLeft = round($hour->totalLeft, 1);
|
||||
$hour->totalReal = $hour->totalConsumed + $hour->totalLeft;
|
||||
$hour->progress = $hour->totalReal ? round($hour->totalConsumed / $hour->totalReal * 100, 2) : 0;
|
||||
}
|
||||
return $hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置项目的导航菜单。
|
||||
* Set menu of project module.
|
||||
@@ -2493,13 +2420,13 @@ class projectModel extends model
|
||||
|
||||
$project->budget = $project->budget != 0 ? zget($this->lang->project->currencySymbol, $project->budgetUnit) . ' ' . $projectBudget : $this->lang->project->future;
|
||||
$project->statusTitle = $this->processStatus('project', $project);
|
||||
$project->estimate = $project->hours->totalEstimate . $this->lang->project->workHourUnit;
|
||||
$project->consume = $project->hours->totalConsumed . $this->lang->project->workHourUnit;
|
||||
$project->surplus = $project->hours->totalLeft . $this->lang->project->workHourUnit;
|
||||
$project->progress = $project->hours->progress;
|
||||
$project->estimate = $project->estimate . $this->lang->project->workHourUnit;
|
||||
$project->consume = $project->consumed . $this->lang->project->workHourUnit;
|
||||
$project->surplus = $project->left . $this->lang->project->workHourUnit;
|
||||
$project->progress = $project->progress;
|
||||
$project->end = $project->end == LONG_TIME ? $this->lang->project->longTime : $project->end;
|
||||
$project->hasProduct = zget($this->lang->project->projectTypeList, $project->hasProduct);
|
||||
$project->invested = !empty($this->config->execution->defaultWorkhours) ? round($project->hours->totalConsumed / $this->config->execution->defaultWorkhours, 2) : 0;
|
||||
$project->invested = !empty($this->config->execution->defaultWorkhours) ? round($project->consumed / $this->config->execution->defaultWorkhours, 2) : 0;
|
||||
|
||||
if($project->PM)
|
||||
{
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
|
||||
zdTable('project')->config('execution')->gen(10);
|
||||
zdTable('task')->config('task')->gen(20);
|
||||
zdTable('taskteam')->gen(20);
|
||||
su('admin');
|
||||
|
||||
/**
|
||||
|
||||
title=测试 projectModel->computeProgress();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('project');
|
||||
$tester->loadModel('execution');
|
||||
|
||||
$executions = $tester->execution->getList(11);
|
||||
$executions = $tester->project->computeProgress($executions);
|
||||
|
||||
r(count($executions)) && p('') && e('1'); // 查看项目11下的所有执行的数量
|
||||
r($executions) && p('101:totalEstimate,totalConsumed,totalLeft,progress,totalReal') && e('44,9,44,16.98,53'); // 查看计算后的执行工时汇总信息
|
||||
@@ -13,7 +13,7 @@ namespace zin;
|
||||
$programAclList = array();
|
||||
$projectAclList = array();
|
||||
foreach($lang->program->subAcls as $acl => $label) $programAclList[] = array('text' => $label, 'value' => $acl);
|
||||
foreach($lang->project->acls as $acl => $label) $projectAclList[] = array('text' => $label, 'value' => $acl);
|
||||
foreach($lang->project->acls as $acl => $label) $projectAclList[] = array('text' => $label, 'value' => $acl);
|
||||
|
||||
jsVar('LONG_TIME', LONG_TIME);
|
||||
jsVar('weekend', $config->execution->weekend);
|
||||
|
||||
@@ -10,6 +10,9 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace zin;
|
||||
|
||||
jsVar('langManDay', $lang->program->manDay);
|
||||
jsVar('langPostponed', $lang->project->statusList['delay']);
|
||||
|
||||
/* zin: Define the feature bar on main menu. */
|
||||
featureBar
|
||||
(
|
||||
@@ -108,6 +111,7 @@ dtable
|
||||
set::sortLink(createLink('project', 'browse', "programID=$programID&browseType=$browseType¶m=$param&orderBy={name}_{sortType}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}")),
|
||||
set::footPager(usePager()),
|
||||
set::customCols(true),
|
||||
set::onRenderCell(jsRaw('window.renderCell')),
|
||||
set::emptyTip($lang->project->empty),
|
||||
set::createTip($lang->project->create),
|
||||
set::createLink(hasPriv('project', 'create') ? createLink('project', 'createGuide') : ''),
|
||||
|
||||
@@ -10,7 +10,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace zin;
|
||||
|
||||
$linkParams = "projectID={$project->id}&productID={$productID}&branch=$branchID&orderBy=status,id_desc&build=$buildID&type={key}" . ($type == 'bysearch' ? '' : "¶m=$param");
|
||||
$linkParams = "projectID={$project->id}&productID={$productID}&branch=$branchID&orderBy=status,id_desc&build=$buildID&type={key}" . ($type == 'bysearch' ? '' : "¶m=$param") . "&recTotal={$pager->recTotal}&recPerpage={$pager->recPerPage}";
|
||||
featureBar
|
||||
(
|
||||
set::current($type),
|
||||
@@ -29,14 +29,16 @@ toolbar
|
||||
'url' => createLink('bug', 'export', "productID={$productID}&orderBy=$orderBy&browseType=&projectID={$project->id}"),
|
||||
'data-toggle' => 'modal'
|
||||
))) : null,
|
||||
$canCreate ? item(set(array
|
||||
$canCreate ? item
|
||||
(
|
||||
'text' => $lang->bug->create,
|
||||
'icon' => 'plus',
|
||||
'type' => 'primary',
|
||||
'url' => createLink('bug', 'create', "productID={$productID}&branch={$branchID}&extras=projectID={$project->id}")
|
||||
)),
|
||||
setData('app', 'project')
|
||||
set(array
|
||||
(
|
||||
'text' => $lang->bug->create,
|
||||
'icon' => 'plus',
|
||||
'type' => 'primary',
|
||||
'url' => createLink('bug', 'create', "productID={$productID}&branch={$branchID}&extras=projectID={$project->id}")
|
||||
)),
|
||||
setData('app', 'project')
|
||||
) : null
|
||||
);
|
||||
|
||||
@@ -51,7 +53,7 @@ sidebar
|
||||
)))
|
||||
);
|
||||
|
||||
$canBatchAssignTo = common::hasPriv('bug', 'batchAssignTo');
|
||||
$canBatchAssignTo = hasPriv('bug', 'batchAssignTo');
|
||||
|
||||
$config->bug->dtable->fieldList['module']['map'] = $modulePairs;
|
||||
$config->bug->dtable->fieldList['story']['map'] = $stories;
|
||||
|
||||
@@ -37,14 +37,11 @@ featureBar
|
||||
|
||||
/* zin: Define the toolbar on main menu. */
|
||||
$canCreateBuild = hasPriv('projectbuild', 'create') && common::canModify('project', $project);
|
||||
if($canCreateBuild) $createItem = array('icon' => 'plus', 'class' => 'primary', 'text' => $lang->build->create, 'url' => $this->createLink('projectbuild', 'create', "projectID={$project->id}"));
|
||||
toolbar
|
||||
(
|
||||
!empty($createItem) ? item(set($createItem)) : null
|
||||
);
|
||||
|
||||
if($canCreateBuild) toolbar(item(set(array('icon' => 'plus', 'class' => 'primary', 'text' => $lang->build->create, 'url' => createLink('projectbuild', 'create', "projectID={$project->id}")))));
|
||||
|
||||
jsVar('projectID', $project->id);
|
||||
jsVar('changeProductLink', helper::createLink($app->rawModule, $app->rawMethod, "projectID={$project->id}&type=product¶m={productID}&orderBy={$orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"));
|
||||
jsVar('changeProductLink', createLink($app->rawModule, $app->rawMethod, "projectID={$project->id}&type=product¶m={productID}&orderBy={$orderBy}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"));
|
||||
jsVar('scmPathTip', $lang->build->scmPath);
|
||||
jsVar('filePathTip', $lang->build->filePath);
|
||||
jsVar('integratedTip', $lang->build->integrated);
|
||||
@@ -59,12 +56,7 @@ dtable
|
||||
set::sortLink(createLink($app->rawModule, $app->rawMethod, "projectID={$project->id}&type={$type}¶m={$param}&orderBy={name}_{sortType}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}")),
|
||||
set::onRenderCell(jsRaw('window.renderCell')),
|
||||
set::getCellSpan(jsRaw('window.getCellSpan')),
|
||||
set::footPager(
|
||||
usePager
|
||||
(
|
||||
array('linkCreator' => helper::createLink($app->rawModule, $app->rawMethod, "projectID={$project->id}&type={$type}¶m={$param}&orderBy={$orderBy}&recTotal={recTotal}&recPerPage={recPerPage}&pageID={page}"), 'recTotal' => $pager->recTotal, 'recPerPage' => $pager->recPerPage)
|
||||
)
|
||||
),
|
||||
set::footPager(usePager(array('linkCreator' => createLink($app->rawModule, $app->rawMethod, "projectID={$project->id}&type={$type}¶m={$param}&orderBy={$orderBy}&recTotal={recTotal}&recPerPage={recPerPage}&pageID={page}"), 'recTotal' => $pager->recTotal, 'recPerPage' => $pager->recPerPage))),
|
||||
set::emptyTip($lang->build->noBuild),
|
||||
set::createTip($lang->build->create),
|
||||
set::createLink($canCreateBuild ? createLink('projectbuild', 'create', "projectID={$project->id}") : '')
|
||||
|
||||
@@ -22,15 +22,17 @@ jsVar('pageExecSummary', $lang->execution->pageExecSummary);
|
||||
jsVar('checkedExecSummary', $lang->execution->checkedExecSummary);
|
||||
|
||||
$footToolbar = array();
|
||||
$canBatchEdit = common::hasPriv('execution', 'batchEdit');
|
||||
$canBatchChangeStatus = common::hasPriv('execution', 'batchChangeStatus');
|
||||
$canBatchEdit = hasPriv('execution', 'batchEdit');
|
||||
$canBatchChangeStatus = hasPriv('execution', 'batchChangeStatus');
|
||||
$canBatchAction = $canBatchEdit || $canBatchChangeStatus;
|
||||
if($canBatchAction)
|
||||
{
|
||||
$editClass = $canBatchEdit ? 'batch-btn' : 'disabled';
|
||||
$footToolbar['items'][] = array(
|
||||
$footToolbar['items'][] = array
|
||||
(
|
||||
'type' => 'btn-group',
|
||||
'items' => array(
|
||||
'items' => array
|
||||
(
|
||||
array('text' => $lang->edit, 'className' => "secondary size-sm {$editClass}", 'btnType' => 'primary', 'data-url' => createLink('execution', 'batchEdit')),
|
||||
)
|
||||
);
|
||||
@@ -58,6 +60,12 @@ $fnGenerateCols = function() use ($config, $project)
|
||||
$fieldList['actions']['actionsMap']['edit']['url'] = createLink('programplan', 'edit', "stageID={rawID}&projectID={projectID}");
|
||||
}
|
||||
|
||||
if(!$this->cookie->showTask)
|
||||
{
|
||||
$fieldList['name']['type'] = 'title';
|
||||
unset($fieldList['name']['nestedToggle']);
|
||||
}
|
||||
|
||||
return array_values($fieldList);
|
||||
};
|
||||
|
||||
@@ -126,12 +134,7 @@ dtable
|
||||
set::canRowCheckable(jsRaw("function(rowID){return this.getRowInfo(rowID).data.id.indexOf('pid') > -1;}")),
|
||||
set::checkInfo(jsRaw("function(checkedIDList){ return window.footerSummary(this, checkedIDList);}")),
|
||||
set::footToolbar($footToolbar),
|
||||
set::footPager(
|
||||
usePager
|
||||
(
|
||||
array('linkCreator' => helper::createLink('project', 'execution', "status={$status}&projectID=$projectID&orderBy={$orderBy}&productID={$productID}&recTotal={recTotal}&recPerPage={recPerPage}&page={page}"))
|
||||
)
|
||||
),
|
||||
set::footPager(usePager(array('linkCreator' => createLink('project', 'execution', "status={$status}&projectID=$projectID&orderBy={$orderBy}&productID={$productID}&recTotal={recTotal}&recPerPage={recPerPage}&page={page}")))),
|
||||
set::emptyTip($lang->execution->noExecution),
|
||||
set::createTip($isStage ? $lang->programplan->create : $lang->execution->create),
|
||||
set::createLink(hasPriv('execution', 'create') ? $createLink : '')
|
||||
|
||||
@@ -17,7 +17,8 @@ jsVar('confirmDelete', $lang->group->confirmDelete);
|
||||
/* zin: Define the feature bar on main menu. */
|
||||
featureBar
|
||||
(
|
||||
set::current('all'),
|
||||
set::linkParams("projectID={$projectID}&programID={$programID}"),
|
||||
set::current('all')
|
||||
);
|
||||
|
||||
/* zin: Define the toolbar on main menu. */
|
||||
|
||||
@@ -161,11 +161,9 @@ $config->repo->fileExt["yaml"] = array('.yaml', '.yml');
|
||||
|
||||
$config->repo->search['module'] = 'repo';
|
||||
$config->repo->search['fields']['name'] = $lang->repo->name;
|
||||
$config->repo->search['fields']['id'] = $lang->repo->id;
|
||||
$config->repo->search['fields']['product'] = $lang->repo->product;
|
||||
$config->repo->search['fields']['projects'] = $lang->repo->projects;
|
||||
$config->repo->search['fields']['SCM'] = $lang->repo->SCM;
|
||||
$config->repo->search['fields']['desc'] = $lang->repo->desc;
|
||||
|
||||
$config->repo->search['params']['name'] = array('operator' => 'include', 'control' => 'input', 'values' => '');
|
||||
$config->repo->search['params']['product'] = array('operator' => 'include', 'control' => 'select', 'values' => '');
|
||||
|
||||
+62
-229
@@ -322,6 +322,7 @@ class repo extends control
|
||||
$entry = urldecode($entry);
|
||||
$pathInfo = helper::mbPathinfo($entry);
|
||||
|
||||
if($repo->SCM == 'Gitlab') $repo = $this->repo->processGitService($repo, true);
|
||||
if($this->app->tab == 'execution') $this->view->executionID = $objectID;
|
||||
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view;
|
||||
$this->view->dropMenus = $this->repoZen->getBranchAndTagItems($repo, $this->cookie->repoBranch);
|
||||
@@ -539,32 +540,33 @@ class repo extends control
|
||||
$this->scm->setEngine($repo);
|
||||
$log = $this->scm->log('', $revision, $revision);
|
||||
|
||||
$history = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('revision')->eq($log[0]->revision)->andWhere('repo')->eq($repoID)->fetch();
|
||||
if($history)
|
||||
$revision = $this->repo->getHistoryRevision($repoID, $log[0]->revision);
|
||||
if($revision)
|
||||
{
|
||||
if(in_array($repo->SCM, $this->config->repo->gitTypeList))
|
||||
{
|
||||
$thisAndPrevRevisions = $this->scm->exec("rev-list -n 2 {$history->revision} --");
|
||||
$thisAndPrevRevisions = $this->scm->exec("rev-list -n 2 {$revision} --");
|
||||
|
||||
array_shift($thisAndPrevRevisions);
|
||||
if($thisAndPrevRevisions) $oldRevision = array_shift($thisAndPrevRevisions);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oldRevision = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('revision')->lt($history->revision)->andWhere('repo')->eq($repoID)->orderBy('revision_desc')->limit(1)->fetch('revision');
|
||||
$oldRevision = $this->repo->getHistoryRevision($repoID, $revision, false, 'lt');
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($oldRevision))
|
||||
{
|
||||
$oldRevision = '^';
|
||||
if($history and in_array($repo->SCM, $this->config->repo->gitTypeList)) $oldRevision = "{$history->revision}^";
|
||||
if($revision and in_array($repo->SCM, $this->config->repo->gitTypeList)) $oldRevision = "{$revision}^";
|
||||
}
|
||||
|
||||
$this->locate($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=&oldrevision=$oldRevision&newRevision={$log[0]->revision}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码blame信息。
|
||||
* Blame repo file.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -575,13 +577,14 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function blame($repoID, $objectID = 0, $entry = '', $revision = 'HEAD', $encoding = '')
|
||||
public function blame(int $repoID, int $objectID = 0, string $entry = '', string $revision = 'HEAD', string $encoding = '')
|
||||
{
|
||||
$this->commonAction($repoID, $objectID);
|
||||
|
||||
if($this->get->repoPath) $entry = $this->get->repoPath;
|
||||
if($repoID == 0) $repoID = $this->session->repoID;
|
||||
$repo = $this->repo->getByID($repoID);
|
||||
|
||||
$file = $entry;
|
||||
$entry = $this->repo->decodePath($entry);
|
||||
|
||||
@@ -596,7 +599,7 @@ class repo extends control
|
||||
if($encoding != 'utf-8') $blames[$i]['content'] = helper::convertEncoding($blame['content'], $encoding);
|
||||
}
|
||||
|
||||
$log = in_array($repo->SCM, $this->config->repo->gitTypeList) ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->eq($revision)->andWhere('repo')->eq($repo->id)->fetch() : '';
|
||||
$log = in_array($repo->SCM, $this->config->repo->gitTypeList) ? $this->repo->getHistoryRevision($repo->id, $revision, true) : '';
|
||||
|
||||
$this->view->title = $this->lang->repo->common;
|
||||
$this->view->repoID = $repoID;
|
||||
@@ -607,14 +610,14 @@ class repo extends control
|
||||
$this->view->entry = $entry;
|
||||
$this->view->file = $file;
|
||||
$this->view->encoding = str_replace('-', '_', $encoding);
|
||||
$this->view->historys = in_array($repo->SCM, $this->config->repo->gitTypeList) ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->andWhere('repo')->eq($repo->id)->fetchPairs() : '';
|
||||
$this->view->revisionName = ($log and in_array($repo->SCM, $this->config->repo->gitTypeList)) ? $this->repo->getGitRevisionName($log->revision, $log->commit) : $revision;
|
||||
$this->view->revisionName = ($log && in_array($repo->SCM, $this->config->repo->gitTypeList)) ? $this->repo->getGitRevisionName($log->revision, $log->commit) : $revision;
|
||||
$this->view->blames = $blames;
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show diff.
|
||||
* 代码diff信息。
|
||||
* Show repo diff.
|
||||
*
|
||||
* @param int $repoID
|
||||
* @param int $objectID
|
||||
@@ -627,125 +630,51 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function diff($repoID, $objectID = 0, $entry = '', $oldRevision = '', $newRevision = '', $showBug = 'false', $encoding = '', $isBranchOrTag = false)
|
||||
public function diff(int $repoID, int $objectID = 0, string $entry = '', string $oldRevision = '', string $newRevision = '', string $showBug = 'false', string $encoding = '', bool $isBranchOrTag = false)
|
||||
{
|
||||
$newRevision = strtr($newRevision, '*', '-');
|
||||
$oldRevision = strtr($oldRevision, '*', '-');
|
||||
$oldRevision = urldecode(urldecode($oldRevision)); //Fix error.
|
||||
if($isBranchOrTag)
|
||||
{
|
||||
$oldRevision = strtr($oldRevision, '*', '-');
|
||||
$newRevision = strtr($newRevision, '*', '-');
|
||||
if($isBranchOrTag)
|
||||
{
|
||||
$oldRevision = urldecode(helper::safe64Decode($oldRevision));
|
||||
$newRevision = urldecode(helper::safe64Decode($newRevision));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oldRevision = urldecode(urldecode($oldRevision)); //Fix error.
|
||||
$oldRevision = urldecode(helper::safe64Decode($oldRevision));
|
||||
$newRevision = urldecode(helper::safe64Decode($newRevision));
|
||||
}
|
||||
|
||||
$this->commonAction($repoID, $objectID);
|
||||
$repo = $this->repo->getByID($repoID);
|
||||
|
||||
if($this->get->repoPath) $entry = $this->get->repoPath;
|
||||
$file = $entry;
|
||||
$repo = $this->repo->getByID($repoID);
|
||||
$entry = $this->repo->decodePath($entry);
|
||||
|
||||
if($repo->SCM == 'Git' and !is_dir($repo->path))
|
||||
{
|
||||
$error = sprintf($this->lang->repo->error->notFound, $repo->name, $repo->path);
|
||||
return print(js::error($error) . js::locate($this->repo->createLink('maintain')));
|
||||
}
|
||||
|
||||
$pathInfo = pathinfo($entry);
|
||||
$suffix = '';
|
||||
if(isset($pathInfo["extension"])) $suffix = strtolower($pathInfo["extension"]);
|
||||
if($repo->SCM == 'Git' && !is_dir($repo->path)) return $this->sendError(sprintf($this->lang->repo->error->notFound, $repo->name, $repo->path), $this->repo->createLink('maintain'));
|
||||
|
||||
$arrange = $this->cookie->arrange ? $this->cookie->arrange : 'inline';
|
||||
if($this->server->request_method == 'POST')
|
||||
{
|
||||
$oldRevision = isset($this->post->revision[1]) ? $this->post->revision[1] : '';
|
||||
$newRevision = isset($this->post->revision[0]) ? $this->post->revision[0] : '';
|
||||
if($this->server->request_method == 'POST') return $this->repoZen->locateDiffPage($repoID, $objectID, $arrange, $isBranchOrTag, $file);
|
||||
|
||||
if($this->post->arrange)
|
||||
{
|
||||
$arrange = $this->post->arrange;
|
||||
helper::setcookie('arrange', $arrange);
|
||||
}
|
||||
if($this->post->encoding) $encoding = $this->post->encoding;
|
||||
if($this->post->isBranchOrTag) $isBranchOrTag = $this->post->isBranchOrTag;
|
||||
|
||||
$this->locate($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($entry) . "&oldrevision=$oldRevision&newRevision=$newRevision&showBug=&encoding=$encoding&isBranchOrTag=$isBranchOrTag"));
|
||||
}
|
||||
|
||||
$info = new stdClass();
|
||||
$diffs = array();
|
||||
$encoding = empty($encoding) ? $repo->encoding : $encoding;
|
||||
$encoding = strtolower(str_replace('_', '-', $encoding));
|
||||
if($oldRevision !== '')
|
||||
{
|
||||
$this->scm->setEngine($repo);
|
||||
$info = $this->scm->info($entry, $newRevision);
|
||||
$diffs = $this->scm->diff($entry, $oldRevision, $newRevision, 'yes', $isBranchOrTag ? 'isBranchOrTag': '');
|
||||
}
|
||||
foreach($diffs as $diff)
|
||||
{
|
||||
if($encoding != 'utf-8')
|
||||
{
|
||||
$diff->fileName = helper::convertEncoding($diff->fileName, $encoding);
|
||||
if(empty($diff->contents)) continue;
|
||||
foreach($diff->contents as $content)
|
||||
{
|
||||
if(empty($content->lines)) continue;
|
||||
foreach($content->lines as $lines)
|
||||
{
|
||||
if(empty($lines->line)) continue;
|
||||
$lines->line = helper::convertEncoding($lines->line, $encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* When arrange is appose then adjust data for show them easy.*/
|
||||
if($arrange == 'appose')
|
||||
{
|
||||
|
||||
foreach($diffs as $diffFile)
|
||||
{
|
||||
if(empty($diffFile->contents)) continue;
|
||||
foreach($diffFile->contents as $content)
|
||||
{
|
||||
$old = array();
|
||||
$new = array();
|
||||
foreach($content->lines as $line)
|
||||
{
|
||||
if($line->type != 'new') $old[$line->oldlc] = $line->line;
|
||||
if($line->type != 'old') $new[$line->newlc] = $line->line;
|
||||
}
|
||||
$content->old = $old;
|
||||
$content->new = $new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->type = 'diff';
|
||||
$this->view->showBug = $showBug;
|
||||
$this->view->entry = urldecode($entry);
|
||||
$this->view->suffix = $suffix;
|
||||
$this->view->file = $file;
|
||||
$this->view->repoID = $repoID;
|
||||
$this->view->branchID = (string) $this->cookie->repoBranch;
|
||||
$this->view->objectID = $objectID;
|
||||
$this->view->repo = $repo;
|
||||
$this->view->encoding = str_replace('-', '_', $encoding);
|
||||
$this->view->arrange = $arrange;
|
||||
$this->view->diffs = $diffs;
|
||||
$this->view->newRevision = $newRevision;
|
||||
$this->view->oldRevision = $oldRevision;
|
||||
$this->view->revision = $newRevision;
|
||||
$this->view->historys = in_array($repo->SCM, $this->config->repo->gitTypeList) ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->in("$oldRevision,$newRevision")->andWhere('repo')->eq($repo->id)->fetchPairs() : '';
|
||||
$this->view->info = $info;
|
||||
if($encoding != 'utf-8') $diffs = $this->repoZen->encodingDiff($diffs, $encoding);
|
||||
if($arrange == 'appose') $diffs = $this->repoZen->getApposeDiff($diffs);
|
||||
|
||||
$this->view->entry = urldecode($entry);
|
||||
$this->view->encoding = str_replace('-', '_', $encoding);
|
||||
$this->view->file = $file;
|
||||
$this->view->repoID = $repoID;
|
||||
$this->view->branchID = (string) $this->cookie->repoBranch;
|
||||
$this->view->objectID = $objectID;
|
||||
$this->view->repo = $repo;
|
||||
$this->view->diffs = $diffs;
|
||||
$this->view->newRevision = $newRevision;
|
||||
$this->view->oldRevision = $oldRevision;
|
||||
$this->view->isBranchOrTag = $isBranchOrTag;
|
||||
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->diff;
|
||||
|
||||
@@ -753,6 +682,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码下载。
|
||||
* Download repo file.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -764,7 +694,7 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function download($repoID, $path, $fromRevision = 'HEAD', $toRevision = '', $type = 'file', $isBranchOrTag = false)
|
||||
public function download(int $repoID, string $path, string $fromRevision = 'HEAD', string $toRevision = '', string $type = 'file', bool $isBranchOrTag = false)
|
||||
{
|
||||
if($this->get->repoPath) $path = $this->get->repoPath;
|
||||
$entry = $this->repo->decodePath($path);
|
||||
@@ -790,8 +720,9 @@ class repo extends control
|
||||
|
||||
$fileName = basename(urldecode($entry));
|
||||
if($type != 'file') $fileName .= "r$fromRevision--r$toRevision.patch";
|
||||
|
||||
$extension = ltrim(strrchr($fileName, '.'), '.');
|
||||
$this->fetch('file', 'sendDownHeader', array("fileName" => $fileName, "fileType" => $extension, "content" => $content));
|
||||
$this->fetch('file', 'sendDownHeader', array("fileName" => $fileName, "fileType" => $extension, "content" => $content));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -823,7 +754,8 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* Show sync comment.
|
||||
* 显示提交同步进度。
|
||||
* Show sync commit.
|
||||
*
|
||||
* @param int $repoID
|
||||
* @param int $objectID projectID|executionID
|
||||
@@ -831,16 +763,15 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function showSyncCommit($repoID = 0, $objectID = 0, $branch = '')
|
||||
public function showSyncCommit(int $repoID = 0, int $objectID = 0, string $branch = '')
|
||||
{
|
||||
$this->commonAction($repoID, $objectID);
|
||||
|
||||
if($repoID == 0) $repoID = $this->session->repoID;
|
||||
if($branch) $branch = base64_decode(helper::safe64Decode($branch));
|
||||
|
||||
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->showSyncCommit;
|
||||
|
||||
$latestInDB = $this->repo->getLatestCommit($repoID);
|
||||
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->showSyncCommit;
|
||||
$this->view->version = $latestInDB ? (int)$latestInDB->commit : 1;
|
||||
$this->view->repoID = $repoID;
|
||||
$this->view->repo = $this->repo->getByID($repoID);
|
||||
@@ -851,6 +782,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据提交信息关联需求。
|
||||
* Link story to commit.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -898,6 +830,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据提交信息关联Bug。
|
||||
* Link bug to commit.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -945,6 +878,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据提交信息关联任务。
|
||||
* Link task to commit.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -999,6 +933,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消提交信息的关联记录。
|
||||
* Unlink object and commit revision.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -1008,7 +943,7 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function unlink($repoID, $revision, $objectType, $objectID)
|
||||
public function unlink(int $repoID, string $revision, string $objectType, int $objectID)
|
||||
{
|
||||
$this->repo->unlink($repoID, $revision, $objectType, $objectID);
|
||||
|
||||
@@ -1042,10 +977,8 @@ class repo extends control
|
||||
$gitlab = empty($server) ? array_shift($gitlabList) : $this->gitlab->getById($server);
|
||||
|
||||
$repoList = $gitlab ? $this->repoZen->getGitlabNotExistRepos($gitlab) : array();
|
||||
|
||||
$products = $this->loadModel('product')->getPairs('', 0, '', 'all');
|
||||
|
||||
|
||||
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->importAction;
|
||||
$this->view->gitlabPairs = $this->gitlab->getPairs();
|
||||
$this->view->products = $products;
|
||||
@@ -1056,6 +989,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码对比编辑器内容。
|
||||
* Get diff editor content by ajax.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -1063,12 +997,12 @@ class repo extends control
|
||||
* @param string $entry
|
||||
* @param string $oldRevision
|
||||
* @param string $newRevision
|
||||
* @param string $showBug
|
||||
* @param string $showBug // Used for biz.
|
||||
* @param string $encoding
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function ajaxGetDiffEditorContent($repoID, $objectID = 0, $entry = '', $oldRevision = '', $newRevision = '', $showBug = 'false', $encoding = '')
|
||||
public function ajaxGetDiffEditorContent(int $repoID, int $objectID = 0, string $entry = '', string $oldRevision = '', string $newRevision = '', string $showBug = 'false', string $encoding = '')
|
||||
{
|
||||
if(!$entry) $entry = (string) $this->cookie->repoCodePath;
|
||||
|
||||
@@ -1106,6 +1040,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码详情的编辑器内容。
|
||||
* Get editor content by ajax.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -1117,7 +1052,7 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function ajaxGetEditorContent($repoID, $objectID = 0, $entry = '', $revision = 'HEAD', $showBug = 'false', $encoding = '')
|
||||
public function ajaxGetEditorContent(int $repoID, int $objectID = 0, string $entry = '', string $revision = 'HEAD', string $showBug = 'false', string $encoding = '')
|
||||
{
|
||||
if(!$entry) $entry = (string) $this->cookie->repoCodePath;
|
||||
|
||||
@@ -1130,6 +1065,7 @@ class repo extends control
|
||||
$info = $this->scm->info($entry, $revision);
|
||||
$path = $entry ? $info->path : '';
|
||||
if($info->kind == 'dir') $this->locate($this->repo->createLink('browse', "repoID=$repoID&branchID=&objectID=$objectID&path=" . $this->repo->encodePath($path) . "&revision=$revision"));
|
||||
|
||||
$content = $this->scm->cat($entry, $revision);
|
||||
$entry = urldecode($entry);
|
||||
$pathInfo = pathinfo($entry);
|
||||
@@ -1152,17 +1088,11 @@ class repo extends control
|
||||
$this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view;
|
||||
$this->view->type = 'view';
|
||||
$this->view->showBug = $showBug;
|
||||
$this->view->encoding = str_replace('-', '_', $encoding);
|
||||
$this->view->repoID = $repoID;
|
||||
$this->view->branchID = $this->cookie->repoBranch;
|
||||
$this->view->objectID = $objectID;
|
||||
$this->view->repo = $repo;
|
||||
$this->view->revision = $revision;
|
||||
$this->view->oldRevision = '';
|
||||
$this->view->file = $file;
|
||||
$this->view->entry = $entry;
|
||||
$this->view->path = $entry;
|
||||
$this->view->info = $info;
|
||||
$this->view->suffix = $suffix;
|
||||
$this->view->content = $content ? $content : '';
|
||||
$this->view->pathInfo = $pathInfo;
|
||||
@@ -1171,6 +1101,7 @@ class repo extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步同步代码提交记录。
|
||||
* Ajax sync comment.
|
||||
*
|
||||
* @param int $repoID
|
||||
@@ -1178,136 +1109,38 @@ class repo extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function ajaxSyncCommit($repoID = 0, $type = 'batch')
|
||||
public function ajaxSyncCommit(int $repoID = 0, string $type = 'batch')
|
||||
{
|
||||
set_time_limit(0);
|
||||
$repo = $this->repo->getByID($repoID);
|
||||
if(empty($repo)) return;
|
||||
if(empty($repo)) return print($this->config->repo->repoSyncLog->finish);
|
||||
if($repo->synced) return print($this->config->repo->repoSyncLog->finish);
|
||||
|
||||
if(in_array($repo->SCM, array('Gitea', 'Gogs')))
|
||||
{
|
||||
$logFile = realPath($this->app->getTmpRoot() . $this->config->repo->repoSyncLog->logFilePrefix . strtolower($repo->SCM) . ".{$repo->name}.log");
|
||||
if($logFile)
|
||||
{
|
||||
$content = file($logFile);
|
||||
foreach($content as $line)
|
||||
{
|
||||
if($this->repo->strposAry($line, $this->config->repo->repoSyncLog->fatal) !== false) return print($line);
|
||||
if($this->repo->strposAry($line, $this->config->repo->repoSyncLog->failed) !== false) return print($line);
|
||||
}
|
||||
|
||||
$lastLine = $content[count($content) - 1];
|
||||
if($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->done) === false)
|
||||
{
|
||||
if($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->emptyRepo) !== false)
|
||||
{
|
||||
@unlink($logFile);
|
||||
}
|
||||
elseif($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->total) !== false)
|
||||
{
|
||||
$logContent = file_get_contents($logFile);
|
||||
if($this->repo->strposAry($logContent, $this->config->repo->repoSyncLog->finishCount) !== false and $this->repo->strposAry($logContent, $this->config->repo->repoSyncLog->finishCompress) !== false)
|
||||
{
|
||||
@unlink($logFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
return print($this->config->repo->repoSyncLog->one);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return print($this->config->repo->repoSyncLog->one);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@unlink($logFile);
|
||||
}
|
||||
}
|
||||
$syncLog = $this->repoZen->syncLocalCommit($repo);
|
||||
if($syncLog) return print($syncLog);
|
||||
}
|
||||
|
||||
$this->commonAction($repoID);
|
||||
$this->scm->setEngine($repo);
|
||||
|
||||
$branchID = '';
|
||||
if(in_array($repo->SCM, $this->config->repo->gitTypeList) and empty($branchID))
|
||||
{
|
||||
$branches = $this->scm->branch();
|
||||
if($branches)
|
||||
{
|
||||
/* Init branchID. */
|
||||
if($this->cookie->syncBranch) $branchID = $this->cookie->syncBranch;
|
||||
if(!isset($branches[$branchID])) $branchID = '';
|
||||
if(empty($branchID)) $branchID = 'master';
|
||||
|
||||
/* Get unsynced branches. */
|
||||
unset($branches['master']);
|
||||
if($branchID != 'master')
|
||||
{
|
||||
foreach($branches as $branch)
|
||||
{
|
||||
unset($branches[$branch]);
|
||||
if($branch == $branchID) break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->repo->setRepoBranch($branchID);
|
||||
helper::setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
|
||||
}
|
||||
}
|
||||
$branches = $this->repoZen->getSyncBranches($repo, $branchID);
|
||||
|
||||
$logs = array();
|
||||
$version = 1;
|
||||
if($repo->SCM != 'Gitlab')
|
||||
{
|
||||
$latestInDB = $this->dao->select('t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
|
||||
->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
|
||||
->where('t1.repo')->eq($repoID)
|
||||
->beginIF($repo->SCM == 'Git' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
|
||||
->beginIF($repo->SCM == 'Gitlab' and $this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
|
||||
->orderBy('t1.time')
|
||||
->limit(1)
|
||||
->fetch();
|
||||
$latestInDB = $this->repo->getLatestCommit($repoID, false);
|
||||
|
||||
$version = empty($latestInDB) ? 1 : $latestInDB->commit + 1;
|
||||
$revision = $version == 1 ? 'HEAD' : (in_array($repo->SCM, array('Git', 'Gitea', 'Gogs')) ? $latestInDB->commit : $latestInDB->revision);
|
||||
if($type == 'batch')
|
||||
{
|
||||
$logs = $this->scm->getCommits($revision, $this->config->repo->batchNum, $branchID);
|
||||
}
|
||||
else
|
||||
{
|
||||
$logs = $this->scm->getCommits($revision, 0, $branchID);
|
||||
}
|
||||
$logs = $this->scm->getCommits($revision, $type == 'batch' ? $this->config->repo->batchNum : 0, $branchID);
|
||||
}
|
||||
|
||||
$commitCount = $this->repo->saveCommit($repoID, $logs, $version, $branchID);
|
||||
if(empty($commitCount))
|
||||
{
|
||||
if(!$repo->synced)
|
||||
{
|
||||
if(in_array($repo->SCM, $this->config->repo->gitTypeList))
|
||||
{
|
||||
if($branchID) $this->repo->saveExistCommits4Branch($repo->id, $branchID);
|
||||
|
||||
$branchID = reset($branches);
|
||||
setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
|
||||
|
||||
if($branchID) $this->repo->fixCommit($repoID);
|
||||
}
|
||||
|
||||
if(empty($branchID))
|
||||
{
|
||||
$this->repo->markSynced($repoID);
|
||||
return print($this->config->repo->repoSyncLog->finish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->dao->update(TABLE_REPO)->set('commits=commits + ' . $commitCount)->where('id')->eq($repoID)->exec();
|
||||
echo $type == 'batch' ? $commitCount : $this->config->repo->repoSyncLog->finish;
|
||||
echo $this->repoZen->checkSyncResult($repo, $branches, $branchID, $commitCount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+35
-12
@@ -983,28 +983,30 @@ class repoModel extends model
|
||||
* Get latest commit.
|
||||
*
|
||||
* @param int $repoID
|
||||
* @param bool $checkCount
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function getLatestCommit($repoID)
|
||||
public function getLatestCommit($repoID, $checkCount = true)
|
||||
{
|
||||
$count = $this->dao->select('count(DISTINCT t1.id) as count')->from(TABLE_REPOHISTORY)->alias('t1')
|
||||
->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
|
||||
->where('t1.repo')->eq($repoID)
|
||||
->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
|
||||
->fetch('count');
|
||||
|
||||
$repo = $this->fetchByID($repoID);
|
||||
$branchID = (string)$this->cookie->repoBranch;
|
||||
$lastComment = $this->dao->select('t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
|
||||
->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
|
||||
->where('t1.repo')->eq($repoID)
|
||||
->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
|
||||
->beginIF($repo->SCM != 'Subversion' && $branchID)->andWhere('t2.branch')->eq($branchID)->fi()
|
||||
->orderBy('t1.time desc')
|
||||
->limit(1)
|
||||
->fetch();
|
||||
if(empty($lastComment)) return null;
|
||||
if(empty($lastComment)) return new stdclass();
|
||||
if(!$checkCount) return $lastComment;
|
||||
|
||||
$repo = $this->getByID($repoID);
|
||||
if($repo->SCM == 'Git' and $lastComment->commit != $count)
|
||||
$count = $this->dao->select('count(DISTINCT t1.id) as count')->from(TABLE_REPOHISTORY)->alias('t1')
|
||||
->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
|
||||
->where('t1.repo')->eq($repoID)
|
||||
->beginIF($repo->SCM != 'Subversion' && $branchID)->andWhere('t2.branch')->eq($branchID)->fi()
|
||||
->fetch('count');
|
||||
|
||||
if($repo->SCM == 'Git' && $lastComment->commit != $count)
|
||||
{
|
||||
$this->fixCommit($repo->id);
|
||||
$lastComment->commit = $count;
|
||||
@@ -3289,4 +3291,25 @@ class repoModel extends model
|
||||
|
||||
return array_merge($folders, $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询提交记录的版本号。
|
||||
* Get history revision.
|
||||
*
|
||||
* @param int $repoID
|
||||
* @param string $revision
|
||||
* @param bool $withCommit
|
||||
* @param string $condition
|
||||
* @access public
|
||||
* @return string|false
|
||||
*/
|
||||
public function getHistoryRevision(int $repoID, string $revision, bool $withCommit = false, string $condition = 'eq'): string|false
|
||||
{
|
||||
$field = $withCommit ? 'revision, commit' : 'revision';
|
||||
return $this->dao->select($field)->from(TABLE_REPOHISTORY)
|
||||
->where('repo')->eq($repoID)
|
||||
->beginIF($condition != 'lt')->andWhere('revision')->eq($revision)->fi()
|
||||
->beginIF($condition == 'lt')->andWhere('revision')->lt($revision)->fi()
|
||||
->fetch($withCommit ? '' : 'revision');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ $queryMenuLink = createLink('repo', 'maintain', "objectID=$objectID&orderBy=&rec
|
||||
(
|
||||
set::current('all'),
|
||||
set::queryMenuLinkCallback(fn($key) => str_replace('{queryID}', (string)$key, $queryMenuLink)),
|
||||
li(searchToggle(set::open($type == 'bySearch')))
|
||||
li(searchToggle(set::module('repo'), set::open($type == 'bySearch')))
|
||||
);
|
||||
|
||||
toolBar
|
||||
|
||||
@@ -1210,4 +1210,220 @@ class repoZen extends repo
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到版本库的diff页面。
|
||||
* Redirect to diff page.
|
||||
*
|
||||
* @param int $repoID
|
||||
* @param int $objectID
|
||||
* @param string $arrange
|
||||
* @param bool $isBranchOrTag
|
||||
* @param string $file
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function locateDiffPage(int $repoID, int $objectID, string $arrange, bool $isBranchOrTag, string $file)
|
||||
{
|
||||
$oldRevision = isset($this->post->revision[1]) ? $this->post->revision[1] : '';
|
||||
$newRevision = isset($this->post->revision[0]) ? $this->post->revision[0] : '';
|
||||
|
||||
if($this->post->arrange)
|
||||
{
|
||||
$arrange = $this->post->arrange;
|
||||
helper::setcookie('arrange', $arrange);
|
||||
}
|
||||
if($this->post->encoding) $encoding = $this->post->encoding;
|
||||
if($this->post->isBranchOrTag) $isBranchOrTag = $this->post->isBranchOrTag;
|
||||
|
||||
return $this->locate($this->repo->createLink('diff', "repoID={$repoID}&objectID={$objectID}&entry={$file}&oldrevision={$oldRevision}&newRevision={$newRevision}&showBug=&encoding={$encoding}&isBranchOrTag={$isBranchOrTag}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对比信息的编码格式。
|
||||
* Set encoding for diff.
|
||||
*
|
||||
* @param array $diffs
|
||||
* @param string $encoding
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function encodingDiff(array $diffs, string $encoding): array
|
||||
{
|
||||
foreach($diffs as $diff)
|
||||
{
|
||||
$diff->fileName = helper::convertEncoding($diff->fileName, $encoding);
|
||||
if(empty($diff->contents)) continue;
|
||||
|
||||
foreach($diff->contents as $content)
|
||||
{
|
||||
if(empty($content->lines)) continue;
|
||||
|
||||
foreach($content->lines as $lines)
|
||||
{
|
||||
if(empty($lines->line)) continue;
|
||||
$lines->line = helper::convertEncoding($lines->line, $encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $diffs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并列展示的对比信息。
|
||||
* Get appose diff.
|
||||
*
|
||||
* @param array $diffs
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getApposeDiff(array $diffs): array
|
||||
{
|
||||
foreach($diffs as $diffFile)
|
||||
{
|
||||
if(empty($diffFile->contents)) continue;
|
||||
foreach($diffFile->contents as $content)
|
||||
{
|
||||
$old = array();
|
||||
$new = array();
|
||||
foreach($content->lines as $line)
|
||||
{
|
||||
if($line->type != 'new') $old[$line->oldlc] = $line->line;
|
||||
if($line->type != 'old') $new[$line->newlc] = $line->line;
|
||||
}
|
||||
$content->old = $old;
|
||||
$content->new = $new;
|
||||
}
|
||||
}
|
||||
return $diffs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码同步本地的日志。
|
||||
* Get sync log.
|
||||
*
|
||||
* @param object $repo
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function syncLocalCommit(object $repo): string
|
||||
{
|
||||
$logFile = realPath($this->app->getTmpRoot() . $this->config->repo->repoSyncLog->logFilePrefix . strtolower($repo->SCM) . ".{$repo->name}.log");
|
||||
if($logFile)
|
||||
{
|
||||
$content = file($logFile);
|
||||
foreach($content as $line)
|
||||
{
|
||||
if($this->repo->strposAry($line, $this->config->repo->repoSyncLog->fatal) !== false) return $line;
|
||||
if($this->repo->strposAry($line, $this->config->repo->repoSyncLog->failed) !== false) return $line;
|
||||
}
|
||||
|
||||
$lastLine = $content[count($content) - 1];
|
||||
if($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->done) === false)
|
||||
{
|
||||
if($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->emptyRepo) !== false)
|
||||
{
|
||||
@unlink($logFile);
|
||||
}
|
||||
elseif($this->repo->strposAry($lastLine, $this->config->repo->repoSyncLog->total) !== false)
|
||||
{
|
||||
$logContent = file_get_contents($logFile);
|
||||
if($this->repo->strposAry($logContent, $this->config->repo->repoSyncLog->finishCount) !== false and $this->repo->strposAry($logContent, $this->config->repo->repoSyncLog->finishCompress) !== false)
|
||||
{
|
||||
@unlink($logFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->config->repo->repoSyncLog->one;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->config->repo->repoSyncLog->one;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@unlink($logFile);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要同步的分支。
|
||||
* Get sync branches.
|
||||
*
|
||||
* @param object $repo
|
||||
* @param string $branchID
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function getSyncBranches(object $repo, string &$branchID = ''): array
|
||||
{
|
||||
$branches = array();
|
||||
if(in_array($repo->SCM, $this->config->repo->gitTypeList))
|
||||
{
|
||||
$branches = $this->scm->branch();
|
||||
if($branches)
|
||||
{
|
||||
/* Init branchID. */
|
||||
if($this->cookie->syncBranch) $branchID = $this->cookie->syncBranch;
|
||||
if(!isset($branches[$branchID])) $branchID = '';
|
||||
if(empty($branchID)) $branchID = 'master';
|
||||
|
||||
/* Get unsynced branches. */
|
||||
unset($branches['master']);
|
||||
if($branchID != 'master')
|
||||
{
|
||||
foreach($branches as $branch)
|
||||
{
|
||||
unset($branches[$branch]);
|
||||
if($branch == $branchID) break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->repo->setRepoBranch($branchID);
|
||||
helper::setcookie("syncBranch", $branchID, 0, $this->config->webRoot, '', $this->config->cookieSecure, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取同步结果。
|
||||
* Get sync result.
|
||||
*
|
||||
* @param object $repo
|
||||
* @param array $branches
|
||||
* @param string $branchID
|
||||
* @param int $commitCount
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function checkSyncResult(object $repo, array $branches, string $branchID, int $commitCount): string
|
||||
{
|
||||
if(empty($commitCount) && !$repo->synced)
|
||||
{
|
||||
if(in_array($repo->SCM, $this->config->repo->gitTypeList))
|
||||
{
|
||||
if($branchID) $this->repo->saveExistCommits4Branch($repo->id, $branchID);
|
||||
helper::setcookie("syncBranch", reset($branches));
|
||||
|
||||
if($branchID) $this->repo->fixCommit($repo->id);
|
||||
}
|
||||
|
||||
if(empty($branchID))
|
||||
{
|
||||
$this->repo->markSynced($repo->id);
|
||||
return $this->config->repo->repoSyncLog->finish;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dao->update(TABLE_REPO)->set('commits=commits + ' . $commitCount)->where('id')->eq($repo->id)->exec();
|
||||
return $type == 'batch' ? $commitCount : $this->config->repo->repoSyncLog->finish;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ $lang->report->singleColor[] = 'F6BD0F';
|
||||
$lang->report->projectDeviation = "{$lang->execution->common}偏差报表";
|
||||
$lang->report->productSummary = $lang->productCommon . '汇总表';
|
||||
$lang->report->bugCreate = 'Bug创建表';
|
||||
$lang->report->bugAssign = 'Bug指派表';
|
||||
$lang->report->bugAssign = '未解决Bug指派表';
|
||||
$lang->report->workload = '员工负载表';
|
||||
$lang->report->workloadAB = '工作负载';
|
||||
$lang->report->bugOpenedDate = 'Bug创建时间';
|
||||
|
||||
@@ -39,7 +39,7 @@ $lang->report->singleColor[] = 'F6BD0F';
|
||||
$lang->report->projectDeviation = "{$lang->execution->common}偏差報表";
|
||||
$lang->report->productSummary = $lang->productCommon . '彙總表';
|
||||
$lang->report->bugCreate = 'Bug創建表';
|
||||
$lang->report->bugAssign = 'Bug指派表';
|
||||
$lang->report->bugAssign = '未解决Bug指派表';
|
||||
$lang->report->workload = '員工負載表';
|
||||
$lang->report->workloadAB = '工作負載';
|
||||
$lang->report->bugOpenedDate = 'Bug創建時間';
|
||||
|
||||
+41
-11
@@ -237,7 +237,7 @@ class screenModel extends model
|
||||
|
||||
list($component, $typeChanged) = $this->initComponent($chart, $type, $component);
|
||||
|
||||
if(empty($chart) || ($chart->stage == 'draft' || $chart->deleted === '1'))
|
||||
if(empty($chart) || ($chart->stage == 'draft' || $chart->deleted == '1'))
|
||||
{
|
||||
$component->option = new stdclass();
|
||||
if($type == 'chart')
|
||||
@@ -357,10 +357,12 @@ class screenModel extends model
|
||||
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->loadModel('chart')->getMultiData($settings, $chart->sql, $filters);
|
||||
|
||||
$fields = json_decode($chart->fields);
|
||||
$dimensions = array($settings['xaxis'][0]['field']);
|
||||
$sourceData = array();
|
||||
$clientLang = $this->app->getClientLang();
|
||||
$fields = json_decode($chart->fields, true);
|
||||
$dimensions = array($settings['xaxis'][0]['field']);
|
||||
$sourceData = array();
|
||||
$clientLang = $this->app->getClientLang();
|
||||
$xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
|
||||
|
||||
foreach($yStats as $index => $dataList)
|
||||
{
|
||||
$field = zget($fields, $metrics[$index]);
|
||||
@@ -371,6 +373,7 @@ class screenModel extends model
|
||||
|
||||
foreach($dataList as $valueField => $value)
|
||||
{
|
||||
$valueField = $xLabelValues[$valueField];
|
||||
if(empty($sourceData[$valueField]))
|
||||
{
|
||||
$sourceData[$valueField] = new stdclass();
|
||||
@@ -406,10 +409,12 @@ class screenModel extends model
|
||||
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->loadModel('chart')->getMultiData($settings, $chart->sql, $filters);
|
||||
|
||||
$fields = json_decode($chart->fields);
|
||||
$dimensions = array($settings['xaxis'][0]['field']);
|
||||
$sourceData = array();
|
||||
$clientLang = $this->app->getClientLang();
|
||||
$fields = json_decode($chart->fields, true);
|
||||
$dimensions = array($settings['xaxis'][0]['field']);
|
||||
$sourceData = array();
|
||||
$clientLang = $this->app->getClientLang();
|
||||
$xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
|
||||
|
||||
foreach($yStats as $index => $dataList)
|
||||
{
|
||||
$field = zget($fields, $metrics[$index]);
|
||||
@@ -420,6 +425,7 @@ class screenModel extends model
|
||||
|
||||
foreach($dataList as $valueField => $value)
|
||||
{
|
||||
$valueField = $xLabelValues[$valueField];
|
||||
if(empty($sourceData[$valueField]))
|
||||
{
|
||||
$sourceData[$valueField] = new stdclass();
|
||||
@@ -509,11 +515,13 @@ class screenModel extends model
|
||||
|
||||
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->loadModel('chart')->getMultiData($settings, $chart->sql, $filters);
|
||||
|
||||
$fields = json_decode($chart->fields);
|
||||
$fields = json_decode($chart->fields, true);
|
||||
$radarIndicator = array();
|
||||
$seriesData = array();
|
||||
$max = 0;
|
||||
$clientLang = $this->app->getClientLang();
|
||||
$xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
|
||||
|
||||
foreach($yStats as $index => $dataList)
|
||||
{
|
||||
$field = zget($fields, $metrics[$index]);
|
||||
@@ -539,7 +547,7 @@ class screenModel extends model
|
||||
foreach($dataList as $valueField => $value)
|
||||
{
|
||||
$indicator = new stdclass();
|
||||
$indicator->name = $valueField;
|
||||
$indicator->name = $xLabelValues[$valueField];
|
||||
$indicator->max = $max;
|
||||
$radarIndicator[] = $indicator;;
|
||||
}
|
||||
@@ -722,6 +730,28 @@ class screenModel extends model
|
||||
return $screenFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process xLabel with lang
|
||||
*
|
||||
* @param array $xLabel
|
||||
* @param string $type
|
||||
* @param string $object
|
||||
* @param string $field
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function processXLabel($xLabels, $type, $object, $field)
|
||||
{
|
||||
$options = $this->getSysOptions($type, $object, $field);
|
||||
$xLabelValues = array();
|
||||
foreach($xLabels as $index => $label)
|
||||
{
|
||||
$xLabelValues[$label] = isset($options[$label]) ? $options[$label] : $label;
|
||||
}
|
||||
|
||||
return $xLabelValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system options.
|
||||
*
|
||||
|
||||
@@ -128,8 +128,8 @@ class space extends control
|
||||
$defaultApp = '';
|
||||
foreach($pagedApps->apps as $app)
|
||||
{
|
||||
if(in_array($app->id, array(29,51,52,53,54,142))) continue;
|
||||
if(!$appID and $app->alias == 'GitLab') $defaultApp = $app->id;
|
||||
if(strpos($app->name, 'zentao') === 0 || strpos($app->name, 'zdoo') === 0 || strpos($app->name, 'xuanxuan') === 0) continue;
|
||||
if(!$appID && $app->alias == 'GitLab') $defaultApp = $app->id;
|
||||
|
||||
$apps[$app->id] = $app->alias;
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ window.treeClick = function(info)
|
||||
if (info.item.items && info.item.items.length > 0) return;
|
||||
if(checkedCategories[info.item.id] != undefined)
|
||||
{
|
||||
$('#' + info.item.id).parent().removeClass('selected');
|
||||
$('.store-tree-' + info.item.id + ' > .listitem').removeClass('active');
|
||||
delete checkedCategories[info.item.id];
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#' + info.item.id).parent().addClass('selected');
|
||||
$('.store-tree-' + info.item.id + ' > .listitem').addClass('active');
|
||||
checkedCategories[info.item.id] = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ $tree = array();
|
||||
foreach($categories as $id => $category)
|
||||
{
|
||||
$tree[] = array(
|
||||
'id' => $id,
|
||||
'parent' => 0,
|
||||
'name' => $category,
|
||||
'key' => 'storeTree-' . $id
|
||||
'id' => $id,
|
||||
'parent' => 0,
|
||||
'name' => $category,
|
||||
'key' => "storeTree-{$id}",
|
||||
'className' => "store-tree-{$id}"
|
||||
);
|
||||
}
|
||||
jsVar('tree', $tree);
|
||||
|
||||
@@ -270,11 +270,11 @@ $lang->story->sourceList['bug'] = 'Bug';
|
||||
$lang->story->sourceList['forum'] = 'Forum';
|
||||
$lang->story->sourceList['other'] = 'Andere';
|
||||
|
||||
$lang->story->priList[''] = '';
|
||||
$lang->story->priList[1] = '1';
|
||||
$lang->story->priList[2] = '2';
|
||||
$lang->story->priList[3] = '3';
|
||||
$lang->story->priList[4] = '4';
|
||||
$lang->story->priList[0] = '';
|
||||
$lang->story->priList[1] = '1';
|
||||
$lang->story->priList[2] = '2';
|
||||
$lang->story->priList[3] = '3';
|
||||
$lang->story->priList[4] = '4';
|
||||
|
||||
$lang->story->changeList = array();
|
||||
$lang->story->changeList['no'] = 'Cancel';
|
||||
|
||||
@@ -270,11 +270,11 @@ $lang->story->sourceList['bug'] = 'Bug';
|
||||
$lang->story->sourceList['forum'] = 'Forum';
|
||||
$lang->story->sourceList['other'] = 'Others';
|
||||
|
||||
$lang->story->priList[''] = '';
|
||||
$lang->story->priList[1] = '1';
|
||||
$lang->story->priList[2] = '2';
|
||||
$lang->story->priList[3] = '3';
|
||||
$lang->story->priList[4] = '4';
|
||||
$lang->story->priList[0] = '';
|
||||
$lang->story->priList[1] = '1';
|
||||
$lang->story->priList[2] = '2';
|
||||
$lang->story->priList[3] = '3';
|
||||
$lang->story->priList[4] = '4';
|
||||
|
||||
$lang->story->changeList = array();
|
||||
$lang->story->changeList['no'] = 'Cancel';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user