diff --git a/config/filter.php b/config/filter.php index d12e06a8b2..75094f3dc9 100755 --- a/config/filter.php +++ b/config/filter.php @@ -2,7 +2,7 @@ $filter = new stdclass(); $filter->rules = new stdclass(); $filter->rules->md5 = '/^[a-z0-9]{32}$/'; -$filter->rules->base64 = '/^[a-zA-Z0-9\+\/\=]+$/'; +$filter->rules->base64 = '/^[a-zA-Z0-9\+\/\=\.]+$/'; $filter->rules->checked = '/^[0-9,\-]+$/'; $filter->rules->idList = '/^[0-9\|]+$/'; $filter->rules->lang = '/^[a-zA-Z_\-]+$/'; diff --git a/db/update18.0.beta1.sql b/db/update18.0.beta1.sql new file mode 100644 index 0000000000..8135c190d3 --- /dev/null +++ b/db/update18.0.beta1.sql @@ -0,0 +1,4 @@ +ALTER TABLE `zt_story` ADD COLUMN `siblings` varchar(255) NOT NULL AFTER `linkRequirements`; +ALTER TABLE `zt_productplan` MODIFY COLUMN `branch` varchar(255) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_effort` ADD `extra` text COLLATE 'utf8_general_ci' NOT NULL AFTER `end`; +ALTER TABLE `zt_build` MODIFY COLUMN `branch` varchar(255) NOT NULL DEFAULT '0'; diff --git a/db/zentao.sql b/db/zentao.sql index 6b1d67115b..9fbac1cf39 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -321,7 +321,7 @@ CREATE TABLE IF NOT EXISTS `zt_build` ( `id` mediumint(8) unsigned NOT NULL auto_increment, `project` mediumint(8) unsigned NOT NULL, `product` mediumint(8) unsigned NOT NULL default '0', - `branch` mediumint(8) unsigned NOT NULL default '0', + `branch` varchar(255) NOT NULL DEFAULT '0', `execution` mediumint(8) unsigned NOT NULL default '0', `builds` varchar(255) NOT NULL, `name` char(150) NOT NULL, @@ -1129,7 +1129,7 @@ CREATE TABLE IF NOT EXISTS `zt_product` ( CREATE TABLE IF NOT EXISTS `zt_productplan` ( `id` mediumint(8) unsigned NOT NULL auto_increment, `product` mediumint(8) unsigned NOT NULL, - `branch` mediumint(8) unsigned NOT NULL, + `branch` varchar(255) NOT NULL DEFAULT '0', `parent` mediumint(9) NOT NULL DEFAULT '0', `title` varchar(90) NOT NULL, `status` enum('wait','doing','done','closed') NOT NULL default 'wait', @@ -1480,6 +1480,7 @@ CREATE TABLE IF NOT EXISTS `zt_story` ( `childStories` varchar(255) NOT NULL, `linkStories` varchar(255) NOT NULL, `linkRequirements` varchar(255) NOT NULL, + `siblings` varchar(255) NOT NULL, `duplicateStory` mediumint(8) unsigned NOT NULL, `version` smallint(6) NOT NULL default '1', `feedbackBy` varchar(100) NOT NULL, @@ -6553,6 +6554,7 @@ ALTER TABLE `zt_effort` CHANGE `begin` `begin` smallint(4) unsigned zerofill NOT ALTER TABLE `zt_effort` CHANGE `end` `end` smallint(4) unsigned zerofill NOT NULL AFTER `begin`; ALTER TABLE `zt_effort` ADD `deleted` enum('0','1') NOT NULL DEFAULT '0' AFTER `end`; ALTER TABLE `zt_effort` ADD `order` tinyint unsigned NOT NULL DEFAULT '0' AFTER `end`; +ALTER TABLE `zt_effort` ADD `extra` text COLLATE 'utf8_general_ci' NULL AFTER `end`; ALTER TABLE `zt_effort` ADD INDEX `execution` (`execution`); ALTER TABLE `zt_effort` ADD INDEX `objectID` (`objectID`); ALTER TABLE `zt_effort` ADD INDEX `date` (`date`); @@ -7177,6 +7179,7 @@ CREATE TABLE IF NOT EXISTS `zt_ticket` ( key `product` (`product`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_ticketsource`; CREATE TABLE IF NOT EXISTS `zt_ticketsource` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `ticketId` mediumint(8) unsigned NOT NULL, @@ -7188,6 +7191,7 @@ CREATE TABLE IF NOT EXISTS `zt_ticketsource` ( key `ticketId` (`ticketId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_ticketrelation`; CREATE TABLE IF NOT EXISTS `zt_ticketrelation` ( `id` mediumint unsigned NOT NULL AUTO_INCREMENT, `ticketId` mediumint unsigned NOT NULL, diff --git a/framework/base/control.class.php b/framework/base/control.class.php index cc4853fe49..ab6415af38 100644 --- a/framework/base/control.class.php +++ b/framework/base/control.class.php @@ -600,7 +600,6 @@ class baseControl if(!empty($jsExtPath)) { - $realModulePath = realPath($modulePath); foreach($jsExtPath as $jsPath) { if(empty($jsPath)) continue; @@ -730,7 +729,7 @@ class baseControl chdir(dirname($viewFile)); /** - * 使用extract安定ob方法渲染$viewFile里面的代码。 + * 使用extract和ob方法渲染$viewFile里面的代码。 * Use extract and ob functions to eval the codes in $viewFile. */ extract((array)$this->view); diff --git a/framework/base/helper.class.php b/framework/base/helper.class.php index 1089b8d145..acec227494 100644 --- a/framework/base/helper.class.php +++ b/framework/base/helper.class.php @@ -87,7 +87,7 @@ class baseHelper /* 生成url链接的开始部分。Set the begin parts of the link. */ if($config->requestType == 'PATH_INFO') $link = $config->webRoot . $appName; if($config->requestType != 'PATH_INFO') $link = $config->webRoot . $appName . basename($_SERVER['SCRIPT_NAME']); - if($config->requestType == 'PATH_INFO2') $link .= '/'; + if($config->requestType == 'PATH_INFO2') $link = '/'; /** * #1: RequestType为GET。When the requestType is GET. @@ -178,7 +178,7 @@ class baseHelper * Check in only body mode or not. * * @access public - * @return void + * @return bool */ public static function inOnlyBodyMode() { @@ -464,7 +464,7 @@ class baseHelper $agent = $_SERVER["HTTP_USER_AGENT"]; - /* Chrome should checked before safari.*/ + /* Chrome should check before safari.*/ if(strpos($agent, 'Firefox') !== false) $browser['name'] = "firefox"; if(strpos($agent, 'Opera') !== false) $browser['name'] = 'opera'; if(strpos($agent, 'Safari') !== false) $browser['name'] = 'safari'; @@ -853,7 +853,7 @@ function a($var) * Judge the server ip is local or not. * * @access public - * @return void + * @return bool */ function isLocalIP() { @@ -954,33 +954,42 @@ function htmlSpecialString($string, $flags = '', $encoding = 'UTF-8') return htmlspecialchars($string, $flags, $encoding); } -if (!function_exists('array_column')) +if(!function_exists('array_column')) { function array_column(array $input, $columnKey, $indexKey = null) { $output = array(); - foreach ($input as $row) { - $key = $value = null; + foreach($input as $row) + { + $key = $value = null; $keySet = $valueSet = false; - if (null !== $indexKey && array_key_exists($indexKey, $row)) { + if(null !== $indexKey && array_key_exists($indexKey, $row)) + { $keySet = true; - $key = (string) $row[$indexKey]; + $key = (string) $row[$indexKey]; } - if (null === $columnKey) { + if(null === $columnKey) + { $valueSet = true; - $value = $row; - } elseif (\is_array($row) && \array_key_exists($columnKey, $row)) { + $value = $row; + } + elseif(\is_array($row) && \array_key_exists($columnKey, $row)) + { $valueSet = true; - $value = $row[$columnKey]; + $value = $row[$columnKey]; } - if ($valueSet) { - if ($keySet) { + if($valueSet) + { + if($keySet) + { $output[$key] = $value; - } else { + } + else + { $output[] = $value; } } diff --git a/framework/base/model.class.php b/framework/base/model.class.php index bf4a0136ab..3c734d0071 100644 --- a/framework/base/model.class.php +++ b/framework/base/model.class.php @@ -281,8 +281,8 @@ class baseModel /* 设置扩展的名字和相应的文件。Set extenson name and extension file. */ $moduleExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, $type); if(!empty($moduleExtPath['site'])) $extensionFile = $moduleExtPath['site'] . 'class/' . $extensionName . '.class.php'; - if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php'; if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['saas'] . 'class/' . $extensionName . '.class.php'; + if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php'; if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['vision'] . 'class/' . $extensionName . '.class.php'; if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['xuan'] . 'class/' . $extensionName . '.class.php'; if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['common'] . 'class/' . $extensionName . '.class.php'; diff --git a/framework/base/router.class.php b/framework/base/router.class.php index cceaba51cd..e1a87cf014 100644 --- a/framework/base/router.class.php +++ b/framework/base/router.class.php @@ -734,10 +734,13 @@ class baseRouter $account = $sql->quote($account); $vision = $this->dbh->query("SELECT * FROM " . TABLE_CONFIG . " WHERE owner = $account AND `key` = 'vision' LIMIT 1")->fetch(); if($vision) $vision = $vision->value; - if(empty($vision)) + + $user = $this->dbh->query("SELECT * FROM " . TABLE_USER . " WHERE account = $account AND deleted = '0' LIMIT 1")->fetch(); + if(!empty($user->visions)) { - $user = $this->dbh->query("SELECT * FROM " . TABLE_USER . " WHERE account = $account AND deleted = '0' LIMIT 1")->fetch(); - if(!empty($user->visions)) list($vision) = explode(',', $user->visions); + $userVisions = explode(',', $user->visions); + if(!in_array($vision, $userVisions)) $vision = ''; + if(empty($vision)) list($vision) = $userVisions; } } @@ -1716,7 +1719,7 @@ class baseRouter } /* 1. 如果extensionLevel == 2,且扩展文件存在,返回该站点扩展文件。 If extensionLevel == 2 and site extensionFile exists, return it. */ - if($this->config->framework->extensionLevel == 2 and !empty( $moduleExtPaths['site'])) + if($this->config->framework->extensionLevel == 2 and !empty($moduleExtPaths['site'])) { $this->extActionFile = $moduleExtPaths['site'] . $this->methodName . '.php'; if(file_exists($this->extActionFile)) return true; @@ -1754,7 +1757,7 @@ class baseRouter if(empty($moduleExtPaths)) return false; /* 如果extensionLevel == 2,且扩展文件存在,返回该站点扩展文件。If extensionLevel == 2 and site extensionFile exists, return it. */ - if($this->config->framework->extensionLevel == 2 and !empty( $moduleExtPaths['site'])) + if($this->config->framework->extensionLevel == 2 and !empty($moduleExtPaths['site'])) { $locateFile = $moduleExtPaths['site'] . $this->methodName . '.302'; if(file_exists($locateFile)) $this->sendAPI($locateFile); @@ -2494,9 +2497,8 @@ class baseRouter * * @param string $moduleName module name * @param string $appName app name - * @param bool $exitIfNone exit or not * @access public - * @return object|bool the config object or false. + * @return void */ public function loadModuleConfig($moduleName, $appName = '') { @@ -2522,7 +2524,7 @@ class baseRouter if(!empty($extConfigPath['saas'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['saas'], '.php')); if(!empty($extConfigPath['custom'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['custom'], '.php')); } - if($config->framework->extensionLevel == 2 and !empty($extConfigPath['site'])) $siteExtConfigFiles = helper::ls($extConfigPath['site'], '.php'); + if($config->framework->extensionLevel == 2 and !empty($extConfigPath['site'])) $siteExtConfigFiles = helper::ls($extConfigPath['site'], '.php'); $extConfigFiles = array_merge($commonExtConfigFiles, $siteExtConfigFiles); /* 将主配置文件和扩展配置文件合并在一起。Put the main config file and extension config files together. */ @@ -2687,12 +2689,12 @@ class baseRouter if($this->config->framework->extensionLevel >= 1) { if(!empty($extLangPath['common'])) $commonExtLangFiles = helper::ls($extLangPath['common'] . $this->clientLang, '.php'); - if(!empty($extLangPath['xuan'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['xuan'] . $this->clientLang, '.php')); + if(!empty($extLangPath['xuan'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['xuan'] . $this->clientLang, '.php')); if(!empty($extLangPath['vision'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['vision'] . $this->clientLang, '.php')); if(!empty($extLangPath['custom'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['custom'] . $this->clientLang, '.php')); if(!empty($extLangPath['saas'])) $commonExtLangFiles = array_merge($commonExtLangFiles, helper::ls($extLangPath['saas'] . $this->clientLang, '.php')); } - if($this->config->framework->extensionLevel == 2 and !empty($extLangPath['site'])) $siteExtLangFiles = helper::ls($extLangPath['site'] . $this->clientLang, '.php'); + if($this->config->framework->extensionLevel == 2 and !empty($extLangPath['site'])) $siteExtLangFiles = helper::ls($extLangPath['site'] . $this->clientLang, '.php'); $extLangFiles = array_merge($commonExtLangFiles, $siteExtLangFiles); } diff --git a/framework/router.class.php b/framework/router.class.php index dac06b4ac9..9be28106e7 100755 --- a/framework/router.class.php +++ b/framework/router.class.php @@ -315,9 +315,8 @@ class router extends baseRouter * * @param string $moduleName module name * @param string $appName app name - * @param bool $exitIfNone exit or not * @access public - * @return object|bool the config object or false. + * @return void */ public function loadModuleConfig($moduleName, $appName = '') { diff --git a/lib/pager/pager.class.php b/lib/pager/pager.class.php index 0d0b016fa9..a3cfd2f4ba 100644 --- a/lib/pager/pager.class.php +++ b/lib/pager/pager.class.php @@ -130,7 +130,11 @@ class pager extends basePager } } - echo ""; + global $app, $lang; + $appendApp = ''; + $moduleName = $this->moduleName; + if(isset($lang->navGroup->{$moduleName}) and $lang->navGroup->{$moduleName} != $app->tab) $appendApp = "#app={$app->tab}"; + echo ""; } } else diff --git a/lib/scm/gitrepo.class.php b/lib/scm/gitrepo.class.php index 335768ffb5..718dbd6983 100644 --- a/lib/scm/gitrepo.class.php +++ b/lib/scm/gitrepo.class.php @@ -28,7 +28,9 @@ class GitRepo if($branch) { $branches = $this->branch(); - if(isset($branches[$branch])) $branch = "origin/$branch"; + $cmd = escapeCmd("$this->client branch -r"); + execCmd($cmd . ' 2>&1', 'string', $result); + if(isset($branches[$branch]) and !empty($result)) $branch = "origin/$branch"; } $this->branch = $branch; $this->repo = $repo; diff --git a/lib/zfile/zfile.class.php b/lib/zfile/zfile.class.php index 4903d50df4..0d2db1c2e6 100644 --- a/lib/zfile/zfile.class.php +++ b/lib/zfile/zfile.class.php @@ -18,10 +18,12 @@ class zfile * @param string $to * @param bool $logLevel * @param string $logFile + * @param array $excludeFiles + * @param bool $toIsLink * @access public * @return array copied files, count, size or message. */ - public function copyDir($from, $to, $logLevel = false, $logFile = '') + public function copyDir($from, $to, $logLevel = false, $logFile = '', $excludeFiles = array(), $toIsLink = false) { static $copiedFiles = array(); static $errorFiles = array(); @@ -41,12 +43,21 @@ class zfile if(!empty($log['message'])) return $log; $from = realpath($from) . '/'; - $to = realpath($to) . '/'; + if(is_link($to) || $toIsLink) + { + $to = $to . '/'; + + $toIsLink = true; + } + else + { + $to = realpath($to) . '/'; + } $entries = scandir($from); foreach($entries as $entry) { - if($entry == '.' or $entry == '..' or $entry == '.svn' or $entry == '.git') continue; + if($entry == '.' or $entry == '..' or $entry == '.svn' or $entry == '.git' or in_array($entry, $excludeFiles)) continue; $fullEntry = $from . $entry; if(is_file($fullEntry)) @@ -82,7 +93,7 @@ class zfile { $nextFrom = $fullEntry; $nextTo = $to . $entry; - $result = $this->copyDir($nextFrom, $nextTo, $logLevel, $logFile); + $result = $this->copyDir($nextFrom, $nextTo, $logLevel, $logFile, array(), $toIsLink); $count += $result['count']; $size += $result['size']; } @@ -101,10 +112,11 @@ class zfile * Get count. * * @param string $dir + * @param array $excludeFiles * @access public * @return int */ - public function getCount($dir) + public function getCount($dir, $excludeFiles = array()) { if(!file_exists($dir)) return 0; if(is_file($dir)) return 1; @@ -113,7 +125,7 @@ class zfile $entries = scandir($dir); foreach($entries as $entry) { - if($entry == '.' or $entry == '..' or $entry == '.svn' or $entry == '.git') continue; + if($entry == '.' or $entry == '..' or $entry == '.svn' or $entry == '.git' or in_array($entry, $excludeFiles)) continue; $fullEntry = $dir . '/' . $entry; $count += $this->getCount($fullEntry); diff --git a/module/action/config.php b/module/action/config.php index 053d148d83..8dce374066 100755 --- a/module/action/config.php +++ b/module/action/config.php @@ -62,7 +62,7 @@ $config->action->majorList['project'] = array('opened', 'edited'); $config->action->majorList['execution'] = array('opened', 'edited'); $config->action->needGetProjectType = 'build,task,bug,case,testcase,caselib,testtask,testsuite,testreport,doc,issue,release,risk,design,opportunity,trainplan,gapanalysis,researchplan,researchreport,'; -$config->action->needGetRelateField = ',story,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,'; +$config->action->needGetRelateField = ',story,productplan,release,task,build,bug,testcase,case,testtask,testreport,doc,doclib,issue,risk,opportunity,trainplan,gapanalysis,team,whitelist,researchplan,researchreport,meeting,kanbanlane,kanbancolumn,module,review,'; $config->action->noLinkModules = ',doclib,module,webhook,gitlab,gitea,gogs,sonarqube,pipeline,jenkins,kanban,kanbanspace,kanbancolumn,kanbanlane,kanbanregion,kanbancard,execution,project,traincategory,apistruct,program,product,user,entry,repo,'; $config->action->ignoreObjectType4Dynamic = 'kanbanregion,kanbanlane,kanbancolumn'; diff --git a/module/action/lang/de.php b/module/action/lang/de.php index 038a56bfb7..c596635e12 100644 --- a/module/action/lang/de.php +++ b/module/action/lang/de.php @@ -225,8 +225,9 @@ $lang->action->desc->reopen = '$date, reopened by $actoraction->desc->merged = '$date, merged by $actor .' . "\n"; $lang->action->desc->submitreview = '$date, submitted for review by $actor.' . "\n"; $lang->action->desc->ganttmove = '$date, sort by $actor .' . "\n"; +$lang->action->desc->relieved = '$date, relieved by $actor .' . "\n"; $lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by '. $lang->admin->system .'.' . "\n"; -$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the sprints of the project from the $extra.' . "\n"; +$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n"; /* Used to describe the history of operations related to parent-child tasks. */ $lang->action->desc->createchildren = '$date, $actor created a child task $extra。' . "\n"; @@ -395,6 +396,8 @@ $lang->action->label->tolib = 'imported'; $lang->action->label->updatetolib = 'updated'; $lang->action->label->ganttmove = 'sorted'; $lang->action->label->submitreview = 'submitted'; +$lang->action->label->syncsiblings = 'synchronized changes'; +$lang->action->label->relieved = 'relieved'; $lang->action->label->switchtolight = 'switch from ALM mode to light mode'; $lang->action->label->linkedrepo = 'Linked Code Repo'; $lang->action->label->unlinkedrepo = 'Unlinked Code Repo'; diff --git a/module/action/lang/en.php b/module/action/lang/en.php index fd98bc2f47..dae713028d 100755 --- a/module/action/lang/en.php +++ b/module/action/lang/en.php @@ -225,8 +225,9 @@ $lang->action->desc->reopen = '$date, reopened by $actoraction->desc->merged = '$date, merged by $actor .' . "\n"; $lang->action->desc->submitreview = '$date, submitted for review by $actor.' . "\n"; $lang->action->desc->ganttmove = '$date, sort by $actor .' . "\n"; +$lang->action->desc->relieved = '$date, relieved by $actor .' . "\n"; $lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by '. $lang->admin->system .'.' . "\n"; -$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the sprints of the project from the $extra.' . "\n"; +$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n"; /* Used to describe the history of operations related to parent-child tasks. */ $lang->action->desc->createchildren = '$date, $actor created a child task $extra。' . "\n"; @@ -395,6 +396,8 @@ $lang->action->label->tolib = 'imported'; $lang->action->label->updatetolib = 'updated'; $lang->action->label->ganttmove = 'sorted'; $lang->action->label->submitreview = 'submitted'; +$lang->action->label->syncsiblings = 'synchronized changes'; +$lang->action->label->relieved = 'relieved'; $lang->action->label->switchtolight = 'switch from ALM mode to light mode'; $lang->action->label->linkedrepo = 'Linked Code Repo'; $lang->action->label->unlinkedrepo = 'Unlinked Code Repo'; diff --git a/module/action/lang/fr.php b/module/action/lang/fr.php index 802602c0c5..f57a68bc0c 100644 --- a/module/action/lang/fr.php +++ b/module/action/lang/fr.php @@ -225,8 +225,9 @@ $lang->action->desc->reopen = '$date, reopened by $actoraction->desc->merged = '$date, merged by $actor .' . "\n"; $lang->action->desc->submitreview = '$date, submitted for review by $actor.' . "\n"; $lang->action->desc->ganttmove = '$date, sort by $actor .' . "\n"; +$lang->action->desc->relieved = '$date, relieved by $actor .' . "\n"; $lang->action->desc->switchtolight = '$date, Switch from ALM mode to light mode by '. $lang->admin->system .'.' . "\n"; -$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the sprints of the project from the $extra.' . "\n"; +$lang->action->desc->unlinkproduct = '$date, the project is disassociated from the $extra, synchronization disassociates the ' . $lang->executionCommon . 's of the project from the $extra.' . "\n"; /* Used to describe the history of operations related to parent-child tasks. */ $lang->action->desc->createchildren = '$date, $actor a créé un sous-tâche $extra。' . "\n"; @@ -395,6 +396,8 @@ $lang->action->label->tolib = 'Importé'; $lang->action->label->updatetolib = 'MàJ'; $lang->action->label->ganttmove = 'sorted'; $lang->action->label->submitreview = 'submitted'; +$lang->action->label->syncsiblings = 'synchronized changes'; +$lang->action->label->relieved = 'relieved'; $lang->action->label->switchtolight = 'switch from ALM mode to light mode'; $lang->action->label->linkedrepo = 'Linked Code Repo'; $lang->action->label->unlinkedrepo = 'Unlinked Code Repo'; diff --git a/module/action/lang/vi.php b/module/action/lang/vi.php index f9e5281266..cc0766191f 100644 --- a/module/action/lang/vi.php +++ b/module/action/lang/vi.php @@ -306,6 +306,7 @@ $lang->action->label->syncproject = 'start'; $lang->action->label->syncexecution = 'start'; $lang->action->label->startProgram = '(The start of the project sets the status of the program as Ongoing)'; $lang->action->label->submitreview = 'submitted'; +$lang->action->label->syncsiblings = 'synchronized changes'; $lang->action->label->linkedrepo = 'Linked Code Repo'; $lang->action->label->unlinkedrepo = 'Unlinked Code Repo'; diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index ace8f4bd1e..11dc8c0688 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -225,8 +225,9 @@ $lang->action->desc->reopen = '$date, 由 $actor $lang->action->desc->merged = '$date, 由 $actor 合并。' . "\n"; $lang->action->desc->submitreview = '$date, 由 $actor 提交评审。' . "\n"; $lang->action->desc->ganttmove = '$date, 由 $actor 排序。' . "\n"; +$lang->action->desc->relieved = '$date, 由 $actor 解除孪生需求关系。' . "\n"; $lang->action->desc->switchtolight = '$date, 由于 '. $lang->admin->system .' 从全生命周期管理模式切换为轻量管理模式,项目访问控制由项目集内公开调整为私有。' . "\n"; -$lang->action->desc->unlinkproduct = '$date, 系统判断由于迭代所属项目与$extra取消关联,同步将迭代与$extra取消关联。' . "\n"; +$lang->action->desc->unlinkproduct = '$date, 系统判断由于' . $lang->executionCommon . '所属项目与$extra取消关联,同步将' . $lang->executionCommon . '与$extra取消关联。' . "\n"; /* 用来描述和父子任务相关的操作历史记录。*/ $lang->action->desc->createchildren = '$date, 由 $actor 创建子任务 $extra。' . "\n"; @@ -395,6 +396,8 @@ $lang->action->label->tolib = '导入了'; $lang->action->label->updatetolib = '更新了'; $lang->action->label->ganttmove = '排序了'; $lang->action->label->submitreview = '提交了评审'; +$lang->action->label->syncsiblings = '同步修改了'; +$lang->action->label->relieved = '解除了'; $lang->action->label->switchtolight = '从全生命周期管理模式切换为轻量管理模式'; $lang->action->label->linkedrepo = '关联代码库到'; $lang->action->label->unlinkedrepo = '取消了项目与代码库的关联'; diff --git a/module/action/model.php b/module/action/model.php index 95dbf0701f..7ee8ea4231 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -50,7 +50,7 @@ class actionModel extends model $action->extra = $extra; if(!defined('IN_UPGRADE')) $action->vision = $this->config->vision; - if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,', ",$actionType,") !== false) $action->actor = $this->lang->action->system; + if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,syncsiblings,', ",$actionType,") !== false) $action->actor = $this->lang->action->system; /* Use purifier to process comment. Fix bug #2683. */ $action->comment = fixer::stripDataTags($comment); @@ -287,6 +287,15 @@ class actionModel extends model if(strpos(',deleted,', ",$actionType,") !== false) $module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($objectID)->fetch(); if(!empty($module) and $module->type == 'story') $record['product'] = $module->root; break; + case 'review': + $result = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch(); + if($result) + { + $products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($result->project)->fetchPairs('product'); + $record['product'] = join(',', array_keys($products)); + $record['project'] = zget($result, 'project', 0); + } + break; default: $result = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch(); $record['product'] = zget($result, 'product', '0'); @@ -952,6 +961,17 @@ class actionModel extends model } } + if($action->objectType == 'story' and $action->action == 'syncsiblings') + { + if(!empty($extra) and strpos($extra, '|') !== false) + { + list($operate, $storyID) = explode('|', $extra); + $desc['operate'] = $this->lang->$objectType->{$desc['operate']}; + $link = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$storyID"), "#$storyID ") : "#$storyID"; + $actionDesc = str_replace(array('$extra', '$operate'), array($link, $desc['operate'][$operate]), $desc['main']); + } + } + if($action->objectType == 'module' and strpos(',created,moved,', $action->action) !== false) { $moduleNames = $this->loadModel('tree')->getOptionMenu($action->objectID, 'story', 0, 'all', ''); diff --git a/module/admin/lang/de.php b/module/admin/lang/de.php index fd3014cc10..47418888f7 100644 --- a/module/admin/lang/de.php +++ b/module/admin/lang/de.php @@ -141,3 +141,4 @@ $lang->admin->safe->resetPWDList[0] = 'Off'; $lang->admin->safe->noticeMode = 'The password will be checked when creating and modifying user information, and changing passwords.'; $lang->admin->safe->noticeWeakMode = 'The password will be checked when logging into the system, creating and modifying user information, and changing passwords.'; $lang->admin->safe->noticeStrong = 'The longer the password, the more letters, numbers, or special characters it contains, and the less repetitive the password, the more secure it is!'; +$lang->admin->safe->noticeGd = 'Your server does not have GD module installed, you cannot use the Captcha function, Please use it after installation.'; diff --git a/module/admin/lang/en.php b/module/admin/lang/en.php index b7fcb08a16..914214cb1a 100644 --- a/module/admin/lang/en.php +++ b/module/admin/lang/en.php @@ -141,3 +141,4 @@ $lang->admin->safe->resetPWDList[0] = 'Off'; $lang->admin->safe->noticeMode = 'The password will be checked when creating and modifying user information, and changing passwords.'; $lang->admin->safe->noticeWeakMode = 'The password will be checked when logging into the system, creating and modifying user information, and changing passwords.'; $lang->admin->safe->noticeStrong = 'The longer the password, the more letters, numbers, or special characters it contains, and the less repetitive the password, the more secure it is!'; +$lang->admin->safe->noticeGd = 'Your server does not have GD module installed, you cannot use the Captcha function, Please use it after installation.'; \ No newline at end of file diff --git a/module/admin/lang/fr.php b/module/admin/lang/fr.php index efe3d511e6..745c72f69b 100644 --- a/module/admin/lang/fr.php +++ b/module/admin/lang/fr.php @@ -141,3 +141,4 @@ $lang->admin->safe->resetPWDList[0] = 'Off'; $lang->admin->safe->noticeMode = "Le mot de passe sera vérifié lors de la création et de la modification des coordonnées de l'utilisateur, et du changement de mot de passe."; $lang->admin->safe->noticeWeakMode = "Le mot de passe sera vérifié lors de la connexion au système, de la création et de la modification des coordonnées de l'utilisateur, et du changement de mot de passe."; $lang->admin->safe->noticeStrong = "Le mot de passe est d'autant plus sécurisé qu'il est long, qu'il contient plus de lettres, de chiffres ou de caractères spéciaux, et que les lettres du mot de passe sont peu répétitives !"; +$lang->admin->safe->noticeGd = 'Your server does not have GD module installed, you cannot use the Captcha function, Please use it after installation.'; diff --git a/module/admin/lang/vi.php b/module/admin/lang/vi.php index 47deb84d6b..4b6e64b021 100644 --- a/module/admin/lang/vi.php +++ b/module/admin/lang/vi.php @@ -84,3 +84,4 @@ $lang->admin->safe->loginCaptchaList[0] = 'No'; $lang->admin->safe->noticeMode = 'Mật khẩu sẽ được kiểm tra khi người dùng đăng nhập hoặc người dùng thêm hoặc sửa.'; $lang->admin->safe->noticeStrong = ''; +$lang->admin->safe->noticeGd = 'Your server does not have GD module installed, you cannot use the Captcha function, Please use it after installation.'; \ No newline at end of file diff --git a/module/admin/lang/zh-cn.php b/module/admin/lang/zh-cn.php index c573762af5..33831b046d 100644 --- a/module/admin/lang/zh-cn.php +++ b/module/admin/lang/zh-cn.php @@ -141,3 +141,4 @@ $lang->admin->safe->resetPWDList[0] = '关闭'; $lang->admin->safe->noticeMode = '系统会在创建和修改用户、修改密码的时候检查用户口令。'; $lang->admin->safe->noticeWeakMode = '系统会在登录、创建和修改用户、修改密码的时候检查用户口令。'; $lang->admin->safe->noticeStrong = '密码长度越长,含有大写字母或数字或特殊符号越多,密码字母越不重复,安全度越强!'; +$lang->admin->safe->noticeGd = '系统检测到您的服务器未安装GD模块,无法使用验证码功能,请安装后使用。'; diff --git a/module/admin/lang/zh-tw.php b/module/admin/lang/zh-tw.php index 457818eefd..6f6c404364 100644 --- a/module/admin/lang/zh-tw.php +++ b/module/admin/lang/zh-tw.php @@ -82,5 +82,7 @@ $lang->admin->safe->modifyPasswordList[0] = '不強制'; $lang->admin->safe->loginCaptchaList[1] = '是'; $lang->admin->safe->loginCaptchaList[0] = '否'; -$lang->admin->safe->noticeMode = '系統會在登錄、創建和修改用戶、修改密碼的時候檢查用戶口令。'; -$lang->admin->safe->noticeStrong = '密碼長度越長,含有大寫字母或數字或特殊符號越多,密碼字母越不重複,安全度越強!'; +$lang->admin->safe->noticeMode = '系統會在創建和修改用戶、修改密碼的時候檢查用戶口令。'; +$lang->admin->safe->noticeWeakMode = '系統會在登錄、創建和修改用戶、修改密碼的時候檢查用戶口令。'; +$lang->admin->safe->noticeStrong = '密碼長度越長,含有大寫字母或數字或特殊符號越多,密碼字母越不重複,安全度越強!'; +$lang->admin->safe->noticeGd = '系統檢測到您的伺服器未安裝GD模組,無法使用驗證碼功能,請安裝後使用。'; diff --git a/module/admin/view/safe.html.php b/module/admin/view/safe.html.php index 1524656a60..716b9cbd35 100644 --- a/module/admin/view/safe.html.php +++ b/module/admin/view/safe.html.php @@ -16,6 +16,7 @@ .notice {color:#2667E3; margin-left: 12px;} admin);?> + diff --git a/module/backup/model.php b/module/backup/model.php index f6d3432609..e2346b008f 100644 --- a/module/backup/model.php +++ b/module/backup/model.php @@ -34,6 +34,7 @@ class backupModel extends model public function backFile($backupFile) { $zfile = $this->app->loadClass('zfile'); + $return = new stdclass(); $return->result = true; $return->error = ''; @@ -42,10 +43,10 @@ class backupModel extends model $tmpLogFile = $this->getTmpLogFile($backupFile); $dataDir = $this->app->getAppRoot() . 'www/data/'; - $count = $zfile->getCount($dataDir); + $count = $zfile->getCount($dataDir, array('course')); file_put_contents($tmpLogFile, json_encode(array('allCount' => $count))); - $result = $zfile->copyDir($dataDir, $backupFile, $logLevel = false, $tmpLogFile); + $result = $zfile->copyDir($dataDir, $backupFile, $logLevel = false, $tmpLogFile, array('course')); $this->processSummary($backupFile, $result['count'], $result['size'], $result['errorFiles'], $count); unlink($tmpLogFile); diff --git a/module/block/view/printassigntomeblock.html.php b/module/block/view/printassigntomeblock.html.php index 9293eb4686..fa81e17117 100644 --- a/module/block/view/printassigntomeblock.html.php +++ b/module/block/view/printassigntomeblock.html.php @@ -19,8 +19,9 @@ + $bool):?> - + > my->audit : $lang->block->availableBlocks->$type;?> @@ -36,7 +37,7 @@ > - block->availableBlocks->$type;?> + my->audit : $lang->block->availableBlocks->$type;?> '> diff --git a/module/block/view/waterfallganttblock.html.php b/module/block/view/waterfallganttblock.html.php index 68b5c3132e..a63f49f165 100644 --- a/module/block/view/waterfallganttblock.html.php +++ b/module/block/view/waterfallganttblock.html.php @@ -86,12 +86,12 @@ minTimeGap = Math.min(minTimeGap, endDatetime - startDatetime); item.tasks = []; item.completeTasks = []; - item.progress = 0; + item.progress = Number.parseFloat(item.taskProgress.replace('%', ''), 10); plans.push(item); } else if(item.type === 'task') { - item.progress = Number.parseInt(item.taskProgress.replace('%', ''), 10); + item.progress = Number.parseFloat(item.taskProgress.replace('%', ''), 10); tasks.push(item); } }); @@ -105,7 +105,6 @@ plan = plansMap[plan.parent]; if(typeof(plan) != 'object') return; } - plan.progress += task.progress; if(task.progress === 100) plan.completeTasks.push(task); plan.tasks.push(task); }); @@ -122,7 +121,6 @@ /* Update gantt plans and bars */ $.each(plans, function(index, plan) { - plan.progress = !plan.tasks.length ? 0 : plan.progress / plan.tasks.length; var $plan = $('
'); $plan.append('
' + plan.text + '
'); $plans.append($plan); diff --git a/module/branch/control.php b/module/branch/control.php index 832bd4b0d3..93009d3205 100644 --- a/module/branch/control.php +++ b/module/branch/control.php @@ -257,18 +257,21 @@ class branch extends control * * @param int $productID * @param int $oldBranch - * @param string $param + * @param string $browseType * @param int $projectID * @param bool $withMainBranch + * @param string $isSiblings + * @param string $fieldID + * @param string $multiple * @access public * @return void */ - public function ajaxGetBranches($productID, $oldBranch = 0, $param = 'all', $projectID = 0, $withMainBranch = true) + public function ajaxGetBranches($productID, $oldBranch = 0, $browseType = 'all', $projectID = 0, $withMainBranch = true, $isSiblings = 'no', $fieldID = '0', $multiple = '') { $product = $this->loadModel('product')->getById($productID); if(empty($product) or $product->type == 'normal') return; - $branches = $this->loadModel('branch')->getList($productID, $projectID, $param, 'order', null, $withMainBranch); + $branches = $this->loadModel('branch')->getList($productID, $projectID, $browseType, 'order', null, $withMainBranch); $branchTagOption = array(); foreach($branches as $branchInfo) { @@ -280,7 +283,10 @@ class branch extends control $branchTagOption[$oldBranch] = $oldBranch == BRANCH_MAIN ? $branch : ($branch->name . ($branch->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '')); } - return print(html::select('branch', $branchTagOption, $oldBranch, "class='form-control' onchange='loadBranch(this)' data-last='{$oldBranch}'")); + $name = $multiple == 'multiple' ? 'branch[]' : 'branch'; + + if($isSiblings == 'yes') return print(html::select("branches[$fieldID]", $branchTagOption, $oldBranch, "onchange='loadBranchRelation(this.value, $fieldID);' class='form-control chosen control-branch'")); + return print(html::select($name, $branchTagOption, $oldBranch, "class='form-control' $multiple onchange='loadBranch(this)' data-last='{$oldBranch}'")); } /** diff --git a/module/bug/config.php b/module/bug/config.php index 7accc01d9d..1d4e336021 100644 --- a/module/bug/config.php +++ b/module/bug/config.php @@ -328,11 +328,13 @@ $config->bug->datatable->fieldList['os']['title'] = 'os'; $config->bug->datatable->fieldList['os']['fixed'] = 'no'; $config->bug->datatable->fieldList['os']['width'] = '80'; $config->bug->datatable->fieldList['os']['required'] = 'no'; +$config->bug->datatable->fieldList['os']['control'] = 'multiple'; $config->bug->datatable->fieldList['browser']['title'] = 'browser'; $config->bug->datatable->fieldList['browser']['fixed'] = 'no'; $config->bug->datatable->fieldList['browser']['width'] = '80'; $config->bug->datatable->fieldList['browser']['required'] = 'no'; +$config->bug->datatable->fieldList['browser']['control'] = 'multiple'; $config->bug->datatable->fieldList['mailto']['title'] = 'mailto'; $config->bug->datatable->fieldList['mailto']['fixed'] = 'no'; diff --git a/module/bug/control.php b/module/bug/control.php index 9fb4edfa17..96ea49c4bf 100755 --- a/module/bug/control.php +++ b/module/bug/control.php @@ -227,7 +227,8 @@ class bug extends control $this->view->product = $product; $this->view->projectProducts = $this->product->getProducts($this->projectID); $this->view->productName = $productName; - $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'withreleased'); + $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $branch); + $this->view->releasedBuilds = $this->loadModel('release')->getReleasedBuilds($productID, $branch); $this->view->modules = $this->tree->getOptionMenu($productID, $viewType = 'bug', $startModuleID = 0, $branch); $this->view->moduleTree = $moduleTree; $this->view->moduleName = $moduleID ? $this->tree->getById($moduleID)->name : $this->lang->tree->all; @@ -487,7 +488,7 @@ class bug extends control $steps = $this->lang->bug->tplStep . $this->lang->bug->tplResult . $this->lang->bug->tplExpect; $os = ''; $browser = ''; - $assignedTo = ''; + $assignedTo = isset($currentProduct->QD) ? $currentProduct->QD : ''; $deadline = ''; $mailto = ''; $keywords = ''; @@ -558,13 +559,13 @@ class bug extends control /* If executionID is setted, get builds and stories of this execution. */ if($executionID) { - $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noterminate,nodone', $executionID, 'execution'); + $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noterminate,nodone,noreleased', $executionID, 'execution'); $stories = $this->story->getExecutionStoryPairs($executionID); if(!$projectID) $projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project'); } else { - $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noterminate,nodone,withbranch'); + $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noterminate,nodone,withbranch,noreleased'); $stories = $this->story->getProductStoryPairs($productID, $branch, 0, 'all','id_desc', 0, 'full', 'story', false); } @@ -655,6 +656,7 @@ class bug extends control $this->view->projectExecutionPairs = $this->loadModel('project')->getProjectExecutionPairs(); $this->view->executions = defined('TUTORIAL') ? $this->loadModel('tutorial')->getExecutionPairs() : $executions; $this->view->builds = $builds; + $this->view->releasedBuilds = $this->loadModel('release')->getReleasedBuilds($productID, $branch); $this->view->moduleID = (int)$moduleID; $this->view->projectID = $projectID; $this->view->projectModel = $projectModel; @@ -765,7 +767,7 @@ class bug extends control /* If executionID is setted, get builds and stories of this execution. */ if($executionID) { - $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty', $executionID, 'execution'); + $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noreleased', $executionID, 'execution'); $stories = $this->story->getExecutionStoryPairs($executionID); $execution = $this->loadModel('execution')->getById($executionID); if($execution->type == 'kanban') @@ -785,7 +787,7 @@ class bug extends control } else { - $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty'); + $builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noreleased'); $stories = $this->story->getProductStoryPairs($productID, $branch); } @@ -933,7 +935,7 @@ class bug extends control $this->view->branchName = $product->type == 'normal' ? '' : zget($branches, $bug->branch, ''); $this->view->users = $this->user->getPairs('noletter'); $this->view->actions = $this->action->getList('bug', $bugID); - $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'withreleased'); + $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, 'all'); $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('bug', $bugID); $this->view->product = $product; @@ -1079,18 +1081,18 @@ class bug extends control $this->view->position[] = $this->lang->bug->edit; /* Assign. */ - $allBuilds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty,withreleased'); + $allBuilds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noempty'); if($executionID) { - $openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch', $executionID, 'execution'); + $openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased', $executionID, 'execution'); } elseif($projectID) { - $openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch', $projectID, 'project'); + $openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased', $projectID, 'project'); } else { - $openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch'); + $openedBuilds = $this->build->getBuildPairs($productID, $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased'); } /* Set the openedBuilds list. */ @@ -1163,7 +1165,6 @@ class bug extends control if($product->shadow) $this->view->project = $this->loadModel('project')->getByShadowProduct($bug->product); $this->view->bug = $bug; - $this->view->productID = $productID; $this->view->product = $product; $this->view->execution = $execution; $this->view->productBugs = $productBugs; @@ -1828,15 +1829,15 @@ class bug extends control $this->bug->checkBugExecutionPriv($bug); $this->qa->setMenu($this->products, $productID, $bug->branch); - $this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->bug->resolve; - $this->view->bug = $bug; - $this->view->users = $users; - $this->view->assignedTo = $assignedTo; - $this->view->productBugs = $productBugs; - $this->view->executions = $this->loadModel('product')->getExecutionPairsByProduct($productID, $bug->branch ? "0,{$bug->branch}" : 0, 'id_desc', $projectID, 'stagefilter'); - $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $bug->branch, 'withbranch'); - $this->view->actions = $this->action->getList('bug', $bugID); - $this->view->execution = isset($execution) ? $execution : ''; + $this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->bug->resolve; + $this->view->bug = $bug; + $this->view->users = $users; + $this->view->assignedTo = $assignedTo; + $this->view->productBugs = $productBugs; + $this->view->executions = $this->loadModel('product')->getExecutionPairsByProduct($productID, $bug->branch ? "0,{$bug->branch}" : 0, 'id_desc', $projectID, 'stagefilter'); + $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $bug->branch, 'withbranch,noreleased'); + $this->view->actions = $this->action->getList('bug', $bugID); + $this->view->execution = isset($execution) ? $execution : ''; $this->display(); } @@ -1933,7 +1934,7 @@ class bug extends control $this->view->bug = $bug; $this->view->users = $this->user->getPairs('noclosed', $bug->resolvedBy); - $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $bug->branch, 'noempty', 0, 'execution', $bug->openedBuild); + $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $bug->branch, 'noempty,noreleased', 0, 'execution', $bug->openedBuild); $this->view->actions = $this->action->getList('bug', $bugID); $this->display(); @@ -2150,7 +2151,7 @@ class bug extends control $this->view->bugs = $bugs; $this->view->users = $this->user->getPairs(); - $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty'); + $this->view->builds = $this->loadModel('build')->getBuildPairs($productID, $branch, 'noempty,noreleased'); $this->display(); } @@ -2412,7 +2413,7 @@ class bug extends control public function ajaxGetBugFieldOptions($productID, $executionID = 0) { $modules = $this->loadModel('tree')->getOptionMenu($productID, 'bug'); - $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', '', $executionID, 'execution'); + $builds = $this->loadModel('build')->getBuildPairs($productID, 'all', 'noreleased', $executionID, 'execution'); $type = $this->lang->bug->typeList; $pri = $this->lang->bug->priList; $severity = $this->lang->bug->severityList; @@ -2500,4 +2501,19 @@ class bug extends control if($project->model == 'kanban') return print($this->lang->bug->kanban); return print($this->lang->bug->execution); } + + /** + * Ajax get released builds. + * + * @param int $productID + * @param int|string $branch + * @access public + * @return string + */ + public function ajaxGetReleasedBuilds($productID, $branch = 'all') + { + $releasedBuilds = $this->loadModel('release')->getReleasedBuilds($productID, $branch); + + return print(helper::jsonEncode($releasedBuilds)); + } } diff --git a/module/bug/css/create.css b/module/bug/css/create.css index 746ea7a8b8..f7b68c49fb 100644 --- a/module/bug/css/create.css +++ b/module/bug/css/create.css @@ -57,4 +57,4 @@ html[lang='de'] #keywordsAddonLabel, .task {width: 73px;} #openedBuildLabel {display: inline; position: relative; width: 50%; left: 14%;} #bugTypeInputGroup > .table-col {width: 40% !important;} -.pri-text > .label-pri {padding: 1px 5px;} +.pri-text > .label-pri {padding: 2px 6px;} diff --git a/module/bug/js/batchcreate.js b/module/bug/js/batchcreate.js index a69fa47d0d..76eb225198 100644 --- a/module/bug/js/batchcreate.js +++ b/module/bug/js/batchcreate.js @@ -38,13 +38,13 @@ function setOpenedBuilds(link, index) var selected = $('#buildBox' + index).find('select').val(); $('#buildBox' + index).html(builds); $('#buildBox' + index).find('select').val(selected); - $('#openedBuilds' + index + '_chosen').remove(); + $('#pickerDropMenu-pk_openedBuild' + index).remove(); $('#openedBuilds' + index).next('.picker').remove(); $('#buildBox' + index + ' select').removeClass('select-3'); $('#buildBox' + index + ' select').addClass('select-1'); $('#buildBox' + index + ' select').attr('name','openedBuilds[' + index + '][]'); $('#buildBox' + index + ' select').attr('id','openedBuilds' + index); - $('#buildBox' + index + ' select').chosen(); + $('#buildBox' + index + ' select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); index++; if($('#executions' + index).val() != 'ditto') break; diff --git a/module/bug/js/common.js b/module/bug/js/common.js index e7baa1bba6..3cf0a2ac8b 100644 --- a/module/bug/js/common.js +++ b/module/bug/js/common.js @@ -19,6 +19,27 @@ $(function() } }); + +if($('#openedBuild').length || $('#resolvedBuild').length || $('[name^=openedBuilds]').length) +{ + $.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json'); + + $('#openedBuild, #resolvedBuild, [name^=openedBuilds]').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); +} + +function markReleasedBuilds($option) +{ + var build = $option.attr('data-value'); + if($.inArray(build, releasedBuilds) != -1) + { + if(!$option.find('.label-released').length) + { + var optionText = $option.find('.picker-option-text').html(); + $option.find('.picker-option-text').replaceWith("

