diff --git a/Makefile b/Makefile index 4f57265a75..f511627ccc 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,8 @@ zentaoxx: sed -i 's/sys_user/zt_user/' zentaoxx/db/*.sql sed -i 's/sys_file/zt_file/' zentaoxx/db/*.sql sed -i '/sys_entry/d' zentaoxx/db/*.sql + mkdir zentaoxx/tools; cp tools/cn2tw.php zentaoxx/tools; cd zentaoxx/tools; php cn2tw.php + rm -rf zentaopms/tools zip -rqm -9 zentaoxx.$(VERSION).zip zentaoxx/* rm -rf xuan.zip xuan zentaoxx package: @@ -92,7 +94,8 @@ package: if [ ! -d "zentaopms/config/ext" ]; then mkdir zentaopms/config/ext; fi for module in `ls zentaopms/module/`; do if [ ! -d "zentaopms/module/$$module/ext" ]; then mkdir zentaopms/module/$$module/ext; fi done find zentaopms/ -name ext |xargs chmod -R 777 - tools/cn2tw.php + mkdir zentaopms/tools; cp tools/cn2tw.php zentaopms/tools; cd zentaopms/tools; php cn2tw.php + rm -rf zentaopms/tools pms: make common make zentaoxx diff --git a/db/update11.2.sql b/db/update11.2.sql new file mode 100644 index 0000000000..83403f975a --- /dev/null +++ b/db/update11.2.sql @@ -0,0 +1,2 @@ +update `zt_build` set `deleted`='1' where `project`='0' and `id` in (select `build` from `zt_release` where `deleted`='1'); +ALTER TABLE `zt_productplan` ADD `parent` mediumint(9) NOT NULL DEFAULT '0' AFTER `branch`; diff --git a/module/block/control.php b/module/block/control.php index 0f07685b64..a67941a4c2 100644 --- a/module/block/control.php +++ b/module/block/control.php @@ -901,7 +901,7 @@ class block extends control $tasks = $this->dao->select("project, count(id) as totalTasks, count(status in ('wait','doing','pause') or null) as undoneTasks, count(finishedDate like '{$yesterday}%' or null) as yesterdayFinished, sum(if(status != 'cancel', estimate, 0)) as totalEstimate, sum(consumed) as totalConsumed, sum(if(status != 'cancel' and status != 'closed', `left`, 0)) as totalLeft")->from(TABLE_TASK) ->where('project')->in($projectIdList) ->andWhere('deleted')->eq(0) - ->andWhere('parent')->lt(1) + ->andWhere('parent')->ge(0) ->groupBy('project') ->fetchAll('project'); foreach($tasks as $projectID => $task) diff --git a/module/bug/view/create.html.php b/module/bug/view/create.html.php index 8dc5594d72..57c8996cb1 100644 --- a/module/bug/view/create.html.php +++ b/module/bug/view/create.html.php @@ -295,7 +295,7 @@ js::set('confirmDeleteTemplate', $lang->bug->confirmDeleteTemplate); - + diff --git a/module/build/model.php b/module/build/model.php index 709713b8c2..3b46e41f86 100644 --- a/module/build/model.php +++ b/module/build/model.php @@ -274,7 +274,7 @@ class buildModel extends model { foreach($data->bugs as $key => $bugID) { - if(isset($_POST['resolvedBy'][$key]))$resolvedPairs[$bugID] = $data->resolvedBy[$key]; + if(isset($_POST['resolvedBy'][$bugID])) $resolvedPairs[$bugID] = $data->resolvedBy[$bugID]; } } diff --git a/module/common/lang/en.php b/module/common/lang/en.php index 8fb5cc38cd..956560872f 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -80,7 +80,7 @@ $lang->ipLimited = "fold = '-'; $lang->homepage = 'Set Home'; -$lang->tutorial = 'Tutorial'; +$lang->noviceTutorial = 'Novice Tutorial'; $lang->changeLog = 'Change Log'; $lang->manual = 'Manual'; $lang->manualUrl = 'https://www.zentao.pm/book/zentaomanual/zentao-installation-11.html'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index 240b020a93..46606369c2 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -80,7 +80,7 @@ $lang->ipLimited = "fold = '-'; $lang->homepage = '设为模块首页'; -$lang->tutorial = '新手教程'; +$lang->noviceTutorial = '新手教程'; $lang->changeLog = '修改日志'; $lang->manual = '手册'; $lang->manualUrl = 'https://www.zentao.net/book/zentaopmshelp.html?fullScreen=zentao'; diff --git a/module/common/model.php b/module/common/model.php index 14ff08a922..43ea854d5e 100644 --- a/module/common/model.php +++ b/module/common/model.php @@ -167,7 +167,7 @@ class commonModel extends model if($this->loadModel('user')->isLogon() or ($this->app->company->guest and $this->app->user->account == 'guest')) { if(stripos($method, 'ajax') !== false) return true; - if($module == 'misc' and $method == 'downloadClient') return true; + if($module == 'misc' and $method == 'downloadclient') return true; if($module == 'block' and $method == 'main') return true; if($module == 'misc' and $method == 'changelog') return true; if($module == 'tutorial') return true; @@ -315,7 +315,7 @@ class commonModel extends model echo "\n"; diff --git a/module/custom/control.php b/module/custom/control.php index 2d494bf029..ad34dac1e8 100644 --- a/module/custom/control.php +++ b/module/custom/control.php @@ -31,8 +31,9 @@ class custom extends control * @access public * @return void */ - public function set($module = 'story', $field = 'priList', $lang = 'zh_cn') + public function set($module = 'story', $field = 'priList', $lang = '') { + if(empty($lang)) $lang = $this->app->getClientLang(); if($module == 'user' and $field == 'priList') $field = 'roleList'; if($module == 'block' and $field == 'priList')$field = 'closed'; $currentLang = $this->app->getClientLang(); @@ -159,7 +160,7 @@ class custom extends control } } if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); - $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload')); + $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->createLink('custom', 'set', "module=$module&field=$field&lang=" . str_replace('-', '_', $lang)))); } /* Check whether the current language has been customized. */ diff --git a/module/doc/control.php b/module/doc/control.php index a88eb8528d..32405ffcae 100644 --- a/module/doc/control.php +++ b/module/doc/control.php @@ -796,6 +796,7 @@ class doc extends control } elseif($from == 'project') { + $this->lang->modulePageActions = common::hasPriv('doc', 'createLib') ? html::a(helper::createLink('doc', 'createLib', "from=project&projectID=$objectID"), " " . $this->lang->doc->createLib, '', "class='btn btn-secondary iframe'") : ''; $this->lang->doc->menu = $this->lang->project->menu; $this->lang->doc->menuOrder = $this->lang->project->menuOrder; $this->project->setMenu($this->project->getPairs('nocode'), $objectID); diff --git a/module/git/model.php b/module/git/model.php index 496f425851..5f4efab6f7 100644 --- a/module/git/model.php +++ b/module/git/model.php @@ -324,7 +324,7 @@ class gitModel extends model list($hash, $account, $date) = $log; $account = preg_replace('/^Author:/', '', $account); - $account = trim(preg_replace('/<[\w\-\.]+@([\w\-]+\.)+\w+>/', '', $account)); + $account = trim(preg_replace('/<[\w\-\.]+@[\w\-]+>/', '', $account)); $date = trim(preg_replace('/^Date:/', '', $date)); $count = count($log); diff --git a/module/misc/control.php b/module/misc/control.php index 50be5e57d2..ec78640a24 100644 --- a/module/misc/control.php +++ b/module/misc/control.php @@ -59,16 +59,15 @@ class misc extends control /** * Check current version is latest or not. * + * @param string $sn * @access public * @return void */ - public function checkUpdate() + public function checkUpdate($sn) { - $note = isset($_GET['note']) ? $_GET['note'] : ''; - $browser = isset($_GET['browser']) ? $_GET['browser'] : ''; - - $this->view->note = urldecode(helper::safe64Decode($note)); - $this->view->browser = $browser; + session_write_close(); + $link = $this->lang->misc->api . "/updater-isLatest-{$this->config->version}-{$sn}.html?lang=" . str_replace('-', '_', $this->app->getClientLang()); + $this->view->note = file_get_contents($link); $this->display(); } diff --git a/module/misc/view/checkupdate.html.php b/module/misc/view/checkupdate.html.php index 1d2c232160..a382cecefd 100644 --- a/module/misc/view/checkupdate.html.php +++ b/module/misc/view/checkupdate.html.php @@ -1,7 +1,7 @@ - style='color:white; font-size:13px; text-align:center'> +server->http_user_agen, 'opera') === false) echo "bgcolor='transparent'";?> style='color:white; font-size:13px; text-align:center'> diff --git a/module/product/model.php b/module/product/model.php index c971f6294f..528d47b3e3 100644 --- a/module/product/model.php +++ b/module/product/model.php @@ -622,6 +622,18 @@ class productModel extends model $roadmap = array(); $total = 0; + $parents = array(); + foreach($plans as $planID => $plan) + { + if($plan->parent == '-1') + { + $parents[$planID] = $plan->title; + unset($plans[$planID]); + } + + if($plan->parent > 0 and isset($parents[$plan->parent])) $plan->title = $parents[$plan->parent] . ' / ' . $plan->title; + } + foreach($plans as $plan) { if(($plan->end != '0000-00-00' and strtotime($plan->end) - time() <= 0) or $plan->end == '2030-01-01') continue; diff --git a/module/productplan/control.php b/module/productplan/control.php index 78c889cc6c..abad7e8419 100644 --- a/module/productplan/control.php +++ b/module/productplan/control.php @@ -42,7 +42,7 @@ class productplan extends control * @access public * @return void */ - public function create($product = '', $branch = 0) + public function create($product = '', $branch = 0, $parent = 0) { if(!empty($_POST)) { @@ -54,7 +54,7 @@ class productplan extends control } $this->commonAction($product, $branch); - $lastPlan = $this->productplan->getLast($product); + $lastPlan = $this->productplan->getLast($product, $branch, $parent); if($lastPlan) { $timestamp = strtotime($lastPlan->end); @@ -65,11 +65,15 @@ class productplan extends control $begin = date('Y-m-d', strtotime("+$delta days", $timestamp)); } $this->view->begin = $lastPlan ? $begin : ''; + if($parent) $this->view->parentPlan = $this->productplan->getById($parent); - $this->view->title = $this->view->product->name . $this->lang->colon . $this->lang->productplan->create; - $this->view->lastPlan = $lastPlan; + $this->view->title = $this->view->product->name . $this->lang->colon . $this->lang->productplan->create; $this->view->position[] = $this->lang->productplan->common; $this->view->position[] = $this->lang->productplan->create; + + $this->view->lastPlan = $lastPlan; + $this->view->branch = $branch; + $this->view->parent = $parent; $this->display(); } @@ -206,6 +210,7 @@ class productplan extends control $this->view->title = $products[$productID] . $this->lang->colon . $this->lang->productplan->browse; $this->view->position[] = $this->lang->productplan->browse; $this->view->productID = $productID; + $this->view->branch = $branch; $this->view->browseType = $browseType; $this->view->orderBy = $orderBy; $this->view->plans = $this->productplan->getList($productID, $branch, $browseType, $pager, $sort); @@ -265,6 +270,9 @@ class productplan extends control $orderBy = str_replace('id', 'order', $orderBy); } + if($plan->parent > 0) $this->view->parentPlan = $this->productplan->getById($plan->parent); + if($plan->parent == '-1') $this->view->childrenPlans = $this->productplan->getChildren($plan->id); + $this->loadModel('datatable'); $this->view->modulePairs = $this->loadModel('tree')->getOptionMenu($plan->product, 'story'); $this->view->title = "PLAN #$plan->id $plan->title/" . $products[$plan->product]; diff --git a/module/productplan/css/browse.css b/module/productplan/css/browse.css index 966f3b4cd7..c650c25ecb 100644 --- a/module/productplan/css/browse.css +++ b/module/productplan/css/browse.css @@ -1 +1,13 @@ .table td.content { overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} + +.table-children {border-left: 2px solid #cbd0db; border-right: 2px solid #cbd0db;} +.table tbody > tr.table-children.table-child-top {border-top: 2px solid #cbd0db;} +.table tbody > tr.table-children.table-child-bottom {border-bottom: 2px solid #cbd0db;} +.table td.has-child > a {max-width: 90%; max-width: calc(100% - 30px); display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;} +.table td.has-child > .task-toggle {color: #838a9d; position: relative; top: 1px;} +.table td.has-child > .task-toggle:hover {color: #006af1; cursor: pointer;} +.table td.has-child > .task-toggle > .icon {font-size: 16px; display: inline-block; transition: transform .2s; -ms-transform:rotate(-90deg); -moz-transform:rotate(-90deg); -o-transform:rotate(-90deg); -webkit-transform:rotate(-90deg); transform: rotate(-90deg);} +.table td.has-child > .task-toggle > .icon:before {text-align: left;} +.table td.has-child > .task-toggle.collapsed > .icon {-ms-transform:rotate(90deg); -moz-transform:rotate(90deg); -o-transform:rotate(90deg); -webkit-transform:rotate(90deg); transform: rotate(90deg);} +.main-table tbody > tr.table-children > td:first-child::before {width: 3px;} +@-moz-document url-prefix() {.main-table tbody > tr.table-children > td:first-child::before {width: 4px;}} diff --git a/module/productplan/js/browse.js b/module/productplan/js/browse.js new file mode 100644 index 0000000000..3cb238d9ef --- /dev/null +++ b/module/productplan/js/browse.js @@ -0,0 +1,10 @@ +$(document).on('click', '.task-toggle', function(e) +{ + var $toggle = $(this); + var id = $(this).data('id'); + var isCollapsed = $toggle.toggleClass('collapsed').hasClass('collapsed'); + $toggle.closest('[data-ride="table"]').find('tr.parent-' + id).toggle(!isCollapsed); + + e.stopPropagation(); + e.preventDefault(); +}); diff --git a/module/productplan/lang/en.php b/module/productplan/lang/en.php index 024d866410..72c1a2bd4e 100644 --- a/module/productplan/lang/en.php +++ b/module/productplan/lang/en.php @@ -27,6 +27,7 @@ $lang->productplan->batchUnlinkStory = "Batch Unlink"; $lang->productplan->linkedStories = 'Linked Story'; $lang->productplan->unlinkedStories = 'Unlinked Story'; $lang->productplan->updateOrder = 'Sort'; +$lang->productplan->createChildren = "Create subplans"; $lang->productplan->linkBug = "Link Bug"; $lang->productplan->unlinkBug = "Unlink Bug"; @@ -54,6 +55,8 @@ $lang->productplan->stories = 'Stories'; $lang->productplan->bugs = 'Bugs'; $lang->productplan->hour = 'Hour'; $lang->productplan->project = $lang->projectCommon; +$lang->productplan->parent = "Father plan"; +$lang->productplan->children= "Sub plan"; $lang->productplan->endList[7] = '1 Week'; $lang->productplan->endList[14] = '2 Weeks'; diff --git a/module/productplan/lang/zh-cn.php b/module/productplan/lang/zh-cn.php index 37f4c71a20..61b6afdfba 100644 --- a/module/productplan/lang/zh-cn.php +++ b/module/productplan/lang/zh-cn.php @@ -27,6 +27,7 @@ $lang->productplan->batchUnlinkStory = "批量移除需求"; $lang->productplan->linkedStories = '需求'; $lang->productplan->unlinkedStories = '未关联需求'; $lang->productplan->updateOrder = '排序'; +$lang->productplan->createChildren = "创建子计划"; $lang->productplan->linkBug = "关联Bug"; $lang->productplan->unlinkBug = "移除Bug"; @@ -54,6 +55,8 @@ $lang->productplan->stories = '需求数'; $lang->productplan->bugs = 'Bug数'; $lang->productplan->hour = '工时'; $lang->productplan->project = $lang->projectCommon; +$lang->productplan->parent = "父计划"; +$lang->productplan->children= "子计划"; $lang->productplan->endList[7] = '一星期'; $lang->productplan->endList[14] = '两星期'; diff --git a/module/productplan/model.php b/module/productplan/model.php index 93918c6b51..aaf12c489b 100644 --- a/module/productplan/model.php +++ b/module/productplan/model.php @@ -47,14 +47,18 @@ class productplanModel extends model * Get last plan. * * @param int $productID + * @param int $branch + * @param int $parent * @access public - * @return void + * @return object */ - public function getLast($productID, $branch = 0) + public function getLast($productID, $branch = 0, $parent = 0) { return $this->dao->select('*')->from(TABLE_PRODUCTPLAN) ->where('deleted')->eq(0) - ->andWhere('product')->eq($productID) + ->beginIF($parent <= 0)->andWhere('parent')->le((int)$parent)->fi() + ->beginIF($parent > 0)->andWhere('parent')->eq((int)$parent)->fi() + ->andWhere('product')->eq((int)$productID) ->andWhere('end')->ne('2030-01-01') ->beginIF($branch)->andWhere('branch')->eq($branch)->fi() ->orderBy('end desc') @@ -75,8 +79,8 @@ class productplanModel extends model */ public function getList($product = 0, $branch = 0, $browseType = 'all', $pager = null, $orderBy = 'begin_desc') { - $date = date('Y-m-d'); - $productPlans = $this->dao->select('t1.*,t2.project')->from(TABLE_PRODUCTPLAN)->alias('t1') + $date = date('Y-m-d'); + $plans = $this->dao->select('t1.*,t2.project')->from(TABLE_PRODUCTPLAN)->alias('t1') ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t2.plan = t1.id and t2.product = ' . $product) ->where('t1.product')->eq($product) ->andWhere('t1.deleted')->eq(0) @@ -87,21 +91,64 @@ class productplanModel extends model ->page($pager) ->fetchAll('id'); - if(!empty($productPlans)) + if(!empty($plans)) { - foreach($productPlans as $productPlan) + $plans = $this->reorder4Children($plans); + $planIdList = array_keys($plans); + + $product = $this->loadModel('product')->getById($product); + if($product->type == 'normal') { - $stories = $this->dao->select('id,estimate')->from(TABLE_STORY) - ->where("CONCAT(',', plan, ',')")->like("%,{$productPlan->id},%") + $storyGroups = $this->dao->select('id,plan,estimate')->from(TABLE_STORY) + ->where("plan")->in($planIdList) ->andWhere('deleted')->eq(0) - ->fetchPairs('id', 'estimate'); - $productPlan->stories = count($stories); - $productPlan->bugs = $this->dao->select()->from(TABLE_BUG)->where("plan")->eq($productPlan->id)->andWhere('deleted')->eq(0)->count(); - $productPlan->hour = array_sum($stories); - $productPlan->projectID = $productPlan->project; + ->fetchGroup('plan', 'id'); + } + + $bugs = $this->dao->select('*')->from(TABLE_BUG)->where("plan")->in($planIdList)->andWhere('deleted')->eq(0)->fetchGroup('plan', 'id'); + $parentStories = $parentBugs = $parentHour = array(); + foreach($plans as $plan) + { + if($product->type == 'normal') + { + $stories = zget($storyGroups, $plan->id, array()); + $storyPairs = array(); + foreach($stories as $story) $storyPairs[$story->id] = $story->estimate; + } + else + { + $storyPairs = $this->dao->select('id,estimate')->from(TABLE_STORY) + ->where("CONCAT(',', plan, ',')")->like("%,{$plan->id},%") + ->andWhere('deleted')->eq(0) + ->fetchPairs('id', 'estimate'); + } + $plan->stories = count($storyPairs); + $plan->bugs = isset($bugs[$plan->id]) ? count($bugs[$plan->id]) : 0; + $plan->hour = array_sum($storyPairs); + $plan->projectID = $plan->project; + + if(!isset($parentStories[$plan->parent])) $parentStories[$plan->parent] = 0; + if(!isset($parentBugs[$plan->parent])) $parentBugs[$plan->parent] = 0; + if(!isset($parentHour[$plan->parent])) $parentHour[$plan->parent] = 0; + + $parentStories[$plan->parent] += $plan->stories; + $parentBugs[$plan->parent] += $plan->bugs; + $parentHour[$plan->parent] += $plan->hour; + } + + unset($parentStories[0]); + unset($parentBugs[0]); + unset($parentHour[0]); + foreach($parentStories as $parentID => $count) + { + if(!isset($plans[$parentID])) continue; + $plan = $plans[$parentID]; + $plan->stories += $count; + $plan->bugs += $parentBugs[$parentID]; + $plan->hour += $parentHour[$parentID]; } } - return $productPlans; + return $plans; } /** @@ -116,17 +163,17 @@ class productplanModel extends model public function getPairs($product = 0, $branch = 0, $expired = '') { $date = date('Y-m-d'); - $plans = $this->dao->select('id,CONCAT(title, " [", begin, " ~ ", end, "]") as title')->from(TABLE_PRODUCTPLAN) + $plans = $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN) ->where('product')->in($product) ->andWhere('deleted')->eq(0) ->beginIF($branch)->andWhere("branch")->in("0,$branch")->fi() ->beginIF($expired == 'unexpired')->andWhere('end')->ge($date)->fi() ->orderBy('begin desc') - ->fetchPairs(); + ->fetchAll('id'); if($expired == 'unexpired') { - $plans += $this->dao->select('id,CONCAT(title, " [", begin, " ~ ", end, "]") as title')->from(TABLE_PRODUCTPLAN) + $plans += $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN) ->where('product')->in($product) ->andWhere('deleted')->eq(0) ->andWhere('end')->lt($date) @@ -134,14 +181,25 @@ class productplanModel extends model ->beginIF($plans)->andWhere("id")->notIN(array_keys($plans))->fi() ->orderBy('begin desc') ->limit(5) - ->fetchPairs(); + ->fetchAll('id'); } - foreach($plans as $key => $value) + $plans = $this->reorder4Children($plans); + $planPairs = array(); + $parent = 0; + $parentTitle = ''; + foreach($plans as $plan) { - $plans[$key] = str_replace('[2030-01-01 ~ 2030-01-01]', '[' . $this->lang->productplan->future . ']', $value); + if($plan->parent == '-1') + { + $parent = $plan->id; + $parentTitle = $plan->title; + } + if($plan->parent > 0 and $plan->parent == $parent) $plan->title = $parentTitle . ' /' . $plan->title; + $planPairs[$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]"; + if($plan->begin == '2030-01-01' and $plan->end == '2030-01-01') $planPairs[$plan->id] = $plan->title . ' ' . $this->lang->productplan->future; } - return array('' => '') + $this->processFuture($plans); + return array('' => '') + $planPairs; } /** @@ -155,27 +213,43 @@ class productplanModel extends model public function getPairsForStory($product = 0, $branch = 0) { $date = date('Y-m-d'); - $plans = $this->dao->select('id, CONCAT(title, " [", begin, " ~ ", end, "]") AS title')->from(TABLE_PRODUCTPLAN) + $plans = $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN) ->where('product')->in($product) ->andWhere('deleted')->eq(0) ->andWhere('end')->ge($date) ->beginIF($branch)->andWhere("branch")->in("0,$branch")->fi() ->orderBy('begin desc') - ->fetchPairs(); + ->fetchAll('id'); if(!$plans) { - $plans = $this->dao->select('id, CONCAT(title, " [", begin, " ~ ", end, "]") AS title')->from(TABLE_PRODUCTPLAN) + $plans = $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN) ->where('product')->in($product) ->andWhere('deleted')->eq(0) ->andWhere('end')->lt($date) ->beginIF($branch)->andWhere("branch")->in("0,$branch")->fi() ->orderBy('begin desc') ->limit(5) - ->fetchPairs(); + ->fetchAll('id'); } - return array('' => '') + $this->processFuture($plans); + $plans = $this->reorder4Children($plans); + $planPairs = array(); + $parent = 0; + $parentTitle = ''; + foreach($plans as $plan) + { + if($plan->parent == '-1') + { + $parent = $plan->id; + $parentTitle = $plan->title; + } + if($plan->parent > 0 and $plan->parent == $parent) $plan->title = $parentTitle . ' /' . $plan->title; + $planPairs[$plan->id] = $plan->title . " [{$plan->begin} ~ {$plan->end}]"; + if($plan->begin == '2030-01-01' and $plan->end == '2030-01-01') $planPairs[$plan->id] = $plan->title . ' ' . $this->lang->productplan->future; + } + + return array('' => '') + $planPairs; } /** @@ -187,10 +261,39 @@ class productplanModel extends model */ public function getForProducts($products) { - return array('' => '') + $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) + $plans = $this->dao->select('id,title,parent,begin,end')->from(TABLE_PRODUCTPLAN) ->where('product')->in(array_keys($products)) ->andWhere('deleted')->eq(0) - ->orderBy('begin desc')->fetchPairs(); + ->orderBy('begin desc') + ->fetchAll('id'); + + $plans = $this->reorder4Children($plans); + $planPairs = array(); + $parent = 0; + $parentTitle = ''; + foreach($plans as $plan) + { + if($plan->parent == '-1') + { + $parent = $plan->id; + $parentTitle = $plan->title; + } + if($plan->parent > 0 and $plan->parent == $parent) $plan->title = $parentTitle . ' /' . $plan->title; + $planPairs[$plan->id] = $plan->title; + } + return array('' => '') + $planPairs; + } + + /** + * Get Children plan. + * + * @param int $planID + * @access public + * @return array + */ + public function getChildren($planID) + { + return $this->dao->select('*')->from(TABLE_PRODUCTPLAN)->where('parent')->eq((int)$planID)->andWhere('deleted')->eq('0')->fetchAll(); } /** @@ -228,6 +331,7 @@ class productplanModel extends model $planID = $this->dao->lastInsertID(); $this->file->updateObjectID($this->post->uid, $planID, 'plan'); $this->loadModel('score')->create('productplan', 'create', $planID); + if(!empty($plan->parent)) $this->dao->update(TABLE_PRODUCTPLAN)->set('parent')->eq('-1')->where('id')->eq($plan->parent)->andWhere('parent')->eq('0')->exec(); return $planID; } } @@ -425,18 +529,41 @@ class productplanModel extends model } /** - * Process future of plans. - * - * @param array $plans + * Reorder for children plans. + * + * @param array $plans * @access public * @return array */ - public function processFuture($plans) + public function reorder4Children($plans) { - foreach($plans as $key => $value) + /* Get children and unset. */ + $childrenPlans = array(); + foreach($plans as $plan) { - $plans[$key] = str_replace('[2030-01-01 ~ 2030-01-01]', '[' . $this->lang->productplan->future . ']', $value); + if($plan->parent > 0) + { + $childrenPlans[$plan->parent][$plan->id] = $plan; + if(isset($plans[$plan->parent])) unset($plans[$plan->id]); + } } + + if(!empty($childrenPlans)) + { + /* Append to parent plan. */ + $reorderedPlans = array(); + foreach($plans as $plan) + { + $reorderedPlans[$plan->id] = $plan; + if(isset($childrenPlans[$plan->id])) + { + $plan->children = count($childrenPlans[$plan->id]); + foreach($childrenPlans[$plan->id] as $childrenPlan) $reorderedPlans[$childrenPlan->id] = $childrenPlan; + } + } + $plans = $reorderedPlans; + } + return $plans; } } diff --git a/module/productplan/view/browse.html.php b/module/productplan/view/browse.html.php index 006ce7b6f3..0f393bf12e 100644 --- a/module/productplan/view/browse.html.php +++ b/module/productplan/view/browse.html.php @@ -63,14 +63,33 @@ productplan->hour;?> productplan->project;?> productplan->desc;?> - actions;?> + actions;?> loadModel('file');?> - file->replaceImgURL($plan, 'desc');?> - + file->replaceImgURL($plan, 'desc'); + if($plan->parent == '-1') + { + $parent = $plan->id; + $children = $plan->children; + } + if($plan->parent == 0) $parent = 0; + if(!empty($parent) and $plan->parent > 0 and $plan->parent != $parent) $parent = 0; + if($plan->parent <= 0) $i = 0; + + $class = ''; + if(!empty($parent) and $plan->parent == $parent) + { + $class = "table-children parent-{$parent}"; + $class .= $i == 0 ? ' table-child-top' : ''; + $class .= ($i + 1 == $children) ? ' table-child-bottom' : ''; + $i++; + } + ?> + id => '')) . html::a(helper::createLink('productplan', 'view', "planID=$plan->id"), sprintf('%03d', $plan->id));?> @@ -78,7 +97,12 @@ id);?> - id"), $plan->title);?> + ' title="title?>"> + id"), $plan->title); + if($plan->parent == '-1') echo ''; + ?> + session->currentProductType != 'normal'):?> branch];?> @@ -95,6 +119,11 @@ if(common::hasPriv('productplan', 'linkStory')) echo html::a(inlink('view', "planID=$plan->id&type=story&orderBy=id_desc&link=true"), '', '', "class='btn' title='{$lang->productplan->linkStory}'"); if(common::hasPriv('productplan', 'linkBug') and $config->global->flow != 'onlyStory') echo html::a(inlink('view', "planID=$plan->id&type=bug&orderBy=id_desc&link=true"), '', '', "class='btn' title='{$lang->productplan->linkBug}'"); common::printIcon('productplan', 'edit', "planID=$plan->id", $plan, 'list'); + if(common::hasPriv('productplan', 'create')) + { + if($plan->parent > '0') echo ""; + if($plan->parent <= '0') echo html::a($this->createLink('productplan', 'create', "product=$productID&branch=$branch&parent={$plan->id}"), "", '', "class='btn' title='{$this->lang->productplan->children}'"); + } if(common::hasPriv('productplan', 'delete', $plan)) { diff --git a/module/productplan/view/create.html.php b/module/productplan/view/create.html.php index ed1e8ea6d8..832dff2d2f 100644 --- a/module/productplan/view/create.html.php +++ b/module/productplan/view/create.html.php @@ -17,11 +17,17 @@
-

productplan->create;?>

+

productplan->createChildren : $lang->productplan->create;?>

+ + + + + + @@ -32,6 +38,7 @@ + @@ -61,6 +68,8 @@ id);?> + branch);?> + diff --git a/module/productplan/view/view.html.php b/module/productplan/view/view.html.php index 4f2b4c81b2..c42053f5ec 100644 --- a/module/productplan/view/view.html.php +++ b/module/productplan/view/view.html.php @@ -52,6 +52,7 @@ { echo html::a(inlink('view', "planID=$plan->id&type=bug&orderBy=id_desc&link=true"), ' ' . $lang->productplan->linkBug, '', "class='btn btn-link'"); } + if(common::hasPriv('productplan', 'create') and $plan->parent <= '0') echo html::a($this->createLink('productplan', 'create', "product={$plan->product}&branch={$plan->branch}&parent={$plan->id}"), "", '', "class='btn btn-link' title='{$this->lang->productplan->children}'"); common::printIcon('productplan', 'edit', "planID=$plan->id", $plan); common::printIcon('productplan', 'delete', "planID=$plan->id", $plan, 'button', '', 'hiddenwin'); } @@ -416,6 +417,12 @@ + parent > 0):?> + + + + + type != 'normal'):?> @@ -430,6 +437,16 @@ + parent == '-1'):?> + + + + + diff --git a/module/project/control.php b/module/project/control.php index 269da8c35f..bddc75f2c2 100644 --- a/module/project/control.php +++ b/module/project/control.php @@ -1000,6 +1000,8 @@ class project extends control if(!empty($_POST)) { $projectID = $copyProjectID == '' ? $this->project->create() : $this->project->create($copyProjectID); + if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + $this->project->updateProducts($projectID); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); @@ -1056,6 +1058,8 @@ class project extends control if(!empty($_POST)) { $changes = $this->project->update($projectID); + if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + $this->project->updateProducts($projectID); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); if($action == 'undelete') @@ -1718,7 +1722,7 @@ class project extends control if(!empty($_POST)) { $this->project->manageMembers($projectID); - $this->locate($this->createLink('project', 'team', "projectID=$projectID")); + die(js::locate($this->createLink('project', 'team', "projectID=$projectID"), 'parent')); } /* Load model. */ diff --git a/module/project/css/edit.css b/module/project/css/edit.css new file mode 100644 index 0000000000..15440cff19 --- /dev/null +++ b/module/project/css/edit.css @@ -0,0 +1 @@ +.chosen-container-single .chosen-single > span{max-width:100%;} diff --git a/module/project/model.php b/module/project/model.php index 67c0b7a743..824cc4b198 100644 --- a/module/project/model.php +++ b/module/project/model.php @@ -1599,6 +1599,7 @@ class projectModel extends model $planStories = array(); $planProducts = array(); + $count = 0; if(!empty($plans)) { foreach($plans as $planID => $productID) @@ -1606,7 +1607,6 @@ class projectModel extends model $planStory = $this->loadModel('story')->getPlanStories($planID); if(!empty($planStory)) { - $count = 0; foreach($planStory as $id => $story) { if($story->status == 'draft') diff --git a/module/project/view/managemembers.html.php b/module/project/view/managemembers.html.php index c58f2fcd9b..57605a826a 100644 --- a/module/project/view/managemembers.html.php +++ b/module/project/view/managemembers.html.php @@ -30,7 +30,7 @@
- +
productplan->parent;?>title;?>
productplan->product;?> name;?>
productplan->title;?>
productplan->title;?> title;?>
productplan->parent;?>id}"), "#{$parentPlan->id} " . $parentPlan->title);?>
product->branch;?>productplan->end;?> end == '2030-01-01' ? $lang->productplan->future : $plan->end;?>
productplan->children;?> + + id}"), "#{$childrenPlan->id} " . $childrenPlan->title) . '
';?> + +
productplan->desc;?> desc;?>
diff --git a/module/release/control.php b/module/release/control.php index cbb6414373..ed06532073 100644 --- a/module/release/control.php +++ b/module/release/control.php @@ -191,6 +191,10 @@ class release extends control { $this->release->delete(TABLE_RELEASE, $releaseID); + $release = $this->dao->select('*')->from(TABLE_RELEASE)->where('id')->eq((int)$releaseID)->fetch(); + $build = $this->dao->select('*')->from(TABLE_BUILD)->where('id')->eq((int)$release->build)->fetch(); + if(empty($build->project)) $this->loadModel('build')->delete(TABLE_BUILD, $build->id); + /* if ajax request, send result. */ if($this->server->ajax) { @@ -203,6 +207,8 @@ class release extends control { $response['result'] = 'success'; $response['message'] = ''; + $release = $this->release->getById($releaseID); + $this->dao->update(TABLE_BUILD)->set('deleted')->eq(1)->where('id')->eq($release->build)->andWhere('name')->eq($release->name)->exec(); } $this->send($response); } diff --git a/module/release/view/browse.html.php b/module/release/view/browse.html.php index ebe663e5b0..b8bfd9d52a 100644 --- a/module/release/view/browse.html.php +++ b/module/release/view/browse.html.php @@ -55,8 +55,8 @@ diff --git a/module/release/view/view.html.php b/module/release/view/view.html.php index 6bac17fa8c..c2e019edfe 100644 --- a/module/release/view/view.html.php +++ b/module/release/view/view.html.php @@ -21,8 +21,8 @@
id;?> - marker ? "" . $release->name : $release->name;?> - + marker ? " " : '';?> + name;?> deleted):?> release->deleted;?> diff --git a/module/report/lang/en.php b/module/report/lang/en.php index 778c2197a1..9591505617 100644 --- a/module/report/lang/en.php +++ b/module/report/lang/en.php @@ -59,7 +59,7 @@ $lang->report->product = $lang->productCommon; $lang->report->user = 'Name'; $lang->report->bugTotal = 'Bug'; $lang->report->task = 'Task'; -$lang->report->estimate = 'Est.(h)'; +$lang->report->estimate = 'Estimates'; $lang->report->consumed = 'Cost'; $lang->report->remain = 'Left'; $lang->report->deviation = 'Deviation'; diff --git a/module/sso/lang/en.php b/module/sso/lang/en.php index 8390dbef2c..8f11a96ddd 100644 --- a/module/sso/lang/en.php +++ b/module/sso/lang/en.php @@ -30,8 +30,8 @@ $lang->sso->bindTypeList['bind'] = 'Bind to existing User'; $lang->sso->bindTypeList['add'] = 'Add User'; $lang->sso->help = <<1. Interface address is required. If use PATH_INFO, it is http://YOUR RANGER ADDRESS/sys/sso-check.html If GET, it is http://YOUR RANGER ADDRESS/sys/index.php?m=sso&f=check

-

2. Code and Key must be the same as set in Ranger.

+

1. Zdoo address is required. If use PATH_INFO, it is http://YOUR ZDOO ADDRESS/sys/sso-check.html If GET, it is http://YOUR ZDOO ADDRESS/sys/index.php?m=sso&f=check

+

2. CAlias and Key must be the same as set in Zdoo.

EOD; $lang->sso->bindNotice = 'User that is just added has no privilege. You have to ask ZenTao Admin to grant permissions to the User.'; $lang->sso->bindNoPassword = 'Password should not be empty.'; diff --git a/module/story/lang/en.php b/module/story/lang/en.php index 5d03b00ce2..5f5aa31069 100644 --- a/module/story/lang/en.php +++ b/module/story/lang/en.php @@ -234,7 +234,7 @@ $lang->story->report->charts['storysPerPlan'] = 'by Plan'; $lang->story->report->charts['storysPerStatus'] = 'by Status'; $lang->story->report->charts['storysPerStage'] = 'by Phase'; $lang->story->report->charts['storysPerPri'] = 'by Priority'; -$lang->story->report->charts['storysPerEstimate'] = 'by Hour'; +$lang->story->report->charts['storysPerEstimate'] = 'by Estimate'; $lang->story->report->charts['storysPerOpenedBy'] = 'by CreatedBy'; $lang->story->report->charts['storysPerAssignedTo'] = 'by Assignee'; $lang->story->report->charts['storysPerClosedReason'] = 'by Closed Reason'; @@ -269,7 +269,7 @@ $lang->story->report->storysPerPri->item = 'Priority'; $lang->story->report->storysPerOpenedBy->item = 'Account'; $lang->story->report->storysPerAssignedTo->item = 'User'; $lang->story->report->storysPerClosedReason->item = 'Reason'; -$lang->story->report->storysPerEstimate->item = 'Hour'; +$lang->story->report->storysPerEstimate->item = 'Estimates'; $lang->story->report->storysPerChange->item = 'Change'; $lang->story->report->storysPerProduct->graph = new stdclass(); @@ -295,7 +295,7 @@ $lang->story->report->storysPerPri->graph->xAxisName = 'Priority'; $lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Created By'; $lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Assigned To'; $lang->story->report->storysPerClosedReason->graph->xAxisName = 'Close Reason'; -$lang->story->report->storysPerEstimate->graph->xAxisName = 'Hour '; +$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimates '; $lang->story->report->storysPerChange->graph->xAxisName = 'Change'; $lang->story->placeholder = new stdclass(); diff --git a/module/task/lang/en.php b/module/task/lang/en.php index 8092c0df0c..209ea94588 100644 --- a/module/task/lang/en.php +++ b/module/task/lang/en.php @@ -22,8 +22,8 @@ $lang->task->delete = "Delete"; $lang->task->deleted = "Deleted"; $lang->task->delayed = 'Delayed'; $lang->task->view = "Overview"; -$lang->task->logEfforts = "Hour"; -$lang->task->record = "Est"; +$lang->task->logEfforts = "Effort"; +$lang->task->record = "Estimates"; $lang->task->start = "Start"; $lang->task->restart = "Continue"; $lang->task->finish = "Finish"; @@ -38,7 +38,7 @@ $lang->task->case = 'Linked Case'; $lang->task->confirmStoryChange = "Confirm Change"; $lang->task->progress = 'Progress'; $lang->task->progressTips = 'Cost/(Cost+Left)'; -$lang->task->copy = 'Duplicate Task'; +$lang->task->copy = 'Duplicated Task'; $lang->task->waitTask = 'Wait Task'; $lang->task->common = 'Task'; @@ -62,7 +62,7 @@ $lang->task->consumed = 'Cost'; $lang->task->myConsumed = 'My Cost'; $lang->task->consumedAB = 'Cost'; $lang->task->hour = 'Hour'; -$lang->task->consumedThisTime = 'Hour'; +$lang->task->consumedThisTime = 'Cost'; $lang->task->leftThisTime = 'Left'; $lang->task->datePlan = 'Plan'; $lang->task->estStarted = 'Plan Start'; diff --git a/module/task/model.php b/module/task/model.php index 70e5fab50e..a47806a148 100644 --- a/module/task/model.php +++ b/module/task/model.php @@ -428,6 +428,12 @@ class taskModel extends model if($status == 'doing') { + if($parentTask->assignedTo == 'closed') + { + $task->assignedTo = $childTask->assignedTo; + $task->assignedDate = $now; + } + $task->finishedBy = ''; $task->finishedDate = ''; $task->closedBy = ''; @@ -661,7 +667,11 @@ class taskModel extends model { foreach($teams as $member) $this->dao->insert(TABLE_TEAM)->data($member)->autoCheck()->exec(); $task = $this->computeHours4Multiple($oldTask, $task); - if($task->status == 'wait') $task->assignedTo = key($teams); + if($task->status == 'wait') + { + reset($teams); + $task->assignedTo = key($teams); + } } $this->dao->update(TABLE_TASK)->data($task) diff --git a/module/testcase/view/create.html.php b/module/testcase/view/create.html.php index b300f59685..035f257273 100644 --- a/module/testcase/view/create.html.php +++ b/module/testcase/view/create.html.php @@ -111,6 +111,7 @@ +
id"), sprintf('%03d', $release->id));?> marker ? "" . $release->name : $release->name; - echo html::a(inlink('view', "release=$release->id"), $releaseName); + $flagIcon = $release->marker ? " " : ''; + echo $flagIcon . html::a(inlink('view', "release=$release->id"), $release->name); ?> createLink('build', 'view', "buildID=$release->buildID"), $release->buildName);?>