Merge branch 'master' into 'sprint/179_tianshujie_46241'

# Conflicts:
#   module/group/lang/resource.php
#   module/kanban/css/space.css
#   module/kanban/js/view.js
This commit is contained in:
孙广明
2021-12-22 06:03:21 +00:00
46 changed files with 573 additions and 193 deletions
+23 -22
View File
@@ -11,28 +11,29 @@
*/
global $config;
$lang->action->common = 'Log';
$lang->action->id = 'ID';
$lang->action->product = $lang->productCommon;
$lang->action->project = 'Project';
$lang->action->execution = $lang->execution->common;
$lang->action->objectType = 'Object Type';
$lang->action->objectID = 'ID';
$lang->action->objectName = 'Object Name';
$lang->action->actor = 'User';
$lang->action->action = 'Action';
$lang->action->actionID = 'Action ID';
$lang->action->date = 'Date';
$lang->action->extra = 'Extra';
$lang->action->system = 'System';
$lang->action->url = 'URL';
$lang->action->contentType = 'Content Type';
$lang->action->data = 'Data';
$lang->action->result = 'Result';
$lang->action->modified = 'Modified';
$lang->action->old = 'Old';
$lang->action->new = 'New';
+$lang->action->to = 'To';
$lang->action->common = 'Log';
$lang->action->id = 'ID';
$lang->action->product = $lang->productCommon;
$lang->action->project = 'Project';
$lang->action->execution = $lang->execution->common;
$lang->action->objectType = 'Object Type';
$lang->action->objectID = 'ID';
$lang->action->objectName = 'Object Name';
$lang->action->actor = 'User';
$lang->action->action = 'Action';
$lang->action->actionID = 'Action ID';
$lang->action->date = 'Date';
$lang->action->extra = 'Extra';
$lang->action->system = 'System';
$lang->action->url = 'URL';
$lang->action->contentType = 'Content Type';
$lang->action->data = 'Data';
$lang->action->result = 'Result';
$lang->action->modified = 'Modified';
$lang->action->old = 'Old';
$lang->action->new = 'New';
$lang->action->to = 'To';
$lang->action->superReviewer = 'Super Reviewer';
$lang->action->trash = 'Recycle';
$lang->action->undelete = 'Restore';
+23 -22
View File
@@ -11,28 +11,29 @@
*/
global $config;
$lang->action->common = '系统日志';
$lang->action->id = '编号';
$lang->action->product = $lang->productCommon;
$lang->action->project = '项目';
$lang->action->execution = $lang->execution->common;
$lang->action->objectType = '对象类型';
$lang->action->objectID = '对象ID';
$lang->action->objectName = '对象名称';
$lang->action->actor = '操作者';
$lang->action->action = '动作';
$lang->action->actionID = '记录ID';
$lang->action->date = '日期';
$lang->action->extra = '附加值';
$lang->action->system = '系统';
$lang->action->url = '网址';
$lang->action->contentType = '内容类型';
$lang->action->data = '数据';
$lang->action->result = '结果';
$lang->action->modified = '修改了';
$lang->action->old = '旧值为';
$lang->action->new = '新值为';
$lang->action->to = '到';
$lang->action->common = '系统日志';
$lang->action->id = '编号';
$lang->action->product = $lang->productCommon;
$lang->action->project = '项目';
$lang->action->execution = $lang->execution->common;
$lang->action->objectType = '对象类型';
$lang->action->objectID = '对象ID';
$lang->action->objectName = '对象名称';
$lang->action->actor = '操作者';
$lang->action->action = '动作';
$lang->action->actionID = '记录ID';
$lang->action->date = '日期';
$lang->action->extra = '附加值';
$lang->action->system = '系统';
$lang->action->url = '网址';
$lang->action->contentType = '内容类型';
$lang->action->data = '数据';
$lang->action->result = '结果';
$lang->action->modified = '修改了';
$lang->action->old = '旧值为';
$lang->action->new = '新值为';
$lang->action->to = '到';
$lang->action->superReviewer = '超级评审人';
$lang->action->trash = '回收站';
$lang->action->undelete = '还原';
+23 -5
View File
@@ -736,7 +736,14 @@ class actionModel extends model
if(is_array($desc))
{
if($key == 'extra') continue;
$desc['main'] = str_replace('$' . $key, $value, $desc['main']);
if($action->objectType == 'story' and $action->action = 'reviewed' and strpos($action->extra, '|') !== false and $key == 'actor')
{
$desc['main'] = str_replace('$actor', $this->lang->action->superReviewer . ' ' . $value, $desc['main']);
}
else
{
$desc['main'] = str_replace('$' . $key, $value, $desc['main']);
}
}
else
{
@@ -762,11 +769,20 @@ class actionModel extends model
$actionDesc = str_replace('$extra', $action->extra, $desc['main']);
}
if($action->objectType == 'story' and $action->action == 'reviewed' and strpos($action->extra, ',') !== false)
if($action->objectType == 'story' and $action->action == 'reviewed')
{
list($extra, $reason) = explode(',', $extra);
$desc['reason'] = $this->lang->$objectType->{$desc['reason']};
$actionDesc = str_replace(array('$extra', '$reason'), array($desc['extra'][$extra], $desc['reason'][$reason]), $desc['main']);
if(strpos($action->extra, ',') !== false)
{
list($extra, $reason) = explode(',', $extra);
$desc['reason'] = $this->lang->$objectType->{$desc['reason']};
$actionDesc = str_replace(array('$extra', '$reason'), array($desc['extra'][$extra], $desc['reason'][$reason]), $desc['main']);
}
if(strpos($action->extra, '|') !== false)
{
list($extra, $isSuperReviewer) = explode('|', $extra);
$actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
}
echo $actionDesc;
}
@@ -982,6 +998,7 @@ class actionModel extends model
{
$this->app->loadLang('todo');
$this->app->loadLang('stakeholder');
$this->app->loadLang('branch');
/* Get commiters and the same department users. */
$commiters = $this->loadModel('user')->getCommiters();
@@ -1005,6 +1022,7 @@ class actionModel extends model
if($action->objectType == 'branch' and $action->action == 'mergedbranch')
{
if($action->objectID == 0) $action->objectName = $this->lang->branch->main;
$action->objectName = '"' . $action->extra . ' "' . $this->lang->action->to . ' "' . $action->objectName . '"';
}
+2 -1
View File
@@ -45,7 +45,7 @@ class branch extends control
$this->view->orderBy = $orderBy;
$this->view->pager = $pager;
$this->view->product = $this->product->getById($productID);
$this->view->branchPairs = $this->branch->getPairs($productID);
$this->view->branchPairs = $this->branch->getPairs($productID, 'active');
$this->display();
}
@@ -316,6 +316,7 @@ class branch extends control
$mergeToBranch = $_POST['createBranch'] ? '' : $_POST['targetBranch'];
return $branch != 0 and $branch != $mergeToBranch;
});
$mergedBranchIDList = implode(',', $mergedBranches);
$mergedBranches = $this->dao->select('id,name')->from(TABLE_BRANCH)->where('id')->in($mergedBranchIDList)->fetchPairs();
+8 -5
View File
@@ -47,13 +47,16 @@ $(function()
$("input[id*='branchIDList']").change(function()
{
if($(this).prop('checked') && $(this).closest('tr').data('status') === 'closed')
$("input[id*='branchIDList']").each(function()
{
$("a[href='#mergeBranch']").hide();
return false;
}
if($(this).prop('checked') && ($(this).closest('tr').data('status') === 'closed' || $(this).closest('tr').data('id') == '0'))
{
$("a[href='#mergeBranch']").hide();
return false;
}
$("a[href='#mergeBranch']").show();
$("a[href='#mergeBranch']").show();
})
});
$('#createBranch').change(function()
+16 -15
View File
@@ -9,21 +9,22 @@ $lang->branch->manageTitle = '%s Management';
$lang->branch->all = 'All ';
$lang->branch->main = 'Main';
$lang->branch->edit = 'Edit %s';
$lang->branch->editAction = 'Edit Branch';
$lang->branch->activate = 'Activate';
$lang->branch->activateAction = 'Activate Branch';
$lang->branch->close = 'Close';
$lang->branch->closeAction = 'Close Branch';
$lang->branch->create = 'Create %s';
$lang->branch->createAction = 'Create Branch';
$lang->branch->merge = 'Merge';
$lang->branch->batchEdit = 'Batch Edit';
$lang->branch->defaultBranch = 'Default Branch';
$lang->branch->setDefault = 'Set Default';
$lang->branch->setDefaultAction = 'Set Default';
$lang->branch->mergeTo = 'Merge branch to';
$lang->branch->mergeBranch = 'Merge branch';
$lang->branch->edit = 'Edit %s';
$lang->branch->editAction = 'Edit Branch';
$lang->branch->activate = 'Activate';
$lang->branch->activateAction = 'Activate Branch';
$lang->branch->close = 'Close';
$lang->branch->closeAction = 'Close Branch';
$lang->branch->create = 'Create %s';
$lang->branch->createAction = 'Create Branch';
$lang->branch->merge = 'Merge';
$lang->branch->batchEdit = 'Batch Edit';
$lang->branch->defaultBranch = 'Default Branch';
$lang->branch->setDefault = 'Set Default';
$lang->branch->setDefaultAction = 'Set Default';
$lang->branch->mergeTo = 'Merge branch to';
$lang->branch->mergeBranch = 'Merge branch';
$lang->branch->mergeBranchAction = 'Merge branch';
$lang->branch->id = 'ID';
$lang->branch->product = 'Product';
+16 -15
View File
@@ -9,21 +9,22 @@ $lang->branch->manageTitle = '%s管理';
$lang->branch->all = '所有';
$lang->branch->main = '主干';
$lang->branch->edit = '编辑%s';
$lang->branch->editAction = '编辑分支';
$lang->branch->activate = '激活';
$lang->branch->activateAction = '激活分支';
$lang->branch->close = '关闭';
$lang->branch->closeAction = '关闭分支';
$lang->branch->create = '新建%s';
$lang->branch->createAction = '新建分支';
$lang->branch->merge = '合并';
$lang->branch->batchEdit = '批量编辑';
$lang->branch->defaultBranch = '默认分支';
$lang->branch->setDefault = '设为默认分支';
$lang->branch->setDefaultAction = '设置默认分支';
$lang->branch->mergeTo = '合并@branch@到';
$lang->branch->mergeBranch = '合并@branch@';
$lang->branch->edit = '编辑%s';
$lang->branch->editAction = '编辑分支';
$lang->branch->activate = '激活';
$lang->branch->activateAction = '激活分支';
$lang->branch->close = '关闭';
$lang->branch->closeAction = '关闭分支';
$lang->branch->create = '新建%s';
$lang->branch->createAction = '新建分支';
$lang->branch->merge = '合并';
$lang->branch->batchEdit = '批量编辑';
$lang->branch->defaultBranch = '默认分支';
$lang->branch->setDefault = '设为默认分支';
$lang->branch->setDefaultAction = '设置默认分支';
$lang->branch->mergeTo = '合并@branch@到';
$lang->branch->mergeBranch = '合并@branch@';
$lang->branch->mergeBranchAction = '合并分支';
$lang->branch->id = 'ID';
$lang->branch->product = '所属产品';
+1 -1
View File
@@ -51,7 +51,7 @@
<li value='<?php echo $i ++;?>'>
<?php
$action->actor = zget($users, $action->actor);
if($action->action == 'assigned') $action->extra = zget($users, $action->extra);
if($action->action == 'assigned' or $action->action == 'toaudit') $action->extra = zget($users, $action->extra);
if(strpos($action->actor, ':') !== false) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1);
?>
<?php $this->action->printAction($action);?>
+4 -2
View File
@@ -62,7 +62,9 @@ class custom extends control
if($module == 'story' and $field == 'reviewRules')
{
$this->app->loadConfig($module);
$this->view->reviewRule = zget($this->config->$module, 'reviewRules', '1');
$this->view->reviewRule = zget($this->config->$module, 'reviewRules', '1');
$this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
$this->view->superReviewers = zget($this->config->$module, 'superReviewers', '');
}
if(($module == 'story' or $module == 'testcase') and $field == 'review')
{
@@ -113,7 +115,7 @@ class custom extends control
}
elseif($module == 'story' and $field == 'reviewRules')
{
$data = fixer::input('post')->get();
$data = fixer::input('post')->join('superReviewers', ',')->get();
$this->loadModel('setting')->setItems("system.$module", $data);
}
elseif($module == 'testcase' and $field == 'review')
+1
View File
@@ -48,6 +48,7 @@ $lang->custom->reviewRule = 'Review Rules';
$lang->custom->switch = "Switch";
$lang->custom->oneUnit = "One {$lang->hourCommon}";
$lang->custom->convertRelationTitle = "Please set the conversion factor of {$lang->hourCommon} to %s first";
$lang->custom->superReviewers = "Super Reviewer";
if($config->systemMode == 'new') $lang->custom->execution = 'Execution';
if($config->systemMode == 'classic' || !$config->systemMode) $lang->custom->execution = $lang->executionCommon;
+1
View File
@@ -48,6 +48,7 @@ $lang->custom->reviewRule = "评审规则";
$lang->custom->switch = "切换";
$lang->custom->oneUnit = "一个{$lang->hourCommon}";
$lang->custom->convertRelationTitle = "请先设置{$lang->hourCommon}转换为%s的换算系数";
$lang->custom->superReviewers = "超级评审人";
if($config->systemMode == 'new') $lang->custom->execution = '执行';
if($config->systemMode == 'classic' || !$config->systemMode) $lang->custom->execution = $lang->executionCommon;
+4
View File
@@ -79,6 +79,10 @@ EOT;
<td><?php echo html::radio('reviewRules', $lang->custom->reviewRules, $reviewRule);?></td>
<td></td>
</tr>
<tr>
<th class="thWidth"><?php echo $lang->custom->superReviewers;?></th>
<td><?php echo html::select('superReviewers[]', $users, $superReviewers, "class='form-control chosen' multiple");?></td>
</tr>
<tr>
<td colspan='2' class='text-center'><?php echo html::submitButton();?></td>
</tr>
+10
View File
@@ -905,6 +905,15 @@ class docModel extends model
$account = ',' . $this->app->user->account . ',';
if(isset($object->addedBy) and $object->addedBy == $this->app->user->account) return true;
if(isset($object->users) and strpos(",{$object->users},", $account) !== false) return true;
if($object->project and $object->main)
{
$project = $this->loadModel('project')->getById($object->project);
$projectTeams = $this->loadModel('user')->getTeamMemberPairs($object->project);
$authorizedUsers = $this->user->getProjectAuthedUsers($project, '', $projectTeams, array_flip(explode(",", $project->whitelist)));
if(array_key_exists($this->app->user->account, array_filter($authorizedUsers))) return true;
}
if($object->acl == 'custom')
{
$userGroups = $this->app->user->groups;
@@ -912,6 +921,7 @@ class docModel extends model
{
if(strpos(",$object->groups,", ",$groupID,") !== false) return true;
}
if(strpos(",{$object->users},", $account) !== false) return true;
}
if(strpos($extra, 'notdoc') === false)
+60 -5
View File
@@ -464,12 +464,12 @@ if(!window.kanbanDropRules)
},
bug:
{
'unconfirmed': ['confirmed'],
'confirmed': ['fixing'],
'unconfirmed': ['confirmed', 'fixing', 'fixed'],
'confirmed': ['fixing', 'fixed'],
'fixing': ['fixed'],
'fixed': ['testing'],
'testing': ['tested'],
'tested': ['closed'],
'fixed': ['testing', 'tested', 'fixing'],
'testing': ['tested', 'closed', 'fixing'],
'tested': ['closed', 'fixing'],
'closed': ['fixing'],
},
task:
@@ -529,6 +529,7 @@ function changeCardColType(card, fromColType, toColType, kanbanID)
if(typeof card == 'undefined') return false;
var objectID = card.id;
var showIframe = false;
var moveCard = false;
/* Task lane. */
if(kanbanID == 'task')
@@ -591,6 +592,60 @@ function changeCardColType(card, fromColType, toColType, kanbanID)
showIframe = true;
}
}
else if(toColType == 'fixing')
{
if(fromColType == 'confirmed' || fromColType == 'unconfirmed') moveCard = true;
if((fromColType == 'closed' || fromColType == 'fixed' || fromColType == 'testing' || fromColType == 'tested') && priv.canActivateBug)
{
var link = createLink('bug', 'activate', 'bugID=' + objectID, '', true);
showIframe = true;
}
}
else if(toColType == 'fixed')
{
if(fromColType == 'fixing' || fromColType == 'confirmed' || fromColType == 'unconfirmed')
{
var link = createLink('bug', 'resolve', 'bugID=' + objectID, '', true);
showIframe = true;
}
}
else if(toColType == 'testing')
{
if(fromColType == 'fixed') moveCard = true;
}
else if(toColType == 'tested')
{
if(fromColType == 'fixed' || fromColType == 'testing') moveCard = true;
}
else if(toColType == 'closed')
{
if(fromColType == 'testing' || fromColType == 'tested')
{
var link = createLink('bug', 'close', 'bugID=' + objectID, '', true);
showIframe = true;
}
}
if(moveCard)
{
var colID = card.$col.columnID;
var link = createLink('kanban', 'ajaxMoveCard', 'cardID=' + objectID + '&colID=' + colID + '&toColType=' + toColType + '&execitionID=' + executionID + '&browseType=' + browseType + '&groupBy=' + groupBy);
$.get(link, function(data)
{
if(data)
{
kanbanGroup = $.parseJSON(data);
if(groupBy == 'default')
{
updateKanban('bug', kanbanGroup.bug);
}
else
{
updateKanban(browseType, kanbanGroup[groupBy]);
}
}
})
}
}
/* Story lane. */
+3 -2
View File
@@ -148,14 +148,15 @@ js::set('priv',
'canLinkStoryByPlane' => $canLinkStoryByPlane,
'canAssignTask' => common::hasPriv('task', 'assignto'),
'canAssignStory' => common::hasPriv('story', 'assignto'),
'canAssignBug' => common::hasPriv('bug', 'assignto'),
'canFinishTask' => common::hasPriv('task', 'finish'),
'canPauseTask' => common::hasPriv('task', 'pause'),
'canCancelTask' => common::hasPriv('task', 'cancel'),
'canCloseTask' => common::hasPriv('task', 'close'),
'canActivateTask' => common::hasPriv('task', 'activate'),
'canStartTask' => common::hasPriv('task', 'start'),
'canConfirmBug' => common::hasPriv('bug', 'confirmBug')
'canAssignBug' => common::hasPriv('bug', 'assignto'),
'canConfirmBug' => common::hasPriv('bug', 'confirmBug'),
'canActivateBug' => common::hasPriv('bug', 'activate')
)
);
?>
+14 -9
View File
@@ -476,15 +476,16 @@ $lang->product->methodOrder[105] = 'unbindWhitelist';
/* Branch. */
$lang->resource->branch = new stdclass();
$lang->resource->branch->manage = 'manage';
$lang->resource->branch->create = 'createAction';
$lang->resource->branch->edit = 'editAction';
$lang->resource->branch->close = 'closeAction';
$lang->resource->branch->activate = 'activateAction';
$lang->resource->branch->sort = 'sort';
$lang->resource->branch->delete = 'delete';
$lang->resource->branch->batchEdit = 'batchEdit';
$lang->resource->branch->setDefault = 'setDefaultAction';
$lang->resource->branch->manage = 'manage';
$lang->resource->branch->create = 'createAction';
$lang->resource->branch->edit = 'editAction';
$lang->resource->branch->close = 'closeAction';
$lang->resource->branch->activate = 'activateAction';
$lang->resource->branch->sort = 'sort';
$lang->resource->branch->delete = 'delete';
$lang->resource->branch->batchEdit = 'batchEdit';
$lang->resource->branch->setDefault = 'setDefaultAction';
$lang->resource->branch->mergeBranch = 'mergeBranchAction';
$lang->branch->methodOrder[0] = 'manage';
$lang->branch->methodOrder[5] = 'create';
@@ -494,6 +495,8 @@ $lang->branch->methodOrder[20] = 'activate';
$lang->branch->methodOrder[25] = 'sort';
$lang->branch->methodOrder[30] = 'delete';
$lang->branch->methodOrder[35] = 'batchEdit';
$lang->branch->methodOrder[40] = 'setDefault';
$lang->branch->methodOrder[45] = 'mergeBranch';
/* Story. */
$lang->resource->story = new stdclass();
@@ -632,6 +635,7 @@ $lang->resource->kanban->delete = 'delete';
$lang->resource->kanban->createRegion = 'createRegion';
$lang->resource->kanban->editRegion = 'editRegion';
$lang->resource->kanban->sortRegion = 'sortRegion';
$lang->resource->kanban->sortGroup = 'sortGroup';
$lang->resource->kanban->deleteRegion = 'deleteRegion';
$lang->resource->kanban->createLane = 'createLane';
$lang->resource->kanban->setLane = 'setLane';
@@ -671,6 +675,7 @@ $lang->kanban->methodOrder[55] = 'delete';
$lang->kanban->methodOrder[60] = 'createRegion';
$lang->kanban->methodOrder[65] = 'editRegion';
$lang->kanban->methodOrder[70] = 'sortRegion';
$lang->kanban->methodOrder[72] = 'sortGroup';
$lang->kanban->methodOrder[75] = 'deleteRegion';
$lang->kanban->methodOrder[80] = 'createLane';
$lang->kanban->methodOrder[85] = 'setLane';
+19
View File
@@ -371,6 +371,25 @@ class kanban extends control
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess));
}
/**
* Sort group.
*
* @param int $region
* @param int $groups
* @access public
* @return void
*/
public function sortGroup($region, $groups)
{
$groups = array_filter(explode(',', trim($groups, ',')));
if(empty($groups)) return $this->send(array('result' => 'fail', 'message' => 'No groups to sort.'));
$this->kanban->sortGroup($region, $groups);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success'));
}
/**
* Delete a region
*
+4
View File
@@ -15,6 +15,10 @@
.kanbans .panel-heading {padding: 12px 24px 10px 16px;}
.kanbans .kanban-name {overflow: hidden; white-space:nowrap}
.kanbans .panel .label-closed {background: #AAA !important; color: #FFF; margin-top:-1px;}
.kanbans .kanban-actions {float: right; display: none;}
.kanbans .kanban-actions .dropdown-menu.pull-right {top:31px; right: -84px; left: auto;}
.kanbans .kanban-actions .dropdown-menu.pull-left {top:31px; right:-1px; left: auto;}
.kanbans .panel .label-closed {background: #AAA !important; color: #FFF; margin-top:-6px;}
.kanbans .panel-body {padding: 0 16px 16px;}
.kanbans .kanban-desc {color: #838a9d; word-break:break-all; height: 70px; overflow: hidden; display: -webkit-box;}
.kanbans .kanban-footer {position: absolute; bottom: 10px; right: 10px; left: 15px;}
+3 -2
View File
@@ -14,6 +14,8 @@ body {background-color: #fff}
.dropdown-menu > li {padding: 0 10px;}
.dropdown-menu > li > a > .icon {position: relative; left: -5px;}
.scrollbar-hover {max-height: 2000px; overflow: scroll !important;}
.region {border: 1px solid #dcdcdc; margin-bottom: 20px;}
.region .region-header {padding: 10px;}
.region .region-header span {font-size: 14px; color: #999EAB}
@@ -83,8 +85,7 @@ body {background-color: #fff}
.region .kanban-item .has-color .info > .label-pri {border-color: #FFFFFF;}
.region .kanban-item .item-more {position: absolute; right: 6px; top: 0px;}
.kanban-card {height: 60px !important}
.kanban-card > .title {display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis}
.kanban-card {height: auto !important}
.kanban-card > .info {position: relative; margin-top: 5px}
.kanban-card > .info > .info + .info {margin-left: 8px}
.kanban-card:hover > .actions {opacity: 1;}
+14 -1
View File
@@ -2,8 +2,21 @@
var $kanbans = $('.kanbans');
$kanbans.on('click', '.panel', function(e)
{
if(!$(e.target).closest('.panel-actions').length)
if(!$(e.target).closest('.kanban-actions').length)
{
window.location.href = $(this).data('url');
}
});
/* Display drop-down menu.*/
$('.panel').mouseenter(function(e)
{
$('.kanban-actions' + e.currentTarget.parentElement.dataset.id).css('display','block');
});
/* Hide drop-down menu. */
$('.panel').mouseleave(function(e)
{
$('.kanban-actions').css('display','none');
$('.dropdown').removeClass('open');
});
+19 -17
View File
@@ -30,18 +30,19 @@ function fullScreen()
var element = document.getElementById('kanban');
var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen;
$('.region-header .action').attr('disabled', true);
if(requestMethod)
{
var afterEnterFullscreen = function()
{
$('#kanban').addClass('scrollbar-hover');
$('#kanban').css('background', '#fff');
$('.actions').hide();
$.cookie('isFullScreen', 1);
};
var whenFailEnterFullscreen = function(error)
{
$.cookie('isFullScreen', 0);
exitFullScreen();
};
try
@@ -72,7 +73,7 @@ function fullScreen()
function exitFullScreen()
{
$('#mainContent').removeClass('scrollbar-hover');
$('.region-header .action').attr('disabled', false);
$('.actions').show();
$.cookie('isFullScreen', 0);
}
@@ -330,7 +331,7 @@ function renderKanbanItem(item, $item)
$user.html(renderUsersAvatar(assignedTo, item.id)).attr('title', title);
$item.css('background-color', item.color);
$item.toggleClass('has-color', item.color != '#fff');
$item.toggleClass('has-color', item.color != '#fff' && item.color != '');
$item.find('.info > .label-light').css('background-color', item.color);
}
@@ -694,7 +695,7 @@ function createLaneMenu(options)
var items = [];
if(privs.includes('setLane')) items.push({label: kanbanLang.setLane, icon: 'edit', url: createLink('kanban', 'setLane', 'laneID=' + lane.id + '&executionID=0&from=kanban'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '635px'}});
if(privs.includes('deleteLane')) items.push({label: kanbanLang.deleteLane, icon: 'trash', url: createLink('kanban', 'deleteLane', 'lane=' + lane.id), className: 'confirmer', attrs: {'data-confirmTitle': kanbanlaneLang.confirmDelete, 'data-confirmDetail': kanbanlaneLang.confirmDeleteDetail, 'target': 'hiddenwin'}});
if(privs.includes('deleteLane')) items.push({label: kanbanLang.deleteLane, icon: 'trash', url: createLink('kanban', 'deleteLane', 'lane=' + lane.id), attrs: {'target': 'hiddenwin'}});
var bounds = options.$trigger[0].getBoundingClientRect();
items.$options = {x: bounds.right, y: bounds.top};
@@ -716,22 +717,23 @@ function createCardMenu(options)
var items = [];
if(privs.includes('editCard')) items.push({label: kanbanLang.editCard, icon: 'edit', url: createLink('kanban', 'editCard', 'cardID=' + card.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal', 'data-width': '80%'}});
if(privs.includes('archiveCard') && kanban.archived == '0') items.push({label: kanbanLang.archiveCard, icon: 'card-archive', url: createLink('kanban', 'archiveCard', 'cardID=' + card.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
if(privs.includes('archiveCard') && kanban.archived == '1') items.push({label: kanbanLang.archiveCard, icon: 'card-archive', url: createLink('kanban', 'archiveCard', 'cardID=' + card.id), attrs: {'target': 'hiddenwin'}});
if(privs.includes('copyCard')) items.push({label: kanbanLang.copyCard, icon: 'copy', url: createLink('kanban', 'copyCard', 'cardID=' + card.id, '', 'true'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
if(privs.includes('deleteCard')) items.push({label: kanbanLang.deleteCard, icon: 'trash', url: createLink('kanban', 'deleteCard', 'cardID=' + card.id), className: 'confirmer', attrs: {'data-confirmTitle': kanbancolumnLang.confirmDelete, 'data-confirmDetail': kanbancolumnLang.confirmDeleteDetail, 'target': 'hiddenwin'}});
if(privs.includes('deleteCard')) items.push({label: kanbanLang.deleteCard, icon: 'trash', url: createLink('kanban', 'deleteCard', 'cardID=' + card.id), attrs: {'target': 'hiddenwin'}});
if(privs.includes('moveCard'))
{
var moveCardItems = [];
var moveColumns = [];
regions[options.$trigger.closest('.region').data('id')].groups.forEach(function(group)
{
if(group.id == options.$trigger.closest('.kanban-board').data('id'))
{
moveColumns = group.columns;
return;
}
});
var parentColumns = [];
var regionGroups = regions[options.$trigger.closest('.region').data('id')].groups;
for(let i = 0; i < regionGroups.length ; i ++ )
{
if(regionGroups[i].id == options.$trigger.closest('.kanban-board').data('id'))
{
moveColumns = regionGroups[i].columns;
break;
}
}
for(let i = moveColumns.length-1 ; i >= 0 ; i -- )
{
if(moveColumns[i].id == card.column || $.inArray(moveColumns[i].id, parentColumns) >= 0) continue;
@@ -777,8 +779,8 @@ function createColumnMenu(options)
items.push({label: kanbanLang.createColumnOnRight, icon: 'col-add-right', url: createLink('kanban', 'createColumn', 'columnID=' + column.id + '&position=right'), className: 'iframe', attrs: {'data-toggle': 'modal'}});
}
if(privs.includes('copyColumn')) items.push({label: kanbanLang.copyColumn, icon: 'copy', url: createLink('kanban', 'copyColumn', 'columnID=' + column.id), className: 'iframe', attrs: {'data-toggle': 'modal'}});
if(privs.includes('archiveColumn') && kanban.archived == '0') items.push({label: kanbanLang.archiveColumn, icon: 'card-archive', url: createLink('kanban', 'archiveColumn', 'columnID=' + column.id), className: 'confirmer', attrs: {'data-confirmTitle': kanbancolumnLang.confirmArchive, 'data-confirmDetail': kanbancolumnLang.confirmArchiveDetail, 'data-confirmButton': lang.archive, 'data-confirming': lang.archiving}});
if(privs.includes('deleteColumn')) items.push({label: kanbanLang.deleteColumn, icon: 'trash', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), className: 'confirmer', attrs: {'data-confirmTitle': kanbancolumnLang.confirmDelete, 'data-confirmDetail': kanbancolumnLang.confirmDeleteDetail, 'target': 'hiddenwin'}});
if(privs.includes('archiveColumn') && kanban.archived == '1') items.push({label: kanbanLang.archiveColumn, icon: 'card-archive', url: createLink('kanban', 'archiveColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}});
if(privs.includes('deleteColumn')) items.push({label: kanbanLang.deleteColumn, icon: 'trash', url: createLink('kanban', 'deleteColumn', 'columnID=' + column.id), attrs: {'target': 'hiddenwin'}});
var bounds = options.$trigger[0].getBoundingClientRect();
items.$options = {x: bounds.right, y: bounds.top};
+3 -2
View File
@@ -13,6 +13,7 @@ $lang->kanban->delete = 'Delete Kanban';
$lang->kanban->createRegion = 'Create Region';
$lang->kanban->editRegion = 'Edit Region';
$lang->kanban->sortRegion = 'Sort Region';
$lang->kanban->sortGroup = 'Sort Group';
$lang->kanban->deleteRegion = 'Delete Region';
$lang->kanban->createLane = 'Create Lane';
$lang->kanban->editLane = 'Edit Lane';
@@ -84,8 +85,8 @@ $lang->kanban->aclGroup['private'] = 'Private';
$lang->kanban->aclList['extend'] = 'Extend (Accessible with space view permissions)';
$lang->kanban->aclList['private'] = 'Private (For the kanban team, whitelist members and space owner only)';
$lang->kanban->enableArchived['0'] = 'Enable';
$lang->kanban->enableArchived['1'] = 'No Enable';
$lang->kanban->enableArchived['0'] = 'No Enable';
$lang->kanban->enableArchived['1'] = 'Enable';
$lang->kanban->type = array();
$lang->kanban->type['all'] = "All KanBan";
+3 -2
View File
@@ -13,6 +13,7 @@ $lang->kanban->delete = '删除看板';
$lang->kanban->createRegion = '创建区域';
$lang->kanban->editRegion = '编辑区域';
$lang->kanban->sortRegion = '区域排序';
$lang->kanban->sortGroup = '泳道组排序';
$lang->kanban->deleteRegion = '删除区域';
$lang->kanban->createLane = '创建泳道';
$lang->kanban->editLane = '泳道设置';
@@ -84,8 +85,8 @@ $lang->kanban->aclList['private'] = '私有(看板团队成员、白名单、
$lang->kanban->aclGroup['open'] = '公开';
$lang->kanban->aclGroup['private'] = '私有';
$lang->kanban->enableArchived['0'] = '启用';
$lang->kanban->enableArchived['1'] = '不启用';
$lang->kanban->enableArchived['0'] = '不启用';
$lang->kanban->enableArchived['1'] = '启用';
$lang->kanban->type = array();
$lang->kanban->type['all'] = "综合看板";
+66 -13
View File
@@ -394,7 +394,7 @@ class kanbanModel extends model
return false;
}
if($this->post->begin > $this->post->end)
if($this->post->end && $this->post->begin > $this->post->end)
{
dao::$errors[] = $this->lang->kanbancard->error->endSmall;
return false;
@@ -543,7 +543,7 @@ class kanbanModel extends model
{
return $this->dao->select('*')->from(TABLE_KANBANGROUP)
->where('region')->in($regions)
->orderBy('id_asc')
->orderBy('order')
->fetchGroup('region', 'id');
}
@@ -1460,6 +1460,11 @@ class kanbanModel extends model
{
foreach($this->config->kanban->storyColumnStageList as $colType => $stage)
{
if($story->stage != $stage and strpos($cardPairs[$colType], ",$storyID,") !== false)
{
$cardPairs[$colType] = str_replace(",$storyID,", ',', $cardPairs[$colType]);
}
if(strpos(',ready,develop,test,', $colType) !== false) continue;
if($lane->groupby and $story->{$lane->groupby} != $lane->extra)
@@ -1474,10 +1479,6 @@ class kanbanModel extends model
{
$cardPairs[$colType] = empty($cardPairs[$colType]) ? ",$storyID," : ",$storyID" . $cardPairs[$colType];
}
elseif($story->stage != $stage and strpos($cardPairs[$colType], ",$storyID,") !== false)
{
$cardPairs[$colType] = str_replace(",$storyID,", ',', $cardPairs[$colType]);
}
}
}
}
@@ -1488,34 +1489,43 @@ class kanbanModel extends model
{
foreach($this->config->kanban->bugColumnStatusList as $colType => $status)
{
if(strpos(',resolving,fixing,test,testing,tested,', $colType) !== false) continue;
if($bug->status != $status and strpos($cardPairs[$colType], ",$bugID,") !== false)
{
$cardPairs[$colType] = str_replace(",$bugID,", ',', $cardPairs[$colType]);
}
if(strpos(',resolving,test,testing,tested,', $colType) !== false) continue;
if($lane->groupby and $bug->{$lane->groupby} != $lane->extra)
{
$cardPairs[$colType] = str_replace(",$bugID,", ',', $cardPairs[$colType]);
}
elseif($colType == 'unconfirmed' and $bug->status == $status and $bug->confirmed == 0 and strpos($cardPairs['unconfirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false)
elseif($colType == 'unconfirmed' and $bug->status == $status and $bug->confirmed == 0 and strpos($cardPairs['unconfirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false and $bug->activatedCount == 0)
{
$cardPairs['unconfirmed'] = empty($cardPairs['unconfirmed']) ? ",$bugID," : ",$bugID" . $cardPairs['unconfirmed'];
if(strpos($cardPairs['closed'], ",$bugID,") !== false) $cardPairs['closed'] = str_replace(",$bugID,", ',', $cardPairs['closed']);
}
elseif($colType == 'confirmed' and $bug->status == $status and $bug->confirmed == 1 and strpos($cardPairs['confirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false)
elseif($colType == 'confirmed' and $bug->status == $status and $bug->confirmed == 1 and strpos($cardPairs['confirmed'], ",$bugID,") === false and strpos($cardPairs['fixing'], ",$bugID,") === false and $bug->activatedCount == 0)
{
$cardPairs['confirmed'] = empty($cardPairs['confirmed']) ? ",$bugID," : ",$bugID" . $cardPairs['confirmed'];
if(strpos($cardPairs['unconfirmed'], ",$bugID,") !== false) $cardPairs['unconfirmed'] = str_replace(",$bugID,", ',', $cardPairs['unconfirmed']);
}
elseif($colType == 'fixing' and $bug->status == $status and $bug->activatedCount > 0 and strpos($cardPairs['fixing'], ",$bugID,") === false)
{
$cardPairs['fixing'] = empty($cardPairs['fixing']) ? ",$bugID," : ",$bugID" . $cardPairs['fixing'];
if(strpos($cardPairs['confirmed'], ",$bugID,") !== false) $cardPairs['confirmed'] = str_replace(",$bugID,", ',', $cardPairs['confirmed']);
if(strpos($cardPairs['unconfirmed'], ",$bugID,") !== false) $cardPairs['unconfirmed'] = str_replace(",$bugID,", ',', $cardPairs['unconfirmed']);
}
elseif($colType == 'fixed' and $bug->status == $status and strpos($cardPairs['fixed'], ",$bugID,") === false and strpos($cardPairs['testing'], ",$bugID,") === false and strpos($cardPairs['tested'], ",$bugID,") === false)
{
$cardPairs['fixed'] = empty($cardPairs['fixed']) ? ",$bugID," : ",$bugID" . $cardPairs['fixed'];
if(strpos($cardPairs['testing'], ",$bugID,") !== false) $cardPairs['testing'] = str_replace(",$bugID,", ',', $cardPairs['testing']);
if(strpos($cardPairs['tested'], ",$bugID,") !== false) $cardPairs['tested'] = str_replace(",$bugID,", ',', $cardPairs['tested']);
}
elseif($colType == 'closed' and $bug->status == 'closed' and strpos($cardPairs[$colType], ",$bugID,") === false)
{
$cardPairs[$colType] = empty($cardPairs[$colType]) ? ",$bugID," : ",$bugID". $cardPairs[$colType];
}
elseif($bug->status != $status and strpos($cardPairs[$colType], ",$bugID,") !== false)
{
$cardPairs[$colType] = str_replace(",$bugID,", ',', $cardPairs[$colType]);
}
}
}
}
@@ -1898,6 +1908,34 @@ class kanbanModel extends model
$this->lang->switcherMenu = $switcher;
}
/**
* Sort kanban group.
*
* @param int $region
* @param array $groups
* @access public
* @return bool
*/
public function sortGroup($region, $groups)
{
$this->loadModel('action');
$groupList = $this->getGroupList($region);
$order = 1;
foreach($groups as $groupID)
{
if(!$groupID) continue;
if(!isset($groupList[$groupID])) continue;
$this->dao->update(TABLE_KANBANGROUP)->set('`order`')->eq($order)->where('id')->eq($groupID)->exec();
$order++;
}
return !dao::isError();
}
/**
* Move a card.
*
@@ -2044,6 +2082,21 @@ class kanbanModel extends model
->fetchGroup('space', 'id');
}
/**
* Get group list by region.
*
* @param int $region
* @access public
* @return array
*/
public function getGroupList($region)
{
return $this->dao->select('*')->from(TABLE_KANBANGROUP)
->where('region')->eq($region)
->orderBy('order')
->fetchAll('id');
}
/**
* Get column by id.
*
+32
View File
@@ -32,6 +32,7 @@
</div>
<?php else:?>
<?php foreach($spaceList as $space):?>
<?php $kanbanCount = 1;?>
<div class='row cell' id='spaceList'>
<div class='space'>
<div class='row menu'>
@@ -66,6 +67,37 @@
<?php endif;?>
<strong title='<?php echo $kanban->name;?>'><?php echo $kanban->name;?></strong>
</div>
<?php $canActions = (common::hasPriv('kanban','edit') or common::hasPriv('kanban','close') or common::hasPriv('kanban','delete'));?>
<?php if($canActions):?>
<div class='kanban-actions kanban-actions<?php echo $kanbanID;?>'>
<div class='dropdown'>
<?php echo html::a('javascript:;', "<i class='icon icon-ellipsis-v'></i>", '', "data-toggle='dropdown' class='btn btn-link'");?>
<ul class='dropdown-menu <?php echo $kanbanCount % 4 == 0 ? 'pull-left' : 'pull-right';?>'>
<?php
if(common::hasPriv('kanban','edit'))
{
echo '<li>';
common::printLink('kanban', 'edit', "kanbanID={$kanban->id}", '<i class="icon icon-edit"></i> ' . $lang->kanban->edit, '', "class='iframe' data-width='75%'", '', true);
echo '</li>';
}
if(common::hasPriv('kanban','close'))
{
echo '<li>';
common::printLink('kanban', 'close', "kanbanID={$kanban->id}", '<i class="icon icon-off"></i> ' . $lang->kanban->close, '', "class='iframe' data-width='75%'", '', true);
echo '</li>';
}
if(common::hasPriv('kanban','delete'))
{
echo '<li>';
common::printLink('kanban', 'delete', "kanbanID={$kanban->id}", '<i class="icon icon-trash"></i> ' . $lang->kanban->delete, '', "class='iframe' data-width='75%'", '', true);
echo '</li>';
}
?>
</ul>
</div>
</div>
<?php endif;?>
<?php $kanbanCount ++;?>
</div>
<div class='panel-body'>
<div class='kanban-desc' title="<?php echo strip_tags(htmlspecialchars_decode($kanban->desc));?>"><?php echo strip_tags(htmlspecialchars_decode($kanban->desc));?></div>
+2 -2
View File
@@ -37,13 +37,13 @@
<?php if(!$card->deleted):?>
<?php
common::printLink('kanban', 'assigntoCard', "cardID=$card->id", "<i class='icon icon-hand-right'></i><span class='text'>{$lang->kanbancard->assign}</span>", '', "class='btn btn-link iframe' title='{$lang->kanbancard->assign}'", true, true);
common::printLink('kanban', 'archiveCard', "cardID=$card->id", "<i class='icon icon-ban-circle'></i><span class='text'>{$lang->kanbancard->archive}</span>", '', "class='btn btn-link iframe' title='{$lang->kanbancard->archive}'", true, true);
common::printLink('kanban', 'archiveCard', "cardID=$card->id", "<i class='icon icon-ban-circle'></i><span class='text'>{$lang->kanbancard->archive}</span>", 'hiddenwin', "class='btn btn-link' title='{$lang->kanbancard->archive}'", true, true);
echo "<div class='divider'></div>";
common::printLink('kanban', 'editCard', "cardID=$card->id", '<i class="icon icon-edit"></i>', '', "class='btn btn-link iframe' data-width='80%' title='{$lang->kanbancard->edit}'", true, true);
common::printLink('kanban', 'copyCard', "cardID=$card->id", '<i class="icon icon-copy"></i>', '', "class='btn btn-link iframe' title='{$lang->kanbancard->copy}'", true, true);
common::printLink('kanban', 'deleteCard', "cardID=$card->id", '<i class="icon icon-trash"></i>', '', "class='btn btn-link iframe' title='{$lang->kanbancard->delete}'",true, true);
common::printLink('kanban', 'deleteCard', "cardID=$card->id", '<i class="icon icon-trash"></i>', 'hiddenwin', "class='btn btn-link' title='{$lang->kanbancard->delete}'",true, true);
?>
<?php endif;?>
</div>
+2 -2
View File
@@ -134,7 +134,8 @@ class product extends control
public function browse($productID = 0, $branch = '', $browseType = '', $param = 0, $storyType = 'story', $orderBy = '', $recTotal = 0, $recPerPage = 20, $pageID = 1, $projectID = 0)
{
$productID = $this->app->tab != 'project' ? $this->product->saveState($productID, $this->products) : $productID;
$branch = ($this->cookie->preBranch !== '' and $branch === '') ? $this->cookie->preBranch : $branch;
$branches = $this->loadModel('branch')->getList($productID, $projectID, 'all');
$branch = ($this->cookie->preBranch !== '' and $branch === '' and isset($branches[$this->cookie->preBranch])) ? $this->cookie->preBranch : $branch;
$branchID = $branch;
/* Set menu. */
@@ -244,7 +245,6 @@ class product extends control
}
/* Display status of branch. */
$branches = $this->loadModel('branch')->getList($productID, $projectID, 'all');
$branchOption = array();
$branchTagOption = array();
foreach($branches as $branchInfo)
+2
View File
@@ -130,6 +130,7 @@ $lang->product->searchStory = 'Search';
$lang->product->assignedToMe = 'AssignedToMe';
$lang->product->openedByMe = 'CreatedByMe';
$lang->product->reviewedByMe = 'ReviewedByMe';
$lang->product->reviewByMe = 'ReviewByMe';
$lang->product->closedByMe = 'ClosedByMe';
$lang->product->draftStory = 'Draft';
$lang->product->activeStory = 'Activated';
@@ -202,6 +203,7 @@ $lang->product->featureBar['browse']['unclosed'] = $lang->product->unclosed;
$lang->product->featureBar['browse']['assignedtome'] = $lang->product->assignedToMe;
$lang->product->featureBar['browse']['openedbyme'] = $lang->product->openedByMe;
$lang->product->featureBar['browse']['reviewedbyme'] = $lang->product->reviewedByMe;
$lang->product->featureBar['browse']['reviewbyme'] = $lang->product->reviewByMe;
$lang->product->featureBar['browse']['draftstory'] = $lang->product->draftStory;
$lang->product->featureBar['browse']['more'] = $lang->more;
+2
View File
@@ -130,6 +130,7 @@ $lang->product->searchStory = '搜索';
$lang->product->assignedToMe = '指给我';
$lang->product->openedByMe = '我创建';
$lang->product->reviewedByMe = '我评审';
$lang->product->reviewByMe = '待我评审';
$lang->product->closedByMe = '我关闭';
$lang->product->draftStory = '草稿';
$lang->product->activeStory = '激活';
@@ -202,6 +203,7 @@ $lang->product->featureBar['browse']['unclosed'] = $lang->product->unclosed;
$lang->product->featureBar['browse']['assignedtome'] = $lang->product->assignedToMe;
$lang->product->featureBar['browse']['openedbyme'] = $lang->product->openedByMe;
$lang->product->featureBar['browse']['reviewedbyme'] = $lang->product->reviewedByMe;
$lang->product->featureBar['browse']['reviewbyme'] = $lang->product->reviewByMe;
$lang->product->featureBar['browse']['draftstory'] = $lang->product->draftStory;
$lang->product->featureBar['browse']['more'] = $lang->more;
+1
View File
@@ -879,6 +879,7 @@ class productModel extends model
if($browseType == 'assignedtome') $stories = $this->story->getByAssignedTo($productID, $branch, $modules, $this->app->user->account, $type, $sort, $pager);
if($browseType == 'openedbyme') $stories = $this->story->getByOpenedBy($productID, $branch, $modules, $this->app->user->account, $type, $sort, $pager);
if($browseType == 'reviewedbyme') $stories = $this->story->getByReviewedBy($productID, $branch, $modules, $this->app->user->account, $type, $sort, $pager);
if($browseType == 'reviewbyme') $stories = $this->story->getByReviewBy($productID, $branch, $modules, $this->app->user->account, $type, $sort, $pager);
if($browseType == 'closedbyme') $stories = $this->story->getByClosedBy($productID, $branch, $modules, $this->app->user->account, $type, $sort, $pager);
if($browseType == 'draftstory') $stories = $this->story->getByStatus($productID, $branch, $modules, 'draft', $type, $sort, $pager);
if($browseType == 'activestory') $stories = $this->story->getByStatus($productID, $branch, $modules, 'active', $type, $sort, $pager);
+2 -1
View File
@@ -45,7 +45,8 @@
<td class='text-center'><?php echo $plan->id . html::hidden("id[$plan->id]", $plan->id);?></td>
<?php if($branchProduct):?>
<td class='text-left'>
<?php echo html::select("branch[$plan->id]", $branches, $plan->branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control chosen' ");?>
<?php $disabled = $plan->parent == '-1' ? "disabled='disabled'" : '';?>
<?php echo html::select("branch[$plan->id]", $plan->parent == '-1' ? '' : $branches, $plan->branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control chosen' $disabled");?>
</td>
<?php endif;?>
<td title='<?php echo $plan->title?>'><?php echo html::input("title[$plan->id]", $plan->title, "class='form-control'")?></td>
+1 -1
View File
@@ -115,7 +115,7 @@
?>
</td>
<?php if($this->session->currentProductType != 'normal'):?>
<td class='c-branch' title='<?php echo $branches[$plan->branch];?>'><?php echo $branches[$plan->branch];?></td>
<td class='c-branch' title='<?php echo $branches[$plan->branch];?>'><?php if($plan->parent != '-1') echo $branches[$plan->branch];?></td>
<?php endif;?>
<td><?php echo $plan->begin == '2030-01-01' ? $lang->productplan->future : $plan->begin;?></td>
<td><?php echo $plan->end == '2030-01-01' ? $lang->productplan->future : $plan->end;?></td>
+1 -1
View File
@@ -28,7 +28,7 @@
<th><?php echo $lang->productplan->product;?></th>
<td class='muted'><?php echo $product->name;?></td><td></td><td></td>
</tr>
<?php if($product->type != 'normal'):?>
<?php if($product->type != 'normal' and $plan->parent != '-1'):?>
<tr>
<th><?php echo $lang->product->branch;?></th>
<td><?php echo html::select('branch', $branches, $plan->branch, "onchange='getConflictStories($plan->id, this.value); 'class='form-control'");?></td><td></td><td></td>
+2 -1
View File
@@ -103,6 +103,7 @@ class projectrelease extends control
$this->loadModel('build');
$this->app->loadConfig('release');
$this->config->projectrelease->create = $this->config->release->create;
$this->app->loadLang('release');
if(!empty($_POST))
{
@@ -126,10 +127,10 @@ class projectrelease extends control
unset($builds['trunk']);
$this->view->title = $this->view->project->name . $this->lang->colon . $this->lang->release->create;
$this->view->position[] = $this->lang->release->create;
$this->view->builds = $builds;
$this->view->lastRelease = $this->projectrelease->getLast($projectID);
$this->view->users = $this->loadModel('user')->getPairs('noletter|noclosed');
$this->view->confirmLink = $this->lang->release->confirmLink;
$this->display();
}
+11
View File
@@ -0,0 +1,11 @@
$('#submit').click(function()
{
var dateFormat = new RegExp(/^\d{4}\-\d{2}\-\d{2}$/);
var name = $('#name').val();
var date = $('#date').val();
if(name && dateFormat.test(date))
{
var result = confirm(confirmLink) ? true : false;
$('#sync').val(result);
}
});
+11 -2
View File
@@ -134,7 +134,7 @@ class projectreleaseModel extends model
->setIF($productID, 'product', $productID)
->setIF($branch, 'branch', $branch)
->stripTags($this->config->release->editor->create['id'], $this->config->allowedTags)
->remove('allchecker,files,labels,uid')
->remove('allchecker,files,labels,uid,sync')
->get();
/* Auto create build when release is not link build. */
@@ -175,7 +175,16 @@ class projectreleaseModel extends model
}
}
if($release->build) $release->branch = $this->dao->select('branch')->from(TABLE_BUILD)->where('id')->eq($release->build)->fetch('branch');
if($release->build)
{
$buildInfo = $this->dao->select('branch, stories, bugs')->from(TABLE_BUILD)->where('id')->eq($release->build)->fetch();
$release->branch = $buildInfo->branch;
if($this->post->sync == 'true');
{
$release->stories = $buildInfo->stories;
$release->bugs = $buildInfo->bugs;
}
}
$release = $this->loadModel('file')->processImgURL($release, $this->config->release->editor->create['id'], $this->post->uid);
$this->dao->insert(TABLE_RELEASE)->data($release)
@@ -12,6 +12,7 @@
?>
<?php include '../../common/view/header.html.php';?>
<?php include '../../common/view/kindeditor.html.php';?>
<?php js::set('confirmLink', $confirmLink);?>
<div id='mainContent' class='main-content'>
<div class='center-block'>
<div class='main-header'>
@@ -74,6 +75,7 @@
<tr>
<td colspan='3' class='text-center form-actions'>
<?php echo html::submitButton();?>
<?php echo html::hidden('sync', 'false');?>
<?php echo html::backButton();?>
</td>
</tr>
+1
View File
@@ -73,6 +73,7 @@ class release extends control
if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $releaseID));
if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.loadProductBuilds($productID)"));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('view', "releaseID=$releaseID")));
}
+11
View File
@@ -0,0 +1,11 @@
$('#submit').click(function()
{
var dateFormat = new RegExp(/^\d{4}\-\d{2}\-\d{2}$/);
var name = $('#name').val();
var date = $('#date').val();
if(name && dateFormat.test(date))
{
var result = confirm(confirmLink) ? true : false;
$('#sync').val(result);
}
});
+1
View File
@@ -24,6 +24,7 @@ $lang->release->batchUnlinkStory = "Batch Unlink Stories";
$lang->release->batchUnlinkBug = "Batch Unlink Bugs";
$lang->release->confirmDelete = "Do you want to delete this release?";
$lang->release->confirmLink = "Whether to link the stories completed in the version and the bugs solved to the release?";
$lang->release->confirmUnlinkStory = "Do you want to remove this story?";
$lang->release->confirmUnlinkBug = "Do you want to remove this bug?";
$lang->release->existBuild = '『Build』『%s』existed. You could change『name』or choose a『build』.';
+1
View File
@@ -24,6 +24,7 @@ $lang->release->batchUnlinkStory = "批量移除{$lang->SRCommon}";
$lang->release->batchUnlinkBug = "批量移除Bug";
$lang->release->confirmDelete = "您确认删除该发布吗?";
$lang->release->confirmLink = "是否将版本中完成的{$lang->SRCommon}和已解决的bug关联到发布下?";
$lang->release->confirmUnlinkStory = "您确认移除该{$lang->SRCommon}吗?";
$lang->release->confirmUnlinkBug = "您确认移除该Bug吗?";
$lang->release->existBuild = '『版本』已经有『%s』这条记录了。您可以更改『发布名称』或者选择一个『版本』。';
+7 -3
View File
@@ -131,7 +131,7 @@ class releaseModel extends model
->join('mailto', ',')
->setIF($this->post->build == false, 'build', $buildID)
->stripTags($this->config->release->editor->create['id'], $this->config->allowedTags)
->remove('allchecker,files,labels,uid')
->remove('allchecker,files,labels,uid,sync')
->get();
/* Auto create build when release is not link build. */
@@ -174,11 +174,15 @@ class releaseModel extends model
if($release->build)
{
$buildInfo = $this->dao->select('project, branch')->from(TABLE_BUILD)->where('id')->eq($release->build)->fetch();
$buildInfo = $this->dao->select('project, branch, stories, bugs')->from(TABLE_BUILD)->where('id')->eq($release->build)->fetch();
$release->branch = $buildInfo->branch;
$release->project = $buildInfo->project;
if($this->post->sync == 'true')
{
$release->stories = $buildInfo->stories;
$release->bugs = $buildInfo->bugs;
}
}
$release = $this->loadModel('file')->processImgURL($release, $this->config->release->editor->create['id'], $this->post->uid);
$this->dao->insert(TABLE_RELEASE)->data($release)
->autoCheck()
+2
View File
@@ -12,6 +12,7 @@
?>
<?php include '../../common/view/header.html.php';?>
<?php include '../../common/view/kindeditor.html.php';?>
<?php js::set('confirmLink', $lang->release->confirmLink);?>
<div id='mainContent' class='main-content'>
<div class='center-block'>
<div class='main-header'>
@@ -64,6 +65,7 @@
<tr>
<td colspan='3' class='text-center form-actions'>
<?php echo html::submitButton();?>
<?php echo html::hidden('sync', 'false');?>
<?php echo html::backButton();?>
</td>
</tr>
+27 -22
View File
@@ -1024,6 +1024,9 @@ class story extends control
$this->loadModel('qa')->setMenu($products, $story->product);
}
$reviewers = $this->story->getReviewerPairs($storyID, $story->version);
$checkSuperReviewed = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . trim($story->reviewedBy, ',') . ',');
$this->executeHooks($storyID);
$title = "STORY #$story->id $story->title - $product->name";
@@ -1031,28 +1034,30 @@ class story extends control
$position[] = $this->lang->story->common;
$position[] = $this->lang->story->view;
$this->view->title = $title;
$this->view->position = $position;
$this->view->product = $product;
$this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id);
$this->view->plan = $plan;
$this->view->bugs = $bugs;
$this->view->fromBug = $fromBug;
$this->view->cases = $cases;
$this->view->story = $story;
$this->view->track = $this->story->getTrackByID($story->id);
$this->view->users = $this->user->getPairs('noletter');
$this->view->reviewers = $this->story->getReviewerPairs($storyID, $story->version);
$this->view->relations = $this->story->getStoryRelation($story->id, $story->type);
$this->view->executions = $this->execution->getPairs(0, 'all', 'nocode');
$this->view->execution = empty($story->execution) ? array() : $this->dao->findById($story->execution)->from(TABLE_EXECUTION)->fetch();
$this->view->actions = $this->action->getList('story', $storyID);
$this->view->storyModule = $storyModule;
$this->view->modulePath = $modulePath;
$this->view->version = $version == 0 ? $story->version : $version;
$this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('story', $storyID);
$this->view->from = $from;
$this->view->param = $param;
$this->view->title = $title;
$this->view->position = $position;
$this->view->product = $product;
$this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id);
$this->view->plan = $plan;
$this->view->bugs = $bugs;
$this->view->fromBug = $fromBug;
$this->view->cases = $cases;
$this->view->story = $story;
$this->view->track = $this->story->getTrackByID($story->id);
$this->view->users = $this->user->getPairs('noletter');
$this->view->reviewers = $reviewers;
$this->view->relations = $this->story->getStoryRelation($story->id, $story->type);
$this->view->executions = $this->execution->getPairs(0, 'all', 'nocode');
$this->view->execution = empty($story->execution) ? array() : $this->dao->findById($story->execution)->from(TABLE_EXECUTION)->fetch();
$this->view->actions = $this->action->getList('story', $storyID);
$this->view->storyModule = $storyModule;
$this->view->modulePath = $modulePath;
$this->view->version = $version == 0 ? $story->version : $version;
$this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('story', $storyID);
$this->view->from = $from;
$this->view->param = $param;
$this->view->checkSuperReviewed = $checkSuperReviewed !== false ? true : false;
$this->display();
}
+110 -17
View File
@@ -1288,7 +1288,10 @@ class storyModel extends model
$story = $this->updateStoryByReview($storyID, $oldStory, $story);
$this->dao->update(TABLE_STORY)->data($story, 'closedReason')
$isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ',');
$skipFields = $isSuperReviewer !== false ? '' : 'closedReason';
$this->dao->update(TABLE_STORY)->data($story, $skipFields)
->autoCheck()
->batchCheck($this->config->story->review->requiredFields, 'notempty')
->checkIF($this->post->result == 'reject', 'closedReason', 'notempty')
@@ -1312,7 +1315,7 @@ class storyModel extends model
}
if($this->post->result != 'reject') $this->setStage($storyID);
if(isset($story->closedReason)) unset($story->closedReason);
if(isset($story->closedReason) and $isSuperReviewer === false) unset($story->closedReason);
return common::createChanges($oldStory, $story);
}
@@ -1320,10 +1323,12 @@ class storyModel extends model
* Batch review stories.
*
* @param array $storyIdList
* @param string $result
* @param string $reason
* @access public
* @return array
*/
function batchReview($storyIdList, $result, $reason)
public function batchReview($storyIdList, $result, $reason)
{
$now = helper::now();
$actions = array();
@@ -1335,8 +1340,10 @@ class storyModel extends model
foreach($storyIdList as $storyID)
{
$oldStory = $oldStories[$storyID];
$isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ',');
if($oldStory->status != 'draft' and $oldStory->status != 'changed') continue;
if(!in_array($this->app->user->account, array_keys($reviewerList[$storyID]))) continue;
if(!in_array($this->app->user->account, array_keys($reviewerList[$storyID])) and $isSuperReviewer === false) continue;
if(isset($hasResult[$storyID]) and $hasResult[$storyID]->version == $oldStories[$storyID]->version) continue;
$story = new stdClass();
@@ -1350,6 +1357,10 @@ class storyModel extends model
/* Update the story status by review rules. */
$reviewedBy = explode(',', trim($story->reviewedBy, ','));
if($isSuperReviewer !== false)
{
$story = $this->superReview($storyID, $oldStory, $story, $result, $reason);
}
if(!array_diff(array_keys($reviewerList[$storyID]), $reviewedBy))
{
$reviewerPairs = array();
@@ -2312,6 +2323,24 @@ class storyModel extends model
return $this->getByField($productID, $branch, $modules, 'reviewedBy', $account, $type, $orderBy, $pager, 'include');
}
/**
* Get stories which need to review.
*
* @param int $productID
* @param int $branch
* @param string $modules
* @param string $account
* @param string $type requirement|story
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getByReviewBy($productID, $branch, $modules, $account, $type = 'story', $orderBy, $pager)
{
return $this->getByField($productID, $branch, $modules, 'reviewBy', $account, $type, $orderBy, $pager);
}
/**
* Get stories by closedBy.
*
@@ -2378,15 +2407,23 @@ class storyModel extends model
*/
public function getByField($productID, $branch, $modules, $fieldName, $fieldValue, $type = 'story', $orderBy, $pager, $operator = 'equal')
{
if(!$this->loadModel('common')->checkField(TABLE_STORY, $fieldName)) return array();
$stories = $this->dao->select('*')->from(TABLE_STORY)
->where('product')->in($productID)
->andWhere('type')->eq($type)
->andWhere('deleted')->eq(0)
->beginIF($branch != 'all')->andWhere("branch")->eq($branch)->fi()
->beginIF($modules)->andWhere("module")->in($modules)->fi()
->beginIF($operator == 'equal')->andWhere($fieldName)->eq($fieldValue)->fi()
->beginIF($operator == 'include')->andWhere($fieldName)->like("%$fieldValue%")->fi()
if(!$this->loadModel('common')->checkField(TABLE_STORY, $fieldName) and $fieldName != 'reviewBy') return array();
$sql = $this->dao->select('t1.*')->from(TABLE_STORY)->alias('t1');
if($fieldName == 'reviewBy') $sql = $sql->leftJoin(TABLE_STORYREVIEW)->alias('t2')->on('t1.id = t2.story and t1.version = t2.version');
$stories = $sql->where('t1.product')->in($productID)
->andWhere('t1.deleted')->eq(0)
->andWhere('t1.type')->eq($type)
->beginIF($branch != 'all')->andWhere("t1.branch")->eq($branch)->fi()
->beginIF($modules)->andWhere("t1.module")->in($modules)->fi()
->beginIF($operator == 'equal' and $fieldName != 'reviewBy')->andWhere('t1.' . $fieldName)->eq($fieldValue)->fi()
->beginIF($operator == 'include' and $fieldName != 'reviewBy')->andWhere('t1.' . $fieldName)->like("%$fieldValue%")->fi()
->beginIF($fieldName == 'reviewBy')
->andWhere('t2.reviewer')->eq($this->app->user->account)
->andWhere('t2.result')->eq('')
->andWhere('t1.status')->in('draft,changed')
->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id');
@@ -3461,13 +3498,15 @@ class storyModel extends model
if($story->parent < 0 and $action != 'edit' and $action != 'batchcreate' and $action != 'change') return false;
global $app;
global $app, $config;
$story->reviewer = isset($story->reviewer) ? $story->reviewer : array();
$story->notReview = isset($story->notReview) ? $story->notReview : array();
if($action == 'change') return (count($story->reviewer) == 0 || count($story->notReview) == 0) and $story->status != 'closed';
if($action == 'review') return in_array($app->user->account, $story->notReview) and ($story->status == 'draft' or $story->status == 'changed');
$isSuperReviewer = strpos(',' . trim(zget($config->story, 'superReviewers', ''), ',') . ',', ',' . $app->user->account . ',');
if($action == 'change') return ($isSuperReviewer !== false or count($story->reviewer) == 0 or count($story->notReview) == 0) and $story->status != 'closed';
if($action == 'review') return (($isSuperReviewer !== false or in_array($app->user->account, $story->notReview)) and ($story->status == 'draft' or $story->status == 'changed'));
if($action == 'recall') return empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer);
if($action == 'close') return $story->status != 'closed';
if($action == 'activate') return $story->status == 'closed';
@@ -4567,11 +4606,20 @@ class storyModel extends model
*/
public function recordReviewAction($story, $result = '', $reason = '')
{
$isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ',');
$comment = isset($_POST['comment']) ? $this->post->comment : '';
if($isSuperReviewer !== false and $this->app->rawMethod != 'edit')
{
$actionID = $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . '|superReviewer');
return $actionID;
}
$reasonParam = $result == 'reject' ? ',' . $reason : '';
$reviewers = $this->getReviewerPairs($story->id, $story->version);
$reviewedBy = explode(',', trim($story->reviewedBy, ','));
$comment = isset($_POST['comment']) ? $this->post->comment : '';
$actionID = !empty($result) ? $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . $reasonParam) : '';
if($result != 'revert')
@@ -4595,6 +4643,9 @@ class storyModel extends model
*/
public function updateStoryByReview($storyID, $oldStory, $story)
{
$isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ',');
if($isSuperReviewer !== false) return $this->superReview($storyID, $oldStory, $story);
$now = helper::now();
$reviewerList = $this->getReviewerPairs($storyID, $oldStory->version);
$reviewedBy = explode(',', trim($story->reviewedBy, ','));
@@ -4615,4 +4666,46 @@ class storyModel extends model
return $story;
}
/**
* To review for super reviewer.
*
* @param int $storyID
* @param object $oldStory
* @param object $story
* @param string $result
* @param string $reason
* @access public
* @return object
*/
public function superReview($storyID, $oldStory, $story, $result = '', $reason = '')
{
$now = helper::now();
$status = '';
$result = isset($_POST['result']) ? $this->post->result : $result;
if(strpos('revert,pass', $result) !== false) $status = 'active';
if($result == 'reject') $status = 'closed';
if($result == 'clarify' or empty($status)) $status = $oldStory->status;
$story->status = $status;
if($story->status == 'closed')
{
$story->closedBy = $this->app->user->account;
$story->closedDate = $now;
$story->assignedTo = 'closed';
$story->assignedDate = $now;
$story->stage = 'closed';
$story->closedReason = isset($_POST['closedReason']) ? $_POST['closedReason'] : $reason;
if($_POST['closedReason'] == 'done') $story->stage = 'released';
}
$this->dao->delete()->from(TABLE_STORYREVIEW)
->where('story')->eq($storyID)
->andWhere('version')->eq($oldStory->version)
->andWhere('result')->eq('')
->exec();
return $story;
}
}
+2
View File
@@ -394,6 +394,7 @@
<th><?php echo $lang->story->assignedTo;?></th>
<td><?php if($story->assignedTo) echo zget($users, $story->assignedTo) . $lang->at . $story->assignedDate;?></td>
</tr>
<?php if(!$checkSuperReviewed):?>
<tr>
<th><?php echo $lang->story->reviewers;?></th>
<td>
@@ -408,6 +409,7 @@
?>
</td>
</tr>
<?php endif;?>
<tr>
<th><?php echo $lang->story->reviewedDate;?></th>
<td><?php if($story->reviewedBy) echo $story->reviewedDate;?></td>