" + optionText + " " + released + "

"); + } + } +} + /** * Load all fields. * @@ -178,6 +199,8 @@ function loadAllExecutionBuilds(executionID, productID, buildBox) { branch = $('#branch').val(); if(typeof(branch) == 'undefined') branch = 0; + $.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json'); + if(page == 'create') { oldOpenedBuild = $('#openedBuild').val() ? $('#openedBuild').val() : 0; @@ -186,10 +209,10 @@ function loadAllExecutionBuilds(executionID, productID, buildBox) { if(!data) data = ''; $('#openedBuild').replaceWith(data); - $('#openedBuild_chosen').remove(); + $('#pickerDropMenu-pk_openedBuild').remove(); $('#openedBuild').next('.picker').remove(); - $("#openedBuild").chosen(); notice(); + $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); }) } if(page == 'edit') @@ -197,12 +220,12 @@ function loadAllExecutionBuilds(executionID, productID, buildBox) if(buildBox == 'openedBuildBox') { link = createLink('build', 'ajaxGetExecutionBuilds', 'executionID=' + executionID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&needCreate=true&type=all'); - $('#openedBuildBox').load(link, function(){$(this).find('select').chosen()}); + $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } if(buildBox == 'resolvedBuildBox') { link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch + '&index=0&type=all'); - $('#resolvedBuildBox').load(link, function(){$(this).find('select').chosen()}); + $('#resolvedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } } } @@ -219,6 +242,9 @@ function loadAllProductBuilds(productID, buildBox) { branch = $('#branch').val(); if(typeof(branch) == 'undefined') branch = 0; + + $.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json'); + if(page == 'create') { link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&type=all'); @@ -226,10 +252,10 @@ function loadAllProductBuilds(productID, buildBox) { if(!data) data = ''; $('#openedBuild').replaceWith(data); - $('#openedBuild_chosen').remove(); + $('#pickerDropMenu-pk_openedBuild').remove(); $('#openedBuild').next('.picker').remove(); - $("#openedBuild").chosen(); notice(); + $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); }) } if(page == 'edit') @@ -237,12 +263,12 @@ function loadAllProductBuilds(productID, buildBox) if(buildBox == 'openedBuildBox') { link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&type=all'); - $('#openedBuildBox').load(link, function(){$(this).find('select').chosen()}); + $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } if(buildBox == 'resolvedBuildBox') { link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch + '&index=0&type=all'); - $('#resolvedBuildBox').load(link, function(){$(this).find('select').chosen()}); + $('#resolvedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } } } @@ -294,7 +320,7 @@ function loadProductStories(productID) function loadProductProjects(productID) { required = $('#project_chosen').hasClass('required'); - branch = $('#branch').val(); + branch = $('#branch').val(); if(typeof(branch) == 'undefined') branch = 0; link = createLink('product', 'ajaxGetProjects', 'productID=' + productID + '&branch=' + branch + '&projectID=' + oldProjectID); $('#projectBox').load(link, function() @@ -437,23 +463,25 @@ function loadProductBuilds(productID) if(typeof(oldOpenedBuild) == 'undefined') oldOpenedBuild = 0; link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch); + $.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json'); + if(page == 'create') { $.get(link, function(data) { if(!data) data = ''; $('#openedBuild').replaceWith(data); - $('#openedBuild_chosen').remove(); + $('#pickerDropMenu-pk_openedBuild').remove(); $('#openedBuild').next('.picker').remove(); - $("#openedBuild").chosen(); notice(); + $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); }) } else { - $('#openedBuildBox').load(link, function(){$(this).find('select').chosen()}); + $('#openedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch); - $('#resolvedBuildBox').load(link, function(){$(this).find('select').chosen()}); + $('#resolvedBuildBox').load(link, function(){$(this).find('select').picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } } @@ -466,9 +494,12 @@ function loadProductBuilds(productID) */ function loadExecutionRelated(executionID) { - executionID = parseInt(executionID); + executionID = parseInt(executionID); + currentProjectID = $('#project').val() == 'undefined' ? 0 : $('#project').val(); + if(executionID) { + if(currentProjectID == 0) loadProjectByExecutionID(executionID); loadExecutionTasks(executionID); loadExecutionStories(executionID); loadExecutionBuilds(executionID); @@ -477,7 +508,6 @@ function loadExecutionRelated(executionID) } else { - var currentProjectID = $('#project').val() == 'undefined' ? 0 : $('#project').val(); var currentProductID = $('#product').val(); $('#taskIdBox').innerHTML = ''; // Reset the task. @@ -496,6 +526,43 @@ function loadExecutionRelated(executionID) } } +/** + * Load a project by execution id. + * + * @param executionID $executionID + * @access public + * @return void + */ +function loadProjectByExecutionID(executionID) +{ + link = createLink('project', 'ajaxGetPairsByExecution', 'executionID=' + executionID, 'json'); + required = $('#project_chosen').hasClass('required'); + productID = $('#product').val(); + + $.post(link, function(data) + { + var originProject = $('#project').html(); + + if($('#project').find('option[value="' + data.id + '"]').length > 0) + { + $('#project').find('option[value="' + data.id + '"]').attr('selected', 'selected'); + originProject = $('#project').html(); + $('#project').replaceWith(''); + } + else + { + var newProject = ''; + $('#project').replaceWith(''); + } + + $('#project_chosen').remove(); + $('#project').next('.picker').remove(); + $('#project').chosen(); + + if(required) $('#project_chosen').addClass('required'); + }, 'json') +} + /** * Load execution tasks. * @@ -552,28 +619,30 @@ function loadProjectBuilds(projectID) var productID = $('#product').val(); var oldOpenedBuild = $('#openedBuild').val() ? $('#openedBuild').val() : 0; + $.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json'); + if(page == 'create') { var link = createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=openedBuild&build=&branch=' + branch); $.get(link, function(data) { - if(!data) data = ''; + if(!data) data = ''; $('#openedBuild').replaceWith(data); $('#openedBuild').val(oldOpenedBuild); - $('#openedBuild_chosen').remove(); + $('#pickerDropMenu-pk_openedBuild').remove(); $('#openedBuild').next('.picker').remove(); - $("#openedBuild").chosen(); notice(); + $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); }) } else { var link = createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch); - $('#openedBuildBox').load(link, function(){$(this).find('select').val(oldOpenedBuild).chosen()}); + $('#openedBuildBox').load(link, function(){$(this).find('select').val(oldOpenedBuild).picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); var oldResolvedBuild = $('#resolvedBuild').val() ? $('#resolvedBuild').val() : 0; var link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch); - $('#resolvedBuildBox').load(link, function(){$(this).find('select').val(oldResolvedBuild).chosen()}); + $('#resolvedBuildBox').load(link, function(){$(this).find('select').val(oldResolvedBuild).picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } } @@ -595,28 +664,30 @@ function loadExecutionBuilds(executionID, num) if(typeof(branch) == 'undefined') var branch = 0; if(typeof(productID) == 'undefined') var productID = 0; + $.get(createLink('bug', 'ajaxGetReleasedBuilds', 'productID=' + productID), function(data){releasedBuilds = data;}, 'json'); + if(page == 'create') { link = createLink('build', 'ajaxGetExecutionBuilds', 'executionID=' + executionID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + "&branch=" + branch + "&index=0&needCreate=true"); $.get(link, function(data) { - if(!data) data = ''; + if(!data) data = ''; $('#openedBuild').replaceWith(data); $('#openedBuild').val(oldOpenedBuild); - $('#openedBuild_chosen').remove(); + $('#pickerDropMenu-pk_openedBuild').remove(); $('#openedBuild').next('.picker').remove(); - $("#openedBuild").chosen(); notice(); + $("#openedBuild").picker({optionRender: markReleasedBuilds, dropWidth: 'auto'}); }) } else { link = createLink('build', 'ajaxGetExecutionBuilds', 'executionID=' + executionID + '&productID=' + productID + '&varName=openedBuild&build=' + oldOpenedBuild + '&branch=' + branch + '&index=0&needCreate=false&type=normal&number=' + num); - $('#openedBuildBox' + num).load(link, function(){$(this).find('select').val(oldOpenedBuild).chosen()}); + $('#openedBuildBox' + num).load(link, function(){$(this).find('select').val(oldOpenedBuild).picker({optionRender: markReleaseBuilds, dropWidth: 'auto'})}); oldResolvedBuild = $('#resolvedBuild').val() ? $('#resolvedBuild').val() : 0; link = createLink('build', 'ajaxGetProductBuilds', 'productID=' + productID + '&varName=resolvedBuild&build=' + oldResolvedBuild + '&branch=' + branch); - $('#resolvedBuildBox').load(link, function(){$(this).find('select').val(oldResolvedBuild).chosen()}); + $('#resolvedBuildBox').load(link, function(){$(this).find('select').val(oldResolvedBuild).picker({optionRender: markReleasedBuilds, dropWidth: 'auto'})}); } } @@ -776,7 +847,9 @@ function notice() if(page == 'edit') return; $('#buildBoxActions').empty().hide(); - if($('#openedBuild').find('option').length <= 1) + var itemCount = $('#openedBuild').find('option').length; + if($('#openedBuild').attr('data-items') != undefined) var itemCount = $('#openedBuild').attr('data-items'); + if(itemCount <= 1) { var html = ''; if($('#execution').length == 0 || $('#execution').val() == 0) diff --git a/module/bug/js/create.js b/module/bug/js/create.js index dd9892d975..6590029f6e 100644 --- a/module/bug/js/create.js +++ b/module/bug/js/create.js @@ -124,6 +124,7 @@ $(function() }); }); -$(window).unload(function(){ +$(window).unload(function() +{ if(blockID) window.parent.refreshBlock($('#block' + blockID)); }); diff --git a/module/bug/model.php b/module/bug/model.php index e24d83085f..e15a7fc9b9 100644 --- a/module/bug/model.php +++ b/module/bug/model.php @@ -81,8 +81,6 @@ class bugModel extends model ->remove('files,labels,uid,oldTaskID,contactListMenu,region,lane,ticket') ->get(); - if($bug->execution != 0) $bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($bug->execution)->fetch('project'); - /* Check repeat bug. */ $result = $this->loadModel('common')->removeDuplicate('bug', $bug, "product={$bug->product}"); if($result and $result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']); @@ -219,8 +217,6 @@ class bugModel extends model if(isset($data->lanes[$i])) $bug->laneID = $data->lanes[$i]; - if($bug->execution != 0) $bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($bug->execution)->fetch('project'); - /* Assign the bug to the person in charge of the module. */ if(!empty($moduleOwners[$bug->module])) { @@ -1623,7 +1619,7 @@ class bugModel extends model $this->config->bug->search['params']['module']['values'] = $modules; $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($productID, 0, 'id_desc', $projectID); $this->config->bug->search['params']['severity']['values'] = array(0 => '') + $this->lang->bug->severityList; //Fix bug #939. - $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($productID, 'all', 'withbranch'); + $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs($productID, 'all', 'withbranch|releasetag'); $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values']; if($this->session->currentProductType == 'normal') { @@ -3027,6 +3023,8 @@ class bugModel extends model } $allBranch = "`branch` = 'all'"; + $branch = trim($branch, ','); + if(strpos($branch, ',') !== false) $branch = str_replace(',', "','", $branch); if($branch !== 'all' and strpos($bugQuery, '`branch` =') === false) $bugQuery .= " AND `branch` in('0','$branch')"; if(strpos($bugQuery, $allBranch) !== false) $bugQuery = str_replace($allBranch, '1', $bugQuery); diff --git a/module/bug/view/activate.html.php b/module/bug/view/activate.html.php index e2f5fce4da..4ea8e2bab9 100644 --- a/module/bug/view/activate.html.php +++ b/module/bug/view/activate.html.php @@ -37,7 +37,7 @@ printExtendFields($bug, 'table');?> bug->openedBuild;?> - openedBuild, 'size=4 multiple=multiple class="form-control chosen"');?> + openedBuild, 'size=4 multiple=multiple class="form-control picker-select"');?> comment;?> @@ -56,4 +56,8 @@
+product); +js::set('released', $lang->build->released); +?> diff --git a/module/bug/view/batchcreate.html.php b/module/bug/view/batchcreate.html.php index c93fe27e54..6e62fc7d47 100755 --- a/module/bug/view/batchcreate.html.php +++ b/module/bug/view/batchcreate.html.php @@ -13,6 +13,8 @@ bug->create->requiredFields); +js::set('productID', $productID); +js::set('released', $lang->build->released); ?> bug->create->requiredFields) as $field) projectBox' style='overflow:visible'> executionBox' style='overflow:visible'> - +
@@ -163,7 +165,7 @@ foreach(explode(',', $config->bug->create->requiredFields) as $field) projectBox' style='overflow:visible'> executionBox' style='overflow:visible'> - +
@@ -224,7 +226,7 @@ foreach(explode(',', $config->bug->create->requiredFields) as $field) projectBox' style='overflow:visible'> executionBox' style='overflow:visible'> - +
@@ -267,7 +269,7 @@ foreach(explode(',', $config->bug->create->requiredFields) as $field) projectBox' style='overflow:visible'> executionBox' style='overflow:visible'> - +
diff --git a/module/bug/view/browse.html.php b/module/bug/view/browse.html.php index 79c85912a5..6a519a0673 100644 --- a/module/bug/view/browse.html.php +++ b/module/bug/view/browse.html.php @@ -328,7 +328,7 @@ $currentBrowseType = isset($lang->bug->mySelects[$browseType]) && in_array($brow { $actionLink = $this->createLink('bug', 'batchResolve', "resolution=fixed&resolvedBuild=$key"); echo "
  • "; - echo html::a('javascript:;', $build, '', "onclick=\"setFormAction('$actionLink', 'hiddenwin', '#bugList')\""); + echo html::a('javascript:;', $build . (in_array($key, $releasedBuilds) ? " {$lang->build->released} " : ''), '', "onclick=\"setFormAction('$actionLink', 'hiddenwin', '#bugList')\""); echo "
  • "; } echo ""; diff --git a/module/bug/view/create.html.php b/module/bug/view/create.html.php index 1ff2b23c25..27819e3c62 100644 --- a/module/bug/view/create.html.php +++ b/module/bug/view/create.html.php @@ -30,6 +30,8 @@ js::set('tab', $this->app->tab); js::set('requiredFields', $config->bug->create->requiredFields); js::set('showFields', $showFields); js::set('projectExecutionPairs', $projectExecutionPairs); +js::set('productID', $productID); +js::set('released', $lang->build->released); if($this->app->tab == 'execution') js::set('objectID', zget($execution, 'id', '')); if($this->app->tab == 'project') js::set('objectID', $projectID); ?> @@ -128,7 +130,7 @@ if($this->app->tab == 'project') js::set('objectID', $projectID);
    bug->openedBuild?> - +
    bug->allBuilds, "class='btn' id='all' data-toggle='tooltip' onclick='loadAllBuilds()'")?>
    diff --git a/module/bug/view/edit.html.php b/module/bug/view/edit.html.php index f2411a486b..10ad3fdc16 100644 --- a/module/bug/view/edit.html.php +++ b/module/bug/view/edit.html.php @@ -30,6 +30,8 @@ js::set('bugID' , $bug->id); js::set('bugBranch' , $bug->branch); js::set('isClosedBug' , $bug->status == 'closed'); js::set('projectExecutionPairs' , $projectExecutionPairs); +js::set('productID' , $product->id); +js::set('released' , $lang->build->released); if($this->app->tab == 'execution') js::set('objectID', $bug->execution); if($this->app->tab == 'project') js::set('objectID', $bug->project); ?> @@ -100,7 +102,7 @@ if($this->app->tab == 'project') js::set('objectID', $bug->project); bug->product;?>
    - + id, "onchange='loadAll(this.value)' class='form-control chosen'");?> type != 'normal') echo html::select('branch', $branchTagOption, $bug->branch, "onchange='loadBranch();' class='form-control'");?>
    @@ -114,9 +116,9 @@ if($this->app->tab == 'project') js::set('objectID', $bug->project); if(count($moduleOptionMenu) == 1) { echo ""; - echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=bug¤tModuleID=0&branch=$bug->branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'"); + echo html::a($this->createLink('tree', 'browse', "rootID={$product->id}&view=bug¤tModuleID=0&branch=$bug->branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'"); echo '  '; - echo html::a("javascript:void(0)", $lang->refreshIcon, '', "class='refresh' title='$lang->refresh' onclick='loadProductModules($productID)'"); + echo html::a("javascript:void(0)", $lang->refreshIcon, '', "class='refresh' title='$lang->refresh' onclick='loadProductModules($product->id)'"); echo ''; } ?> @@ -240,7 +242,7 @@ if($this->app->tab == 'project') js::set('objectID', $bug->project); bug->openedBuild;?>
    - openedBuild, 'size=4 multiple=multiple class="chosen form-control"');?> + openedBuild, 'size=4 multiple=multiple class="picker-select form-control"');?> bug->allBuilds, "class='btn' onclick='loadAllBuilds(this)'")?>
    @@ -257,7 +259,7 @@ if($this->app->tab == 'project') js::set('objectID', $bug->project); bug->resolvedBuild;?>
    - resolvedBuild, "class='form-control chosen'");?> + resolvedBuild, "class='form-control picker-select'");?> bug->allBuilds, "class='btn' onclick='loadAllBuilds(this)'")?>
    diff --git a/module/bug/view/resolve.html.php b/module/bug/view/resolve.html.php index aea25327aa..78ed516c22 100644 --- a/module/bug/view/resolve.html.php +++ b/module/bug/view/resolve.html.php @@ -14,8 +14,9 @@ product); +js::set('page', 'resolve'); +js::set('productID', $bug->product); +js::set('released', $lang->build->released); ?>
    @@ -49,7 +50,7 @@ js::set('productID' , $bug->product);
    -
    +
    diff --git a/module/build/control.php b/module/build/control.php index a479bdba7e..f351795698 100644 --- a/module/build/control.php +++ b/module/build/control.php @@ -181,6 +181,7 @@ class build extends control } $executions = $this->product->getExecutionPairsByProduct($build->product, $build->branch, 'id_desc', $this->session->project, 'stagefilter'); + if($build->execution and !isset($executions[$build->execution])) $executions[$build->execution] = $this->loadModel('execution')->getById($build->execution)->name; /* Get stories and bugs. */ $orderBy = 'status_asc, stage_asc, id_desc'; @@ -198,7 +199,10 @@ class build extends control { $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); } - if(!isset($branchTagOption[$build->branch])) $branchTagOption[$build->branch] = $this->branch->getById($build->branch, 0, 'name'); + foreach(explode(',', $build->branch) as $buildBranch) + { + if(!isset($branchTagOption[$buildBranch])) $branchTagOption[$buildBranch] = $this->branch->getById($buildBranch, 0, 'name'); + } foreach($productGroups as $product) $products[$product->id] = $product->name; @@ -305,6 +309,16 @@ class build extends control $this->view->generatedBugPager = $generatedBugPager; $this->executeHooks($buildID); + $branchName = ''; + if($build->productType != 'normal') + { + foreach(explode(',', $build->branch) as $buildBranch) + { + $branchName .= $this->loadModel('branch')->getById($buildBranch); + $branchName .= ','; + } + $branchName = trim($branchName, ','); + } /* Assign. */ $this->view->canBeChanged = common::canBeChanged('build', $build); // Determines whether an object is editable. @@ -320,7 +334,8 @@ class build extends control $this->view->bugs = $bugs; $this->view->type = $type; $this->view->bugPager = $bugPager; - $this->view->branchName = $build->productType == 'normal' ? '' : $this->loadModel('branch')->getById($build->branch); + $this->view->branchName = empty($branchName) ? $this->lang->branch->main : $branchName; + $this->view->childBuilds = empty($build->builds) ? array() : $this->dao->select('id,name,bugs,stories')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchAll(); if($this->app->getViewType() == 'json') { @@ -385,32 +400,38 @@ class build extends control * @param string|int $branch * @param int $index the index of batch create bug. * @param string $type get all builds or some builds belong to normal releases and executions are not done. + * @param string $extra * @access public * @return string */ - public function ajaxGetProductBuilds($productID, $varName, $build = '', $branch = 'all', $index = 0, $type = 'normal') + public function ajaxGetProductBuilds($productID, $varName, $build = '', $branch = 'all', $index = 0, $type = 'normal', $extra = '') { $isJsonView = $this->app->getViewType() == 'json'; if($varName == 'openedBuild' ) { - $params = ($type == 'all') ? 'noempty,withbranch' : 'noempty,noterminate,nodone,withbranch'; + $params = ($type == 'all') ? 'noempty,withbranch,noreleased' : 'noempty,noterminate,nodone,withbranch,noreleased'; $builds = $this->build->getBuildPairs($productID, $branch, $params, 0, 'project', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName . '[]', $builds, $build, 'size=4 class=form-control multiple')); } if($varName == 'openedBuilds' ) { - $builds = $this->build->getBuildPairs($productID, $branch, 'noempty', 0, 'project', $build); + $builds = $this->build->getBuildPairs($productID, $branch, 'noempty,noreleased', 0, 'project', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName . "[$index][]", $builds, $build, 'size=4 class=form-control multiple')); } if($varName == 'resolvedBuild') { - $params = ($type == 'all') ? 'withbranch' : 'noterminate,nodone,withbranch'; + $params = ($type == 'all') ? 'withbranch,noreleased' : 'noterminate,nodone,withbranch,noreleased'; $builds = $this->build->getBuildPairs($productID, $branch, $params, 0, 'project', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName, $builds, $build, "class='form-control'")); } + + $builds = $this->build->getBuildPairs($productID, $branch, $type, 0, 'project', $build, false); + if(strpos($extra, 'multiple') !== false) $varName .= '[]'; + if($isJsonView) return print(json_encode($builds)); + return print(html::select($varName, $builds, $build, "class='form-control chosen' $extra")); } /** @@ -434,7 +455,7 @@ class build extends control { if(empty($projectID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); - $params = ($type == 'all') ? 'noempty,withbranch' : 'noempty,noterminate,nodone,withbranch'; + $params = ($type == 'all') ? 'noempty,withbranch,noreleased' : 'noempty,noterminate,nodone,withbranch,noreleased'; $builds = $this->build->getBuildPairs($productID, $branch, $params, $projectID, 'project', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName . '[]', $builds , '', 'size=4 class=form-control multiple')); @@ -443,13 +464,13 @@ class build extends control { if(empty($projectID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); - $params = ($type == 'all') ? 'withbranch' : 'noterminate,nodone,withbranch'; + $params = ($type == 'all') ? 'withbranch,noreleased' : 'noterminate,nodone,withbranch,noreleased'; $builds = $this->build->getBuildPairs($productID, $branch, $params, $projectID, 'project', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName, $builds, $build, "class='form-control'")); } - if(empty($projectID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); + if(empty($projectID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type, $extra); $builds = $this->build->getBuildPairs($productID, $branch, $type, $projectID, 'project', $build, false); if(strpos($extra, 'multiple') !== false) $varName .= '[]'; if($isJsonView) return print(json_encode($builds)); @@ -478,7 +499,7 @@ class build extends control { if(empty($executionID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); - $params = ($type == 'all') ? 'noempty' : 'noempty,noterminate,nodone'; + $params = ($type == 'all') ? 'noempty,noreleased' : 'noempty,noterminate,nodone,noreleased'; $builds = $this->build->getBuildPairs($productID, $branch, $params, $executionID, 'execution', $build); if($isJsonView) return print(json_encode($builds)); @@ -489,7 +510,7 @@ class build extends control { if(empty($executionID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); - $builds = $this->build->getBuildPairs($productID, $branch, 'noempty', $executionID, 'execution', $build); + $builds = $this->build->getBuildPairs($productID, $branch, 'noempty,noreleased', $executionID, 'execution', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName . "[$index][]", $builds , $build, 'size=4 class=form-control multiple')); } @@ -497,14 +518,14 @@ class build extends control { if(empty($executionID)) return $this->ajaxGetProductBuilds($productID, $varName, $build, $branch, $index, $type); - $params = ($type == 'all') ? '' : 'noterminate,nodone'; + $params = ($type == 'all') ? ',noreleased' : 'noterminate,nodone,noreleased'; $builds = $this->build->getBuildPairs($productID, $branch, $params, $executionID, 'execution', $build); if($isJsonView) return print(json_encode($builds)); return print(html::select($varName, $builds, $build, "class='form-control'")); } if($varName == 'testTaskBuild') { - $builds = $this->build->getBuildPairs($productID, $branch, 'noempty,notrunk', $executionID, 'execution'); + $builds = $this->build->getBuildPairs($productID, $branch, 'noempty,notrunk', $executionID, 'execution', '', false); if($isJsonView) return print(json_encode($builds)); if(empty($builds)) @@ -543,7 +564,7 @@ class build extends control { $lastBuild = $this->build->getLast($executionID, $projectID); if($lastBuild) - { + { echo "
      " . $this->lang->build->last . ": " . $lastBuild->name . "
    "; } else @@ -714,7 +735,7 @@ class build extends control $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairsForStory($build->product, $build->branch, 'skipParent'); $this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($build->product, 'bug', 0, $build->branch); $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($build->product, $build->branch, 'id_desc', $this->session->project); - $this->config->bug->search['params']['openedBuild']['values'] = $this->build->getBuildPairs($build->product, $branch = 'all', $params = ''); + $this->config->bug->search['params']['openedBuild']['values'] = $this->build->getBuildPairs($build->product, $branch = 'all', $params = 'releasetag'); $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values']; unset($this->config->bug->search['fields']['product']); diff --git a/module/build/css/view.css b/module/build/css/view.css index 7c6550c08f..0789a2c0c3 100644 --- a/module/build/css/view.css +++ b/module/build/css/view.css @@ -1,8 +1,9 @@ .linkBox #queryBox .search-form .form-actions {padding-bottom: 5px;} .linkBox .table-header {padding: 8px 15px; border-bottom: 1px solid #cbd0db;} #unlinkStoryList, #unlinkBugList {border-top: 1px solid #cbd0db;} -.page-title .dropdown-menu {top: 20px; left: 170px;} +.page-title .dropdown-menu {top: 20px; left: 170px; max-height:300px; overflow:auto;} .page-title .text>a {font-weight: 700;} .body-modal #mainContent {min-height: 200px;} td.article-content{overflow:auto !important;} +td.c-build{white-space: nowrap; overflow:hidden;} diff --git a/module/build/js/common.js b/module/build/js/common.js index 4dbf5d9cec..8f0538b32f 100644 --- a/module/build/js/common.js +++ b/module/build/js/common.js @@ -7,6 +7,11 @@ */ function loadBranches(productID) { + if($('input[name=isIntegrated]:checked').val() == 'yes') + { + $('#branchBox').closest('tr').addClass('hidden'); + return false; + } $('#branch').remove(); $('#branch_chosen').remove(); var oldBranch = 0; @@ -16,12 +21,17 @@ function loadBranches(productID) } projectID = currentTab == 'execution' ? executionID : projectID; - $.get(createLink('branch', 'ajaxGetBranches', 'productID=' + productID + '&oldBranch=0¶m=active&projectID=' + projectID), function(data) + $.get(createLink('branch', 'ajaxGetBranches', 'productID=' + productID + '&oldBranch=0¶m=active&projectID=' + projectID + '&withMainBranch=true&isSiblings=no&fieldID=0&multiple=multiple'), function(data) { if(data) { - $('#product').closest('.input-group').append(data); + $('#branchBox').append(data); $('#branch').chosen(); + $('#branchBox').closest('tr').removeClass('hidden'); + } + else + { + $('#branchBox').closest('tr').addClass('hidden'); } }); } diff --git a/module/build/js/create.js b/module/build/js/create.js index b84d711c00..35625a6d40 100644 --- a/module/build/js/create.js +++ b/module/build/js/create.js @@ -9,12 +9,16 @@ $().ready(function() { var projectID = $('#project').val(); var productID = $('#product').val(); - var branch = $('#branch').length > 0 ? $('#branch').val() : ''; - $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=' + branch + '&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data) + $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=all&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data) { if(data) $('#buildBox').html(data); $('#builds').attr('data-placeholder', multipleSelect).chosen(); }); + + $.get(createLink('product', 'ajaxGetProductById', 'produtID=' + productID), function(data) + { + $('#branchBox').closest('tr').find('th').text(data.branchName); + }, 'json'); }); $('input[name=isIntegrated]').change(function() @@ -70,6 +74,12 @@ function loadProducts(executionID) loadBranches($("#product").val()); } }); + + $.get(createLink('product', 'ajaxGetProductById', 'produtID=' + $("#product").val()), function(data) + { + $('#branchBox').closest('tr').find('th').text(data.branchName); + }, 'json'); + loadLastBuild(); } diff --git a/module/build/js/edit.js b/module/build/js/edit.js index 7088791ec2..cded7b0ab5 100644 --- a/module/build/js/edit.js +++ b/module/build/js/edit.js @@ -3,20 +3,25 @@ $().ready(function() var oldExecutionID = $('#execution').val(); $(document).on('change', '#product, #branch', function() { + var productID = $('#product').val(); + if(executionID) { loadExecutions(oldExecutionID); } else { - var productID = $('#product').val(); - var branch = $('#branch').val(); - $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=' + branch + '&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data) + $.get(createLink('build', 'ajaxGetProjectBuilds', 'projectID=' + projectID + '&productID=' + productID + '&varName=builds&build=&branch=all&index=&needCreate=&type=noempty,notrunk,separate,singled&extra=multiple'), function(data) { if(data) $('#buildBox').html(data); $('#builds').attr('data-placeholder', multipleSelect).chosen(); }); } + + $.get(createLink('product', 'ajaxGetProductById', 'produtID=' + productID), function(data) + { + $('#branchBox').closest('tr').find('th').text(data.branchName); + }, 'json'); }); }); diff --git a/module/build/lang/de.php b/module/build/lang/de.php index 58e816e8fe..12095b323b 100644 --- a/module/build/lang/de.php +++ b/module/build/lang/de.php @@ -36,9 +36,11 @@ $lang->build->execution = $lang->executionCommon; $lang->build->integrated = 'Integrated'; $lang->build->singled = 'Singled'; $lang->build->builds = 'Included Builds'; +$lang->build->released = 'Released'; $lang->build->name = 'Name'; $lang->build->date = 'Datum'; $lang->build->builder = 'Builder'; +$lang->build->url = 'URL'; $lang->build->scmPath = 'SCM Pfad'; $lang->build->filePath = 'Dateipfad'; $lang->build->desc = 'Beschreibung'; @@ -60,6 +62,7 @@ $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the vers $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; $lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version"; +$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted"; $lang->build->finishStories = " %s {$lang->SRCommon} sind abgeschlossen."; $lang->build->resolvedBugs = ' %s Bugs sind gelöst.'; diff --git a/module/build/lang/en.php b/module/build/lang/en.php index 1de167d912..6be3a2d6df 100644 --- a/module/build/lang/en.php +++ b/module/build/lang/en.php @@ -36,9 +36,11 @@ $lang->build->execution = $lang->executionCommon; $lang->build->integrated = 'Integrated'; $lang->build->singled = 'Singled'; $lang->build->builds = 'Included Builds'; +$lang->build->released = 'Released'; $lang->build->name = 'Name'; $lang->build->date = 'Date'; $lang->build->builder = 'Builder'; +$lang->build->url = 'URL'; $lang->build->scmPath = 'SCM Path'; $lang->build->filePath = 'File Path'; $lang->build->desc = 'Description'; @@ -60,6 +62,7 @@ $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the vers $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; $lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version"; +$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted"; $lang->build->finishStories = " Finished {$lang->SRCommon} %s"; $lang->build->resolvedBugs = ' Resolved Bug %s'; diff --git a/module/build/lang/fr.php b/module/build/lang/fr.php index 68b572a9fe..ee7d3312db 100644 --- a/module/build/lang/fr.php +++ b/module/build/lang/fr.php @@ -36,9 +36,11 @@ $lang->build->execution = $lang->executionCommon; $lang->build->integrated = 'Integrated'; $lang->build->singled = 'Singled'; $lang->build->builds = 'Included Builds'; +$lang->build->released = 'Released'; $lang->build->name = 'Nom'; $lang->build->date = 'Date'; $lang->build->builder = 'Builder'; +$lang->build->url = 'URL'; $lang->build->scmPath = 'Chemin SCM'; $lang->build->filePath = 'Chemin Fichier'; $lang->build->desc = 'Description'; @@ -60,6 +62,7 @@ $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the vers $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; $lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version"; +$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted"; $lang->build->finishStories = " {$lang->SRCommon} Terminées %s"; $lang->build->resolvedBugs = ' Bugs Résolus %s'; diff --git a/module/build/lang/vi.php b/module/build/lang/vi.php index aaf8d2800b..0c493659f2 100644 --- a/module/build/lang/vi.php +++ b/module/build/lang/vi.php @@ -54,6 +54,7 @@ $lang->build->notice->changeProduct = "The {$lang->SRCommon}, bug, or the vers $lang->build->notice->changeExecution = "The version of the submitted test order cannot be modified {$lang->executionCommon}"; $lang->build->notice->changeBuilds = "The version of the submitted test order cannot be modified builds"; $lang->build->notice->autoRelation = "The completed requirements, resolved bugs, and generated bugs under the relevant version will be automatically associated with the project version"; +$lang->build->notice->createTest = "The execution of this version has been deleted, and the test cannot be submitted"; $lang->build->finishStories = " {$lang->SRCommon} đã kết thúc %s"; $lang->build->resolvedBugs = ' Bug đã giải quyết %s'; diff --git a/module/build/lang/zh-cn.php b/module/build/lang/zh-cn.php index 2f8c8ae839..a9f9d874db 100644 --- a/module/build/lang/zh-cn.php +++ b/module/build/lang/zh-cn.php @@ -28,7 +28,7 @@ $lang->build->confirmUnlinkBug = "您确认移除该Bug吗?"; $lang->build->basicInfo = '基本信息'; $lang->build->id = 'ID'; -$lang->build->product = $lang->productCommon; +$lang->build->product = '所属' . $lang->productCommon; $lang->build->project = '所属项目'; $lang->build->branch = '平台/分支'; $lang->build->branchName = '所属%s'; @@ -36,9 +36,11 @@ $lang->build->execution = '所属' . $lang->executionCommon; $lang->build->integrated = '集成版本'; $lang->build->singled = '单一版本'; $lang->build->builds = '包含版本'; +$lang->build->released = '发布'; $lang->build->name = '名称编号'; $lang->build->date = '打包日期'; $lang->build->builder = '构建者'; +$lang->build->url = '地址'; $lang->build->scmPath = '源代码地址'; $lang->build->filePath = '下载地址'; $lang->build->desc = '描述'; @@ -60,6 +62,7 @@ $lang->build->notice->changeProduct = "已经关联{$lang->SRCommon}、Bug或 $lang->build->notice->changeExecution = "提交测试单的版本,不能修改其所属{$lang->executionCommon}"; $lang->build->notice->changeBuilds = "提交测试单的版本,不能修改关联版本"; $lang->build->notice->autoRelation = "相关版本下完成的需求、解决的Bug、产生的Bug将会自动关联到项目版本中"; +$lang->build->notice->createTest = "该版本所属执行已删除,不能提交测试"; $lang->build->finishStories = " 本次共完成 %s 个{$lang->SRCommon}"; $lang->build->resolvedBugs = ' 本次共解决 %s 个Bug'; diff --git a/module/build/model.php b/module/build/model.php index f9dae4d54a..ad3e227710 100644 --- a/module/build/model.php +++ b/module/build/model.php @@ -63,11 +63,10 @@ class buildModel extends model */ public function getProjectBuilds($projectID = 0, $type = 'all', $param = 0, $orderBy = 't1.date_desc,t1.id_desc', $pager = null) { - return $this->dao->select('t1.*, t2.name as executionName, t2.id as executionID, t3.name as productName, t4.name as branchName') + return $this->dao->select('t1.*, t2.name as executionName, t2.id as executionID, t3.name as productName') ->from(TABLE_BUILD)->alias('t1') ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id') ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') - ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id') ->where('t1.deleted')->eq(0) ->andWhere('t1.project')->ne(0) ->beginIF($projectID)->andWhere('t1.project')->eq((int)$projectID)->fi() @@ -129,11 +128,10 @@ class buildModel extends model */ public function getExecutionBuilds($executionID, $type = '', $param = '', $orderBy = 't1.date_desc,t1.id_desc', $pager = null) { - return $this->dao->select('t1.*, t2.name as executionName, t3.name as productName, t4.name as branchName') + return $this->dao->select('t1.*, t2.name as executionName, t3.name as productName') ->from(TABLE_BUILD)->alias('t1') ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id') ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') - ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id') ->where('t1.deleted')->eq(0) ->beginIF($executionID)->andWhere('t1.execution')->eq((int)$executionID)->fi() ->beginIF($type == 'product' and $param)->andWhere('t1.product')->eq($param)->fi() @@ -181,6 +179,38 @@ class buildModel extends model return $this->getExecutionBuilds($executionID, 'bysearch', $buildQuery); } + /** + * Filter linked stories or bugs builds. + * + * @param array $buildIdList + * @access public + * @return array + */ + public function filterLinked($buildIdList) + { + $linkeds = array(); + $buildList = $this->getByList($buildIdList); + foreach($buildList as $build) + { + if(!$build->execution && !empty($build->builds)) + { + $childBuilds = $this->getByList($build->builds); + foreach($childBuilds as $childBuild) + { + $childBuild->stories = trim($childBuild->stories, ','); + $childBuild->bugs = trim($childBuild->bugs, ','); + + if($childBuild->stories) $build->stories .= ',' . $childBuild->stories; + if($childBuild->bugs) $build->bugs .= ',' . $childBuild->bugs; + } + } + + if(!empty($build->stories) or !empty($build->bugs)) $linkeds[$build->id] = $build->id; + } + + return $linkeds; + } + /** * Get story builds. * @@ -202,7 +232,7 @@ class buildModel extends model * * @param int|array $products * @param string|int $branch - * @param string $params noempty|notrunk|noterminate|withbranch|hasproject|noDeleted|singled|withreleased, can be a set of them + * @param string $params noempty|notrunk|noterminate|withbranch|hasproject|noDeleted|singled|noreleased|releasedtag, can be a set of them * @param string|int $objectID * @param string $objectType * @param int|array $buildIdList @@ -226,14 +256,16 @@ class buildModel extends model ->fetchPairs(); } + $shadows = $this->dao->select('shadow')->from(TABLE_RELEASE)->where('product')->in($products)->fetchPairs('shadow', 'shadow'); $branchs = strpos($params, 'separate') === false ? "0,$branch" : $branch; - $allBuilds = $this->dao->select('t1.id, t1.name, t1.date, t1.deleted, t2.status as objectStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName, t5.type as productType')->from(TABLE_BUILD)->alias('t1') + $allBuilds = $this->dao->select('t1.id, t1.name, t1.execution, t1.date, t1.deleted, t2.status as objectStatus, t3.id as releaseID, t3.status as releaseStatus, t4.name as branchName, t5.type as productType')->from(TABLE_BUILD)->alias('t1') ->beginIF($objectType === 'execution')->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')->fi() ->beginIF($objectType === 'project')->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')->fi() ->leftJoin(TABLE_RELEASE)->alias('t3')->on("FIND_IN_SET(t1.id,t3.build)") ->leftJoin(TABLE_BRANCH)->alias('t4')->on('t1.branch = t4.id') ->leftJoin(TABLE_PRODUCT)->alias('t5')->on('t1.product = t5.id') ->where('1=1') + ->andWhere('t1.id')->notIN($shadows) ->beginIF(strpos($params, 'hasDeleted') === false)->andWhere('t1.deleted')->eq(0)->fi() ->beginIF(strpos($params, 'hasproject') !== false)->andWhere('t1.project')->ne(0)->fi() ->beginIF(strpos($params, 'singled') !== false)->andWhere('t1.execution')->ne(0)->fi() @@ -243,6 +275,8 @@ class buildModel extends model ->beginIF($branch !== 'all')->andWhere('t1.branch')->in("$branchs")->fi() ->orderBy('t1.date desc, t1.id desc')->fetchAll('id'); + $deletedExecutions = $this->dao->select('id, deleted')->from(TABLE_EXECUTION)->where('type')->eq('sprint')->andWhere('deleted')->eq('1')->fetchPairs(); + /* Set builds and filter done executions and terminate releases. */ $builds = array(); $buildIdList = array(); @@ -251,6 +285,7 @@ class buildModel extends model { if(empty($build->releaseID) and (strpos($params, 'nodone') !== false) and ($build->objectStatus === 'done')) continue; if((strpos($params, 'noterminate') !== false) and ($build->releaseStatus === 'terminate')) continue; + if((strpos($params, 'withexecution') !== false) and $build->execution and isset($executions[$build->execution])) continue; if($build->deleted == 1) $build->name .= ' (' . $this->lang->build->deleted . ')'; $branchName = $build->branchName ? $build->branchName : $this->lang->branch->main; @@ -292,12 +327,13 @@ class buildModel extends model $releaseName = $release->name; $branchName = $release->branchName ? $release->branchName : $this->lang->branch->main; if($release->productType != 'normal') $releaseName = (strpos($params, 'withbranch') !== false ? $branchName . '/' : '') . $releaseName; + if(strpos($params, 'releasetag') !== false) $releaseName = $releaseName . " [{$this->lang->build->released}]"; $builds[$release->date][$release->shadow] = $releaseName; foreach(explode(',', trim($release->build, ',')) as $buildID) { if(!isset($allBuilds[$buildID])) continue; $build = $allBuilds[$buildID]; - if(strpos($params, 'withreleased') === false) unset($builds[$build->date][$buildID]); + if(strpos($params, 'noreleased') !== false) unset($builds[$build->date][$buildID]); } } } @@ -346,21 +382,44 @@ class buildModel extends model ->setDefault('product', 0) ->setDefault('branch', 0) ->setDefault('builds', '') - ->cleanInt('product,branch') + ->cleanInt('product') ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) ->stripTags($this->config->build->editor->create['id'], $this->config->allowedTags) ->join('builds', ',') + ->join('branch', ',') ->remove('resolvedBy,allchecker,files,labels,isIntegrated,uid') ->get(); - if($this->post->isIntegrated == 'yes') $build->execution = 0; + if($this->post->isIntegrated == 'yes') + { + $build->execution = 0; + $branchPairs = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchPairs(); + $relationBranch = array(); + foreach($branchPairs as $branches) + { + foreach(explode(',', $branches) as $branch) + { + if(!isset($relationBranch[$branch])) $relationBranch[$branch] = $branch; + } + } + $build->branch = implode(',', $relationBranch); + } + + $product = $this->loadModel('product')->getByID($build->product); + if($product->type != 'normal' and $this->post->isIntegrated == 'no' and !isset($_POST['branch'])) + { + $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]); + dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch); + } + + if(dao::isError()) return false; $build = $this->loadModel('file')->processImgURL($build, $this->config->build->editor->create['id'], $this->post->uid); $this->dao->insert(TABLE_BUILD)->data($build) ->autoCheck() ->batchCheck($this->config->build->create->requiredFields, 'notempty') - ->check('name', 'unique', "product = {$build->product} AND branch = {$build->branch} AND deleted = '0'") + ->check('name', 'unique', "product = {$build->product} AND branch = '{$build->branch}' AND deleted = '0'") ->checkFlow() ->exec(); @@ -390,17 +449,27 @@ class buildModel extends model ->setIF(!isset($_POST['branch']), 'branch', $oldBuild->branch) ->setDefault('product', $oldBuild->product) ->setDefault('builds', '') - ->cleanInt('product,branch,execution') + ->cleanInt('product,execution') ->join('builds', ',') + ->join('branch', ',') ->remove('allchecker,resolvedBy,files,labels,uid') ->get(); + $product = $this->loadModel('product')->getByID($build->product); + if($product->type != 'normal' and $this->post->isIntegrated == 'no' and !isset($_POST['branch'])) + { + $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]); + dao::$errors['branch'] = sprintf($this->lang->error->notempty, $this->lang->product->branch); + } + + if(dao::isError()) return false; + $build = $this->loadModel('file')->processImgURL($build, $this->config->build->editor->edit['id'], $this->post->uid); $this->dao->update(TABLE_BUILD)->data($build) ->autoCheck() ->batchCheck($this->config->build->edit->requiredFields, 'notempty') ->where('id')->eq($buildID) - ->check('name', 'unique', "id != $buildID AND product = {$build->product} AND branch = {$build->branch} AND deleted = '0'") + ->check('name', 'unique', "id != $buildID AND product = {$build->product} AND branch = '{$build->branch}' AND deleted = '0'") ->checkFlow() ->exec(); if(isset($build->branch) and $oldBuild->branch != $build->branch) $this->dao->update(TABLE_RELEASE)->set('branch')->eq($build->branch)->where('build')->eq($buildID)->exec(); @@ -592,8 +661,8 @@ class buildModel extends model { $build->allBugs = $build->bugs; $build->allStories = $build->stories; - $childBuilds = $this->dao->select('bugs, stories')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchAll(); + $childBuilds = $this->dao->select('id,name,bugs,stories')->from(TABLE_BUILD)->where('id')->in($build->builds)->fetchAll(); foreach($childBuilds as $childBuild) { if($childBuild->bugs) $build->allBugs .= ",{$childBuild->bugs}"; @@ -621,7 +690,7 @@ class buildModel extends model { $action = strtolower($action); - if($module == 'testtask' && $action == 'create') return !!$object->execution; + if($module == 'testtask' and $action == 'create') return !$object->executionDeleted; return true; } @@ -651,11 +720,14 @@ class buildModel extends model { $executionID = $tab == 'execution' ? $extraParams['executionID'] : $build->execution; $execution = $this->loadModel('execution')->getByID($executionID); + $build->executionDeleted = $execution ? $execution->deleted : 0; + $testtaskApp = (!empty($execution->type) and $execution->type == 'kanban') ? 'data-app="qa"' : "data-app='{$tab}'"; if(common::hasPriv($module, 'linkstory') and common::canBeChanged('build', $build)) $menu .= $this->buildMenu($module, 'view', "{$params}&type=story&link=true", $build, $type, 'link', '', '', '', "data-app={$tab}", $this->lang->build->linkStory); - $menu .= $this->buildMenu('testtask', 'create', "product=$build->product&execution={$executionID}&build=$build->id&projectID=$build->project", $build, $type, 'bullhorn', '', '', '', $testtaskApp); + $title = ($execution and $execution->deleted === '1') ? $this->lang->build->notice->createTest : ''; + $menu .= $this->buildMenu('testtask', 'create', "product=$build->product&execution={$executionID}&build=$build->id&projectID=$build->project", $build, $type, 'bullhorn', '', '', '', $testtaskApp, $title); if($tab == 'execution' and !empty($execution->type) and $execution->type != 'kanban') $menu .= $this->buildMenu('execution', 'bug', "execution={$extraParams['executionID']}&productID={$extraParams['productID']}&branchID=all&orderBy=status&build=$build->id", $build, $type, '', '', '', '', $this->lang->execution->viewBug); if($tab == 'project' or empty($execution->type) or $execution->type == 'kanban') $menu .= $this->buildMenu($module, 'view', "{$params}&type=generatedBug", $build, $type, 'bug', '', '', '', "data-app='$tab'", $this->lang->project->bug); diff --git a/module/build/view/create.html.php b/module/build/view/create.html.php index 543af32def..40269a87dd 100644 --- a/module/build/view/create.html.php +++ b/module/build/view/create.html.php @@ -33,12 +33,6 @@
    id, "onchange='loadBranches(this.value);' class='form-control chosen' required");?> - type != 'normal') - { - echo "" . html::select('branch', $branches, key($product->branches), "class='form-control chosen'"); - } - ?>
    @@ -50,6 +44,14 @@ + '> + type == 'normal' ? '' : $lang->product->branchName[$product->type]?> + +
    + branches), "class='form-control chosen' multiple required"); ?> +
    + + build->builds;?> build->placeholder->multipleSelect}'");?> diff --git a/module/build/view/edit.html.php b/module/build/view/edit.html.php index 21e386d8d3..5e2ff18c64 100644 --- a/module/build/view/edit.html.php +++ b/module/build/view/edit.html.php @@ -32,16 +32,19 @@ ?>
    product, "onchange='loadBranches(this.value);' class='form-control chosen' $disabled required");?> - productType != 'normal') - { - echo "" . html::select('branch', $branchTagOption, $build->branch, "class='form-control chosen' $disabled"); - } - ?>
    build->notice->changeProduct;?> + + '> + type == 'normal' ? '' : $lang->product->branchName[$product->type]?> + +
    + branch, "class='form-control chosen' multiple required $disabled"); ?> +
    + + execution):?> diff --git a/module/build/view/view.html.php b/module/build/view/view.html.php index aa18d5ecae..dfdcb00dbf 100644 --- a/module/build/view/view.html.php +++ b/module/build/view/view.html.php @@ -26,7 +26,11 @@ tbody tr td:first-child input {display: none;}