Merge branch 'sprint/170' of https://gitlab.zcorp.cc/easycorp/zentaopms into 170

This commit is contained in:
zhujinyong
2021-11-10 13:50:45 +08:00
35 changed files with 718 additions and 211 deletions
+6 -4
View File
@@ -279,6 +279,7 @@ $lang->action->label->reviewrejected = 'Reject';
$lang->action->label->reviewclarified = 'Clarify';
$lang->action->label->commitsummary = 'Commit Summary';
$lang->action->label->updatetrainee = 'Update Trainee';
$lang->action->label->setdefaultbranch = 'Set default branch';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
@@ -312,10 +313,11 @@ $lang->action->dynamicAction->product['closed'] = 'Close ' . $lang->productCo
$lang->action->dynamicAction->product['undeleted'] = 'Restore ' . $lang->productCommon;
$lang->action->dynamicAction->product['hidden'] = 'Hide ' . $lang->productCommon;
$lang->action->dynamicAction->branch['opened'] = 'Create Branch';
$lang->action->dynamicAction->branch['edited'] = 'Edit Branch';
$lang->action->dynamicAction->branch['closed'] = 'Close Branch';
$lang->action->dynamicAction->branch['activated'] = 'Activate Branch';
$lang->action->dynamicAction->branch['opened'] = 'Create Branch';
$lang->action->dynamicAction->branch['edited'] = 'Edit Branch';
$lang->action->dynamicAction->branch['closed'] = 'Close Branch';
$lang->action->dynamicAction->branch['activated'] = 'Activate Branch';
$lang->action->dynamicAction->branch['setdefaultbranch'] = 'Set Default Branch';
$lang->action->dynamicAction->productplan['opened'] = 'Create Plan';
$lang->action->dynamicAction->productplan['edited'] = 'Edit Plan';
+6 -4
View File
@@ -279,6 +279,7 @@ $lang->action->label->reviewrejected = '拒绝';
$lang->action->label->reviewclarified = '有待明确';
$lang->action->label->commitsummary = '提交培训总结';
$lang->action->label->updatetrainee = '更新培训人员';
$lang->action->label->setdefaultbranch = '设置了默认分支';
/* 动态信息按照对象分组 */
$lang->action->dynamicAction = new stdclass();
@@ -312,10 +313,11 @@ $lang->action->dynamicAction->product['closed'] = '关闭' . $lang->productCo
$lang->action->dynamicAction->product['undeleted'] = '还原' . $lang->productCommon;
$lang->action->dynamicAction->product['hidden'] = '隐藏' . $lang->productCommon;
$lang->action->dynamicAction->branch['opened'] = '创建分支';
$lang->action->dynamicAction->branch['edited'] = '编辑分支';
$lang->action->dynamicAction->branch['closed'] = '关闭分支';
$lang->action->dynamicAction->branch['activated'] = '激活分支';
$lang->action->dynamicAction->branch['opened'] = '创建分支';
$lang->action->dynamicAction->branch['edited'] = '编辑分支';
$lang->action->dynamicAction->branch['closed'] = '关闭分支';
$lang->action->dynamicAction->branch['activated'] = '激活分支';
$lang->action->dynamicAction->branch['setdefaultbranch'] = '设置默认分支';
$lang->action->dynamicAction->productplan['opened'] = "创建计划";
$lang->action->dynamicAction->productplan['edited'] = "编辑计划";
+13 -2
View File
@@ -240,6 +240,12 @@ class actionModel extends model
$record->execution = $testtask->execution;
}
if($objectType == 'branch' and $objectID == 0)
{
$record = new stdclass();
$record->product = $extra;
}
if($objectType == 'whitelist' and $extra == 'product') $record->product = $objectID;
if($objectType == 'whitelist' and $extra == 'project') $record->project = $objectID;
if($objectType == 'whitelist' and ($extra == 'sprint' or $extra == 'stage')) $record->execution = $objectID;
@@ -1059,6 +1065,12 @@ class actionModel extends model
->where('t1.id')->in($objectIdList)
->fetchPairs();
}
elseif($objectType == 'branch')
{
$this->app->loadLang('branch');
$objectName = $this->dao->select("id,name")->from(TABLE_BRANCH)->where('id')->in($objectIdList)->fetchPairs();
if(in_array(BRANCH_MAIN, $objectIdList)) $objectName[BRANCH_MAIN] = $this->lang->branch->main;
}
else
{
$objectName = $this->dao->select("id, $field AS name")->from($table)->where('id')->in($objectIdList)->fetchPairs();
@@ -1179,8 +1191,7 @@ class actionModel extends model
}
elseif($action->objectType == 'branch')
{
$productID = $this->dao->select('product')->from(TABLE_BRANCH)->where('id')->eq($action->objectID)->fetch('product');
$params = sprintf($vars, $productID);
$params = sprintf($vars, trim($action->product, ','));
}
else
{
+77 -2
View File
@@ -23,9 +23,10 @@ class branch extends control
* @access public
* @return void
*/
public function manage($productID, $browseType = 'active', $orderBy = 'order_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
public function manage($productID, $browseType = 'active', $orderBy = 'order', $recTotal = 0, $recPerPage = 20, $pageID = 1)
{
$this->loadModel('product')->setMenu($productID);
$this->session->set('branchManage', $this->app->getURI(true), 'product');
$branchList = $this->branch->getList($productID, $browseType, $orderBy);
@@ -89,6 +90,43 @@ class branch extends control
$this->display();
}
/**
* Batch edit branch.
*
* @param int $productID
* @access public
* @return void
*/
public function batchEdit($productID)
{
$this->loadModel('action');
$this->loadModel('product')->setMenu($productID);
if($this->post->IDList)
{
$changes = $this->branch->batchUpdate($productID);
foreach($changes as $branchID => $change)
{
$extra = $branchID == BRANCH_MAIN ? $productID : '';
if($change) $this->action->create('branch', $branchID, 'Edited', '', $extra);
}
die(js::locate($this->session->branchManage, 'parent'));
}
$branchList = $this->branch->getList($productID, 'all');
$branchIDList = $this->post->branchIDList;
if(empty($branchIDList)) die(js::locate($this->session->branchManage, 'parent'));
foreach($branchList as $branch)
{
if(!in_array($branch->id, $branchIDList)) unset($branchList[$branch->id]);
}
$this->view->branchList = $branchList;
$this->display();
}
/**
* Close a branch.
*
@@ -207,16 +245,53 @@ class branch extends control
* @param int $productID
* @param int $oldBranch
* @param string $param
* @param int $projectID
* @access public
* @return void
*/
public function ajaxGetBranches($productID, $oldBranch = 0, $param = '')
public function ajaxGetBranches($productID, $oldBranch = 0, $param = '', $projectID = 0)
{
$product = $this->loadModel('product')->getById($productID);
if(empty($product) or $product->type == 'normal') die();
$branches = $this->branch->getPairs($productID, $param);
/* Remove unlinked branches of the project. */
if($projectID)
{
$projectProducts = $this->loadModel('project')->getBranchesByProject($projectID);
foreach($branches as $branchID => $branchName)
{
if(!isset($projectProducts[$productID][$branchID])) unset($branches[$branchID]);
}
}
if($oldBranch) $branches = array($oldBranch => $branches[$oldBranch]);
die(html::select('branch', $branches, '', "class='form-control' onchange='loadBranch(this)'"));
}
/**
* Set default branch.
*
* @param int $productID
* @param int $branchID
* @param string $confirm yes|no
* @access public
* @return void
*/
public function setDefault($productID, $branchID, $confirm = 'no')
{
if($confirm == 'no')
{
$this->app->loadLang('product');
$productType = $this->branch->getProductType($branchID);
die(js::confirm(str_replace('@branch@', $this->lang->product->branchName[$productType], $this->lang->branch->confirmSetDefault), inlink('setDefault', "productID=$productID&branchID=$branchID&confirm=yes")));
}
$this->branch->setDefault($productID, $branchID);
$this->loadModel('action')->create('branch', $branchID, 'SetDefaultBranch', '', $productID);
die(js::reload('parent'));
}
}
+2
View File
@@ -0,0 +1,2 @@
.c-desc {width : 600px;}
.c-defult {width: 100px;}
+4
View File
@@ -1,3 +1,7 @@
.c-desc {width: 500px;}
.c-order {width: 70px;}
.c-check {width: 40px}
td.flex {display: flex; flex-flow: row nowrap; justify-content: flex-start; align-items: center;}
td.flex .label-primary {min-width: 40px;}
td.flex .setDefault {min-width: 120px;}
+18
View File
@@ -0,0 +1,18 @@
/**
* Can set default branch.
*
* @param obj $obj
* @access public
* @return void
*/
function canSetDefaultBranch(obj)
{
if(obj.value == 'active')
{
$(obj).closest('tr').find("input[name^='default']").removeAttr('disabled');
}
else
{
$(obj).closest('tr').find("input[name^='default']").removeAttr('checked').attr('disabled', 'disabled');
}
}
+25 -6
View File
@@ -13,16 +13,35 @@ function deleteItem(obj)
$(function()
{
$('#branches').sortable(
$('#branchTableList').addClass('sortable').sortable(
{
selector: '.input-group',
reverse: orderBy === 'order_desc',
selector: 'tr',
dragCssClass: 'drag-row',
trigger: $('#branches').find('.sort-handler').length ? '.sort-handler' : null,
trigger: $('#branchTableList').find('.sort-handler').length ? '.sort-handler' : null,
canMoveHere: function($ele, $target)
{
return $target.data('id') != 0;
},
finish: function(e)
{
var list = '';
$('#branches').find('.input-group').each(function(){list += $(this).attr('data-id') + ',';});
$.post(createLink('branch', 'sort'), {'branches' : list});
var branches = '';
e.list.each(function()
{
branches += $(this.item).data('id') + ',';
});
$.post(createLink('branch', 'sort'), {'branches': branches, 'orderBy': orderBy});
}
});
$('td.c-name.flex').mouseenter(function()
{
$(this).find('.setDefault').removeClass('hidden');
}).mouseleave(function()
{
$(this).find('.setDefault').addClass('hidden');
})
});
+19 -13
View File
@@ -9,14 +9,18 @@ $lang->branch->manageTitle = '%s Management';
$lang->branch->all = 'All ';
$lang->branch->main = 'Main';
$lang->branch->edit = 'Edit';
$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 Branch';
$lang->branch->merge = 'Merge';
$lang->branch->edit = 'Edit';
$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 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->id = 'ID';
$lang->branch->product = 'Product';
@@ -28,12 +32,14 @@ $lang->branch->desc = 'Desc';
$lang->branch->order = 'Order';
$lang->branch->deleted = 'Delete';
$lang->branch->closed = 'Closed';
$lang->branch->default = 'Default';
$lang->branch->confirmDelete = 'Do you want to delete this @branch@?';
$lang->branch->canNotDelete = 'There is data in @branch@. It cannot be deleted.';
$lang->branch->nameNotEmpty = 'Name must not be empty';
$lang->branch->confirmClose = 'Do you want to close this @branch@';
$lang->branch->confirmActivate = 'Do you want to activate this @branch@';
$lang->branch->confirmDelete = 'Do you want to delete this @branch@?';
$lang->branch->confirmSetDefault = 'Do you want to set @branch@ as default @branch@? After your setting, the default @branch@ will be selected and shown in its Plan/Release list.';
$lang->branch->canNotDelete = 'There is data in @branch@. It cannot be deleted.';
$lang->branch->nameNotEmpty = 'Name must not be empty';
$lang->branch->confirmClose = 'Do you want to close this @branch@';
$lang->branch->confirmActivate = 'Do you want to activate this @branch@';
$lang->branch->noData = 'No branches.';
$lang->branch->mainBranch = 'The default main branch of the product.';
+19 -13
View File
@@ -9,14 +9,18 @@ $lang->branch->manageTitle = '%s管理';
$lang->branch->all = '所有';
$lang->branch->main = '主干';
$lang->branch->edit = '编辑';
$lang->branch->editAction = '编辑分支';
$lang->branch->activate = '激活';
$lang->branch->activateAction = '激活分支';
$lang->branch->close = '关闭';
$lang->branch->closeAction = '关闭分支';
$lang->branch->create = '新增分支';
$lang->branch->merge = '合并';
$lang->branch->edit = '编辑';
$lang->branch->editAction = '编辑分支';
$lang->branch->activate = '激活';
$lang->branch->activateAction = '激活分支';
$lang->branch->close = '关闭';
$lang->branch->closeAction = '关闭分支';
$lang->branch->create = '新增分支';
$lang->branch->merge = '合并';
$lang->branch->batchEdit = '批量编辑';
$lang->branch->defaultBranch = '默认分支';
$lang->branch->setDefault = '设为默认分支';
$lang->branch->setDefaultAction = '设置默认分支';
$lang->branch->id = 'ID';
$lang->branch->product = '所属产品';
@@ -28,12 +32,14 @@ $lang->branch->desc = '分支描述';
$lang->branch->order = '排序';
$lang->branch->deleted = '已删除';
$lang->branch->closed = '已关闭';
$lang->branch->default = '默认';
$lang->branch->confirmDelete = '是否删除该@branch@';
$lang->branch->canNotDelete = '该@branch@下已经有数据,不能删除!';
$lang->branch->nameNotEmpty = '名称不能为空';
$lang->branch->confirmClose = '是否关闭该@branch@';
$lang->branch->confirmActivate = '是否激活该@branch@';
$lang->branch->confirmDelete = '是否删除该@branch@';
$lang->branch->confirmSetDefault = '请确认是否需要将该@branch@设置为默认@branch@,设置成功后计划和发布列表将默认选中默认@branch@。';
$lang->branch->canNotDelete = '该@branch@下已经有数据,不能删除';
$lang->branch->nameNotEmpty = '名称不能为空!';
$lang->branch->confirmClose = '是否关闭该@branch@';
$lang->branch->confirmActivate = '是否激活该@branch@';
$lang->branch->noData = '暂时没有分支。';
$lang->branch->mainBranch = '产品默认主干分支。';
+83 -6
View File
@@ -45,7 +45,7 @@ class branchModel extends model
* @access public
* @return array
*/
public function getList($productID, $browseType = 'active', $orderBy = 'order_desc', $pager = null)
public function getList($productID, $browseType = 'active', $orderBy = 'order', $pager = null)
{
$branchList = $this->dao->select('*')->from(TABLE_BRANCH)
->where('deleted')->eq(0)
@@ -57,12 +57,15 @@ class branchModel extends model
if($browseType == 'closed') return $branchList;
$defaultBranch = BRANCH_MAIN;
foreach($branchList as $branch) $defaultBranch = $branch->default ? $branch->id : $defaultBranch;
/* Display the main branch under all and active page. */
$mainBranch = new stdclass();
$mainBranch->id = BRANCH_MAIN;
$mainBranch->product = $productID;
$mainBranch->name = $this->lang->branch->main;
$mainBranch->default = 1;
$mainBranch->default = $defaultBranch ? 0 : 1;
$mainBranch->status = 'active';
$mainBranch->createdDate = '';
$mainBranch->closedDate = '';
@@ -181,6 +184,52 @@ class branchModel extends model
return false;
}
/**
* Batch update branch.
*
* @access public
* @return array
*/
public function batchUpdate($productID)
{
$data = fixer::input('post')->get();
$oldBranchList = $this->getList($productID, 'all');
$branchIDList = array_keys($this->post->IDList);
foreach($branchIDList as $branchID)
{
if($branchID == BRANCH_MAIN)
{
$newMainBranch = new stdClass();
$newMainBranch->default = (isset($data->default) and $data->default == BRANCH_MAIN) ? 1 : 0;
$changes[$branchID] = common::createChanges($oldBranchList[BRANCH_MAIN], $newMainBranch);
}
else
{
$branch = new stdclass();
$branch->name = $data->name[$branchID];
$branch->desc = $data->desc[$branchID];
$branch->status = $data->status[$branchID];
$branch->default = (isset($data->default) and $branchID == $data->default) ? 1 : 0;
$branch->closedDate = $branch->status == 'closed' ? helper::today() : '';
$this->dao->update(TABLE_BRANCH)->data($branch, 'default')
->batchCheck($this->config->branch->create->requiredFields, 'notempty')
->where('id')->eq($branchID)
->exec();
if(dao::isError()) die(js::error('branch#' . $branchID . dao::getError(true)));
$changes[$branchID] = common::createChanges($oldBranchList[$branchID], $branch);
}
}
if(isset($data->default)) $this->setDefault($productID, $data->default);
return $changes;
}
/**
* Close a branch.
*
@@ -193,6 +242,7 @@ class branchModel extends model
$this->dao->update(TABLE_BRANCH)
->set('status')->eq('closed')
->set('closedDate')->eq(helper::today())
->set('`default`')->eq('0')
->where('id')->eq($branchID)
->exec();
}
@@ -314,11 +364,18 @@ class branchModel extends model
*/
public function sort()
{
$data = fixer::input('post')->get();
$branches = trim($data->branches, ',');
foreach(explode(',', $branches) as $order => $branchID)
$orderBy = $this->post->orderBy;
$branchIDList = explode(',', trim($this->post->branches, ','));
if(strpos($orderBy, 'order') === false) return false;
if(in_array(BRANCH_MAIN, $branchIDList)) unset($branchIDList[array_search(BRANCH_MAIN, $branchIDList)]);
$branches = $this->dao->select('id,`order`')->from(TABLE_BRANCH)->where('id')->in($branchIDList)->orderBy($orderBy)->fetchPairs('order', 'id');
foreach($branches as $order => $id)
{
$this->dao->update(TABLE_BRANCH)->set('`order`')->eq($order)->where('id')->eq($branchID)->exec();
$newID = array_shift($branchIDList);
if($id == $newID) continue;
$this->dao->update(TABLE_BRANCH)->set('`order`')->eq($order)->where('id')->eq($newID)->exec();
}
}
@@ -362,4 +419,24 @@ class branchModel extends model
$linkHtml = strpos('programplan', $module) !== false ? sprintf($link, $projectID, $productID, $branch) : sprintf($link, $productID, $branch);
return $linkHtml;
}
/**
* Set default branch.
*
* @param int $productID
* @param int $branchID
* @accesss public
* @return void
*/
public function setDefault($productID, $branchID)
{
$defaultBranch = $this->dao->select('id')->from(TABLE_BRANCH)
->where('product')->eq($productID)
->andWhere('`default`')->eq('1')
->fetch('id');
if(!empty($defaultBranch)) $this->dao->update(TABLE_BRANCH)->set('`default`')->eq('0')->where('id')->eq($defaultBranch)->exec();
$this->dao->update(TABLE_BRANCH)->set('`default`')->eq('1')->where('id')->eq($branchID)->exec();
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
/**
* The batchedit of branch module of ZenTaoPMS.
*
* @copyright Copyright 2009-2021 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Qiyu Xie <xieqiyu@easycorp.ltd>
* @package branch
* @version $Id: batchedit.html.php 4903 2021-11-09 13:14:59Z hfz $
* @link https://www.zentao.net
*/
?>
<?php include '../../common/view/header.html.php';?>
<div id="mainContent" class="main-content fade">
<div class="main-header">
<h2><?php echo $lang->branch->common . '-' . $lang->branch->batchEdit;?></h2>
</div>
<form class="load-indicator main-form" method='post' target='hiddenwin' enctype='multipart/form-data' id='dataform'>
<table class="table table-form">
<thead>
<tr>
<th class='c-id'><?php echo $lang->branch->id;?></th>
<th class='required c-name'><?php echo $lang->branch->name;?></th>
<th class='c-desc'><?php echo $lang->branch->desc;?></th>
<th class='c-status'><?php echo $lang->branch->status;?></th>
<th class='c-default text-center'><?php echo $lang->branch->defaultBranch;?></th>
</tr>
</thead>
<tbody>
<?php foreach($branchList as $branch):?>
<?php $disabled = $branch->id == BRANCH_MAIN ? 'disabled' : '';?>
<tr>
<td><?php echo ($branch->id == BRANCH_MAIN ? '' : $branch->id) . html::hidden("IDList[$branch->id]", $branch);?></td>
<td><?php echo html::input("name[$branch->id]", $branch->name, "class='form-control chosen' $disabled");?></td>
<td><?php echo html::input("desc[$branch->id]", $branch->desc, "class='form-control' $disabled");?></td>
<td><?php echo html::select("status[$branch->id]", $lang->branch->statusList, $branch->status, "class='form-control' chosen $disabled onchange='canSetDefaultBranch(this)'");?></td>
<td class='text-center'><input type='radio' name='default' value='<?php echo $branch->id;?>' <?php if($branch->default) echo 'checked';?> <?php if($branch->status == 'closed') echo 'disabled';?>></td>
</tr>
<?php endforeach;?>
<tr>
<td colspan='6' class='form-actions text-center'>
<?php echo html::submitButton() . html::backButton();?>
</td>
</tr>
</tbody>
</table>
</form>
</div>
<?php include '../../common/view/footer.html.php';?>
+36 -10
View File
@@ -12,7 +12,9 @@
?>
<?php include '../../common/view/header.html.php';?>
<?php include '../../common/view/sortable.html.php';?>
<?php js::set('orderBy', $orderBy)?>
<?php $canCreate = common::hasPriv('branch', 'create');?>
<?php $canOrder = common::hasPriv('branch', 'sort');?>
<?php $canBatchEdit = common::hasPriv('branch', 'batchEdit');?>
<?php $canMergeBranch = common::hasPriv('branch', 'mergeBranch');?>
<?php $canBatchAction = ($canBatchEdit or $canMergeBranch);?>
@@ -39,7 +41,7 @@
</p>
</div>
<?php else:?>
<form class='main-table table-branch' data-ride='table' method='post' id='branchForm'>
<form class='main-table' data-ride='table' method='post' id='branchForm'>
<table id="branchList" class="table has-sort-head">
<thead>
<tr>
@@ -49,7 +51,9 @@
<div class="checkbox-primary check-all" title="<?php echo $lang->selectAll?>"><label></label></div>
</th>
<?php endif;?>
<?php if($canOrder):?>
<th class='c-order sort-default'><?php echo $lang->branch->order;?></th>
<?php endif;?>
<th class='text-left'><?php common::printOrderLink('name', $orderBy, $vars, $lang->branch->name);?></th>
<th class='c-status'><?php common::printOrderLink('status', $orderBy, $vars, $lang->branch->status);?></th>
<th class='c-date'><?php common::printOrderLink('createdDate', $orderBy, $vars, $lang->branch->createdDate);?></th>
@@ -58,23 +62,43 @@
<th class='c-actions-2'><?php echo $lang->actions;?></th>
</tr>
</thead>
<tbody class="sortable">
<tbody id='branchTableList'>
<?php foreach($branchList as $branch):?>
<tr>
<?php $isMain = $branch->id == BRANCH_MAIN;?>
<tr data-id='<?php echo $branch->id;?>'>
<?php if($canBatchAction):?>
<td class='cell-id'>
<?php echo html::checkbox('branchIDList', array($branch->id => ''));?>
</td>
<?php endif;?>
<td class='c-actions sort-handler'><i class="icon icon-move"></i></td>
<td class='c-name' title='<?php echo $branch->name;?>'><?php echo $branch->name;?></td>
<?php if($canOrder):?>
<td class='c-actions <?php echo $isMain ? '' : 'sort-handler';?>'>
<?php echo $isMain ? '' : '<i class="icon icon-move"></i>';?>
</td>
<?php endif;?>
<td class='c-name flex' title='<?php echo $branch->name;?>'>
<span class="text-ellipsis"><?php echo $branch->name;?></span>
<?php
if($branch->default)
{
echo '&nbsp;<span class="label label-primary label-badge">' . $lang->branch->default . '</span>';
}
elseif($branch->status == 'active')
{
$setDefaultLink = helper::createLink('branch', 'setDefault', "productID=$productID&branchID=$branch->id", '', true);
$setDefaultHtml = html::a($setDefaultLink, "<i class='icon icon-hand-right'></i> <span>{$lang->branch->setDefault}</span>", '', "class='iframe btn btn-icon-left btn-sm setDefault hidden'");
echo common::hasPriv('branch', 'setDefault') ? $setDefaultHtml : '';
}
?>
</td>
<td><?php echo zget($lang->branch->statusList, $branch->status);?></td>
<td><?php echo helper::isZeroDate($branch->createdDate) ? '' : $branch->createdDate;?></td>
<td><?php echo helper::isZeroDate($branch->closedDate) ? '' : $branch->closedDate;?></td>
<td class='c-name' title='<?php echo $branch->desc;?>'><?php echo $branch->desc;?></td>
<td class='c-actions'>
<?php
$disabled = $branch->id == BRANCH_MAIN ? 'disabled' : '';
$disabled = $isMain ? 'disabled' : '';
common::printIcon('branch', 'edit', "branchID=$branch->id", $branch, 'list', '', '', "$disabled iframe", true);
if($branch->status == 'active')
{
@@ -95,13 +119,15 @@
<div class="checkbox-primary check-all">
<label><?php echo $lang->selectAll?></label>
</div>
<?php if($canBatchEdit):?>
<div class="table-actions btn-toolbar">
<?php echo html::submitButton($lang->edit, '', 'btn');?>
</div>
<div class="table-actions btn-toolbar">
<?php echo html::submitButton($lang->branch->merge, '', 'btn');?>
<?php
$batchEditLink = $this->createLink('branch', 'batchEdit', "productID=$productID");
echo html::submitButton($lang->edit, "data-form-action='$batchEditLink'", 'btn');
?>
</div>
<?php endif;?>
<?php endif;?>
<?php $pager->show('right', 'pagerjs');?>
</div>
</form>
+15 -6
View File
@@ -6,7 +6,7 @@
$(function()
{
<?php if(!empty($setModule)):?>
$('#sidebar .cell .text-center:last').append("<a href='#showModuleModal' data-toggle='modal' class='btn btn-info btn-wide'><?php echo $lang->datatable->moduleSetting?></a><hr class='space-sm' />");
$('#sidebar .cell .text-center:last').append("<a href='#showModuleModal' data-toggle='modal' class='btn btn-info btn-wide'><?php echo $app->moduleName=='product' ? $lang->datatable->listSetting : $lang->datatable->moduleSetting?></a><hr class='space-sm' />");
<?php endif;?>
var addSettingButton = function()
@@ -36,10 +36,12 @@ $(function()
$('#setShowModule').click(function()
{
if('<?php echo $this->app->user->account?>' == 'guest') return;
datatableId = '<?php echo $datatableId?>';
var value = $('#showModuleModal input[name="showModule"]:checked').val();
var allModule = $('#showModuleModal input[name="showAllModule"]:checked').val();
if(typeof allModule === 'undefined') allModule = false;
datatableId = '<?php echo $datatableId?>';
var value = $('#showModuleModal input[name="showModule"]:checked').val();
var allModule = $('#showModuleModal input[name="showAllModule"]:checked').val();
var showBranch = $('#showModuleModal input[name="showBranch"]:checked').val();
if(typeof allModule === 'undefined') allModule = false;
if(typeof showBranch === 'undefined') showBranch = false;
$.ajax(
{
type: "POST",
@@ -50,6 +52,7 @@ $(function()
name: 'showModule',
value: value,
allModule: allModule,
showBranch: showBranch,
},
success:function(){window.location.reload();},
url: '<?php echo $this->createLink('datatable', 'ajaxSave')?>'
@@ -80,7 +83,7 @@ $(function()
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><i class="icon icon-close"></i></button>
<h4 class="modal-title"><i class="icon-cog-outline"></i> <?php echo $lang->datatable->moduleSetting;?></h4>
<h4 class="modal-title"><i class="icon-cog-outline"></i> <?php echo $app->moduleName=='product' ? $lang->datatable->listSetting : $lang->datatable->moduleSetting;?></h4>
</div>
<div class="modal-body">
<form class='form-condensed' method='post' target='hiddenwin' action='<?php echo $this->createLink('datatable', 'ajaxSave')?>'>
@@ -95,6 +98,12 @@ $(function()
<td><?php echo html::radio('showAllModule', $lang->datatable->showAllModuleList, isset($config->execution->task->allModule) ? $config->execution->task->allModule : 0);?></td>
</tr>
<?php endif;?>
<?php if($app->moduleName == 'product' && $app->methodName == 'browse'):?>
<tr>
<td><?php echo $lang->datatable->showBranch;?></td>
<td><?php echo html::radio('showBranch', $lang->datatable->showBranchList, isset($config->product->browse->showBranch) ? $config->product->browse->showBranch : 1);?></td>
</tr>
<?php endif;?>
<tr>
<td colspan='2' class='text-center'><button type='button' id='setShowModule' class='btn btn-primary'><?php echo $lang->save?></button></td>
</tr>
+1
View File
@@ -38,6 +38,7 @@ class datatable extends control
$name = 'datatable.' . $this->post->target . '.' . $this->post->name;
$this->loadModel('setting')->setItem($account . '.' . $name, $this->post->value);
if($this->post->allModule !== false) $this->setting->setItem("$account.execution.task.allModule", $this->post->allModule);
if($this->post->showBranch !== false) $this->setting->setItem("$account.product.browse.showBranch", $this->post->showBranch);
if($this->post->global) $this->setting->setItem('system.' . $name, $this->post->value);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => 'dao error.'));
+4
View File
@@ -19,10 +19,14 @@ $lang->datatable->branch = 'Branch';
$lang->datatable->platform = 'Platform';
$lang->datatable->moduleSetting = 'Set Module';
$lang->datatable->listSetting = 'Set list';
$lang->datatable->showModule = 'Show modules in the list';
$lang->datatable->showBranch = 'Show branches in the list';
$lang->datatable->showAllModule = 'Show product modules';
$lang->datatable->showModuleList[] = 'N/A';
$lang->datatable->showModuleList['base'] = 'Base Node';
$lang->datatable->showModuleList['end'] = 'End Node';
$lang->datatable->showAllModuleList[0] = 'Hide';
$lang->datatable->showAllModuleList[1] = 'Show';
$lang->datatable->showBranchList[0] = 'Hide';
$lang->datatable->showBranchList[1] = 'Show';
+4
View File
@@ -19,10 +19,14 @@ $lang->datatable->branch = '分支';
$lang->datatable->platform = '平台';
$lang->datatable->moduleSetting = '模块设置';
$lang->datatable->listSetting = '列表设置';
$lang->datatable->showModule = '列表页是否显示模块名';
$lang->datatable->showBranch = '列表页是否显示分支名';
$lang->datatable->showAllModule = '是否显示完整产品模块';
$lang->datatable->showModuleList[] = '不显示';
$lang->datatable->showModuleList['base'] = '只显示一级模块';
$lang->datatable->showModuleList['end'] = '只显示最后一级模块';
$lang->datatable->showAllModuleList[0] = '不显示';
$lang->datatable->showAllModuleList[1] = '显示';
$lang->datatable->showBranchList[0] = '不显示';
$lang->datatable->showBranchList[1] = '显示';
+109 -75
View File
@@ -1276,17 +1276,29 @@ class execution extends control
if($copyExecutionID)
{
$copyExecution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($copyExecutionID)->fetch();
$name = $copyExecution->name;
$code = $copyExecution->code;
$team = $copyExecution->team;
$acl = $copyExecution->acl;
$whitelist = $copyExecution->whitelist;
$projectID = $copyExecution->project;
$products = $this->execution->getProducts($copyExecutionID);
foreach($products as $product)
$name = $copyExecution->name;
$code = $copyExecution->code;
$team = $copyExecution->team;
$acl = $copyExecution->acl;
$whitelist = $copyExecution->whitelist;
$projectID = $copyExecution->project;
$products = $this->execution->getProducts($copyExecutionID);
$branches = $this->project->getBranchesByProject($copyExecutionID);
$plans = $this->loadModel('productplan')->getGroupByProduct(array_keys($products));
$branchGroups = $this->execution->getBranchByProduct(array_keys($products), $projectID);
$linkedBranches = array();
foreach($products as $productID => $product)
{
$productPlans[$product->id] = $this->loadModel('productplan')->getPairs($product->id);
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranches[$productID][$branchID] = $branchID;
$productPlans[$productID][$branchID] = isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
}
}
$this->view->branches = $branches;
$this->view->linkedBranches = $linkedBranches;
}
if(!empty($planID))
@@ -1297,7 +1309,7 @@ class execution extends control
->where('t1.id')->eq($plan->product)
->fetchAll('id');
$productPlan = $this->loadModel('productplan')->getPairs($plan->product, 0, 'unexpired');
$productPlan = $this->loadModel('productplan')->getPairs($plan->product, $plan->branch, 'unexpired');
}
if(!empty($_POST))
@@ -1322,9 +1334,12 @@ class execution extends control
$planID = '';
if(isset($_POST['plans']))
{
foreach($_POST['plans'] as $planID)
foreach($_POST['plans'] as $plans)
{
if(!empty($planID)) break;
foreach($plans as $planID)
{
if(!empty($planID)) break;
}
}
}
@@ -1355,34 +1370,35 @@ class execution extends control
$rdUsers = $this->user->getPairs('noclosed|nodeleted|devfirst', '', $this->config->maxCount);
if(!empty($this->config->user->moreLink)) $this->config->moreLinks["RD"] = $this->config->user->moreLink;
$this->view->title = (($this->app->tab == 'execution') and ($this->config->systemMode == 'new')) ? $this->lang->execution->createExec : $this->lang->execution->create;
$this->view->position[] = $this->view->title;
$this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('execution', 'all') : '';
$this->view->executions = array('' => '') + $this->execution->getList($projectID);
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->allProducts = array(0 => '') + $this->loadModel('product')->getProductPairsByProject($projectID, 'noclosed');
$this->view->acl = $acl;
$this->view->plan = $plan;
$this->view->name = $name;
$this->view->code = $code;
$this->view->team = $team;
$this->view->teams = array(0 => '') + $this->execution->getTeamPairsByProject((int)$projectID);
$this->view->allProjects = array(0 => '') + $this->project->getPairsByModel();
$this->view->executionID = $executionID;
$this->view->productID = $productID;
$this->view->projectID = $projectID;
$this->view->products = $products;
$this->view->productPlan = array(0 => '') + $productPlan;
$this->view->productPlans = array(0 => '') + $productPlans;
$this->view->whitelist = $whitelist;
$this->view->copyExecutionID = $copyExecutionID;
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
$this->view->poUsers = $poUsers;
$this->view->pmUsers = $pmUsers;
$this->view->qdUsers = $qdUsers;
$this->view->rdUsers = $rdUsers;
$this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed');
$this->view->from = $this->app->tab;
$this->view->title = (($this->app->tab == 'execution') and ($this->config->systemMode == 'new')) ? $this->lang->execution->createExec : $this->lang->execution->create;
$this->view->position[] = $this->view->title;
$this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('execution', 'all') : '';
$this->view->executions = array('' => '') + $this->execution->getList($projectID);
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->allProducts = array(0 => '') + $this->loadModel('product')->getProductPairsByProject($projectID, 'noclosed');
$this->view->acl = $acl;
$this->view->plan = $plan;
$this->view->name = $name;
$this->view->code = $code;
$this->view->team = $team;
$this->view->teams = array(0 => '') + $this->execution->getTeamPairsByProject((int)$projectID);
$this->view->allProjects = array(0 => '') + $this->project->getPairsByModel();
$this->view->executionID = $executionID;
$this->view->productID = $productID;
$this->view->projectID = $projectID;
$this->view->products = $products;
$this->view->multiBranchProducts = $this->product->getMultiBranchPairs();
$this->view->productPlan = array(0 => '') + $productPlan;
$this->view->productPlans = array(0 => '') + $productPlans;
$this->view->whitelist = $whitelist;
$this->view->copyExecutionID = $copyExecutionID;
$this->view->branchGroups = isset($branchGroups) ? $branchGroups : $this->execution->getBranchByProduct(array_keys($products), $projectID);
$this->view->poUsers = $poUsers;
$this->view->pmUsers = $pmUsers;
$this->view->qdUsers = $qdUsers;
$this->view->rdUsers = $rdUsers;
$this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed');
$this->view->from = $this->app->tab;
$this->display();
}
@@ -1434,14 +1450,19 @@ class execution extends control
}
/* Link the plan stories. */
$newPlans = $this->dao->select('plan')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->andWhere('plan')->ne(0)->fetchPairs('plan');
$newPlans = array();
foreach($_POST['plans'] as $plans)
{
foreach($plans as $planID) $newPlans[$planID] = $planID;
}
$diffResult = array_diff($oldPlans, $newPlans);
$diffResult = array_merge($diffResult, array_diff($newPlans, $oldPlans));
if(!empty($newPlans) and !empty($diffResult))
{
$projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
$this->loadModel('productplan')->linkProject($executionID, $_POST['plans']);
$this->productplan->linkProject($projectID, $_POST['plans']);
$this->loadModel('productplan')->linkProject($executionID, $newPlans);
$this->productplan->linkProject($projectID, $newPlans);
}
$this->executeHooks($executionID);
@@ -1466,28 +1487,30 @@ class execution extends control
$executionProsucts = $this->execution->getProducts($execution->project, true, 'noclosed');
foreach($executionProsucts as $product) $allProducts[$product->id] = $product->name;
$linkedProducts = $this->execution->getProducts($execution->id);
$linkedBranches = array();
$this->loadModel('productplan');
$productPlans = array(0 => '');
$linkedBranches = array();
$linkedProducts = $this->execution->getProducts($executionID);
$branches = $this->project->getBranchesByProject($executionID);
$plans = $this->productplan->getGroupByProduct(array_keys($linkedProducts));
$executionStories = $this->project->getStoriesByProject($executionID);
/* If the story of the product which linked the execution, you don't allow to remove the product. */
$unmodifiableProducts = array();
$unmodifiableBranches = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
$executionStories = $this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->andWhere('product')->eq($productID)->fetchAll('story');
if(!empty($executionStories)) array_push($unmodifiableProducts, $productID);
}
foreach($linkedProducts as $product)
{
if(!isset($allProducts[$product->id])) $allProducts[$product->id] = $product->name;
if($product->branch) $linkedBranches[$product->branch] = $product->branch;
}
$this->loadModel('productplan');
$productPlans = array(0 => '');
foreach($linkedProducts as $product)
{
$productPlans[$product->id] = $this->productplan->getPairs($product->id);
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranches[$productID][$branchID] = $branchID;
$productPlans[$productID][$branchID] = isset($plans[$productID][$branchID]) ? $plans[$productID][$branchID] : array();
if(!empty($executionStories[$productID][$branchID]))
{
array_push($unmodifiableProducts, $productID);
array_push($unmodifiableBranches, $branchID);
}
}
}
$this->loadModel('user');
@@ -1516,9 +1539,13 @@ class execution extends control
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->allProducts = $allProducts;
$this->view->linkedProducts = $linkedProducts;
$this->view->linkedBranches = $linkedBranches;
$this->view->branches = $branches;
$this->view->unmodifiableProducts = $unmodifiableProducts;
$this->view->unmodifiableBranches = $unmodifiableBranches;
$this->view->multiBranchProducts = $this->loadModel('product')->getMultiBranchPairs();
$this->view->productPlans = $productPlans;
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($linkedProducts), '', $linkedBranches);
$this->view->branchGroups = $this->execution->getBranchByProduct(array_keys($linkedProducts), $execution->project);
$this->display();
}
@@ -2243,33 +2270,40 @@ class execution extends control
$position[] = html::a($browseExecutionLink, $execution->name);
$position[] = $this->lang->execution->manageProducts;
$allProducts = $this->config->systemMode == 'classic' ? $this->product->getPairs('noclosed') : $this->product->getProductPairsByProject($execution->project);
$linkedProducts = $this->execution->getProducts($execution->id);
$linkedBranches = array();
$allProducts = $this->config->systemMode == 'classic' ? $this->product->getPairs('noclosed') : $this->product->getProductPairsByProject($execution->project);
$linkedProducts = $this->execution->getProducts($execution->id);
$linkedBranches = array();
$branches = $this->project->getBranchesByProject($executionID);
$executionStories = $this->project->getStoriesByProject($executionID);
/* If the story of the product which linked the execution, you don't allow to remove the product. */
$unmodifiableProducts = array();
$unmodifiableBranches = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
$executionStories = $this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->andWhere('product')->eq($productID)->fetchAll('story');
if(!empty($executionStories)) array_push($unmodifiableProducts, $productID);
}
// Merge allProducts and linkedProducts for closed product.
foreach($linkedProducts as $product)
{
if(!isset($allProducts[$product->id])) $allProducts[$product->id] = $product->name;
if(!empty($product->branch)) $linkedBranches[$product->branch] = $product->branch;
$linkedBranches[$productID] = array();
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranches[$productID][$branchID] = $branchID;
if(!empty($executionStories[$productID][$branchID]))
{
array_push($unmodifiableProducts, $productID);
array_push($unmodifiableBranches, $branchID);
}
}
}
/* Assign. */
$this->view->title = $title;
$this->view->position = $position;
$this->view->allProducts = $allProducts;
$this->view->execution = $execution;
$this->view->execution = $execution;
$this->view->linkedProducts = $linkedProducts;
$this->view->unmodifiableProducts = $unmodifiableProducts;
$this->view->branchGroups = $this->loadModel('branch')->getByProducts(array_keys($allProducts), 'ignoreNormal', $linkedBranches);
$this->view->unmodifiableBranches = $unmodifiableBranches;
$this->view->linkedBranches = $linkedBranches;
$this->view->branchGroups = $this->execution->getBranchByProduct(array_keys($linkedProducts), $execution->project);
$this->display();
}
+4 -3
View File
@@ -127,7 +127,7 @@ function loadBranches(product)
$('#productsBox select').each(function()
{
var $product = $(product);
if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id'))
if($product.val() != 0 && $product.val() == $(this).val() && $product.attr('id') != $(this).attr('id') && !multiBranchProducts[$product.val()])
{
alert(errorSameProducts);
$product.val(0);
@@ -152,8 +152,9 @@ function loadBranches(product)
if($inputgroup.find('select').size() >= 2) $inputgroup.removeClass('has-branch').find('select:last').remove();
if($inputgroup.find('.chosen-container').size() >= 2) $inputgroup.find('.chosen-container:last').remove();
var projectID = $('#project').val();
var index = $inputgroup.find('select:first').attr('id').replace('products' , '');
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val()), function(data)
$.get(createLink('branch', 'ajaxGetBranches', "productID=" + $(product).val() + "&oldBranch=0&param=active&projectID=" + projectID), function(data)
{
if(data)
{
@@ -182,7 +183,7 @@ function loadPlans(product, branchID)
if(data)
{
if($("div#plan" + index).size() == 0) $("#plansBox .row").append('<div class="col-sm-4" id="plan' + index + '"></div>');
$("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + ']').attr('id', 'plans' + productID).chosen();
$("div#plan" + index).html(data).find('select').attr('name', 'plans[' + productID + '][' + branchID + ']').attr('id', 'plans' + productID).chosen();
adjustPlanBoxMargin();
}
+36
View File
@@ -78,6 +78,42 @@ $(function()
var acl = $("[name^='acl']:checked").val();
setWhite(acl);
$('#submit').click(function()
{
var products = new Array();
var existedBranch = false;
/* Determine whether the products of the same branch are linked. */
$("#productsBox select[name^='products']").each(function()
{
var productID = $(this).val();
products[productID] = new Array();
if(multiBranchProducts[productID])
{
$("#productsBox select[name^='branch']").each(function()
{
var branchID = $(this).val();
if(products[productID][branchID])
{
existedBranch = true;
return false;
}
else
{
products[productID][branchID] = branchID;
}
})
if(existedBranch) return false;
}
})
if(existedBranch)
{
bootbox.alert(errorSameBranches);
return false;
}
})
});
function showLifeTimeTips()
+52 -4
View File
@@ -4,22 +4,70 @@ $().ready(function()
{
$('#products0').removeAttr("disabled");
$('#branch0').removeAttr("disabled");
});
var products = new Array();
var existedBranch = false;
/* Determine whether the products of the same branch are linked. */
$("#productsBox select[name^='products']").each(function()
{
var productID = $(this).val();
products[productID] = new Array();
if(multiBranchProducts[productID])
{
$("#productsBox select[name^='branch']").each(function()
{
var branchID = $(this).val();
if(products[productID][branchID])
{
existedBranch = true;
return false;
}
else
{
products[productID][branchID] = branchID;
}
})
if(existedBranch) return false;
}
})
if(existedBranch)
{
bootbox.alert(errorSameBranches);
return false;
}
})
});
$(function()
{
/* If the story of the product which linked the execution under the project, you don't allow to remove the product. */
$("#productsBox select").each(function()
$("#productsBox select[name^='products']").each(function()
{
var isExisted = $.inArray($(this).attr('data-last'), unmodifiableProducts);
if(isExisted != -1)
var isExistedProduct = $.inArray($(this).attr('data-last'), unmodifiableProducts);
var productType = $(this).attr('data-type');
if(isExistedProduct != -1 && productType == 'normal')
{
$(this).prop('disabled', true).trigger("chosen:updated");
$(this).siblings('div').find('span').attr('title', tip);
}
});
$("#productsBox select[name^='branch']").each(function()
{
var isExistedBranch = $.inArray($(this).attr('data-last'), unmodifiableBranches);
if(isExistedBranch != -1)
{
var $product = $(this).closest('.has-branch').find("[name^='products']");
if($.inArray($product.val(), unmodifiableProducts) != -1)
{
$(this).prop('disabled', true).trigger("chosen:updated");
$product.prop('disabled', true).trigger("chosen:updated");
$product.siblings('div').find('span').attr('title', tip);
}
}
});
oldProject = $("#project").val();
$('#project').change(function()
{
+1
View File
@@ -328,6 +328,7 @@ $lang->execution->notAllowedUnlinkStory = "This {$lang->SRCommon} is linke
$lang->execution->notAllowRemoveProducts = "The story of this product is linked with the {$lang->executionCommon}. Unlink it before doing any action.";
$lang->execution->errorNoLinkedProducts = "No {$lang->productCommon} is linked to {$lang->executionCommon}. You will be directed to {$lang->productCommon} page to link one.";
$lang->execution->errorSameProducts = "{$lang->executionCommon} cannot be linked to the same {$lang->productCommon} twice.";
$lang->execution->errorSameBranches = "{$lang->executionCommon} cannot be linked to the same branch twice";
$lang->execution->errorBegin = "The start time of {$lang->executionCommon} cannot be less than the start time of the project %s.";
$lang->execution->errorEnd = "The end time of {$lang->executionCommon} cannot be greater than the end time %s of the project.";
$lang->execution->accessDenied = "Your access to {$lang->executionCommon} is denied!";
+1
View File
@@ -328,6 +328,7 @@ $lang->execution->notAllowedUnlinkStory = "该{$lang->SRCommon}已经与
$lang->execution->notAllowRemoveProducts = "{$lang->productCommon}中的{$lang->SRCommon}已与该{$lang->executionCommon}进行了关联,请取消关联后再操作。";
$lang->execution->errorNoLinkedProducts = "{$lang->executionCommon}没有关联的{$lang->productCommon},系统将转到{$lang->productCommon}关联页面";
$lang->execution->errorSameProducts = "{$lang->executionCommon}不能关联多个相同的{$lang->productCommon}";
$lang->execution->errorSameBranches = "{$lang->executionCommon}不能关联多个相同的分支。";
$lang->execution->errorBegin = "{$lang->executionCommon}的开始时间不能小于所属项目的开始时间%s。";
$lang->execution->errorEnd = "{$lang->executionCommon}的截止时间不能大于所属项目的结束时间%s。";
$lang->execution->accessDenied = "您无权访问该{$lang->executionCommon}";
+30 -5
View File
@@ -525,7 +525,7 @@ class executionModel extends model
->exec();
$changedAccounts = array();
$teamMembers = array();
$teamMembers = array();
foreach($this->config->execution->ownerFields as $ownerField)
{
$owner = zget($execution, $ownerField, '');
@@ -542,7 +542,7 @@ class executionModel extends model
$this->dao->replace(TABLE_TEAM)->data($member)->exec();
$changedAccounts[$owner] = $owner;
$teamMembers[$ownerField] = $member;
$teamMembers[$ownerField] = $member;
}
if($execution->project) $this->addProjectMembers($execution->project, $teamMembers);
@@ -1655,6 +1655,28 @@ class executionModel extends model
return $query->fetchAll('id');
}
/**
* Get branch pairs by product id list.
*
* @param array $products
* @param int $projectID
* @access public
* @return array
*/
public function getBranchByProduct($products, $projectID)
{
$branchGroups = $this->loadModel('branch')->getByProducts($products, 'noclosed');
$projectProducts = $this->loadModel('project')->getBranchesByProject($projectID);
foreach($branchGroups as $productID => $branchPairs)
{
foreach($branchPairs as $branchID => $branchName)
{
if(!isset($projectProducts[$productID][$branchID])) unset($branchGroups[$productID][$branchID]);
}
}
return $branchGroups;
}
/**
* Get ordered executions.
*
@@ -1817,10 +1839,13 @@ class executionModel extends model
foreach($products as $i => $productID)
{
if(empty($productID)) continue;
if(isset($existedProducts[$productID])) continue;
if(!isset($existedProducts[$productID])) $existedProducts[$productID] = array();
$oldPlan = 0;
$branch = isset($branches[$i]) ? $branches[$i] : 0;
if(isset($existedProducts[$productID][$branch])) continue;
if(isset($oldProducts[$productID][$branch]))
{
$oldProduct = $oldProducts[$productID][$branch];
@@ -1831,9 +1856,9 @@ class executionModel extends model
$data->project = $executionID;
$data->product = $productID;
$data->branch = $branch;
$data->plan = isset($plans[$productID]) ? $plans[$productID] : $oldPlan;
$data->plan = isset($plans[$productID][$branch]) ? $plans[$productID][$branch] : $oldPlan;
$this->dao->insert(TABLE_PROJECTPRODUCT)->data($data)->exec();
$existedProducts[$productID] = true;
$existedProducts[$productID][$branch] = true;
}
$oldProductKeys = array_keys($oldProducts);
+7 -3
View File
@@ -34,11 +34,13 @@
<?php js::set('weekend', $config->execution->weekend);?>
<?php js::set('holders', $lang->execution->placeholder);?>
<?php js::set('errorSameProducts', $lang->execution->errorSameProducts);?>
<?php js::set('errorSameBranches', $lang->execution->errorSameBranches);?>
<?php js::set('productID', empty($productID) ? 0 : $productID);?>
<?php js::set('isStage', false);?>
<?php js::set('copyExecutionID', $copyExecutionID);?>
<?php js::set('systemMode', $config->systemMode);?>
<?php js::set('projectCommon', $lang->project->common);?>
<?php js::set('multiBranchProducts', $multiBranchProducts);?>
<div id='mainContent' class='main-content'>
<div class='center-block'>
<div class='main-header'>
@@ -132,12 +134,14 @@
<?php elseif($copyExecutionID):?>
<?php $i = 0;?>
<?php foreach($products as $product):?>
<?php $plans = zget($productPlans, $product->id, array(0 => ''));?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select("plans[" . $product->id . "]", $plans, '', "class='form-control chosen'");?></div>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<?php $plans = isset($productPlans[$product->id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select("plans[{$product->id}][$branchID]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen'");?></div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
<?php else:?>
<div class="col-sm-4" id="plan0"><?php echo html::select("plans[]", $productPlan, '', "class='form-control chosen'");?></div>
<div class="col-sm-4" id="plan0"><?php echo html::select("plans[][]", $productPlan, '', "class='form-control chosen'");?></div>
<?php js::set('currentPlanID', '')?>
<?php endif;?>
</div>
+20 -7
View File
@@ -116,16 +116,24 @@
<div class='row'>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<div class='col-sm-4'>
<?php $hasBranch = $product->type != 'normal' and isset($branchGroups[$product->id]);?>
<div class="input-group<?php if($hasBranch) echo ' has-branch';?>">
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $class onchange='loadBranches(this)' data-last='" . $product->id . "'");?>
<?php echo html::select("products[$i]", $allProducts, $product->id, "class='form-control chosen' $class onchange='loadBranches(this)' data-last='" . $product->id . "' data-type='". $product->type ."'");?>
<span class='input-group-addon fix-border'></span>
<?php if($hasBranch) echo html::select("branch[$i]", $branchGroups[$product->id], $product->branch, "class='form-control chosen' $class onchange=\"loadPlans('#products{$i}', this.value)\"");?>
<?php if($hasBranch) echo html::select("branch[$i]", $branchGroups[$product->id], $branchID, "class='form-control chosen' $class onchange=\"loadPlans('#products{$i}', this.value)\" data-last='" . $branchID . "'");?>
</div>
</div>
<?php if(in_array($product->id, $unmodifiableProducts)) echo html::hidden("products[$i]", $product->id);?>
<?php $i++;?>
<?php
if(in_array($product->id, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches))
{
echo html::hidden("products[$i]", $product->id);
echo html::hidden("branch[$i]", $branchID);
}
$i++;
?>
<?php endforeach;?>
<?php endforeach;?>
<div class='col-sm-4'>
<div class="input-group">
@@ -142,10 +150,12 @@
<div class='row'>
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<?php $plans = zget($productPlans, $product->id, array(0 => ''));?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select("plans[" . $product->id . "]", $plans, $product->plan, "class='form-control chosen'");?></div>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<?php $plans = isset($productPlans[$product->id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
<div class="col-sm-4" id="plan<?php echo $i;?>"><?php echo html::select("plans[{$product->id}][{$branchID}]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen'");?></div>
<?php $i++;?>
<?php endforeach;?>
<?php endforeach;?>
</div>
</td>
</tr>
@@ -171,7 +181,10 @@
</div>
<?php js::set('weekend', $config->execution->weekend);?>
<?php js::set('errorSameProducts', $lang->execution->errorSameProducts);?>
<?php js::set('errorSameBranches', $lang->execution->errorSameBranches);?>
<?php js::set('unmodifiableProducts',$unmodifiableProducts);?>
<?php js::set('unmodifiableBranches', $unmodifiableBranches)?>
<?php js::set('multiBranchProducts', $multiBranchProducts);?>
<?php js::set('tip', $lang->execution->notAllowRemoveProducts);?>
<?php js::set('confirmSyncStories', $lang->execution->confirmSyncStories);?>
<?php include '../../common/view/footer.html.php';?>
+15 -8
View File
@@ -22,23 +22,30 @@
<div class='detail'>
<div class='detail-title'><?php echo $lang->execution->linkedProducts;?></div>
<div class='detail-content row'>
<?php $class = $execution->grade == 2 ? "disabled='disabled'" : '';?>
<?php $i = 0;?>
<?php $attr = $execution->grade == 2 ? "disabled='disabled'" : '';?>
<?php foreach($allProducts as $productID => $productName):?>
<?php if(isset($linkedProducts[$productID])):?>
<?php $isDisabled = in_array($productID, $unmodifiableProducts) ? "disabled='disabled'" : '';?>
<?php $title = in_array($productID, $unmodifiableProducts) ? $lang->execution->notAllowRemoveProducts : $productName;?>
<?php $checked = 'checked';?>
<?php foreach($linkedBranches[$productID] as $branchID):?>
<?php if(($attr != "disabled='disabled'" and in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches))) $attr .= "disabled='disabled'";?>
<?php $title = in_array($productID, $unmodifiableProducts) ? $lang->execution->notAllowRemoveProducts : $productName;?>
<?php $checked = 'checked';?>
<div class='col-sm-4'>
<div class='product <?php echo $checked . (isset($branchGroups[$productID]) ? ' has-branch' : '')?>'>
<div class="checkbox-primary" title='<?php echo $title;?>'>
<?php echo "<input type='checkbox' name='products[$productID]' value='$productID' $checked $class id='products{$productID}' $isDisabled>";?>
<?php echo "<input type='checkbox' name='products[$i]' value='$productID' $checked $attr id='products{$productID}'>";?>
<label class='text-ellipsis checkbox-inline' for='<?php echo 'products' . $productID;?>' title='<?php echo $productName;?>'><?php echo $productName;?></label>
</div>
<?php if(isset($branchGroups[$productID])) echo html::select("branch[$productID]", $branchGroups[$productID], $linkedProducts[$productID]->branch, "class='form-control chosen' $class");?>
<?php if(isset($branchGroups[$productID])) echo html::select("branch[$i]", $branchGroups[$productID], $branchID, "class='form-control chosen' $attr");?>
</div>
</div>
<?php if(!empty($isDisabled)) echo html::hidden("products[$productID]", $productID);?>
<?php unset($allProducts[$productID]);?>
<?php if(!empty($attr)) echo html::hidden("products[$i]", $productID);?>
<?php if(!empty($attr)) echo html::hidden("branch[$i]", $branchID);?>
<?php if(!isset($branchGroups[$productID])) unset($allProducts[$productID]);?>
<?php if(isset($branchGroups[$productID][$branchID])) unset($branchGroups[$productID][$branchID]);?>
<?php if(isset($branchGroups[$productID]) and empty($branchGroups[$productID])) unset($allProducts[$productID]);?>
<?php $i++;?>
<?php endforeach;?>
<?php endif;?>
<?php endforeach;?>
</div>
+10 -7
View File
@@ -420,13 +420,15 @@ $lang->product->methodOrder[105] = 'unbindWhitelist';
/* Branch. */
$lang->resource->branch = new stdclass();
$lang->resource->branch->manage = 'manage';
$lang->resource->branch->create = 'create';
$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->manage = 'manage';
$lang->resource->branch->create = 'create';
$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->branch->methodOrder[0] = 'manage';
$lang->branch->methodOrder[5] = 'create';
@@ -435,6 +437,7 @@ $lang->branch->methodOrder[15] = 'close';
$lang->branch->methodOrder[20] = 'activate';
$lang->branch->methodOrder[25] = 'sort';
$lang->branch->methodOrder[30] = 'delete';
$lang->branch->methodOrder[35] = 'batchEdit';
/* Story. */
$lang->resource->story = new stdclass();
+2 -2
View File
@@ -542,7 +542,7 @@ class project extends control
$unmodifiableBranches = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
if(!isset($allProducts[$productID])) $allProducts[$productID] = $product->name;
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranches[$productID][$branchID] = $branchID;
@@ -1716,8 +1716,8 @@ class project extends control
$unmodifiableBranches = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
if(!isset($allProducts[$productID])) $allProducts[$productID] = $product->name;
$linkedBranches[$productID] = array();
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
foreach($branches[$productID] as $branchID => $branch)
{
$linkedBranches[$productID][$branchID] = $branchID;
+1 -1
View File
@@ -28,7 +28,7 @@ $(function()
$('#submit').click(function()
{
var products = [];
var products = new Array();
var existedBranch = false;
/* Determine whether the products of the same branch are linked. */
+1 -1
View File
@@ -96,7 +96,7 @@ $(function()
$('#submit').click(function()
{
var products = [];
var products = new Array();
var existedBranch = false;
/* Determine whether the products of the same branch are linked. */
+1 -1
View File
@@ -149,7 +149,7 @@
<?php $i = 0;?>
<?php foreach($linkedProducts as $product):?>
<?php foreach($linkedBranches[$product->id] as $branchID => $branch):?>
<?php $plans = $productPlans[$product->id][$branchID];?>
<?php $plans = isset($productPlans[$product->id][$branchID]) ? $productPlans[$product->id][$branchID] : array();?>
<div class="col-sm-4" id="plan<?php echo $i;?>" style="padding-right: 6px;"><?php echo html::select("plans[{$product->id}][{$branchID}]", $plans, $branches[$product->id][$branchID]->plan, "class='form-control chosen'");?></div>
<?php $i++;?>
<?php endforeach;?>
+24 -13
View File
@@ -287,37 +287,43 @@ class reportModel extends model
if($assign == 'noassign')
{
$members = $this->dao->select('t1.account,t2.name,t1.root')->from(TABLE_TEAM)->alias('t1')
$members = $this->dao->select('t1.account,t2.name,t1.root,t3.id as project,t3.name as projectname')->from(TABLE_TEAM)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t2.id = t1.root')
->leftJoin(TABLE_PROJECT)->alias('t3')->on('t3.id = t2.project')
->where('t2.status')->notin('cancel, closed, done, suspended')
->beginIF($dept)->andWhere('t1.account')->in(array_keys($deptUsers))->fi()
->andWhere('t1.type')->eq('execution')
->andWhere("t1.account NOT IN(SELECT `assignedTo` FROM " . TABLE_TASK . " WHERE `execution` = t1.`root` AND `status` NOT IN('cancel, closed, done, pause') AND assignedTo != '' GROUP BY assignedTo)")
->fetchGroup('account', 'name');
$workload = array();
$workload = array();
if(!empty($members))
{
foreach($members as $member => $executions)
{
$project = array();
if(!empty($executions))
{
foreach($executions as $name => $execution)
{
$workload[$member]['task'][$name]['count'] = 0;
$workload[$member]['task'][$name]['manhour'] = 0;
$workload[$member]['task'][$name]['executionID'] = $execution->root;
$workload[$member]['total']['count'] = 0;
$workload[$member]['total']['manhour'] = 0;
$project[$execution->projectname]['projectID'] = $execution->project;
$project[$execution->projectname]['execution'][$name]['executionID'] = $execution->root;
$project[$execution->projectname]['execution'][$name]['count'] = 0;
$project[$execution->projectname]['execution'][$name]['manhour'] = 0;
$workload[$member]['total']['count'] = 0;
$workload[$member]['total']['manhour'] = 0;
}
}
$workload[$member]['task']['project'] = $project;
}
}
return $workload;
}
$stmt = $this->dao->select('t1.*, t2.name as executionName')->from(TABLE_TASK)->alias('t1')
$stmt = $this->dao->select('t1.*, t2.name as executionName,t3.name as projectname')->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
->leftJoin(TABLE_PROJECT)->alias('t3')->on('t3.id = t2.project')
->where('t1.deleted')->eq(0)
->andWhere('t1.status')->in('wait,pause,doing')
->andWhere('t2.deleted')->eq(0)
@@ -371,17 +377,22 @@ class reportModel extends model
{
if($user)
{
$project = array();
foreach($userTasks as $task)
{
if(isset($parents[$task->id])) continue;
$workload[$user]['task'][$task->executionName]['count'] = isset($workload[$user]['task'][$task->executionName]['count']) ? $workload[$user]['task'][$task->executionName]['count'] + 1 : 1;
$workload[$user]['task'][$task->executionName]['manhour'] = isset($workload[$user]['task'][$task->executionName]['manhour']) ? $workload[$user]['task'][$task->executionName]['manhour'] + $task->left : $task->left;
$workload[$user]['task'][$task->executionName]['executionID'] = $task->execution;
$workload[$user]['total']['count'] = isset($workload[$user]['total']['count']) ? $workload[$user]['total']['count'] + 1 : 1;
$project[$task->projectname]['projectID'] = isset($project[$task->projectname]['projectID']) ? $project[$task->projectname]['projectID'] : $task->project;
$project[$task->projectname]['execution'][$task->executionName]['executionID'] = isset($project[$task->projectname]['execution'][$task->executionName]['executionID']) ? $project[$task->projectname]['execution'][$task->executionName]['executionID'] : $task->execution;
$project[$task->projectname]['execution'][$task->executionName]['count'] = isset($project[$task->projectname]['execution'][$task->executionName]['count']) ? $project[$task->projectname]['execution'][$task->executionName]['count'] + 1 : 1;
$project[$task->projectname]['execution'][$task->executionName]['manhour'] = isset($project[$task->projectname]['execution'][$task->executionName]['manhour']) ? $project[$task->projectname]['execution'][$task->executionName]['manhour'] + $task->left : $task->left;
$workload[$user]['total']['count'] = isset($workload[$user]['total']['count']) ? $workload[$user]['total']['count'] + 1 : 1;
$workload[$user]['total']['manhour'] = isset($workload[$user]['total']['manhour']) ? $workload[$user]['total']['manhour'] + $task->left : $task->left;
}
$workload[$user]['task']['project'] = $project;
}
}
}
unset($workload['closed']);
return $workload;
}
+22 -14
View File
@@ -70,6 +70,7 @@
<thead>
<tr class='colhead text-center'>
<th class="c-user"><?php echo $lang->report->user;?></th>
<th class="c-project"><?php echo $lang->report->project ;?>
<th><?php echo $lang->report->execution;?></th>
<th class="c-count"><?php echo $lang->report->task;?></th>
<th class="c-hours"><?php echo $lang->report->remain;?></th>
@@ -83,24 +84,31 @@
<?php foreach($workload as $account => $load):?>
<?php if(!isset($users[$account])) continue;?>
<tr class="text-center">
<td rowspan="<?php echo count($load['task']);?>"><?php echo $users[$account];?></td>
<?php $id = 1;?>
<?php foreach($load['task'] as $execution => $info):?>
<?php $class = $color ? 'rowcolor' : '';?>
<?php if($id != 1) echo '<tr class="text-center">';?>
<td title='<?php echo $execution?>' class="<?php echo $class;?> text-left"><?php echo html::a($this->createLink('execution', 'view', "executionID={$info['executionID']}"), $execution);?></td>
<td class="<?php echo $class;?>"><?php echo $info['count'];?></td>
<td class="<?php echo $class;?>"><?php echo $info['manhour'];?></td>
<?php if($id == 1):?>
<td rowspan="<?php echo count($load['task']);?>"><?php echo $load['total']['count'];?></td>
<td rowspan="<?php echo count($load['task']);?>"><?php echo $load['total']['manhour'];?></td>
<td rowspan="<?php echo count($load['task']);?>"><?php echo round($load['total']['manhour'] / $allHour * 100, 2) . '%';?></td>
<?php $idusername = 1; $countusername = 0;?>
<?php foreach($load['task']['project'] as $project => $info) foreach($info['execution'] as $exec => $execinfo) $countusername ++;?>
<td class="<?php echo $class;?>" rowspan="<?php echo $countusername;?>"><?php echo $users[$account];?></td>
<?php foreach($load['task']['project'] as $project => $info):?>
<?php $idprojectname = 1; $countprojectname = 0;?>
<?php foreach($info['execution'] as $exec) $countprojectname ++ ;?>
<?php foreach($info['execution'] as $exec => $execinfo):?>
<?php if($idprojectname != 1 || $idusername != 1) echo "<tr>";?>
<?php if($idprojectname == 1):?>
<td class="text-center" rowspan="<?php echo $countprojectname;?>"><?php echo html::a($this->createLink('project', 'view', "projectID={$info['projectID']}"), $project);?></td>
<?php endif;?>
<?php if($id != 1) echo '</tr>'; $id ++;?>
<td class="text-center"><?php echo html::a($this->createLink('execution', 'view', "executionID={$execinfo['executionID']}"), $exec);?></td>
<td class="text-center"><?php echo $execinfo['count'];?></td>
<td class="text-center"><?php echo $execinfo['manhour'];?></td>
<?php if($idusername == 1):?>
<td rowspan="<?php echo $countusername;?>"><?php echo $load['total']['count'];?></td>
<td rowspan="<?php echo $countusername;?>"><?php echo $load['total']['manhour'];?></td>
<td rowspan="<?php echo $countusername;?>"><?php echo round($load['total']['manhour'] / $allHour * 100, 2) . '%';?></td>
<?php endif;?>
<?php if($idprojectname != 1 || $idusername != 1) echo "</tr>"; $idprojectname ++; $idusername ++;?>
<?php $color = !$color;?>
<?php endforeach;?>
<?php endforeach;?>
</tr>
<?php endforeach;?>
<?php endforeach;?>
</tbody>
</table>
</div>
+1 -1
View File
@@ -3722,7 +3722,7 @@ class storyModel extends model
case 'title':
if($storyType == 'requirement') echo '<span class="label label-badge label-light">SR</span> ';
if($story->parent > 0 and isset($story->parentName)) $story->title = "{$story->parentName} / {$story->title}";
if($story->branch and isset($branches[$story->branch])) echo "<span class='label label-outline label-badge'>{$branches[$story->branch]}</span> ";
if($story->branch and isset($branches[$story->branch]) and !empty($this->config->product->browse->showBranch)) echo "<span class='label label-outline label-badge'>{$branches[$story->branch]}</span> ";
if($story->module and isset($modulePairs[$story->module])) echo "<span class='label label-gray label-badge'>{$modulePairs[$story->module]}</span> ";
if($story->parent > 0) echo '<span class="label label-badge label-light" title="' . $this->lang->story->children . '">' . $this->lang->story->childrenAB . '</span> ';
echo $canView ? html::a($storyLink, $story->title, '', "style='color: $story->color' data-app='$tab'") : "<span style='color: $story->color'>{$story->title}</span>";