diff --git a/module/bug/css/create.css b/module/bug/css/create.css index 746ea7a8b8..f7b68c49fb 100644 --- a/module/bug/css/create.css +++ b/module/bug/css/create.css @@ -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;} diff --git a/module/execution/control.php b/module/execution/control.php index acce62c2ea..e3d6492020 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -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(); diff --git a/module/execution/lang/zh-cn.php b/module/execution/lang/zh-cn.php index ce258165b5..decdee30da 100644 --- a/module/execution/lang/zh-cn.php +++ b/module/execution/lang/zh-cn.php @@ -351,8 +351,8 @@ $lang->execution->howToUpdateBurn = "关联{$lang->SRCommon}。"; -$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 = '选择一个部门的成员'; diff --git a/module/project/control.php b/module/project/control.php index 76f901238a..6a5787404b 100755 --- a/module/project/control.php +++ b/module/project/control.php @@ -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(); diff --git a/module/project/model.php b/module/project/model.php index 21467dd1af..8d4afe8a87 100644 --- a/module/project/model.php +++ b/module/project/model.php @@ -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(); } diff --git a/module/projectrelease/control.php b/module/projectrelease/control.php index e69975257a..abcca88bf6 100644 --- a/module/projectrelease/control.php +++ b/module/projectrelease/control.php @@ -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(); } diff --git a/module/projectrelease/view/view.html.php b/module/projectrelease/view/view.html.php index b3ef89b38b..19ed80d00d 100644 --- a/module/projectrelease/view/view.html.php +++ b/module/projectrelease/view/view.html.php @@ -71,6 +71,7 @@ priAB);?> story->title);?> + build->common;?> openedByAB);?> story->estimateAB);?> statusAB);?> @@ -98,6 +99,7 @@ echo html::a($storyLink,$story->title, '', "class='preview'"); ?> + buildName?> openedBy);?> estimate . $config->hourUnit;?> @@ -167,6 +169,7 @@ bug->title);?> bug->status);?> + bug->resolvedBuild;?> openedByAB);?> bug->openedDateAB);?> bug->resolvedByAB);?> @@ -191,6 +194,8 @@ processStatus('bug', $bug);?> + resolvedBuild, '');?> + openedBy);?> openedDate) ? '' : substr($bug->openedDate, 5, 11);?> resolvedBy);?> @@ -257,6 +262,7 @@ bug->severity;?>>bug->severityAB);?> bug->title);?> bug->status);?> + bug->openedBuild;?> openedByAB);?> bug->openedDateAB);?> actions;?> @@ -295,6 +301,11 @@ title, '', "class='preview'");?> processStatus('bug', $bug);?> + openedBuild) as $buildID) $openedBuildName .= zget($builds, $buildID, '') . ' '; + ?> + openedBy);?> openedDate?> diff --git a/module/release/control.php b/module/release/control.php index 1fedaf1531..5e2b5508f0 100644 --- a/module/release/control.php +++ b/module/release/control.php @@ -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') { diff --git a/module/release/view/view.html.php b/module/release/view/view.html.php index 6ec80c73bd..727313e5d5 100644 --- a/module/release/view/view.html.php +++ b/module/release/view/view.html.php @@ -73,6 +73,7 @@ pri;?>>priAB);?> story->title);?> + build->common;?> openedByAB);?> story->estimateAB);?> statusAB);?> @@ -100,6 +101,7 @@ echo html::a($storyLink,$story->title, '', "class='preview'"); ?> + buildName?> openedBy);?> estimate . $config->hourUnit;?> @@ -169,6 +171,7 @@ bug->title);?> bug->status);?> + bug->resolvedBuild;?> openedByAB);?> bug->openedDateAB);?> bug->resolvedByAB);?> @@ -193,6 +196,8 @@ processStatus('bug', $bug);?> + resolvedBuild, '');?> + openedBy);?> openedDate, 5, 11)?> resolvedBy);?> @@ -259,6 +264,7 @@ bug->severity;?>>bug->severityAB);?> bug->title);?> bug->status);?> + bug->openedBuild;?> openedByAB);?> bug->openedDateAB);?> actions;?> @@ -297,6 +303,11 @@ title, '', "class='preview'");?> processStatus('bug', $bug);?> + openedBuild) as $buildID) $openedBuildName .= zget($builds, $buildID, '') . ' '; + ?> + openedBy);?> openedDate?> diff --git a/module/testsuite/control.php b/module/testsuite/control.php index 98b4023dc1..87254e67b7 100644 --- a/module/testsuite/control.php +++ b/module/testsuite/control.php @@ -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); diff --git a/module/upgrade/model.php b/module/upgrade/model.php index 8edafef735..f3262ff000 100644 --- a/module/upgrade/model.php +++ b/module/upgrade/model.php @@ -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; + } } diff --git a/www/theme/default/style.css b/www/theme/default/style.css index 39a049cd7c..d878316af2 100644 --- a/www/theme/default/style.css +++ b/www/theme/default/style.css @@ -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;} diff --git a/xuanxuan/extension/xuan/im/ext/bot/zentao.bot.php b/xuanxuan/extension/xuan/im/ext/bot/zentao.bot.php new file mode 100644 index 0000000000..940530236b --- /dev/null +++ b/xuanxuan/extension/xuan/im/ext/bot/zentao.bot.php @@ -0,0 +1,991 @@ +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 = "
$paging
"; + + $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 .= "{$value}"; + + $thead = "{$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 .= ""; + if($isParent) $tr .= " {$lang->task->parentAB} "; + if($isChild) $tr .= " {$lang->task->childrenAB} "; + if($isMulti) $tr .= " " . zget($lang->task->modeList, $task->mode) . " "; + $tr .= "{$task->$key}"; + $tr .= ""; + break; + case 'status': + $tr .= "{$this->taskStatusList[$task->$key]}"; + break; + case 'assignedTo': + if($task->mode == 'multi') + { + $tr .= "{$lang->task->team}"; + } + else if($task->$key == 'closed') + { + $tr .= "Closed"; + } + else + { + $tr .= "{$this->users[$task->$key]}"; + } + 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 .= '
'; + $tr .= $canStart ? "" : "
"; + $tr .= $canFinish ? "" : "
"; + $tr .= $canClose ? "" : "
"; + $tr .= '
'; + break; + case 'estimate': + case 'consumed': + case 'left': + $tr .= "{$task->$key}h"; + break; + default: + $tr .= "{$task->$key}"; + } + } + $tbody .= "{$tr}"; + } + return "{$thead}{$tbody}
"; + } + + /** + * 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(); + } + } +} diff --git a/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php b/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php index cde654dd0b..c2285839a0 100644 --- a/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php +++ b/xuanxuan/extension/xuan/im/ext/lang/de/xuanxuan.php @@ -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 = <<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 = <<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 = <<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 = << 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'); +} diff --git a/xuanxuan/www/data/image/xuanbot.png b/xuanxuan/www/data/image/xuanbot.png new file mode 100644 index 0000000000..ff8f3fa2d7 Binary files /dev/null and b/xuanxuan/www/data/image/xuanbot.png differ