diff --git a/db/update17.0.sql b/db/update17.0.sql index 832ca723af..362a0073ff 100644 --- a/db/update17.0.sql +++ b/db/update17.0.sql @@ -11,3 +11,5 @@ DELETE FROM `zt_workflowaction` WHERE `module`='task' AND `action`='browse'; DELETE FROM `zt_workflowaction` WHERE `module`='build' AND `action`='browse'; ALTER TABLE `zt_projectproduct` MODIFY COLUMN `plan` varchar(255) NOT NULL; + +UPDATE `zt_project` SET status = 'closed' WHERE status='done'; diff --git a/module/doc/control.php b/module/doc/control.php index 263aacc735..8b4b0922fe 100755 --- a/module/doc/control.php +++ b/module/doc/control.php @@ -827,19 +827,56 @@ class doc extends control } /** - * Ajax get doclib whitelist. + * Ajax get whitelist. * * @param int $doclibID * @param string $acl open|custom|private + * @param string $control user|group * @access public * @return string */ - public function ajaxGetWhitelist($doclibID, $acl) + public function ajaxGetWhitelist($doclibID, $acl = '', $control = '') { - $doclib = $this->doc->getLibById($doclibID); - $users = $this->user->getPairs('noletter|noempty|noclosed'); - + $doclib = $this->doc->getLibById($doclibID); + $users = $this->user->getPairs('noletter|noempty|noclosed'); $selectedUser = $doclib->users; + + if($control == 'group') + { + $groups = $this->loadModel('group')->getPairs(); + $selectedGroup = $doclib->groups; + if($doclib->acl == 'custom') + { + foreach($groups as $groupID => $group) + { + if(strpos($doclib->groups, (string)$groupID) === false) unset($groups[$groupID]); + } + return print(html::select('groups[]', $groups, $selectedGroup, "class='form-control chosen' multiple")); + } + if($doclib->acl == 'open') return print(html::select('groups[]', $groups, $selectedGroup, "class='form-control chosen' multiple")); + if($doclib->acl == 'private') echo 'private'; + if($doclib->acl == 'default') echo 'default'; + return false; + } + + if($control == 'user') + { + foreach($users as $account => $user) + { + if(($doclib->acl == 'custom' or $doclib->acl == 'private') and strpos($doclib->users, (string)$account) === false ) unset($users[$account]); + } + + if($doclib->acl == 'custom') + { + return print(html::select('users[]', $users, $selectedUser, "multiple class='form-control")); + } + if($doclib->acl == 'open') return print(html::select('users[]', $users, $selectedUser, "multiple class='form-control")); + if($doclib->acl == 'private') echo 'private'; + if($doclib->acl == 'default') echo 'default'; + return false; + } + + /* Sync whitelist when doclib permissions changed. */ if($doclib->acl != 'custom' and !empty($doclib->project) and $acl == 'custom') { $project = $this->loadModel('project')->getById($doclib->project); diff --git a/module/doc/js/common.js b/module/doc/js/common.js index 48edfdbc1d..dfa45c85a3 100644 --- a/module/doc/js/common.js +++ b/module/doc/js/common.js @@ -21,15 +21,18 @@ function loadModules(libID) */ function toggleAcl(acl, type) { + var libID = $('#lib').val(); if(acl == 'custom') { $('#whiteListBox').removeClass('hidden'); $('#groupBox').removeClass('hidden'); + if(type == 'doc') loadWhitelist(libID); } else if(acl == 'private') { $('#whiteListBox').removeClass('hidden'); $('#groupBox').addClass('hidden'); + if(type == 'doc') loadWhitelist(libID); } else { @@ -78,6 +81,61 @@ function loadDocModule(libID) $('#module_chosen').remove(); $('#module').chosen(); }); + + loadWhitelist(libID); +} + +/** + * Load whitelist by libID. + * + * @param int $libID + * @access public + * @return void + */ +function loadWhitelist(libID) +{ + var groupLink = createLink('doc', 'ajaxGetWhitelist', 'libID=' + libID + '&acl=&control=group'); + var userLink = createLink('doc', 'ajaxGetWhitelist', 'libID=' + libID + '&acl=&control=user'); + $.post(groupLink, function(groups) + { + if(!(groups == 'default' || groups == 'private')) + { + $('#groups').replaceWith(groups); + $('#groups_chosen').remove(); + $('#groups').chosen(); + } + }); + + $.post(userLink, function(users) + { + if(users != 'default') + { + if(users == 'private') + { + $('#aclopen').parent('.radio-inline').addClass('hidden'); + $('#aclcustom').parent('.radio-inline').addClass('hidden'); + $('#aclprivate').prop('checked', true); + } + else + { + $('#aclopen').parent('.radio-inline').removeClass('hidden'); + $('#aclcustom').parent('.radio-inline').removeClass('hidden'); + $('#aclprivate').prop('checked', false); + + $('#users').replaceWith(users); + $('#users_chosen').remove(); + $('#whiteListBox .picker').remove(); + if($('#users option').length < maxCount) + { + $('#users').chosen(); + } + else + { + $('#users').picker(); + } + } + } + }); } /** diff --git a/module/doc/model.php b/module/doc/model.php index bd8dcd90f5..6798d9e23f 100644 --- a/module/doc/model.php +++ b/module/doc/model.php @@ -1084,7 +1084,7 @@ class docModel extends model if(strpos(",{$object->users},", $account) !== false) return true; } - if(strpos($extra, 'notdoc') === false) + if(strpos($extra, 'notdoc') !== false) { static $extraDocLibs; if($extraDocLibs === null) $extraDocLibs = $this->getPrivLibsByDoc(); @@ -1118,7 +1118,7 @@ class docModel extends model if($extraDocLibs === null) $extraDocLibs = $this->getPrivLibsByDoc(); static $libs; - if($libs === null) $libs = $this->getLibs('all', 'notdoc'); + if($libs === null) $libs = $this->getLibs('all'); if(isset($libs[$object->lib]) and isset($extraDocLibs[$object->lib])) unset($extraDocLibs[$object->lib]); if($object->acl == 'open' and !isset($extraDocLibs[$object->lib])) return true; diff --git a/module/doc/view/create.html.php b/module/doc/view/create.html.php index 6be6f00aaa..380295240f 100644 --- a/module/doc/view/create.html.php +++ b/module/doc/view/create.html.php @@ -10,6 +10,7 @@ * @link http://www.zentao.net */ ?> +config->maxCount);?> doc->officeTypes, $docType) !== false):?>
diff --git a/module/doc/view/edit.html.php b/module/doc/view/edit.html.php index b9c8706261..bb1a1fa5d7 100644 --- a/module/doc/view/edit.html.php +++ b/module/doc/view/edit.html.php @@ -17,6 +17,7 @@ doc->confirmUpdateContent);?> id);?> draft);?> +config->maxCount);?>
diff --git a/module/execution/control.php b/module/execution/control.php index bb72b95d4d..839c553183 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -740,6 +740,7 @@ class execution extends control else { $this->session->set('executionStoryBrowseType', $type); + $this->session->set('storyBrowseType', $type, 'execution'); } /* Save session. */ @@ -1026,6 +1027,7 @@ class execution extends control $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false); $cases = $this->testcase->appendData($cases, 'case'); + $cases = $this->loadModel('story')->checkNeedConfirm($cases); $this->view->title = $this->lang->execution->testcase; $this->view->executionID = $executionID; diff --git a/module/execution/model.php b/module/execution/model.php index eb642d6ba9..4530017fcf 100644 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -4220,7 +4220,7 @@ class executionModel extends model { echo "
"; } - echo printf('%03d', $execution->id); + echo sprintf('%03d', $execution->id); break; case 'name': $label = $execution->type == 'stage' ? 'label-warning' : 'label-info'; diff --git a/module/execution/view/testcase.html.php b/module/execution/view/testcase.html.php index eaca8e00a3..56ef3d6e4e 100644 --- a/module/execution/view/testcase.html.php +++ b/module/execution/view/testcase.html.php @@ -32,7 +32,7 @@ testtask->lastRunTime);?> testtask->lastRunResult);?> statusAB);?> - actions;?> + actions;?> @@ -40,7 +40,6 @@ case : $case->id; $runID = $type == 'assigntome' ? $case->id : 0; - $canBeChanged = common::canBeChanged('testcase', $case); ?> @@ -55,18 +54,26 @@ lastRunner);?> lastRunDate)) echo date(DT_MONTHTIME1, strtotime($case->lastRunDate));?> lastRunResult) echo $lang->testcase->resultList[$case->lastRunResult];?> - processStatus('testcase', $case);?> + + needconfirm) + { + echo "{$this->lang->story->changed}"; + } + elseif(isset($case->fromCaseVersion) and $case->fromCaseVersion > $case->version and !$case->needconfirm) + { + echo "{$this->lang->testcase->changed}"; + } + else + { + echo "" . $this->processStatus('testcase', $case) . ""; + } + ?> + status == 'wait' ? 'disabled' : ''; - common::printIcon('testcase', 'createBug', "product=$case->product&branch=$case->branch&extra=caseID=$caseID,version=$case->version,runID=$runID,executionID=$executionID", $case, 'list', 'bug', '', 'iframe', '', "data-width='90%'", '', $case->project); - common::printIcon('testcase', 'create', "productID=$case->product&branch=$case->branch&moduleID=$case->module&from=testcase¶m=$caseID", $case, 'list', 'copy', '', '', '', '', '', $case->project); - common::printIcon('testtask', 'runCase', "runID=$runID&caseID=$caseID&version=$case->version", '', 'list', 'play', '', "iframe $disabled", true, "data-width='95%'", '', $case->project); - common::printIcon('testtask', 'results', "runID=$runID&caseID=$caseID", '', 'list', 'list-alt', '', 'iframe', true, "data-width='95%'", '', $case->project); - common::printIcon('testcase', 'edit', "caseID=$caseID&comment=false&executionID=$executionID", $case, 'list', 'edit', '', '', '', '', '', $case->project); - } + $case->browseType = $type; + echo $this->testcase->buildOperateMenu($case, 'browse'); ?> diff --git a/module/kanban/control.php b/module/kanban/control.php index 6302d2919f..0d921abfbd 100644 --- a/module/kanban/control.php +++ b/module/kanban/control.php @@ -615,6 +615,7 @@ class kanban extends control } $kanban = $from == 'execution' ? $this->loadModel('execution')->getByID($kanbanID) : $this->kanban->getByID($kanbanID); + if($from == 'execution') $this->lang->kanbanlane->heightTypeList['auto'] = $this->lang->kanbanlane->heightByCard; $this->view->heightType = $kanban->displayCards > 2 ? 'custom' : 'auto'; $this->view->displayCards = $kanban->displayCards ? $kanban->displayCards : ''; diff --git a/module/kanban/lang/en.php b/module/kanban/lang/en.php index 331c09cda7..4e5a92b40f 100644 --- a/module/kanban/lang/en.php +++ b/module/kanban/lang/en.php @@ -308,13 +308,14 @@ $lang->kanbancolumn->confirmDeleteChild = 'Are you sure to delete this column? A $lang->kanbancolumn->confirmRestore = 'Are you sure you want to restore this Kanban column? After restoring the Kanban column, the Kanban column and all tasks in the Kanban column will be restored to the previous position at the same time.'; $lang->kanbanlane = new stdclass(); -$lang->kanbanlane->name = $lang->kanban->laneName; -$lang->kanbanlane->common = 'Lane'; -$lang->kanbanlane->default = 'Default Lane'; -$lang->kanbanlane->column = 'Lane Kanban Column'; -$lang->kanbanlane->otherlane = 'Select Existed Lane'; -$lang->kanbanlane->color = 'Lane Color'; -$lang->kanbanlane->WIPType = 'Lane WIP Type'; +$lang->kanbanlane->name = $lang->kanban->laneName; +$lang->kanbanlane->common = 'Lane'; +$lang->kanbanlane->default = 'Default Lane'; +$lang->kanbanlane->column = 'Lane Kanban Column'; +$lang->kanbanlane->otherlane = 'Select Existed Lane'; +$lang->kanbanlane->color = 'Lane Color'; +$lang->kanbanlane->WIPType = 'Lane WIP Type'; +$lang->kanbanlane->heightByCard = 'Adaptive (Adaptive to card height)'; $lang->kanbanlane->confirmDelete = 'Are you sure to delete this lane? After deleting the lane, all data (columns and cards) in the lane will also be deleted.'; $lang->kanbanlane->confirmDeleteTip = 'Are you sure to delete this lane? After deleting the lane, all %s in the lane will be hidden.'; diff --git a/module/kanban/lang/zh-cn.php b/module/kanban/lang/zh-cn.php index 870a18253f..42e5bf807e 100644 --- a/module/kanban/lang/zh-cn.php +++ b/module/kanban/lang/zh-cn.php @@ -308,13 +308,14 @@ $lang->kanbancolumn->confirmDeleteChild = '您确认删除该列吗?删除列 $lang->kanbancolumn->confirmRestore = '您确定要还原该看板列吗?还原后,该看板列将回到之前的位置。'; $lang->kanbanlane = new stdclass(); -$lang->kanbanlane->name = $lang->kanban->laneName; -$lang->kanbanlane->common = '泳道'; -$lang->kanbanlane->default = '默认泳道'; -$lang->kanbanlane->column = '泳道看板列'; -$lang->kanbanlane->otherlane = '选择共享看板列的泳道'; -$lang->kanbanlane->color = '泳道颜色'; -$lang->kanbanlane->WIPType = '泳道在制品类型'; +$lang->kanbanlane->name = $lang->kanban->laneName; +$lang->kanbanlane->common = '泳道'; +$lang->kanbanlane->default = '默认泳道'; +$lang->kanbanlane->column = '泳道看板列'; +$lang->kanbanlane->otherlane = '选择共享看板列的泳道'; +$lang->kanbanlane->color = '泳道颜色'; +$lang->kanbanlane->WIPType = '泳道在制品类型'; +$lang->kanbanlane->heightByCard = '自适应(根据卡片高度自适应)'; $lang->kanbanlane->confirmDelete = '您确认删除该泳道吗?删除泳道后,该泳道中所有数据(列、卡片)也会被删除。'; $lang->kanbanlane->confirmDeleteTip = '您确认删除该泳道吗?删除泳道后,该泳道中所有的%s将被隐藏。'; diff --git a/module/product/css/all.css b/module/product/css/all.css index 347cb22388..7e09a76608 100644 --- a/module/product/css/all.css +++ b/module/product/css/all.css @@ -43,7 +43,7 @@ th.c-story {width: 290px;} th.c-bug {width: 200px;} th.c-plan {width: 45px;} th.c-release {width: 50px;} -th.c-actions {width: 60px;} +th.c-actions {width: 50px;} [lang='en'] .en-wrap-text {white-space: normal; height: 24px; line-height: 1; font-size: 12px;} diff --git a/module/product/model.php b/module/product/model.php index 11c790166b..758f61f6ec 100644 --- a/module/product/model.php +++ b/module/product/model.php @@ -2160,7 +2160,6 @@ class productModel extends model $menu .= $this->buildMenu('product', 'edit', $params, $product, $type); if($type == 'view') $menu .= $this->buildMenu('product', 'delete', $params, $product, $type, 'trash', 'hiddenwin'); - if($type == 'browse' && common::hasPriv('product', 'updateOrder')) $menu .= ""; return $menu; } diff --git a/module/product/view/all.html.php b/module/product/view/all.html.php index 6d51918596..8e5990a2b2 100644 --- a/module/product/view/all.html.php +++ b/module/product/view/all.html.php @@ -207,7 +207,7 @@ id => ''));?> - + createLink('product', 'browse', 'productID=' . $product->id), $product->name); echo "" . $productLink; @@ -229,7 +229,7 @@ plans;?> releases;?> " . $this->loadModel('flow')->getFieldValue($extendField, $product) . "";?> - product->buildOperateMenu($product, 'browse');?> + product->buildOperateMenu($product, 'browse');?> diff --git a/module/program/model.php b/module/program/model.php index 6748fbe90b..f0f9ec6fb1 100644 --- a/module/program/model.php +++ b/module/program/model.php @@ -1326,7 +1326,7 @@ class programModel extends model { $parent = $this->dao->select('id,parent,path,grade')->from(TABLE_PROGRAM)->where('id')->eq($parentID)->fetch(); - $childNodes = $this->dao->select('id,parent,path,grade')->from(TABLE_PROGRAM) + $childNodes = $this->dao->select('id,parent,path,grade,type')->from(TABLE_PROGRAM) ->where('path')->like("{$oldPath}%") ->andWhere('deleted')->eq(0) ->orderBy('grade') @@ -1335,13 +1335,16 @@ class programModel extends model /* Process child node path and grade field. */ foreach($childNodes as $childNode) { - $path = substr($childNode->path, strpos($childNode->path, ",{$programID},")); - $grade = $childNode->grade - $oldGrade + 1; + $path = substr($childNode->path, strpos($childNode->path, ",{$programID},")); + + /* Only program and project sets update grade. */ + $grade = in_array($childNode->type, array('program', 'project')) ? $childNode->grade - $oldGrade + 1 : $childNode->grade; if($parent) { $path = rtrim($parent->path, ',') . $path; - $grade = $parent->grade + $grade; + $grade = in_array($childNode->type, array('program', 'project'))? $parent->grade + $grade : $grade; } + $this->dao->update(TABLE_PROGRAM)->set('path')->eq($path)->set('grade')->eq($grade)->where('id')->eq($childNode->id)->exec(); } diff --git a/module/project/control.php b/module/project/control.php index 4d96af5f2e..bf493b29ce 100644 --- a/module/project/control.php +++ b/module/project/control.php @@ -78,7 +78,7 @@ class project extends control if($this->config->edition != 'open') list($fields, $projects) = $this->loadModel('workflowfield')->appendDataFromFlow($fields, $projects); $this->post->set('fields', $fields); $this->post->set('rows', $projects); - $this->post->set('kind', 'project'); + $this->post->set('kind', $this->lang->project->common); $this->fetch('file', 'export2' . $this->post->fileType, $_POST); } diff --git a/module/story/model.php b/module/story/model.php index 1df659b653..4dbd940f30 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -2902,7 +2902,7 @@ class storyModel extends model $storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in(array_keys($executions))->fetchPairs(); } - $type = ($type == 'bymodule' and $this->session->storyBrowseType) ? $this->session->storyBrowseType : $type; + $type = (strpos('bymodule|byproduct', $type) !== false and $this->session->storyBrowseType) ? $this->session->storyBrowseType : $type; $stories = $this->dao->select('distinct t1.*, t2.*,t3.type as productType,t2.version as version')->from(TABLE_PROJECTSTORY)->alias('t1') ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') diff --git a/module/upgrade/model.php b/module/upgrade/model.php index 73e2edea46..8d8d3a8325 100644 --- a/module/upgrade/model.php +++ b/module/upgrade/model.php @@ -513,6 +513,7 @@ class upgradeModel extends model break; case '17_0': $this->replaceSetLanePriv(); + $this->updateProjectData(); break; } @@ -6358,4 +6359,82 @@ class upgradeModel extends model return true; } + + /** + * Update path and grade of program, project and execution. + * + * @access public + * @return bool + */ + public function updateProjectData() + { + /* Process programs. */ + $programs = $this->dao->select('id,parent,grade,path')->from(TABLE_PROJECT)->where('type')->eq('program')->orderBy('parent_asc')->fetchAll('id'); + foreach($programs as $program) + { + if(!$program->parent) + { + $program->grade = 1; + $program->path = ",$program->id,"; + } + else + { + $program->grade = $programs[$program->parent]->grade + 1; + $program->path = $programs[$program->parent]->path . "$program->id,"; + } + + $this->dao->update(TABLE_PROGRAM) + ->set('path')->eq($program->path) + ->set('grade')->eq($program->grade) + ->where('id')->eq($program->id)->exec(); + } + + /* Process projects. */ + $projects = $this->dao->select('id,project,parent,grade,path')->from(TABLE_PROJECT)->where('type')->eq('project')->orderBy('parent_asc')->fetchAll('id'); + foreach($projects as $project) + { + if(!$project->parent) + { + $project->grade = 1; + $project->path = ",$project->id,"; + } + else + { + $project->grade = $programs[$project->parent]->grade + 1; + $project->path = $programs[$project->parent]->path . "$project->id,"; + } + + $this->dao->update(TABLE_PROJECT) + ->set('path')->eq($project->path) + ->set('grade')->eq($project->grade) + ->where('id')->eq($project->id)->exec(); + } + + /* Process executions. */ + $sprints = $this->dao->select('id,project,parent,grade,path')->from(TABLE_PROJECT) + ->where('type')->ne('project') + ->andWhere('type')->ne('program') + ->orderBy('parent_asc')->fetchAll('id'); + + foreach($sprints as $sprint) + { + if($sprint->parent == $sprint->project) + { + $sprint->grade = 1; + $sprint->path = ",$sprint->project,$sprint->id,"; + } + else + { + $sprint->grade = 2; + $sprint->path = $sprints[$sprint->parent]->path . "$sprint->id,"; + } + + $this->dao->update(TABLE_EXECUTION) + ->set('path')->eq($sprint->path) + ->set('grade')->eq($sprint->grade) + ->where('id')->eq($sprint->id)->exec(); + } + + return true; + } }