diff --git a/db/update18.1.sql b/db/update18.1.sql index feba83e887..ef6ffc263f 100644 --- a/db/update18.1.sql +++ b/db/update18.1.sql @@ -25,3 +25,6 @@ ALTER table `zt_reviewcl` ADD `type` varchar(255) NOT NULL DEFAULT '' AFTER `cat UPDATE `zt_reviewcl` SET `type` = 'waterfall' WHERE `type` = ''; UPDATE `zt_activity` SET `order` = `id` * 5 WHERE `order` = '0'; + +ALTER table `zt_cmcl` ADD `projectType` varchar(255) NOT NULL DEFAULT '' AFTER `type`; +UPDATE `zt_cmcl` SET `projectType` = 'waterfall' WHERE `projectType` = ''; diff --git a/db/zentao.sql b/db/zentao.sql index cad8faf602..9abe206715 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -9780,6 +9780,7 @@ CREATE TABLE IF NOT EXISTS `zt_reviewlist` ( CREATE TABLE IF NOT EXISTS `zt_cmcl` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `type` char(30) NOT NULL, + `projectType` varchar(255) NOT NULL, `title` int(11) NOT NULL, `contents` text NOT NULL, `assignedTo` varchar(30) NOT NULL, diff --git a/extension/lite/project/ext/view/create.html.php b/extension/lite/project/ext/view/create.html.php index ce3e1f1454..0308b9d310 100644 --- a/extension/lite/project/ext/view/create.html.php +++ b/extension/lite/project/ext/view/create.html.php @@ -40,7 +40,7 @@ project->name;?> - setCode) or $config->setCode == 1):?> + setCode) and $config->setCode == 1):?> project->code;?> diff --git a/framework/helper.class.php b/framework/helper.class.php index 6435c7b69e..cb474da2c8 100644 --- a/framework/helper.class.php +++ b/framework/helper.class.php @@ -339,12 +339,12 @@ class helper extends baseHelper $dateInterval->hour = $interval->format('%H'); $dateInterval->minute = $interval->format('%i'); $dateInterval->secound = $interval->format('%s'); - $dateInterval->year = ltrim($dateInterval->year, '0'); - $dateInterval->month = ltrim($dateInterval->month, '0'); - $dateInterval->day = ltrim($dateInterval->day, '0'); - $dateInterval->hour = ltrim($dateInterval->hour, '0'); - $dateInterval->minute = ltrim($dateInterval->minute, '0'); - $dateInterval->secound = ltrim($dateInterval->secound, '0'); + $dateInterval->year = $dateInterval->year == '00' ? 0 : ltrim($dateInterval->year, '0'); + $dateInterval->month = $dateInterval->month == '00' ? 0 : ltrim($dateInterval->month, '0'); + $dateInterval->day = $dateInterval->day == '00' ? 0 : ltrim($dateInterval->day, '0'); + $dateInterval->hour = $dateInterval->hour == '00' ? 0 : ltrim($dateInterval->hour, '0'); + $dateInterval->minute = $dateInterval->minute == '00' ? 0 : ltrim($dateInterval->minute, '0'); + $dateInterval->secound = $dateInterval->secound == '00' ? 0 : ltrim($dateInterval->secound, '0'); } return $dateInterval; } diff --git a/lib/cache/cache.class.php b/lib/cache/cache.class.php new file mode 100644 index 0000000000..7d17873b08 --- /dev/null +++ b/lib/cache/cache.class.php @@ -0,0 +1,53 @@ + + * @package cache + * @link http://www.zentao.net + */ + +helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'CacheInterface.php'); +helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'CacheException.php'); +helper::import(dirname(__FILE__) . DS . 'simple-cache' . DS . 'InvalidArgumentException.php'); +helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'ApcuDriver.php'); +helper::import(dirname(__FILE__) . DS . 'driver' . DS . 'YacDriver.php'); + +use ZenTao\Cache\SimpleCache\InvalidArgumentException; + +class cache +{ + /** + * @var ZenTao\Cache\SimpleCache\CacheInterface + */ + protected $client; + + public function __construct($driver = 'Apcu', $namespace = '', $defaultLifetime = 0) + { + $driver = ucfirst(strtolower($driver)); + switch($driver) + { + case 'Apcu': + $className = 'ZenTao\Cache\Driver\ApcuDriver'; + break; + case 'Yac': + $className = 'ZenTao\Cache\Driver\YacDriver'; + break; + default: + throw new InvalidArgumentException("Driver {$driver} is not supported."); + } + + if(!extension_loaded($driver)) throw new InvalidArgumentException("Driver ext-{$driver} is not loaded."); + + $this->client = new $className($namespace, $defaultLifetime); + } + + public function __call($name, $arguments) + { + if(!method_exists($this->client, $name)) throw new InvalidArgumentException("Method {$name} does not exist."); + + return call_user_func_array(array($this->client, $name), $arguments); + } +} diff --git a/lib/cache/driver/ApcuDriver.php b/lib/cache/driver/ApcuDriver.php new file mode 100644 index 0000000000..7c2def717a --- /dev/null +++ b/lib/cache/driver/ApcuDriver.php @@ -0,0 +1,173 @@ + + * @package cache + * @link http://www.zentao.net + */ + +namespace ZenTao\Cache\Driver; + +use ZenTao\Cache\SimpleCache\CacheInterface; +use ZenTao\Cache\SimpleCache\InvalidArgumentException; + +class ApcuDriver implements CacheInterface +{ + /** + * @var string + */ + private $namespace; + + /** + * @var int + */ + private $defaultLifetime; + + public function __construct($namespace = '', $defaultLifetime = 0) + { + $this->namespace = $namespace; + $this->defaultLifetime = $defaultLifetime; + } + + public function get($key, $default = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + $value = apcu_fetch($key, $success); + + return $success === false ? $default : $value; + } + + public function set($key, $value, $ttl = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + return apcu_store($key, $value, (int) $ttl); + } + + public function delete($key) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return apcu_delete($key); + } + + public function clear() + { + return apcu_clear_cache(); + } + + public function getMultiple($keys, $default = null) + { + $this->assertKeyNames($keys); + $keys = $this->buildKeyNames($keys); + + $result = apcu_fetch($keys); + + if(!is_null($default) && is_array($result) && count($keys) > count($result)) + { + $notFoundKeys = array_diff($keys, array_keys($result)); + $result = array_merge($result, array_fill_keys($notFoundKeys, $default)); + } + + $mappedResult = array(); + + foreach($result as $key => $value) + { + $key = preg_replace("/^$this->namespace/", '', $key); + + $mappedResult[$key] = $value; + } + + return $mappedResult; + } + + public function setMultiple($values, $ttl = null) + { + $this->assertKeyNames(array_keys($values)); + + $mappedByNamespaceValues = array(); + + foreach($values as $key => $value) + { + $mappedByNamespaceValues[$this->buildKeyName($key)] = $value; + } + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + $result = apcu_store($mappedByNamespaceValues, (int) $ttl); + + return $result === true ? true : (is_array($result) && count($result) == 0 ? true : false); + } + + public function deleteMultiple($keys) + { + $this->assertKeyNames($keys); + $keys = $this->buildKeyNames($keys); + + $result = apcu_delete($keys); + + return count($result) === count($keys) ? false : true; + } + + public function has($key) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return (bool) apcu_exists($key); + } + + /** + * @param string $key + * + * @return string + */ + private function buildKeyName($key) + { + return $this->namespace . $key; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private function buildKeyNames(array $keys) + { + return array_map(function($key) { + return $this->buildKeyName($key); + }, $keys); + + } + + /** + * @param mixed $key + * + * @throws InvalidArgumentException + */ + private function assertKeyName($key) + { + if(!is_scalar($key) || is_bool($key)) throw new InvalidArgumentException(); + } + + /** + * @param string[] $keys + * + * @throws InvalidArgumentException + */ + private function assertKeyNames(array $keys) + { + array_map(function ($value) { + $this->assertKeyName($value); + }, $keys); + } +} diff --git a/lib/cache/driver/YacDriver.php b/lib/cache/driver/YacDriver.php new file mode 100644 index 0000000000..739582c2fa --- /dev/null +++ b/lib/cache/driver/YacDriver.php @@ -0,0 +1,197 @@ + + * @package cache + * @link http://www.zentao.net + */ + +namespace ZenTao\Cache\Driver; + +use ZenTao\Cache\SimpleCache\CacheInterface; +use ZenTao\Cache\SimpleCache\InvalidArgumentException; + +class YacDriver implements CacheInterface +{ + /** + * @var string + */ + private $namespace; + + /** + * @var int + */ + private $defaultLifetime; + + /** + * yac client + * + * @var \Yac + */ + protected $yac; + + /** + * if your key is longer than this, maybe you can use md5 result as the key + */ + const KEY_MAX_LEN = 48; + + public function __construct($namespace = '', $defaultLifetime = 0) + { + $this->namespace = $namespace; + $this->defaultLifetime = $defaultLifetime; + $this->yac = new \Yac($namespace); + } + + public function get($key, $default = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return $this->yac->get($key) ?: $default; + } + + public function set($key, $value, $ttl = null) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + return $this->yac->set($key, $value, (int)$ttl); + } + + public function delete($key) + { + $this->assertKeyName($key); + $key = $this->buildKeyName($key); + + return $this->yac->delete($key); + } + + public function clear() + { + return $this->yac->flush(); + } + + public function getMultiple($keys, $default = null) + { + if(!is_array($keys)) { + return array(); + } + + $hashKeyMap = array(); + foreach($keys as $index => $key) + { + $this->assertKeyName($key); + if(strlen($key) > self::KEY_MAX_LEN) + { + $keys[$index] = $this->buildKeyName($key); + $hashKeyMap[$keys[$index]] = $key; + } + } + + $results = $this->yac->get($keys); + if($results !== false) + { + foreach($results as $key => $value) + { + if(isset($hashKeyMap[$key])) + { + $results[$hashKeyMap[$key]] = $value; + unset($results[$key]); + } + } + return $results; + } + + $results = array(); + foreach($keys as $key) + { + $results[$key] = $default; + } + return $results; + } + + public function setMultiple($values, $ttl = null) + { + if(!is_array($values)) return false; + + foreach($values as $key => $value) + { + if(strlen($key) > self::KEY_MAX_LEN) + { + $values[$this->buildKeyName($key)] = $value; + unset($values[$key]); + } + } + + $ttl = is_null($ttl) ? $this->defaultLifetime : $ttl; + + return $this->yac->set($values, $ttl); + } + + public function deleteMultiple($keys) + { + foreach($keys as $index => $key) + { + $keys[$index] = $this->buildKeyName($key); + } + return $this->yac->delete($keys); + } + + public function has($key) + { + return $this->get($key) !== null; + } + + /** + * @param string $key + * + * @return string + */ + private function buildKeyName($key) + { + if(strlen($key) > self::KEY_MAX_LEN) + { + $key = md5($key); + } + return $key; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private function buildKeyNames(array $keys) + { + return array_map(function ($key) { + return $this->buildKeyName($key); + }, $keys); + } + + /** + * @param mixed $key + * + * @throws InvalidArgumentException + */ + private function assertKeyName($key) + { + if(!is_scalar($key) || is_bool($key)) throw new InvalidArgumentException(); + } + + /** + * @param string[] $keys + * + * @throws InvalidArgumentException + */ + private function assertKeyNames(array $keys) + { + array_map(function ($value) { + $this->assertKeyName($value); + }, $keys); + } +} diff --git a/lib/cache/simple-cache/CacheException.php b/lib/cache/simple-cache/CacheException.php new file mode 100644 index 0000000000..330b06084b --- /dev/null +++ b/lib/cache/simple-cache/CacheException.php @@ -0,0 +1,7 @@ + $keys A list of keys that can be obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * + * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/lib/cache/simple-cache/InvalidArgumentException.php b/lib/cache/simple-cache/InvalidArgumentException.php new file mode 100644 index 0000000000..825bcdf380 --- /dev/null +++ b/lib/cache/simple-cache/InvalidArgumentException.php @@ -0,0 +1,7 @@ + .setting-box > .btn:hover {background: unset;} .settings-list > .setting-box > .btn[disabled] {cursor: not-allowed; pointer-events: unset;} .settings-list > .setting-box > .btn[disabled] > a {pointer-events: none;} +.settings-list > .setting-box > .btn > h4 {padding-right: 5px;} +.settings-list > .setting-box > .btn > h4 > div {gap: 3px;} .settings-list > .setting-box > .btn > h4 > img {padding-right: 8px; padding-bottom: 3px; width: 32px; height: 27px;} -.settings-list > .setting-box > .btn > .setting-desc {padding-bottom: 16px; color: #5E626D; line-height: 25px; height: 50px;} +.settings-list > .setting-box > .btn > .setting-desc {padding-bottom: 16px; color: #5E626D; line-height: 25px; height: 50px; overflow: hidden;} .settings-list.lite-setting > .setting-box {flex: 1 1 30%;} .plugin-list {display: flex; flex-wrap: wrap; padding: 12px 4px 4px 4px;} @@ -71,7 +75,7 @@ .panel.publicClass {padding: 0 10px;} .icon.follow-us {font-size: 20px; padding-left: 10px;} -.setting-help {width: 45px; color: #9EA3B0; padding-top: 15px;} +.setting-help {color: #9EA3B0;} [lang^='zh'] .setting-help {padding-top: 0;} .pointer-none {pointer-events: none;} @@ -86,7 +90,7 @@ .time-block {background: #EDEEF2; padding:2px 4px; border-radius: 4px; color: #0B0F18; font-size: 15px; margin: 0 4px;} .dynamic-block {height: 74px; padding: 12px 16px; display: flex; justify-content: space-between; position: relative; line-height: 20px; border-top: 1px solid #E3E4E9;} .dynamic-content {height: 50px; line-height: 25px; overflow: hidden;} -.dynamic-time {white-space: nowrap; position: absolute; right: 12px; bottom: 10px; padding-right: 12px; background: white; color: #5E626D;} +.dynamic-time {white-space: nowrap; position: absolute; right: 12px; bottom: 12px; padding-left: 5px; padding-right: 12px; background: white; color: #5E626D;} .time-count {letter-spacing: 1px;} .patch-block {padding: 10px 16px 12px; line-height: 20px; border-top: 1px solid #E3E4E9;} diff --git a/module/admin/lang/menu.php b/module/admin/lang/menu.php index 33b05eeafc..5e09e80592 100644 --- a/module/admin/lang/menu.php +++ b/module/admin/lang/menu.php @@ -11,6 +11,7 @@ $lang->admin->menuList->user['order'] = 2; $lang->admin->menuList->switch['name'] = $lang->admin->menuSetting['switch']['name']; $lang->admin->menuList->switch['desc'] = $lang->admin->menuSetting['switch']['desc']; +$lang->admin->menuList->switch['link'] = 'admin|setmodule'; $lang->admin->menuList->switch['order'] = 3; $lang->admin->menuList->model['name'] = $lang->admin->menuSetting['model']['name']; @@ -71,10 +72,6 @@ $lang->admin->menuList->user['menuOrder']['5'] = 'dept'; $lang->admin->menuList->user['menuOrder']['10'] = 'user'; $lang->admin->menuList->user['menuOrder']['15'] = 'group'; -$lang->admin->menuList->switch['subMenu']['setmodule'] = array('link' => "{$lang->admin->module}|admin|setmodule|"); - -$lang->admin->menuList->switch['menuOrder']['5'] = 'setmodule'; - $lang->admin->menuList->model['subMenu']['common'] = array('link' => "{$lang->globalSetting}|custom|required|module=project", 'subModule' => 'custom,subject,holiday,stage', 'exclude' => 'stage-browse,stage-plusbrowse,stage-create,stage-edit,stage-batchcreate'); $lang->admin->menuList->model['subMenu']['scrum'] = array('link' => "{$lang->scrumModel}|auditcl|scrumbrowse|", 'subModule' => 'auditcl'); $lang->admin->menuList->model['subMenu']['waterfall'] = array('link' => "{$lang->waterfallModel}|stage|browse|", 'subModule' => 'stage', 'exclude' => 'stage-settype,stage-plusbrowse'); @@ -165,7 +162,7 @@ $lang->admin->menuList->message['menuOrder']['10'] = 'webhook'; $lang->admin->menuList->message['menuOrder']['20'] = 'browser'; $lang->admin->menuList->message['menuOrder']['25'] = 'setting'; -$lang->admin->menuList->dev['subMenu']['api'] = array('link' => "API|dev|api|module=index"); +$lang->admin->menuList->dev['subMenu']['api'] = array('link' => "{$lang->api}|dev|api|module=index"); $lang->admin->menuList->dev['subMenu']['db'] = array('link' => "{$lang->database}|dev|db|table=" . trim(TABLE_EFFORT, '`')); $lang->admin->menuList->dev['subMenu']['editor'] = array('link' => "{$lang->editor}|dev|editor|"); $lang->admin->menuList->dev['subMenu']['entry'] = array('link' => "{$lang->admin->entry}|entry|browse|", 'subModule' => 'entry'); diff --git a/module/admin/view/index.html.php b/module/admin/view/index.html.php index f695d67c5f..0786657a6c 100755 --- a/module/admin/view/index.html.php +++ b/module/admin/view/index.html.php @@ -21,9 +21,14 @@ vision == 'lite' and !in_array($menuKey, $config->admin->liteMenuList)) continue;?>
diff --git a/module/common/lang/de.php b/module/common/lang/de.php index 4a789e9048..e9c596f4de 100644 --- a/module/common/lang/de.php +++ b/module/common/lang/de.php @@ -253,7 +253,8 @@ $lang->indexPage = 'Index'; $lang->model = 'Model'; $lang->redev = 'Develop'; $lang->browser = 'Browser'; -$lang->database = 'Database'; +$lang->api = 'API Document'; +$lang->database = 'Data Dictionary'; $lang->editor = 'Editor'; $lang->timezone = 'Timezone'; $lang->security = 'Security'; @@ -303,7 +304,7 @@ $lang->devops->set = 'Set'; $lang->admin->module = 'Module'; $lang->admin->system = 'System'; -$lang->admin->entry = 'Application'; +$lang->admin->entry = 'Access ZenTao'; $lang->admin->data = 'Data'; $lang->admin->cron = 'Cron'; $lang->admin->buildIndex = 'Full Text Search'; diff --git a/module/common/lang/en.php b/module/common/lang/en.php index 841d43be23..faa72599f3 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -253,7 +253,8 @@ $lang->indexPage = 'Index'; $lang->model = 'Model'; $lang->redev = 'Develop'; $lang->browser = 'Browser'; -$lang->database = 'Database'; +$lang->api = 'API Document'; +$lang->database = 'Data Dictionary'; $lang->editor = 'Editor'; $lang->timezone = 'Timezone'; $lang->security = 'Security'; @@ -303,7 +304,7 @@ $lang->devops->set = 'Set'; $lang->admin->module = 'Module'; $lang->admin->system = 'System'; -$lang->admin->entry = 'Application'; +$lang->admin->entry = 'Access ZenTao'; $lang->admin->data = 'Data'; $lang->admin->cron = 'Cron'; $lang->admin->buildIndex = 'Full Text Search'; diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php index 3154e31753..e160ac7054 100644 --- a/module/common/lang/fr.php +++ b/module/common/lang/fr.php @@ -253,7 +253,8 @@ $lang->indexPage = 'Index'; $lang->model = 'Model'; $lang->redev = 'Develop'; $lang->browser = 'Browser'; -$lang->database = 'Database'; +$lang->api = 'API Document'; +$lang->database = 'Data Dictionary'; $lang->editor = 'Editor'; $lang->timezone = 'Timezone'; $lang->security = 'Security'; @@ -303,7 +304,7 @@ $lang->devops->set = 'Set'; $lang->admin->module = 'Module'; $lang->admin->system = 'System'; -$lang->admin->entry = 'Application'; +$lang->admin->entry = 'Access ZenTao'; $lang->admin->data = 'Data'; $lang->admin->cron = 'Cron'; $lang->admin->buildIndex = 'Full Text Search'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index 27c1acac06..c3d6b8feab 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -253,7 +253,8 @@ $lang->indexPage = '首页'; $lang->model = '模型'; $lang->redev = '二次开发'; $lang->browser = '浏览器'; -$lang->database = '数据库'; +$lang->api = '接口文档'; +$lang->database = '数据字典'; $lang->editor = '编辑器'; $lang->timezone = '时区'; $lang->security = '安全'; @@ -303,7 +304,7 @@ $lang->devops->set = '设置'; $lang->admin->module = '功能配置'; $lang->admin->system = '系统'; -$lang->admin->entry = '应用'; +$lang->admin->entry = '接入禅道'; $lang->admin->data = '数据'; $lang->admin->cron = '定时'; $lang->admin->buildIndex = '重建索引'; diff --git a/module/custom/model.php b/module/custom/model.php index a497df3e59..84e64c72c6 100644 --- a/module/custom/model.php +++ b/module/custom/model.php @@ -204,6 +204,9 @@ class customModel extends model ksort($menuOrder); foreach($menuOrder as $name) { + /* If menu is removed, delete the menuOrder. */ + if(!isset($allMenu->$name)) continue; + $item = new stdclass(); $item->name = $name; $item->hidden = false; @@ -1134,13 +1137,15 @@ class customModel extends model $disabledFeatures = $this->setting->getItem('owner=system&module=common§ion=&key=disabledFeatures'); $disabledFeatures = $disabledFeatures . ',' . $closedFeatures; - $hasWaterfall = strpos(",{$disabledFeatures},", ',waterfall,') === false; - $hasWaterfallPlus = strpos(",{$disabledFeatures},", ',waterfallplus,') === false; - $hasScrumMeasrecord = strpos(",{$disabledFeatures},", ',scrumMeasrecord,') === false; - $hasWaterfallMeasrecord = (strpos(",{$disabledFeatures},", ',waterfallMeasrecord,') === false and ($hasWaterfall or $hasWaterfallPlus)); + $hasWaterfall = strpos(",{$disabledFeatures},", ',waterfall,') === false; + $hasWaterfallPlus = strpos(",{$disabledFeatures},", ',waterfallplus,') === false; + $hasScrumMeasrecord = strpos(",{$disabledFeatures},", ',scrumMeasrecord,') === false; + $hasAgilePlusMeasrecord = strpos(",{$disabledFeatures},", ',agileMeasrecord,') === false; + $hasWaterfallMeasrecord = (strpos(",{$disabledFeatures},", ',waterfallMeasrecord,') === false and $hasWaterfall); + $hasWaterfallPlusMeasrecord = (strpos(",{$disabledFeatures},", ',waterfallplusMeasrecord,') === false and $hasWaterfallPlus); $cronStatus = 'normal'; - if(!$hasScrumMeasrecord and !$hasWaterfallMeasrecord) $cronStatus = 'stop'; + if(!$hasScrumMeasrecord and !$hasAgilePlusMeasrecord and !$hasWaterfallMeasrecord and $hasWaterfallPlusMeasrecord) $cronStatus = 'stop'; $this->loadModel('cron'); $cron = $this->dao->select('id,status')->from(TABLE_CRON)->where('command')->like('%methodName=initCrontabQueue')->fetch(); diff --git a/module/custom/view/code.html.php b/module/custom/view/code.html.php index 65a80730d4..643aeb940b 100644 --- a/module/custom/view/code.html.php +++ b/module/custom/view/code.html.php @@ -16,7 +16,7 @@ custom->setCode;?> - setCode) ? $config->setCode : 1;?> + setCode) ? $config->setCode : 0;?> custom->conceptOptions->URAndSR as $key => $value):?> diff --git a/module/custom/view/mode.html.php b/module/custom/view/mode.html.php index 5376929d50..7e7dcfc8e8 100644 --- a/module/custom/view/mode.html.php +++ b/module/custom/view/mode.html.php @@ -39,7 +39,7 @@ lang->custom->scrum->common, implode($lang->comma, $disabledScrumFeatures)) : $this->lang->custom->features[$feature];?> - + diff --git a/module/execution/config.php b/module/execution/config.php index 216136c250..89311d721e 100644 --- a/module/execution/config.php +++ b/module/execution/config.php @@ -176,7 +176,7 @@ $config->execution->gantt->linkType['end']['end'] = 2; $config->execution->gantt->linkType['begin']['end'] = 3; $config->execution->datatable = new stdclass(); -if(!isset($config->setCode) or $config->setCode == 1) +if(isset($config->setCode) and $config->setCode == 1) { $config->execution->datatable->defaultField = array('id', 'name', 'code', 'project', 'PM', 'status', 'progress', 'openedDate', 'begin', 'end', 'estimate', 'consumed', 'left', 'burn'); } @@ -201,7 +201,7 @@ $config->execution->datatable->fieldList['name']['nestedToggle'] = true; $config->execution->datatable->fieldList['name']['iconRender'] = true; $config->execution->datatable->fieldList['name']['required'] = 'yes'; -if(!isset($config->setCode) or $config->setCode == 1) +if(isset($config->setCode) and $config->setCode == 1) { $config->execution->datatable->fieldList['code']['title'] = 'execCode'; $config->execution->datatable->fieldList['code']['width'] = '180'; diff --git a/module/execution/control.php b/module/execution/control.php index b97958c31b..de1faad15c 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -2732,12 +2732,17 @@ class execution extends control if($groupBy == 'story' and $browseType == 'task' and !isset($this->lang->kanban->orderList[$orderBy])) $orderBy = 'id_asc'; $kanbanGroup = $this->kanban->getExecutionKanban($executionID, $browseType, $groupBy, '', $orderBy); + if(empty($kanbanGroup)) { $this->kanban->createExecutionLane($executionID, $browseType); $kanbanGroup = $this->kanban->getExecutionKanban($executionID, $browseType, $groupBy, '', $orderBy); } + /* Show lanes of the attribute: no story&bug in request, no bug in design. */ + if(!isset($this->lang->execution->menu->story)) unset($kanbanGroup['story']); + if(!isset($this->lang->execution->menu->qa)) unset($kanbanGroup['bug']); + /* Determines whether an object is editable. */ $canBeChanged = common::canModify('execution', $execution); diff --git a/module/execution/css/batchedit.css b/module/execution/css/batchedit.css index 6051595822..5fb6ae17e4 100644 --- a/module/execution/css/batchedit.css +++ b/module/execution/css/batchedit.css @@ -2,3 +2,5 @@ .c-team-name {width: 120px;} .c-project, .c-user, .c-code, .c-desc, .c-days {width: 150px;} .c-method {width: 45px;} +.c-type > .icon-help {vertical-align: text-top;} +.c-type > .popover .popover-content {font-weight: 400;} diff --git a/module/execution/css/batchedit.en.css b/module/execution/css/batchedit.en.css index 1bc7227968..d4858d89b7 100644 --- a/module/execution/css/batchedit.en.css +++ b/module/execution/css/batchedit.en.css @@ -1,3 +1,4 @@ .c-user {width: 150px !important;} .c-date {width: 125px;} .c-method {width: 60px;} +.c-type {width: 130px;} diff --git a/module/execution/css/create.css b/module/execution/css/create.css index 8a387053df..8ba2c65184 100644 --- a/module/execution/css/create.css +++ b/module/execution/css/create.css @@ -39,3 +39,4 @@ .productsBox .required + .text-danger.help-text {position: relative; left: 10px;} .productsBox > #productNameLabel {padding-top: 8px;} .productsBox div[id^='branch'].chosen-disabled {pointer-events: none;} +.methodTip icon:before {margin-bottom: 5px;} diff --git a/module/execution/js/batchedit.js b/module/execution/js/batchedit.js index db84c6b085..603122e2bd 100644 --- a/module/execution/js/batchedit.js +++ b/module/execution/js/batchedit.js @@ -27,3 +27,8 @@ $('#executionForm').on('change input mousedown', '.has-error', function() $(this).parent().find('.text-danger').remove(); $(this).removeClass('has-error'); }) + +$(function() +{ + $('[data-toggle="popover"]').popover(); +}) diff --git a/module/execution/js/create.js b/module/execution/js/create.js index 750bdae6b0..56ac637410 100644 --- a/module/execution/js/create.js +++ b/module/execution/js/create.js @@ -153,6 +153,8 @@ $(function() { $('.disabledBranch div[id^="branch"]').addClass('chosen-disabled'); } + + $('[data-toggle="popover"]').popover(); }); function showLifeTimeTips() diff --git a/module/execution/js/edit.js b/module/execution/js/edit.js index 5527fd5b0b..58f5d31712 100644 --- a/module/execution/js/edit.js +++ b/module/execution/js/edit.js @@ -112,6 +112,8 @@ $(function() { $('.disabledBranch div[id^="branch"]').addClass('chosen-disabled'); } + + $('[data-toggle="popover"]').popover(); }) var lastProjectID = $("#project").val(); diff --git a/module/execution/lang/de.php b/module/execution/lang/de.php index d6c506033d..aacc1a5dba 100644 --- a/module/execution/lang/de.php +++ b/module/execution/lang/de.php @@ -151,6 +151,8 @@ $lang->execution->left = 'Left'; $lang->execution->copyTeamTip = "copy Project/project team members"; $lang->execution->daysGreaterProject = 'Days cannot be greater than days of execution 『%s』'; $lang->execution->errorHours = 'Hours/Day cannot be greater than『24』'; +$lang->execution->agileplusMethodTip = 'When creating executions in an Agile Plus project, both Iteration and Kanban management methods are supported.'; +$lang->execution->typeTip = "The sub-stages of other types can be created under the parent stage of the 'mix' type, while the type of other parent-child levels is consistent."; $lang->execution->start = 'Start'; $lang->execution->activate = 'Aktivieren'; diff --git a/module/execution/lang/en.php b/module/execution/lang/en.php index 995e4f9d53..d8f522124d 100644 --- a/module/execution/lang/en.php +++ b/module/execution/lang/en.php @@ -151,6 +151,8 @@ $lang->execution->left = 'Left'; $lang->execution->copyTeamTip = "copy Project/project team members"; $lang->execution->daysGreaterProject = 'Days cannot be greater than days of execution 『%s』'; $lang->execution->errorHours = 'Hours/Day cannot be greater than『24』'; +$lang->execution->agileplusMethodTip = 'When creating executions in an Agile Plus project, both Iteration and Kanban management methods are supported.'; +$lang->execution->typeTip = "The sub-stages of other types can be created under the parent stage of the 'mix' type, while the type of other parent-child levels is consistent."; $lang->execution->start = 'Start'; $lang->execution->activate = 'Activate'; diff --git a/module/execution/lang/fr.php b/module/execution/lang/fr.php index ac5351d360..46f9661aa7 100644 --- a/module/execution/lang/fr.php +++ b/module/execution/lang/fr.php @@ -151,6 +151,8 @@ $lang->execution->left = 'Left'; $lang->execution->copyTeamTip = "copy Project/project team members"; $lang->execution->daysGreaterProject = 'Days cannot be greater than days of execution 『%s』'; $lang->execution->errorHours = 'Hours/Day cannot be greater than『24』'; +$lang->execution->agileplusMethodTip = 'When creating executions in an Agile Plus project, both Iteration and Kanban management methods are supported.'; +$lang->execution->typeTip = "The sub-stages of other types can be created under the parent stage of the 'mix' type, while the type of other parent-child levels is consistent."; $lang->execution->start = 'Démarrer'; $lang->execution->activate = 'Activer'; diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php index a83b65dc25..211e47444a 100644 --- a/module/execution/lang/zh-cn.php +++ b/module/execution/lang/zh-cn.php @@ -151,6 +151,8 @@ $lang->execution->left = '剩余'; $lang->execution->copyTeamTip = "可以选择复制项目或{$lang->execution->common}团队的成员"; $lang->execution->daysGreaterProject = '可用工日不能大于执行的可用工日『%s』'; $lang->execution->errorHours = '可用工时/天不能大于『24』'; +$lang->execution->agileplusMethodTip = '融合敏捷项目创建执行时,支持迭代和看板两种管理方法。'; +$lang->execution->typeTip = '“综合”类型的父阶段可以创建其它类型的子级,其它父子层级的类型均一致。'; $lang->execution->start = "开始"; $lang->execution->activate = "激活"; diff --git a/module/execution/model.php b/module/execution/model.php index 76f9b07418..ea425e1b3b 100755 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -533,7 +533,7 @@ class executionModel extends model $oldExecution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch(); /* Judgment of required items. */ - if($oldExecution->type != 'stage' and $this->post->code == '' and (!isset($this->config->setCode) or $this->config->setCode == 1)) + if($oldExecution->type != 'stage' and $this->post->code == '' and isset($this->config->setCode) and $this->config->setCode == 1) { dao::$errors['code'] = sprintf($this->lang->error->notempty, $this->lang->execution->code); return false; @@ -5789,7 +5789,7 @@ class executionModel extends model $_POST['status'] = $project->status; $_POST['acl'] = 'open'; - if(!isset($this->config->setCode) or $this->config->setCode == 1) $_POST['code'] = $project->code; + if(isset($this->config->setCode) and $this->config->setCode == 1) $_POST['code'] = $project->code; $projectProducts = $this->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchAll(); foreach($projectProducts as $projectProduct) diff --git a/module/execution/view/batchedit.html.php b/module/execution/view/batchedit.html.php index 5b95828e0f..7f75310b9d 100755 --- a/module/execution/view/batchedit.html.php +++ b/module/execution/view/batchedit.html.php @@ -55,14 +55,17 @@ execution->method;?> execution->$name;?> - setCode) or $config->setCode == 1):?> + setCode) and $config->setCode == 1):?> execution->$code;?> '>execution->$PM;?> '>execution->PO;?> '>execution->QD;?> '>execution->RD;?> - '>execution->$type;?> + '> + execution->$type;?> + + execution->begin;?> execution->end;?> '>execution->$desc;?> @@ -91,7 +94,7 @@ execution->typeList, $executions[$executionID]->type);?> name, "class='form-control' id='names{$executionID}'");?> - setCode) or $config->setCode == 1):?> + setCode) and $config->setCode == 1):?> code, "id='codes{$executionID}' class='form-control'");?> ' style='overflow:visible'>PM, "class='form-control picker-select'");?> diff --git a/module/execution/view/create.html.php b/module/execution/view/create.html.php index cb134dfd7e..7f3cc53419 100644 --- a/module/execution/view/create.html.php +++ b/module/execution/view/create.html.php @@ -66,7 +66,10 @@ execution->method;?> execution->typeList, $type, "class='form-control chosen' required onchange='setType(this.value)'");?> - + + + + @@ -74,7 +77,7 @@ - setCode) or $config->setCode == 1):?> + setCode) and $config->setCode == 1):?> execution->execCode : $lang->execution->code;?> diff --git a/module/execution/view/edit.html.php b/module/execution/view/edit.html.php index 6ade126e6e..3eb89bfe7e 100644 --- a/module/execution/view/edit.html.php +++ b/module/execution/view/edit.html.php @@ -50,7 +50,7 @@ execution->name;?> name, "class='form-control' required");?> - setCode) or $config->setCode == 1):?> + setCode) and $config->setCode == 1):?> execution->code;?> code, "class='form-control' required");?> @@ -98,6 +98,9 @@ } ?> + + + diff --git a/module/execution/view/grouptask.html.php b/module/execution/view/grouptask.html.php index 4dccd0971f..5633b05d66 100644 --- a/module/execution/view/grouptask.html.php +++ b/module/execution/view/grouptask.html.php @@ -63,10 +63,13 @@ $link = common::hasPriv('execution', 'importTask') ? $this->createLink('execution', 'importTask', "execution=$execution->id") : '#'; echo "
  • " . html::a($link, $lang->execution->importTask, '', $misc) . "
  • "; - $class = common::hasPriv('execution', 'importBug') ? '' : "class=disabled"; - $misc = common::hasPriv('execution', 'importBug') ? "class='import'" : "class=disabled"; - $link = common::hasPriv('execution', 'importBug') ? $this->createLink('execution', 'importBug', "execution=$execution->id") : '#'; - echo "
  • " . html::a($link, $lang->execution->importBug, '', $misc) . "
  • "; + if(isset($this->lang->execution->menu->qa)) + { + $class = common::hasPriv('execution', 'importBug') ? '' : "class=disabled"; + $misc = common::hasPriv('execution', 'importBug') ? "class='import'" : "class=disabled"; + $link = common::hasPriv('execution', 'importBug') ? $this->createLink('execution', 'importBug', "execution=$execution->id") : '#'; + echo "
  • " . html::a($link, $lang->execution->importBug, '', $misc) . "
  • "; + } ?> diff --git a/module/execution/view/taskkanban.html.php b/module/execution/view/taskkanban.html.php index b1446f0498..2c87281cd1 100644 --- a/module/execution/view/taskkanban.html.php +++ b/module/execution/view/taskkanban.html.php @@ -17,7 +17,7 @@