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

This commit is contained in:
lanzongjun
2022-12-07 09:13:19 +08:00
19 changed files with 1454 additions and 22 deletions
+1 -1
View File
@@ -57,4 +57,4 @@ html[lang='de'] #keywordsAddonLabel, .task {width: 73px;}
#openedBuildLabel {display: inline; position: relative; width: 50%; left: 14%;}
#bugTypeInputGroup > .table-col {width: 40% !important;}
.pri-text > .label-pri {padding: 1px 5px;}
.pri-text > .label-pri {padding: 2px 6px;}
+2 -1
View File
@@ -1579,6 +1579,7 @@ class execution extends control
$execution = $this->commonAction($executionID);
$executionID = $execution->id;
$deptID = $this->app->user->admin ? 0 : $this->app->user->dept;
$title = $execution->name . $this->lang->colon . $this->lang->execution->team;
$position[] = html::a($this->createLink('execution', 'browse', "executionID=$executionID"), $execution->name);
@@ -1586,7 +1587,7 @@ class execution extends control
$this->view->title = $title;
$this->view->position = $position;
$this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($this->app->user->dept, 'id');
$this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($deptID, 'id');
$this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
$this->display();
+2 -2
View File
@@ -351,8 +351,8 @@ $lang->execution->howToUpdateBurn = "<a href='https://api.zentao.net/goto.p
$lang->execution->whyNoStories = "看起来没有{$lang->SRCommon}可以关联。请检查下{$lang->executionCommon}关联的{$lang->productCommon}中有没有{$lang->SRCommon},而且要确保它们已经审核通过。";
$lang->execution->projectNoStories = "看起来没有{$lang->SRCommon}可以关联。请检查下项目中有没有{$lang->SRCommon},而且要确保它们已经审核通过。";
$lang->execution->productStories = "{$lang->executionCommon}关联的{$lang->SRCommon}是{$lang->productCommon}{$lang->SRCommon}的子集,并且只有评审通过的{$lang->SRCommon}才能关联。请<a href='%s'>关联{$lang->SRCommon}</a>。";
$lang->execution->haveBranchDraft = "有%s条草稿状态或非本{$lang->executionCommon}关联分支的{$lang->SRCommon}无法关联到该{$lang->executionCommon}";
$lang->execution->haveDraft = "有%s条草稿状态的{$lang->SRCommon}无法关联到该{$lang->executionCommon}";
$lang->execution->haveBranchDraft = "有%s条非激活状态或不是{$lang->executionCommon}关联分支的{$lang->SRCommon}无法导入";
$lang->execution->haveDraft = "有%s条非激活状态的{$lang->SRCommon}无法导入";
$lang->execution->doneExecutions = '已结束';
$lang->execution->selectDept = '选择部门';
$lang->execution->selectDeptTitle = '选择一个部门的成员';
+2 -1
View File
@@ -1610,11 +1610,12 @@ class project extends control
$this->project->setMenu($projectID);
$project = $this->project->getById($projectID);
$deptID = $this->app->user->admin ? 0 : $this->app->user->dept;
$this->view->title = $project->name . $this->lang->colon . $this->lang->project->team;
$this->view->projectID = $projectID;
$this->view->teamMembers = $this->project->getTeamMembers($projectID);
$this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($this->app->user->dept, 'id');
$this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($deptID, 'id');
$this->view->canBeChanged = common::canModify('project', $project);
$this->display();
+3 -3
View File
@@ -1583,7 +1583,7 @@ class projectModel extends model
->exec();
if(dao::isError()) return false;
if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent)) $this->updateShadowProduct($project);
if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent or $oldProject->acl != $project->acl)) $this->updateShadowProduct($project);
/* Get team and language item. */
$this->loadModel('user');
@@ -1744,7 +1744,7 @@ class projectModel extends model
if(!dao::isError())
{
if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent)) $this->updateShadowProduct($project);
if(!$oldProject->hasProduct and ($oldProject->name != $project->name or $oldProject->parent != $project->parent or $oldProject->acl != $project->acl)) $this->updateShadowProduct($project);
if(isset($project->parent))
{
@@ -2048,7 +2048,7 @@ class projectModel extends model
{
$product = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($project->id)->fetch('product');
$topProgram = !empty($project->parent) ? $this->loadModel('program')->getTopByID($project->parent) : 0;
$this->dao->update(TABLE_PRODUCT)->set('name')->eq($project->name)->set('program')->eq($topProgram)->where('id')->eq($product)->exec();
$this->dao->update(TABLE_PRODUCT)->set('name')->eq($project->name)->set('program')->eq($topProgram)->set('acl')->eq($project->acl)->where('id')->eq($product)->exec();
return !dao::isError();
}
+6 -4
View File
@@ -249,13 +249,14 @@ class projectrelease extends control
if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
$storyPager = new pager($type == 'story' ? $recTotal : 0, $recPerPage, $type == 'story' ? $pageID : 1);
$stories = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)
->where('id')->in($release->stories)
->andWhere('deleted')->eq(0)
$stories = $this->dao->select("t1.*,t2.id as buildID, t2.name as buildName,IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)->alias('t1')
->leftJoin(TABLE_BUILD)->alias('t2')->on("FIND_IN_SET(t1.id, t2.stories)")
->where('t1.id')->in($release->stories)
->andWhere('t1.deleted')->eq(0)
->beginIF($type == 'story')->orderBy($sort)->fi()
->page($storyPager)
->fetchAll('id');
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story');
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
$stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in(array_keys($stories))->andWhere('branch')->eq($release->branch)->fetchPairs('story', 'stage');
foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage;
@@ -304,6 +305,7 @@ class projectrelease extends control
$this->view->storyPager = $storyPager;
$this->view->bugPager = $bugPager;
$this->view->leftBugPager = $leftBugPager;
$this->view->builds = $this->loadModel('build')->getBuildPairs($release->product, 'all', 'withbranch|hasproject', 0, 'execution', '', false);
$this->display();
}
+11
View File
@@ -71,6 +71,7 @@
</th>
<th class='c-pri' title='<?php echo $lang->pri;?>'><?php common::printOrderLink('pri', $orderBy, $vars, $lang->priAB);?></th>
<th class="text-left"><?php common::printOrderLink('title', $orderBy, $vars, $lang->story->title);?></th>
<th class='c-build w-200px text-left'><?php echo $lang->build->common;?></th>
<th class='c-user'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='w-80px text-right' style='padding-right:20px'><?php common::printOrderLink('estimate', $orderBy, $vars, $lang->story->estimateAB);?></th>
<th class='w-90px'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->statusAB);?></th>
@@ -98,6 +99,7 @@
echo html::a($storyLink,$story->title, '', "class='preview'");
?>
</td>
<td class='text-left' title='<?php echo $story->buildName?>'><?php echo $story->buildName?></td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td class='text-right' style='padding-right:25px' title="<?php echo $story->estimate . ' ' . $lang->hourCommon;?>"><?php echo $story->estimate . $config->hourUnit;?></td>
<td>
@@ -167,6 +169,7 @@
</th>
<th class='text-left'><?php common::printOrderLink('title', $orderBy, $vars, $lang->bug->title);?></th>
<th class='w-100px'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->bug->status);?></th>
<th class='c-build w-200px text-left'><?php echo $lang->bug->resolvedBuild;?></th>
<th class='c-user'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='w-date'> <?php common::printOrderLink('openedDate', $orderBy, $vars, $lang->bug->openedDateAB);?></th>
<th class='c-user'> <?php common::printOrderLink('resolvedBy', $orderBy, $vars, $lang->bug->resolvedByAB);?></th>
@@ -191,6 +194,8 @@
<td>
<span class='status-bug status-<?php echo $bug->status?>'><?php echo $this->processStatus('bug', $bug);?></span>
</td>
<?php $resolvedBuildName = zget($builds, $bug->resolvedBuild, '');?>
<td class='text-left' title='<?php echo $resolvedBuildName?>'><?php echo $resolvedBuildName;?></td>
<td><?php echo zget($users, $bug->openedBy);?></td>
<td><?php echo helper::isZeroDate($bug->openedDate) ? '' : substr($bug->openedDate, 5, 11);?></td>
<td><?php echo zget($users, $bug->resolvedBy);?></td>
@@ -257,6 +262,7 @@
<th class='w-80px' title=<?php echo $lang->bug->severity;?>><?php common::printOrderLink('severity', $orderBy, $vars, $lang->bug->severityAB);?></th>
<th class='text-left'> <?php common::printOrderLink('title', $orderBy, $vars, $lang->bug->title);?></th>
<th class='w-100px'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->bug->status);?></th>
<th class='c-build w-200px text-left'><?php echo $lang->bug->openedBuild;?></th>
<th class='c-user'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='w-150px'> <?php common::printOrderLink('openedDate', $orderBy, $vars, $lang->bug->openedDateAB);?></th>
<th class='w-50px'> <?php echo $lang->actions;?></th>
@@ -295,6 +301,11 @@
</td>
<td class='text-left nobr' title='<?php echo $bug->title?>'><?php echo html::a($bugLink, $bug->title, '', "class='preview'");?></td>
<td><span class='status-<?php echo $bug->status?>'> <?php echo $this->processStatus('bug', $bug);?></span></td>
<?php
$openedBuildName = '';
foreach(explode(',', $bug->openedBuild) as $buildID) $openedBuildName .= zget($builds, $buildID, '') . ' ';
?>
<td class='text-left' title='<?php echo $openedBuildName?>'><?php echo $openedBuildName;?></td>
<td><?php echo zget($users, $bug->openedBy);?></td>
<td><?php echo $bug->openedDate?></td>
<td class='c-actions'>
+6 -4
View File
@@ -185,14 +185,15 @@ class release extends control
if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
$storyPager = new pager($type == 'story' ? $recTotal : 0, $recPerPage, $type == 'story' ? $pageID : 1);
$stories = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) as priOrder")->from(TABLE_STORY)
->where('id')->in($release->stories)
->andWhere('deleted')->eq(0)
$stories = $this->dao->select("t1.*,t2.id as buildID, t2.name as buildName, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) as priOrder")->from(TABLE_STORY)->alias('t1')
->leftJoin(TABLE_BUILD)->alias('t2')->on("FIND_IN_SET(t1.id, t2.stories)")
->where('t1.id')->in($release->stories)
->andWhere('t1.deleted')->eq(0)
->beginIF($type == 'story')->orderBy($sort)->fi()
->page($storyPager)
->fetchAll('id');
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story');
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
$stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in(array_keys($stories))->andWhere('branch')->eq($release->branch)->fetchPairs('story', 'stage');
foreach($stages as $storyID => $stage) $stories[$storyID]->stage = $stage;
@@ -238,6 +239,7 @@ class release extends control
$this->view->storyPager = $storyPager;
$this->view->bugPager = $bugPager;
$this->view->leftBugPager = $leftBugPager;
$this->view->builds = $this->loadModel('build')->getBuildPairs($release->product, 'all', 'withbranch|hasproject', 0, 'execution', '', false);
if($this->app->getViewType() == 'json')
{
+11
View File
@@ -73,6 +73,7 @@
</th>
<th class='c-pri' title=<?php echo $lang->pri;?>><?php common::printOrderLink('pri', $orderBy, $vars, $lang->priAB);?></th>
<th class="text-left"><?php common::printOrderLink('title', $orderBy, $vars, $lang->story->title);?></th>
<th class='c-build w-200px text-left'><?php echo $lang->build->common;?></th>
<th class='c-user'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='w-80px text-right'><?php common::printOrderLink('estimate', $orderBy, $vars, $lang->story->estimateAB);?></th>
<th class='w-90px text-center'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->statusAB);?></th>
@@ -100,6 +101,7 @@
echo html::a($storyLink,$story->title, '', "class='preview'");
?>
</td>
<td class='text-left' title='<?php echo $story->buildName?>'><?php echo $story->buildName?></td>
<td><?php echo zget($users, $story->openedBy);?></td>
<td class='text-right' title="<?php echo $story->estimate . ' ' . $lang->hourCommon;?>"><?php echo $story->estimate . $config->hourUnit;?></td>
<td class='text-center'>
@@ -169,6 +171,7 @@
</th>
<th class='text-left'><?php common::printOrderLink('title', $orderBy, $vars, $lang->bug->title);?></th>
<th class='w-100px'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->bug->status);?></th>
<th class='c-build w-200px text-left'><?php echo $lang->bug->resolvedBuild;?></th>
<th class='c-user'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='w-date'> <?php common::printOrderLink('openedDate', $orderBy, $vars, $lang->bug->openedDateAB);?></th>
<th class='c-user'> <?php common::printOrderLink('resolvedBy', $orderBy, $vars, $lang->bug->resolvedByAB);?></th>
@@ -193,6 +196,8 @@
<td>
<span class='status-bug status-<?php echo $bug->status?>'><?php echo $this->processStatus('bug', $bug);?></span>
</td>
<?php $resolvedBuildName = zget($builds, $bug->resolvedBuild, '');?>
<td class='text-left' title='<?php echo $resolvedBuildName?>'><?php echo $resolvedBuildName;?></td>
<td><?php echo zget($users, $bug->openedBy);?></td>
<td><?php echo substr($bug->openedDate, 5, 11)?></td>
<td><?php echo zget($users, $bug->resolvedBy);?></td>
@@ -259,6 +264,7 @@
<th class='w-80px' title=<?php echo $lang->bug->severity;?>><?php common::printOrderLink('severity', $orderBy, $vars, $lang->bug->severityAB);?></th>
<th class='text-left'> <?php common::printOrderLink('title', $orderBy, $vars, $lang->bug->title);?></th>
<th class='w-100px'> <?php common::printOrderLink('status', $orderBy, $vars, $lang->bug->status);?></th>
<th class='c-build w-200px text-left'><?php echo $lang->bug->openedBuild;?></th>
<th class='c-user'> <?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='w-150px'> <?php common::printOrderLink('openedDate', $orderBy, $vars, $lang->bug->openedDateAB);?></th>
<th class='w-50px'> <?php echo $lang->actions;?></th>
@@ -297,6 +303,11 @@
</td>
<td class='text-left nobr' title='<?php echo $bug->title?>'><?php echo html::a($bugLink, $bug->title, '', "class='preview'");?></td>
<td><span class='status-<?php echo $bug->status?>'> <?php echo $this->processStatus('bug', $bug);?></span></td>
<?php
$openedBuildName = '';
foreach(explode(',', $bug->openedBuild) as $buildID) $openedBuildName .= zget($builds, $buildID, '') . ' ';
?>
<td class='text-left' title='<?php echo $openedBuildName?>'><?php echo $openedBuildName;?></td>
<td><?php echo zget($users, $bug->openedBy);?></td>
<td><?php echo $bug->openedDate?></td>
<td class='c-actions'>
+1 -1
View File
@@ -89,7 +89,7 @@ class testsuite extends control
$privateNum++;
}
}
$suitesNum = !empty(count($suites, 0)) ? count($suites, 0) : 0;
$suitesNum = !empty($suites) ? count($suites) : 0;
$publicNum = $suitesNum - $privateNum;
$summary = str_replace(array('%total%', '%public%', '%private%'), array($suitesNum, $publicNum, $privateNum), $this->lang->testsuite->summary);
+30
View File
@@ -756,6 +756,9 @@ class upgradeModel extends model
case 'max3_3':
$this->addReviewIssusApprovalData();
break;
case 'max4_0_beta1':
$this->initReviewEfforts();
break;
}
}
@@ -7969,4 +7972,31 @@ class upgradeModel extends model
}
return true;
}
/**
* Init review efforts.
*
* @access public
* @return void
*/
public function initReviewEfforts()
{
$nodes = $this->dao->select('t1.id,t3.id as reviewID,t3.title,t2.id as approvalID,t1.extra as consumed')->from(TABLE_APPROVALNODE)->alias('t1')
->leftJoin(TABLE_APPROVAL)->alias('t2')->on("t1.approval=t2.id")
->leftJoin(TABLE_REVIEW)->alias('t3')->on("t2.objectID=t3.id")
->where('t3.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere('t2.objectType')->eq('review')
->andWhere('t1.extra')->ne('')
->andWhere('t1.extra')->ne(0)
->orderBy('t1.approval,t1.id')
->fetchAll('id');
$this->loadModel('effort');
foreach($nodes as $node)
{
$this->dao->delete()->from(TABLE_EFFORT)->where('objectType')->eq('review')->andWhere('objectID')->eq($node->reviewID)->exec();
$this->effort->create('review', $node->reviewID, (int)$node->consumed, $node->title, $node->approvalID);
}
return true;
}
}
+5 -5
View File
@@ -219,11 +219,11 @@ tbody tr td.c-actions .dividing-line {width: 1px; height: 16px; display: inline-
.pri-text > .label-pri {padding: 1px 5px;}
.pri-text > .label-pri, .pri-text > .label-severity {height: 20px;}
.pri-text > .label-severity {background-size: 80%; background-position: 2px -130px;}
.pri-text > .label-severity[data-severity="1"] {color: #FB2B2B; background-position: 2px 1px;}
.pri-text > .label-severity[data-severity="2"] {color: #FF8058; background-position: 2px -31px;}
.pri-text > .label-severity[data-severity="3"] {color: #FAAE1A; background-position: 2px -64px;}
.pri-text > .label-severity[data-severity="4"] {color: #B89664; background-position: 2px -97px;}
.pri-text > .label-severity {background-size: 85%; background-position: 1px -139px;}
.pri-text > .label-severity[data-severity="1"] {color: #FB2B2B; background-position: 2px 0px;}
.pri-text > .label-severity[data-severity="2"] {color: #FF8058; background-position: 2px -34px;}
.pri-text > .label-severity[data-severity="3"] {color: #FAAE1A; background-position: 1px -69px;}
.pri-text > .label-severity[data-severity="4"] {color: #B89664; background-position: 1px -104px;}
.flow-block .footerbar span.label-success {background-color: #0DBB7D;}
.flow-block .footerbar span.label-default {background-color: #9EA3B0;}
@@ -0,0 +1,991 @@
<?php
class zentaoBot extends xuanBot
{
/**
* Bot name.
*
* @var string
* @access public
*/
public $name = 'ZenTao';
/**
* Bot codename.
*
* @var string
* @access public
*/
public $code = 'zentao';
/**
* Command list, inits in init() since all commands are internal commands.
*
* @var array
* @access public
*/
public $commands = array();
/**
* Construct function, load lang of zentao and setup commands.
*
* @access public
* @return void
*/
public function __construct()
{
if(isset($_SESSION['clientLang']))
{
if($_SESSION['clientLang'] == 'zh-cn') $this->name = '禅道';
if($_SESSION['clientLang'] == 'zh-tw') $this->name = '禪道';
}
}
/**
* Init bot, load model.
*
* @access public
* @return void
*/
public function init()
{
$this->lang = $this->im->lang->im->bot->zentaoBot;
foreach(array('view', 'start', 'close', 'finish') as $command)
{
$this->commands[] = array('command' => $command, 'alias' => $this->lang->commands->$command->alias, 'description' => $this->lang->commands->$command->description, 'internal' => true);
}
$this->im->loadModel('task');
$this->im->loadModel('user');
$this->im->app->loadClass('pager', $static = true);
$this->users = array_filter($this->im->user->getPairs('noclosed|noletter'));
$this->taskStatusList = array_filter($this->im->lang->task->statusList);
$this->help = $this->lang->help;
$this->inited = true;
}
/**
* Close command.
*
* @param array $args bot command arguments
* @access public
* @return object|string
*/
public function close($args = array())
{
$args = $this->removeEqualSign($args);
foreach($args as $key => $value)
{
if(in_array(strtolower($value), $this->lang->condKeywords['task'])) unset($args[$key]);
}
$task = $this->closeTask($args);
return $task;
}
/**
* Finish command.
*
* @param array $args bot command arguments
* @access public
* @return object|string
*/
public function finish($args = array())
{
$originArgs = $args;
$args = array_filter(explode('=', implode('=', $args)));
foreach($args as $key => $value)
{
if(in_array(strtolower($value), $this->lang->condKeywords['task'])) unset($args[$key]);
}
return $this->finishTask($args, $originArgs);
}
/**
* View command.
*
* @param array $args bot command arguments
* @param int $userID
* @param object $user
* @access public
* @return object|string
*/
public function view($args = array(), $userID = 0, $user = null)
{
$pager = pager::init(0, 10, 1);
$pageSearchRegex = $this->lang->pageSearchRegex;
$pageIDKeywords = $this->lang->condKeywords['pageID'];
$args = array_filter($args, function($arg) use ($pager, $pageSearchRegex, $pageIDKeywords)
{
$matches = array();
preg_match($pageSearchRegex, $arg, $matches);
if(count($matches) > 2)
{
if(in_array(strtolower($matches[1]), $pageIDKeywords))
{
$pager->pageID = $matches[2];
}
else
{
$pager->recPerPage = $matches[2];
}
return false;
}
return true;
});
$originArgs = $args;
$args = $this->removeEqualSign($args);
foreach($args as $key => $value)
{
if(in_array(strtolower($value), $this->lang->condKeywords['task']))
{
unset($args[$key]);
$tasks = $this->viewTask($args, $user, $pager);
return $this->renderTask($tasks, $originArgs, $pager);
}
}
}
/**
* Parse bot command arguments.
*
* @param array $args bot command arguments
* @param array $keys
* @access public
* @return object
*/
public function parseArguments($args = array(), $keys = array())
{
$assignedToList = '';
$priList = '';
$statusList = '';
$idList = '';
$taskName = '';
$comment = '';
while(!empty($args))
{
$arg = array_shift($args);
if(isset($keys['pri']))
{
if(in_array(strtolower($arg), $this->lang->condKeywords['pri']))
{
$pris = array_shift($args);
$priList = $this->buildCondParamByModifier($pris, $priList, 'p');
continue;
}
if(preg_match('/p[1-4]/i', $arg) == 1)
{
$priList = $this->buildCondParamByModifier($arg, $priList, 'p');
continue;
}
}
if(isset($keys['id']))
{
if(in_array(strtolower($arg), $this->lang->condKeywords['id']))
{
$ids = array_shift($args);
$idList .= $this->buildCondParamByModifier($ids, $idList, '#');
continue;
}
if(preg_match('/#\d+/', $arg) == 1)
{
$idList .= $this->buildCondParamByModifier($arg, $idList, '#');
continue;
}
}
if(isset($keys['status']))
{
if(in_array(strtolower($arg), $this->lang->condKeywords['status']))
{
$statuses = array_shift($args);
$statusList = $this->buildCondParam($statuses, $statusList, $this->taskStatusList);
continue;
}
if($this->checkCondType($arg, $this->taskStatusList))
{
$statusList = $this->buildCondParam($arg, $statusList, $this->taskStatusList);
continue;
}
}
if(isset($keys['assignTo']))
{
if(in_array(strtolower($arg), $this->lang->condKeywords['assignTo']))
{
$usernames = array_shift($args);
$assignedToList = $this->buildCondParam($usernames, $assignedToList, $this->users);
continue;
}
if($this->checkCondType($arg, $this->users))
{
$assignedToList = $this->buildCondParam($arg, $assignedToList, $this->users);
continue;
}
}
if(isset($keys['comment']))
{
if(in_array(strtolower($arg), $this->lang->condKeywords['comment']))
{
$comment = array_shift($args);
}
else {
$comment = $arg;
}
continue;
}
if(isset($keys['taskName']))
{
if(in_array(strtolower($arg), $this->lang->condKeywords['taskName']))
{
$taskName = array_shift($args);
}
else
{
$taskName = $arg;
}
}
}
$conds = new stdClass();
if(isset($keys['pri'])) $conds->priList = trim($priList, ',');
if(isset($keys['assignTo'])) $conds->assignedToList = trim($assignedToList, ',');
if(isset($keys['status'])) $conds->statusList = trim($statusList, ',');
if(isset($keys['id'])) $conds->idList = trim($idList, ',');
if(isset($keys['taskName'])) $conds->taskName = $taskName;
if(isset($keys['comment'])) $conds->comment = $comment;
return $conds;
}
/**
* Check condition type.
*
* @param string $arg
* @param array $list
* @access public
* @return bool
*/
public function checkCondType($arg, $list)
{
$args = explode(',', $arg);
$array_inter = array_intersect($args, $list);
if(!empty($array_inter)) return true;
$list = array_flip($list);
$array_inter = array_intersect($args, $list);
if(!empty($array_inter)) return true;
return false;
}
/**
* Build condition param.
*
* @param string $arg bot command arguments
* @param string $condParam
* @param array $condArr
* @access public
* @return string
*/
public function buildCondParam($arg, $condParam, $condArr)
{
$arr = explode(',', $arg);
foreach($arr as $cond)
{
if(array_key_exists($cond, $condArr))
{
$condParam .= ',' . $cond;
}
else
{
$key = array_search($cond, $condArr);
if($key !== false) $condParam .= ',' . $key;
}
}
return $condParam;
}
/**
* Remove equal sign from bot command arguments.
*
* @param array $args bot command arguments
* @access public
* @return array
*/
public function removeEqualSign($args)
{
return array_filter(explode('=', implode('=', $args)));
}
/**
* Build condition param by modifier.
*
* @param array $args bot command arguments
* @param string $condParam
* @param string $modifier
* @access public
* @return string
*/
public function buildCondParamByModifier($arg, $condParam, $modifier)
{
$arg = strtolower($arg);
$arg = str_replace($modifier, '', $arg);
$condParam .= ',' . $arg;
return $condParam;
}
/**
* View task command.
*
* @param array $args bot command arguments
* @param object $user
* @param object $pager
* @access public
* @return object|string
*/
public function viewTask($args = array(), $user = null, $pager = null)
{
if(empty($args))
{
$conds = new stdClass();
$conds->assignedToList = is_object($user) ? $user->account : false;
$conds->statusList = 'wait,doing,done,pause,cancel';
return $this->im->task->getListByConds($conds, 'status_asc', 0, $pager);
}
$keys = array();
$keys['pri'] = true;
$keys['id'] = true;
$keys['status'] = true;
$keys['assignTo'] = true;
$keys['taskName'] = true;
$conds = $this->parseArguments($args, $keys);
return $this->im->task->getListByConds($conds, 'status_asc', 0, $pager);
}
/**
* Close task command.
*
* @param array $args bot command arguments
* @access public
* @return object|string
*/
public function closeTask($args)
{
$keys = array();
$keys['id'] = true;
$keys['comment'] = true;
$conds = $this->parseArguments($args, $keys);
$tasks = array_filter(explode(',', $conds->idList));
$taskID = array_pop($tasks);
$comment = $conds->comment;
if(!is_numeric($taskID)) return $this->lang->errors->invalidCommand;
$task = $this->loadEntry('task', 'get', array('taskID'=> $taskID));
if(empty($task)) return $this->lang->errors->emptyResult;
if($task->status != 'done' && $task->status != 'cancel') return sprintf($this->lang->errors->invalidStatus, $this->taskStatusList[$task->status]);
$task = $this->loadEntry('taskclose', 'post', array('taskID' => $taskID, 'comment' => $comment));
if($task->result == 'fail') return $task->message;
$link = str_replace('x.php', 'index.php', helper::createLink('task', 'view', "taskID=$taskID", 'html'));
$messages = new stdClass();
$messages->type = 'url';
$messages->url = common::getSysURL() . $link;
return array($this->lang->success, $messages);
}
/**
* Finish task.
*
* @param array $args
* @param array $originArgs
* @access public
* @return object|string
*/
private function finishTask($args, $originArgs)
{
$taskID = '';
$params = array();
while(!empty($args))
{
$arg = array_shift($args);
if(in_array(strtolower($arg), $this->lang->condKeywords['id']))
{
$taskID = array_shift($args);
$taskID = str_replace('#', '', $taskID);
}
elseif(preg_match('/#\d+/', $arg) == 1)
{
$taskID = str_replace('#', '', $arg);
}
else
{
parse_str(str_replace('amp;', '', end($originArgs)), $params);
}
}
if(!is_numeric($taskID)) return $this->lang->errors->invalidCommand;
$task = $this->loadEntry('task', 'get', array('taskID'=> $taskID));
if(!common::hasPriv('task', 'finish')) return $this->lang->errors->unauthorized;
if(empty($task) || isset($task->error)) return $this->lang->errors->emptyResult;
if($task->status != 'wait' && $task->status != 'doing') return sprintf($this->lang->errors->invalidStatus, $this->taskStatusList[$task->status]);
$reply = new stdClass();
$reply->messages = array();
$reply->responses = array();
$realStarted = $this->formatDate($task, 'realStarted');
$finishedDate = $this->formatDate($task, 'finishedDate');
if(empty($params))
{
$originCommand = rawurlencode($this->lang->finishTask . ' ' . implode(' ', $originArgs));
$json = (object)array
(
'title' => "#{$taskID} {$task->name}",
'submitLabel' => $this->im->lang->task->finish,
'inputs' =>
array(
(object)array
(
'name' => $this->im->lang->task->hasConsumed,
'type' => 'input',
'label' => $this->im->lang->task->hasConsumed,
'value' => $task->consumed,
'disabled' => true,
'addon' => $this->im->lang->task->hour,
),
(object)array
(
'name' => $this->im->lang->task->currentConsumed,
'type' => 'input',
'label' => $this->im->lang->task->currentConsumed,
'value' => 0,
'addon' => $this->im->lang->task->hour,
),
(object)array
(
'name' => $this->im->lang->task->realStarted,
'type' => 'datetime-local',
'label' => $this->im->lang->task->realStarted,
'value' => $realStarted,
),
(object)array
(
'name' => $this->im->lang->task->finishedDate,
'type' => 'datetime-local',
'label' => $this->im->lang->task->finishedDate,
'value' => $finishedDate,
),
),
);
$json = rawurlencode(json_encode($json));
$reply->messages[] = $this->lang->finish->tip;
$reply->messages[] = "[{$this->lang->finish->tipLinkTitle}](xxc://openFormAndSendToServerBySendbox/$json/$originCommand)";
}
else
{
$messageID = isset($params['messageId']) ? $params['messageId'] : '';
unset($params['messageId']);
$currentConsumed = 0;
foreach($params as $key => $value)
{
if(in_array(strtolower($key), $this->lang->condKeywords['realStarted'])) $realStarted = $value;
if(in_array(strtolower($key), $this->lang->condKeywords['finishedDate'])) $finishedDate = $value;
if(in_array(strtolower($key), $this->lang->condKeywords['currentConsumed'])) $currentConsumed = $value;
};
if(empty($realStarted) || empty($finishedDate)) return '';
$params = array
(
'currentConsumed' => $currentConsumed,
'realStarted' => $realStarted,
'finishedDate' => $finishedDate,
);
$task = $this->loadEntry('taskfinish', 'post', array_merge(array('taskID' => $taskID, 'assignedTo' => '', 'comment' => ''), $params));
if(isset($task->error))
{
foreach($task->error as $error) $reply->messages[] = is_array($error) ? implode(',', $error): $error;
return $reply;
}
$finishedDate = $this->formatDate($task, 'finishedDate', '', 'Y-m-d H:i:s');
$reply->messages[] = sprintf($this->lang->finish->done, $taskID, $finishedDate, $task->consumed);
$reply->messages[] = $this->renderTaskTable(array($task));
$reply->responses[] = $this->disableMarkdownLinkInMessage($messageID);
if(!empty($task->fromBug))
{
$reply->messages[] = sprintf($this->lang->finish->bugTip, $taskID);
$link = commonModel::getSysURL() . str_replace('x.php', 'index.php', helper::createLink('bug', 'view', "bugID={$task->fromBug}", 'html'));
$href = urlencode($link);
$reply->messages[] = "[{$this->lang->finish->bugTipLinkTitle}](xxc:openInApp/zentao-integrated/{$href})";
}
}
return $reply;
}
/**
* Format date.
*
* @param object $task
* @param string $field
* @param string $default
* @param string $format
* @return string
*/
private function formatDate($task, $field, $default = '', $format = 'Y-m-d\TH:i')
{
try
{
$datetime = new DateTime(empty($task->$field) ? 'now' : $task->$field, new DateTimeZone('UTC'));
$datetime->setTimezone(new DateTimeZone(date_default_timezone_get()));
return $datetime->format($format);
}
catch(Exception $e)
{
return $default;
}
}
/**
* Render tasks as table html.
*
* @param array $tasks
* @param array $originArgs
* @param pager $pager
* @return string|array
*/
public function renderTask($tasks, $originArgs, $pager)
{
$lang = $this->im->lang;
if(!$tasks || $pager->recTotal === 0) return $lang->task->noTask;
$sysURL = common::getSysURL();
if($pager->recTotal == 1)
{
$task = current($tasks);
$link = str_replace('x.php', 'index.php', helper::createLink('task', 'view', "taskID=$task->id", 'html'));
$messages = new stdclass();
$messages->type = 'url';
$messages->url = $sysURL . $link;
return array(sprintf($this->lang->tasksFound, $pager->recTotal), $messages);
}
$messages = array();
if($pager->pageID == 1)
{
$messages[] = sprintf($this->lang->tasksFound, $pager->recTotal);
}
$taskTable = $this->renderTaskTable($tasks);
$originCommand = $this->lang->viewTask . ' ' . implode(' ', $originArgs);
$paging = "$pager->pageID / $pager->pageTotal";
if($pager->pageID != 1)
{
$previousPageCommand = $originCommand . " {$this->lang->condKeywords['pageID'][1]}=" . ($pager->pageID - 1) . " {$this->lang->condKeywords['recPerPage'][1]}=" . $pager->recPerPage;
$previousPageCommand = rawurlencode($previousPageCommand);
$paging .= " [{$this->lang->prevPage}](xxc://sendContentToServerBySendbox/{$previousPageCommand})";
}
if($pager->pageID != $pager->pageTotal)
{
$nextPageCommand = $originCommand . " {$this->lang->condKeywords['pageID'][1]}=" . ($pager->pageID + 1) . " {$this->lang->condKeywords['recPerPage'][1]}=" . $pager->recPerPage;
$nextPageCommand = rawurlencode($nextPageCommand);
$paging .= " [{$this->lang->nextPage}](xxc://sendContentToServerBySendbox/{$nextPageCommand})";
}
$paging = "<div class=\"text-right\">$paging</div>";
$messages[] = $taskTable . $paging;
return $messages;
}
/**
* Render tasks as markdown table.
*
* @param array $tasks
* @access public
* @return void
*/
public function renderTaskTable($tasks)
{
$lang = $this->im->lang;
$sysURL = common::getSysURL();
$headMap = array('id'=>'ID', 'pri' => $lang->task->pri, 'name' => $lang->task->name, 'assignedTo' => $lang->task->assignedTo, 'status' => $lang->task->status, 'estimate' => $lang->task->estimateAB, 'consumed' => $lang->task->consumedAB, 'left' => $lang->task->leftAB, 'actions' => $lang->actions);
$thead = '';
foreach($headMap as $value) $thead .= "<th>{$value}</th>";
$thead = "<thead class='text-nowrap'><tr>{$thead}</tr></thead>";
$tbody = '';
foreach($tasks as $task)
{
$tr = '';
$link = str_replace('x.php', 'index.php', helper::createLink('task', 'view', "taskID=$task->id", 'html'));
$href = urlencode($sysURL . $link);
$isParent = $task->parent == -1;
$isChild = $task->parent > 0;
$isMulti = !empty($task->mode);
foreach($headMap as $key => $value)
{
switch($key)
{
case 'name':
$tr .= "<td>";
if($isParent) $tr .= " <span class='label label-sm circle'>{$lang->task->parentAB}</span>&nbsp;";
if($isChild) $tr .= " <span class='label label-sm circle'>{$lang->task->childrenAB}</span>&nbsp;";
if($isMulti) $tr .= " <span class='label label-sm circle'>" . zget($lang->task->modeList, $task->mode) . "</span>&nbsp;";
$tr .= "<a href='xxc:openInApp/zentao-integrated/{$href}'>{$task->$key}</a>";
$tr .= "</td>";
break;
case 'status':
$tr .= "<td class='text-nowrap'>{$this->taskStatusList[$task->$key]}</td>";
break;
case 'assignedTo':
if($task->mode == 'multi')
{
$tr .= "<td class='text-nowrap'>{$lang->task->team}</td>";
}
else if($task->$key == 'closed')
{
$tr .= "<td class='text-nowrap'>Closed</td>";
}
else
{
$tr .= "<td class='text-nowrap'>{$this->users[$task->$key]}</td>";
}
break;
case 'actions':
$startUrl = 'xxc://sendContentToServerBySendbox/' . "{$this->lang->startTask} #$task->id";
$finishUrl = 'xxc://sendContentToServerBySendbox/' . "{$this->lang->finishTask} #$task->id";
$closeUrl = 'xxc://sendContentToServerBySendbox/' . "{$this->lang->closeTask} #$task->id";
$canStart = in_array($task->status, array('wait', 'pause'));
$canFinish = in_array($task->status, array('wait', 'pause', 'doing'));
$canClose = in_array($task->status, array('done', 'cancel'));
/* Disable all if task has children. */
if($isParent)
{
$canStart = false;
$canFinish = false;
$canClose = false;
}
$tr .= '<td><div class="flex gap-xs">';
$tr .= $canStart ? "<a href='$startUrl'><i class='icon-zt-play'></i></a>" : "<div><i class='icon-zt-play disabled'></i></div>";
$tr .= $canFinish ? "<a href='$finishUrl'><i class='icon-zt-checked'></i></a>" : "<div><i class='icon-zt-checked disabled'></i></div>";
$tr .= $canClose ? "<a href='$closeUrl'><i class='icon-zt-off'></i></a>" : "<div><i class='icon-zt-off disabled'></i></div>";
$tr .= '</div></td>';
break;
case 'estimate':
case 'consumed':
case 'left':
$tr .= "<td class='text-nowrap'>{$task->$key}h</td>";
break;
default:
$tr .= "<td class='text-nowrap'>{$task->$key}</td>";
}
}
$tbody .= "<tr>{$tr}</tr>";
}
return "<table>{$thead}{$tbody}</table>";
}
/**
* Start task command.
*
* @param array $args bot command arguments
* @param int $userID
* @access public
* @return object|string
*/
public function start($args = array(), $userID = 0)
{
$originArgs = $args;
foreach($args as $key => $value)
{
if(in_array(strtolower($value), $this->lang->condKeywords['task']))
{
unset($args[$key]);
}
}
if(count($args) == 0) return $this->lang->errors->taskIDRequired;
$taskID = 0;
foreach($args as $key => $value)
{
if(strpos($value, '#') !== false)
{
$id = str_replace('#', '', $value);
if($id && is_numeric($id))
{
$taskID = $id;
unset($args[$key]);
}
else
{
if($args[$key + 1] && is_numeric($args[$key + 1]))
{
$taskID = $args[$key + 1];
unset($args[$key]);
unset($args[$key + 1]);
}
}
}
}
if(!$taskID) return $this->lang->errors->taskIDRequired;
$task = $this->loadEntry('task', 'get', array('taskID' => $taskID));
if(!$task) return $this->lang->errors->taskNotFound;
if($task->status != 'wait') return sprintf($this->lang->errors->invalidStatus, $this->taskStatusList[$task->status]);
$reply = new stdClass();
$reply->messages = array();
$reply->responses = array();
if(count($args) == 0)
{
$originCommand = rawurlencode($this->lang->startTask . ' ' . implode(' ', $originArgs));
$realStarted = $this->formatDate($task, 'realStarted');
$json = (object)array
(
'title' => "#{$taskID} {$task->name}",
'submitLabel' => $this->im->lang->task->start,
'inputs' => array
(
(object)array
(
'name' => $this->im->lang->task->realStarted,
'type' => 'datetime-local',
'label' => $this->im->lang->task->realStarted,
'value' => $realStarted,
),
(object)array
(
'name' => $this->im->lang->task->consumed,
'type' => 'number',
'label' => $this->im->lang->task->consumed,
'value' => $task->consumed,
'addon' => $this->im->lang->task->hour,
),
(object)array
(
'name' => $this->im->lang->task->left,
'type' => 'number',
'label' => $this->im->lang->task->left,
'value' => $task->left,
'addon' => $this->im->lang->task->hour,
'helpText' => $this->lang->start->finishWithZeroLeft,
)
),
);
$json = rawurlencode(json_encode($json));
$reply->messages[] = sprintf($this->lang->start->tip, $taskID);
$reply->messages[] = "[{$this->lang->start->tipLinkTitle}](xxc://openFormAndSendToServerBySendbox/$json/$originCommand)";
}
else
{
$hasFormArgs = false;
$left = $task->left;
$consumed = $task->consumed;
$realStarted = $task->realStarted;
foreach($args as $arg)
{
if(strpos($arg, 'messageId'))
{
$hasFormArgs = true;
$formArgs = explode('&', $arg);
$replyArgs = array();
foreach($formArgs as $formArg)
{
$item = explode('=', $formArg);
if(count($item) == 2)
{
$replyArgs[$item[0]] = $item[1];
}
}
foreach($replyArgs as $key => $value)
{
if(in_array(strtolower($key), $this->lang->condKeywords['consumed'])) $consumed = $value;
if(in_array(strtolower($key), $this->lang->condKeywords['realStarted'])) $realStarted = $value;
if(in_array(strtolower($key), $this->lang->condKeywords['left'])) $left = $value;
}
if($consumed == 0 && $left == 0) return $this->im->lang->task->noticeTaskStart;
$task = $this->loadEntry('taskStart', 'post', array
(
'taskID' => $taskID,
'left' => $left,
'realStarted' => $realStarted,
'consumed' => $consumed,
));
if($task->result and $task->result == 'fail')
{
$reply->messages[] = $task->message;
}
elseif(isset($task->error))
{
foreach($task->error as $error) $reply->messages[] = is_array($error) ? implode(',', $error): $error;
}
else
{
$reply->messages[] = sprintf($this->lang->effortRecorded, $taskID);
$reply->messages[] = $this->renderTaskTable(array($task));
$reply->responses[] = $this->disableMarkdownLinkInMessage($replyArgs['messageId']);
}
}
}
if(!$hasFormArgs)
{
while(!empty($args))
{
$arg = array_shift($args);
if(in_array(strtolower($arg), $this->lang->condKeywords['left']))
{
$left = array_shift($args);
}
elseif(in_array(strtolower($arg), $this->lang->condKeywords['consumed']))
{
$consumed = array_shift($args);
}
elseif(in_array(strtolower($arg), $this->lang->condKeywords['realStarted']))
{
$realStarted = array_shift($args);
}
}
$task = $this->loadEntry('taskStart', 'post', array
(
'taskID' => $taskID,
'left' => $left,
'realStarted' => $realStarted,
'consumed' => $consumed,
));
if($task->result and $task->result == 'fail')
{
$reply->messages[] = $task->message;
}
elseif(isset($task->error))
{
foreach($task->error as $error) $reply->messages[] = is_array($error) ? implode(',', $error): $error;
}
else
{
$reply->messages[] = sprintf($this->lang->effortRecorded, $taskID);
$reply->messages[] = $this->renderTaskTable(array($task));
}
}
}
return $reply;
}
/**
* Remove a Markdown link in the message and generate a response.
*
* @param $messageID
* @access public
* @return object
*/
private function disableMarkdownLinkInMessage($messageID)
{
if(!$messageID) return null;
$message = $this->im->message->getList('', array($messageID));
$message = current($message);
if($message->user != 'xuanbot') return null;
$message->content = preg_replace('/\[(.*?)\]\(.*?\)/', '$1', $message->content);
$this->im->message->setMessage($message);
$userID = array_filter(explode('&', $message->cgid), function($item)
{
return is_numeric($item);
});
$response = new stdclass();
$response->result = 'success';
$response->method = 'messageUpdate';
$response->users = $userID;
$response->data = array($message);
return $response;
}
/**
* Used to call zentao's API.
*
* @param string $entry
* @param string $action
* @param array $params
* @param string $version
* @access public
* @return object
*/
public function loadEntry($entry, $action, $params = array(), $version = 'v1')
{
try
{
$this->im->loadModel('user');
$user = $this->im->user->getByID($this->im->app->input['userID'], 'id');
/* Authorize him and save to session. */
$user->rights = $this->im->user->authorize($user->account);
$user->groups = $this->im->user->getGroups($user->account);
$user->view = $this->im->user->grantUserView($user->account, $user->rights['acls'], $user->rights['projects']);
$user->admin = strpos($this->im->app->company->admins, ",{$user->account},") !== false;
global $app;
$app->action = $action;
$app->user = $user;
include_once($this->im->app->appRoot . "framework/api/entry.class.php");
include_once($this->im->app->appRoot . "api/{$version}/entries/" . strtolower($entry) . ".php");
$entryName = $entry . 'Entry';
$entry = new $entryName();
if($action == 'post' || $action == 'put')
{
$entry->requestBody = (object)$params;
}
$content = call_user_func_array(array($entry, $action), $params);
return json_decode($content);
}
catch(EndResponseException $endResponseException)
{
return $endResponseException->getContent();
}
}
}
@@ -36,3 +36,89 @@ $lang->im->xxd->aes = 'Server-side AES';
$lang->im->xxdAESTip = 'This only affects server-side AES encryption between XXB and XXD.';
$lang->im->aesOptions['on'] = 'Enabled';
$lang->im->aesOptions['off'] = 'Disabled';
$lang->im->bot->zentaoBot = new stdclass();
$lang->im->bot->zentaoBot->name = 'ZenTao';
$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage)=(\d+)/';
$lang->im->bot->zentaoBot->commands = new stdclass();
$lang->im->bot->zentaoBot->commands->view = new stdclass();
$lang->im->bot->zentaoBot->commands->view->description = 'View task';
$lang->im->bot->zentaoBot->commands->start = new stdclass();
$lang->im->bot->zentaoBot->commands->start->description = 'Start task';
$lang->im->bot->zentaoBot->commands->close = new stdclass();
$lang->im->bot->zentaoBot->commands->close->description = 'Close task';
$lang->im->bot->zentaoBot->commands->finish = new stdclass();
$lang->im->bot->zentaoBot->commands->finish->description = 'Finish task';
$lang->im->bot->zentaoBot->condKeywords = array();
$lang->im->bot->zentaoBot->condKeywords['task'] = array('task');
$lang->im->bot->zentaoBot->condKeywords['pri'] = array('pri');
$lang->im->bot->zentaoBot->condKeywords['status'] = array('status');
$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('assignto', 'user');
$lang->im->bot->zentaoBot->condKeywords['id'] = array('id');
$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('taskname');
$lang->im->bot->zentaoBot->condKeywords['comment'] = array('comment');
$lang->im->bot->zentaoBot->condKeywords['left'] = array('left');
$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('consumed');
$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('realStarted');
$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID');
$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage');
$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('finishedDate');
$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('currentConsumed');
$lang->im->bot->zentaoBot->success = 'Command executed successfully.';
$lang->im->bot->zentaoBot->tasksFound = 'Found %d tasks.';
$lang->im->bot->zentaoBot->prevPage = 'Prev Page';
$lang->im->bot->zentaoBot->nextPage = 'Next Page';
$lang->im->bot->zentaoBot->effortRecorded = 'Effort recorded for task #%d.';
$lang->im->bot->zentaoBot->finishTask = 'finish';
$lang->im->bot->zentaoBot->closeTask = 'close';
$lang->im->bot->zentaoBot->startTask = 'start';
$lang->im->bot->zentaoBot->viewTask = 'view';
$lang->im->bot->zentaoBot->errors = new stdclass();
$lang->im->bot->zentaoBot->errors->emptyResult = 'No task found.';
$lang->im->bot->zentaoBot->errors->invalidCommand = 'Invalid command.';
$lang->im->bot->zentaoBot->errors->invalidStatus = 'Cannot perform such action on task with %s status.';
$lang->im->bot->zentaoBot->errors->unauthorized = 'You are not authorized to perform this action.';
$lang->im->bot->zentaoBot->errors->taskIDRequired = 'Task ID is required.';
$lang->im->bot->zentaoBot->errors->taskNotFound = 'Task not found.';
$lang->im->bot->zentaoBot->finish = new stdclass();
$lang->im->bot->zentaoBot->finish->tip = 'Click the link below to finish the task. Time consumed and starting time of this task are required.';
$lang->im->bot->zentaoBot->finish->tipLinkTitle = 'Finish Task';
$lang->im->bot->zentaoBot->finish->done = 'Task #%d is finished, ended at:%s, time consumed: %.1f hours.';
$lang->im->bot->zentaoBot->finish->bugTip = 'Task #%d is associated with a bug, you may mark the bug as resolved by clicking the link below.';
$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = 'Resolve Bug';
$lang->im->bot->zentaoBot->start = new stdclass();
$lang->im->bot->zentaoBot->start->tip = 'Click the link below to start task #%d.';
$lang->im->bot->zentaoBot->start->tipLinkTitle = 'Start Task';
$lang->im->bot->zentaoBot->start->finishWithZeroLeft = 'Hours left is 0 thus the task is finished.';
$lang->im->bot->zentaoBot->help = <<<EOT
### 1. Task command
Command:`view task condition...`
Example:`view task dev1 P1 doing` Displays tasks assigned to dev1, with priority P1 and status in progress
| Command | Description |
| ---- | ---- |
| view task | Show all open tasks under the current username |
| view task Name Keyword | Show tasks that match the name keyword |
| view task Assignor | Show tasks whose assignor is the entered value |
| view task Priority | Show tasks with priority as entered |
| view task Status | Show tasks with status as input |
| view task ID | Show tasks with ID as input |
### 2. Task Edit command
The Task Edit command supports making status changes to tasks.
| Command | Description |
| ---- | ---- |
| start task #ID | Start the task and record its consumption/remaining hours |
| complete task #ID | Complete the task and record its consumption/remaining hours |
| close task #ID | Close the task and record its consumption/remaining work hours |
EOT;
@@ -36,3 +36,89 @@ $lang->im->xxd->aes = 'Server-side AES';
$lang->im->xxdAESTip = 'This only affects server-side AES encryption between XXB and XXD.';
$lang->im->aesOptions['on'] = 'Enabled';
$lang->im->aesOptions['off'] = 'Disabled';
$lang->im->bot->zentaoBot = new stdclass();
$lang->im->bot->zentaoBot->name = 'ZenTao';
$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage)=(\d+)/';
$lang->im->bot->zentaoBot->commands = new stdclass();
$lang->im->bot->zentaoBot->commands->view = new stdclass();
$lang->im->bot->zentaoBot->commands->view->description = 'View task';
$lang->im->bot->zentaoBot->commands->start = new stdclass();
$lang->im->bot->zentaoBot->commands->start->description = 'Start task';
$lang->im->bot->zentaoBot->commands->close = new stdclass();
$lang->im->bot->zentaoBot->commands->close->description = 'Close task';
$lang->im->bot->zentaoBot->commands->finish = new stdclass();
$lang->im->bot->zentaoBot->commands->finish->description = 'Finish task';
$lang->im->bot->zentaoBot->condKeywords = array();
$lang->im->bot->zentaoBot->condKeywords['task'] = array('task');
$lang->im->bot->zentaoBot->condKeywords['pri'] = array('pri');
$lang->im->bot->zentaoBot->condKeywords['status'] = array('status');
$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('assignto', 'user');
$lang->im->bot->zentaoBot->condKeywords['id'] = array('id');
$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('taskname');
$lang->im->bot->zentaoBot->condKeywords['comment'] = array('comment');
$lang->im->bot->zentaoBot->condKeywords['left'] = array('left');
$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('consumed');
$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('realStarted');
$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID');
$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage');
$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('finishedDate');
$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('currentConsumed');
$lang->im->bot->zentaoBot->success = 'Command executed successfully.';
$lang->im->bot->zentaoBot->tasksFound = 'Found %d tasks.';
$lang->im->bot->zentaoBot->prevPage = 'Prev Page';
$lang->im->bot->zentaoBot->nextPage = 'Next Page';
$lang->im->bot->zentaoBot->effortRecorded = 'Effort recorded for task #%d.';
$lang->im->bot->zentaoBot->finishTask = 'finish';
$lang->im->bot->zentaoBot->closeTask = 'close';
$lang->im->bot->zentaoBot->startTask = 'start';
$lang->im->bot->zentaoBot->viewTask = 'view';
$lang->im->bot->zentaoBot->errors = new stdclass();
$lang->im->bot->zentaoBot->errors->emptyResult = 'No task found.';
$lang->im->bot->zentaoBot->errors->invalidCommand = 'Invalid command.';
$lang->im->bot->zentaoBot->errors->invalidStatus = 'Cannot perform such action on task with %s status.';
$lang->im->bot->zentaoBot->errors->unauthorized = 'You are not authorized to perform this action.';
$lang->im->bot->zentaoBot->errors->taskIDRequired = 'Task ID is required.';
$lang->im->bot->zentaoBot->errors->taskNotFound = 'Task not found.';
$lang->im->bot->zentaoBot->finish = new stdclass();
$lang->im->bot->zentaoBot->finish->tip = 'Click the link below to finish the task. Time consumed and starting time of this task are required.';
$lang->im->bot->zentaoBot->finish->tipLinkTitle = 'Finish Task';
$lang->im->bot->zentaoBot->finish->done = 'Task #%d is finished, ended at:%s, time consumed: %.1f hours.';
$lang->im->bot->zentaoBot->finish->bugTip = 'Task #%d is associated with a bug, you may mark the bug as resolved by clicking the link below.';
$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = 'Resolve Bug';
$lang->im->bot->zentaoBot->start = new stdclass();
$lang->im->bot->zentaoBot->start->tip = 'Click the link below to start task #%d.';
$lang->im->bot->zentaoBot->start->tipLinkTitle = 'Start Task';
$lang->im->bot->zentaoBot->start->finishWithZeroLeft = 'Hours left is 0 thus the task is finished.';
$lang->im->bot->zentaoBot->help = <<<EOT
### 1. Task command
Command:`view task condition...`
Example:`view task dev1 P1 doing` Displays tasks assigned to dev1, with priority P1 and status in progress
| Command | Description |
| ---- | ---- |
| view task | Show all open tasks under the current username |
| view task Name Keyword | Show tasks that match the name keyword |
| view task Assignor | Show tasks whose assignor is the entered value |
| view task Priority | Show tasks with priority as entered |
| view task Status | Show tasks with status as input |
| view task ID | Show tasks with ID as input |
### 2. Task Edit command
The Task Edit command supports making status changes to tasks.
| Command | Description |
| ---- | ---- |
| start task #ID | Start the task and record its consumption/remaining hours |
| complete task #ID | Complete the task and record its consumption/remaining hours |
| close task #ID | Close the task and record its consumption/remaining work hours |
EOT;
@@ -36,3 +36,89 @@ $lang->im->xxd->aes = 'Server-side AES';
$lang->im->xxdAESTip = 'This only affects server-side AES encryption between XXB and XXD.';
$lang->im->aesOptions['on'] = 'Enabled';
$lang->im->aesOptions['off'] = 'Disabled';
$lang->im->bot->zentaoBot = new stdclass();
$lang->im->bot->zentaoBot->name = 'ZenTao';
$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage)=(\d+)/';
$lang->im->bot->zentaoBot->commands = new stdclass();
$lang->im->bot->zentaoBot->commands->view = new stdclass();
$lang->im->bot->zentaoBot->commands->view->description = 'View task';
$lang->im->bot->zentaoBot->commands->start = new stdclass();
$lang->im->bot->zentaoBot->commands->start->description = 'Start task';
$lang->im->bot->zentaoBot->commands->close = new stdclass();
$lang->im->bot->zentaoBot->commands->close->description = 'Close task';
$lang->im->bot->zentaoBot->commands->finish = new stdclass();
$lang->im->bot->zentaoBot->commands->finish->description = 'Finish task';
$lang->im->bot->zentaoBot->condKeywords = array();
$lang->im->bot->zentaoBot->condKeywords['task'] = array('task');
$lang->im->bot->zentaoBot->condKeywords['pri'] = array('pri');
$lang->im->bot->zentaoBot->condKeywords['status'] = array('status');
$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('assignto', 'user');
$lang->im->bot->zentaoBot->condKeywords['id'] = array('id');
$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('taskname');
$lang->im->bot->zentaoBot->condKeywords['comment'] = array('comment');
$lang->im->bot->zentaoBot->condKeywords['left'] = array('left');
$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('consumed');
$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('realStarted');
$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID');
$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage');
$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('finishedDate');
$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('currentConsumed');
$lang->im->bot->zentaoBot->success = 'Command executed successfully.';
$lang->im->bot->zentaoBot->tasksFound = 'Found %d tasks.';
$lang->im->bot->zentaoBot->prevPage = 'Prev Page';
$lang->im->bot->zentaoBot->nextPage = 'Next Page';
$lang->im->bot->zentaoBot->effortRecorded = 'Effort recorded for task #%d.';
$lang->im->bot->zentaoBot->finishTask = 'finish';
$lang->im->bot->zentaoBot->closeTask = 'close';
$lang->im->bot->zentaoBot->startTask = 'start';
$lang->im->bot->zentaoBot->viewTask = 'view';
$lang->im->bot->zentaoBot->errors = new stdclass();
$lang->im->bot->zentaoBot->errors->emptyResult = 'No task found.';
$lang->im->bot->zentaoBot->errors->invalidCommand = 'Invalid command.';
$lang->im->bot->zentaoBot->errors->invalidStatus = 'Cannot perform such action on task with %s status.';
$lang->im->bot->zentaoBot->errors->unauthorized = 'You are not authorized to perform this action.';
$lang->im->bot->zentaoBot->errors->taskIDRequired = 'Task ID is required.';
$lang->im->bot->zentaoBot->errors->taskNotFound = 'Task not found.';
$lang->im->bot->zentaoBot->finish = new stdclass();
$lang->im->bot->zentaoBot->finish->tip = 'Click the link below to finish the task. Time consumed and starting time of this task are required.';
$lang->im->bot->zentaoBot->finish->tipLinkTitle = 'Finish Task';
$lang->im->bot->zentaoBot->finish->done = 'Task #%d is finished, ended at:%s, time consumed: %.1f hours.';
$lang->im->bot->zentaoBot->finish->bugTip = 'Task #%d is associated with a bug, you may mark the bug as resolved by clicking the link below.';
$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = 'Resolve Bug';
$lang->im->bot->zentaoBot->start = new stdclass();
$lang->im->bot->zentaoBot->start->tip = 'Click the link below to start task #%d.';
$lang->im->bot->zentaoBot->start->tipLinkTitle = 'Start Task';
$lang->im->bot->zentaoBot->start->finishWithZeroLeft = 'Hours left is 0 thus the task is finished.';
$lang->im->bot->zentaoBot->help = <<<EOT
### 1. Task command
Command:`view task condition...`
Example:`view task dev1 P1 doing` Displays tasks assigned to dev1, with priority P1 and status in progress
| Command | Description |
| ---- | ---- |
| view task | Show all open tasks under the current username |
| view task Name Keyword | Show tasks that match the name keyword |
| view task Assignor | Show tasks whose assignor is the entered value |
| view task Priority | Show tasks with priority as entered |
| view task Status | Show tasks with status as input |
| view task ID | Show tasks with ID as input |
### 2. Task Edit command
The Task Edit command supports making status changes to tasks.
| Command | Description |
| ---- | ---- |
| start task #ID | Start the task and record its consumption/remaining hours |
| complete task #ID | Complete the task and record its consumption/remaining hours |
| close task #ID | Close the task and record its consumption/remaining work hours |
EOT;
@@ -36,3 +36,98 @@ $lang->im->xxd->aes = '服务端通信 AES';
$lang->im->xxdAESTip = '该设置仅针对 XXB 和 XXD 之间的通讯加密,不影响客户端通讯加密。';
$lang->im->aesOptions['on'] = '开启';
$lang->im->aesOptions['off'] = '关闭';
$lang->im->bot->commonName = '阿道';
$lang->im->bot->welcome->title = '哈喽~我是你的助手阿道';
$lang->im->bot->upgradeWelcome->title = '哈喽~我是你的助手阿道';
$lang->im->bot->zentaoBot = new stdclass();
$lang->im->bot->zentaoBot->name = '禅道';
$lang->im->bot->zentaoBot->pageSearchRegex = '/(pageID|recPerPage|页码|每页数量|頁碼|每頁數量)=(\d+)/';
$lang->im->bot->zentaoBot->commands = new stdclass();
$lang->im->bot->zentaoBot->commands->view = new stdclass();
$lang->im->bot->zentaoBot->commands->view->description = '查看任务';
$lang->im->bot->zentaoBot->commands->view->alias = array('查看', '搜索', '查询', '筛选');
$lang->im->bot->zentaoBot->commands->start = new stdclass();
$lang->im->bot->zentaoBot->commands->start->description = '开始任务';
$lang->im->bot->zentaoBot->commands->start->alias = array('开始', '开始任务');
$lang->im->bot->zentaoBot->commands->close = new stdclass();
$lang->im->bot->zentaoBot->commands->close->description = '关闭任务';
$lang->im->bot->zentaoBot->commands->close->alias = array('关闭', '关闭任务');
$lang->im->bot->zentaoBot->commands->finish = new stdclass();
$lang->im->bot->zentaoBot->commands->finish->description = '完成任务';
$lang->im->bot->zentaoBot->commands->finish->alias = array('完成', '完成任务');
$lang->im->bot->zentaoBot->condKeywords = array();
$lang->im->bot->zentaoBot->condKeywords['task'] = array('任务', 'task');
$lang->im->bot->zentaoBot->condKeywords['pri'] = array('优先级', 'pri');
$lang->im->bot->zentaoBot->condKeywords['status'] = array('状态', 'status');
$lang->im->bot->zentaoBot->condKeywords['assignTo'] = array('指派人', '指派给', 'assignto', 'user');
$lang->im->bot->zentaoBot->condKeywords['id'] = array('编号', 'id');
$lang->im->bot->zentaoBot->condKeywords['taskName'] = array('任务名', '任务名称', 'taskname');
$lang->im->bot->zentaoBot->condKeywords['comment'] = array('备注', 'comment');
$lang->im->bot->zentaoBot->condKeywords['left'] = array('预计剩余', 'left');
$lang->im->bot->zentaoBot->condKeywords['consumed'] = array('总计消耗', 'consumed');
$lang->im->bot->zentaoBot->condKeywords['realStarted'] = array('实际开始', 'realStarted');
$lang->im->bot->zentaoBot->condKeywords['pageID'] = array('pageID', '页码', '頁碼');
$lang->im->bot->zentaoBot->condKeywords['recPerPage'] = array('recPerPage', '每页数量', '每頁數量');
$lang->im->bot->zentaoBot->condKeywords['finishedDate'] = array('实际完成', 'finishedDate');
$lang->im->bot->zentaoBot->condKeywords['currentConsumed'] = array('本次消耗', 'currentConsumed');
$lang->im->bot->zentaoBot->success = '指令执行完成';
$lang->im->bot->zentaoBot->tasksFound = '为您匹配到 %d 项任务';
$lang->im->bot->zentaoBot->prevPage = '上一页';
$lang->im->bot->zentaoBot->nextPage = '下一页';
$lang->im->bot->zentaoBot->effortRecorded = '任务 #%d 已完成工时信息填写';
$lang->im->bot->zentaoBot->finishTask = '完成任务';
$lang->im->bot->zentaoBot->closeTask = '关闭任务';
$lang->im->bot->zentaoBot->startTask = '开始任务';
$lang->im->bot->zentaoBot->viewTask = '查看任务';
$lang->im->bot->zentaoBot->errors = new stdclass();
$lang->im->bot->zentaoBot->errors->emptyResult = '未查询到相关匹配信息';
$lang->im->bot->zentaoBot->errors->invalidCommand = '无法识别该指令';
$lang->im->bot->zentaoBot->errors->invalidStatus = '检测到该任务为%s状态,无法实现指令操作';
$lang->im->bot->zentaoBot->errors->unauthorized = '您无权操作此任务';
$lang->im->bot->zentaoBot->errors->taskIDRequired = '请输入任务编号';
$lang->im->bot->zentaoBot->errors->taskNotFound = '任务不存在';
$lang->im->bot->zentaoBot->finish = new stdclass();
$lang->im->bot->zentaoBot->finish->tip = '完成任务指令需要填入工时与记录起始时间,请点击下方入口';
$lang->im->bot->zentaoBot->finish->tipLinkTitle = '工时记录';
$lang->im->bot->zentaoBot->finish->done = '任务 #%d 已完成,完成时间:%s,消耗:%.1f 小时';
$lang->im->bot->zentaoBot->finish->bugTip = '检测到任务 #%d 关联相关 Bug,您可以点击以下链接进行处理';
$lang->im->bot->zentaoBot->finish->bugTipLinkTitle = '关联 Bug 处理';
$lang->im->bot->zentaoBot->start = new stdclass();
$lang->im->bot->zentaoBot->start->tip = '点击链接开始任务 #%d';
$lang->im->bot->zentaoBot->start->tipLinkTitle = '开始任务';
$lang->im->bot->zentaoBot->start->finishWithZeroLeft = '剩余工时为 0,任务将被标记为"已完成"';
$lang->im->bot->zentaoBot->help = <<<EOT
### 1. 任务查询指令
指令:`查看 任务 条件a 值a1,值a2 条件b 值b1,值b2···条件n 值n1,值n2`
示例:`查看 任务 阿道 P1 进行中` 显示指派给阿道、优先级为P1且状态为进行中的任务
| 命令 | 描述 |
| ---- | ---- |
| 查看 任务 | 显示当前用户名下所有未关闭的任务 |
| 查看 任务 名称关键字 | 显示匹配到名称关键字的任务 |
| 查看 任务 指派人 | 显示指派人为输入值的任务 |
| 查看 任务 优先级 | 显示优先级为输入值的任务 |
| 查看 任务 状态 | 显示状态为输入值的任务 |
| 查看 任务 ID | 显示ID为输入值的任务 |
### 2. 任务编辑指令
任务编辑指令支持对任务进行状态变更。
| 命令 | 描述 |
| ---- | ---- |
| 开始 任务 #ID | 开始任务并记录其消耗/剩余工时 |
| 完成 任务 #ID | 完成任务并记录其消耗/剩余工时 |
| 关闭 任务 #ID | 关闭任务并记录其消耗/剩余工时 |
EOT;
@@ -0,0 +1,30 @@
<?php
/**
* Get task list by conditions.
*
* @param object $conds
* @param string $orderBy
* @param int $limit
* @param object $pager
* @access public
* @return array
*/
public function getListByConds($conds, $orderBy = 'id_desc', $limit = 0, $pager = null)
{
foreach(array('priList' => array(), 'assignedToList' => array(), 'statusList' => array(), 'idList' => array(), 'taskName' => '') as $condKey => $defaultValue)
{
if(!isset($conds->$condKey)) $conds->$condKey = $defaultValue;
}
return $this->dao->select('*')->from(TABLE_TASK)
->where('deleted')->eq(0)
->beginIF(!empty($conds->priList))->andWhere('pri')->in($conds->priList)->fi()
->beginIF(!empty($conds->assignedToList))->andWhere('assignedTo')->in($conds->assignedToList)->fi()
->beginIF(!empty($conds->statusList))->andWhere('status')->in($conds->statusList)->fi()
->beginIF(!empty($conds->idList))->andWhere('id')->in($conds->idList)->fi()
->beginIF(!empty($conds->taskName))->andWhere('name')->like("%{$conds->taskName}%")
->orderBy($orderBy)
->beginIF($limit > 0)->limit($limit)->fi()
->page($pager)
->fetchAll('id');
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB