From 5c103233e362280780dcfcbda9bf225d9dd6de56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E8=BE=B0=E8=BD=A9?= Date: Fri, 8 Jul 2022 10:54:34 +0800 Subject: [PATCH 1/3] * Edit design module lang. --- module/common/lang/en.php | 8 +- module/design/lang/en.php | 2 +- module/story:q/config.php | 239 + module/story:q/control.php | 2660 +++++++++ module/story:q/css/affected.css | 2 + module/story:q/css/batchclose.css | 2 + module/story:q/css/batchcreate.css | 12 + module/story:q/css/batchedit.css | 11 + module/story:q/css/bugs.css | 4 + module/story:q/css/cases.css | 4 + module/story:q/css/change.css | 5 + module/story:q/css/close.en.css | 1 + module/story:q/css/close.vi.css | 1 + module/story:q/css/close.zh-cn.css | 1 + module/story:q/css/close.zh-tw.css | 1 + module/story:q/css/common.css | 19 + module/story:q/css/create.css | 9 + module/story:q/css/create.en.css | 1 + module/story:q/css/create.vi.css | 0 module/story:q/css/create.zh-cn.css | 0 module/story:q/css/edit.css | 1 + module/story:q/css/edit.en.css | 2 + module/story:q/css/edit.vi.css | 2 + module/story:q/css/edit.zh-cn.css | 2 + module/story:q/css/edit.zh-tw.css | 2 + module/story:q/css/linkstory.css | 1 + module/story:q/css/report.css | 8 + module/story:q/css/review.en.css | 1 + module/story:q/css/review.vi.css | 1 + module/story:q/css/review.zh-cn.css | 1 + module/story:q/css/review.zh-tw.css | 1 + module/story:q/css/tasks.css | 4 + module/story:q/css/track.css | 1 + module/story:q/css/view.css | 5 + module/story:q/css/view.en.css | 2 + module/story:q/css/view.vi.css | 2 + module/story:q/css/view.zh-cn.css | 2 + module/story:q/css/view.zh-tw.css | 2 + module/story:q/css/x.change.css | 2 + module/story:q/css/x.create.css | 2 + module/story:q/css/x.edit.css | 7 + module/story:q/css/x.review.css | 2 + module/story:q/css/x.view.css | 17 + module/story:q/css/zerocase.en.css | 1 + module/story:q/css/zerocase.vi.css | 1 + module/story:q/css/zerocase.zh-cn.css | 1 + module/story:q/css/zerocase.zh-tw.css | 1 + module/story:q/js/batchclose.js | 26 + module/story:q/js/batchcreate.js | 138 + module/story:q/js/batchedit.js | 101 + module/story:q/js/change.js | 51 + module/story:q/js/close.js | 18 + module/story:q/js/common.js | 22 + module/story:q/js/create.js | 108 + module/story:q/js/edit.js | 35 + module/story:q/js/report.js | 19 + module/story:q/js/review.js | 94 + module/story:q/js/track.js | 0 module/story:q/js/view.js | 25 + module/story:q/js/x.create.js | 8 + module/story:q/js/x.view.js | 46 + module/story:q/js/zerocase.js | 4 + module/story:q/lang/de.php | 441 ++ module/story:q/lang/en.php | 441 ++ module/story:q/lang/fr.php | 441 ++ module/story:q/lang/vi.php | 441 ++ module/story:q/lang/zh-cn.php | 441 ++ module/story:q/lang/zh-tw.php | 416 ++ module/story:q/model.php | 5309 +++++++++++++++++ module/story:q/view/activate.html.php | 60 + module/story:q/view/affected.html.php | 102 + module/story:q/view/assignto.html.php | 53 + module/story:q/view/batchclose.html.php | 68 + module/story:q/view/batchcreate.html.php | 196 + module/story:q/view/batchedit.html.php | 159 + module/story:q/view/bugs.html.php | 40 + module/story:q/view/cases.html.php | 40 + module/story:q/view/change.html.php | 107 + module/story:q/view/close.html.php | 60 + module/story:q/view/create.html.php | 321 + module/story:q/view/edit.html.php | 292 + module/story:q/view/export.html.php | 13 + module/story:q/view/header.html.php | 174 + module/story:q/view/linkstory.html.php | 92 + .../story:q/view/processstorychange.html.php | 47 + module/story:q/view/report.html.php | 92 + module/story:q/view/review.html.php | 93 + module/story:q/view/sendmail.html.php | 35 + module/story:q/view/tasks.html.php | 77 + module/story:q/view/track.html.php | 120 + module/story:q/view/view.html.php | 587 ++ module/story:q/view/zerocase.html.php | 155 + 92 files changed, 14659 insertions(+), 5 deletions(-) create mode 100644 module/story:q/config.php create mode 100644 module/story:q/control.php create mode 100644 module/story:q/css/affected.css create mode 100644 module/story:q/css/batchclose.css create mode 100644 module/story:q/css/batchcreate.css create mode 100644 module/story:q/css/batchedit.css create mode 100644 module/story:q/css/bugs.css create mode 100644 module/story:q/css/cases.css create mode 100644 module/story:q/css/change.css create mode 100644 module/story:q/css/close.en.css create mode 100644 module/story:q/css/close.vi.css create mode 100644 module/story:q/css/close.zh-cn.css create mode 100644 module/story:q/css/close.zh-tw.css create mode 100644 module/story:q/css/common.css create mode 100644 module/story:q/css/create.css create mode 100644 module/story:q/css/create.en.css create mode 100644 module/story:q/css/create.vi.css create mode 100644 module/story:q/css/create.zh-cn.css create mode 100644 module/story:q/css/edit.css create mode 100644 module/story:q/css/edit.en.css create mode 100644 module/story:q/css/edit.vi.css create mode 100644 module/story:q/css/edit.zh-cn.css create mode 100644 module/story:q/css/edit.zh-tw.css create mode 100644 module/story:q/css/linkstory.css create mode 100644 module/story:q/css/report.css create mode 100644 module/story:q/css/review.en.css create mode 100644 module/story:q/css/review.vi.css create mode 100644 module/story:q/css/review.zh-cn.css create mode 100644 module/story:q/css/review.zh-tw.css create mode 100644 module/story:q/css/tasks.css create mode 100644 module/story:q/css/track.css create mode 100644 module/story:q/css/view.css create mode 100644 module/story:q/css/view.en.css create mode 100644 module/story:q/css/view.vi.css create mode 100644 module/story:q/css/view.zh-cn.css create mode 100644 module/story:q/css/view.zh-tw.css create mode 100644 module/story:q/css/x.change.css create mode 100644 module/story:q/css/x.create.css create mode 100644 module/story:q/css/x.edit.css create mode 100644 module/story:q/css/x.review.css create mode 100644 module/story:q/css/x.view.css create mode 100644 module/story:q/css/zerocase.en.css create mode 100644 module/story:q/css/zerocase.vi.css create mode 100644 module/story:q/css/zerocase.zh-cn.css create mode 100644 module/story:q/css/zerocase.zh-tw.css create mode 100755 module/story:q/js/batchclose.js create mode 100644 module/story:q/js/batchcreate.js create mode 100644 module/story:q/js/batchedit.js create mode 100644 module/story:q/js/change.js create mode 100644 module/story:q/js/close.js create mode 100644 module/story:q/js/common.js create mode 100644 module/story:q/js/create.js create mode 100644 module/story:q/js/edit.js create mode 100644 module/story:q/js/report.js create mode 100644 module/story:q/js/review.js create mode 100644 module/story:q/js/track.js create mode 100644 module/story:q/js/view.js create mode 100644 module/story:q/js/x.create.js create mode 100644 module/story:q/js/x.view.js create mode 100644 module/story:q/js/zerocase.js create mode 100644 module/story:q/lang/de.php create mode 100644 module/story:q/lang/en.php create mode 100644 module/story:q/lang/fr.php create mode 100644 module/story:q/lang/vi.php create mode 100644 module/story:q/lang/zh-cn.php create mode 100644 module/story:q/lang/zh-tw.php create mode 100644 module/story:q/model.php create mode 100644 module/story:q/view/activate.html.php create mode 100644 module/story:q/view/affected.html.php create mode 100644 module/story:q/view/assignto.html.php create mode 100755 module/story:q/view/batchclose.html.php create mode 100755 module/story:q/view/batchcreate.html.php create mode 100644 module/story:q/view/batchedit.html.php create mode 100644 module/story:q/view/bugs.html.php create mode 100644 module/story:q/view/cases.html.php create mode 100644 module/story:q/view/change.html.php create mode 100644 module/story:q/view/close.html.php create mode 100644 module/story:q/view/create.html.php create mode 100644 module/story:q/view/edit.html.php create mode 100644 module/story:q/view/export.html.php create mode 100644 module/story:q/view/header.html.php create mode 100644 module/story:q/view/linkstory.html.php create mode 100644 module/story:q/view/processstorychange.html.php create mode 100644 module/story:q/view/report.html.php create mode 100644 module/story:q/view/review.html.php create mode 100644 module/story:q/view/sendmail.html.php create mode 100644 module/story:q/view/tasks.html.php create mode 100644 module/story:q/view/track.html.php create mode 100644 module/story:q/view/view.html.php create mode 100644 module/story:q/view/zerocase.html.php diff --git a/module/common/lang/en.php b/module/common/lang/en.php index de9c5c39bd..cb3f305c44 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -186,10 +186,10 @@ $lang->upgrade->common = 'Update'; $lang->program->list = 'Program List'; $lang->program->kanban = 'Program Kanban'; $lang->design->common = 'Design'; -$lang->design->HLDS = 'HLDS'; -$lang->design->DDS = 'DDS'; -$lang->design->DBDS = 'DBDS'; -$lang->design->ADS = 'ADS'; +$lang->design->HLDS = 'Preliminary Design'; +$lang->design->DDS = 'Detailed Design'; +$lang->design->DBDS = 'Database Design'; +$lang->design->ADS = 'Interface Design'; $lang->stage->common = 'Stage'; $lang->stage->list = 'Stage List'; $lang->execution->list = "{$lang->executionCommon} List"; diff --git a/module/design/lang/en.php b/module/design/lang/en.php index 595098ea3f..9a25dfdb79 100644 --- a/module/design/lang/en.php +++ b/module/design/lang/en.php @@ -42,7 +42,7 @@ $lang->design->comment = 'Comment'; $lang->design->common = 'Design'; $lang->design->create = 'Create Design'; $lang->design->batchCreate = 'Batch Create'; -$lang->design->edit = 'Edit'; +$lang->design->edit = 'Change'; $lang->design->delete = 'Delete'; $lang->design->view = 'View'; $lang->design->browse = 'List'; diff --git a/module/story:q/config.php b/module/story:q/config.php new file mode 100644 index 0000000000..13992a4a8c --- /dev/null +++ b/module/story:q/config.php @@ -0,0 +1,239 @@ +story = new stdclass(); + +$config->story->batchCreate = 10; +$config->story->affectedFixedNum = 7; +$config->story->needReview = 1; +$config->story->removeFields = 'objectTypeList,productList,executionList,execution'; +$config->story->feedbackSource = array('customer', 'user', 'market', 'service', 'operation', 'support', 'forum'); + +$config->story->batchClose = new stdclass(); +$config->story->batchClose->columns = 10; +$config->story->create = new stdclass(); +$config->story->edit = new stdclass(); +$config->story->change = new stdclass(); +$config->story->close = new stdclass(); +$config->story->review = new stdclass(); +$config->story->create->requiredFields = 'title'; +$config->story->change->requiredFields = 'title'; +$config->story->close->requiredFields = 'closedReason'; +$config->story->review->requiredFields = ''; + +$config->story->editor = new stdclass(); +$config->story->editor->create = array('id' => 'spec,verify', 'tools' => 'simpleTools'); +$config->story->editor->change = array('id' => 'spec,verify,comment', 'tools' => 'simpleTools'); +$config->story->editor->edit = array('id' => 'comment', 'tools' => 'simpleTools'); +$config->story->editor->view = array('id' => 'comment,lastComment', 'tools' => 'simpleTools'); +$config->story->editor->close = array('id' => 'comment', 'tools' => 'simpleTools'); +$config->story->editor->review = array('id' => 'comment', 'tools' => 'simpleTools'); +$config->story->editor->activate = array('id' => 'comment', 'tools' => 'simpleTools'); +$config->story->editor->assignto = array('id' => 'comment', 'tools' => 'simpleTools'); + +$config->story->list = new stdclass(); +$config->story->list->exportFields = ' + id, product, branch, module, plan, source, sourceNote, title, spec, verify, keywords, + pri, estimate, status, stage, taskCountAB, bugCountAB, caseCountAB, + openedBy, openedDate, assignedTo, assignedDate, mailto, + reviewedBy, reviewedDate, + closedBy, closedDate, closedReason, + lastEditedBy, lastEditedDate, + childStories, linkStories, duplicateStory, files'; + +$config->story->list->customCreateFields = 'source,verify,pri,estimate,mailto,keywords'; +$config->story->list->customBatchCreateFields = 'plan,spec,source,verify,pri,estimate,review,keywords'; +$config->story->list->customBatchEditFields = 'branch,plan,estimate,pri,assignedTo,source,stage,closedBy,closedReason,keywords'; + +$config->story->list->actionsOpratedParentStory = ',edit,batchcreate,change,review,recall,'; + +$config->story->custom = new stdclass(); +$config->story->custom->createFields = $config->story->list->customCreateFields; +$config->story->custom->batchCreateFields = 'module,plan,spec,pri,estimate,review,%s'; +$config->story->custom->batchEditFields = 'branch,module,plan,estimate,pri,source,stage,closedBy,closedReason'; + +global $lang, $app; +$config->story->datatable = new stdclass(); +if($app->tab == 'execution') +{ + $config->story->datatable->defaultField = array('id','order', 'pri', 'title', 'plan', 'openedBy', 'assignedTo', 'estimate', 'status', 'stage', 'taskCount', 'actions'); +} +else +{ + $config->story->datatable->defaultField = array('id', 'pri', 'title', 'plan', 'openedBy', 'assignedTo', 'estimate', 'status', 'stage', 'taskCount', 'actions'); +} + +$config->story->datatable->fieldList['id']['title'] = 'idAB'; +$config->story->datatable->fieldList['id']['fixed'] = 'left'; +$config->story->datatable->fieldList['id']['width'] = '60'; +$config->story->datatable->fieldList['id']['required'] = 'yes'; + +if($app->tab == 'execution') +{ + $config->story->datatable->fieldList['order']['title'] = 'order'; + $config->story->datatable->fieldList['order']['fixed'] = 'left'; + $config->story->datatable->fieldList['order']['width'] = '60'; + $config->story->datatable->fieldList['order']['sort'] = 'no'; + $config->story->datatable->fieldList['order']['required'] = 'no'; + $config->story->datatable->fieldList['order']['name'] = $this->lang->story->order; +} + +$config->story->datatable->fieldList['pri']['title'] = 'priAB'; +$config->story->datatable->fieldList['pri']['fixed'] = 'left'; +$config->story->datatable->fieldList['pri']['width'] = '50'; +$config->story->datatable->fieldList['pri']['required'] = 'no'; +$config->story->datatable->fieldList['pri']['name'] = $this->lang->story->pri; + +$config->story->datatable->fieldList['title']['title'] = 'title'; +$config->story->datatable->fieldList['title']['fixed'] = 'left'; +$config->story->datatable->fieldList['title']['width'] = 'auto'; +$config->story->datatable->fieldList['title']['required'] = 'yes'; + +$config->story->datatable->fieldList['branch']['title'] = 'branch'; +$config->story->datatable->fieldList['branch']['fixed'] = 'no'; +$config->story->datatable->fieldList['branch']['width'] = '100'; +$config->story->datatable->fieldList['branch']['required'] = 'no'; + +$config->story->datatable->fieldList['keywords']['title'] = 'keywords'; +$config->story->datatable->fieldList['keywords']['fixed'] = 'no'; +$config->story->datatable->fieldList['keywords']['width'] = '100'; +$config->story->datatable->fieldList['keywords']['required'] = 'no'; + +$config->story->datatable->fieldList['plan']['title'] = 'planAB'; +$config->story->datatable->fieldList['plan']['fixed'] = 'no'; +$config->story->datatable->fieldList['plan']['width'] = '90'; +$config->story->datatable->fieldList['plan']['required'] = 'no'; + +$config->story->datatable->fieldList['source']['title'] = 'source'; +$config->story->datatable->fieldList['source']['fixed'] = 'no'; +$config->story->datatable->fieldList['source']['width'] = '90'; +$config->story->datatable->fieldList['source']['required'] = 'no'; + +$config->story->datatable->fieldList['sourceNote']['title'] = 'sourceNote'; +$config->story->datatable->fieldList['sourceNote']['fixed'] = 'no'; +$config->story->datatable->fieldList['sourceNote']['width'] = '90'; +$config->story->datatable->fieldList['sourceNote']['required'] = 'no'; + +$config->story->datatable->fieldList['category']['title'] = 'category'; +$config->story->datatable->fieldList['category']['fixed'] = 'no'; +$config->story->datatable->fieldList['category']['width'] = '60'; +$config->story->datatable->fieldList['category']['required'] = 'no'; + +$config->story->datatable->fieldList['status']['title'] = 'statusAB'; +$config->story->datatable->fieldList['status']['fixed'] = 'no'; +$config->story->datatable->fieldList['status']['width'] = '60'; +$config->story->datatable->fieldList['status']['required'] = 'no'; + +$config->story->datatable->fieldList['estimate']['title'] = 'estimateAB'; +$config->story->datatable->fieldList['estimate']['fixed'] = 'no'; +$config->story->datatable->fieldList['estimate']['width'] = '50'; +$config->story->datatable->fieldList['estimate']['required'] = 'no'; + +$config->story->datatable->fieldList['stage']['title'] = 'stageAB'; +$config->story->datatable->fieldList['stage']['fixed'] = 'no'; +$config->story->datatable->fieldList['stage']['width'] = '70'; +$config->story->datatable->fieldList['stage']['required'] = 'no'; + +$config->story->datatable->fieldList['openedBy']['title'] = 'openedByAB'; +$config->story->datatable->fieldList['openedBy']['fixed'] = 'no'; +$config->story->datatable->fieldList['openedBy']['width'] = '60'; +$config->story->datatable->fieldList['openedBy']['required'] = 'no'; + +$config->story->datatable->fieldList['openedDate']['title'] = 'openedDate'; +$config->story->datatable->fieldList['openedDate']['fixed'] = 'no'; +$config->story->datatable->fieldList['openedDate']['width'] = '90'; +$config->story->datatable->fieldList['openedDate']['required'] = 'no'; + +$config->story->datatable->fieldList['assignedTo']['title'] = 'assignedToAB'; +$config->story->datatable->fieldList['assignedTo']['fixed'] = 'no'; +$config->story->datatable->fieldList['assignedTo']['width'] = '90'; +$config->story->datatable->fieldList['assignedTo']['required'] = 'no'; + +$config->story->datatable->fieldList['assignedDate']['title'] = 'assignedDate'; +$config->story->datatable->fieldList['assignedDate']['fixed'] = 'no'; +$config->story->datatable->fieldList['assignedDate']['width'] = '90'; +$config->story->datatable->fieldList['assignedDate']['required'] = 'no'; + +$config->story->datatable->fieldList['reviewedBy']['title'] = 'reviewedBy'; +$config->story->datatable->fieldList['reviewedBy']['fixed'] = 'no'; +$config->story->datatable->fieldList['reviewedBy']['width'] = '80'; +$config->story->datatable->fieldList['reviewedBy']['required'] = 'no'; + +$config->story->datatable->fieldList['reviewedDate']['title'] = 'reviewedDate'; +$config->story->datatable->fieldList['reviewedDate']['fixed'] = 'no'; +$config->story->datatable->fieldList['reviewedDate']['width'] = '90'; +$config->story->datatable->fieldList['reviewedDate']['required'] = 'no'; + +$config->story->datatable->fieldList['closedBy']['title'] = 'closedBy'; +$config->story->datatable->fieldList['closedBy']['fixed'] = 'no'; +$config->story->datatable->fieldList['closedBy']['width'] = '80'; +$config->story->datatable->fieldList['closedBy']['required'] = 'no'; + +$config->story->datatable->fieldList['closedDate']['title'] = 'closedDate'; +$config->story->datatable->fieldList['closedDate']['fixed'] = 'no'; +$config->story->datatable->fieldList['closedDate']['width'] = '90'; +$config->story->datatable->fieldList['closedDate']['required'] = 'no'; + +$config->story->datatable->fieldList['closedReason']['title'] = 'closedReason'; +$config->story->datatable->fieldList['closedReason']['fixed'] = 'no'; +$config->story->datatable->fieldList['closedReason']['width'] = '90'; +$config->story->datatable->fieldList['closedReason']['required'] = 'no'; + +$config->story->datatable->fieldList['lastEditedBy']['title'] = 'lastEditedBy'; +$config->story->datatable->fieldList['lastEditedBy']['fixed'] = 'no'; +$config->story->datatable->fieldList['lastEditedBy']['width'] = '80'; +$config->story->datatable->fieldList['lastEditedBy']['required'] = 'no'; + +$config->story->datatable->fieldList['lastEditedDate']['title'] = 'lastEditedDate'; +$config->story->datatable->fieldList['lastEditedDate']['fixed'] = 'no'; +$config->story->datatable->fieldList['lastEditedDate']['width'] = '90'; +$config->story->datatable->fieldList['lastEditedDate']['required'] = 'no'; + +$config->story->datatable->fieldList['activatedDate']['title'] = 'activatedDate'; +$config->story->datatable->fieldList['activatedDate']['fixed'] = 'no'; +$config->story->datatable->fieldList['activatedDate']['width'] = '90'; +$config->story->datatable->fieldList['activatedDate']['required'] = 'no'; + +$config->story->datatable->fieldList['feedbackBy']['title'] = 'feedbackBy'; +$config->story->datatable->fieldList['feedbackBy']['fixed'] = 'no'; +$config->story->datatable->fieldList['feedbackBy']['width'] = '100'; +$config->story->datatable->fieldList['feedbackBy']['required'] = 'no'; + +$config->story->datatable->fieldList['notifyEmail']['title'] = 'notifyEmail'; +$config->story->datatable->fieldList['notifyEmail']['fixed'] = 'no'; +$config->story->datatable->fieldList['notifyEmail']['width'] = '100'; +$config->story->datatable->fieldList['notifyEmail']['required'] = 'no'; + +$config->story->datatable->fieldList['mailto']['title'] = 'mailto'; +$config->story->datatable->fieldList['mailto']['fixed'] = 'no'; +$config->story->datatable->fieldList['mailto']['width'] = '100'; +$config->story->datatable->fieldList['mailto']['required'] = 'no'; + +$config->story->datatable->fieldList['version']['title'] = 'version'; +$config->story->datatable->fieldList['version']['fixed'] = 'no'; +$config->story->datatable->fieldList['version']['width'] = '60'; +$config->story->datatable->fieldList['version']['required'] = 'no'; + +$config->story->datatable->fieldList['taskCount']['title'] = 'T'; +$config->story->datatable->fieldList['taskCount']['fixed'] = 'no'; +$config->story->datatable->fieldList['taskCount']['width'] = '30'; +$config->story->datatable->fieldList['taskCount']['required'] = 'no'; +$config->story->datatable->fieldList['taskCount']['sort'] = 'no'; +$config->story->datatable->fieldList['taskCount']['name'] = $lang->story->taskCount; + +$config->story->datatable->fieldList['bugCount']['title'] = 'B'; +$config->story->datatable->fieldList['bugCount']['fixed'] = 'no'; +$config->story->datatable->fieldList['bugCount']['width'] = '30'; +$config->story->datatable->fieldList['bugCount']['required'] = 'no'; +$config->story->datatable->fieldList['bugCount']['sort'] = 'no'; +$config->story->datatable->fieldList['bugCount']['name'] = $lang->story->bugCount; + +$config->story->datatable->fieldList['caseCount']['title'] = 'C'; +$config->story->datatable->fieldList['caseCount']['fixed'] = 'no'; +$config->story->datatable->fieldList['caseCount']['width'] = '30'; +$config->story->datatable->fieldList['caseCount']['required'] = 'no'; +$config->story->datatable->fieldList['caseCount']['sort'] = 'no'; +$config->story->datatable->fieldList['caseCount']['name'] = $lang->story->caseCount; + +$config->story->datatable->fieldList['actions']['title'] = 'actions'; +$config->story->datatable->fieldList['actions']['fixed'] = 'right'; +$config->story->datatable->fieldList['actions']['width'] = $app->tab == 'project' ? '220' : '200'; +$config->story->datatable->fieldList['actions']['required'] = 'yes'; diff --git a/module/story:q/control.php b/module/story:q/control.php new file mode 100644 index 0000000000..6b3a43444e --- /dev/null +++ b/module/story:q/control.php @@ -0,0 +1,2660 @@ + + * @package story + * @version $Id: control.php 5145 2013-07-15 06:47:26Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +class story extends control +{ + /** + * The construct function, load product, tree, user auto. + * + * @access public + * @return void + */ + public function __construct($module = '', $method = '') + { + parent::__construct($module, $method); + $this->loadModel('product'); + $this->loadModel('project'); + $this->loadModel('execution'); + $this->loadModel('tree'); + $this->loadModel('user'); + $this->loadModel('action'); + } + + /** + * Create a story. + * + * @param int $productID + * @param int $branch + * @param int $moduleID + * @param int $storyID + * @param int $objectID projectID|executionID + * @param int $bugID + * @param int $planID + * @param int $todoID + * @param string $extra for example feedbackID=0 + * @param string $type requirement|story + * @access public + * @return void + */ + public function create($productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $objectID = 0, $bugID = 0, $planID = 0, $todoID = 0, $extra = '', $type = 'story') + { + /* Whether there is a object to transfer story, for example feedback. */ + $extra = str_replace(array(',', ' '), array('&', ''), $extra); + parse_str($extra, $output); + + if($productID == 0 and $objectID == 0) $this->locate($this->createLink('product', 'create')); + + /* Get product id according to the project id when lite vision todo transfer story */ + if($this->config->vision == 'lite' and $productID == 0) + { + $product = $this->loadModel('product')->getProductPairsByProject($objectID); + if(!empty($project)) $productID = key($product); + } + + $this->story->replaceURLang($type); + if($this->app->tab == 'product') + { + $this->product->setMenu($productID); + } + else if($this->app->tab == 'project') + { + $objectID = empty($objectID) ? $this->session->project : $objectID; + $objects = $this->project->getPairsByProgram(); + $objectID = $this->project->saveState($objectID, $objects); + $this->project->setMenu($objectID); + } + else if($this->app->tab == 'execution') + { + $objectID = empty($objectID) ? $this->session->execution : $objectID; + $this->execution->setMenu($objectID); + $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); + if($execution->type == 'kanban') + { + $this->loadModel('kanban'); + $regionPairs = $this->kanban->getRegionPairs($execution->id, 0, 'execution'); + $regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs); + $lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'story'); + $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs); + + $this->view->executionType = $execution->type; + $this->view->regionID = $regionID; + $this->view->laneID = $laneID; + $this->view->regionPairs = $regionPairs; + $this->view->lanePairs = $lanePairs; + } + } + + foreach($output as $paramKey => $paramValue) + { + if(isset($this->config->story->fromObjects[$paramKey])) + { + $fromObjectIDKey = $paramKey; + $fromObjectID = $paramValue; + $fromObjectName = $this->config->story->fromObjects[$fromObjectIDKey]['name']; + $fromObjectAction = $this->config->story->fromObjects[$fromObjectIDKey]['action']; + break; + } + } + + /* If there is a object to transfer story, get it by getById function and set objectID,object in views. */ + if(isset($fromObjectID)) + { + $fromObject = $this->loadModel($fromObjectName)->getById($fromObjectID); + if(!$fromObject) return print(js::error($this->lang->notFound) . js::locate('back', 'parent')); + + $this->view->$fromObjectIDKey = $fromObjectID; + $this->view->$fromObjectName = $fromObject; + } + + if(!empty($_POST)) + { + $response['result'] = 'success'; + + setcookie('lastStoryModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false); + $storyResult = $this->story->create($objectID, $bugID, $from = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $extra); + if(!$storyResult or dao::isError()) + { + $response['result'] = 'fail'; + $response['message'] = dao::getError(); + return $this->send($response); + } + + $storyID = $storyResult['id']; + $productID = $this->post->product ? $this->post->product : $productID; + + if($storyResult['status'] == 'exists') + { + $response['message'] = sprintf($this->lang->duplicate, $this->lang->story->common); + if($objectID == 0) + { + $response['locate'] = $this->createLink('story', 'view', "storyID={$storyID}"); + } + else + { + $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); + $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; + $param = $execution->type == 'project' ? "projectID=$objectID&productID=$productID" : "executionID=$objectID"; + $response['locate'] = $this->createLink($moduleName, 'story', $param); + } + return $this->send($response); + } + + $action = $bugID == 0 ? 'Opened' : 'Frombug'; + $extra = $bugID == 0 ? '' : $bugID; + /* Record related action, for example FromFeedback. */ + if(isset($fromObjectID)) + { + $action = $fromObjectAction; + $extra = $fromObjectID; + } + $actionID = $this->action->create('story', $storyID, $action, '', $extra); + + if($objectID != 0) + { + $object = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch(); + if($object->type != 'project') + { + $actionType = $object->type == 'kanban' ? 'linked2kanban' : 'linked2execution'; + $this->loadModel('action')->create('story', $storyID, $actionType, '', $objectID); + } + else + { + $this->loadModel('action')->create('story', $storyID, 'linked2project', '', $objectID); + } + } + + if($todoID > 0) + { + $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec(); + $this->action->create('todo', $todoID, 'finished', '', "STORY:$storyID"); + } + + $message = $this->executeHooks($storyID); + if($message) $this->lang->saveSuccess = $message; + $response['message'] = $this->lang->saveSuccess; + + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $storyID)); + + /* If link from no head then reload. */ + if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent')); + + if($this->post->newStory) + { + $response['message'] = $this->lang->story->successSaved . $this->lang->story->newStory; + $response['locate'] = $this->createLink('story', 'create', "productID=$productID&branch=$branch&moduleID=$moduleID&story=0&objectID=$objectID&bugID=$bugID"); + return $this->send($response); + } + + $moduleID = $this->post->module ? $this->post->module : 0; + if($objectID == 0) + { + setcookie('storyModule', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); + $branchID = $this->post->branch ? $this->post->branch : $branch; + $response['locate'] = $this->createLink('product', 'browse', "productID=$productID&branch=$branchID&browseType=¶m=0&type=$type&orderBy=id_desc"); + if($this->session->storyList) $response['locate'] = $this->session->storyList; + } + else + { + setcookie('storyModuleParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, true); + $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); + $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; + $param = $execution->type == 'project' ? "projectID=$objectID&productID=$productID" : "executionID=$objectID&orderBy=order_desc&browseType=unclosed"; + $response['locate'] = $this->createLink($moduleName, 'story', $param); + } + if($this->app->getViewType() == 'xhtml') $response['locate'] = $this->createLink('story', 'view', "storyID=$storyID", 'html'); + return $this->send($response); + } + + /* Set products, users and module. */ + if($objectID != 0) + { + $onlyNoClosed = empty($this->config->CRProduct) ? 'noclosed' : ''; + $products = $this->product->getProductPairsByProject($objectID, $onlyNoClosed); + $productID = empty($productID) ? key($products) : $productID; + $product = $this->product->getById(($productID and array_key_exists($productID, $products)) ? $productID : key($products)); + $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct($productID, $objectID, 'noclosed|withMain') : array(); + $branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array(); + $branch = key($branches); + } + else + { + $products = array(); + $productList = $this->product->getOrderedProducts('noclosed'); + foreach($productList as $product) $products[$product->id] = $product->name; + $product = $this->product->getById($productID ? $productID : key($products)); + if(!isset($products[$product->id])) $products[$product->id] = $product->name; + $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array(); + } + + $users = $this->user->getPairs('pdfirst|noclosed|nodeleted'); + $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch); + if(empty($moduleOptionMenu)) return print(js::locate(helper::createLink('tree', 'browse', "productID=$productID&view=story"))); + + /* Init vars. */ + $source = ''; + $sourceNote = ''; + $pri = 3; + $estimate = ''; + $title = ''; + $spec = ''; + $verify = ''; + $keywords = ''; + $mailto = ''; + $color = ''; + + if($storyID > 0) + { + $story = $this->story->getByID($storyID); + $planID = $story->plan; + $source = $story->source; + $sourceNote = $story->sourceNote; + $color = $story->color; + $pri = $story->pri; + $productID = $story->product; + $moduleID = $story->module; + $estimate = $story->estimate; + $title = $story->title; + $spec = htmlSpecialString($story->spec); + $verify = htmlSpecialString($story->verify); + $keywords = $story->keywords; + $mailto = $story->mailto; + } + + if($bugID > 0) + { + $oldBug = $this->loadModel('bug')->getById($bugID); + $productID = $oldBug->product; + $source = 'bug'; + $title = $oldBug->title; + $keywords = $oldBug->keywords; + $spec = $oldBug->steps; + $pri = !empty($oldBug->pri) ? $oldBug->pri : '3'; + if(strpos($oldBug->mailto, $oldBug->openedBy) === false) + { + $mailto = $oldBug->mailto . $oldBug->openedBy . ','; + } + else + { + $mailto = $oldBug->mailto; + } + } + + if($todoID > 0) + { + $todo = $this->loadModel('todo')->getById($todoID); + $source = 'todo'; + $title = $todo->name; + $spec = $todo->desc; + $pri = $todo->pri; + } + + /* Replace the value of story that needs to be replaced with the value of the object that is transferred to story. */ + if(isset($fromObject)) + { + if(isset($this->config->story->fromObjects[$fromObjectIDKey]['source'])) + { + $sourceField = $this->config->story->fromObjects[$fromObjectIDKey]['source']; + $sourceUser = $this->loadModel('user')->getById($fromObject->{$sourceField}); + $source = $sourceUser->role; + $sourceNote = $sourceUser->realname; + } + else + { + $source = $fromObjectName; + $sourceNote = $fromObjectID; + } + + foreach($this->config->story->fromObjects[$fromObjectIDKey]['fields'] as $storyField => $fromObjectField) + { + $$storyField = $fromObject->{$fromObjectField}; + } + } + + /* Get block id of assinge to me. */ + $blockID = 0; + if(isonlybody()) + { + $blockID = $this->dao->select('id')->from(TABLE_BLOCK) + ->where('block')->eq('assingtome') + ->andWhere('module')->eq('my') + ->andWhere('account')->eq($this->app->user->account) + ->orderBy('order_desc') + ->fetch('id'); + } + + /* Get reviewers. */ + $reviewers = $product->reviewer; + if(!$reviewers and $product->acl != 'open') $reviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); + + /* Set Custom. */ + foreach(explode(',', $this->config->story->list->customCreateFields) as $field) $customFields[$field] = $this->lang->story->$field; + $this->view->customFields = $customFields; + $this->view->showFields = $this->config->story->custom->createFields; + + $this->view->title = $product->name . $this->lang->colon . $this->lang->story->create; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID&branch=$branch"), $product->name); + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $this->lang->story->create; + $this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('product', 'browse', "productID=$productID") : ''; + $this->view->products = $products; + $this->view->users = $users; + $this->view->moduleID = $moduleID ? $moduleID : (int)$this->cookie->lastStoryModule; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->plans = str_replace('2030-01-01', $this->lang->story->undetermined, $this->loadModel('productplan')->getPairsForStory($productID, $branch, 'skipParent|unexpired|noclosed')); + $this->view->planID = $planID; + $this->view->source = $source; + $this->view->sourceNote = $sourceNote; + $this->view->color = $color; + $this->view->pri = $pri; + $this->view->branch = $branch; + $this->view->branches = $branches; + $this->view->stories = $this->story->getParentStoryPairs($productID); + $this->view->productID = $productID; + $this->view->product = $product; + $this->view->reviewers = $this->user->getPairs('noclosed|nodeleted', '', 0, $reviewers); + $this->view->objectID = $objectID; + $this->view->estimate = $estimate; + $this->view->storyTitle = $title; + $this->view->spec = $spec; + $this->view->verify = $verify; + $this->view->keywords = $keywords; + $this->view->mailto = $mailto; + $this->view->blockID = $blockID; + $this->view->URS = $type == 'story' ? $this->story->getRequirements($productID) : ''; + $this->view->needReview = ($this->app->user->account == $product->PO || $objectID > 0 || $this->config->story->needReview == 0) ? "checked='checked'" : ""; + $this->view->type = $type; + + $this->display(); + } + + /** + * Create a batch stories. + * + * @param int $productID + * @param int $branch + * @param int $moduleID + * @param int $storyID + * @param int $executionID + * @param int $plan + * @param string $type requirement|story + * @param string $extra for example feedbackID=0 + * @access public + * @return void + */ + public function batchCreate($productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $executionID = 0, $plan = 0, $type = 'story', $extra = '') + { + /* Set menu. */ + if($executionID) + { + $execution = $this->dao->findById((int)$executionID)->from(TABLE_EXECUTION)->fetch(); + if($execution->type == 'project') + { + $this->project->setMenu($executionID); + $this->app->rawModule = 'projectstory'; + $this->lang->navGroup->story = 'project'; + $this->lang->product->menu = $this->lang->{$execution->model}->menu; + } + else + { + if($execution->type == 'kanban') + { + $this->loadModel('kanban'); + $extra = str_replace(array(',', ' '), array('&', ''), $extra); + parse_str($extra, $output); + + $regionPairs = $this->kanban->getRegionPairs($executionID, 0, 'execution'); + $regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs); + $lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'story'); + $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs); + + $this->view->regionID = $regionID; + $this->view->laneID = $laneID; + $this->view->regionPairs = $regionPairs; + $this->view->lanePairs = $lanePairs; + } + + $this->execution->setMenu($executionID); + $this->app->rawModule = 'execution'; + $this->lang->navGroup->story = 'execution'; + } + $this->view->execution = $execution; + } + else + { + $this->product->setMenu($productID, $branch); + } + + /* Clear title when switching products and set the session for the current product. */ + if($productID != $this->cookie->preProductID) unset($_SESSION['storyImagesFile']); + setcookie('preProductID', $productID, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true); + + $this->story->replaceURLang($type); + + /* Check can subdivide or not. */ + if($storyID) + { + $story = $this->story->getById($storyID); + if(($story->status != 'active' or $story->stage != 'wait' or $story->parent > 0) and $this->config->vision != 'lite') return print(js::alert($this->lang->story->errorNotSubdivide) . js::locate('back')); + } + + if(!empty($_POST)) + { + $mails = $this->story->batchCreate($productID, $branch, $type); + if(dao::isError()) return print(js::error(dao::getError())); + + $stories = array(); + foreach($mails as $mail) $stories[] = $mail->storyID; + + $lanes = array(); + if(isset($_POST['lanes'])) + { + foreach($mails as $i => $mail) $lanes[$mail->storyID] = $_POST['lanes'][$i]; + } + + /* Project or execution linked stories. */ + if($executionID) + { + $products = array(); + foreach($mails as $story) $products[$story->storyID] = $productID; + $this->execution->linkStory($executionID, $stories, $products, $extra, $lanes); + if($executionID != $this->session->project) $this->execution->linkStory($this->session->project, $stories, $products); + } + + /* If storyID not equal zero, subdivide this story to child stories and close it. */ + if($storyID and !empty($mails)) + { + $this->story->subdivide($storyID, $stories); + if(dao::isError()) return print(js::error(dao::getError())); + } + + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $stories)); + + if(isonlybody()) return print(js::closeModal('parent.parent', 'this')); + + if($storyID) + { + return print(js::locate(inlink('view', "storyID=$storyID"), 'parent')); + } + elseif($executionID) + { + setcookie('storyModuleParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); + $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; + $param = $execution->type == 'project' ? "projectID=$executionID&productID=$productID" : "executionID=$executionID&orderBy=id_desc&browseType=unclosed"; + $link = $this->createLink($moduleName, 'story', $param); + return print(js::locate($link, 'parent')); + } + else + { + setcookie('storyModule', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); + $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID=$productID&branch=$branch&browseType=unclosed&queryID=0&type=$type"); + return print(js::locate($locateLink, 'parent')); + } + } + + /* Set branch and module. */ + $product = $this->product->getById($productID); + $products = $this->product->getPairs(); + if($product) $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]); + + if($executionID != 0) + { + $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct($productID, $executionID, 'noclosed|withMain') : array(); + $branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array(); + $branch = key($branches); + } + else + { + $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array(); + } + + $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch); + $moduleOptionMenu['ditto'] = $this->lang->story->ditto; + + /* Get reviewers. */ + $reviewers = $product->reviewer; + if(!$reviewers and $product->acl != 'open') $reviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); + + /* Init vars. */ + $planID = $plan; + $pri = 0; + $estimate = ''; + $title = ''; + $spec = ''; + + /* Process upload images. */ + if($this->session->storyImagesFile) + { + $files = $this->session->storyImagesFile; + foreach($files as $fileName => $file) + { + $title = $file['title']; + $titles[$title] = $fileName; + } + $this->view->titles = $titles; + } + $plans = $this->loadModel('productplan')->getPairsForStory($productID, ($branch === 'all' or !in_array($branch, array_keys($branches))) ? 0 : $branch, 'skipParent|unexpired|noclosed'); + $plans['ditto'] = $this->lang->story->ditto; + + $priList = (array)$this->lang->story->priList; + $priList['ditto'] = $this->lang->story->ditto; + + $sourceList = (array)$this->lang->story->sourceList; + $sourceList['ditto'] = $this->lang->story->ditto; + + /* Set Custom*/ + foreach(explode(',', $this->config->story->list->customBatchCreateFields) as $field) + { + if($product->type != 'normal') $customFields[$product->type] = $this->lang->product->branchName[$product->type]; + $customFields[$field] = $this->lang->story->$field; + } + + if($product->type != 'normal') + { + $this->config->story->custom->batchCreateFields = sprintf($this->config->story->custom->batchCreateFields, $product->type); + } + else + { + $this->config->story->custom->batchCreateFields = trim(sprintf($this->config->story->custom->batchCreateFields, ''), ','); + } + + $showFields = $this->config->story->custom->batchCreateFields; + if($product->type == 'normal') + { + $showFields = str_replace(array(0 => ",branch,", 1 => ",platform,"), '', ",$showFields,"); + $showFields = trim($showFields, ','); + } + if($type == 'requirement') + { + unset($customFields['plan']); + $showFields = str_replace('plan', '', $showFields); + } + + $this->view->customFields = $customFields; + $this->view->showFields = $showFields; + + $this->view->title = $product->name . $this->lang->colon . ($storyID ? $this->lang->story->subdivide : $this->lang->story->batchCreate); + $this->view->productName = $product->name; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID&branch=$branch"), $product->name); + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $storyID ? $this->lang->story->subdivide : $this->lang->story->batchCreate; + $this->view->storyID = $storyID; + $this->view->products = $products; + $this->view->product = $product; + $this->view->moduleID = $moduleID; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->plans = $plans; + $this->view->reviewers = $this->user->getPairs('noclosed|nodeleted', '', 0, $reviewers); + $this->view->users = $this->user->getPairs('pdfirst|noclosed|nodeleted'); + $this->view->priList = $priList; + $this->view->sourceList = $sourceList; + $this->view->planID = $planID; + $this->view->pri = $pri; + $this->view->productID = $productID; + $this->view->estimate = $estimate; + $this->view->storyTitle = isset($story->title) ? $story->title : ''; + $this->view->spec = $spec; + $this->view->type = $type; + $this->view->branch = $branch; + $this->view->branches = $branches; + /* When the user is product owner or add story in project or not set review, the default is not to review. */ + $this->view->needReview = ($this->app->user->account == $product->PO || $executionID > 0 || $this->config->story->needReview == 0) ? 0 : 1; + $this->view->forceReview = $this->story->checkForceReview(); + $this->view->executionID = $executionID; + + $this->display(); + } + + /** + * The common action when edit or change a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function commonAction($storyID) + { + /* Get datas. */ + $story = $this->story->getById($storyID); + $product = $this->product->getById($story->product); + $products = $this->product->getPairs(); + $moduleOptionMenu = $this->tree->getOptionMenu($product->id, $viewType = 'story', 0, $story->branch); + + /* Set menu. */ + if($this->app->tab == 'project') + { + $this->loadModel('project')->setMenu($this->session->project); + } + elseif($this->app->tab == 'product') + { + $this->product->setMenu($product->id, $story->branch); + } + elseif($this->app->tab == 'execution') + { + $this->loadModel('execution')->setMenu($this->session->execution); + } + + $this->story->replaceURLang($story->type); + + /* Assign. */ + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); + $this->view->position[] = $this->lang->story->common; + $this->view->product = $product; + $this->view->products = $products; + $this->view->story = $story; + $this->view->moduleOptionMenu = $moduleOptionMenu; + $this->view->plans = $this->loadModel('productplan')->getPairs($product->id, 0, '', true); + $this->view->actions = $this->action->getList('story', $storyID); + } + + /** + * Edit a story. + * + * @param int $storyID + * @param string $kanbanGroup + * @access public + * @return void + */ + public function edit($storyID, $kanbanGroup = 'default') + { + if(!empty($_POST)) + { + $changes = $this->story->update($storyID); + if(dao::isError()) return print(js::error(dao::getError())); + if($this->post->comment != '' or !empty($changes)) + { + $action = !empty($changes) ? 'Edited' : 'Commented'; + $actionID = $this->action->create('story', $storyID, $action, $this->post->comment); + $this->action->logHistory($actionID, $changes); + + $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + if(isset($_POST['reviewer'])) $this->story->recordReviewAction($story); + } + + $this->executeHooks($storyID); + + if(isonlybody()) + { + $execution = $this->execution->getByID($this->session->execution); + if($this->app->tab == 'execution' and $execution->type == 'kanban') + { + $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; + $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', 0, $kanbanGroup, $rdSearchValue); + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); + } + else + { + return print(js::reload('parent.parent')); + } + } + if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $storyID)); + return print(js::locate($this->createLink($this->app->rawModule, 'view', "storyID=$storyID"), 'parent')); + } + + $this->commonAction($storyID); + + /* Sort products. */ + $myProducts = array(); + $othersProducts = array(); + $products = $this->loadModel('product')->getList(); + + foreach($products as $product) + { + if($product->status == 'normal' and $product->PO == $this->app->user->account) $myProducts[$product->id] = $product->name; + if($product->status == 'normal' and !($product->PO == $this->app->user->account)) $othersProducts[$product->id] = $product->name; + if($product->status == 'closed') continue; + } + $products = $myProducts + $othersProducts; + + /* Assign. */ + $story = $this->story->getById($storyID, 0, true); + $product = $this->product->getById($story->product); + $stories = $this->story->getParentStoryPairs($story->product, $story->parent); + if(isset($stories[$storyID])) unset($stories[$storyID]); + + /* Get users. */ + $users = $this->user->getPairs('pofirst|nodeleted|noclosed', "$story->assignedTo,$story->openedBy,$story->closedBy"); + + $isShowReviewer = false; + $reviewerList = $this->story->getReviewerPairs($story->id, $story->version); + $reviewerList = array_keys($reviewerList); + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + if(array_diff($reviewerList, $reviewedBy) and strpos('draft,changed', $story->status) !== false) $isShowReviewer = true; + + $reviewedReviewer = array(); + foreach($reviewedBy as $reviewer) $reviewedReviewer[] = zget($users, $reviewer); + + if($this->app->tab == 'project' or $this->app->tab == 'execution') + { + $objectID = $this->app->tab == 'project' ? $this->session->project : $this->session->execution; + $products = $this->product->getProductPairsByProject($objectID); + $this->view->objectID = $objectID; + } + + /* Display status of branch. */ + $branches = $this->loadModel('branch')->getList($product->id, isset($objectID) ? $objectID : 0, 'all'); + $branchOption = array(); + $branchTagOption = array(); + foreach($branches as $branchInfo) + { + $branchOption[$branchInfo->id] = $branchInfo->name; + $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); + } + + /* Get product reviewers. */ + $productReviewers = $product->reviewer; + if(!$productReviewers and $product->acl != 'open') $productReviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); + + /* Process the module when branch products are switched to normal products. */ + if($product->type == 'normal' and !empty($story->branch)) $this->view->moduleOptionMenu += $this->tree->getModulesName($story->module); + + $this->view->title = $this->lang->story->edit . "STORY" . $this->lang->colon . $this->view->story->title; + $this->view->position[] = $this->lang->story->edit; + $this->view->story = $story; + $this->view->stories = $stories; + $this->view->users = $users; + $this->view->product = $product; + $this->view->plans = $this->loadModel('productplan')->getPairsForStory($story->product, $story->branch == 0 ? 'all' : $story->branch, 'skipParent'); + $this->view->products = $products; + $this->view->branchOption = $branchOption; + $this->view->branchTagOption = $branchTagOption; + $this->view->reviewers = implode(',', $reviewerList); + $this->view->reviewedReviewer = $reviewedReviewer; + $this->view->productReviewers = $this->user->getPairs('noclosed|nodeleted', $reviewerList, 0, $productReviewers); + $this->view->isShowReviewer = $isShowReviewer; + $this->display(); + } + + /** + * Batch edit story. + * + * @param int $productID + * @param int $executionID + * @param int $branch + * @param string $storyType + * @param string $from + * @access public + * @return void + */ + public function batchEdit($productID = 0, $executionID = 0, $branch = 0, $storyType = 'story', $from = '') + { + $this->story->replaceURLang($storyType); + + if($this->app->tab == 'product') + { + $this->product->setMenu($productID); + } + else if($this->app->tab == 'project') + { + $this->project->setMenu($executionID); + } + else if($this->app->tab == 'execution') + { + $this->execution->setMenu($executionID); + } + else if($this->app->tab == 'qa') + { + $this->loadModel('qa')->setMenu('', $productID); + } + else if($this->app->tab == 'my') + { + $this->loadModel('my'); + if($from == 'work') $this->lang->my->menu->work['subModule'] = 'story'; + if($from == 'contribute') $this->lang->my->menu->contribute['subModule'] = 'story'; + } + + /* Load model. */ + $this->loadModel('productplan'); + + if($this->post->titles) + { + $allChanges = $this->story->batchUpdate(); + + if($allChanges) + { + foreach($allChanges as $storyID => $changes) + { + if(empty($changes)) continue; + + $actionID = $this->action->create('story', $storyID, 'Edited'); + $this->action->logHistory($actionID, $changes); + } + } + return print(js::locate($this->session->storyList, 'parent')); + } + + if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent')); + $storyIdList = $this->post->storyIdList; + $storyIdList = array_unique($storyIdList); + + /* Get edited stories. */ + $stories = $this->story->getByList($storyIdList); + + $this->loadModel('branch'); + if($productID and !$executionID) + { + $product = $this->product->getByID($productID); + $branchProduct = $product->type == 'normal' ? false : true; + + $branches = 0; + $branchTagOption = array(); + if($branchProduct) + { + $branches = $this->branch->getList($productID, $executionID, 'all'); + foreach($branches as $branchInfo) $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); + $branches = array_keys($branches); + } + + $modulePairs = $this->tree->getOptionMenu($productID, 'story', 0, $branches); + $moduleList = $branchProduct ? $modulePairs : array(0 => $modulePairs); + + $modules = array($productID => $moduleList); + $plans = array($productID => $this->productplan->getBranchPlanPairs($productID, '', true)); + $products = array($productID => $product); + $branchTagOption = array($productID => $branchTagOption); + } + else + { + $branchProduct = false; + $modules = array(); + $branchTagOption = array(); + $products = array(); + + if($executionID) + { + /* The stories of project or execution. */ + $execution = $this->execution->getByID($executionID); + $products = $this->loadModel('product')->getProducts($executionID); + } + else + { + /* The stories of my. */ + $productIdList = array(); + foreach($stories as $story) $productIdList[$story->product] = $story->product; + $products = $this->product->getByIdList($productIdList); + } + + foreach($products as $storyProduct) + { + $branches = 0; + if($storyProduct->type != 'normal') + { + $branches = $this->branch->getList($storyProduct->id, $executionID, 'all'); + foreach($branches as $branchInfo) $branches[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); + $branchTagOption[$storyProduct->id] = array(BRANCH_MAIN => $this->lang->branch->main) + $branches; + + $branches = array_keys($branches); + } + + $modulePairs = $this->tree->getOptionMenu($storyProduct->id, 'story', 0, $branches); + $modules[$storyProduct->id] = $storyProduct->type != 'normal' ? $modulePairs : array(0 => $modulePairs); + + $plans[$storyProduct->id] = $this->productplan->getBranchPlanPairs($storyProduct->id, $branches, true); + if(empty($plans[$storyProduct->id])) $plans[$storyProduct->id][0] = $plans[$storyProduct->id]; + + if($storyProduct->type != 'normal') $branchProduct = true; + } + } + + /* Set ditto option for users. */ + $users = $this->loadModel('user')->getPairs('nodeleted'); + $users = array('' => '', 'ditto' => $this->lang->story->ditto) + $users; + + /* Set Custom*/ + foreach(explode(',', $this->config->story->list->customBatchEditFields) as $field) $customFields[$field] = $this->lang->story->$field; + $showFields = $this->config->story->custom->batchEditFields; + if($storyType == 'requirement') + { + unset($customFields['plan']); + unset($customFields['stage']); + $showFields = str_replace('plan', '', $showFields); + $showFields = str_replace('stage', '', $showFields); + } + $this->view->customFields = $customFields; + $this->view->showFields = $showFields; + + /* Judge whether the editedStories is too large and set session. */ + $countInputVars = count($stories) * (count(explode(',', $this->config->story->custom->batchEditFields)) + 3); + $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); + if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); + + /* Append module when change product type. */ + $moduleList = array(0 => '/'); + foreach($stories as $story) + { + if(isset($modules[$story->product][$story->branch])) + { + $moduleList[$story->id] = $modules[$story->product][$story->branch]; + } + else + { + $moduleList[$story->id] = $modules[$story->product][0] + $this->tree->getModulesName($story->module); + } + } + + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $this->lang->story->batchEdit; + $this->view->title = $this->lang->story->batchEdit; + $this->view->users = $users; + $this->view->priList = array('0' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->priList; + $this->view->sourceList = array('' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->sourceList; + $this->view->reasonList = array('' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->reasonList; + $this->view->stageList = array('' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->stageList; + $this->view->productID = $productID; + $this->view->products = $products; + $this->view->branchProduct = $branchProduct; + $this->view->storyIdList = $storyIdList; + $this->view->branch = $branch; + $this->view->plans = array('' => '') + $plans; + $this->view->storyType = $storyType; + $this->view->stories = $stories; + $this->view->executionID = $executionID; + $this->view->branchTagOption = $branchTagOption; + $this->view->moduleList = $moduleList; + $this->display(); + } + + /** + * Change a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function change($storyID) + { + if(!empty($_POST)) + { + $changes = $this->story->change($storyID); + if(dao::isError()) + { + if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'fail', 'message' => dao::getError())); + return print(js::error(dao::getError())); + } + $story = $this->story->getByID($storyID); + $version = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch('version'); + $files = $this->loadModel('file')->saveUpload($story->type, $storyID, $version); + + if(empty($files) and $this->post->uid != '' and isset($_SESSION['album']['used'][$this->post->uid])) $files = $this->file->getPairs($_SESSION['album']['used'][$this->post->uid]); + + if($this->post->comment != '' or !empty($changes) or !empty($files)) + { + $action = (!empty($changes) or !empty($files)) ? 'Changed' : 'Commented'; + $fileAction = ''; + if(!empty($files)) $fileAction = $this->lang->addFiles . join(',', $files) . "\n" ; + $actionID = $this->action->create('story', $storyID, $action, $fileAction . $this->post->comment); + $this->action->logHistory($actionID, $changes); + } + + $this->executeHooks($storyID); + + $module = $this->app->tab == 'project' ? 'projectstory' : 'story'; + + if(isonlybody()) return print(js::reload('parent.parent')); + + if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); + return print(js::locate($this->createLink($module, 'view', "storyID=$storyID"), 'parent')); + } + + $this->commonAction($storyID); + $this->story->getAffectedScope($this->view->story); + $this->app->loadLang('task'); + $this->app->loadLang('bug'); + $this->app->loadLang('testcase'); + $this->app->loadLang('execution'); + + $story = $this->story->getById($storyID); + $reviewer = $this->story->getReviewerPairs($storyID, $story->version); + $product = $this->loadModel('product')->getByID($story->product); + + /* Get product reviewers. */ + $productReviewers = $product->reviewer; + if(!$productReviewers and $product->acl != 'open') $productReviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); + + /* Assign. */ + $this->view->title = $this->lang->story->change . "STORY" . $this->lang->colon . $this->view->story->title; + $this->view->users = $this->user->getPairs('pofirst|nodeleted|noclosed', $this->view->story->assignedTo); + $this->view->position[] = $this->lang->story->change; + $this->view->needReview = ($this->app->user->account == $this->view->product->PO || $this->config->story->needReview == 0) ? "checked='checked'" : ""; + $this->view->reviewer = implode(',', array_keys($reviewer)); + $this->view->productReviewers = $this->user->getPairs('noclosed|nodeleted', $reviewer, 0, $productReviewers); + + $this->display(); + } + + /** + * Activate a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function activate($storyID) + { + if(!empty($_POST)) + { + $changes = $this->story->activate($storyID); + if(dao::isError()) return print(js::error(dao::getError())); + + if($changes) + { + $actionID = $this->action->create('story', $storyID, 'Activated', $this->post->comment); + $this->action->logHistory($actionID, $changes); + } + + $this->executeHooks($storyID); + + if(isonlybody()) + { + $execution = $this->execution->getByID($this->session->execution); + if($this->app->tab == 'execution' and $execution->type == 'kanban') + { + $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; + $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; + $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; + $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue); + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); + } + else + { + return print(js::closeModal('parent.parent', 'this')); + } + } + return print(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); + } + + $this->commonAction($storyID); + + /* Assign. */ + $this->view->title = $this->lang->story->activate . "STORY" . $this->lang->colon . $this->view->story->title; + $this->view->users = $this->user->getPairs('pofirst|nodeleted', $this->view->story->closedBy); + $this->view->position[] = $this->lang->story->activate; + $this->display(); + } + + /** + * View a story. + * + * @param int $storyID + * @param int $version + * @param int $param + * @access public + * @return void + */ + public function view($storyID, $version = 0, $param = 0) + { + $uri = $this->app->getURI(true); + $this->session->set('productList', $uri . "#app={$this->app->tab}", 'product'); + $this->session->set('productPlanList', $uri, 'product'); + + $storyID = (int)$storyID; + $story = $this->story->getById($storyID, $version, true); + $linkModuleName = $this->config->vision == 'lite' ? 'project' : 'product'; + if(!$story) return print(js::error($this->lang->notFound) . js::locate($this->createLink($linkModuleName, 'index'))); + + $story = $this->story->mergeReviewer($story, true); + + $this->story->replaceURLang($story->type); + + $story->files = $this->loadModel('file')->getByObject($story->type, $storyID); + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id, type, status')->fetch(); + $plan = $this->dao->findById($story->plan)->from(TABLE_PRODUCTPLAN)->fetch('title'); + $bugs = $this->dao->select('id,title,status,pri,severity')->from(TABLE_BUG)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll(); + $fromBug = $this->dao->select('id,title')->from(TABLE_BUG)->where('toStory')->eq($storyID)->fetch(); + $cases = $this->dao->select('id,title,status,pri')->from(TABLE_CASE)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll(); + $linkedMRs = $this->loadModel('mr')->getLinkedMRPairs($storyID, 'story'); + $modulePath = $this->tree->getParents($story->module); + $storyModule = empty($story->module) ? '' : $this->tree->getById($story->module); + + /* Set the menu. */ + $from = $this->app->tab; + if($from == 'execution') + { + $this->execution->setMenu($param); + } + elseif($from == 'project') + { + $this->loadModel('project')->setMenu($this->session->project); + } + elseif($from == 'qa') + { + $products = $this->product->getProductPairsByProject(0, 'noclosed'); + $this->loadModel('qa')->setMenu($products, $story->product); + } + else + { + $this->product->setMenu($story->product, $story->branch); + } + + if($product->type != 'normal') $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]); + + $reviewers = $this->story->getReviewerPairs($storyID, $story->version); + $reviewedBy = trim($story->reviewedBy, ','); + + $this->executeHooks($storyID); + + $title = "STORY #$story->id $story->title - $product->name"; + $position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); + $position[] = $this->lang->story->common; + $position[] = $this->lang->story->view; + + $this->view->title = $title; + $this->view->position = $position; + $this->view->product = $product; + $this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id); + $this->view->plan = $plan; + $this->view->bugs = $bugs; + $this->view->fromBug = $fromBug; + $this->view->cases = $cases; + $this->view->story = $story; + $this->view->linkedMRs = $linkedMRs; + $this->view->track = $this->story->getTrackByID($story->id); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->reviewers = $reviewers; + $this->view->relations = $this->story->getStoryRelation($story->id, $story->type); + $this->view->executions = $this->execution->getPairs(0, 'all', 'nocode'); + $this->view->execution = empty($story->execution) ? array() : $this->dao->findById($story->execution)->from(TABLE_EXECUTION)->fetch(); + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->storyModule = $storyModule; + $this->view->modulePath = $modulePath; + $this->view->version = $version == 0 ? $story->version : $version; + $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('story', $storyID); + $this->view->from = $from; + $this->view->param = $param; + + $this->display(); + } + + /** + * Delete a story. + * + * @param int $storyID + * @param string $confirm yes|no + * @param string $from taskkanban + * @access public + * @return void + */ + public function delete($storyID, $confirm = 'no', $from = '') + { + $story = $this->story->getById($storyID); + if($story->parent < 0) return print(js::alert($this->lang->story->cannotDeleteParent)); + + if($confirm == 'no') + { + return print(js::confirm($this->lang->story->confirmDelete, $this->createLink('story', 'delete', "story=$storyID&confirm=yes&from=$from"), '')); + } + else + { + $this->dao->update(TABLE_STORY)->set('deleted')->eq(1)->where('id')->eq($storyID)->exec(); + $this->loadModel('action')->create($story->type, $storyID, 'deleted', '', ACTIONMODEL::CAN_UNDELETED); + + if($story->parent > 0) + { + $this->story->updateParentStatus($story->id); + $this->loadModel('action')->create('story', $task->parent, 'deleteChildrenStory', '', $storyID); + } + + $this->executeHooks($storyID); + + if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); + + if($this->app->tab == 'execution' and $from == 'taskkanban') return print(js::reload('parent')); + + if(isonlybody()) return print(js::reload('parent.parent')); + + $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID={$story->product}"); + return print(js::locate($locateLink, 'parent')); + } + } + + /** + * Review a story. + * + * @param int $storyID + * @param string $from product|project + * @access public + * @return void + */ + public function review($storyID, $from = 'product') + { + if(!empty($_POST)) + { + $changes = $this->story->review($storyID); + if(dao::isError()) return print(js::error(dao::getError())); + + if($changes) + { + $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $actionID = $this->story->recordReviewAction($story, $this->post->result, $this->post->closedReason); + $this->action->logHistory($actionID, $changes); + } + + $this->executeHooks($storyID); + + if(isonlybody()) return print(js::reload('parent.parent')); + + $module = $from == 'project' ? 'projectstory' : 'story'; + if(defined('RUN_MODE') and RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $storyID)); + return print(js::locate($this->createLink($module, 'view', "storyID=$storyID"), 'parent')); + } + + /* Get story and product. */ + $story = $this->story->getById($storyID); + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); + + $this->story->replaceURLang($story->type); + + /* Set menu. */ + if($this->app->tab == 'project') + { + $this->loadModel('project')->setMenu($this->session->project); + } + elseif($this->app->tab == 'product') + { + $this->product->setMenu($product->id, $story->branch); + } + elseif($this->app->tab == 'execution') + { + $this->loadModel('execution')->setMenu($this->session->execution); + } + + /* Set the review result options. */ + $reviewers = $this->story->getReviewerPairs($storyID, $story->version); + $this->lang->story->resultList = $this->lang->story->reviewResultList; + + if($story->status == 'draft' and $story->version == 1) unset($this->lang->story->resultList['revert']); + + if($story->status == 'changed') unset($this->lang->story->resultList['reject']); + + if(count($reviewers) > 1) unset($this->lang->story->resultList['revert']); + + $this->view->title = $this->lang->story->review . "STORY" . $this->lang->colon . $story->title; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $this->lang->story->review; + + $this->view->product = $product; + $this->view->story = $story; + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noletter', "$story->lastEditedBy,$story->openedBy"); + $this->view->reviewers = $reviewers; + + /* Get the affcected things. */ + $this->story->getAffectedScope($this->view->story); + $this->app->loadLang('task'); + $this->app->loadLang('bug'); + $this->app->loadLang('testcase'); + $this->app->loadLang('execution'); + + $this->display(); + } + + /** + * Batch review stories. + * + * @param string $result + * @param string $reason + * @access public + * @return void + */ + public function batchReview($result, $reason = '') + { + if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent')); + $storyIdList = $this->post->storyIdList; + $storyIdList = array_unique($storyIdList); + $actions = $this->story->batchReview($storyIdList, $result, $reason); + + if(dao::isError()) return print(js::error(dao::getError())); + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + echo js::reload('parent'); + } + + /** + * Recall the story review. + * + * @param int $storyID + * @access public + * @return void + */ + public function recall($storyID) + { + $story = $this->story->getById($storyID); + $this->story->recall($storyID); + $this->loadModel('action')->create('story', $storyID, 'Recalled'); + + $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID={$story->product}"); + echo js::locate($locateLink, 'parent'); + } + + /** + * Close a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function close($storyID) + { + if(!empty($_POST)) + { + $changes = $this->story->close($storyID); + if(dao::isError()) return print(js::error(dao::getError())); + $this->story->closeParentRequirement($storyID); + + if($changes) + { + $actionID = $this->action->create('story', $storyID, 'Closed', $this->post->comment, ucfirst($this->post->closedReason) . ($this->post->duplicateStory ? ':' . (int)$this->post->duplicateStory : '')); + $this->action->logHistory($actionID, $changes); + } + + $this->executeHooks($storyID); + + if(isonlybody()) + { + $execution = $this->execution->getByID($this->session->execution); + if($this->app->tab == 'execution' and $execution->type == 'kanban') + { + $this->loadModel('kanban')->updateLane($this->session->execution, 'story', $storyID); + $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; + $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; + $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; + $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue); + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); + } + else + { + return print(js::closeModal('parent.parent', 'this', "function(){parent.parent.location.reload();}")); + } + } + + if(defined('RUN_MODE') && RUN_MODE == 'api') + { + return $this->send(array('status' => 'success', 'data' => $storyID)); + } + else + { + return print(js::locate(inlink('view', "storyID=$storyID"), 'parent')); + } + } + + /* Get story and product. */ + $story = $this->story->getById($storyID); + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); + + $this->story->replaceURLang($story->type); + + /* Set menu. */ + $this->product->setMenu($product->id, $story->branch); + + /* Set the closed reason options and remove subdivided options. */ + if($story->status == 'draft') unset($this->lang->story->reasonList['cancel']); + unset($this->lang->story->reasonList['subdivided']); + + $this->view->title = $this->lang->story->close . "STORY" . $this->lang->colon . $story->title; + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $this->lang->story->close; + + $this->view->product = $product; + $this->view->story = $story; + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->users = $this->loadModel('user')->getPairs(); + $this->view->reasonList = $this->lang->story->reasonList; + $this->display(); + } + + /** + * Batch close story. + * + * @param int $productID + * @param int $executionID + * @param string $storyType + * @param string $from + * @access public + * @return void + */ + public function batchClose($productID = 0, $executionID = 0, $storyType = 'story', $from = '') + { + if($this->post->comments) + { + $data = fixer::input('post')->get(); + $allChanges = $this->story->batchClose(); + + if($allChanges) + { + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, 'Closed', htmlSpecialString($this->post->comments[$storyID]), ucfirst($this->post->closedReasons[$storyID]) . ($this->post->duplicateStoryIDList[$storyID] ? ':' . (int)$this->post->duplicateStoryIDList[$storyID] : '')); + $this->action->logHistory($actionID, $changes); + } + } + + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + return print(js::locate($this->session->storyList, 'parent')); + } + + if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent')); + $storyIdList = $this->post->storyIdList; + $storyIdList = array_unique($storyIdList); + + /* Get edited stories. */ + $stories = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($storyIdList)->fetchAll('id'); + foreach($stories as $story) + { + if($story->parent == -1) + { + $skipStory[] = $story->id; + unset($stories[$story->id]); + } + if($story->status == 'closed') + { + $closedStory[] = $story->id; + unset($stories[$story->id]); + } + } + + $errorTips = ''; + if(isset($closedStory)) $errorTips .= sprintf($this->lang->story->closedStory, join(',', $closedStory)); + if(isset($skipStory)) $errorTips .= sprintf($this->lang->story->skipStory, join(',', $skipStory)); + if(isset($skipStory) || isset($closedStory)) echo js::alert($errorTips); + + /* The stories of a product. */ + if($this->app->tab == 'product') + { + $this->product->setMenu($productID); + $product = $this->product->getByID($productID); + $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id"), $product->name); + $this->view->title = $product->name . $this->lang->colon . $this->lang->story->batchClose; + } + /* The stories of a execution. */ + elseif($executionID) + { + $this->lang->story->menu = $this->lang->execution->menu; + $this->lang->story->menuOrder = $this->lang->execution->menuOrder; + $this->execution->setMenu($executionID); + $execution = $this->execution->getByID($executionID); + $this->view->position[] = html::a($this->createLink('execution', 'story', "executionID=$execution->id"), $execution->name); + $this->view->title = $execution->name . $this->lang->colon . $this->lang->story->batchClose; + } + else + { + if($this->app->tab == 'project') + { + $this->project->setMenu($this->session->project); + $this->view->title = $this->lang->story->batchEdit; + } + else + { + $this->lang->story->menu = $this->lang->my->menu; + $this->lang->story->menuOrder = $this->lang->my->menuOrder; + + if($from == 'work') $this->lang->my->menu->work['subModule'] = 'story'; + if($from == 'contribute') $this->lang->my->menu->contribute['subModule'] = 'story'; + + $this->view->position[] = html::a($this->createLink('my', 'story'), $this->lang->my->story); + $this->view->title = $this->lang->story->batchEdit; + } + } + + /* Judge whether the editedStories is too large and set session. */ + $countInputVars = count($stories) * $this->config->story->batchClose->columns; + $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); + if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); + + unset($this->lang->story->reasonList['subdivided']); + + $this->view->position[] = $this->lang->story->common; + $this->view->position[] = $this->lang->story->batchClose; + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story'); + $this->view->plans = $this->loadModel('productplan')->getPairs($productID); + $this->view->productID = $productID; + $this->view->stories = $stories; + $this->view->storyIdList = $storyIdList; + $this->view->storyType = $storyType; + $this->view->reasonList = $this->lang->story->reasonList; + + $this->display(); + } + + /** + * Batch change the module of story. + * + * @param int $moduleID + * @access public + * @return void + */ + public function batchChangeModule($moduleID) + { + if(empty($_POST['storyIdList'])) return print(js::locate($this->session->storyList, 'parent')); + $storyIdList = $this->post->storyIdList; + $storyIdList = array_unique($storyIdList); + $allChanges = $this->story->batchChangeModule($storyIdList, $moduleID); + if(dao::isError()) return print(js::error(dao::getError())); + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, 'Edited'); + $this->action->logHistory($actionID, $changes); + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + echo js::locate($this->session->storyList, 'parent'); + } + + /** + * Batch stories convert to tasks. + * + * @param int $executionID + * @access public + * @return void + */ + public function batchToTask($executionID = 0) + { + if(!empty($_POST)) + { + $response['result'] = 'success'; + $response['message'] = $this->lang->story->successToTask; + + $tasks = $this->story->batchToTask($executionID, $this->session->project); + + if(dao::isError()) + { + $response['result'] = 'fail'; + $response['message'] = dao::getError(); + return $this->send($response); + } + + if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $tasks)); + $response['locate'] = $this->createLink('execution', 'task', "executionID=$executionID"); + return $this->send($response); + } + } + + /** + * Batch change the plan of story. + * + * @param int $planID + * @access public + * @return void + */ + public function batchChangePlan($planID, $oldPlanID = 0) + { + if(empty($_POST['storyIdList'])) return print(js::locate($this->session->storyList, 'parent')); + $storyIdList = $this->post->storyIdList; + $storyIdList = array_unique($storyIdList); + $allChanges = $this->story->batchChangePlan($storyIdList, $planID, $oldPlanID); + if(dao::isError()) return print(js::error(dao::getError())); + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, 'Edited'); + $this->action->logHistory($actionID, $changes); + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + echo js::locate($this->session->storyList, 'parent'); + } + + /** + * Batch change branch. + * + * @param int $branchID + * @param string $confirm yes|no + * @param string $storyIdList + * @access public + * @return void + */ + public function batchChangeBranch($branchID, $confirm = '', $storyIdList = '') + { + if(empty($_POST['storyIdList'])) return print(js::locate($this->session->storyList, 'parent')); + $storyIdList = $this->post->storyIdList; + $plans = $this->loadModel('productplan')->getPlansByStories($storyIdList); + if(empty($confirm)) + { + $stories = $this->story->getByList($storyIdList); + $normalStotyIdList = ''; + $conflictStoryIdList = ''; + $conflictStoryArray = array(); + + /* Determine whether there is a conflict between the branch of the story and the linked plan. */ + foreach($storyIdList as $storyID) + { + if($stories[$storyID]->branch != $branchID and $branchID != BRANCH_MAIN and isset($plans[$storyID])) + { + foreach($plans[$storyID] as $plan) + { + if($plan->branch != $branchID) + { + $conflictStoryIdList .= '[' . $storyID . ']'; + $conflictStoryArray[] = $storyID; + break; + } + } + } + } + + /* Prompt the user whether to continue to modify the conflicting stories branch. */ + if($conflictStoryIdList) + { + $normalStotyIdList = array_diff($storyIdList, $conflictStoryArray); + $normalStotyIdList = implode(',', $normalStotyIdList); + $storyIdList = implode(',', $storyIdList); + $confirmURL = $this->createLink('story', 'batchChangeBranch', "branchID=$branchID&confirm=yes&storyIdList=$storyIdList"); + $cancelURL = $this->createLink('story', 'batchChangeBranch', "branchID=$branchID&confirm=no&storyIdList=$normalStotyIdList"); + return print(js::confirm(sprintf($this->lang->story->confirmChangeBranch, $conflictStoryIdList ), $confirmURL, $cancelURL)); + } + } + + if(is_string($storyIdList)) $storyIdList = array_filter(explode(',', $storyIdList)); + $storyIdList = array_unique($storyIdList); + $allChanges = $this->story->batchChangeBranch($storyIdList, $branchID, $confirm, $plans); + if(dao::isError()) return print(js::error(dao::getError())); + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, 'Edited'); + $this->action->logHistory($actionID, $changes); + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + echo js::reload('parent'); + } + + /** + * Batch change the stage of story. + * + * @param string $stage + * @access public + * @return void + */ + public function batchChangeStage($stage) + { + $storyIdList = $this->post->storyIdList; + if(empty($storyIdList)) return print(js::locate($this->session->storyList, 'parent')); + + $storyIdList = array_unique($storyIdList); + $allChanges = $this->story->batchChangeStage($storyIdList, $stage); + if(dao::isError()) return print(js::error(dao::getError())); + + $action = $stage == 'verified' ? 'Verified' : 'Edited'; + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, $action); + $this->action->logHistory($actionID, $changes); + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + echo js::locate($this->session->storyList, 'parent'); + } + + /** + * Assign to. + * + * @param int $storyID + * @param string $kanbanGroup + * @access public + * @return void + */ + public function assignTo($storyID, $kanbanGroup = 'default') + { + if(!empty($_POST)) + { + $changes = $this->story->assign($storyID); + if(dao::isError()) return print(js::error(dao::getError())); + if($changes) + { + $actionID = $this->loadModel('action')->create('story', $storyID, 'Assigned', $this->post->comment, $this->post->assignedTo); + $this->action->logHistory($actionID, $changes); + } + + $this->executeHooks($storyID); + + if(isonlybody()) + { + $execution = $this->execution->getByID($this->session->execution); + if($this->app->tab == 'execution' and $execution->type == 'kanban') + { + $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; + $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; + $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $execLaneType, 'id_desc', 0, $kanbanGroup, $rdSearchValue); + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); + } + else + { + return print(js::closeModal('parent.parent', 'this', 'function(){parent.parent.$(\'[data-ride="searchList"]\').searchList();}')); + } + } + return print(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); + } + + /* Get story and product. */ + $story = $this->story->getById($storyID); + $products = $this->product->getPairs(); + + /* Set menu. */ + $this->product->setMenu($story->product, $story->branch); + + $this->view->title = zget($products, $story->product, '') . $this->lang->colon . $this->lang->story->assign; + $this->view->position[] = $this->lang->story->assign; + $this->view->story = $story; + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->users = ($this->config->vision == 'lite') ? $this->loadModel('user')->getTeamMemberPairs($this->session->project) : $this->loadModel('user')->getPairs('nodeleted|noclosed|pofirst|noletter'); + $this->display(); + } + + /** + * Batch assign to. + * + * @access public + * @return void + */ + public function batchAssignTo() + { + if(!empty($_POST) && isset($_POST['storyIdList'])) + { + $allChanges = $this->story->batchAssignTo(); + if(dao::isError()) return print(js::error(dao::getError())); + foreach($allChanges as $storyID => $changes) + { + $actionID = $this->action->create('story', $storyID, 'Assigned', '', $this->post->assignedTo); + $this->action->logHistory($actionID, $changes); + } + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); + echo js::locate($this->session->storyList, 'parent'); + } + + /** + * Story track. + * + * @param int $productID + * @param int|string $branch + * @param int $projectID + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function track($productID, $branch = '', $projectID = 0, $recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $branch = ($this->cookie->preBranch !== '' and $branch === '') ? $this->cookie->preBranch : $branch; + setcookie('preBranch', $branch, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true); + + /* Set menu. The projectstory module does not execute. */ + if(!$projectID) + { + $products = $this->product->getPairs(); + $productID = $this->product->saveState($productID, $products); + $this->product->products = $this->product->saveState($productID, $products); + $this->product->setMenu($productID, $branch); + } + + /* Save session. */ + $this->session->set('storyList', $this->app->getURI(true), 'product'); + $this->session->set('taskList', $this->app->getURI(true), 'execution'); + $this->session->set('designList', $this->app->getURI(true), 'project'); + $this->session->set('bugList', $this->app->getURI(true), 'qa'); + $this->session->set('caseList', $this->app->getURI(true), 'qa'); + $this->session->set('revisionList', $this->app->getURI(true), 'repo'); + + /* Load pager and get tracks. */ + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + $tracks = $this->story->getTracks($productID, $branch, $projectID, $pager); + + if($projectID) + { + $this->loadModel('project')->setMenu($projectID); + $projectProducts = $this->product->getProducts($projectID); + } + + $this->view->title = $this->lang->story->track; + $this->view->position[] = $this->lang->story->track; + + $this->view->tracks = $tracks; + $this->view->pager = $pager; + $this->view->productID = $productID; + $this->view->projectProducts = isset($projectProducts) ? $projectProducts : array(); + $this->display(); + } + + /** + * Tasks of a story. + * + * @param int $storyID + * @param int $executionID + * @access public + * @return void + */ + public function tasks($storyID, $executionID = 0) + { + $this->loadModel('task'); + $tasks = $this->task->getStoryTasks($storyID, $executionID); + $this->view->tasks = $tasks; + $this->view->users = $this->user->getPairs('noletter'); + $this->view->summary = $this->execution->summary($tasks); + $this->display(); + } + + /** + * Bugs of a story. + * + * @param int $storyID + * @param int $executionID + * @access public + * @return void + */ + public function bugs($storyID, $executionID = 0) + { + $this->loadModel('bug'); + $this->view->bugs = $this->bug->getStoryBugs($storyID, $executionID); + $this->view->users = $this->user->getPairs('noletter'); + $this->display(); + } + + /** + * Cases of a story. + * + * @param int $storyID + * @access public + * @return void + */ + public function cases($storyID) + { + $this->loadModel('testcase'); + $this->view->cases = $this->testcase->getStoryCases($storyID); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->resultList = array('' => '') + $this->lang->testcase->resultList; + $this->display(); + } + + /** + * Show zero case story. + * + * @param int $productID + * @param int $branchID + * @param string $orderBy + * @access public + * @return void + */ + public function zeroCase($productID = 0, $branchID = 0, $orderBy = 'id_desc', $projectID = 0) + { + $orderBy = empty($orderBy) ? 'id_desc' : $orderBy; + $this->session->set('storyList', $this->app->getURI(true) . '#app=' . $this->app->tab, 'product'); + $this->session->set('caseList', $this->app->getURI(true), $this->app->tab); + + $this->loadModel('testcase'); + if($this->app->tab == 'project') + { + $this->loadModel('project')->setMenu($this->session->project); + $this->app->rawModule = 'qa'; + $this->lang->project->menu->qa['subMenu']->testcase['subModule'] = 'story'; + $products = $this->product->getProducts($this->session->project, 'all', '', false); + $productID = $this->product->saveState($productID, $products); + $this->lang->modulePageNav = $this->product->select($products, $productID, 'story', 'zeroCase', "projectID=$projectID", $branchID); + } + else + { + $products = $this->product->getPairs(); + $productID = $this->product->saveState($productID, $products); + $this->loadModel('qa'); + $this->app->rawModule = 'testcase'; + foreach($this->config->qa->menuList as $module) $this->lang->navGroup->$module = 'qa'; + $this->qa->setMenu($products, $productID, $branchID); + } + + /* Append id for secend sort. */ + $sort = common::appendOrder($orderBy); + + $this->view->title = $this->lang->story->zeroCase; + $this->view->position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $products[$productID]); + $this->view->position[] = $this->lang->story->zeroCase; + + $this->view->stories = $this->story->getZeroCase($productID, $branchID, $sort); + $this->view->users = $this->user->getPairs('noletter'); + $this->view->projectID = $projectID; + $this->view->productID = $productID; + $this->view->branchID = $branchID; + $this->view->orderBy = $orderBy; + $this->view->suiteList = $this->loadModel('testsuite')->getSuites($productID); + $this->view->browseType = ''; + $this->view->product = $this->product->getByID($productID); + $this->display(); + } + + /** + * If type is linkStories, link related stories else link child stories. + * + * @param int $storyID + * @param string $type + * @param string $browseType + * @param int $param + * @access public + * @return void + */ + public function linkStory($storyID, $type = 'linkStories', $linkedStoryID = 0, $browseType = '', $queryID = 0) + { + $this->commonAction($storyID); + + if($type == 'remove') + { + $result = $this->story->unlinkStory($storyID, $linkedStoryID); + return print(js::reload('parent')); + } + + if($_POST) + { + $this->story->linkStories($storyID); + + if(dao::isError()) return print(js::error(dao::getError())); + return print(js::closeModal('parent.parent', 'this')); + } + + /* Get story, product, products, and queryID. */ + $story = $this->story->getById($storyID); + $products = $this->product->getPairs(); + $queryID = 0; + + /* Change for requirement story title. */ + if($story->type == 'story') + { + $this->lang->story->title = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->story->title); + $this->lang->story->create = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->story->create); + $this->config->product->search['fields']['title'] = $this->lang->story->title; + unset($this->config->product->search['fields']['plan']); + unset($this->config->product->search['fields']['stage']); + } + + /* Build search form. */ + $actionURL = $this->createLink('story', 'linkStory', "storyID=$storyID&type=$type&linkedStoryID=$linkedStoryID&browseType=bySearch&queryID=myQueryID", '', true); + $this->product->buildSearchForm($story->product, $products, $queryID, $actionURL); + + /* Get stories to link. */ + $storyType = $story->type; + $stories2Link = $this->story->getStories2Link($storyID, $type, $browseType, $queryID, $storyType); + + /* Assign. */ + $this->view->title = $this->lang->story->linkStory . "STORY" . $this->lang->colon .$this->lang->story->linkStory; + $this->view->position[] = $this->lang->story->linkStory; + $this->view->type = $type; + $this->view->stories2Link = $stories2Link; + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + + $this->display(); + } + + /** + * Process story change. + * + * @param int $storyID + * @param string $result yes|no + * @access public + * @return void + */ + public function processStoryChange($storyID, $result = 'yes') + { + $this->commonAction($storyID); + $story = $this->story->getByID($storyID); + + if($result == 'no') + { + $this->dao->update(TABLE_STORY)->set('URChanged')->eq(0)->where('id')->eq($storyID)->exec(); + return print(js::reload('parent', 'this')); + } + + $this->view->changedStories = $this->story->getChangedStories($story); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->storyID = $storyID; + $this->display(); + } + + /** + * AJAX: get stories of a execution in html select. + * + * @param int $executionID + * @param int $productID + * @param int $branch + * @param int $moduleID + * @param int $storyID + * @param string $number + * @param string $type full + * @param string $status all|unclosed + * @param string $from bug + * @access public + * @return void + */ + public function ajaxGetExecutionStories($executionID, $productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $number = '', $type = 'full', $status = 'all', $from = '') + { + if($moduleID) + { + $moduleID = $this->loadModel('tree')->getStoryModule($moduleID); + $moduleID = $this->tree->getAllChildID($moduleID); + } + $stories = $this->story->getExecutionStoryPairs($executionID, $productID, $branch, $moduleID, $type, $status); + if($this->app->getViewType() === 'json') + { + return print(json_encode($stories)); + } + elseif($this->app->getViewType() == 'mhtml') + { + return print(html::select('story', empty($stories) ? array('' => '') : $stories, $storyID, 'onchange=setStoryRelated()')); + } + else + { + $storyName = $number === '' ? 'story' : "story[$number]"; + $misc = $from === 'bug' ? 'class=form-control' : 'class=form-control onchange=setStoryRelated(' . $number . ');'; + return print(html::select($storyName, empty($stories) ? array('' => '') : $stories, $storyID, $misc)); + } + } + + /** + * AJAX: get stories of a product in html select. + * + * @param int $productID + * @param int $branch + * @param int $moduleID + * @param int $storyID + * @param string $onlyOption + * @param string $status + * @param int $limit + * @param string $type + * @param bool $hasParent + * @param int $executionID + * @param int $number + * @access public + * @return void + */ + public function ajaxGetProductStories($productID, $branch = 0, $moduleID = 0, $storyID = 0, $onlyOption = 'false', $status = '', $limit = 0, $type = 'full', $hasParent = 1, $executionID = 0, $number = '') + { + if($moduleID) + { + $moduleID = $this->loadModel('tree')->getStoryModule($moduleID); + $moduleID = $this->tree->getAllChildID($moduleID); + } + + $storyStatus = ''; + if($status == 'noclosed') + { + $storyStatus = $this->lang->story->statusList; + unset($storyStatus['closed']); + $storyStatus = array_keys($storyStatus); + } + + if($executionID) + { + $stories = $this->story->getExecutionStoryPairs($executionID, $productID, $branch, $moduleID, $type); + } + else + { + $stories = $this->story->getProductStoryPairs($productID, $branch, $moduleID, $storyStatus, 'id_desc', $limit, $type, 'story', $hasParent); + } + + if(!in_array($this->app->tab, array('execution', 'project')) and empty($stories)) $stories = $this->story->getProductStoryPairs($productID, $branch, 0, $storyStatus, 'id_desc', $limit, $type, 'story', $hasParent); + + $storyID = isset($stories[$storyID]) ? $storyID : 0; + $select = html::select('story' . $number, empty($stories) ? array('' => '') : $stories, $storyID, "class='form-control'"); + + /* If only need options, remove select wrap. */ + if($onlyOption == 'true') return print(substr($select, strpos($select, '>') + 1, -10)); + echo $select; + } + + /** + * AJAX: search stories of a product as json + * + * @param string $key + * @param int $productID + * @param int $moduleID + * @param int $storyID + * @param string $status + * @param int $limit + * @access public + * @return void + */ + public function ajaxSearchProductStories($key, $productID, $branch = 0, $moduleID = 0, $storyID = 0, $status = 'noclosed', $limit = 50) + { + if($moduleID) + { + $moduleID = $this->loadModel('tree')->getStoryModule($moduleID); + $moduleID = $this->tree->getAllChildID($moduleID); + } + + $storyStatus = ''; + if($status == 'noclosed') + { + $storyStatus = $this->lang->story->statusList; + unset($storyStatus['closed']); + $storyStatus = array_keys($storyStatus); + } + + $stories = $this->story->getProductStoryPairs($productID, $branch, $moduleID, $storyStatus, 'id_desc'); + $result = array(); + $i = 0; + foreach ($stories as $id => $story) + { + if($limit > 0 && $i > $limit) break; + if(('#' . $id) === $key || stripos($story, $key) !== false) + { + $result[$id] = $story; + $i++; + } + } + if($i < 1) + { + $result['info'] = $this->lang->noResultsMatch; + } + + echo json_encode($result); + } + + /** + * AJAX: get spec and verify of a story. for web app. + * + * @param int $storyID + * @access public + * @return void + */ + public function ajaxGetDetail($storyID) + { + $this->view->actions = $this->action->getList('story', $storyID); + $this->view->story = $this->story->getByID($storyID); + $this->display(); + } + + /** + * AJAX: get module of a story. + * + * @param int $storyID + * @access public + * @return string + */ + public function ajaxGetInfo($storyID) + { + $story = $this->story->getByID($storyID); + if(empty($story)) return; + + $storyInfo['moduleID'] = $story->module; + $storyInfo['estimate'] = $story->estimate; + $storyInfo['pri'] = $story->pri; + $storyInfo['spec'] = html_entity_decode($story->spec, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'); + + echo json_encode($storyInfo); + } + + /** + * The report page. + * + * @param int $productID + * @param int $branchID + * @param string $storyType + * @param string $browseType + * @param int $moduleID + * @param string $chartType + * @access public + * @return void + */ + public function report($productID, $branchID, $storyType = 'story', $browseType = 'unclosed', $moduleID = 0, $chartType = 'pie') + { + $this->loadModel('report'); + $this->view->charts = array(); + + if(!empty($_POST)) + { + foreach($this->post->charts as $chart) + { + $chartFunc = 'getDataOf' . $chart; + $chartData = $this->story->$chartFunc(); + $chartOption = $this->lang->story->report->$chart; + if(!empty($chartType)) $chartOption->type = $chartType; + $this->story->mergeChartOption($chart); + + $this->view->charts[$chart] = $chartOption; + $this->view->datas[$chart] = $this->report->computePercent($chartData); + } + } + + $this->story->replaceURLang($storyType); + $this->products = $this->product->getPairs(); + $this->product->setMenu($productID, $branchID); + + $this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->story->reportChart; + $this->view->position[] = $this->products[$productID]; + $this->view->position[] = $this->lang->story->reportChart; + $this->view->productID = $productID; + $this->view->branchID = $branchID; + $this->view->browseType = $browseType; + $this->view->storyType = $storyType; + $this->view->moduleID = $moduleID; + $this->view->chartType = $chartType; + $this->view->checkedCharts = $this->post->charts ? join(',', $this->post->charts) : ''; + $this->display(); + } + + /** + * get data to export + * + * @param int $productID + * @param string $orderBy + * @param int $executionID + * @param string $browseType + * @param string $type requirement|story + * @access public + * @return void + */ + public function export($productID, $orderBy, $executionID = 0, $browseType = '', $type = 'story') + { + /* format the fields of every story in order to export data. */ + if($_POST) + { + $this->loadModel('file'); + $this->loadModel('branch'); + $storyLang = $this->lang->story; + $storyConfig = $this->config->story; + + /* Create field lists. */ + $fields = $this->post->exportFields ? $this->post->exportFields : explode(',', $storyConfig->list->exportFields); + foreach($fields as $key => $fieldName) + { + $fieldName = trim($fieldName); + $fields[$fieldName] = isset($storyLang->$fieldName) ? $storyLang->$fieldName : $fieldName; + unset($fields[$key]); + } + + /* Get stories. */ + $stories = array(); + if($this->session->storyOnlyCondition) + { + if($this->post->exportType == 'selected') + { + $stories = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($this->cookie->checkedItem)->orderBy($orderBy)->fetchAll('id'); + } + else + { + $stories = $this->dao->select('*')->from(TABLE_STORY)->where($this->session->storyQueryCondition)->orderBy($orderBy)->fetchAll('id'); + } + } + else + { + $field = $executionID ? 't2.id' : 't1.id'; + if($this->post->exportType == 'selected') + { + $stmt = $this->dbh->query("SELECT * FROM " . TABLE_STORY . "WHERE `id` IN({$this->cookie->checkedItem})" . " ORDER BY " . strtr($orderBy, '_', ' ')); + } + else + { + $stmt = $this->dbh->query($this->session->storyQueryCondition . " ORDER BY " . strtr($orderBy, '_', ' ')); + } + while($row = $stmt->fetch()) $stories[$row->id] = $row; + } + $storyIdList = array_keys($stories); + + if($stories) + { + $children = array(); + foreach($stories as $story) + { + if($story->parent > 0 and isset($stories[$story->parent])) + { + $children[$story->parent][$story->id] = $story; + unset($stories[$story->id]); + } + } + + if(!empty($children)) + { + $reorderStories = array(); + foreach($stories as $story) + { + $reorderStories[$story->id] = $story; + if(isset($children[$story->id])) + { + foreach($children[$story->id] as $childrenID => $childrenStory) + { + $reorderStories[$childrenID] = $childrenStory; + } + } + unset($stories[$story->id]); + } + $stories = $reorderStories; + } + } + + /* Get users, products and relations. */ + $users = $this->loadModel('user')->getPairs('noletter'); + $products = $this->product->getPairs('nocode'); + $relations = $this->story->getStoryRelationByIds($storyIdList, $type); + + /* Get related objects id lists. */ + $relatedProductIdList = array(); + $relatedStoryIdList = array(); + $relatedPlanIdList = array(); + $relatedBranchIdList = array(); + $relatedStoryIds = array(); + + foreach($stories as $story) + { + $relatedProductIdList[$story->product] = $story->product; + $relatedPlanIdList[$story->plan] = $story->plan; + $relatedBranchIdList[$story->branch] = $story->branch; + $relatedStoryIds[$story->id] = $story->id; + + if(isset($relations[$story->id])) $story->childStories = $story->childStories . ',' . $relations[$story->id]; + + /* Process related stories. */ + $relatedStories = $story->childStories . ',' . $story->linkStories . ',' . $story->duplicateStory; + $relatedStories = explode(',', $relatedStories); + foreach($relatedStories as $storyID) + { + if($storyID) $relatedStoryIdList[$storyID] = trim($storyID); + } + } + + $storyTasks = $this->loadModel('task')->getStoryTaskCounts($relatedStoryIds); + $storyBugs = $this->loadModel('bug')->getStoryBugCounts($relatedStoryIds); + $storyCases = $this->loadModel('testcase')->getStoryCaseCounts($relatedStoryIds); + + /* Get related objects title or names. */ + $productsType = $this->dao->select('id, type')->from(TABLE_PRODUCT)->where('id')->in($relatedProductIdList)->fetchPairs(); + $relatedPlans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in(join(',', $relatedPlanIdList))->fetchPairs(); + $relatedStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($relatedStoryIdList)->fetchPairs(); + $relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq($type)->andWhere('objectID')->in($storyIdList)->andWhere('extra')->ne('editor')->fetchGroup('objectID'); + $relatedSpecs = $this->dao->select('*')->from(TABLE_STORYSPEC)->where('`story`')->in($storyIdList)->orderBy('version desc')->fetchGroup('story'); + $relatedBranch = array('0' => $this->lang->branch->main) + $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($relatedBranchIdList)->fetchPairs(); + $relatedModules = $this->loadModel('tree')->getAllModulePairs(); + + foreach($stories as $story) + { + $story->spec = ''; + $story->verify = ''; + if(isset($relatedSpecs[$story->id])) + { + $storySpec = $relatedSpecs[$story->id][0]; + $story->title = $storySpec->title; + $story->spec = $storySpec->spec; + $story->verify = $storySpec->verify; + } + + if($this->post->fileType == 'csv') + { + $story->spec = htmlspecialchars_decode($story->spec); + $story->spec = str_replace("
", "\n", $story->spec); + $story->spec = str_replace('"', '""', $story->spec); + $story->spec = str_replace(' ', ' ', $story->spec); + + $story->verify = htmlspecialchars_decode($story->verify); + $story->verify = str_replace("
", "\n", $story->verify); + $story->verify = str_replace('"', '""', $story->verify); + $story->verify = str_replace(' ', ' ', $story->verify); + } + /* fill some field with useful value. */ + if(isset($products[$story->product])) $story->product = $this->post->fileType == 'word' ? $products[$story->product] : $products[$story->product] . "(#$story->product)"; + if(isset($relatedModules[$story->module])) $story->module = $this->post->fileType == 'word' ? $relatedModules[$story->module] : $relatedModules[$story->module] . "(#$story->module)"; + if(isset($relatedBranch[$story->branch])) $story->branch = $relatedBranch[$story->branch] . "(#$story->branch)"; + if(isset($story->plan)) + { + $plans = ''; + foreach(explode(',', $story->plan) as $planID) + { + if(empty($planID)) continue; + if(isset($relatedPlans[$planID])) $plans .= $this->post->fileType == 'word' ? $relatedPlans[$planID] : $relatedPlans[$planID] . "(#$planID)"; + } + $story->plan = $plans; + } + if(isset($relatedStories[$story->duplicateStory]) and $story->closedReason != 'duplicate') $story->duplicateStory = $relatedStories[$story->duplicateStory]; + + if(isset($storyLang->priList[$story->pri])) $story->pri = $storyLang->priList[$story->pri]; + if(isset($storyLang->statusList[$story->status])) $story->status = $this->processStatus('story', $story); + if(isset($storyLang->stageList[$story->stage])) $story->stage = $storyLang->stageList[$story->stage]; + if(isset($storyLang->reasonList[$story->closedReason])) $story->closedReason = $storyLang->reasonList[$story->closedReason]; + if(isset($storyLang->sourceList[$story->source])) $story->source = $storyLang->sourceList[$story->source]; + if(isset($storyLang->sourceList[$story->sourceNote])) $story->sourceNote = $storyLang->sourceList[$story->sourceNote]; + + if(isset($users[$story->openedBy])) $story->openedBy = $users[$story->openedBy]; + if(isset($users[$story->assignedTo])) $story->assignedTo = $users[$story->assignedTo] . "(#$story->assignedTo)"; + if(isset($users[$story->lastEditedBy])) $story->lastEditedBy = $users[$story->lastEditedBy]; + if(isset($users[$story->closedBy])) $story->closedBy = $users[$story->closedBy]; + + if(isset($storyTasks[$story->id])) $story->taskCountAB = $storyTasks[$story->id]; + if(isset($storyBugs[$story->id])) $story->bugCountAB = $storyBugs[$story->id]; + if(isset($storyCases[$story->id])) $story->caseCountAB = $storyCases[$story->id]; + + $story->openedDate = substr($story->openedDate, 0, 10); + $story->assignedDate = substr($story->assignedDate, 0, 10); + $story->lastEditedDate = substr($story->lastEditedDate, 0, 10); + $story->closedDate = substr($story->closedDate, 0, 10); + + if($story->linkStories) + { + $tmpLinkStories = array(); + $linkStoriesIdList = explode(',', $story->linkStories); + foreach($linkStoriesIdList as $linkStoryID) + { + $linkStoryID = trim($linkStoryID); + $tmpLinkStories[] = isset($relatedStories[$linkStoryID]) ? $relatedStories[$linkStoryID] : $linkStoryID; + } + $story->linkStories = join("; \n", $tmpLinkStories); + } + + if($story->childStories) + { + $tmpChildStories = array(); + $childStoriesIdList = explode(',', $story->childStories); + foreach($childStoriesIdList as $childStoryID) + { + if(empty($childStoryID)) continue; + + $childStoryID = trim($childStoryID); + $tmpChildStories[] = isset($relatedStories[$childStoryID]) ? $relatedStories[$childStoryID] : $childStoryID; + } + $story->childStories = join("; \n", $tmpChildStories); + } + + /* Set related files. */ + $story->files = ''; + if(isset($relatedFiles[$story->id])) + { + foreach($relatedFiles[$story->id] as $file) + { + $fileURL = common::getSysURL() . helper::createLink('file', 'download', "fileID=$file->id"); + $story->files .= html::a($fileURL, $file->title, '_blank') . '
'; + } + } + + $story->mailto = trim(trim($story->mailto), ','); + $mailtos = explode(',', $story->mailto); + $story->mailto = ''; + foreach($mailtos as $mailto) + { + $mailto = trim($mailto); + if(isset($users[$mailto])) $story->mailto .= $users[$mailto] . ','; + } + $story->mailto = rtrim($story->mailto, ','); + + $story->reviewedBy = trim(trim($story->reviewedBy), ','); + $reviewedBys = explode(',', $story->reviewedBy); + $story->reviewedBy = ''; + foreach($reviewedBys as $reviewedBy) + { + $reviewedBy = trim($reviewedBy); + if(isset($users[$reviewedBy])) $story->reviewedBy .= $users[$reviewedBy] . ','; + } + $story->reviewedBy = rtrim($story->reviewedBy, ','); + + /* Set child story title. */ + if($story->parent > 0 && strpos($story->title, htmlentities('>', ENT_COMPAT | ENT_HTML401, 'UTF-8')) !== 0) $story->title = '>' . $story->title; + } + + if($executionID) + { + $header = new stdclass(); + $header->name = 'execution'; + $header->tableName = TABLE_EXECUTION; + + $this->post->set('header', $header); + } + if(!(in_array('platform', $productsType) or in_array('branch', $productsType))) unset($fields['branch']);// If products's type are normal, unset branch field. + + if($this->config->edition != 'open') list($fields, $stories) = $this->loadModel('workflowfield')->appendDataFromFlow($fields, $stories); + + $this->post->set('fields', $fields); + $this->post->set('rows', $stories); + $this->post->set('kind', 'story'); + $this->fetch('file', 'export2' . $this->post->fileType, $_POST); + } + + $fileName = $type == 'requirement' ? $this->lang->URCommon : $this->lang->SRCommon; + if($executionID) + { + $executionName = $this->dao->findById($executionID)->from(TABLE_PROJECT)->fetch('name'); + $fileName = $executionName . $this->lang->dash . $fileName; + } + else + { + $productName = $productID ? $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch('name') : $this->lang->product->all; + if(isset($this->lang->product->featureBar['browse'][$browseType])) + { + $browseType = $this->lang->product->featureBar['browse'][$browseType]; + } + else + { + $browseType = isset($this->lang->product->moreSelects[$browseType]) ? $this->lang->product->moreSelects[$browseType] : ''; + } + + $fileName = $productName . $this->lang->dash . $browseType . $fileName; + } + + $this->view->fileName = $fileName; + $this->view->allExportFields = $this->config->story->list->exportFields; + $this->view->customExport = true; + $this->display(); + } + + /** + * AJAX: get stories of a user in html select. + * + * @param int $userID + * @param string $id the id of the select control. + * @param int $appendID + * @access public + * @return string + */ + public function ajaxGetUserStories($userID = '', $id = '', $appendID = 0) + { + if($userID == '') $userID = $this->app->user->id; + $user = $this->loadModel('user')->getById($userID, 'id'); + $stories = $this->story->getUserStoryPairs($user->account, 10, 'story', '', $appendID); + + if($id) return print(html::select("stories[$id]", $stories, '', 'class="form-control"')); + echo html::select('story', $stories, '', 'class=form-control'); + } + + /** + * Ajax get story status. + * + * @param string $method + * @param string $params + * @access public + * @return void + */ + public function ajaxGetStatus($method, $params = '') + { + parse_str(str_replace(',', '&', $params), $params); + $status = ''; + if($method == 'create') + { + $status = 'draft'; + if(!empty($params['needNotReview'])) $status = 'active'; + if(!empty($params['project'])) $status = 'active'; + if($this->story->checkForceReview()) $status = 'draft'; + } + elseif($method == 'change') + { + $oldStory = $this->dao->findById((int)$params['storyID'])->from(TABLE_STORY)->fetch(); + $status = $oldStory->status; + if($params['changed'] and $oldStory->status == 'active' and empty($params['needNotReview'])) $status = 'changed'; + if($params['changed'] and $oldStory->status == 'active' and $this->story->checkForceReview()) $status = 'changed'; + if($params['changed'] and $oldStory->status == 'draft' and $params['needNotReview']) $status = 'active'; + } + elseif($method == 'review') + { + $oldStory = $this->dao->findById((int)$params['storyID'])->from(TABLE_STORY)->fetch(); + $status = $oldStory->status; + if($params['result'] == 'revert') $status = 'active'; + } + echo $status; + } + + /** + * Ajax get story assignee. + * + * @param string $type create|review|change + * @param int $storyID + * @param array $assignees + * + * @access public + * @return void + */ + public function ajaxGetAssignedTo($type = '', $storyID = 0, $assignees = '') + { + $users = $this->loadModel('user')->getPairs('noletter|noclosed'); + + if($type == 'create') + { + $selectUser = is_array($assignees) ? current($assignees) : ''; + + return print(html::select('assignedTo', $users, $selectUser, "class='from-control picker-select'")); + } + + if($type == 'review') + { + $story = $this->story->getByID($storyID); + $reviewers = $this->story->getReviewerPairs($storyID, $story->version); + $isChanged = $story->changedBy ? true : false; + $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); + + if(count($reviewers) == 1) + { + $selectUser = $isChanged ? $story->changedBy : $story->openedBy; + } + else + { + unset($reviewers[$this->app->user->account]); + foreach($reviewers as $account => $result) + { + if(!$reviewers[$account]) + { + $selectUser = $account; + break; + } + else + { + $selectUser = $isChanged ? $story->changedBy : $story->openedBy; + } + } + } + + if($isSuperReviewer !== false) $selectUser = $isChanged ? $story->changedBy : $story->openedBy; + + return print(html::select('assignedTo', $users, $selectUser, "class='from-control picker-select'")); + } + + if($type == 'change') + { + $selectUser = is_array($assignees) ? current($assignees) : ''; + + return print(html::select('assignedTo', $users, $selectUser, "class='from-control picker-select'")); + } + + return false; + + } +} diff --git a/module/story:q/css/affected.css b/module/story:q/css/affected.css new file mode 100644 index 0000000000..95b64370eb --- /dev/null +++ b/module/story:q/css/affected.css @@ -0,0 +1,2 @@ +.c-consumed {width :100px;} +.c-left {width :90px;} diff --git a/module/story:q/css/batchclose.css b/module/story:q/css/batchclose.css new file mode 100644 index 0000000000..9b0816cf80 --- /dev/null +++ b/module/story:q/css/batchclose.css @@ -0,0 +1,2 @@ +.c-id {width: 50px;} +.c-reason {width: 150px;} diff --git a/module/story:q/css/batchcreate.css b/module/story:q/css/batchcreate.css new file mode 100644 index 0000000000..b748ec13a0 --- /dev/null +++ b/module/story:q/css/batchcreate.css @@ -0,0 +1,12 @@ +#importLinesModal .modal-dialog {width: 80%;} + +#batchCreateForm .c-id {width: 50px; text-align: center; color: #838A9D;} +#batchCreateForm .c-module, +#batchCreateForm .c-plan {width: 180px;} +#batchCreateForm .c-name {width: 180px;} +#batchCreateForm .c-pri {width: 100px;} +#batchCreateForm .c-review {width: 95px;} +#batchCreateForm .c-estimate {width: 90px;} +#batchCreateForm .c-verify {width: 140px;} +#batchCreateForm .c-keywords {width: 140px;} +.body-modal .main-header > h2 {max-width: 80%;} diff --git a/module/story:q/css/batchedit.css b/module/story:q/css/batchedit.css new file mode 100644 index 0000000000..27f97a560b --- /dev/null +++ b/module/story:q/css/batchedit.css @@ -0,0 +1,11 @@ +#suhosinInfo {margin-bottom: 0;} +.c-id {width: 40px;} +.c-estimate {width: 50px;} +.c-branch, .c-module {width: 120px;} +.c-title {width: 150px;} +.c-plan {width: 170px;} +.c-category {width: 90px;} +.c-pri {width: 70px;} +.c-source {width: 130px;} +.c-note, .c-stage, .c-reason {width: 100px;} +.c-keywords {width: 80px;} diff --git a/module/story:q/css/bugs.css b/module/story:q/css/bugs.css new file mode 100644 index 0000000000..32fcae10c7 --- /dev/null +++ b/module/story:q/css/bugs.css @@ -0,0 +1,4 @@ +body {background: white;} +.bugsList {padding: 10px;} +.bugsList .table {border: 1px solid #ddd;} +.c-id {width: 40px} diff --git a/module/story:q/css/cases.css b/module/story:q/css/cases.css new file mode 100644 index 0000000000..f4783dc8df --- /dev/null +++ b/module/story:q/css/cases.css @@ -0,0 +1,4 @@ +body {background: white;} +.casesList {padding: 10px;} +.casesList .table {border: 1px solid #ddd;} +.c-id, .c-result {width: 50px;} diff --git a/module/story:q/css/change.css b/module/story:q/css/change.css new file mode 100644 index 0000000000..3fec429184 --- /dev/null +++ b/module/story:q/css/change.css @@ -0,0 +1,5 @@ +.input-group .chosen-container-single .chosen-single {border-radius: 2px 0 0 2px;} +#affectedProjects .table + h6 {border-top: 1px solid #ddd;} +.nav-tabs>li>a {height: 36px;} +.table-form>tbody>tr>th {width: 100px !important;} +.colorpicker {right: 7px; top: 7px; opacity: 1;} \ No newline at end of file diff --git a/module/story:q/css/close.en.css b/module/story:q/css/close.en.css new file mode 100644 index 0000000000..7e102351a5 --- /dev/null +++ b/module/story:q/css/close.en.css @@ -0,0 +1 @@ +.thWidth {width: 150px !important;} diff --git a/module/story:q/css/close.vi.css b/module/story:q/css/close.vi.css new file mode 100644 index 0000000000..7e102351a5 --- /dev/null +++ b/module/story:q/css/close.vi.css @@ -0,0 +1 @@ +.thWidth {width: 150px !important;} diff --git a/module/story:q/css/close.zh-cn.css b/module/story:q/css/close.zh-cn.css new file mode 100644 index 0000000000..76da72e24c --- /dev/null +++ b/module/story:q/css/close.zh-cn.css @@ -0,0 +1 @@ +.thWidth {width: 80px !important;} diff --git a/module/story:q/css/close.zh-tw.css b/module/story:q/css/close.zh-tw.css new file mode 100644 index 0000000000..76da72e24c --- /dev/null +++ b/module/story:q/css/close.zh-tw.css @@ -0,0 +1 @@ +.thWidth {width: 80px !important;} diff --git a/module/story:q/css/common.css b/module/story:q/css/common.css new file mode 100644 index 0000000000..c4fb6f3f82 --- /dev/null +++ b/module/story:q/css/common.css @@ -0,0 +1,19 @@ +.linkbox {height: 180px; overflow-y: auto;} +.tab-pane h6 {margin: 0px; padding: 10px;} +.tab-pane h6, .tab-pane table {border: 1px solid #ddd; border-top: none;} +.tab-pane .table-data.table-borderless {border: none !important;} + +.input-group .control-product + .chosen-container-single .chosen-single {border-radius: 2px 0 0 2px; border-right-width: 0; padding-right: 1px;} +.input-group .control-product + .chosen-container-active .chosen-single {border-right-width: 1px; padding-right: 0;} +.input-group .control-branch + .chosen-container-single .chosen-single {border-radius: 0 2px 2px 0;} + +#batchCreateForm .input-group, #batchEditForm .input-group, +#batchCreateForm .input-group .form-control, #batchEditForm .input-group .form-control {position: static;} +#batchCreateForm .input-group .colorpicker, #batchEditForm .input-group .colorpicker {z-index: 2;} +#batchCreateForm .input-group .colorpicker.open, #batchEditForm .input-group .colorpicker.open {z-index: 5;} +#branch_chosen .chosen-single span {word-break: break-all;} + +.c-branch, .c-spec {width: 150px} +.c-source {width: 130px} +.c-note {width: 100px} +.c-category {width: 90px} diff --git a/module/story:q/css/create.css b/module/story:q/css/create.css new file mode 100644 index 0000000000..fa75e956fd --- /dev/null +++ b/module/story:q/css/create.css @@ -0,0 +1,9 @@ +.input-control.required:after {display: block; right: 29px; top: 5px;} +#pri + .chosen-container > .chosen-single {border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important;} + +.pri-selector > .btn {padding: 5px 8px !important; width: 100%;} +.pri-selector > .dropdown-menu {padding: 10px;} + +#source_chosen {min-width: 50px !important;} +#feedbackBox > .input-group + #notifyEmailLabel {position: relative; width: 50%; left: 48%} +#product + .picker{min-width: 400px} diff --git a/module/story:q/css/create.en.css b/module/story:q/css/create.en.css new file mode 100644 index 0000000000..b56a63b71b --- /dev/null +++ b/module/story:q/css/create.en.css @@ -0,0 +1 @@ +.input-size {width: 525px;} diff --git a/module/story:q/css/create.vi.css b/module/story:q/css/create.vi.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/module/story:q/css/create.zh-cn.css b/module/story:q/css/create.zh-cn.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/module/story:q/css/edit.css b/module/story:q/css/edit.css new file mode 100644 index 0000000000..325971cb2f --- /dev/null +++ b/module/story:q/css/edit.css @@ -0,0 +1 @@ +#product_chosen .chosen-single{border-right-width:1px} diff --git a/module/story:q/css/edit.en.css b/module/story:q/css/edit.en.css new file mode 100644 index 0000000000..55736d2890 --- /dev/null +++ b/module/story:q/css/edit.en.css @@ -0,0 +1,2 @@ +.thWidth {width: 100px !important;} +.linkThWidth {width: 110px !important;} diff --git a/module/story:q/css/edit.vi.css b/module/story:q/css/edit.vi.css new file mode 100644 index 0000000000..55736d2890 --- /dev/null +++ b/module/story:q/css/edit.vi.css @@ -0,0 +1,2 @@ +.thWidth {width: 100px !important;} +.linkThWidth {width: 110px !important;} diff --git a/module/story:q/css/edit.zh-cn.css b/module/story:q/css/edit.zh-cn.css new file mode 100644 index 0000000000..637f071167 --- /dev/null +++ b/module/story:q/css/edit.zh-cn.css @@ -0,0 +1,2 @@ +.thWidth {width: 80px !important;} +.linkThWidth {width: 70px !important;} diff --git a/module/story:q/css/edit.zh-tw.css b/module/story:q/css/edit.zh-tw.css new file mode 100644 index 0000000000..637f071167 --- /dev/null +++ b/module/story:q/css/edit.zh-tw.css @@ -0,0 +1,2 @@ +.thWidth {width: 80px !important;} +.linkThWidth {width: 70px !important;} diff --git a/module/story:q/css/linkstory.css b/module/story:q/css/linkstory.css new file mode 100644 index 0000000000..17e2033b52 --- /dev/null +++ b/module/story:q/css/linkstory.css @@ -0,0 +1 @@ +#linkStoryForm {min-height: 300px;} diff --git a/module/story:q/css/report.css b/module/story:q/css/report.css new file mode 100644 index 0000000000..3f6d52bed1 --- /dev/null +++ b/module/story:q/css/report.css @@ -0,0 +1,8 @@ +.table-bordered caption {border: 1px solid #ddd;} +.table-bordered tr > th:first-child, .table-bordered tr > td:first-child {border-left: 1px solid #ddd !important;} +.table-bordered tr > th:last-child, .table-bordered tr > td:last-child {border-right: 1px solid #ddd !important;} + +.panel-heading > strong > span {float: right; color: #29a8cd;} + +.checkboxes {margin-bottom: 10px;} +.checkboxes .checkbox-primary {margin-bottom: 5px;} diff --git a/module/story:q/css/review.en.css b/module/story:q/css/review.en.css new file mode 100644 index 0000000000..51f62d77b9 --- /dev/null +++ b/module/story:q/css/review.en.css @@ -0,0 +1 @@ +.thWidth {width: 110px !important;} diff --git a/module/story:q/css/review.vi.css b/module/story:q/css/review.vi.css new file mode 100644 index 0000000000..51f62d77b9 --- /dev/null +++ b/module/story:q/css/review.vi.css @@ -0,0 +1 @@ +.thWidth {width: 110px !important;} diff --git a/module/story:q/css/review.zh-cn.css b/module/story:q/css/review.zh-cn.css new file mode 100644 index 0000000000..c50aad163d --- /dev/null +++ b/module/story:q/css/review.zh-cn.css @@ -0,0 +1 @@ +.thWidth {width: 120px !important;} diff --git a/module/story:q/css/review.zh-tw.css b/module/story:q/css/review.zh-tw.css new file mode 100644 index 0000000000..76da72e24c --- /dev/null +++ b/module/story:q/css/review.zh-tw.css @@ -0,0 +1 @@ +.thWidth {width: 80px !important;} diff --git a/module/story:q/css/tasks.css b/module/story:q/css/tasks.css new file mode 100644 index 0000000000..254f5276ee --- /dev/null +++ b/module/story:q/css/tasks.css @@ -0,0 +1,4 @@ +body {background: white;} +.tasksList {padding: 10px;} +.tasksList .table {border: 1px solid #ddd;} +.c-id, .c-estiamte, .c-consumed, .c-left, .c-progress {width: 40px;} diff --git a/module/story:q/css/track.css b/module/story:q/css/track.css new file mode 100644 index 0000000000..7408dc06fc --- /dev/null +++ b/module/story:q/css/track.css @@ -0,0 +1 @@ +.table td {text-align: center;} diff --git a/module/story:q/css/view.css b/module/story:q/css/view.css new file mode 100644 index 0000000000..191a5b1f34 --- /dev/null +++ b/module/story:q/css/view.css @@ -0,0 +1,5 @@ +.side-col .cell {padding: 0px;} +.side-col #legendProjectAndTask .list-unstyled {padding: 10px; border-top: 0px; margin: 0px;} +#legendStories ul li {padding: 10px 0 0 12px; line-height: 20px;} +.main-actions .btn-toolbar .btn {padding-right: 6px; padding-left: 6px;} +.btn-edit-comment {z-index: 100;} diff --git a/module/story:q/css/view.en.css b/module/story:q/css/view.en.css new file mode 100644 index 0000000000..1c5a496e3c --- /dev/null +++ b/module/story:q/css/view.en.css @@ -0,0 +1,2 @@ +#legendLifeTime .thWidth {width: 100px !important;} +#legendRelated .thWidth {width: 110px !important;} diff --git a/module/story:q/css/view.vi.css b/module/story:q/css/view.vi.css new file mode 100644 index 0000000000..1c5a496e3c --- /dev/null +++ b/module/story:q/css/view.vi.css @@ -0,0 +1,2 @@ +#legendLifeTime .thWidth {width: 100px !important;} +#legendRelated .thWidth {width: 110px !important;} diff --git a/module/story:q/css/view.zh-cn.css b/module/story:q/css/view.zh-cn.css new file mode 100644 index 0000000000..974243e6af --- /dev/null +++ b/module/story:q/css/view.zh-cn.css @@ -0,0 +1,2 @@ +#legendLifeTime .thWidth {width: 70px !important;} +#legendRelated .thWidth {width: 70px !important;} diff --git a/module/story:q/css/view.zh-tw.css b/module/story:q/css/view.zh-tw.css new file mode 100644 index 0000000000..974243e6af --- /dev/null +++ b/module/story:q/css/view.zh-tw.css @@ -0,0 +1,2 @@ +#legendLifeTime .thWidth {width: 70px !important;} +#legendRelated .thWidth {width: 70px !important;} diff --git a/module/story:q/css/x.change.css b/module/story:q/css/x.change.css new file mode 100644 index 0000000000..287413342d --- /dev/null +++ b/module/story:q/css/x.change.css @@ -0,0 +1,2 @@ +.main-header {display: none;} +.form-actions button:last-child {display: none;} diff --git a/module/story:q/css/x.create.css b/module/story:q/css/x.create.css new file mode 100644 index 0000000000..502dabc8aa --- /dev/null +++ b/module/story:q/css/x.create.css @@ -0,0 +1,2 @@ +a.btn-back {display: none;} +#mailto ~ span.input-group-btn {display: none;} diff --git a/module/story:q/css/x.edit.css b/module/story:q/css/x.edit.css new file mode 100644 index 0000000000..d7f6148b4c --- /dev/null +++ b/module/story:q/css/x.edit.css @@ -0,0 +1,7 @@ +.form-actions {margin-top: 0px;} +.form-actions button:last-child {display: none;} +#mailto ~ .input-group-btn {display: none;} +#mainContent .main-row .col-8 {width: 55%;} +#mainContent .main-row .col-4 {width: 45%;} +.main-header {display: none;} +.main-row .side-col.col-4 .detail:last-child {display: none;} diff --git a/module/story:q/css/x.review.css b/module/story:q/css/x.review.css new file mode 100644 index 0000000000..287413342d --- /dev/null +++ b/module/story:q/css/x.review.css @@ -0,0 +1,2 @@ +.main-header {display: none;} +.form-actions button:last-child {display: none;} diff --git a/module/story:q/css/x.view.css b/module/story:q/css/x.view.css new file mode 100644 index 0000000000..dccc99b6bf --- /dev/null +++ b/module/story:q/css/x.view.css @@ -0,0 +1,17 @@ +body * {font-size: 13px !important; line-height: 1.42857143; color: rgb(51, 51, 51);} +#main {margin-bottom: 40px; min-width: unset;} +#mainMenu {position: fixed; width: 100%; z-index: 999;} +#mainMenu .pull-left>a, #mainMenu .pull-left .divider, #mainMenu .pull-right, #mainContent .col-4, .main-actions {display: none;} +#scrollContent {margin-top: 35px; height: calc(100% - 70px); display: block; overflow: hidden;} +#scrollContent:hover {overflow: overlay;} +html, body, #main, .container { height: 100%;} +.btn span {line-height: 16px; vertical-align: middle;} +.col-8>.cell {min-height: 300px;} +.modal-dialog {width: 90%;} +.page-title {width: 100%;} +.page-title > .label-id {min-width: 25px; line-height: 13px;} +.page-title > .text {display: inline-block; width: calc(100% - 90px); text-overflow: ellipsis; white-space: nowrap; overflow: hidden; line-height: 13px;} +.linkButton {float: right; padding-right: 10px; cursor: pointer;} +.linkButton i {font-size: 17px !important;} +.linkButton span {line-height: 34px; display: block;} +.btn-toolbar {width: calc(100% - 30px);} diff --git a/module/story:q/css/zerocase.en.css b/module/story:q/css/zerocase.en.css new file mode 100644 index 0000000000..c50aad163d --- /dev/null +++ b/module/story:q/css/zerocase.en.css @@ -0,0 +1 @@ +.thWidth {width: 120px !important;} diff --git a/module/story:q/css/zerocase.vi.css b/module/story:q/css/zerocase.vi.css new file mode 100644 index 0000000000..c50aad163d --- /dev/null +++ b/module/story:q/css/zerocase.vi.css @@ -0,0 +1 @@ +.thWidth {width: 120px !important;} diff --git a/module/story:q/css/zerocase.zh-cn.css b/module/story:q/css/zerocase.zh-cn.css new file mode 100644 index 0000000000..b72a60e0e5 --- /dev/null +++ b/module/story:q/css/zerocase.zh-cn.css @@ -0,0 +1 @@ +.thWidth {width: 90px !important;} diff --git a/module/story:q/css/zerocase.zh-tw.css b/module/story:q/css/zerocase.zh-tw.css new file mode 100644 index 0000000000..b72a60e0e5 --- /dev/null +++ b/module/story:q/css/zerocase.zh-tw.css @@ -0,0 +1 @@ +.thWidth {width: 90px !important;} diff --git a/module/story:q/js/batchclose.js b/module/story:q/js/batchclose.js new file mode 100755 index 0000000000..45150d3088 --- /dev/null +++ b/module/story:q/js/batchclose.js @@ -0,0 +1,26 @@ +/** + * Set duplicate field. + * + * @param string $resolution + * @param int $storyID + * @access public + * @return void + */ +function setDuplicateAndChild(resolution, storyID) +{ + if(resolution == 'duplicate') + { + $('#childStoryBox' + storyID).hide(); + $('#duplicateStoryBox' + storyID).show(); + } + else if(resolution == 'subdivided') + { + $('#duplicateStoryBox' + storyID).hide(); + $('#childStoryBox' + storyID).show(); + } + else + { + $('#duplicateStoryBox' + storyID).hide(); + $('#childStoryBox' + storyID).hide(); + } +} diff --git a/module/story:q/js/batchcreate.js b/module/story:q/js/batchcreate.js new file mode 100644 index 0000000000..5660e14ee6 --- /dev/null +++ b/module/story:q/js/batchcreate.js @@ -0,0 +1,138 @@ +$(function() +{ + if($('#batchCreateForm table thead tr th.col-name').width() < 200) $('#batchCreateForm table thead tr th.col-name').width(200); +}); +$(document).on('click', '.chosen-with-drop', function() +{ + var select = $(this).prev('select'); + if($(select).val() == 'ditto') + { + var index = $(select).closest('td').index(); + var row = $(select).closest('tr').index(); + var table = $(select).closest('tr').parent(); + var value = ''; + for(i = row - 1; i >= 0; i--) + { + value = $(table).find('tr').eq(i).find('td').eq(index).find('select').val(); + if(value != 'ditto') break; + } + $(select).val(value); + $(select).trigger("chosen:updated"); + } +}); +$(document).on('mousedown', 'select', function() +{ + if($(this).val() == 'ditto') + { + var index = $(this).closest('td').index(); + var row = $(this).closest('tr').index(); + var table = $(this).closest('tr').parent(); + var value = ''; + for(i = row - 1; i >= 0; i--) + { + value = $(table).find('tr').eq(i).find('td').eq(index).find('select').val(); + if(value != 'ditto') break; + } + $(this).val(value); + } +}); + +/** + * Set modules and plans. + * + * @param int $branchID + * @param int $productID + * @param int $num + * @access public + * @return void + */ +function setModuleAndPlan(branchID, productID, num) +{ + moduleLink = createLink('tree', 'ajaxGetModules', 'productID=' + productID + '&viewType=story&branch=' + branchID + '&num=' + num); + $.get(moduleLink, function(modules) + { + if(!modules) modules = ''; + $('#module' + num).replaceWith(modules); + $("#module" + num + "_chosen").remove(); + $("#module" + num).next('.picker').remove(); + $("#module" + num).chosen(); + }); + + planLink = createLink('productPlan', 'ajaxGetProductPlans', 'productID=' + productID + '&branch=' + branchID + '&num=' + num + '&expired=unexpired'); + $.get(planLink, function(plans) + { + if(!plans) plans = ''; + $('#plan' + num).replaceWith(plans); + $("#plan" + num + "_chosen").remove(); + $("#plan" + num).next('.picker').remove(); + $("#plan" + num).chosen(); + }); + + /* If the branch of the current row is inconsistent with the one below, clear the module and plan of the nex row. */ + var nextBranchID = $('#branch' + (num + 1)).val(); + if(nextBranchID != branchID) + { + $('#module' + (num + 1)).find("option[value='ditto']").remove(); + $('#module' + (num + 1)).trigger("chosen:updated"); + + $('#plan' + (num + 1)).find("option[value='ditto']").remove(); + $('#plan' + (num + 1)).trigger("chosen:updated"); + } +} + +/* Copy story title as story spec. */ +function copyTitle(num) +{ + var title = $('#title\\[' + num + '\\]').val(); + $('#spec\\[' + num + '\\]').val(title); +} + +$(document).on('change', "[name*='reviewer']", function() +{ + toggleCheck($(this)); +}) + +/** + * Toggle checkbox. + * + * @param obj $obj + * @access public + * @return void + */ +function toggleCheck(obj) +{ + var $this = $(obj); + var data = $this.val(); + var $ditto = $this.closest('div').find("input[name*='reviewDitto']"); + if(data == '') + { + $ditto.attr('checked', true); + $ditto.closest('.input-group-addon').show(); + } + else + { + $ditto.removeAttr('checked'); + $ditto.closest('.input-group-addon').hide(); + } +} + +/** + * Set lane. + * + * @param int $regionID + * @param int $num + * @access public + * @return void + */ +function setLane(regionID, num) +{ + laneLink = createLink('kanban', 'ajaxGetLanes', 'regionID=' + regionID + '&type=story&field=lanes&i=' + num); + $.get(laneLink, function(lanes) + { + if(!lanes) lanes = ''; + $('#lanes' + num).replaceWith(lanes); + $("#lanes" + num + "_chosen").remove(); + $("#lanes" + num).next('.picker').remove(); + $("#lanes" + num).chosen(); + }); +} diff --git a/module/story:q/js/batchedit.js b/module/story:q/js/batchedit.js new file mode 100644 index 0000000000..6847a40766 --- /dev/null +++ b/module/story:q/js/batchedit.js @@ -0,0 +1,101 @@ +/** + * Set duplicate field. + * + * @param string $resolution + * @param int $storyID + * @access public + * @return void + */ +function setDuplicateAndChild(resolution, storyID) +{ + if(resolution == 'duplicate') + { + $('#childStoryBox' + storyID).hide(); + $('#duplicateStoryBox' + storyID).show(); + } + else if(resolution == 'subdivided') + { + $('#duplicateStoryBox' + storyID).hide(); + $('#childStoryBox' + storyID).show(); + } + else + { + $('#duplicateStoryBox' + storyID).hide(); + $('#childStoryBox' + storyID).hide(); + } +} + +function loadBranches(product, branch, storyID) +{ + if(typeof(branch) == 'undefined') branch = 0; + if(!branch) branch = 0; + + var currentModuleID = $('#modules' + storyID).val(); + moduleLink = createLink('tree', 'ajaxGetOptionMenu', 'productID=' + product + '&viewtype=story&branch=' + branch + '&rootModuleID=0&returnType=html&fieldID=' + storyID + '&needManage=false&extra=¤tModuleID=' + currentModuleID); + $('#modules' + storyID).parent('td').load(moduleLink, function(){$('#modules' + storyID).chosen();}); + + planID = $('#plans' + storyID).val(); + planLink = createLink('product', 'ajaxGetPlans', 'productID=' + product + '&branch=' + branch + '&planID=' + planID + '&fieldID=' + storyID + '&needCreate=false&expired=¶m=skipParent'); + $('#plans' + storyID).parent('td').load(planLink, function(){$('#plans' + storyID).chosen();}); +} + +$(function() +{ + //Remove 'ditto' in first row. + removeDitto(); + + // Init bactch action form + $('#batchEditForm').batchActionForm(); +}); + +$(document).on('click', '.chosen-with-drop', function(){oldValue = $(this).prev('select').val();})//Save old value. + +/* Set ditto value. */ +$(document).on('change', 'select', function() +{ + if($(this).val() == 'ditto') + { + var index = $(this).closest('td').index(); + var row = $(this).closest('tr').index(); + var tbody = $(this).closest('tr').parent(); + + if($(this).attr('name').indexOf('closedReasons') != -1) + { + index = $(this).closest('tr').closest('td').index(); + row = $(this).closest('tr').closest('td').parent().index(); + tbody = $(this).closest('tr').closest('td').parent().parent(); + } + + var value = ''; + for(i = row - 1; i >= 0; i--) + { + value = tbody.children('tr').eq(i).find('td').eq(index).find('select').val(); + if(value != 'ditto') break; + } + + isModules = $(this).attr('name').indexOf('modules') != -1; + isPlans = $(this).attr('name').indexOf('plans') != -1; + + if(isModules || isPlans) + { + + var valueStr = ',' + $(this).find('option').map(function(){return $(this).val();}).get().join(',') + ','; + if(valueStr.indexOf(',' + value + ',') != -1) + { + $(this).val(value); + } + else + { + alert(dittoNotice); + $(this).val(oldValue); + } + } + else + { + $(this).val(value); + } + + $(this).trigger("chosen:updated"); + $(this).trigger("change"); + } +}); diff --git a/module/story:q/js/change.js b/module/story:q/js/change.js new file mode 100644 index 0000000000..3d075f1112 --- /dev/null +++ b/module/story:q/js/change.js @@ -0,0 +1,51 @@ +$(function() +{ + $('#needNotReview').on('change', function() + { + $('#reviewer').attr('disabled', $(this).is(':checked') ? 'disabled' : null).trigger('chosen:updated'); + if($(this).is(':checked')) + { + $('.input-group-addon').removeClass('required'); + } + else + { + $('.input-group-addon').addClass('required'); + } + + loadAssignedTo(); + }); + $('#needNotReview').change(); + + if($('#reviewer').val()) loadAssignedTo(); + + if($('.tabs .tab-content .tab-pane.active').children().length == 0) $('.tabs .nav-tabs li.active').css('border-bottom', '1px solid #ccc'); +}); + +/** + * Load assignedTo. + * + * @access public + * @return void + */ +function loadAssignedTo() +{ + var assignees = $('#reviewer').val(); + var link = createLink('story', 'ajaxGetAssignedTo', 'type=change&storyID=' + storyID + '&assignees=' + assignees); + $.post(link, function(data) + { + $('#assignedTo').replaceWith(data); + $('#assignedToBox .picker').remove(); + $('#assignedTo').picker(); + }); + + if($('#needNotReview').is(':checked')) + { + $('#assignedToBox').removeClass('hidden'); + $('#reviewerBox').attr('colspan', 1); + } + else + { + $('#assignedToBox').addClass('hidden'); + $('#reviewerBox').attr('colspan', 2); + } +} diff --git a/module/story:q/js/close.js b/module/story:q/js/close.js new file mode 100644 index 0000000000..73b0e1988a --- /dev/null +++ b/module/story:q/js/close.js @@ -0,0 +1,18 @@ +function setStory(reason) +{ + if(reason == 'duplicate') + { + $('#duplicateStoryBox').show(); + $('#childStoriesBox').hide(); + } + else if(reason == 'subdivided') + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').show(); + } + else + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + } +} diff --git a/module/story:q/js/common.js b/module/story:q/js/common.js new file mode 100644 index 0000000000..8cc11344f3 --- /dev/null +++ b/module/story:q/js/common.js @@ -0,0 +1,22 @@ +$(function() +{ + if(typeof(resetActive) != 'undefined') return false; + if(typeof(storyType) == 'undefined') storyType = ''; + if(typeof(rawModule) == 'undefined') rawModule = 'product'; + if(typeof(app) == 'undefined') app = ''; + if(typeof(execution) != 'undefined') rawModule = 'projectstory'; + if(['project', 'projectstory'].indexOf(rawModule) === -1 && app != 'qa') + { + if(app != 'my') $('#navbar .nav li').removeClass('active'); + $("#navbar .nav li[data-id=" + storyType + ']').addClass('active'); + $('#subNavbar li[data-id="' + storyType + '"]').addClass('active'); + } +}) + +function getStatus(method, params) +{ + $.get(createLink('story', 'ajaxGetStatus', "method=" + method + '¶ms=' + params), function(status) + { + $('form #status').val(status).change(); + }); +} diff --git a/module/story:q/js/create.js b/module/story:q/js/create.js new file mode 100644 index 0000000000..150abcd111 --- /dev/null +++ b/module/story:q/js/create.js @@ -0,0 +1,108 @@ +$(function() +{ + $('#needNotReview').on('change', function() + { + $('#reviewer').attr('disabled', $(this).is(':checked') ? 'disabled' : null).trigger('chosen:updated'); + if($(this).is(':checked')) + { + $('#reviewerBox').removeClass('required'); + } + else + { + $('#reviewerBox').addClass('required'); + } + loadAssignedTo(); + + getStatus('create', "product=" + $('#product').val() + ",execution=" + executionID + ",needNotReview=" + ($(this).prop('checked') ? 1 : 0)); + }); + $('#needNotReview').change(); + + if($('#reviewer').val()) loadAssignedTo(); + + // init pri selector + $('#pri').on('change', function() + { + var $select = $(this); + var $selector = $select.closest('.pri-selector'); + var value = $select.val(); + $selector.find('.pri-text').html('' + value + ''); + }); + + $('#source').on('change', function() + { + if(storyType == 'requirement') return false; + + var source = $(this).val(); + if($.inArray(source, feedbackSource) != -1) + { + $('#feedbackBox').removeClass('hidden'); + $('#reviewerBox').attr('colspan', $('#assignedToBox').hasClass('hidden') ? 2 : 1); + $('#assignedToBox').attr('colspan', 1); + } + else + { + $('#feedbackBox').addClass('hidden'); + $('#reviewerBox').attr('colspan', $('#assignedToBox').hasClass('hidden') ? 4 : 2); + $('#assignedToBox').attr('colspan', 2); + } + }); +}); + +/** + * Load assignedTo. + * + * @access public + * @return void + */ +function loadAssignedTo() +{ + var assignees = $('#reviewer').val(); + var link = createLink('story', 'ajaxGetAssignedTo', 'type=create&storyID=0&assignees=' + assignees); + $.post(link, function(data) + { + $('#assignedTo').replaceWith(data); + $('#assignedToBox .picker').remove(); + $('#assignedTo').picker(); + }); + + var colspan = $('#assignedToBox').attr('colspan'); + if($('#needNotReview').is(':checked')) + { + $('#assignedToBox').removeClass('hidden'); + $('#reviewerBox').attr('colspan', colspan); + } + else + { + $('#assignedToBox').addClass('hidden'); + $('#reviewerBox').attr('colspan', colspan * 2); + } +} + +function refreshPlan() +{ + loadProductPlans($('#product').val(), $('#branch').val()); +} + +/** + * Set lane. + * + * @param int $regionID + * @access public + * @return void + */ +function setLane(regionID) +{ + laneLink = createLink('kanban', 'ajaxGetLanes', 'regionID=' + regionID + '&type=story&field=lane'); + $.get(laneLink, function(lane) + { + if(!lane) lane = ""; + $('#lane').replaceWith(lane); + $('#lane' + "_chosen").remove(); + $('#lane').next('.picker').remove(); + $('#lane').chosen(); + }); +} + +$(window).unload(function(){ + if(blockID) window.parent.refreshBlock($('#block' + blockID)); +}); diff --git a/module/story:q/js/edit.js b/module/story:q/js/edit.js new file mode 100644 index 0000000000..f234363a3e --- /dev/null +++ b/module/story:q/js/edit.js @@ -0,0 +1,35 @@ +$(function() +{ + $('.main-side #branch').closest('td').find('#product_chosen .chosen-single').css('width', '153px'); + $('#reviewer_chosen .search-choice').each(function() + { + if(reviewedReviewer.indexOf($(this).find('span').text()) != -1) $(this).css('pointer-events', 'none'); + }) + + $('#reviewer').change(function() + { + if($('#reviewer_chosen .search-choice').length === 1) + { + alert(reviewerNotEmpty); + $('#reviewer').val(reviewers); + $('#reviewer').trigger('chosen:updated'); + } + else + { + reviewers = $('#reviewer').val(); + } + }) + + $('#source').on('change', function() + { + var source = $(this).val(); + if($.inArray(source, feedbackSource) != -1) + { + $('.feedbackBox').removeClass('hidden'); + } + else + { + $('.feedbackBox').addClass('hidden'); + } + }); +}) diff --git a/module/story:q/js/report.js b/module/story:q/js/report.js new file mode 100644 index 0000000000..ccc0b1fa55 --- /dev/null +++ b/module/story:q/js/report.js @@ -0,0 +1,19 @@ +$(function() +{ + var resizeChartTable = function() + { + $('.table-wrapper').each(function() + { + var $this = $(this); + $this.css('max-height', $this.closest('.table').find('.chart-wrapper').outerHeight()); + }); + }; + resizeChartTable(); + $(window).resize(resizeChartTable); +}); + +function changeChartType(type) +{ + $('form').attr('action', createLink('story', 'report', 'productID=' + productID + '&branchID=' + branchID + '&storyType=' + storyType + '&browseType=' + browseType + '&moduleID=' + moduleID + '&chartType=' + type)); + $('form').find('#submit').click(); +} diff --git a/module/story:q/js/review.js b/module/story:q/js/review.js new file mode 100644 index 0000000000..afac5139c7 --- /dev/null +++ b/module/story:q/js/review.js @@ -0,0 +1,94 @@ +function switchShow(result) +{ + $('#priBox').hide(); + $('#estimateBox').hide(); + if(result == 'reject') + { + $('#rejectedReasonBox').show(); + $('#preVersionBox').hide(); + $('#assignedToBox').hide(); + if(isMultiple) + { + $('#assignedToBox').show(); + loadAssignedTo(); + } + } + else if(result == 'revert') + { + $('#preVersionBox').show(); + $('#rejectedReasonBox').hide(); + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + $('#assignedToBox').show(); + loadAssignedTo(); + } + else if(result == 'clarify') + { + $('#preVersionBox').hide(); + $('#rejectedReasonBox').hide(); + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + $('#rejectedReasonBox').hide(); + $('#assignedToBox').show(); + loadAssignedTo(); + } + else + { + $('#preVersionBox').hide(); + $('#rejectedReasonBox').hide(); + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + $('#rejectedReasonBox').hide(); + $('#assignedToBox').hide(); + if(result == 'pass') + { + $('#priBox').show(); + $('#estimateBox').show(); + $('#assignedToBox').show(); + loadAssignedTo(); + } + } + + getStatus('review', "storyID=" + storyID + ",result=" + result); +} + +function setStory(reason) +{ + if(reason == 'duplicate') + { + $('#duplicateStoryBox').show(); + $('#childStoriesBox').hide(); + } + else if(reason == 'subdivided') + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').show(); + } + else + { + $('#duplicateStoryBox').hide(); + $('#childStoriesBox').hide(); + } +} + +/** + * Load assignedTo. + * + * @access public + * @return void + */ +function loadAssignedTo() +{ + var link = createLink('story', 'ajaxGetAssignedTo', 'type=review&storyID=' + storyID); + $.post(link, function(data) + { + $('#assignedTo').replaceWith(data); + $('#assignedToBox .picker').remove(); + $('#assignedTo').picker(); + }); +} + +$(function() +{ + if($('.tabs .tab-content .tab-pane.active').children().length == 0) $('.tabs .nav-tabs li.active').css('border-bottom', '1px solid #ccc'); +}) diff --git a/module/story:q/js/track.js b/module/story:q/js/track.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/module/story:q/js/view.js b/module/story:q/js/view.js new file mode 100644 index 0000000000..9a215855b9 --- /dev/null +++ b/module/story:q/js/view.js @@ -0,0 +1,25 @@ +$(function() +{ + if(canCreate) + { + link = createLink('story', 'create', 'productID=' + productID + "&branch=" + branch + '&moduleID=' + moduleID); + $('#modulemenu ul.nav li.right:first').before("
  • " + createStory + "
  • "); + } + + /* Add for task #5385. */ + if(config.onlybody == 'yes') $('.main-actions').css('width', '100%') + + $('#unlinkStory').click(function() + { + if($('.removeButton').hasClass('hide')) + { + $('.removeButton').removeClass('hide'); + $(this).text(cancel); + } + else + { + $('.removeButton').addClass('hide'); + $(this).text(unlink); + } + }) +}); diff --git a/module/story:q/js/x.create.js b/module/story:q/js/x.create.js new file mode 100644 index 0000000000..f107b0e075 --- /dev/null +++ b/module/story:q/js/x.create.js @@ -0,0 +1,8 @@ +$(function() +{ + $('#sourceNote').prev('span').remove(); + $('#sourceNote').remove(); + $('table tbody tr th').addClass('w-100px'); + + $('#tplBoxWrapper .btn-toolbar .btn-group').remove(); +}) diff --git a/module/story:q/js/x.view.js b/module/story:q/js/x.view.js new file mode 100644 index 0000000000..6e946f6ebc --- /dev/null +++ b/module/story:q/js/x.view.js @@ -0,0 +1,46 @@ +$(function() +{ + $('.main-actions-holder').css('height', '0px'); + var xuanAction = "
    "; + $('.main-col div.main-actions .btn-toolbar a').each(function(){ + var $that = $(this); + + if($that.attr('id') == 'back') return true; + + var href = $that.attr('href'); + var title = $that.attr('title') == undefined ? '' : " title='" + $that.attr('title') + "'"; + var btnClass = " class='" + $that.attr('class') + "'"; + var action = $that.html(); + + if(href.indexOf('browse') >= 0 || href.indexOf('batchCreate') >= 0 + || href.indexOf('create') >= 0 || href.indexOf('delete') >= 0) return true; + + if($that.hasClass('iframe')) + { + url = href; + target = ''; + } + else + { + url = 'xxc:openUrlInDialog/' + encodeURIComponent(sysurl + href); + target = " target='_blank'"; + } + + xuanAction += "' + action + ""; + }); + + if(xuanAction != "
    ") + { + var params = window.location.search; + if(params.indexOf('isNotice=1') == -1) + { + xuanAction += '
    '; + $('body').append(xuanAction); + } + } + else + { + $('#scrollContent').css('height', 'calc(100% - 36px)'); + } + $('.xuancard-actions a.iframe').modalTrigger(); +}) diff --git a/module/story:q/js/zerocase.js b/module/story:q/js/zerocase.js new file mode 100644 index 0000000000..618b5d612c --- /dev/null +++ b/module/story:q/js/zerocase.js @@ -0,0 +1,4 @@ +$(document).ready(function() +{ + $('#zerocaseTab').addClass('btn-active-text'); +}); diff --git a/module/story:q/lang/de.php b/module/story:q/lang/de.php new file mode 100644 index 0000000000..78ed708a0f --- /dev/null +++ b/module/story:q/lang/de.php @@ -0,0 +1,441 @@ + + * @package story + * @version $Id: en.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +global $config; +$lang->story->create = "Story hinzufügen"; + +$lang->story->requirement = zget($lang, 'URCommon', "Requirement"); +$lang->story->story = zget($lang, 'SRCommon', "Story"); +$lang->story->createStory = 'Create ' . $lang->story->story; +$lang->story->createRequirement = 'Create ' . $lang->story->requirement; +$lang->story->affectedStories = "Affected {$lang->story->story}"; + +$lang->story->batchCreate = "Mehere hinzufügen"; +$lang->story->change = "Ändern"; +$lang->story->changed = 'Geändert'; +$lang->story->assignTo = 'Assign'; +$lang->story->review = 'Prüfen'; +$lang->story->recall = 'Revoke'; +$lang->story->needReview = 'Need Review'; +$lang->story->batchReview = 'Mehere prüfen'; +$lang->story->edit = "Bearbeiten"; +$lang->story->batchEdit = "Mehere bearbeiten"; +$lang->story->subdivide = 'Aufteilen'; +$lang->story->link = 'Link'; +$lang->story->unlink = 'Unlink'; +$lang->story->track = 'Track'; +$lang->story->trackAB = 'Track'; +$lang->story->processStoryChange = 'Confirm Story Change'; +$lang->story->splitRequirent = 'Decompose'; +$lang->story->close = 'Schließen'; +$lang->story->batchClose = 'Mehere schließen'; +$lang->story->activate = 'Aktivieren'; +$lang->story->delete = "Löschen"; +$lang->story->view = "Storydetails"; +$lang->story->setting = "Einstellungen"; +$lang->story->tasks = "Verknüpfte Aufgaben"; +$lang->story->bugs = "Verknüpfte Bugs"; +$lang->story->cases = "Verknüpfte Fälle"; +$lang->story->taskCount = 'Aufgaben'; +$lang->story->bugCount = 'Bugs'; +$lang->story->caseCount = 'Fälle'; +$lang->story->taskCountAB = 'T'; +$lang->story->bugCountAB = 'B'; +$lang->story->caseCountAB = 'C'; +$lang->story->linkStory = 'Verküpfen'; +$lang->story->unlinkStory = 'Verknüpfung aufheben'; +$lang->story->export = "Daten exportieren"; +$lang->story->zeroCase = "Storys ohne Fälle"; +$lang->story->zeroTask = "Storys ohne Aufgaben anzeigen"; +$lang->story->reportChart = "Bericht"; +$lang->story->copyTitle = "Titel kopieren"; +$lang->story->batchChangePlan = "Mehere Pläne ändern"; +$lang->story->batchChangeBranch = "Mehere Branches ändern"; +$lang->story->batchChangeStage = "Mehere Phasen ändern"; +$lang->story->batchAssignTo = "Mehere zuordnen"; +$lang->story->batchChangeModule = "Mehere Module ändern"; +$lang->story->viewAll = "Alle"; +$lang->story->toTask = 'Convert to Task'; +$lang->story->batchToTask = 'Batch Convert to Task'; +$lang->story->convertRelations = 'Convert Relations'; +$lang->story->undetermined = 'undetermined'; +$lang->story->order = 'Order'; + +$lang->story->editAction = "Edit {$lang->SRCommon}"; +$lang->story->changeAction = "Change Story"; +$lang->story->assignAction = 'Assign Story'; +$lang->story->reviewAction = 'Review Story'; +$lang->story->subdivideAction = 'Decompose Story'; +$lang->story->closeAction = 'Close Story'; +$lang->story->activateAction = 'Activate Story'; +$lang->story->deleteAction = "Delete Story"; +$lang->story->exportAction = "Export Story"; +$lang->story->reportAction = "Story Report"; + +$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; +$lang->story->closedStory = 'Story %s is closed and will not be closed.'; +$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; +$lang->story->successToTask = "Converted to task."; +$lang->story->storyRound = '%s time estimation'; + +$lang->story->id = 'ID'; +$lang->story->parent = 'Parent'; +$lang->story->product = $lang->productCommon; +$lang->story->project = 'Project'; +$lang->story->branch = "Branch/Platform"; +$lang->story->module = 'Module'; +$lang->story->moduleAB = 'Module'; +$lang->story->source = 'Von'; +$lang->story->sourceNote = 'Hinweis'; +$lang->story->fromBug = 'Von Bug'; +$lang->story->title = 'Titel'; +$lang->story->type = "Story/Requirement"; +$lang->story->category = 'Category'; +$lang->story->color = 'Color'; +$lang->story->toBug = 'ToBug'; +$lang->story->spec = 'Beschreibung'; +$lang->story->assign = 'Assign'; +$lang->story->verify = 'Akzeptanz'; +$lang->story->pri = 'Priorität'; +$lang->story->estimate = 'Schätzung(h)'; +$lang->story->estimateAB = 'Schätzung(h)'; +$lang->story->hour = 'Stunde'; +$lang->story->status = 'Status'; +$lang->story->subStatus = 'Sub Status'; +$lang->story->stage = 'Phase'; +$lang->story->stageAB = 'Phase'; +$lang->story->stagedBy = 'SetBy'; +$lang->story->mailto = 'Mail an'; +$lang->story->openedBy = 'Ersteller'; +$lang->story->openedDate = 'Erstellt am'; +$lang->story->assignedTo = 'Zuständiger'; +$lang->story->assignedDate = 'Zugewisen am'; +$lang->story->lastEditedBy = 'Letzte Bearbeitung'; +$lang->story->lastEditedDate = 'Bearbeitet am'; +$lang->story->closedBy = 'Geschlossen von'; +$lang->story->closedDate = 'Geschlossen am'; +$lang->story->closedReason = 'Geschlossen weil'; +$lang->story->rejectedReason = 'Abgelehnt weil'; +$lang->story->reviewedBy = 'Prüfer'; +$lang->story->reviewer = $lang->story->reviewedBy; +$lang->story->reviewers = 'Reviewers'; +$lang->story->reviewedDate = 'Geprüft am'; +$lang->story->activatedDate = 'Activated Date'; +$lang->story->version = 'Version'; +$lang->story->feedbackBy = 'From Name'; +$lang->story->notifyEmail = 'From Email'; +$lang->story->plan = 'Plan'; +$lang->story->planAB = 'Plan'; +$lang->story->comment = 'Kommentar'; +$lang->story->children = "Child {$lang->SRCommon}"; +$lang->story->childrenAB = "C"; +$lang->story->linkStories = 'Story verknüpfen'; +$lang->story->childStories = 'Story aufteilen'; +$lang->story->duplicateStory = 'Story ID kopieren'; +$lang->story->reviewResult = 'Ergbnis prüfen'; +$lang->story->preVersion = 'Frühere Version'; +$lang->story->keywords = 'Tags'; +$lang->story->newStory = 'Weitere Story'; +$lang->story->colorTag = 'Farb-Tag'; +$lang->story->files = 'Dateien'; +$lang->story->copy = "Story kopieren"; +$lang->story->total = "Total Storys"; +$lang->story->draft = 'Entwurf'; +$lang->story->unclosed = 'Nicht geschlossen'; +$lang->story->deleted = 'Gelöscht'; +$lang->story->released = 'Released Linked Stories'; +$lang->story->URChanged = 'Requirement Changed'; +$lang->story->design = 'Designs'; +$lang->story->case = 'Cases'; +$lang->story->bug = 'Bugs'; +$lang->story->repoCommit = 'Commits'; +$lang->story->noRequirement = 'No Requirements'; +$lang->story->one = 'One'; +$lang->story->field = 'Synchronized fields'; +$lang->story->completeRate = 'Completion Rate'; +$lang->story->reviewed = 'Reviewed'; +$lang->story->toBeReviewed = 'To Be Reviewed'; +$lang->story->linkMR = 'Related MRs'; + +$lang->story->ditto = 'Dito'; +$lang->story->dittoNotice = 'Die Story gehört nicht zum Projekt wie die vorherige!'; + +$lang->story->needNotReviewList[0] = 'Need Review'; +$lang->story->needNotReviewList[1] = 'Need Not Review'; + +$lang->story->useList[0] = 'Ja'; +$lang->story->useList[1] = 'Nein'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = 'Entwurf'; +$lang->story->statusList['active'] = 'Aktiv'; +$lang->story->statusList['closed'] = 'Geschlossen'; +$lang->story->statusList['changed'] = 'Geändert'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = 'Wartend'; +$lang->story->stageList['planned'] = 'Geplant'; +$lang->story->stageList['projected'] = 'Projektiert'; +$lang->story->stageList['developing'] = 'Entwicklung'; +$lang->story->stageList['developed'] = 'Entwickelt'; +$lang->story->stageList['testing'] = 'Testen'; +$lang->story->stageList['tested'] = 'Getestet'; +$lang->story->stageList['verified'] = 'Akzepiert'; +$lang->story->stageList['released'] = 'Freigegeben'; +$lang->story->stageList['closed'] = 'Geschlossen'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = 'Erledigt'; +$lang->story->reasonList['subdivided'] = 'Aufgeteilt'; +$lang->story->reasonList['duplicate'] = 'Dublette'; +$lang->story->reasonList['postponed'] = 'Verschoben'; +$lang->story->reasonList['willnotdo'] = "Ignoriert"; +$lang->story->reasonList['cancel'] = 'Abgebrochen'; +$lang->story->reasonList['bydesign'] = 'Kein Fehler'; +//$lang->story->reasonList['isbug'] = 'Bug!'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = 'Gültig'; +$lang->story->reviewResultList['revert'] = 'Umkehren'; +$lang->story->reviewResultList['clarify'] = 'Klären'; +$lang->story->reviewResultList['reject'] = 'Ablehnen'; + +$lang->story->reviewList[0] = 'Nein'; +$lang->story->reviewList[1] = 'Ja'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = 'Kunde'; +$lang->story->sourceList['user'] = 'Benutzer'; +$lang->story->sourceList['po'] = $lang->productCommon . ' Eigentümer'; +$lang->story->sourceList['market'] = 'Marketing'; +$lang->story->sourceList['service'] = 'Service'; +$lang->story->sourceList['operation'] = 'Operative'; +$lang->story->sourceList['support'] = 'Support'; +$lang->story->sourceList['competitor'] = 'Wettbewerber'; +$lang->story->sourceList['partner'] = 'Partner'; +$lang->story->sourceList['dev'] = 'Entwickler'; +$lang->story->sourceList['tester'] = 'QS-Team'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['forum'] = 'Forum'; +$lang->story->sourceList['other'] = 'Andere'; + +$lang->story->priList[] = ''; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[3] = '3'; +$lang->story->priList[4] = '4'; + +$lang->story->changeList = array(); +$lang->story->changeList['no'] = 'Cancel'; +$lang->story->changeList['yes'] = 'Confirm'; + +$lang->story->legendBasicInfo = 'Basis Infos'; +$lang->story->legendLifeTime = 'Story Leben '; +$lang->story->legendRelated = 'Weitere Infos'; +$lang->story->legendMailto = 'Mail an'; +$lang->story->legendAttatch = 'Dateien'; +$lang->story->legendProjectAndTask = $lang->executionCommon . ' Aufgaben'; +$lang->story->legendBugs = 'Verküpfte Bugs'; +$lang->story->legendFromBug = 'Verküpfte Formular Bugs'; +$lang->story->legendCases = 'Verküpfte Fälle'; +$lang->story->legendLinkStories = 'Verküpfte Storys'; +$lang->story->legendChildStories = 'Story auteien'; +$lang->story->legendSpec = 'Beschreibung'; +$lang->story->legendVerify = 'Akzeptanz'; +$lang->story->legendMisc = 'Sonstiges '; +$lang->story->legendInformation = 'Story Information'; + +$lang->story->lblChange = 'Ändern'; +$lang->story->lblReview = 'Prüfen'; +$lang->story->lblActivate = 'Aktivieren'; +$lang->story->lblClose = 'Close'; +$lang->story->lblTBC = 'Task/Bug/Case'; + +$lang->story->checkAffection = 'Impact'; +$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; +$lang->story->affectedBugs = 'Bug'; +$lang->story->affectedCases = 'Fall'; + +$lang->story->specTemplate = "Als ein < type of user >, möchte ich < some goal > dass < some reason >."; +$lang->story->needNotReview = 'Keine Prüfung'; +$lang->story->successSaved = "Story wurde gespeichrt!"; +$lang->story->confirmDelete = "Möchten Sie diese Story löschen?"; +$lang->story->confirmRecall = "Do you want to recall this story?"; +$lang->story->errorEmptyChildStory = '『Unterteilte Story』 darf nicht leer sein.'; +$lang->story->errorNotSubdivide = "If the status is not active, or the stage is not wait, or a sub story, it cannot be subdivided."; +$lang->story->errorEmptyReviewedBy = "『ReviewedBy』darf nicht leer sein."; +$lang->story->mustChooseResult = 'Ergebnis wählen'; +$lang->story->mustChoosePreVersion = 'Version wählen um es umzukhren.'; +$lang->story->noStory = 'Keine Storys. '; +$lang->story->noRequirement = 'No Requirements'; +$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->cannotDeleteParent = "Can not delete parent {$lang->SRCommon}"; +$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; +$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; +$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; +$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; +$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; +$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; +$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; + +$lang->story->form = new stdclass(); +$lang->story->form->area = 'Story Bereich'; +$lang->story->form->desc = 'Welche Story ist es? Was sind die Abnahmebedingungen?'; +$lang->story->form->resource = 'Wer plant die Resourcen? Wie lange wird das dauern?'; +$lang->story->form->file = 'Wenn Dateien zu dieser Story gehören, laden Sie diese bIttr hoch.'; + +$lang->story->action = new stdclass(); +$lang->story->action->reviewed = array('main' => '$date, geprüft von $actor. Das Ergebnis ist $extra.', 'extra' => 'reviewResultList'); +$lang->story->action->rejectreviewed = array('main' => '$date, geprüft von $actor. Das Ergebnis ist $extra, Der Grund ist $reason.', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); +$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); +$lang->story->action->closed = array('main' => '$date, geschlossen von $actor. Der Grund ist $extra $appendLink.', 'extra' => 'reasonList'); +$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); +$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); +$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); +$lang->story->action->linked2plan = array('main' => '$date, verknüpft von $actor mit Plan $extra'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Plan $extra.'); +$lang->story->action->linked2execution = array('main' => '$date, verknüpft von $actor mit ' . $lang->executionCommon . ' $extra.'); +$lang->story->action->unlinkedfromexecution = array('main' => '$date, Verknüpfung aufgelöst durch $actor mit ' . $lang->executionCommon . ' $extra.'); +$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); +$lang->story->action->linked2project = array('main' => '$date, verknüpft von $actor mit project $extra.'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, Verknüpfung aufgelöst durch $actor mit project $extra.'); +$lang->story->action->linked2build = array('main' => '$date, verknüpft von $actor mit Build $extra'); +$lang->story->action->unlinkedfrombuild = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Build $extra.'); +$lang->story->action->linked2release = array('main' => '$date, verknüpft von $actor mit Release $extra'); +$lang->story->action->unlinkedfromrelease = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Release $extra.'); +$lang->story->action->linkrelatedstory = array('main' => '$date, verknüpft von $actor mit Story $extra.'); +$lang->story->action->subdividestory = array('main' => '$date, aufgeteilt von $actor mit Story $extra.'); +$lang->story->action->unlinkrelatedstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Story $extra.'); +$lang->story->action->unlinkchildstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor Story $extra.'); + +/* Statistical statement. */ +$lang->story->report = new stdclass(); +$lang->story->report->common = 'Bericht'; +$lang->story->report->select = 'Storys gruppieren nach'; +$lang->story->report->create = 'Erzeugen'; +$lang->story->report->value = 'Story Anzahl'; + +$lang->story->report->charts['storysPerProduct'] = 'nach ' . $lang->productCommon; +$lang->story->report->charts['storysPerModule'] = 'nach Modul'; +$lang->story->report->charts['storysPerSource'] = 'nach Source'; +$lang->story->report->charts['storysPerPlan'] = 'nach Plan'; +$lang->story->report->charts['storysPerStatus'] = 'nach Status'; +$lang->story->report->charts['storysPerStage'] = 'nach Phase'; +$lang->story->report->charts['storysPerPri'] = 'nach Priorität'; +$lang->story->report->charts['storysPerEstimate'] = 'nach Stunden'; +$lang->story->report->charts['storysPerOpenedBy'] = 'nach Ersteller'; +$lang->story->report->charts['storysPerAssignedTo'] = 'nach Zuständiger'; +$lang->story->report->charts['storysPerClosedReason'] = 'nach Grund'; +$lang->story->report->charts['storysPerChange'] = 'nach Änderungszeit'; + +$lang->story->report->options = new stdclass(); +$lang->story->report->options->graph = new stdclass(); +$lang->story->report->options->type = 'pie'; +$lang->story->report->options->width = 500; +$lang->story->report->options->height = 140; + +$lang->story->report->storysPerProduct = new stdclass(); +$lang->story->report->storysPerModule = new stdclass(); +$lang->story->report->storysPerSource = new stdclass(); +$lang->story->report->storysPerPlan = new stdclass(); +$lang->story->report->storysPerStatus = new stdclass(); +$lang->story->report->storysPerStage = new stdclass(); +$lang->story->report->storysPerPri = new stdclass(); +$lang->story->report->storysPerOpenedBy = new stdclass(); +$lang->story->report->storysPerAssignedTo = new stdclass(); +$lang->story->report->storysPerClosedReason = new stdclass(); +$lang->story->report->storysPerEstimate = new stdclass(); +$lang->story->report->storysPerChange = new stdclass(); + +$lang->story->report->storysPerProduct->item = $lang->productCommon; +$lang->story->report->storysPerModule->item = 'Modul'; +$lang->story->report->storysPerSource->item = 'Source'; +$lang->story->report->storysPerPlan->item = 'Plan'; +$lang->story->report->storysPerStatus->item = 'Status'; +$lang->story->report->storysPerStage->item = 'Phase'; +$lang->story->report->storysPerPri->item = 'Priorität'; +$lang->story->report->storysPerOpenedBy->item = 'Konto'; +$lang->story->report->storysPerAssignedTo->item = 'Benutzer'; +$lang->story->report->storysPerClosedReason->item = 'Grund'; +$lang->story->report->storysPerEstimate->item = 'Stunden'; +$lang->story->report->storysPerChange->item = 'Änderung'; + +$lang->story->report->storysPerProduct->graph = new stdclass(); +$lang->story->report->storysPerModule->graph = new stdclass(); +$lang->story->report->storysPerSource->graph = new stdclass(); +$lang->story->report->storysPerPlan->graph = new stdclass(); +$lang->story->report->storysPerStatus->graph = new stdclass(); +$lang->story->report->storysPerStage->graph = new stdclass(); +$lang->story->report->storysPerPri->graph = new stdclass(); +$lang->story->report->storysPerOpenedBy->graph = new stdclass(); +$lang->story->report->storysPerAssignedTo->graph = new stdclass(); +$lang->story->report->storysPerClosedReason->graph = new stdclass(); +$lang->story->report->storysPerEstimate->graph = new stdclass(); +$lang->story->report->storysPerChange->graph = new stdclass(); + +$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; +$lang->story->report->storysPerModule->graph->xAxisName = 'Modul'; +$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; +$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; +$lang->story->report->storysPerStatus->graph->xAxisName = 'Status'; +$lang->story->report->storysPerStage->graph->xAxisName = 'Phase'; +$lang->story->report->storysPerPri->graph->xAxisName = 'Priorität'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Ersteller'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Zuständiger'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Grund'; +$lang->story->report->storysPerEstimate->graph->xAxisName = 'Stunden '; +$lang->story->report->storysPerChange->graph->xAxisName = 'Änderung'; + +$lang->story->placeholder = new stdclass(); +$lang->story->placeholder->estimate = $lang->story->hour; + +$lang->story->chosen = new stdClass(); +$lang->story->chosen->reviewedBy = 'Prüfer wählen'; + +$lang->story->notice = new stdClass(); +$lang->story->notice->closed = 'Die ausgewählten Storys wurden bereits geschlossen!'; +$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; + +$lang->story->convertToTask = new stdClass(); +$lang->story->convertToTask->fieldList = array(); +$lang->story->convertToTask->fieldList['module'] = 'Module'; +$lang->story->convertToTask->fieldList['spec'] = "Description"; +$lang->story->convertToTask->fieldList['pri'] = 'Priority'; +$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; +$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; + +$lang->story->categoryList['feature'] = 'Feature'; +$lang->story->categoryList['interface'] = 'Interface'; +$lang->story->categoryList['performance'] = 'Performance'; +$lang->story->categoryList['safe'] = 'Safe'; +$lang->story->categoryList['experience'] = 'Experience'; +$lang->story->categoryList['improve'] = 'Improve'; +$lang->story->categoryList['other'] = 'Other'; + +$lang->story->changeTip = 'This Story is under review and cannot be changed.'; + +$lang->story->reviewTip = array(); +$lang->story->reviewTip['active'] = 'The Story is already active,no review requirements.'; +$lang->story->reviewTip['notReviewer'] = 'You are not the reviewer of this Story and cannot perform review operations.'; +$lang->story->reviewTip['reviewed'] = 'Reviewed'; +$lang->story->reviewTip['recalled'] = 'This Story has been withdrawn from review and cannot be reviewed.'; + +$lang->story->recallTip = array(); +$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.'; +$lang->story->recallTip['recalled'] = 'This Story has been withdrawn from review and cannot be cancelled.'; +$lang->story->recallTip['reviewed'] = 'The review process has been started and cannot be cancelled.'; +$lang->story->recallTip['notOpenedBy'] = 'You are not the Story creator and cannot cancel the operation.'; + +$lang->story->subDivideTip = array(); +$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.'; +$lang->story->subDivideTip['planned'] = 'The Story has been planned and cannot be subdivided.'; +$lang->story->subDivideTip['projected'] = 'The Story has been initiated and cannot be subdivided.'; +$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.'; diff --git a/module/story:q/lang/en.php b/module/story:q/lang/en.php new file mode 100644 index 0000000000..0261736f59 --- /dev/null +++ b/module/story:q/lang/en.php @@ -0,0 +1,441 @@ + + * @package story + * @version $Id: en.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +global $config; +$lang->story->create = "Create Story"; + +$lang->story->requirement = zget($lang, 'URCommon', "Requirement"); +$lang->story->story = zget($lang, 'SRCommon', "Story"); +$lang->story->createStory = 'Create ' . $lang->story->story; +$lang->story->createRequirement = 'Create ' . $lang->story->requirement; +$lang->story->affectedStories = "Affected {$lang->story->story}"; + +$lang->story->batchCreate = "Batch Create"; +$lang->story->change = "Change"; +$lang->story->changed = 'Change'; +$lang->story->assignTo = 'Assign'; +$lang->story->review = 'Review'; +$lang->story->recall = 'Revoke'; +$lang->story->needReview = 'Need Review'; +$lang->story->batchReview = 'Batch Review'; +$lang->story->edit = "Edit Story"; +$lang->story->batchEdit = "Batch Edit"; +$lang->story->subdivide = 'Decompose'; +$lang->story->link = 'Link'; +$lang->story->unlink = 'Unlink'; +$lang->story->track = 'Track'; +$lang->story->trackAB = 'Track'; +$lang->story->processStoryChange = 'Confirm Story Change'; +$lang->story->splitRequirent = 'Decompose'; +$lang->story->close = 'Close'; +$lang->story->batchClose = 'Batch Close'; +$lang->story->activate = 'Activate'; +$lang->story->delete = "Delete"; +$lang->story->view = "Story Detail"; +$lang->story->setting = "Settings"; +$lang->story->tasks = "Linked Tasks"; +$lang->story->bugs = "Linked Bugs"; +$lang->story->cases = "Linked Cases"; +$lang->story->taskCount = 'Tasks'; +$lang->story->bugCount = 'Bugs'; +$lang->story->caseCount = 'Cases'; +$lang->story->taskCountAB = 'T'; +$lang->story->bugCountAB = 'B'; +$lang->story->caseCountAB = 'C'; +$lang->story->linkStory = 'Link Story'; +$lang->story->unlinkStory = 'UnLinked'; +$lang->story->export = "Export Data"; +$lang->story->zeroCase = "Stories without cases"; +$lang->story->zeroTask = "Only list stories without tasks"; +$lang->story->reportChart = "Report"; +$lang->story->copyTitle = "Copy Title"; +$lang->story->batchChangePlan = "Batch Change Plans"; +$lang->story->batchChangeBranch = "Batch Change Branches"; +$lang->story->batchChangeStage = "Batch Change Phases"; +$lang->story->batchAssignTo = "Batch Assign"; +$lang->story->batchChangeModule = "Batch Change Modules"; +$lang->story->viewAll = "See All"; +$lang->story->toTask = 'Convert to Task'; +$lang->story->batchToTask = 'Batch Convert to Task'; +$lang->story->convertRelations = 'Convert Relations'; +$lang->story->undetermined = 'undetermined'; +$lang->story->order = 'Order'; + +$lang->story->editAction = "Edit {$lang->SRCommon}"; +$lang->story->changeAction = "Change {$lang->SRCommon}"; +$lang->story->assignAction = "Assign {$lang->SRCommon}"; +$lang->story->reviewAction = "Review {$lang->SRCommon}"; +$lang->story->subdivideAction = "Subdivide {$lang->SRCommon}"; +$lang->story->closeAction = "Close {$lang->SRCommon}"; +$lang->story->activateAction = "Activate {$lang->SRCommon}"; +$lang->story->deleteAction = "Delete {$lang->SRCommon}"; +$lang->story->exportAction = "Export {$lang->SRCommon}"; +$lang->story->reportAction = "Report"; + +$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; +$lang->story->closedStory = 'Story %s is closed and will not be closed.'; +$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; +$lang->story->successToTask = "Converted to task."; +$lang->story->storyRound = '%s time estimation'; + +$lang->story->id = 'ID'; +$lang->story->parent = 'Parent'; +$lang->story->product = $lang->productCommon; +$lang->story->project = 'Project'; +$lang->story->branch = "Branch/Platform"; +$lang->story->module = 'Module'; +$lang->story->moduleAB = 'Module'; +$lang->story->source = 'From'; +$lang->story->sourceNote = 'Note'; +$lang->story->fromBug = 'From Bug'; +$lang->story->title = 'Title'; +$lang->story->type = "Story/Requirement"; +$lang->story->category = 'Category'; +$lang->story->color = 'Color'; +$lang->story->toBug = 'ToBug'; +$lang->story->spec = 'Description'; +$lang->story->assign = 'Assign'; +$lang->story->verify = 'Acceptance'; +$lang->story->pri = 'Priority'; +$lang->story->estimate = "Estimates"; +$lang->story->estimateAB = 'Est.'; +$lang->story->hour = $lang->hourCommon; +$lang->story->status = 'Status'; +$lang->story->subStatus = 'Sub Status'; +$lang->story->stage = 'Phase'; +$lang->story->stageAB = 'Phase'; +$lang->story->stagedBy = 'SetBy'; +$lang->story->mailto = 'Mailto'; +$lang->story->openedBy = 'Created By'; +$lang->story->openedDate = 'Created Date'; +$lang->story->assignedTo = 'AssignTo'; +$lang->story->assignedDate = 'AssignedDate'; +$lang->story->lastEditedBy = 'EditedBy'; +$lang->story->lastEditedDate = 'EditedDate'; +$lang->story->closedBy = 'ClosedBy'; +$lang->story->closedDate = 'ClosedDate'; +$lang->story->closedReason = 'Reason'; +$lang->story->rejectedReason = 'Reject Reason'; +$lang->story->reviewedBy = 'ReviewedBy'; +$lang->story->reviewer = $lang->story->reviewedBy; +$lang->story->reviewers = 'Reviewers'; +$lang->story->reviewedDate = 'ReviewedDate'; +$lang->story->activatedDate = 'Activated Date'; +$lang->story->version = 'Version'; +$lang->story->feedbackBy = 'From Name'; +$lang->story->notifyEmail = 'From Email'; +$lang->story->plan = 'Linked Plan'; +$lang->story->planAB = 'Plan'; +$lang->story->comment = 'Comment'; +$lang->story->children = "Child {$lang->SRCommon}"; +$lang->story->childrenAB = "C"; +$lang->story->linkStories = 'Linked Story'; +$lang->story->childStories = 'Decomposed Story'; +$lang->story->duplicateStory = 'Duplicated Story ID'; +$lang->story->reviewResult = 'Review Result'; +$lang->story->preVersion = 'Last Version'; +$lang->story->keywords = 'Tags'; +$lang->story->newStory = 'Continue adding'; +$lang->story->colorTag = 'Color'; +$lang->story->files = 'Files'; +$lang->story->copy = "Copy Story"; +$lang->story->total = "Total Stories"; +$lang->story->draft = 'Draft'; +$lang->story->unclosed = 'Unclosed'; +$lang->story->deleted = 'Deleted'; +$lang->story->released = 'Released Stories'; +$lang->story->URChanged = 'Requirement Changed'; +$lang->story->design = 'Design'; +$lang->story->case = 'Cases'; +$lang->story->bug = 'Bugs'; +$lang->story->repoCommit = 'Commits'; +$lang->story->noRequirement = 'No Requirements'; +$lang->story->one = 'One'; +$lang->story->field = 'Sync Field'; +$lang->story->completeRate = 'Completion Rate'; +$lang->story->reviewed = 'Reviewed'; +$lang->story->toBeReviewed = 'To Be Reviewed'; +$lang->story->linkMR = 'Related MRs'; + +$lang->story->ditto = 'Ditto'; +$lang->story->dittoNotice = 'This story is not linked to the same product as the last one is!'; + +$lang->story->needNotReviewList[0] = 'Need Review'; +$lang->story->needNotReviewList[1] = 'No Review'; + +$lang->story->useList[0] = 'Yes'; +$lang->story->useList[1] = 'No'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = 'Draft'; +$lang->story->statusList['active'] = 'Active'; +$lang->story->statusList['closed'] = 'Closed'; +$lang->story->statusList['changed'] = 'Changed'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = 'Waiting'; +$lang->story->stageList['planned'] = 'Planned'; +$lang->story->stageList['projected'] = 'Projected'; +$lang->story->stageList['developing'] = 'Developing'; +$lang->story->stageList['developed'] = 'Developed'; +$lang->story->stageList['testing'] = 'Testing'; +$lang->story->stageList['tested'] = 'Tested'; +$lang->story->stageList['verified'] = 'Accepted'; +$lang->story->stageList['released'] = 'Released'; +$lang->story->stageList['closed'] = 'Closed'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = 'Already Done'; +$lang->story->reasonList['subdivided'] = 'Decomposed'; +$lang->story->reasonList['duplicate'] = 'Duplicate'; +$lang->story->reasonList['postponed'] = 'Postponed'; +$lang->story->reasonList['willnotdo'] = "Won't Do"; +$lang->story->reasonList['cancel'] = 'Cancelled'; +$lang->story->reasonList['bydesign'] = 'As Designed'; +//$lang->story->reasonList['isbug'] = 'Bug!'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = 'Pass'; +$lang->story->reviewResultList['revert'] = 'Revert'; +$lang->story->reviewResultList['clarify'] = 'To Be Clarified'; +$lang->story->reviewResultList['reject'] = 'Reject'; + +$lang->story->reviewList[0] = 'No'; +$lang->story->reviewList[1] = 'Yes'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = 'Customer'; +$lang->story->sourceList['user'] = 'User'; +$lang->story->sourceList['po'] = $lang->productCommon . ' Owner'; +$lang->story->sourceList['market'] = 'Marketing'; +$lang->story->sourceList['service'] = 'Customer Service'; +$lang->story->sourceList['operation'] = 'Operations'; +$lang->story->sourceList['support'] = 'Technical Support'; +$lang->story->sourceList['competitor'] = 'Competitor'; +$lang->story->sourceList['partner'] = 'Partner'; +$lang->story->sourceList['dev'] = 'Dev Team'; +$lang->story->sourceList['tester'] = 'QA Team'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['forum'] = 'Forum'; +$lang->story->sourceList['other'] = 'Others'; + +$lang->story->priList[] = ''; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[3] = '3'; +$lang->story->priList[4] = '4'; + +$lang->story->changeList = array(); +$lang->story->changeList['no'] = 'Cancel'; +$lang->story->changeList['yes'] = 'Confirm'; + +$lang->story->legendBasicInfo = 'Basic Info'; +$lang->story->legendLifeTime = 'Story Life '; +$lang->story->legendRelated = 'Related Info'; +$lang->story->legendMailto = 'Mailto'; +$lang->story->legendAttatch = 'Files'; +$lang->story->legendProjectAndTask = $lang->executionCommon . ' And Task'; +$lang->story->legendBugs = 'Linked Bugs'; +$lang->story->legendFromBug = 'From Bug'; +$lang->story->legendCases = 'Linked Cases'; +$lang->story->legendLinkStories = 'Linked Stories'; +$lang->story->legendChildStories = 'Child Stories'; +$lang->story->legendSpec = 'Description'; +$lang->story->legendVerify = 'Acceptance'; +$lang->story->legendMisc = 'Misc.'; +$lang->story->legendInformation = 'Story Information'; + +$lang->story->lblChange = 'Change'; +$lang->story->lblReview = 'Review'; +$lang->story->lblActivate = 'Activate'; +$lang->story->lblClose = 'Close'; +$lang->story->lblTBC = 'Task/Bug/Case'; + +$lang->story->checkAffection = 'Influence'; +$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; +$lang->story->affectedBugs = 'Bugs'; +$lang->story->affectedCases = 'Cases'; + +$lang->story->specTemplate = "As a < type of user >, I want < some goal > so that < some reason >."; +$lang->story->needNotReview = 'No Review'; +$lang->story->successSaved = "Story is saved!"; +$lang->story->confirmDelete = "Do you want to delete this story?"; +$lang->story->confirmRecall = "Do you want to recall this story?"; +$lang->story->errorEmptyChildStory = '『Decomposed Stories』canot be blank.'; +$lang->story->errorNotSubdivide = "If the status is not active, or the stage is not wait, or a sub story, it cannot be subdivided."; +$lang->story->errorEmptyReviewedBy = "『ReviewedBy』canot be blank."; +$lang->story->mustChooseResult = 'Select Result'; +$lang->story->mustChoosePreVersion = 'Select a version to revert to.'; +$lang->story->noStory = 'No stories yet. '; +$lang->story->noRequirement = 'No requirements yet. '; +$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->cannotDeleteParent = "Can not delete parent {$lang->SRCommon}"; +$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; +$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; +$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; +$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; +$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; +$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; +$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; + +$lang->story->form = new stdclass(); +$lang->story->form->area = 'Scope'; +$lang->story->form->desc = 'What story is it? What is the acceptance?'; +$lang->story->form->resource = 'Who will allocate resources? How long does it take?'; +$lang->story->form->file = 'If any file that is linked to a story, please click Here to upload it.'; + +$lang->story->action = new stdclass(); +$lang->story->action->reviewed = array('main' => '$date, recorded by $actor. The result is $extra.', 'extra' => 'reviewResultList'); +$lang->story->action->rejectreviewed = array('main' => '$date, recorded by $actor. The result is $extra. The reasion is $reason.', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); +$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); +$lang->story->action->closed = array('main' => '$date, closed by $actor. The reasion is $extra $appendLink.', 'extra' => 'reasonList'); +$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); +$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); +$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); +$lang->story->action->linked2plan = array('main' => '$date, linked by $actor to Plan $extra'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, unlinked by $actor from Plan $extra.'); +$lang->story->action->linked2execution = array('main' => '$date, linked by $actor to ' . $lang->executionCommon . ' $extra.'); +$lang->story->action->unlinkedfromexecution = array('main' => '$date, unlinked by $actor from ' . $lang->executionCommon . ' $extra.'); +$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); +$lang->story->action->linked2project = array('main' => '$date, linked by $actor to project $extra.'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, unlinked by $actor from project $extra.'); +$lang->story->action->linked2build = array('main' => '$date, linked by $actor to Build $extra'); +$lang->story->action->unlinkedfrombuild = array('main' => '$date, unlinked by $actor from Build $extra.'); +$lang->story->action->linked2release = array('main' => '$date, linked by $actor to Release $extra'); +$lang->story->action->unlinkedfromrelease = array('main' => '$date, unlinked by $actor from Release $extra.'); +$lang->story->action->linkrelatedstory = array('main' => '$date, linked by $actor to Story $extra.'); +$lang->story->action->subdividestory = array('main' => '$date, decomposed by $actor to Story $extra.'); +$lang->story->action->unlinkrelatedstory = array('main' => '$date, unlinked by $actor from Story $extra.'); +$lang->story->action->unlinkchildstory = array('main' => '$date, unlinked by $actor Decomposed Story $extra.'); + +/* Statistical statement. */ +$lang->story->report = new stdclass(); +$lang->story->report->common = 'Report'; +$lang->story->report->select = 'Select Report Type'; +$lang->story->report->create = 'Create Report'; +$lang->story->report->value = 'Reports'; + +$lang->story->report->charts['storysPerProduct'] = 'Group by ' . $lang->productCommon . ' Story'; +$lang->story->report->charts['storysPerModule'] = 'Group by Module Story'; +$lang->story->report->charts['storysPerSource'] = 'Group by Story Source'; +$lang->story->report->charts['storysPerPlan'] = 'Group by Plan'; +$lang->story->report->charts['storysPerStatus'] = 'Group by Status'; +$lang->story->report->charts['storysPerStage'] = 'Group by Phase'; +$lang->story->report->charts['storysPerPri'] = 'Group by Priority'; +$lang->story->report->charts['storysPerEstimate'] = 'Group by Estimates'; +$lang->story->report->charts['storysPerOpenedBy'] = 'Group by CreatedBy'; +$lang->story->report->charts['storysPerAssignedTo'] = 'Group by AssignedTo'; +$lang->story->report->charts['storysPerClosedReason'] = 'Group by Closed Reason'; +$lang->story->report->charts['storysPerChange'] = 'Group by Changed Story'; + +$lang->story->report->options = new stdclass(); +$lang->story->report->options->graph = new stdclass(); +$lang->story->report->options->type = 'pie'; +$lang->story->report->options->width = 500; +$lang->story->report->options->height = 140; + +$lang->story->report->storysPerProduct = new stdclass(); +$lang->story->report->storysPerModule = new stdclass(); +$lang->story->report->storysPerSource = new stdclass(); +$lang->story->report->storysPerPlan = new stdclass(); +$lang->story->report->storysPerStatus = new stdclass(); +$lang->story->report->storysPerStage = new stdclass(); +$lang->story->report->storysPerPri = new stdclass(); +$lang->story->report->storysPerOpenedBy = new stdclass(); +$lang->story->report->storysPerAssignedTo = new stdclass(); +$lang->story->report->storysPerClosedReason = new stdclass(); +$lang->story->report->storysPerEstimate = new stdclass(); +$lang->story->report->storysPerChange = new stdclass(); + +$lang->story->report->storysPerProduct->item = $lang->productCommon; +$lang->story->report->storysPerModule->item = 'Module'; +$lang->story->report->storysPerSource->item = 'Source'; +$lang->story->report->storysPerPlan->item = 'Plan'; +$lang->story->report->storysPerStatus->item = 'Status'; +$lang->story->report->storysPerStage->item = 'Phase'; +$lang->story->report->storysPerPri->item = 'Priority'; +$lang->story->report->storysPerOpenedBy->item = 'OpenedBy'; +$lang->story->report->storysPerAssignedTo->item = 'AssignedTo'; +$lang->story->report->storysPerClosedReason->item = 'Reason'; +$lang->story->report->storysPerEstimate->item = 'Estimates'; +$lang->story->report->storysPerChange->item = 'Changed Story'; + +$lang->story->report->storysPerProduct->graph = new stdclass(); +$lang->story->report->storysPerModule->graph = new stdclass(); +$lang->story->report->storysPerSource->graph = new stdclass(); +$lang->story->report->storysPerPlan->graph = new stdclass(); +$lang->story->report->storysPerStatus->graph = new stdclass(); +$lang->story->report->storysPerStage->graph = new stdclass(); +$lang->story->report->storysPerPri->graph = new stdclass(); +$lang->story->report->storysPerOpenedBy->graph = new stdclass(); +$lang->story->report->storysPerAssignedTo->graph = new stdclass(); +$lang->story->report->storysPerClosedReason->graph = new stdclass(); +$lang->story->report->storysPerEstimate->graph = new stdclass(); +$lang->story->report->storysPerChange->graph = new stdclass(); + +$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; +$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; +$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; +$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; +$lang->story->report->storysPerStatus->graph->xAxisName = 'Status'; +$lang->story->report->storysPerStage->graph->xAxisName = 'Phase'; +$lang->story->report->storysPerPri->graph->xAxisName = 'Priority'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'CreatedBy'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'AssignedTo'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Close Reason'; +$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimates '; +$lang->story->report->storysPerChange->graph->xAxisName = 'Change Times'; + +$lang->story->placeholder = new stdclass(); +$lang->story->placeholder->estimate = $lang->story->hour; + +$lang->story->chosen = new stdClass(); +$lang->story->chosen->reviewedBy = 'Choose ReviewedBy'; + +$lang->story->notice = new stdClass(); +$lang->story->notice->closed = 'Story that you select is closed!'; +$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; + +$lang->story->convertToTask = new stdClass(); +$lang->story->convertToTask->fieldList = array(); +$lang->story->convertToTask->fieldList['module'] = 'Module'; +$lang->story->convertToTask->fieldList['spec'] = "Description"; +$lang->story->convertToTask->fieldList['pri'] = 'Priority'; +$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; +$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; + +$lang->story->categoryList['feature'] = 'Feature'; +$lang->story->categoryList['interface'] = 'Interface'; +$lang->story->categoryList['performance'] = 'Performance'; +$lang->story->categoryList['safe'] = 'Safe'; +$lang->story->categoryList['experience'] = 'Experience'; +$lang->story->categoryList['improve'] = 'Improve'; +$lang->story->categoryList['other'] = 'Other'; + +$lang->story->changeTip = 'This Story is under review and cannot be changed.'; + +$lang->story->reviewTip = array(); +$lang->story->reviewTip['active'] = 'The Story is already active,no review requirements.'; +$lang->story->reviewTip['notReviewer'] = 'You are not the reviewer of this Story and cannot perform review operations.'; +$lang->story->reviewTip['reviewed'] = 'Reviewed'; +$lang->story->reviewTip['recalled'] = 'This Story has been withdrawn from review and cannot be reviewed.'; + +$lang->story->recallTip = array(); +$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.'; +$lang->story->recallTip['recalled'] = 'This Story has been withdrawn from review and cannot be cancelled.'; +$lang->story->recallTip['reviewed'] = 'The review process has been started and cannot be cancelled.'; +$lang->story->recallTip['notOpenedBy'] = 'You are not the Story creator and cannot cancel the operation.'; + +$lang->story->subDivideTip = array(); +$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.'; +$lang->story->subDivideTip['planned'] = 'The Story has been planned and cannot be subdivided.'; +$lang->story->subDivideTip['projected'] = 'The Story has been initiated and cannot be subdivided.'; +$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.'; diff --git a/module/story:q/lang/fr.php b/module/story:q/lang/fr.php new file mode 100644 index 0000000000..47ef083382 --- /dev/null +++ b/module/story:q/lang/fr.php @@ -0,0 +1,441 @@ + + * @package story + * @version $Id: en.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +global $config; +$lang->story->create = "Créer Story"; + +$lang->story->requirement = zget($lang, 'URCommon', "Requirement"); +$lang->story->story = zget($lang, 'SRCommon', "Story"); +$lang->story->createStory = 'Create ' . $lang->story->story; +$lang->story->createRequirement = 'Create ' . $lang->story->requirement; +$lang->story->affectedStories = "Affected {$lang->story->story}"; + +$lang->story->batchCreate = "Créer par Lot"; +$lang->story->change = "Changer"; +$lang->story->changed = 'Changée'; +$lang->story->assignTo = 'Affecter'; +$lang->story->review = 'Valider'; +$lang->story->recall = 'Revoke'; +$lang->story->needReview = 'Validation requise'; +$lang->story->batchReview = 'Validation par lot'; +$lang->story->edit = "Editer Story"; +$lang->story->batchEdit = "Editer par Lot"; +$lang->story->subdivide = 'Décomposer'; +$lang->story->link = 'Link'; +$lang->story->unlink = 'Unlink'; +$lang->story->track = 'Track'; +$lang->story->trackAB = 'Track'; +$lang->story->processStoryChange = 'Confirm Story Change'; +$lang->story->splitRequirent = 'Décompose'; +$lang->story->close = 'Fermer'; +$lang->story->batchClose = 'Fermer par Lot'; +$lang->story->activate = 'Activer'; +$lang->story->delete = "Supprimer"; +$lang->story->view = "Détail Story"; +$lang->story->setting = "Paramétrage"; +$lang->story->tasks = "Tâches Associées"; +$lang->story->bugs = "Bugs Associés"; +$lang->story->cases = "CasTest Associés"; +$lang->story->taskCount = 'Tâches'; +$lang->story->bugCount = 'Bugs'; +$lang->story->caseCount = 'CasTests'; +$lang->story->taskCountAB = 'T'; +$lang->story->bugCountAB = 'B'; +$lang->story->caseCountAB = 'C'; +$lang->story->linkStory = 'Lier Story'; +$lang->story->unlinkStory = 'Dissocier'; +$lang->story->export = "Exporter Données"; +$lang->story->zeroCase = "Stories sans CasTests"; +$lang->story->zeroTask = "Seulement liste des stories sans tâches"; +$lang->story->reportChart = "Rapport"; +$lang->story->copyTitle = "Copier Titre"; +$lang->story->batchChangePlan = "Changer Plans par lot"; +$lang->story->batchChangeBranch = "Changer Branches par lot"; +$lang->story->batchChangeStage = "Changer Phases par lot"; +$lang->story->batchAssignTo = "Affecter par lot"; +$lang->story->batchChangeModule = "Changer Modules par lot"; +$lang->story->viewAll = "Voir Tout"; +$lang->story->toTask = 'Convert to Task'; +$lang->story->batchToTask = 'Batch Convert to Task'; +$lang->story->convertRelations = 'Convert Relations'; +$lang->story->undetermined = 'undetermined'; +$lang->story->order = 'Order'; + +$lang->story->editAction = "Edit {$lang->SRCommon}"; +$lang->story->changeAction = "Changer Story"; +$lang->story->assignAction = 'Affecter Story'; +$lang->story->reviewAction = 'Valider Story'; +$lang->story->subdivideAction = 'Décomposer Story'; +$lang->story->closeAction = 'Fermer Story'; +$lang->story->activateAction = 'Activer Story'; +$lang->story->deleteAction = "Supprimer Story"; +$lang->story->exportAction = "Exporter Story"; +$lang->story->reportAction = "Rapport de Story"; + +$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; +$lang->story->closedStory = 'Story %s is closed and will not be closed.'; +$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; +$lang->story->successToTask = "Converted to task."; +$lang->story->storyRound = '%s time estimation'; + +$lang->story->id = 'ID'; +$lang->story->parent = 'Parent'; +$lang->story->product = $lang->productCommon; +$lang->story->project = 'Project'; +$lang->story->branch = "Branche/Plateforme"; +$lang->story->module = 'Module'; +$lang->story->moduleAB = 'Module'; +$lang->story->source = 'De'; +$lang->story->sourceNote = 'Note'; +$lang->story->fromBug = 'Depuis Bug'; +$lang->story->title = 'Titre'; +$lang->story->type = "Story/Requirement"; +$lang->story->category = 'Category'; +$lang->story->color = 'Couleur'; +$lang->story->toBug = 'Vers Bug'; +$lang->story->spec = 'Description'; +$lang->story->assign = 'Affecter'; +$lang->story->verify = 'Acceptance'; +$lang->story->pri = 'Priorité'; +$lang->story->estimate = 'Estimation'; +$lang->story->estimateAB = 'Esti.(h)'; +$lang->story->hour = 'Heures'; +$lang->story->status = 'Statut'; +$lang->story->subStatus = 'Sous-statut'; +$lang->story->stage = 'Phase'; +$lang->story->stageAB = 'Phase'; +$lang->story->stagedBy = 'Fixé par'; +$lang->story->mailto = 'Mailto'; +$lang->story->openedBy = 'Créée par'; +$lang->story->openedDate = 'Date Création'; +$lang->story->assignedTo = 'Assignée à'; +$lang->story->assignedDate = 'Date Assignation'; +$lang->story->lastEditedBy = 'Editée par'; +$lang->story->lastEditedDate = 'Date Edition'; +$lang->story->closedBy = 'Fermée par'; +$lang->story->closedDate = 'Date Fermeture'; +$lang->story->closedReason = 'Raison'; +$lang->story->rejectedReason = 'Raison du Rejet'; +$lang->story->reviewedBy = 'Validée par'; +$lang->story->reviewer = $lang->story->reviewedBy; +$lang->story->reviewers = 'Reviewers'; +$lang->story->reviewedDate = 'Date Validation'; +$lang->story->activatedDate = 'Activated Date'; +$lang->story->version = 'Version'; +$lang->story->feedbackBy = 'From Name'; +$lang->story->notifyEmail = 'From Email'; +$lang->story->plan = 'Intégrée Plans'; +$lang->story->planAB = 'Plan'; +$lang->story->comment = 'Commentaire'; +$lang->story->children = "Enfant {$lang->SRCommon}"; +$lang->story->childrenAB = "C"; +$lang->story->linkStories = 'Stories Liées'; +$lang->story->childStories = 'Stories Décomposées'; +$lang->story->duplicateStory = 'Story ID Dupliquées'; +$lang->story->reviewResult = 'Résultat Validation'; +$lang->story->preVersion = 'Dernière Version'; +$lang->story->keywords = 'Tags'; +$lang->story->newStory = "Continuer d'ajouter"; +$lang->story->colorTag = 'Couleur'; +$lang->story->files = 'Fichiers'; +$lang->story->copy = "Copier Story"; +$lang->story->total = "Stories Total"; +$lang->story->draft = 'Brouillon'; +$lang->story->unclosed = 'Non Fermées'; +$lang->story->deleted = 'Supprimée'; +$lang->story->released = 'Stories Versionnées'; +$lang->story->URChanged = 'Requirement Changed'; +$lang->story->design = 'Designs'; +$lang->story->case = 'Cases'; +$lang->story->bug = 'Bugs'; +$lang->story->repoCommit = 'Commits'; +$lang->story->noRequirement = 'No Requirements'; +$lang->story->one = 'One'; +$lang->story->field = 'Synchronized fields'; +$lang->story->completeRate = 'Completion Rate'; +$lang->story->reviewed = 'Reviewed'; +$lang->story->toBeReviewed = 'To Be Reviewed'; +$lang->story->linkMR = 'Related MRs'; + +$lang->story->ditto = 'Idem'; +$lang->story->dittoNotice = "La story n'est pas associée au même product que la précédente !"; + +$lang->story->needNotReviewList[0] = 'Need Review'; +$lang->story->needNotReviewList[1] = 'Need Not Review'; + +$lang->story->useList[0] = 'Oui'; +$lang->story->useList[1] = 'Non'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = 'Brouillon'; +$lang->story->statusList['active'] = 'Active'; +$lang->story->statusList['closed'] = 'Fermée'; +$lang->story->statusList['changed'] = 'Changée'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = 'En Attente'; +$lang->story->stageList['planned'] = 'Plannifée'; +$lang->story->stageList['projected'] = 'Projetée'; +$lang->story->stageList['developing'] = 'En Dév.'; +$lang->story->stageList['developed'] = 'Développée'; +$lang->story->stageList['testing'] = 'En Test'; +$lang->story->stageList['tested'] = 'Testée'; +$lang->story->stageList['verified'] = 'Acceptée'; +$lang->story->stageList['released'] = 'Versionnée'; +$lang->story->stageList['closed'] = 'Fermée'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = 'Déjà Fait'; +$lang->story->reasonList['subdivided'] = 'Décomposée'; +$lang->story->reasonList['duplicate'] = 'Doublon'; +$lang->story->reasonList['postponed'] = 'Reportée'; +$lang->story->reasonList['willnotdo'] = "On ne fera pas"; +$lang->story->reasonList['cancel'] = 'Annulée'; +$lang->story->reasonList['bydesign'] = 'Du Design'; +//$lang->story->reasonList['isbug'] = 'Bug!'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = 'Accepté'; +$lang->story->reviewResultList['revert'] = 'Retour Arrière'; +$lang->story->reviewResultList['clarify'] = 'A Clarifier'; +$lang->story->reviewResultList['reject'] = 'Refusé'; + +$lang->story->reviewList[0] = 'Non'; +$lang->story->reviewList[1] = 'Oui'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = 'Client'; +$lang->story->sourceList['user'] = 'Utilisateur'; +$lang->story->sourceList['po'] = $lang->productCommon . ' Owner'; +$lang->story->sourceList['market'] = 'Marketing'; +$lang->story->sourceList['service'] = 'Service Client'; +$lang->story->sourceList['operation'] = 'Opérations'; +$lang->story->sourceList['support'] = 'Support'; +$lang->story->sourceList['competitor'] = 'Concurrence'; +$lang->story->sourceList['partner'] = 'Partenaire'; +$lang->story->sourceList['dev'] = 'Equipe de Dev'; +$lang->story->sourceList['tester'] = 'Equipe de Test'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['forum'] = 'Forum'; +$lang->story->sourceList['other'] = 'Autre'; + +$lang->story->priList[] = ''; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[3] = '3'; +$lang->story->priList[4] = '4'; + +$lang->story->changeList = array(); +$lang->story->changeList['no'] = 'Cancel'; +$lang->story->changeList['yes'] = 'Confirm'; + +$lang->story->legendBasicInfo = 'Infos de Base'; +$lang->story->legendLifeTime = 'Vie de la Story '; +$lang->story->legendRelated = 'Info Connexes'; +$lang->story->legendMailto = 'Mailto'; +$lang->story->legendAttatch = 'Fichiers'; +$lang->story->legendProjectAndTask = $lang->executionCommon . ' et Tâches'; +$lang->story->legendBugs = 'Bugs Liés'; +$lang->story->legendFromBug = 'du Bug'; +$lang->story->legendCases = 'CasTests Liés'; +$lang->story->legendLinkStories = 'Stories Liées'; +$lang->story->legendChildStories = 'Sous-Stories'; +$lang->story->legendSpec = 'Description'; +$lang->story->legendVerify = 'Acceptance'; +$lang->story->legendMisc = 'Divers'; +$lang->story->legendInformation = 'Story Information'; + +$lang->story->lblChange = 'Changer'; +$lang->story->lblReview = 'Valider'; +$lang->story->lblActivate = 'Activer'; +$lang->story->lblClose = 'Fermer'; +$lang->story->lblTBC = 'Tâche/Bug/CasTest'; + +$lang->story->checkAffection = 'Influence'; +$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; +$lang->story->affectedBugs = 'Bugs'; +$lang->story->affectedCases = 'CasTests'; + +$lang->story->specTemplate = "En tant que < type utilisateur >, je veux < différents objectifs > pour < différentes raisons >."; +$lang->story->needNotReview = 'Aucune Validation Requise'; +$lang->story->successSaved = "Story est sauvegardée !"; +$lang->story->confirmDelete = "Voulez-vous vraiment supprimer cette story ?"; +$lang->story->confirmRecall = "Do you want to recall this story?"; +$lang->story->errorEmptyChildStory = '『Decomposed Stories』ne peuvent être vides.'; +$lang->story->errorNotSubdivide = "Si le statut n'est pas actif, ou l'étape n'est pas en attente, ou si c'est une sous-story, elle ne peut pas être subdivisée."; +$lang->story->errorEmptyReviewedBy = "『ReviewedBy』canot be blank."; +$lang->story->mustChooseResult = 'Sélect Résultat'; +$lang->story->mustChoosePreVersion = 'Sélect une version pour revenir en arrière.'; +$lang->story->noStory = "Aucune story pour l'instant. "; +$lang->story->noRequirement = 'No Requirements'; +$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->cannotDeleteParent = "Impossible de supprimer {$lang->SRCommon} parent"; +$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; +$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; +$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; +$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; +$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; +$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; +$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; + +$lang->story->form = new stdclass(); +$lang->story->form->area = 'Périmètre'; +$lang->story->form->desc = "Quelle story est-ce ? Quel est son état d'acceptance ?"; +$lang->story->form->resource = 'Qui va allouer des resources ? Combien de temps cela va-t-il prendre ?'; +$lang->story->form->file = 'Si certains fichiers sont associés à la story, cliquez ici pour les uploader.'; + +$lang->story->action = new stdclass(); +$lang->story->action->reviewed = array('main' => '$date, validée par $actor. Le résultat est $extra.', 'extra' => 'reviewResultList'); +$lang->story->action->rejectreviewed = array('main' => '$date, validée par $actor. Le résultat est $extra. La raison est $reason.', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); +$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); +$lang->story->action->closed = array('main' => '$date, Fermée par $actor. La raison est $extra $appendLink.', 'extra' => 'reasonList'); +$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); +$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); +$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); +$lang->story->action->linked2plan = array('main' => '$date, planifiée par $actor au Plan $extra'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, déplanifiée par $actor du Plan $extra.'); +$lang->story->action->linked2execution = array('main' => '$date, associée au ' . $lang->executionCommon . ' $extra par $actor.'); +$lang->story->action->unlinkedfromexecution = array('main' => '$date, dissociée du ' . $lang->executionCommon . ' $extra par $actor.'); +$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); +$lang->story->action->linked2project = array('main' => '$date, associée au project $extra par $actor.'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, dissociée d uproject $extra par $actor.'); +$lang->story->action->linked2build = array('main' => '$date, intégrée par $actor au Build $extra'); +$lang->story->action->unlinkedfrombuild = array('main' => '$date, retirée par $actor du Build $extra.'); +$lang->story->action->linked2release = array('main' => '$date, ajoutée par $actor à la Release $extra'); +$lang->story->action->unlinkedfromrelease = array('main' => '$date, retirée par $actor de la Release $extra.'); +$lang->story->action->linkrelatedstory = array('main' => '$date, associée par $actor à la Story $extra.'); +$lang->story->action->subdividestory = array('main' => '$date, décomposée par $actor en Story $extra.'); +$lang->story->action->unlinkrelatedstory = array('main' => '$date, dissociée par $actor de la Story $extra.'); +$lang->story->action->unlinkchildstory = array('main' => '$date, dissociée par $actor de la sous-Story $extra.'); + +/* Statistical statement. */ +$lang->story->report = new stdclass(); +$lang->story->report->common = 'Rapport'; +$lang->story->report->select = 'Choix Type de Rapport'; +$lang->story->report->create = 'Créer Rapport'; +$lang->story->report->value = 'Rapports'; + +$lang->story->report->charts['storysPerProduct'] = 'Regroupé par ' . $lang->productCommon . ' Story'; +$lang->story->report->charts['storysPerModule'] = 'Regroupé par Module Story'; +$lang->story->report->charts['storysPerSource'] = 'Regroupé par Source'; +$lang->story->report->charts['storysPerPlan'] = 'Regroupé par Plan'; +$lang->story->report->charts['storysPerStatus'] = 'Regroupé par Statut'; +$lang->story->report->charts['storysPerStage'] = 'Regroupé par Phase'; +$lang->story->report->charts['storysPerPri'] = 'Regroupé par Priorité'; +$lang->story->report->charts['storysPerEstimate'] = 'Regroupé par Estimés'; +$lang->story->report->charts['storysPerOpenedBy'] = 'Regroupé par Créateur'; +$lang->story->report->charts['storysPerAssignedTo'] = 'Regroupé par Détenteur'; +$lang->story->report->charts['storysPerClosedReason'] = 'Par Raison de Fermeture'; +$lang->story->report->charts['storysPerChange'] = 'Regroupé par Modif de Story'; + +$lang->story->report->options = new stdclass(); +$lang->story->report->options->graph = new stdclass(); +$lang->story->report->options->type = 'pie'; +$lang->story->report->options->width = 500; +$lang->story->report->options->height = 140; + +$lang->story->report->storysPerProduct = new stdclass(); +$lang->story->report->storysPerModule = new stdclass(); +$lang->story->report->storysPerSource = new stdclass(); +$lang->story->report->storysPerPlan = new stdclass(); +$lang->story->report->storysPerStatus = new stdclass(); +$lang->story->report->storysPerStage = new stdclass(); +$lang->story->report->storysPerPri = new stdclass(); +$lang->story->report->storysPerOpenedBy = new stdclass(); +$lang->story->report->storysPerAssignedTo = new stdclass(); +$lang->story->report->storysPerClosedReason = new stdclass(); +$lang->story->report->storysPerEstimate = new stdclass(); +$lang->story->report->storysPerChange = new stdclass(); + +$lang->story->report->storysPerProduct->item = $lang->productCommon; +$lang->story->report->storysPerModule->item = 'Module'; +$lang->story->report->storysPerSource->item = 'Source'; +$lang->story->report->storysPerPlan->item = 'Plan'; +$lang->story->report->storysPerStatus->item = 'Statut'; +$lang->story->report->storysPerStage->item = 'Phase'; +$lang->story->report->storysPerPri->item = 'Priorité'; +$lang->story->report->storysPerOpenedBy->item = 'Créateur'; +$lang->story->report->storysPerAssignedTo->item = 'Détenteur'; +$lang->story->report->storysPerClosedReason->item = 'Raison'; +$lang->story->report->storysPerEstimate->item = 'Estimation'; +$lang->story->report->storysPerChange->item = 'Chang. Story'; + +$lang->story->report->storysPerProduct->graph = new stdclass(); +$lang->story->report->storysPerModule->graph = new stdclass(); +$lang->story->report->storysPerSource->graph = new stdclass(); +$lang->story->report->storysPerPlan->graph = new stdclass(); +$lang->story->report->storysPerStatus->graph = new stdclass(); +$lang->story->report->storysPerStage->graph = new stdclass(); +$lang->story->report->storysPerPri->graph = new stdclass(); +$lang->story->report->storysPerOpenedBy->graph = new stdclass(); +$lang->story->report->storysPerAssignedTo->graph = new stdclass(); +$lang->story->report->storysPerClosedReason->graph = new stdclass(); +$lang->story->report->storysPerEstimate->graph = new stdclass(); +$lang->story->report->storysPerChange->graph = new stdclass(); + +$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; +$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; +$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; +$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; +$lang->story->report->storysPerStatus->graph->xAxisName = 'Statut'; +$lang->story->report->storysPerStage->graph->xAxisName = 'Phase'; +$lang->story->report->storysPerPri->graph->xAxisName = 'Priorité'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Créateur'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Détenteur'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Raison Fermeture'; +$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimattion '; +$lang->story->report->storysPerChange->graph->xAxisName = 'Change Times'; + +$lang->story->placeholder = new stdclass(); +$lang->story->placeholder->estimate = $lang->story->hour; + +$lang->story->chosen = new stdClass(); +$lang->story->chosen->reviewedBy = 'Choisir valideur'; + +$lang->story->notice = new stdClass(); +$lang->story->notice->closed = 'La Story que vous avez sélectionnée est malheureusement fermée !'; +$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; + +$lang->story->convertToTask = new stdClass(); +$lang->story->convertToTask->fieldList = array(); +$lang->story->convertToTask->fieldList['module'] = 'Module'; +$lang->story->convertToTask->fieldList['spec'] = "Description"; +$lang->story->convertToTask->fieldList['pri'] = 'Priority'; +$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; +$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; + +$lang->story->categoryList['feature'] = 'Feature'; +$lang->story->categoryList['interface'] = 'Interface'; +$lang->story->categoryList['performance'] = 'Performance'; +$lang->story->categoryList['safe'] = 'Safe'; +$lang->story->categoryList['experience'] = 'Experience'; +$lang->story->categoryList['improve'] = 'Improve'; +$lang->story->categoryList['other'] = 'Other'; + +$lang->story->changeTip = 'Cette Story est en cours de vérification et ne peut pas être modifiée.'; + +$lang->story->reviewTip = array(); +$lang->story->reviewTip['active'] = "Cette Story est déjà active, il n'y a pas de demande de révision."; +$lang->story->reviewTip['notReviewer'] = "Vous n'êtes pas l'évaluateur de cette Story et ne pouvez pas effectuer d'opérations d'évaluation."; +$lang->story->reviewTip['reviewed'] = "Révisé"; +$lang->story->reviewTip['recalled'] = "Cette Story a été retirée pour vérification, et l'opération de révision ne peut pas être effectuée."; + +$lang->story->recallTip = array(); +$lang->story->recallTip['actived'] = "Aucun processus de révision n'a été lancé pour cette Story, et il n'est pas nécessaire de révoquer l'opération."; +$lang->story->recallTip['recalled'] = "Cette Story a été retirée pour vérification, et l'opération ne peut pas être annulée."; +$lang->story->recallTip['reviewed'] = "Il est entré dans le processus de révision, et l'opération ne peut pas être annulée."; +$lang->story->recallTip['notOpenedBy'] = "Vous n'êtes pas l'initiateur de la révision et vous ne pouvez pas annuler l'opération."; + +$lang->story->subDivideTip = array(); +$lang->story->subDivideTip['subStory'] = 'Les sous-stories ne peuvent pas être décomposées.'; +$lang->story->subDivideTip['planned'] = 'Cette Story est planifiée et ne peut être subdivisée.'; +$lang->story->subDivideTip['projected'] = 'Cette Story a été créée et ne peut être subdivisée.'; +$lang->story->subDivideTip['notActive'] = "Cette Story n'est pas active et ne peut être subdivisée."; diff --git a/module/story:q/lang/vi.php b/module/story:q/lang/vi.php new file mode 100644 index 0000000000..fcd4ee08ac --- /dev/null +++ b/module/story:q/lang/vi.php @@ -0,0 +1,441 @@ + + * @package story + * @version $Id: vi.php 5141 2013-07-15 05:57:15Z quocnho@gmail.com $ + * @link http://www.zentao.net + */ +global $config; +$lang->story->create = "Create Story"; + +$lang->story->requirement = zget($lang, 'UrCommon', "Requirement"); +$lang->story->story = zget($lang, 'SrCommon', "Story"); +$lang->story->createStory = 'Create ' . $lang->story->story; +$lang->story->createRequirement = 'Create ' . $lang->story->requirement; +$lang->story->affectedStories = "Affected {$lang->story->story}"; + +$lang->story->batchCreate = "Tạo hàng loạt"; +$lang->story->change = "Thay đổi"; +$lang->story->changed = "Đã đổi"; +$lang->story->assignTo = 'Giao cho'; +$lang->story->review = 'Xét duyệt'; +$lang->story->recall = 'Revoke'; +$lang->story->needReview = 'Duyệt nhu cầu'; +$lang->story->batchReview = 'Duyệt hàng loạt'; +$lang->story->edit = "Sửa câu chuyện"; +$lang->story->batchEdit = "Sửa hàng loạt"; +$lang->story->subdivide = 'Phân rã'; +$lang->story->link = 'Link'; +$lang->story->unlink = 'Unlink'; +$lang->story->track = 'Track'; +$lang->story->trackAB = 'Track'; +$lang->story->processStoryChange= 'Process Story Change'; +$lang->story->splitRequirent = 'Phân rã'; +$lang->story->close = 'Đóng'; +$lang->story->batchClose = 'Đóng hàng loạt'; +$lang->story->activate = 'Kích hoạt'; +$lang->story->delete = "Xóa"; +$lang->story->view = "Chi tiết câu chuyện"; +$lang->story->setting = "Thiết lập"; +$lang->story->tasks = "Nhiệm vụ liên kết"; +$lang->story->bugs = "Bugs liên kết"; +$lang->story->cases = "Tình huống liên kết"; +$lang->story->taskCount = 'Nhiệm vụ'; +$lang->story->bugCount = 'Bugs'; +$lang->story->caseCount = 'Tình huống'; +$lang->story->taskCountAB = 'T'; +$lang->story->bugCountAB = 'B'; +$lang->story->caseCountAB = 'C'; +$lang->story->linkStory = 'Liên kết câu chuyện'; +$lang->story->unlinkStory = 'Hủy liên kết'; +$lang->story->export = "Xuất dữ liệu"; +$lang->story->zeroCase = "Câu chuyện không tình huống"; +$lang->story->zeroTask = "Chỉ liệt kê câu chuyện không nhiệm vụ"; +$lang->story->reportChart = "Báo cáo"; +$lang->story->copyTitle = "Sao chép tiêu đề"; +$lang->story->batchChangePlan = "Thay đổi kế hoạch hàng loạt"; +$lang->story->batchChangeBranch = "Thay đổi chi nhánh hàng loạt"; +$lang->story->batchChangeStage = "Thay đổi giai đoạn hàng loạt"; +$lang->story->batchAssignTo = "Bàn giao hàng loạt"; +$lang->story->batchChangeModule = "Thay đổi Module hàng loạt"; +$lang->story->viewAll = "Xem tất cả"; +$lang->story->toTask = 'Convert to Task'; +$lang->story->batchToTask = 'Batch Convert to Task'; +$lang->story->convertRelations = 'Convert Relations'; +$lang->story->undetermined = 'undetermined'; +$lang->story->order = 'Order'; + +$lang->story->editAction = "Edit {$lang->SRCommon}"; +$lang->story->changeAction = "Change {$lang->SRCommon}"; +$lang->story->assignAction = "Assign {$lang->SRCommon}"; +$lang->story->reviewAction = "Review {$lang->SRCommon}"; +$lang->story->subdivideAction = "Subdivide {$lang->SRCommon}"; +$lang->story->closeAction = "Close {$lang->SRCommon}"; +$lang->story->activateAction = "Activate {$lang->SRCommon}"; +$lang->story->deleteAction = "Delete {$lang->SRCommon}"; +$lang->story->exportAction = "Export {$lang->SRCommon}"; +$lang->story->reportAction = "Report"; + +$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; +$lang->story->closedStory = 'Story %s is closed and will not be closed.'; +$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; +$lang->story->successToTask = "Converted to task."; +$lang->story->storyRound = '%s time estimation'; + +$lang->story->id = 'ID'; +$lang->story->parent = 'Mẹ'; +$lang->story->product = $lang->productCommon; +$lang->story->project = 'Project'; +$lang->story->branch = "Branch/Platform"; +$lang->story->module = 'Module'; +$lang->story->moduleAB = 'Module'; +$lang->story->source = 'Từ'; +$lang->story->sourceNote = 'Ghi chú'; +$lang->story->fromBug = 'Từ Bug'; +$lang->story->title = 'Tiêu đề'; +$lang->story->type = "Story/Requirement"; +$lang->story->category = 'Category'; +$lang->story->color = 'Màu'; +$lang->story->toBug = 'Tới Bug'; +$lang->story->spec = 'Mô tả'; +$lang->story->assign = 'Giao cho'; +$lang->story->verify = 'Chấp thuận'; +$lang->story->pri = 'Ưu tiên'; +$lang->story->estimate = "{$lang->hourCommon} dự tính"; +$lang->story->estimateAB = 'DT.' . $lang->hourCommon == 'giờ' ? '(giờ)' : '(SP)'; +$lang->story->hour = $lang->hourCommon; +$lang->story->status = 'Tình trạng'; +$lang->story->subStatus = 'Tình trạng con'; +$lang->story->stage = 'Giai đoạn'; +$lang->story->stageAB = 'Giai đoạn'; +$lang->story->stagedBy = 'Thiết lập bởi'; +$lang->story->mailto = 'Mail tới'; +$lang->story->openedBy = 'Người tạo'; +$lang->story->openedDate = 'Ngày tạo'; +$lang->story->assignedTo = 'Giao cho'; +$lang->story->assignedDate = 'Ngày giao'; +$lang->story->lastEditedBy = 'Người sửa'; +$lang->story->lastEditedDate = 'Ngày sửa'; +$lang->story->closedBy = 'Người đóng'; +$lang->story->closedDate = 'Ngày đóng'; +$lang->story->closedReason = 'Lý do'; +$lang->story->rejectedReason = 'Lý do từ chối'; +$lang->story->reviewedBy = 'Người duyệt'; +$lang->story->reviewer = $lang->story->reviewedBy; +$lang->story->reviewers = 'Reviewers'; +$lang->story->reviewedDate = 'Ngày duyệt'; +$lang->story->activatedDate = 'Activated Date'; +$lang->story->version = 'Phiên bản'; +$lang->story->feedbackBy = 'From Name'; +$lang->story->notifyEmail = 'From Email'; +$lang->story->plan = 'Kế hoạch liên kết'; +$lang->story->planAB = 'Kế hoạch'; +$lang->story->comment = 'Nhận xét'; +$lang->story->children = "$lang->SRCommon} con"; +$lang->story->childrenAB = "C"; +$lang->story->linkStories = 'Câu chuyện liên kết'; +$lang->story->childStories = 'Câu chuyện được phân rã'; +$lang->story->duplicateStory = 'ID câu chuyện được nhân bản'; +$lang->story->reviewResult = 'Duyệt kết quả'; +$lang->story->preVersion = 'Phiên bản mới nhất'; +$lang->story->keywords = 'Tags'; +$lang->story->newStory = 'Tiếp tục thêm'; +$lang->story->colorTag = 'Màu'; +$lang->story->files = 'Files'; +$lang->story->copy = "Copy"; +$lang->story->total = "Tổng câu chuyện"; +$lang->story->draft = 'Nháp'; +$lang->story->unclosed = 'Chưa đóng'; +$lang->story->deleted = 'Đã xóa'; +$lang->story->released = 'Đã phát hành'; +$lang->story->URChanged = 'Requirement Changed'; +$lang->story->design = 'Designs'; +$lang->story->case = 'Cases'; +$lang->story->bug = 'Bugs'; +$lang->story->repoCommit = 'Commits'; +$lang->story->noRequirement = 'No Requirements'; +$lang->story->one = 'One'; +$lang->story->field = 'Synchronized fields'; +$lang->story->completeRate = 'Completion Rate'; +$lang->story->reviewed = 'Reviewed'; +$lang->story->toBeReviewed = 'To Be Reviewed'; +$lang->story->linkMR = 'Related MRs'; + +$lang->story->ditto = 'Như trên'; +$lang->story->dittoNotice = 'Câu chuyện này chưa liên kết tới cùng sản phẩm bởi bởi vì nó là cuối cùng!'; + +$lang->story->needNotReviewList[0] = 'Need Review'; +$lang->story->needNotReviewList[1] = 'Need Not Review'; + +$lang->story->useList[0] = 'Có'; +$lang->story->useList[1] = 'Không'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = 'Nháp'; +$lang->story->statusList['active'] = 'Kích hoạt'; +$lang->story->statusList['closed'] = 'Đã đóng'; +$lang->story->statusList['changed'] = 'Đã thay đổi'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = 'Đang đợi'; +$lang->story->stageList['planned'] = 'Kế hoạch'; +$lang->story->stageList['projected'] = 'Đã dự án'; +$lang->story->stageList['developing'] = 'Đang phát triển'; +$lang->story->stageList['developed'] = 'Đang phát triển'; +$lang->story->stageList['testing'] = 'Đang test'; +$lang->story->stageList['tested'] = 'Đã test'; +$lang->story->stageList['verified'] = 'Đã chấp thuận'; +$lang->story->stageList['released'] = 'Đã phát hành'; +$lang->story->stageList['closed'] = 'Đã đóng'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = 'Đã hoàn thành'; +$lang->story->reasonList['subdivided'] = 'Đã phân rã'; +$lang->story->reasonList['duplicate'] = 'Đã nhân bản'; +$lang->story->reasonList['postponed'] = 'Đã hoãn lại'; +$lang->story->reasonList['willnotdo'] = "Sẽ không làm"; +$lang->story->reasonList['cancel'] = 'Đã hủy'; +$lang->story->reasonList['bydesign'] = 'Như thiết kế'; +//$lang->story->reasonList['isbug'] = 'Bug!'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = 'Đạt'; +$lang->story->reviewResultList['revert'] = 'Hoàn lại'; +$lang->story->reviewResultList['clarify'] = 'Đã làm rõ'; +$lang->story->reviewResultList['reject'] = 'Từ chối'; + +$lang->story->reviewList[0] = 'Không'; +$lang->story->reviewList[1] = 'Có'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = 'Khách hàng'; +$lang->story->sourceList['user'] = 'Người dùng'; +$lang->story->sourceList['po'] = 'Sở hữu '.$lang->productCommon; +$lang->story->sourceList['market'] = 'Marketing'; +$lang->story->sourceList['service'] = 'Dịch vụ khách hàng'; +$lang->story->sourceList['operation'] = 'Vận hành'; +$lang->story->sourceList['support'] = 'Hỗ trợ kỹ thuật'; +$lang->story->sourceList['competitor'] = 'Đối thủ'; +$lang->story->sourceList['partner'] = 'Đối tác'; +$lang->story->sourceList['dev'] = 'Dev đội nhóm'; +$lang->story->sourceList['tester'] = 'Đội QA'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['forum'] = 'Forum'; +$lang->story->sourceList['other'] = 'Khác'; + +$lang->story->priList[] = ''; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[3] = '3'; +$lang->story->priList[4] = '4'; + +$lang->story->changeList = array(); +$lang->story->changeList['no'] = 'Cancel'; +$lang->story->changeList['yes'] = 'Confirm'; + +$lang->story->legendBasicInfo = 'Thông tin cơ bản'; +$lang->story->legendLifeTime = 'Tổng quan'; +$lang->story->legendRelated = 'Thông tin liên quan'; +$lang->story->legendMailto = 'Mail tới'; +$lang->story->legendAttatch = 'Files'; +$lang->story->legendProjectAndTask = $lang->executionCommon . ' và nhiệm vụ'; +$lang->story->legendBugs = 'Bugs liên kết'; +$lang->story->legendFromBug = 'Từ Bug'; +$lang->story->legendCases = 'Tình huống liên kết'; +$lang->story->legendLinkStories = 'Câu chuyện liên kết'; +$lang->story->legendChildStories = 'Câu chuyện con'; +$lang->story->legendSpec = 'Mô tả'; +$lang->story->legendVerify = 'Chấp thuận'; +$lang->story->legendMisc = 'Khác'; +$lang->story->legendInformation = 'Story Information'; + +$lang->story->lblChange = "Đã đổi"; +$lang->story->lblReview = 'Xét duyệt'; +$lang->story->lblActivate = 'Kích hoạt'; +$lang->story->lblClose = 'Đóng'; +$lang->story->lblTBC = 'Nhiệm vụ/Bug/Tình huống'; + +$lang->story->checkAffection = 'Ảnh hưởng'; +$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; +$lang->story->affectedBugs = 'Bugs'; +$lang->story->affectedCases = 'Tình huống'; + +$lang->story->specTemplate = "Theo , Tôi muốn ra bởi đó là ."; +$lang->story->needNotReview = 'Không có xét duyệt được yêu cầu'; +$lang->story->successSaved = "Câu chuyện đã lưu lại!"; +$lang->story->confirmDelete = "Bạn có muốn xóa câu chuyện này?"; +$lang->story->confirmRecall = "Do you want to recall this story?"; +$lang->story->errorEmptyChildStory = '『Câu chuyện đã phân rã』 không thể để trống.'; +$lang->story->errorNotSubdivide = "Nếu tình trạng này là chưa kích hoạt, hoặc giai đoạn này chưa đợi, hoặc một câu chuyện con, nó không thể bị chia nhỏ."; +$lang->story->errorEmptyReviewedBy = "『ReviewedBy』 không thể để trống."; +$lang->story->mustChooseResult = 'Chọn kết quả'; +$lang->story->mustChoosePreVersion = 'Chọn một phiên bản để chuyển thành.'; +$lang->story->noStory = 'Không có câu chuyện nào'; +$lang->story->noRequirement = 'Không có câu chuyện nào'; +$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; +$lang->story->cannotDeleteParent = "Không thể xóa {$lang->SRCommon} mẹ"; +$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; +$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; +$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; +$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; +$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; +$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; +$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; + +$lang->story->form = new stdclass(); +$lang->story->form->area = 'Phạm vi'; +$lang->story->form->desc = 'Nó là câu chuyện gì? Điều gì chấp nhận được?'; +$lang->story->form->resource = 'Ai sẽ phân bổ nguồn lực? Bao lâu để thực hiện nó?'; +$lang->story->form->file = 'Nếu bất kỳ tập tin mà liên kết tới một câu chuyện, Vui lòng click tại đây để tải nó lên.'; + +$lang->story->action = new stdclass(); +$lang->story->action->reviewed = array('main' => '$date, ghi nhận bởi $actor. Kết quả là $extra.', 'extra' => 'reviewResultList'); +$lang->story->action->rejectreviewed = array('main' => '$date, ghi nhận bởi $actor. Kết quả là $extra. Lý do là $reason', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); +$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); +$lang->story->action->closed = array('main' => '$date, được đóng bởi $actor. Lý do là $extra $appendLink.', 'extra' => 'reasonList'); +$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); +$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); +$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); +$lang->story->action->linked2plan = array('main' => '$date, liên kết bởi $actor tới kế hoạch $extra'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, bị hủy bởi $actor từ kế hoạch $extra.'); +$lang->story->action->linked2execution = array('main' => '$date, liên kết bởi $actor tới ' . $lang->executionCommon . ' $extra.'); +$lang->story->action->unlinkedfromexecution = array('main' => '$date, bị hủy bởi $actor từ ' . $lang->executionCommon . ' $extra.'); +$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); +$lang->story->action->linked2project = array('main' => '$date, liên kết bởi $actor tớ project $extra.'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, bị hủy bởi $actor từ project $extra.'); +$lang->story->action->linked2build = array('main' => '$date, liên kết bởi $actor tới Bản dựng $extra'); +$lang->story->action->unlinkedfrombuild = array('main' => '$date, bị hủy bởi $actor từ bản dựng $extra.'); +$lang->story->action->linked2release = array('main' => '$date, liên kết bởi $actor tới Phát hành $extra'); +$lang->story->action->unlinkedfromrelease = array('main' => '$date, bị hủy bởi $actor từ Phát hành $extra.'); +$lang->story->action->linkrelatedstory = array('main' => '$date, liên kết bởi $actor tới Câu chuyện $extra.'); +$lang->story->action->subdividestory = array('main' => '$date, được phân rã bởi $actor tới Câu chuyện $extra.'); +$lang->story->action->unlinkrelatedstory = array('main' => '$date, bị hủy bởi $actor từ Câu chuyện $extra.'); +$lang->story->action->unlinkchildstory = array('main' => '$date, bị hủy bởi $actor Câu chuyện được phân rã $extra.'); + +/* Statistical statement. */ +$lang->story->report = new stdclass(); +$lang->story->report->common = 'Báo cáo'; +$lang->story->report->select = 'Chọn loại báo cáo'; +$lang->story->report->create = 'Tạo báo cáo'; +$lang->story->report->value = 'Báo cáo'; + +$lang->story->report->charts['storysPerProduct'] = '' . $lang->productCommon . ' câu chuyện'; +$lang->story->report->charts['storysPerModule'] = 'Module câu chuyện'; +$lang->story->report->charts['storysPerSource'] = 'Nguồn câu chuyện'; +$lang->story->report->charts['storysPerPlan'] = 'kế hoạch'; +$lang->story->report->charts['storysPerStatus'] = 'tình trạng'; +$lang->story->report->charts['storysPerStage'] = 'Giai đoạn'; +$lang->story->report->charts['storysPerPri'] = 'ưu tiên'; +$lang->story->report->charts['storysPerEstimate'] = 'Dự tính'; +$lang->story->report->charts['storysPerOpenedBy'] = 'tạo bởi'; +$lang->story->report->charts['storysPerAssignedTo'] = 'giao cho'; +$lang->story->report->charts['storysPerClosedReason'] = 'Lý do đóng'; +$lang->story->report->charts['storysPerChange'] = 'Câu chuyện đã thay đổi'; + +$lang->story->report->options = new stdclass(); +$lang->story->report->options->graph = new stdclass(); +$lang->story->report->options->type = 'pie'; +$lang->story->report->options->width = 500; +$lang->story->report->options->height = 140; + +$lang->story->report->storysPerProduct = new stdclass(); +$lang->story->report->storysPerModule = new stdclass(); +$lang->story->report->storysPerSource = new stdclass(); +$lang->story->report->storysPerPlan = new stdclass(); +$lang->story->report->storysPerStatus = new stdclass(); +$lang->story->report->storysPerStage = new stdclass(); +$lang->story->report->storysPerPri = new stdclass(); +$lang->story->report->storysPerOpenedBy = new stdclass(); +$lang->story->report->storysPerAssignedTo = new stdclass(); +$lang->story->report->storysPerClosedReason = new stdclass(); +$lang->story->report->storysPerEstimate = new stdclass(); +$lang->story->report->storysPerChange = new stdclass(); + +$lang->story->report->storysPerProduct->item = $lang->productCommon; +$lang->story->report->storysPerModule->item = 'Module'; +$lang->story->report->storysPerSource->item = 'Nguồn'; +$lang->story->report->storysPerPlan->item = 'Kế hoạch'; +$lang->story->report->storysPerStatus->item = 'Tình trạng'; +$lang->story->report->storysPerStage->item = 'Giai đoạn'; +$lang->story->report->storysPerPri->item = 'Ưu tiên'; +$lang->story->report->storysPerOpenedBy->item = 'Mở bởi'; +$lang->story->report->storysPerAssignedTo->item = 'Giao cho'; +$lang->story->report->storysPerClosedReason->item = 'Lý do'; +$lang->story->report->storysPerEstimate->item = 'Dự tính'; +$lang->story->report->storysPerChange->item = 'Đã thay đổi câu chuyện'; + +$lang->story->report->storysPerProduct->graph = new stdclass(); +$lang->story->report->storysPerModule->graph = new stdclass(); +$lang->story->report->storysPerSource->graph = new stdclass(); +$lang->story->report->storysPerPlan->graph = new stdclass(); +$lang->story->report->storysPerStatus->graph = new stdclass(); +$lang->story->report->storysPerStage->graph = new stdclass(); +$lang->story->report->storysPerPri->graph = new stdclass(); +$lang->story->report->storysPerOpenedBy->graph = new stdclass(); +$lang->story->report->storysPerAssignedTo->graph = new stdclass(); +$lang->story->report->storysPerClosedReason->graph = new stdclass(); +$lang->story->report->storysPerEstimate->graph = new stdclass(); +$lang->story->report->storysPerChange->graph = new stdclass(); + +$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; +$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; +$lang->story->report->storysPerSource->graph->xAxisName = 'Nguồn'; +$lang->story->report->storysPerPlan->graph->xAxisName = 'Kế hoạch'; +$lang->story->report->storysPerStatus->graph->xAxisName = 'Tình trạng'; +$lang->story->report->storysPerStage->graph->xAxisName = 'Giai đoạn'; +$lang->story->report->storysPerPri->graph->xAxisName = 'Ưu tiên'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Người tạo'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Giao cho'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Lý do đóng'; +$lang->story->report->storysPerEstimate->graph->xAxisName = 'Dự tính '; +$lang->story->report->storysPerChange->graph->xAxisName = 'Số lần thay đổi'; + +$lang->story->placeholder = new stdclass(); +$lang->story->placeholder->estimate = $lang->story->hour; + +$lang->story->chosen = new stdClass(); +$lang->story->chosen->reviewedBy = 'Chọn xét duyệt bởi'; + +$lang->story->notice = new stdClass(); +$lang->story->notice->closed = 'Câu chuyện mà bạn chọn đã đóng!'; +$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; + +$lang->story->convertToTask = new stdClass(); +$lang->story->convertToTask->fieldList = array(); +$lang->story->convertToTask->fieldList['module'] = 'Module'; +$lang->story->convertToTask->fieldList['spec'] = "Description"; +$lang->story->convertToTask->fieldList['pri'] = 'Priority'; +$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; +$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; + +$lang->story->categoryList['feature'] = 'Feature'; +$lang->story->categoryList['interface'] = 'Interface'; +$lang->story->categoryList['performance'] = 'Performance'; +$lang->story->categoryList['safe'] = 'Safe'; +$lang->story->categoryList['experience'] = 'Experience'; +$lang->story->categoryList['improve'] = 'Improve'; +$lang->story->categoryList['other'] = 'Other'; + +$lang->story->changeTip = 'This Story is under review and cannot be changed.'; + +$lang->story->reviewTip = array(); +$lang->story->reviewTip['active'] = 'The Story is already active,no review requirements.'; +$lang->story->reviewTip['notReviewer'] = 'You are not the reviewer of this Story and cannot perform review operations.'; +$lang->story->reviewTip['reviewed'] = 'Reviewed'; +$lang->story->reviewTip['recalled'] = 'This Story has been withdrawn from review and cannot be reviewed.'; + +$lang->story->recallTip = array(); +$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.'; +$lang->story->recallTip['recalled'] = 'This Story has been withdrawn from review and cannot be cancelled.'; +$lang->story->recallTip['reviewed'] = 'The review process has been started and cannot be cancelled.'; +$lang->story->recallTip['notOpenedBy'] = 'You are not the Story creator and cannot cancel the operation.'; + +$lang->story->subDivideTip = array(); +$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.'; +$lang->story->subDivideTip['planned'] = 'The Story has been planned and cannot be subdivided.'; +$lang->story->subDivideTip['projected'] = 'The Story has been initiated and cannot be subdivided.'; +$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.'; diff --git a/module/story:q/lang/zh-cn.php b/module/story:q/lang/zh-cn.php new file mode 100644 index 0000000000..a55f5559d7 --- /dev/null +++ b/module/story:q/lang/zh-cn.php @@ -0,0 +1,441 @@ + + * @package story + * @version $Id: zh-cn.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +global $config; +$lang->story->create = "提{$lang->SRCommon}"; + +$lang->story->requirement = zget($lang, 'URCommon', "需求"); +$lang->story->story = zget($lang, 'SRCommon', "故事"); +$lang->story->createStory = '添加' . $lang->story->story; +$lang->story->createRequirement = '添加' . $lang->story->requirement; +$lang->story->affectedStories = "影响的{$lang->story->story}"; + +$lang->story->batchCreate = "批量创建"; +$lang->story->change = "变更"; +$lang->story->changed = "{$lang->SRCommon}变更"; +$lang->story->assignTo = '指派'; +$lang->story->review = '评审'; +$lang->story->recall = '撤销评审'; +$lang->story->needReview = '需要评审'; +$lang->story->batchReview = '批量评审'; +$lang->story->edit = "编辑"; +$lang->story->batchEdit = "批量编辑"; +$lang->story->subdivide = '细分'; +$lang->story->link = '关联'; +$lang->story->unlink = '移除'; +$lang->story->track = '跟踪矩阵'; +$lang->story->trackAB = '矩阵'; +$lang->story->processStoryChange = '确认需求变动'; +$lang->story->splitRequirent = '拆分'; +$lang->story->close = '关闭'; +$lang->story->batchClose = '批量关闭'; +$lang->story->activate = '激活'; +$lang->story->delete = "删除"; +$lang->story->view = "{$lang->SRCommon}详情"; +$lang->story->setting = "设置"; +$lang->story->tasks = "相关任务"; +$lang->story->bugs = "相关Bug"; +$lang->story->cases = "相关用例"; +$lang->story->taskCount = '任务数'; +$lang->story->bugCount = 'Bug数'; +$lang->story->caseCount = '用例数'; +$lang->story->taskCountAB = 'T'; +$lang->story->bugCountAB = 'B'; +$lang->story->caseCountAB = 'C'; +$lang->story->linkStory = "关联{$lang->SRCommon}"; +$lang->story->unlinkStory = "移除相关{$lang->SRCommon}"; +$lang->story->export = "导出数据"; +$lang->story->zeroCase = "零用例{$lang->SRCommon}"; +$lang->story->zeroTask = "只列零任务{$lang->SRCommon}"; +$lang->story->reportChart = "统计报表"; +$lang->story->copyTitle = "同{$lang->SRCommon}名称"; +$lang->story->batchChangePlan = "批量修改计划"; +$lang->story->batchChangeBranch = "批量修改分支"; +$lang->story->batchChangeStage = "批量修改阶段"; +$lang->story->batchAssignTo = "批量指派"; +$lang->story->batchChangeModule = "批量修改模块"; +$lang->story->viewAll = '查看全部'; +$lang->story->toTask = '转任务'; +$lang->story->batchToTask = '批量转任务'; +$lang->story->convertRelations = '换算关系'; +$lang->story->undetermined = '待定'; +$lang->story->order = '排序'; + +$lang->story->editAction = "编辑{$lang->SRCommon}"; +$lang->story->changeAction = "变更{$lang->SRCommon}"; +$lang->story->assignAction = "指派{$lang->SRCommon}"; +$lang->story->reviewAction = "评审{$lang->SRCommon}"; +$lang->story->subdivideAction = "细分{$lang->SRCommon}"; +$lang->story->closeAction = "关闭{$lang->SRCommon}"; +$lang->story->activateAction = "激活{$lang->SRCommon}"; +$lang->story->deleteAction = "删除{$lang->SRCommon}"; +$lang->story->exportAction = "导出{$lang->SRCommon}"; +$lang->story->reportAction = "统计报表"; + +$lang->story->skipStory = '需求:%s 为父需求,将不会被关闭。'; +$lang->story->closedStory = '需求:%s 已关闭,将不会被关闭。'; +$lang->story->batchToTaskTips = "此操作会创建与所选{$lang->SRCommon}同名的任务,并将{$lang->SRCommon}关联到任务中,已关闭的需求不会转为任务。"; +$lang->story->successToTask = '批量转任务成功'; +$lang->story->storyRound = '第 %s 轮估算'; + +$lang->story->id = '编号'; +$lang->story->parent = '父需求'; +$lang->story->product = "所属{$lang->productCommon}"; +$lang->story->project = "所属项目"; +$lang->story->branch = "分支/平台"; +$lang->story->module = '所属模块'; +$lang->story->moduleAB = '模块'; +$lang->story->source = "来源"; +$lang->story->sourceNote = '来源备注'; +$lang->story->fromBug = '来源Bug'; +$lang->story->title = "{$lang->SRCommon}名称"; +$lang->story->type = "需求类型"; +$lang->story->category = "类别"; +$lang->story->color = '标题颜色'; +$lang->story->toBug = '转Bug'; +$lang->story->spec = "描述"; +$lang->story->assign = '指派给'; +$lang->story->verify = '验收标准'; +$lang->story->pri = '优先级'; +$lang->story->estimate = "预计{$lang->hourCommon}"; +$lang->story->estimateAB = '预计'; +$lang->story->hour = $lang->hourCommon; +$lang->story->status = '当前状态'; +$lang->story->subStatus = '子状态'; +$lang->story->stage = '所处阶段'; +$lang->story->stageAB = '阶段'; +$lang->story->stagedBy = '设置阶段者'; +$lang->story->mailto = '抄送给'; +$lang->story->openedBy = '由谁创建'; +$lang->story->openedDate = '创建日期'; +$lang->story->assignedTo = '指派给'; +$lang->story->assignedDate = '指派日期'; +$lang->story->lastEditedBy = '最后修改'; +$lang->story->lastEditedDate = '最后修改日期'; +$lang->story->closedBy = '由谁关闭'; +$lang->story->closedDate = '关闭日期'; +$lang->story->closedReason = '关闭原因'; +$lang->story->rejectedReason = '拒绝原因'; +$lang->story->reviewedBy = '由谁评审'; +$lang->story->reviewer = $lang->story->reviewedBy; +$lang->story->reviewers = '评审人员'; +$lang->story->reviewedDate = '评审时间'; +$lang->story->activatedDate = '激活日期'; +$lang->story->version = '版本号'; +$lang->story->feedbackBy = '反馈者'; +$lang->story->notifyEmail = '通知邮箱'; +$lang->story->plan = "所属计划"; +$lang->story->planAB = '计划'; +$lang->story->comment = '备注'; +$lang->story->children = "子需求"; +$lang->story->childrenAB = "子"; +$lang->story->linkStories = "相关{$lang->SRCommon}"; +$lang->story->childStories = "细分需求"; +$lang->story->duplicateStory = "重复需求ID"; +$lang->story->reviewResult = '评审意见'; +$lang->story->preVersion = '之前版本'; +$lang->story->keywords = '关键词'; +$lang->story->newStory = "继续添加{$lang->SRCommon}"; +$lang->story->colorTag = '颜色标签'; +$lang->story->files = '附件'; +$lang->story->copy = "复制{$lang->SRCommon}"; +$lang->story->total = "总{$lang->SRCommon}"; +$lang->story->draft = '草稿'; +$lang->story->unclosed = '未关闭'; +$lang->story->deleted = '已删除'; +$lang->story->released = "已发布{$lang->SRCommon}数"; +$lang->story->URChanged = '用需变更'; +$lang->story->design = '相关设计'; +$lang->story->case = '相关用例'; +$lang->story->bug = '相关Bug'; +$lang->story->repoCommit = '相关提交'; +$lang->story->noRequirement = '无需求'; +$lang->story->one = '一个'; +$lang->story->field = '同步的字段'; +$lang->story->completeRate = '完成率'; +$lang->story->reviewed = '已评审'; +$lang->story->toBeReviewed = '待评审'; +$lang->story->linkMR = '相关合并请求'; + +$lang->story->ditto = '同上'; +$lang->story->dittoNotice = "该{$lang->SRCommon}与上一{$lang->SRCommon}不属于同一产品!"; + +$lang->story->needNotReviewList[0] = '需要评审'; +$lang->story->needNotReviewList[1] = '不需要评审'; + +$lang->story->useList[0] = '不使用'; +$lang->story->useList[1] = '使用'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = '草稿'; +$lang->story->statusList['active'] = '激活'; +$lang->story->statusList['closed'] = '已关闭'; +$lang->story->statusList['changed'] = '已变更'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = '未开始'; +$lang->story->stageList['planned'] = "已计划"; +$lang->story->stageList['projected'] = '已立项'; +$lang->story->stageList['developing'] = '研发中'; +$lang->story->stageList['developed'] = '研发完毕'; +$lang->story->stageList['testing'] = '测试中'; +$lang->story->stageList['tested'] = '测试完毕'; +$lang->story->stageList['verified'] = '已验收'; +$lang->story->stageList['released'] = '已发布'; +$lang->story->stageList['closed'] = '已关闭'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = '已完成'; +$lang->story->reasonList['subdivided'] = '已细分'; +$lang->story->reasonList['duplicate'] = '重复'; +$lang->story->reasonList['postponed'] = '延期'; +$lang->story->reasonList['willnotdo'] = '不做'; +$lang->story->reasonList['cancel'] = '已取消'; +$lang->story->reasonList['bydesign'] = '设计如此'; +//$lang->story->reasonList['isbug'] = '是个Bug'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = '确认通过'; +$lang->story->reviewResultList['revert'] = '撤销变更'; +$lang->story->reviewResultList['clarify'] = '有待明确'; +$lang->story->reviewResultList['reject'] = '拒绝'; + +$lang->story->reviewList[0] = '否'; +$lang->story->reviewList[1] = '是'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = '客户'; +$lang->story->sourceList['user'] = '用户'; +$lang->story->sourceList['po'] = $lang->productCommon . '经理'; +$lang->story->sourceList['market'] = '市场'; +$lang->story->sourceList['service'] = '客服'; +$lang->story->sourceList['operation'] = '运营'; +$lang->story->sourceList['support'] = '技术支持'; +$lang->story->sourceList['competitor'] = '竞争对手'; +$lang->story->sourceList['partner'] = '合作伙伴'; +$lang->story->sourceList['dev'] = '开发人员'; +$lang->story->sourceList['tester'] = '测试人员'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['forum'] = '论坛'; +$lang->story->sourceList['other'] = '其他'; + +$lang->story->priList[] = ''; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[3] = '3'; +$lang->story->priList[4] = '4'; + +$lang->story->changeList = array(); +$lang->story->changeList['no'] = '不变更'; +$lang->story->changeList['yes'] = '变更'; + +$lang->story->legendBasicInfo = '基本信息'; +$lang->story->legendLifeTime = "需求的一生"; +$lang->story->legendRelated = '相关信息'; +$lang->story->legendMailto = '抄送给'; +$lang->story->legendAttatch = '附件'; +$lang->story->legendProjectAndTask = $lang->executionCommon . '任务'; +$lang->story->legendBugs = '相关Bug'; +$lang->story->legendFromBug = '来源Bug'; +$lang->story->legendCases = '相关用例'; +$lang->story->legendLinkStories = "相关{$lang->SRCommon}"; +$lang->story->legendChildStories = "细分{$lang->SRCommon}"; +$lang->story->legendSpec = "需求描述"; +$lang->story->legendVerify = '验收标准'; +$lang->story->legendMisc = '其他相关'; +$lang->story->legendInformation = '需求信息'; + +$lang->story->lblChange = "变更{$lang->SRCommon}"; +$lang->story->lblReview = "评审{$lang->SRCommon}"; +$lang->story->lblActivate = "激活{$lang->SRCommon}"; +$lang->story->lblClose = "关闭{$lang->SRCommon}"; +$lang->story->lblTBC = '任务Bug用例'; + +$lang->story->checkAffection = '影响范围'; +$lang->story->affectedProjects = $config->systemMode == 'new' ? "影响的{$lang->project->common}或{$lang->execution->common}" : "影响的{$lang->project->common}"; +$lang->story->affectedBugs = '影响的Bug'; +$lang->story->affectedCases = '影响的用例'; + +$lang->story->specTemplate = "建议参考的模板:作为一名<某种类型的用户>,我希望<达成某些目的>,这样可以<开发的价值>。"; +$lang->story->needNotReview = '不需要评审'; +$lang->story->successSaved = "{$lang->SRCommon}成功添加,"; +$lang->story->confirmDelete = "您确认删除该{$lang->SRCommon}吗?"; +$lang->story->confirmRecall = "您确认撤销该{$lang->SRCommon}吗?"; +$lang->story->errorEmptyChildStory = "『细分{$lang->SRCommon}』不能为空。"; +$lang->story->errorNotSubdivide = "状态不是激活,或者阶段不是未开始的{$lang->SRCommon},或者是子需求,则不能细分。"; +$lang->story->errorEmptyReviewedBy = "『由谁评审』不能为空。"; +$lang->story->mustChooseResult = '必须选择评审意见'; +$lang->story->mustChoosePreVersion = '必须选择回溯的版本'; +$lang->story->noStory = "暂时没有{$lang->SRCommon}。"; +$lang->story->noRequirement = "暂时没有{$lang->URCommon}。"; +$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 为草稿状态或已关闭状态,没有修改其阶段。"; +$lang->story->cannotDeleteParent = "不能删除父{$lang->SRCommon}"; +$lang->story->moveChildrenTips = "修改父{$lang->SRCommon}的所属产品会将其下的子{$lang->SRCommon}也移动到所选产品下。"; +$lang->story->changeTips = '该软件需求关联的用户需求有变更,点击“不变更”忽略此条变更,点击“变更”来进行该软件需求的变更。'; +$lang->story->estimateMustBeNumber = '估算值必须是数字'; +$lang->story->estimateMustBePlus = '估算值不能是负数'; +$lang->story->confirmChangeBranch = $lang->SRCommon . '%s已关联在之前所属分支的计划中,调整分支后,' . $lang->SRCommon . '将从之前所属分支的计划中移除,请确认是否继续修改上述' . $lang->SRCommon . '的分支。'; +$lang->story->confirmChangePlan = $lang->SRCommon . '%s已关联在之前计划的所属分支中,调整分支后,' . $lang->SRCommon . '将会从计划中移除,请确认是否继续修改计划的所属分支。'; +$lang->story->errorDuplicateStory = $lang->SRCommon . '%s不存在'; + +$lang->story->form = new stdclass(); +$lang->story->form->area = "该{$lang->SRCommon}所属范围"; +$lang->story->form->desc = "描述及标准,什么{$lang->SRCommon}?如何验收?"; +$lang->story->form->resource = '资源分配,有谁完成?需要多少时间?'; +$lang->story->form->file = "附件,如果该{$lang->SRCommon}有相关文件,请点此上传。"; + +$lang->story->action = new stdclass(); +$lang->story->action->reviewed = array('main' => '$date, 由 $actor 记录评审意见,评审意见为 $extra。', 'extra' => 'reviewResultList'); +$lang->story->action->rejectreviewed = array('main' => '$date, 由 $actor 记录评审意见,评审意见为 $extra,原因为 $reason。', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); +$lang->story->action->recalled = array('main' => '$date, 由 $actor 撤销评审。'); +$lang->story->action->closed = array('main' => '$date, 由 $actor 关闭,原因为 $extra $appendLink。', 'extra' => 'reasonList'); +$lang->story->action->reviewpassed = array('main' => '$date, 由 系统 判定,结果为 确认通过。'); +$lang->story->action->reviewrejected = array('main' => '$date, 由 系统 关闭,原因为 拒绝。'); +$lang->story->action->reviewclarified = array('main' => '$date, 由 系统 判定,结果为 有待明确,请变更后重新发起评审。'); +$lang->story->action->linked2plan = array('main' => '$date, 由 $actor 关联到计划 $extra。'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 从计划 $extra 移除。'); +$lang->story->action->linked2execution = array('main' => '$date, 由 $actor 关联到' . $lang->executionCommon . ' $extra。'); +$lang->story->action->unlinkedfromexecution = array('main' => '$date, 由 $actor 从' . $lang->executionCommon . ' $extra 移除。'); +$lang->story->action->linked2kanban = array('main' => '$date, 由 $actor 关联到看板 $extra。'); +$lang->story->action->linked2project = array('main' => '$date, 由 $actor 关联到项目 $extra。'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 从项目 $extra 移除。'); +$lang->story->action->linked2build = array('main' => '$date, 由 $actor 关联到版本 $extra。'); +$lang->story->action->unlinkedfrombuild = array('main' => '$date, 由 $actor 从版本 $extra 移除。'); +$lang->story->action->linked2release = array('main' => '$date, 由 $actor 关联到发布 $extra。'); +$lang->story->action->unlinkedfromrelease = array('main' => '$date, 由 $actor 从发布 $extra 移除。'); +$lang->story->action->linkrelatedstory = array('main' => "\$date, 由 \$actor 关联相关{$lang->SRCommon} \$extra。"); +$lang->story->action->subdividestory = array('main' => "\$date, 由 \$actor 细分为{$lang->SRCommon} \$extra。"); +$lang->story->action->unlinkrelatedstory = array('main' => "\$date, 由 \$actor 移除相关{$lang->SRCommon} \$extra。"); +$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 \$actor 移除细分{$lang->SRCommon} \$extra。"); + +/* 统计报表。*/ +$lang->story->report = new stdclass(); +$lang->story->report->common = '报表'; +$lang->story->report->select = '请选择报表类型'; +$lang->story->report->create = '生成报表'; +$lang->story->report->value = "需求数"; + +$lang->story->report->charts['storysPerProduct'] = $lang->productCommon . "{$lang->SRCommon}数量"; +$lang->story->report->charts['storysPerModule'] = "模块{$lang->SRCommon}数量"; +$lang->story->report->charts['storysPerSource'] = "按{$lang->SRCommon}来源统计"; +$lang->story->report->charts['storysPerPlan'] = "按计划进行统计"; +$lang->story->report->charts['storysPerStatus'] = '按状态进行统计'; +$lang->story->report->charts['storysPerStage'] = '按所处阶段进行统计'; +$lang->story->report->charts['storysPerPri'] = '按优先级进行统计'; +$lang->story->report->charts['storysPerEstimate'] = "按预计{$lang->hourCommon}进行统计"; +$lang->story->report->charts['storysPerOpenedBy'] = '按由谁创建来进行统计'; +$lang->story->report->charts['storysPerAssignedTo'] = '按当前指派来进行统计'; +$lang->story->report->charts['storysPerClosedReason'] = '按关闭原因来进行统计'; +$lang->story->report->charts['storysPerChange'] = '按变更次数来进行统计'; + +$lang->story->report->options = new stdclass(); +$lang->story->report->options->graph = new stdclass(); +$lang->story->report->options->type = 'pie'; +$lang->story->report->options->width = 500; +$lang->story->report->options->height = 140; + +$lang->story->report->storysPerProduct = new stdclass(); +$lang->story->report->storysPerModule = new stdclass(); +$lang->story->report->storysPerSource = new stdclass(); +$lang->story->report->storysPerPlan = new stdclass(); +$lang->story->report->storysPerStatus = new stdclass(); +$lang->story->report->storysPerStage = new stdclass(); +$lang->story->report->storysPerPri = new stdclass(); +$lang->story->report->storysPerOpenedBy = new stdclass(); +$lang->story->report->storysPerAssignedTo = new stdclass(); +$lang->story->report->storysPerClosedReason = new stdclass(); +$lang->story->report->storysPerEstimate = new stdclass(); +$lang->story->report->storysPerChange = new stdclass(); + +$lang->story->report->storysPerProduct->item = $lang->productCommon; +$lang->story->report->storysPerModule->item = '模块'; +$lang->story->report->storysPerSource->item = '来源'; +$lang->story->report->storysPerPlan->item = '计划'; +$lang->story->report->storysPerStatus->item = '状态'; +$lang->story->report->storysPerStage->item = '阶段'; +$lang->story->report->storysPerPri->item = '优先级'; +$lang->story->report->storysPerOpenedBy->item = '由谁创建'; +$lang->story->report->storysPerAssignedTo->item = '指派给'; +$lang->story->report->storysPerClosedReason->item = '原因'; +$lang->story->report->storysPerEstimate->item = "预计{$lang->hourCommon}"; +$lang->story->report->storysPerChange->item = '变更次数'; + +$lang->story->report->storysPerProduct->graph = new stdclass(); +$lang->story->report->storysPerModule->graph = new stdclass(); +$lang->story->report->storysPerSource->graph = new stdclass(); +$lang->story->report->storysPerPlan->graph = new stdclass(); +$lang->story->report->storysPerStatus->graph = new stdclass(); +$lang->story->report->storysPerStage->graph = new stdclass(); +$lang->story->report->storysPerPri->graph = new stdclass(); +$lang->story->report->storysPerOpenedBy->graph = new stdclass(); +$lang->story->report->storysPerAssignedTo->graph = new stdclass(); +$lang->story->report->storysPerClosedReason->graph = new stdclass(); +$lang->story->report->storysPerEstimate->graph = new stdclass(); +$lang->story->report->storysPerChange->graph = new stdclass(); + +$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; +$lang->story->report->storysPerModule->graph->xAxisName = '模块'; +$lang->story->report->storysPerSource->graph->xAxisName = '来源'; +$lang->story->report->storysPerPlan->graph->xAxisName = '计划'; +$lang->story->report->storysPerStatus->graph->xAxisName = '状态'; +$lang->story->report->storysPerStage->graph->xAxisName = '所处阶段'; +$lang->story->report->storysPerPri->graph->xAxisName = '优先级'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由谁创建'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = '当前指派'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = '关闭原因'; +$lang->story->report->storysPerEstimate->graph->xAxisName = '预计时间'; +$lang->story->report->storysPerChange->graph->xAxisName = '变更次数'; + +$lang->story->placeholder = new stdclass(); +$lang->story->placeholder->estimate = $lang->story->hour; + +$lang->story->chosen = new stdClass(); +$lang->story->chosen->reviewedBy = '选择评审人...'; + +$lang->story->notice = new stdClass(); +$lang->story->notice->closed = "您选择的{$lang->SRCommon}已经被关闭了!"; +$lang->story->notice->reviewerNotEmpty = '该需求需要评审,评审人员不能为空。'; + +$lang->story->convertToTask = new stdClass(); +$lang->story->convertToTask->fieldList = array(); +$lang->story->convertToTask->fieldList['module'] = '所属模块'; +$lang->story->convertToTask->fieldList['spec'] = "{$lang->SRCommon}描述"; +$lang->story->convertToTask->fieldList['pri'] = '优先级'; +$lang->story->convertToTask->fieldList['mailto'] = '抄送给'; +$lang->story->convertToTask->fieldList['assignedTo'] = '指派给'; + +$lang->story->categoryList['feature'] = '功能'; +$lang->story->categoryList['interface'] = '接口'; +$lang->story->categoryList['performance'] = '性能'; +$lang->story->categoryList['safe'] = '安全'; +$lang->story->categoryList['experience'] = '体验'; +$lang->story->categoryList['improve'] = '改进'; +$lang->story->categoryList['other'] = '其他'; + +$lang->story->changeTip = '该需求正在评审中,无法进行变更'; + +$lang->story->reviewTip = array(); +$lang->story->reviewTip['active'] = '该需求已是激活状态,无需评审'; +$lang->story->reviewTip['notReviewer'] = '您不是该需求的评审人员,无法进行评审操作'; +$lang->story->reviewTip['reviewed'] = '您已评审'; +$lang->story->reviewTip['recalled'] = '该需求已撤销评审,无法进行评审操作'; + +$lang->story->recallTip = array(); +$lang->story->recallTip['actived'] = '该需求未发起评审流程,无需撤销操作'; +$lang->story->recallTip['recalled'] = '该需求已经撤销评审,无法进行撤销操作'; +$lang->story->recallTip['reviewed'] = '已进入评审流程,无法进行撤销操作'; +$lang->story->recallTip['notOpenedBy'] = '您不是需求创建者,无法进行撤销操作'; + +$lang->story->subDivideTip = array(); +$lang->story->subDivideTip['subStory'] = '子需求无法细分'; +$lang->story->subDivideTip['planned'] = '该需求已计划,无法进行细分操作'; +$lang->story->subDivideTip['projected'] = '该需求已立项,无法进行细分操作'; +$lang->story->subDivideTip['notActive'] = '需求不是激活状态,无法进行细分操作'; diff --git a/module/story:q/lang/zh-tw.php b/module/story:q/lang/zh-tw.php new file mode 100644 index 0000000000..ae595fa813 --- /dev/null +++ b/module/story:q/lang/zh-tw.php @@ -0,0 +1,416 @@ + + * @package story + * @version $Id: zh-tw.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +global $config; +$lang->story->create = "提{$lang->SRCommon}"; + +$lang->story->requirement = zget($lang, 'URCommon', "需求"); +$lang->story->story = zget($lang, 'SRCommon', "故事"); +$lang->story->createStory = '添加' . $lang->story->story; +$lang->story->createRequirement = '添加' . $lang->story->requirement; +$lang->story->affectedStories = "影響的{$lang->story->story}"; + +$lang->story->batchCreate = "批量創建"; +$lang->story->change = "變更"; +$lang->story->changed = "{$lang->SRCommon}變更"; +$lang->story->assignTo = '指派'; +$lang->story->review = '評審'; +$lang->story->recall = '撤銷評審'; +$lang->story->needReview = '需要評審'; +$lang->story->batchReview = '批量評審'; +$lang->story->edit = "編輯"; +$lang->story->batchEdit = "批量編輯"; +$lang->story->subdivide = '細分'; +$lang->story->link = '關聯'; +$lang->story->unlink = '移除'; +$lang->story->track = '跟蹤矩陣'; +$lang->story->trackAB = '矩陣'; +$lang->story->processStoryChange = '處理需求變更'; +$lang->story->splitRequirent = '拆分'; +$lang->story->close = '關閉'; +$lang->story->batchClose = '批量關閉'; +$lang->story->activate = '激活'; +$lang->story->delete = "刪除"; +$lang->story->view = "{$lang->SRCommon}詳情"; +$lang->story->setting = "設置"; +$lang->story->tasks = "相關任務"; +$lang->story->bugs = "相關Bug"; +$lang->story->cases = "相關用例"; +$lang->story->taskCount = '任務數'; +$lang->story->bugCount = 'Bug數'; +$lang->story->caseCount = '用例數'; +$lang->story->taskCountAB = 'T'; +$lang->story->bugCountAB = 'B'; +$lang->story->caseCountAB = 'C'; +$lang->story->linkStory = "關聯{$lang->SRCommon}"; +$lang->story->unlinkStory = "移除相關{$lang->SRCommon}"; +$lang->story->export = "導出數據"; +$lang->story->zeroCase = "零用例{$lang->SRCommon}"; +$lang->story->zeroTask = "只列零任務{$lang->SRCommon}"; +$lang->story->reportChart = "統計報表"; +$lang->story->copyTitle = "同{$lang->SRCommon}名稱"; +$lang->story->batchChangePlan = "批量修改計劃"; +$lang->story->batchChangeBranch = "批量修改分支"; +$lang->story->batchChangeStage = "批量修改階段"; +$lang->story->batchAssignTo = "批量指派"; +$lang->story->batchChangeModule = "批量修改模組"; +$lang->story->viewAll = '查看全部'; +$lang->story->toTask = '轉任務'; +$lang->story->batchToTask = '批量轉任務'; +$lang->story->convertRelations = '換算關係'; + +$lang->story->editAction = "編輯{$lang->SRCommon}"; +$lang->story->changeAction = "變更{$lang->SRCommon}"; +$lang->story->assignAction = "指派{$lang->SRCommon}"; +$lang->story->reviewAction = "評審{$lang->SRCommon}"; +$lang->story->subdivideAction = "細分{$lang->SRCommon}"; +$lang->story->closeAction = "關閉{$lang->SRCommon}"; +$lang->story->activateAction = "激活{$lang->SRCommon}"; +$lang->story->deleteAction = "刪除{$lang->SRCommon}"; +$lang->story->exportAction = "導出{$lang->SRCommon}"; +$lang->story->reportAction = "統計報表"; + +$lang->story->skipStory = '需求:%s 為父需求,將不會被關閉。'; +$lang->story->closedStory = '需求:%s 已關閉,將不會被關閉。'; +$lang->story->batchToTaskTips = "此操作會創建與所選{$lang->SRCommon}同名的任務,並將{$lang->SRCommon}關聯到任務中,已關閉的需求不會轉為任務。"; +$lang->story->successToTask = '批量轉任務成功'; +$lang->story->storyRound = '第 %s 輪估算'; + +$lang->story->common = $lang->SRCommon; +$lang->story->id = '編號'; +$lang->story->parent = '父需求'; +$lang->story->product = "所屬{$lang->productCommon}"; +$lang->story->project = "所屬項目"; +$lang->story->branch = "分支/平台"; +$lang->story->module = '所屬模組'; +$lang->story->moduleAB = '模組'; +$lang->story->source = "來源"; +$lang->story->sourceNote = '來源備註'; +$lang->story->fromBug = '來源Bug'; +$lang->story->title = "{$lang->SRCommon}名稱"; +$lang->story->type = "需求類型"; +$lang->story->category = "類型"; +$lang->story->color = '標題顏色'; +$lang->story->toBug = '轉Bug'; +$lang->story->spec = "描述"; +$lang->story->assign = '指派給'; +$lang->story->verify = '驗收標準'; +$lang->story->pri = '優先順序'; +$lang->story->estimate = "預計{$lang->hourCommon}"; +$lang->story->estimateAB = '預計'; +$lang->story->hour = $lang->hourCommon; +$lang->story->status = '當前狀態'; +$lang->story->subStatus = '子狀態'; +$lang->story->stage = '所處階段'; +$lang->story->stageAB = '階段'; +$lang->story->stagedBy = '設置階段者'; +$lang->story->mailto = '抄送給'; +$lang->story->openedBy = '由誰創建'; +$lang->story->openedDate = '創建日期'; +$lang->story->assignedTo = '指派給'; +$lang->story->assignedDate = '指派日期'; +$lang->story->lastEditedBy = '最後修改'; +$lang->story->lastEditedDate = '最後修改日期'; +$lang->story->closedBy = '由誰關閉'; +$lang->story->closedDate = '關閉日期'; +$lang->story->closedReason = '關閉原因'; +$lang->story->rejectedReason = '拒絶原因'; +$lang->story->reviewedBy = '由誰評審'; +$lang->story->reviewers = '評審人員'; +$lang->story->reviewedDate = '評審時間'; +$lang->story->activatedDate = '激活日期'; +$lang->story->version = '版本號'; +$lang->story->feedbackBy = '反饋者'; +$lang->story->notifyEmail = '通知郵箱'; +$lang->story->plan = "所屬計劃"; +$lang->story->planAB = '計劃'; +$lang->story->comment = '備註'; +$lang->story->children = "子需求"; +$lang->story->childrenAB = "子"; +$lang->story->linkStories = "相關{$lang->SRCommon}"; +$lang->story->childStories = "細分需求"; +$lang->story->duplicateStory = "重複需求ID"; +$lang->story->reviewResult = '評審意見'; +$lang->story->preVersion = '之前版本'; +$lang->story->keywords = '關鍵詞'; +$lang->story->newStory = "繼續添加{$lang->SRCommon}"; +$lang->story->colorTag = '顏色標籤'; +$lang->story->files = '附件'; +$lang->story->copy = "複製{$lang->SRCommon}"; +$lang->story->total = "總{$lang->SRCommon}"; +$lang->story->allStories = "所有{$lang->SRCommon}"; +$lang->story->draft = '草稿'; +$lang->story->unclosed = '未關閉'; +$lang->story->deleted = '已刪除'; +$lang->story->released = "已發佈{$lang->SRCommon}數"; +$lang->story->URChanged = '用需變更'; +$lang->story->design = '相關設計'; +$lang->story->case = '相關用例'; +$lang->story->bug = '相關Bug'; +$lang->story->repoCommit = '相關提交'; +$lang->story->noRequirement = '無需求'; +$lang->story->one = '一個'; +$lang->story->field = '同步的欄位'; +$lang->story->completeRate = '完成率'; +$lang->story->reviewed = '已評審'; +$lang->story->toBeReviewed = '待評審'; + +$lang->story->ditto = '同上'; +$lang->story->dittoNotice = "該{$lang->SRCommon}與上一{$lang->SRCommon}不屬於同一產品!"; + +$lang->story->needNotReviewList[0] = '需要評審'; +$lang->story->needNotReviewList[1] = '不需要評審'; + +$lang->story->useList[0] = '不使用'; +$lang->story->useList[1] = '使用'; + +$lang->story->statusList[''] = ''; +$lang->story->statusList['draft'] = '草稿'; +$lang->story->statusList['active'] = '激活'; +$lang->story->statusList['closed'] = '已關閉'; +$lang->story->statusList['changed'] = '已變更'; + +$lang->story->stageList[''] = ''; +$lang->story->stageList['wait'] = '未開始'; +$lang->story->stageList['planned'] = "已計劃"; +$lang->story->stageList['projected'] = '已立項'; +$lang->story->stageList['developing'] = '研發中'; +$lang->story->stageList['developed'] = '研發完畢'; +$lang->story->stageList['testing'] = '測試中'; +$lang->story->stageList['tested'] = '測試完畢'; +$lang->story->stageList['verified'] = '已驗收'; +$lang->story->stageList['released'] = '已發佈'; +$lang->story->stageList['closed'] = '已關閉'; + +$lang->story->reasonList[''] = ''; +$lang->story->reasonList['done'] = '已完成'; +$lang->story->reasonList['subdivided'] = '已細分'; +$lang->story->reasonList['duplicate'] = '重複'; +$lang->story->reasonList['postponed'] = '延期'; +$lang->story->reasonList['willnotdo'] = '不做'; +$lang->story->reasonList['cancel'] = '已取消'; +$lang->story->reasonList['bydesign'] = '設計如此'; +//$lang->story->reasonList['isbug'] = '是個Bug'; + +$lang->story->reviewResultList[''] = ''; +$lang->story->reviewResultList['pass'] = '確認通過'; +$lang->story->reviewResultList['revert'] = '撤銷變更'; +$lang->story->reviewResultList['clarify'] = '有待明確'; +$lang->story->reviewResultList['reject'] = '拒絶'; + +$lang->story->reviewList[0] = '否'; +$lang->story->reviewList[1] = '是'; + +$lang->story->sourceList[''] = ''; +$lang->story->sourceList['customer'] = '客戶'; +$lang->story->sourceList['user'] = '用戶'; +$lang->story->sourceList['po'] = $lang->productCommon . '經理'; +$lang->story->sourceList['market'] = '市場'; +$lang->story->sourceList['service'] = '客服'; +$lang->story->sourceList['operation'] = '運營'; +$lang->story->sourceList['support'] = '技術支持'; +$lang->story->sourceList['competitor'] = '競爭對手'; +$lang->story->sourceList['partner'] = '合作夥伴'; +$lang->story->sourceList['dev'] = '開發人員'; +$lang->story->sourceList['tester'] = '測試人員'; +$lang->story->sourceList['bug'] = 'Bug'; +$lang->story->sourceList['forum'] = '論壇'; +$lang->story->sourceList['other'] = '其他'; + +$lang->story->priList[] = ''; +$lang->story->priList[1] = '1'; +$lang->story->priList[2] = '2'; +$lang->story->priList[3] = '3'; +$lang->story->priList[4] = '4'; + +$lang->story->changeList = array(); +$lang->story->changeList['no'] = '不變更'; +$lang->story->changeList['yes'] = '變更'; + +$lang->story->legendBasicInfo = '基本信息'; +$lang->story->legendLifeTime = "需求的一生"; +$lang->story->legendRelated = '相關信息'; +$lang->story->legendMailto = '抄送給'; +$lang->story->legendAttatch = '附件'; +$lang->story->legendProjectAndTask = $lang->executionCommon . '任務'; +$lang->story->legendBugs = '相關Bug'; +$lang->story->legendFromBug = '來源Bug'; +$lang->story->legendCases = '相關用例'; +$lang->story->legendLinkStories = "相關{$lang->SRCommon}"; +$lang->story->legendChildStories = "細分{$lang->SRCommon}"; +$lang->story->legendSpec = "需求描述"; +$lang->story->legendVerify = '驗收標準'; +$lang->story->legendMisc = '其他相關'; +$lang->story->legendInformation = '需求信息'; + +$lang->story->lblChange = "變更{$lang->SRCommon}"; +$lang->story->lblReview = "評審{$lang->SRCommon}"; +$lang->story->lblActivate = "激活{$lang->SRCommon}"; +$lang->story->lblClose = "關閉{$lang->SRCommon}"; +$lang->story->lblTBC = '任務Bug用例'; + +$lang->story->checkAffection = '影響範圍'; +$lang->story->affectedProjects = $config->systemMode == 'new' ? "影響的{$lang->project->common}或{$lang->execution->common}" : "影響的{$lang->project->common}"; +$lang->story->affectedBugs = '影響的Bug'; +$lang->story->affectedCases = '影響的用例'; + +$lang->story->specTemplate = "建議參考的模板:作為一名<某種類型的用戶>,我希望<達成某些目的>,這樣可以<開發的價值>。"; +$lang->story->needNotReview = '不需要評審'; +$lang->story->successSaved = "{$lang->SRCommon}成功添加,"; +$lang->story->confirmDelete = "您確認刪除該{$lang->SRCommon}嗎?"; +$lang->story->errorEmptyChildStory = "『細分{$lang->SRCommon}』不能為空。"; +$lang->story->errorNotSubdivide = "狀態不是激活,或者階段不是未開始的{$lang->SRCommon},或者是子需求,則不能細分。"; +$lang->story->errorEmptyReviewedBy = "『由誰評審』不能為空。"; +$lang->story->mustChooseResult = '必須選擇評審意見'; +$lang->story->mustChoosePreVersion = '必須選擇回溯的版本'; +$lang->story->noStory = "暫時沒有{$lang->SRCommon}。"; +$lang->story->noRequirement = "暫時沒有{$lang->URCommon}。"; +$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 為草稿狀態或已關閉狀態,沒有修改其階段。"; +$lang->story->cannotDeleteParent = "不能刪除父{$lang->SRCommon}"; +$lang->story->moveChildrenTips = "修改父{$lang->SRCommon}的所屬產品會將其下的子{$lang->SRCommon}也移動到所選產品下。"; +$lang->story->changeTips = '該軟件需求關聯的用戶需求有變更,點擊“不變更”忽略此條變更,點擊“變更”來進行該軟件需求的變更。'; +$lang->story->estimateMustBeNumber = '估算值必須是數字'; +$lang->story->estimateMustBePlus = '估算值不能是負數'; +$lang->story->confirmChangeBranch = $lang->SRCommon . '%s已關聯在之前所屬分支的計劃中,調整分支後,' . $lang->SRCommon . '將從之前所屬分支的計劃中移除,請確認是否繼續修改上述' . $lang->SRCommon . '的分支。'; +$lang->story->confirmChangePlan = $lang->SRCommon . '%s已關聯在之前計劃的所屬分支中,調整分支後,' . $lang->SRCommon . '將會從計劃中移除,請確認是否繼續修改計劃的所屬分支。'; + +$lang->story->form = new stdclass(); +$lang->story->form->area = "該{$lang->SRCommon}所屬範圍"; +$lang->story->form->desc = "描述及標準,什麼{$lang->SRCommon}?如何驗收?"; +$lang->story->form->resource = '資源分配,有誰完成?需要多少時間?'; +$lang->story->form->file = "附件,如果該{$lang->SRCommon}有相關檔案,請點此上傳。"; + +$lang->story->action = new stdclass(); +$lang->story->action->reviewed = array('main' => '$date, 由 $actor 記錄評審意見,評審意見為 $extra。', 'extra' => 'reviewResultList'); +$lang->story->action->rejectreviewed = array('main' => '$date, 由 $actor 記錄評審意見,評審意見為 $extra,原因為 $reason。', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); +$lang->story->action->recalled = array('main' => '$date, 由 $actor 撤銷評審。'); +$lang->story->action->closed = array('main' => '$date, 由 $actor 關閉,原因為 $extra $appendLink。', 'extra' => 'reasonList'); +$lang->story->action->reviewpassed = array('main' => '$date, 由 系統 判定,結果為 確認通過。'); +$lang->story->action->reviewrejected = array('main' => '$date, 由 系統 關閉,原因為 拒絶。'); +$lang->story->action->reviewclarified = array('main' => '$date, 由 系統 判定,結果為 有待明確,請變更後重新發起評審。'); +$lang->story->action->linked2plan = array('main' => '$date, 由 $actor 關聯到計劃 $extra。'); +$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 從計劃 $extra 移除。'); +$lang->story->action->linked2execution = array('main' => '$date, 由 $actor 關聯到' . $lang->executionCommon . ' $extra。'); +$lang->story->action->unlinkedfromexecution = array('main' => '$date, 由 $actor 從' . $lang->executionCommon . ' $extra 移除。'); +$lang->story->action->linked2project = array('main' => '$date, 由 $actor 關聯到項目 $extra。'); +$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 從項目 $extra 移除。'); +$lang->story->action->linked2build = array('main' => '$date, 由 $actor 關聯到版本 $extra。'); +$lang->story->action->unlinkedfrombuild = array('main' => '$date, 由 $actor 從版本 $extra 移除。'); +$lang->story->action->linked2release = array('main' => '$date, 由 $actor 關聯到發佈 $extra。'); +$lang->story->action->unlinkedfromrelease = array('main' => '$date, 由 $actor 從發佈 $extra 移除。'); +$lang->story->action->linkrelatedstory = array('main' => "\$date, 由 \$actor 關聯相關{$lang->SRCommon} \$extra。"); +$lang->story->action->subdividestory = array('main' => "\$date, 由 \$actor 細分為{$lang->SRCommon} \$extra。"); +$lang->story->action->unlinkrelatedstory = array('main' => "\$date, 由 \$actor 移除相關{$lang->SRCommon} \$extra。"); +$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 \$actor 移除細分{$lang->SRCommon} \$extra。"); + +/* 統計報表。*/ +$lang->story->report = new stdclass(); +$lang->story->report->common = '報表'; +$lang->story->report->select = '請選擇報表類型'; +$lang->story->report->create = '生成報表'; +$lang->story->report->value = "需求數"; + +$lang->story->report->charts['storysPerProduct'] = $lang->productCommon . "{$lang->SRCommon}數量"; +$lang->story->report->charts['storysPerModule'] = "模組{$lang->SRCommon}數量"; +$lang->story->report->charts['storysPerSource'] = "按{$lang->SRCommon}來源統計"; +$lang->story->report->charts['storysPerPlan'] = "按計划進行統計"; +$lang->story->report->charts['storysPerStatus'] = '按狀態進行統計'; +$lang->story->report->charts['storysPerStage'] = '按所處階段進行統計'; +$lang->story->report->charts['storysPerPri'] = '按優先順序進行統計'; +$lang->story->report->charts['storysPerEstimate'] = "按預計{$lang->hourCommon}進行統計"; +$lang->story->report->charts['storysPerOpenedBy'] = '按由誰創建來進行統計'; +$lang->story->report->charts['storysPerAssignedTo'] = '按當前指派來進行統計'; +$lang->story->report->charts['storysPerClosedReason'] = '按關閉原因來進行統計'; +$lang->story->report->charts['storysPerChange'] = '按變更次數來進行統計'; + +$lang->story->report->options = new stdclass(); +$lang->story->report->options->graph = new stdclass(); +$lang->story->report->options->type = 'pie'; +$lang->story->report->options->width = 500; +$lang->story->report->options->height = 140; + +$lang->story->report->storysPerProduct = new stdclass(); +$lang->story->report->storysPerModule = new stdclass(); +$lang->story->report->storysPerSource = new stdclass(); +$lang->story->report->storysPerPlan = new stdclass(); +$lang->story->report->storysPerStatus = new stdclass(); +$lang->story->report->storysPerStage = new stdclass(); +$lang->story->report->storysPerPri = new stdclass(); +$lang->story->report->storysPerOpenedBy = new stdclass(); +$lang->story->report->storysPerAssignedTo = new stdclass(); +$lang->story->report->storysPerClosedReason = new stdclass(); +$lang->story->report->storysPerEstimate = new stdclass(); +$lang->story->report->storysPerChange = new stdclass(); + +$lang->story->report->storysPerProduct->item = $lang->productCommon; +$lang->story->report->storysPerModule->item = '模組'; +$lang->story->report->storysPerSource->item = '來源'; +$lang->story->report->storysPerPlan->item = '計劃'; +$lang->story->report->storysPerStatus->item = '狀態'; +$lang->story->report->storysPerStage->item = '階段'; +$lang->story->report->storysPerPri->item = '優先順序'; +$lang->story->report->storysPerOpenedBy->item = '由誰創建'; +$lang->story->report->storysPerAssignedTo->item = '指派給'; +$lang->story->report->storysPerClosedReason->item = '原因'; +$lang->story->report->storysPerEstimate->item = "預計{$lang->hourCommon}"; +$lang->story->report->storysPerChange->item = '變更次數'; + +$lang->story->report->storysPerProduct->graph = new stdclass(); +$lang->story->report->storysPerModule->graph = new stdclass(); +$lang->story->report->storysPerSource->graph = new stdclass(); +$lang->story->report->storysPerPlan->graph = new stdclass(); +$lang->story->report->storysPerStatus->graph = new stdclass(); +$lang->story->report->storysPerStage->graph = new stdclass(); +$lang->story->report->storysPerPri->graph = new stdclass(); +$lang->story->report->storysPerOpenedBy->graph = new stdclass(); +$lang->story->report->storysPerAssignedTo->graph = new stdclass(); +$lang->story->report->storysPerClosedReason->graph = new stdclass(); +$lang->story->report->storysPerEstimate->graph = new stdclass(); +$lang->story->report->storysPerChange->graph = new stdclass(); + +$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; +$lang->story->report->storysPerModule->graph->xAxisName = '模組'; +$lang->story->report->storysPerSource->graph->xAxisName = '來源'; +$lang->story->report->storysPerPlan->graph->xAxisName = '計劃'; +$lang->story->report->storysPerStatus->graph->xAxisName = '狀態'; +$lang->story->report->storysPerStage->graph->xAxisName = '所處階段'; +$lang->story->report->storysPerPri->graph->xAxisName = '優先順序'; +$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由誰創建'; +$lang->story->report->storysPerAssignedTo->graph->xAxisName = '當前指派'; +$lang->story->report->storysPerClosedReason->graph->xAxisName = '關閉原因'; +$lang->story->report->storysPerEstimate->graph->xAxisName = '預計時間'; +$lang->story->report->storysPerChange->graph->xAxisName = '變更次數'; + +$lang->story->placeholder = new stdclass(); +$lang->story->placeholder->estimate = $lang->story->hour; + +$lang->story->chosen = new stdClass(); +$lang->story->chosen->reviewedBy = '選擇評審人...'; + +$lang->story->notice = new stdClass(); +$lang->story->notice->closed = "您選擇的{$lang->SRCommon}已經被關閉了!"; +$lang->story->notice->reviewerNotEmpty = '該需求需要評審,評審人員不能為空。'; + +$lang->story->convertToTask = new stdClass(); +$lang->story->convertToTask->fieldList = array(); +$lang->story->convertToTask->fieldList['module'] = '所屬模組'; +$lang->story->convertToTask->fieldList['spec'] = "{$lang->SRCommon}描述"; +$lang->story->convertToTask->fieldList['pri'] = '優先順序'; +$lang->story->convertToTask->fieldList['mailto'] = '抄送給'; +$lang->story->convertToTask->fieldList['assignedTo'] = '指派給'; + +$lang->story->categoryList['feature'] = '功能'; +$lang->story->categoryList['interface'] = '介面'; +$lang->story->categoryList['performance'] = '性能'; +$lang->story->categoryList['safe'] = '安全'; +$lang->story->categoryList['experience'] = '體驗'; +$lang->story->categoryList['improve'] = '改進'; +$lang->story->categoryList['other'] = '其他'; diff --git a/module/story:q/model.php b/module/story:q/model.php new file mode 100644 index 0000000000..605f04d858 --- /dev/null +++ b/module/story:q/model.php @@ -0,0 +1,5309 @@ + + * @package story + * @version $Id: model.php 5145 2013-07-15 06:47:26Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +?> +dao->select('*')->from(TABLE_STORY) + ->where('id')->eq($storyID) + ->andWhere('vision')->eq($this->config->vision) + ->fetch(); + if(!$story) return false; + + if(helper::isZeroDate($story->closedDate)) $story->closedDate = ''; + if($version == 0) $version = $story->version; + $spec = $this->dao->select('title,spec,verify')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWhere('version')->eq($version)->fetch(); + $story->title = isset($spec->title) ? $spec->title : ''; + $story->spec = isset($spec->spec) ? $spec->spec : ''; + $story->verify = isset($spec->verify) ? $spec->verify : ''; + if(!empty($story->fromStory)) $story->sourceName = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($story->fromStory)->fetch('title'); + + /* Check parent story. */ + if($story->parent > 0) $story->parentName = $this->dao->findById($story->parent)->from(TABLE_STORY)->fetch('title'); + + $story = $this->loadModel('file')->replaceImgURL($story, 'spec,verify'); + if($setImgSize) $story->spec = $this->file->setImgSize($story->spec); + if($setImgSize) $story->verify = $this->file->setImgSize($story->verify); + + $story->executions = $this->dao->select('t1.project, t2.name, t2.status, t2.type')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project = t2.id') + ->where('t1.story')->eq($storyID) + ->andWhere('t2.type')->in('sprint,stage,kanban') + ->orderBy('t1.`order` DESC') + ->fetchAll('project'); + $story->tasks = $this->dao->select('id, name, assignedTo, execution, status, consumed, `left`,type')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->orderBy('id DESC')->fetchGroup('execution'); + $story->stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchPairs('branch', 'stage'); + //$story->bugCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_BUG)->where('story')->eq($storyID)->fetch('count'); + //$story->caseCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_CASE)->where('story')->eq($storyID)->fetch('count'); + if($story->toBug) $story->toBugTitle = $this->dao->findById($story->toBug)->from(TABLE_BUG)->fetch('title'); + if($story->plan) + { + $plans = $this->dao->select('id,title,branch')->from(TABLE_PRODUCTPLAN)->where('id')->in($story->plan)->fetchAll('id'); + foreach($plans as $planID => $plan) + { + $story->planTitle[$planID] = $plan->title; + if($plan->branch and !isset($story->stages[$plan->branch]) and empty($story->branch)) $story->stages[$plan->branch] = 'planned'; + } + } + $extraStories = array(); + if($story->duplicateStory) $extraStories = array($story->duplicateStory); + if($story->linkStories) $extraStories = explode(',', $story->linkStories); + if($story->childStories) $extraStories = array_merge($extraStories, explode(',', $story->childStories)); + $extraStories = array_unique($extraStories); + if(!empty($extraStories)) $story->extraStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($extraStories)->fetchPairs(); + + $story->children = array(); + if($story->parent == '-1') $story->children = $this->dao->select('*')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll('id'); + + return $story; + } + + /** + * Get stories by idList. + * + * @param int|array|string $storyIdList + * @param string $type requirement|story + * @access public + * @return array + */ + public function getByList($storyIdList = 0, $type = 'story') + { + return $this->dao->select('t1.*, t2.spec, t2.verify, t3.name as productTitle') + ->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product=t3.id') + ->where('t1.deleted')->eq(0) + ->andWhere('t1.version=t2.version') + ->beginIF($storyIdList)->andWhere('t1.id')->in($storyIdList)->fi() + ->beginIF(!$storyIdList)->andWhere('t1.type')->eq($type)->fi() + ->fetchAll('id'); + } + + /** + * Get test stories. + * + * @param array $storyIdList + * @param int $executionID + * @access public + * @return array + */ + public function getTestStories($storyIdList, $executionID) + { + return $this->dao->select('story')->from(TABLE_TASK)->where('execution')->eq($executionID)->andWhere('type')->eq('test')->andWhere('story')->in($storyIdList)->andWhere('deleted')->eq(0)->fetchPairs('story', 'story'); + } + + /** + * Get story specs. + * + * @param array $storyIdList + * @access public + * @return array + */ + public function getStorySpecs($storyIdList) + { + return $this->dao->select('story,spec,verify')->from(TABLE_STORYSPEC) + ->where('story')->in($storyIdList) + ->orderBy('version') + ->fetchAll('story'); + } + + /** + * Get affected things. + * + * @param object $story + * @access public + * @return object + */ + public function getAffectedScope($story) + { + /* Remove closed executions. */ + if($story->executions) + { + foreach($story->executions as $executionID => $execution) if($execution->status == 'done') unset($story->executions[$executionID]); + } + + /* Get team members. */ + if($story->executions) + { + $story->teams = $this->dao->select('account, root') + ->from(TABLE_TEAM) + ->where('root')->in(array_keys($story->executions)) + ->andWhere('type')->eq('project') + ->fetchGroup('root'); + } + + /* Get affected bugs. */ + $story->bugs = $this->dao->findByStory($story->id)->from(TABLE_BUG) + ->andWhere('status')->ne('closed') + ->andWhere('deleted')->eq(0) + ->orderBy('id desc')->fetchAll(); + + /* Get affected cases. */ + $story->cases = $this->dao->findByStory($story->id)->from(TABLE_CASE)->andWhere('deleted')->eq(0)->fetchAll(); + + return $story; + } + + /** + * Get requirements for story. + * + * @param int $productID + * @access public + * @return void + */ + public function getRequirements($productID) + { + return $this->dao->select('id,title')->from(TABLE_STORY) + ->where('deleted')->eq(0) + ->andWhere('product')->eq($productID) + ->andWhere('type')->eq('requirement') + ->andWhere('status')->notIN('draft,closed') + ->fetchPairs(); + } + + /** + * Create a story. + * + * @param int $executionID + * @param int $bugID + * @param string $from + * @param string $extra + * @access public + * @return int|bool the id of the created story or false when error. + */ + public function create($executionID = 0, $bugID = 0, $from = '', $extra = '') + { + $extra = str_replace(array(',', ' '), array('&', ''), $extra); + parse_str($extra, $output); + + if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']); + if(!$this->post->needNotReview and empty($_POST['reviewer'])) + { + dao::$errors[] = $this->lang->story->errorEmptyReviewedBy; + return false; + } + + $now = helper::now(); + $story = fixer::input('post') + ->cleanInt('product,module,pri,plan') + ->callFunc('title', 'trim') + ->add('version', 1) + ->add('status', 'draft') + ->setDefault('plan,verify,notifyEmail', '') + ->setDefault('openedBy', $this->app->user->account) + ->setDefault('openedDate', $now) + ->setIF($this->post->needNotReview, 'status', 'active') + ->setIF($this->post->plan > 0, 'stage', 'planned') + ->setIF($this->post->estimate, 'estimate', (float)$this->post->estimate) + ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'feedbackBy', '') + ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'notifyEmail', '') + ->setIF($executionID > 0, 'stage', 'projected') + ->setIF($bugID > 0, 'fromBug', $bugID) + ->join('assignedTo', '') + ->join('mailto', ',') + ->stripTags($this->config->story->editor->create['id'], $this->config->allowedTags) + ->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane') + ->get(); + + /* Check repeat story. */ + $result = $this->loadModel('common')->removeDuplicate('story', $story, "product={$story->product}"); + if(isset($result['stop']) and $result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']); + + if($this->checkForceReview()) $story->status = 'draft'; + if($story->status == 'draft') $story->stage = $this->post->plan > 0 ? 'planned' : 'wait'; + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->create['id'], $this->post->uid); + + $requiredFields = "," . $this->config->story->create->requiredFields . ","; + + if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields); + if(strpos($requiredFields, ',estimate,') !== false) + { + if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate); + $requiredFields = str_replace(',estimate,', ',', $requiredFields); + } + + $requiredFields = trim($requiredFields, ','); + + $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify') + ->autoCheck() + ->checkIF($story->notifyEmail, 'notifyEmail', 'email') + ->batchCheck($requiredFields, 'notempty') + ->checkFlow() + ->exec(); + + if(!dao::isError()) + { + $storyID = $this->dao->lastInsertID(); + + /* Fix bug #21992, user story have no parent story. */ + if(isset($story->parent) and $story->parent) + { + $stories = array($storyID); + $this->subdivide($story->parent, $stories); + } + + $this->file->updateObjectID($this->post->uid, $storyID, $story->type); + $this->file->saveUpload($story->type, $storyID, $extra = 1); + + if(!empty($story->plan)) $this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan. + + $data = new stdclass(); + $data->story = $storyID; + $data->version = 1; + $data->title = $story->title; + $data->spec = $story->spec; + $data->verify = $story->verify; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + + /* Save the story reviewer to storyreview table. */ + if(isset($_POST['reviewer'])) + { + $assignedTo = ''; + foreach($this->post->reviewer as $reviewer) + { + if(empty($reviewer)) continue; + + $reviewData = new stdclass(); + $reviewData->story = $storyID; + $reviewData->version = 1; + $reviewData->reviewer = $reviewer; + $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); + + if(empty($assignedTo)) $assignedTo = $reviewer; + } + if($assignedTo) $this->dao->update(TABLE_STORY)->set('assignedTo')->eq($assignedTo)->set('assignedDate')->eq(helper::now())->where('id')->eq($storyID)->exec(); + } + + /* Project or execution linked story. */ + if($executionID != 0) + { + $this->linkStory($executionID, $this->post->product, $storyID); + if($this->config->systemMode == 'new' and $executionID != $this->session->project) $this->linkStory($this->session->project, $this->post->product, $storyID); + + $this->loadModel('kanban'); + + $laneID = isset($output['laneID']) ? $output['laneID'] : 0; + if(isset($_POST['lane'])) $laneID = $_POST['lane']; + + $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog'); + if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0; + + if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID); + if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'story'); + } + + if(is_array($this->post->URS)) + { + foreach($this->post->URS as $URID) + { + $requirement = $this->getByID($URID); + $data = new stdclass(); + $data->product = $story->product; + $data->AType = 'requirement'; + $data->relation = 'subdivideinto'; + $data->BType = 'story'; + $data->AID = $URID; + $data->BID = $storyID; + $data->AVersion = $requirement->version; + $data->BVersion = 1; + $data->extra = 1; + + $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); + + $data->AType = 'story'; + $data->relation = 'subdividedfrom'; + $data->BType = 'requirement'; + $data->AID = $storyID; + $data->BID = $URID; + $data->AVersion = 1; + $data->BVersion = $requirement->version; + + $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); + } + } + + if($bugID > 0) + { + $bug = new stdclass(); + $bug->toStory = $storyID; + $bug->status = 'closed'; + $bug->resolution = 'tostory'; + $bug->resolvedBy = $this->app->user->account; + $bug->resolvedDate = $now; + $bug->closedBy = $this->app->user->account; + $bug->closedDate = $now; + $bug->assignedTo = 'closed'; + $bug->assignedDate = $now; + $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec(); + + $this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID); + $this->action->create('bug', $bugID, 'Closed'); + + /* add files to story from bug. */ + $files = $this->dao->select('*')->from(TABLE_FILE) + ->where('objectType')->eq('bug') + ->andWhere('objectID')->eq($bugID) + ->fetchAll(); + if(!empty($files)) + { + foreach($files as $file) + { + $file->objectType = 'story'; + $file->objectID = $storyID; + unset($file->id); + $this->dao->insert(TABLE_FILE)->data($file)->exec(); + } + } + } + $this->setStage($storyID); + if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID); + + /* Callback the callable method to process the related data for object that is transfered to story. */ + if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID); + + return array('status' => 'created', 'id' => $storyID); + } + return false; + } + + /** + * Create story from gitlab issue. + * + * @param object $story + * @param int $executionID + * @access public + * @return int + */ + public function createStoryFromGitlabIssue($story, $executionID) + { + $story->status = 'active'; + $story->stage = 'projected'; + $story->openedBy = $this->app->user->account; + $story->version = 1; + $story->pri = 3; + $story->assignedDate = isset($story->assignedTo) ? helper::now() : 0; + + if(isset($story->execution)) unset($story->execution); + + $requiredFields = $this->config->story->create->requiredFields; + $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify,gitlab,gitlabProject')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); + if(!dao::isError()) + { + $storyID = $this->dao->lastInsertID(); + + $data = new stdclass(); + $data->story = $storyID; + $data->version = 1; + $data->title = $story->title; + $data->spec = $story->spec; + $data->verify = $story->spec; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + + /* Link story to execution. */ + $this->linkStory($executionID, $story->product, $storyID); + + return $storyID; + } + + return false; + } + + /** + * Batch create stories. + * + * @access public + * @return int|bool the id of the created story or false when error. + * @return type requirement|story + */ + public function batchCreate($productID = 0, $branch = 0, $type = 'story') + { + $forceReview = $this->checkForceReview(); + + $reviewers = ''; + foreach($_POST['title'] as $index => $value) + { + if($_POST['title'][$index] and isset($_POST['reviewer'][$index]) and empty($_POST['reviewDitto'][$index])) $reviewers = $_POST['reviewer'][$index] = array_filter($_POST['reviewer'][$index]); + if($_POST['title'][$index] and isset($_POST['reviewer'][$index]) and !empty($_POST['reviewDitto'][$index])) $_POST['reviewer'][$index] = $reviewers; + if($_POST['title'][$index] and empty($_POST['reviewer'][$index]) and $forceReview) + { + dao::$errors[] = $this->lang->story->errorEmptyReviewedBy; + return false; + } + } + + $this->loadModel('action'); + $branch = (int)$branch; + $productID = (int)$productID; + $now = helper::now(); + $mails = array(); + $stories = fixer::input('post')->get(); + + $result = $this->loadModel('common')->removeDuplicate('story', $stories, "product={$productID}"); + $stories = $result['data']; + + $module = 0; + $plan = ''; + $pri = 0; + $source = ''; + + foreach($stories->title as $i => $title) + { + $module = $stories->module[$i] == 'ditto' ? $module : $stories->module[$i]; + $plan = isset($stories->plan[$i]) ? ($stories->plan[$i] == 'ditto' ? $plan : $stories->plan[$i]) : ''; + $pri = $stories->pri[$i] == 'ditto' ? $pri : $stories->pri[$i]; + $source = $stories->source[$i] == 'ditto' ? $source : $stories->source[$i]; + $stories->module[$i] = (int)$module; + $stories->plan[$i] = $plan; + $stories->pri[$i] = (int)$pri; + $stories->source[$i] = $source; + } + + if(isset($stories->uploadImage)) $this->loadModel('file'); + + $extendFields = $this->getFlowExtendFields(); + $data = array(); + foreach($stories->title as $i => $title) + { + if(empty($title)) continue; + + if(empty($stories->reviewer[$i]) and empty($stories->reviewerDitto[$i])) $stories->reviewer[$i] = array(); + + $story = new stdclass(); + $story->type = $type; + $story->branch = isset($stories->branch[$i]) ? $stories->branch[$i] : 0; + $story->module = $stories->module[$i]; + $story->plan = $stories->plan[$i]; + $story->color = $stories->color[$i]; + $story->title = $stories->title[$i]; + $story->source = $stories->source[$i]; + $story->category = $stories->category[$i]; + $story->pri = $stories->pri[$i]; + $story->estimate = $stories->estimate[$i]; + $story->status = (empty($stories->reviewer[$i]) and !$forceReview) ? 'active' : 'draft'; + $story->stage = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? 'projected' : 'wait'; + $story->keywords = $stories->keywords[$i]; + $story->sourceNote = $stories->sourceNote[$i]; + $story->product = $productID; + $story->openedBy = $this->app->user->account; + $story->vision = $this->config->vision; + $story->openedDate = $now; + $story->version = 1; + + foreach($extendFields as $extendField) + { + $story->{$extendField->field} = $this->post->{$extendField->field}[$i]; + if(is_array($story->{$extendField->field})) $story->{$extendField->field} = join(',', $story->{$extendField->field}); + + $story->{$extendField->field} = htmlSpecialString($story->{$extendField->field}); + } + + foreach(explode(',', $this->config->story->create->requiredFields) as $field) + { + $field = trim($field); + if(empty($field)) continue; + if($type == 'requirement' and $field == 'plan') continue; + + if(!isset($story->$field)) continue; + if(!empty($story->$field)) continue; + if($field == 'estimate' and strlen(trim($story->estimate)) != 0) continue; + + dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->story->$field); + return false; + } + $data[$i] = $story; + } + + $planStories = array(); + + foreach($data as $i => $story) + { + $this->dao->insert(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->exec(); + if(dao::isError()) + { + echo js::error(dao::getError()); + return print(js::reload('parent')); + } + + $storyID = $this->dao->lastInsertID(); + $this->setStage($storyID); + + /* Update product plan stories order. */ + if($story->plan) $this->updateStoryOrderOfPlan($storyID, $story->plan); + + $specData = new stdclass(); + $specData->story = $storyID; + $specData->version = 1; + $specData->title = $stories->title[$i]; + $specData->spec = ''; + $specData->verify = ''; + if(!empty($stories->spec[$i])) $specData->spec = nl2br($stories->spec[$i]); + if(!empty($stories->verify[$i]))$specData->verify = nl2br($stories->verify[$i]); + + if(!empty($stories->uploadImage[$i])) + { + $fileName = $stories->uploadImage[$i]; + $file = $this->session->storyImagesFile[$fileName]; + + $realPath = $file['realpath']; + unset($file['realpath']); + + if(!is_dir($this->file->savePath)) mkdir($this->file->savePath, 0777, true); + if($realPath and rename($realPath, $this->file->savePath . $this->file->getSaveName($file['pathname']))) + { + $file['addedBy'] = $this->app->user->account; + $file['addedDate'] = $now; + $file['objectType'] = 'story'; + $file['objectID'] = $storyID; + if(in_array($file['extension'], $this->config->file->imageExtensions)) + { + $file['extra'] = 'editor'; + $this->dao->insert(TABLE_FILE)->data($file)->exec(); + + $fileID = $this->dao->lastInsertID(); + $specData->spec .= ''; + } + else + { + $this->dao->insert(TABLE_FILE)->data($file)->exec(); + } + } + } + + $this->dao->insert(TABLE_STORYSPEC)->data($specData)->exec(); + + /* Save the story reviewer to storyreview table. */ + $assignedTo = ''; + foreach($_POST['reviewer'][$i] as $reviewer) + { + if(empty($reviewer)) continue; + + $reviewData = new stdclass(); + $reviewData->story = $storyID; + $reviewData->version = 1; + $reviewData->reviewer = $reviewer; + $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); + + if(empty($assignedTo)) $assignedTo = $reviewer; + } + if($assignedTo) $this->dao->update(TABLE_STORY)->set('assignedTo')->eq($assignedTo)->set('assignedDate')->eq($now)->where('id')->eq($storyID)->exec(); + + $this->executeHooks($storyID); + + $actionID = $this->action->create('story', $storyID, 'Opened', ''); + if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID); + $mails[$i] = new stdclass(); + $mails[$i]->storyID = $storyID; + $mails[$i]->actionID = $actionID; + } + + /* Remove upload image file and session. */ + if(!empty($stories->uploadImage) and $this->session->storyImagesFile) + { + $classFile = $this->app->loadClass('zfile'); + $file = current($_SESSION['storyImagesFile']); + $realPath = dirname($file['realpath']); + if(is_dir($realPath)) $classFile->removeDir($realPath); + unset($_SESSION['storyImagesFile']); + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchCreate'); + return $mails; + } + + /** + * Change a story. + * + * @param int $storyID + * @access public + * @return array the change of the story. + */ + public function change($storyID) + { + $specChanged = false; + $oldStory = $this->dao->findById((int)$storyID)->from(TABLE_STORY)->fetch(); + $oldSpec = $this->dao->select('title,spec,verify')->from(TABLE_STORYSPEC)->where('story')->eq((int)$storyID)->andWhere('version')->eq($oldStory->version)->fetch(); + $oldStory->title = isset($oldSpec->title) ? $oldSpec->title : ''; + $oldStory->spec = isset($oldSpec->spec) ? $oldSpec->spec : ''; + $oldStory->verify = isset($oldSpec->verify) ? $oldSpec->verify : ''; + + if(!empty($_POST['lastEditedDate']) and $oldStory->lastEditedDate != $this->post->lastEditedDate) + { + dao::$errors[] = $this->lang->error->editedByOther; + return false; + } + + if(strpos($this->config->story->change->requiredFields, 'comment') !== false and !$this->post->comment) + { + dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->comment); + return false; + } + + if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']); + if(!$this->post->needNotReview and empty($_POST['reviewer'])) + { + dao::$errors[] = $this->lang->story->errorEmptyReviewedBy; + return false; + } + + $story = fixer::input('post')->stripTags($this->config->story->editor->change['id'], $this->config->allowedTags)->get(); + + $oldStoryReviewers = $this->getReviewerPairs($storyID, $oldStory->version); + $_POST['reviewer'] = isset($_POST['reviewer']) ? $_POST['reviewer'] : array(); + $reviewerHasChanged = (array_diff(array_keys($oldStoryReviewers), $_POST['reviewer']) or array_diff($_POST['reviewer'], array_keys($oldStoryReviewers))); + if($story->spec != $oldStory->spec or $story->verify != $oldStory->verify or $story->title != $oldStory->title or $this->loadModel('file')->getCount() or $reviewerHasChanged) $specChanged = true; + + $now = helper::now(); + $story = fixer::input('post') + ->callFunc('title', 'trim') + ->setDefault('lastEditedBy', $this->app->user->account) + ->add('id', $storyID) + ->add('lastEditedDate', $now) + ->setIF(!$this->post->assignedTo, 'assignedTo', '') + ->setIF($specChanged, 'version', $oldStory->version + 1) + ->setIF($specChanged and $oldStory->status == 'active' and $this->post->needNotReview == false, 'status', 'changed') + ->setIF($oldStory->status == 'draft' and $this->post->needNotReview, 'status', 'active') + ->setIF($specChanged, 'reviewedBy', '') + ->setIF($specChanged, 'changedBy', $this->app->user->account) + ->setIF($specChanged, 'changedDate', $now) + ->setIF($specChanged, 'closedBy', '') + ->setIF($specChanged, 'closedReason', '') + ->setIF($specChanged and $oldStory->reviewedBy, 'reviewedDate', '0000-00-00') + ->setIF($specChanged and $oldStory->closedBy, 'closedDate', '0000-00-00') + ->stripTags($this->config->story->editor->change['id'], $this->config->allowedTags) + ->remove('files,labels,reviewer,comment,needNotReview,uid') + ->get(); + if($specChanged and isset($story->status) && $story->status == 'active' and $this->checkForceReview()) $story->status = 'changed'; + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->change['id'], $this->post->uid); + $this->dao->update(TABLE_STORY)->data($story, 'spec,verify') + ->autoCheck() + ->batchCheck($this->config->story->change->requiredFields, 'notempty') + ->checkFlow() + ->where('id')->eq((int)$storyID)->exec(); + + if(!dao::isError()) + { + if($specChanged) + { + $data = new stdclass(); + $data->story = $storyID; + $data->version = $oldStory->version + 1; + $data->title = $story->title; + $data->spec = $story->spec; + $data->verify = $story->verify; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + + $story = $this->getById($storyID); + /* IF is story and has changed, update its relation version to new. */ + if($oldStory->type == 'story') + { + $this->dao->update(TABLE_STORY)->set('URChanged')->eq(0)->where('id')->eq($oldStory->id)->exec(); + $this->updateStoryVersion($story); + } + else + { + /* IF is requirement changed, notify its relation. */ + $relations = $this->dao->select('BID')->from(TABLE_RELATION) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere('AID')->eq($story->id) + ->fetchPairs(); + + foreach($relations as $relationID) $this->dao->update(TABLE_STORY)->set('URChanged')->eq(1)->where('id')->eq($relationID)->exec(); + } + + /* Update the reviewer. */ + $assignedTo = ''; + foreach($_POST['reviewer'] as $reviewer) + { + $reviewData = new stdclass(); + $reviewData->story = $storyID; + $reviewData->version = $story->version; + $reviewData->reviewer = $reviewer; + $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); + + if(empty($assignedTo)) $assignedTo = $reviewer; + } + if($assignedTo and $assignedTo != $oldStory->assignedTo) $this->dao->update(TABLE_STORY)->set('assignedTo')->eq($assignedTo)->set('assignedDate')->eq(helper::now())->where('id')->eq($storyID)->exec(); + } + + $this->file->updateObjectID($this->post->uid, $storyID, 'story'); + + $oldStory->reviewers = implode(',', array_keys($oldStoryReviewers)); + $story->reviewers = implode(',', $_POST['reviewer']); + return common::createChanges($oldStory, $story); + } + } + + /** + * Update a story. + * + * @param int $storyID + * @access public + * @return array the changes of the story. + */ + public function update($storyID) + { + $now = helper::now(); + $oldStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($storyID)->fetch(); + if(!empty($_POST['lastEditedDate']) and $oldStory->lastEditedDate != $this->post->lastEditedDate) + { + dao::$errors[] = $this->lang->error->editedByOther; + return false; + } + + $story = fixer::input('post') + ->cleanInt('product,module,pri,duplicateStory') + ->cleanFloat('estimate') + ->setDefault('assignedDate', $oldStory->assignedDate) + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('reviewedBy', '') + ->setDefault('mailto', '') + ->add('id', $storyID) + ->add('lastEditedDate', $now) + ->setDefault('plan,notifyEmail', '') + ->setDefault('status', $oldStory->status) + ->setDefault('product', $oldStory->product) + ->setDefault('branch', $oldStory->branch) + ->setIF(!$this->post->linkStories, 'linkStories', '') + ->setIF($this->post->assignedTo != $oldStory->assignedTo, 'assignedDate', $now) + ->setIF($this->post->closedBy != false and $oldStory->closedDate == '', 'closedDate', $now) + ->setIF($this->post->closedReason != false and $oldStory->closedDate == '', 'closedDate', $now) + ->setIF($this->post->closedBy != false or $this->post->closedReason != false, 'status', 'closed') + ->setIF($this->post->closedReason != false and $this->post->closedBy == false, 'closedBy', $this->app->user->account) + ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'feedbackBy', '') + ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'notifyEmail', '') + ->setIF(!empty($_POST['plan'][0]) and $oldStory->stage == 'wait', 'stage', 'planned') + ->stripTags($this->config->story->editor->edit['id'], $this->config->allowedTags) + ->join('reviewedBy', ',') + ->join('mailto', ',') + ->join('linkStories', ',') + ->join('childStories', ',') + ->remove('files,labels,comment,contactListMenu,stages,reviewer') + ->get(); + + if(isset($story->plan) and is_array($story->plan)) $story->plan = trim(join(',', $story->plan), ','); + if(isset($_POST['branch']) and $_POST['branch'] == 0) $story->branch = 0; + if(!empty($_POST['stages'])) + { + $oldStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchAll('branch'); + $this->dao->delete()->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->exec(); + + $stageList = join(',', array_keys($this->lang->story->stageList)); + $minStagePos = strlen($stageList); + $minStage = ''; + foreach($this->post->stages as $branch => $stage) + { + $newStage = new stdclass(); + $newStage->story = $storyID; + $newStage->branch = $branch; + $newStage->stage = $stage; + if(isset($oldStages[$branch])) + { + $oldStage = $oldStages[$branch]; + $newStage->stagedBy = $oldStage->stagedBy; + if($stage != $oldStage->stage) $newStage->stagedBy = (strpos('tested|verified|released|closed', $stage) !== false) ? $this->app->user->account : ''; + } + if($story->branch == 0) $this->dao->insert(TABLE_STORYSTAGE)->data($newStage)->exec(); + if(strpos($stageList, $stage) !== false and strpos($stageList, $stage) < $minStagePos) + { + $minStage = $stage; + $minStagePos = strpos($stageList, $stage); + } + } + $story->stage = $minStage; + } + + if(isset($story->stage) and $oldStory->stage != $story->stage) $story->stagedBy = (strpos('tested|verified|released|closed', $story->stage) !== false) ? $this->app->user->account : ''; + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->edit['id'], $this->post->uid); + + if(isset($_POST['reviewer'])) + { + $_POST['reviewer'] = array_filter($_POST['reviewer']); + $oldReviewer = $this->getReviewerPairs($storyID, $oldStory->version); + if(array_diff($_POST['reviewer'], array_keys($oldReviewer)) or array_diff(array_keys($oldReviewer), $_POST['reviewer'])) + { + /* Update story reviewer. */ + $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))->exec(); + foreach($_POST['reviewer'] as $reviewer) + { + if(in_array($reviewer, array_keys($oldReviewer))) continue; + + $reviewData = new stdclass(); + $reviewData->story = $storyID; + $reviewData->version = $oldStory->version; + $reviewData->reviewer = $reviewer; + $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); + } + + $story->reviewedBy = $oldStory->reviewedBy; + $story = $this->updateStoryByReview($storyID, $oldStory, $story); + } + + $oldStory->reviewers = implode(',', array_keys($oldReviewer)); + $story->reviewers = implode(',', array_keys($this->getReviewerPairs($storyID, $oldStory->version))); + } + + $this->dao->update(TABLE_STORY) + ->data($story, 'reviewers') + ->autoCheck() + ->checkIF(isset($story->closedBy), 'closedReason', 'notempty') + ->checkIF(isset($story->closedReason) and $story->closedReason == 'done', 'stage', 'notempty') + ->checkIF(isset($story->closedReason) and $story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkIF($story->notifyEmail, 'notifyEmail', 'email') + ->checkFlow() + ->where('id')->eq((int)$storyID)->exec(); + + if(!dao::isError()) + { + if($story->product != $oldStory->product) + { + $this->updateStoryProduct($storyID, $story->product); + if($oldStory->parent == '-1') + { + $childStories = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchPairs('id'); + foreach($childStories as $childStoryID) $this->updateStoryProduct($childStoryID, $story->product); + } + } + + $this->loadModel('action'); + $changed = $story->parent != $oldStory->parent; + if($oldStory->parent > 0) + { + $oldParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($oldStory->parent)->fetch(); + $this->updateParentStatus($storyID, $oldStory->parent, !$changed); + + if($changed) + { + $oldChildren = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($oldStory->parent)->andWhere('deleted')->eq(0)->fetchPairs('id', 'id'); + if(empty($oldChildren)) $this->dao->update(TABLE_STORY)->set('parent')->eq(0)->where('id')->eq($oldStory->parent)->exec(); + $this->dao->update(TABLE_STORY)->set('childStories')->eq(join(',', $oldChildren))->set('lastEditedBy')->eq($this->app->user->account)->set('lastEditedDate')->eq(helper::now())->where('id')->eq($oldStory->parent)->exec(); + $this->action->create('story', $storyID, 'unlinkParentStory', '', $oldStory->parent, '', false); + + $actionID = $this->action->create('story', $oldStory->parent, 'unLinkChildrenStory', '', $storyID, '', false); + + $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($oldStory->parent)->fetch(); + $changes = common::createChanges($oldParentStory, $newParentStory); + if(!empty($changes)) $this->action->logHistory($actionID, $changes); + } + } + + if($story->parent > 0) + { + $parentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($story->parent)->fetch(); + $this->dao->update(TABLE_STORY)->set('parent')->eq(-1)->where('id')->eq($story->parent)->exec(); + $this->updateParentStatus($storyID, $story->parent, !$changed); + + if($changed) + { + $children = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($story->parent)->andWhere('deleted')->eq(0)->fetchPairs('id', 'id'); + $this->dao->update(TABLE_STORY) + ->set('parent')->eq('-1') + ->set('childStories')->eq(join(',', $children)) + ->set('lastEditedBy')->eq($this->app->user->account) + ->set('lastEditedDate')->eq(helper::now()) + ->where('id')->eq($story->parent) + ->exec(); + + $this->action->create('story', $storyID, 'linkParentStory', '', $story->parent, '', false); + $actionID = $this->action->create('story', $story->parent, 'linkChildStory', '', $storyID, '', false); + + $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($story->parent)->fetch(); + $changes = common::createChanges($parentStory, $newParentStory); + if(!empty($changes)) $this->action->logHistory($actionID, $changes); + } + } + + if(isset($story->closedReason) and $story->closedReason == 'done') $this->loadModel('score')->create('story', 'close'); + + /* Set new stage and update story sort of plan when story plan has changed. */ + if($oldStory->plan != $story->plan) + { + $this->updateStoryOrderOfPlan($storyID, $story->plan, $oldStory->plan); // Insert a new story sort in this plan. + + if(empty($oldStory->plan) or empty($story->plan)) $this->setStage($storyID); // Set new stage for this story. + } + + if($oldStory->stage != $story->stage) + { + $executionIdList = $this->dao->select('t1.project')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t1.story')->eq($storyID) + ->andWhere('t2.deleted')->eq(0) + ->andWhere('t2.type')->in('sprint,stage,kanban') + ->fetchPairs(); + + $this->loadModel('kanban'); + foreach($executionIdList as $executionID) $this->kanban->updateLane($executionID, 'story', $storyID); + } + + unset($oldStory->parent); + unset($story->parent); + return common::createChanges($oldStory, $story); + } + } + + /** + * Update story product. + * + * @param int $storyID + * @param int $productID + * @access public + * @return void + */ + public function updateStoryProduct($storyID, $productID) + { + $this->dao->update(TABLE_STORY)->set('product')->eq($productID)->where('id')->eq($storyID)->exec(); + $this->dao->update(TABLE_PROJECTSTORY)->set('product')->eq($productID)->where('story')->eq($storyID)->exec(); + $storyProjects = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($storyID)->orderBy('project')->fetchPairs('project', 'project'); + $linkedProjects = $this->dao->select('project')->from(TABLE_PROJECTPRODUCT)->where('project')->in($storyProjects)->andWhere('product')->eq($productID)->orderBy('project')->fetchPairs('project','project'); + $unlinkedProjects = array_diff($storyProjects, $linkedProjects); + foreach($unlinkedProjects as $projectID) + { + $data = new stdclass(); + $data->project = $projectID; + $data->product = $productID; + $this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec(); + } + } + + /** + * Update parent status. + * + * @param int $storyID + * @param int $parentID + * @param bool $createAction + * @access public + * @return mixed + */ + public function updateParentStatus($storyID, $parentID = 0, $createAction = true) + { + $childStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($storyID)->fetch(); + if(empty($parentID)) $parentID = $childStory->parent; + if($parentID <= 0) return true; + + $oldParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->andWhere('deleted')->eq(0)->fetch(); + if(empty($oldParentStory)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($storyID)->exec(); + if($oldParentStory->parent != '-1') $this->dao->update(TABLE_STORY)->set('parent')->eq('-1')->where('id')->eq($parentID)->exec(); + $this->computeEstimate($parentID); + + $childrenStatus = $this->dao->select('id,status')->from(TABLE_STORY)->where('parent')->eq($parentID)->andWhere('deleted')->eq(0)->fetchPairs('status', 'status'); + if(empty($childrenStatus)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($parentID)->exec(); + + $status = $oldParentStory->status; + if(count($childrenStatus) == 1 and $oldParentStory->status != 'changed') + { + $status = current($childrenStatus); + if($status == 'draft' or $status == 'changed') $status = 'active'; + } + elseif(count($childrenStatus) != 1 and $oldParentStory->status == 'closed') + { + $status = 'active'; + } + + if($status and $oldParentStory->status != $status) + { + $now = helper::now(); + $story = new stdclass(); + $story->status = $status; + $story->stage = 'wait'; + if($status == 'active') + { + $story->assignedTo = $oldParentStory->openedBy; + $story->assignedDate = $now; + $story->closedBy = ''; + $story->closedReason = ''; + $story->closedDate = '0000-00-00'; + $story->reviewedBy = ''; + $story->reviewedDate = '0000-00-00'; + } + + if($status == 'closed') + { + $story->assignedTo = 'closed'; + $story->assignedDate = $now; + $story->closedBy = $this->app->user->account; + $story->closedDate = $now; + $story->closedReason = 'done'; + $story->closedReason = 'done'; + } + + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->parent = '-1'; + $this->dao->update(TABLE_STORY)->data($story)->where('id')->eq($parentID)->exec(); + if(!dao::isError()) + { + if(!$createAction) return $story; + + $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->fetch(); + $changes = common::createChanges($oldParentStory, $newParentStory); + $action = ''; + if($status == 'active') $action = 'Activated'; + if($status == 'closed') $action = 'Closed'; + if($action) + { + $actionID = $this->loadModel('action')->create('story', $parentID, $action, '', '', '', false); + $this->action->logHistory($actionID, $changes); + } + } + } + else + { + if(!dao::isError()) + { + $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->fetch(); + $changes = common::createChanges($oldParentStory, $newParentStory); + if($changes) + { + $actionID = $this->loadModel('action')->create('story', $parentID, 'Edited', '', '', '', false); + $this->action->logHistory($actionID, $changes); + } + } + } + } + + /** + * If story changed, update relation table version filed. + * + * @param object $story + * @access public + * @return void + */ + public function updateStoryVersion($story) + { + $changedStories = $this->getChangedStories($story); + + if(!empty($changedStories)) + { + foreach($changedStories as $changedStory) + { + $this->dao->update(TABLE_RELATION) + ->set('AVersion')->eq($changedStory->version) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere('AID')->eq($changedStory->id) + ->andWhere('BID')->eq($story->id) + ->exec(); + } + } + } + + /** + * Update the story order of plan. + * + * @param int $storyID + * @param string $oldPlanIDList + * @param string $planIDList + * @access public + * @return void + */ + public function updateStoryOrderOfPlan($storyID, $planIDList = '', $oldPlanIDList = '') + { + $planIDList = $planIDList ? explode(',', $planIDList) : array(); + $oldPlanIDList = $oldPlanIDList ? explode(',', $oldPlanIDList) : array(); + + /* Get the ids to be inserted and deleted by comparing plan ids. */ + $plansTobeInsert = array_diff($planIDList, $oldPlanIDList); + $plansTobeDelete = array_diff($oldPlanIDList, $planIDList); + + /* Delete old story sort of plan. */ + if(!empty($plansTobeDelete)) $this->dao->delete()->from(TABLE_PLANSTORY)->where('story')->eq($storyID)->andWhere('plan')->in($plansTobeDelete)->exec(); + + if(!empty($plansTobeInsert)) + { + /* Get last story order of plan list. */ + $maxOrders = $this->dao->select('plan, max(`order`) as `order`')->from(TABLE_PLANSTORY)->where('plan')->in($plansTobeInsert)->groupBy('plan')->fetchPairs(); + + foreach($plansTobeInsert as $planID) + { + /* Set story order in new plan. */ + $data = new stdClass(); + $data->plan = $planID; + $data->story = $storyID; + $data->order = zget($maxOrders, $planID, 0) + 1; + + $this->dao->replace(TABLE_PLANSTORY)->data($data)->exec(); + } + } + } + + /** + * Compute parent story estimate. + * + * @param int $storyID + * @access public + * @return bool + */ + public function computeEstimate($storyID) + { + if(!$storyID) return true; + + $stories = $this->dao->select('`id`,`estimate`,status')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll('id'); + if(empty($stories)) return true; + + $estimate = 0; + foreach($stories as $story) $estimate += $story->estimate; + $this->dao->update(TABLE_STORY)->set('estimate')->eq($estimate)->autoCheck()->where('id')->eq($storyID)->exec(); + return !dao::isError(); + } + + /** + * Batch update stories. + * + * @access public + * @return array. + */ + public function batchUpdate() + { + /* Init vars. */ + $stories = array(); + $allChanges = array(); + $now = helper::now(); + $data = fixer::input('post')->get(); + $storyIdList = $this->post->storyIdList ? $this->post->storyIdList : array(); + + /* Init $stories. */ + if(!empty($storyIdList)) + { + $oldStories = $this->getByList($storyIdList); + + /* Process the data if the value is 'ditto'. */ + foreach($storyIdList as $storyID) + { + if($data->pris[$storyID] == 'ditto') $data->pris[$storyID] = isset($prev['pri']) ? $prev['pri'] : 0; + if(isset($data->branches) and $data->branches[$storyID] == 'ditto') $data->branches[$storyID] = isset($prev['branch']) ? $prev['branch'] : 0; + if($data->modules[$storyID] == 'ditto') $data->modules[$storyID] = isset($prev['module']) ? $prev['module'] : 0; + if($data->plans[$storyID] == 'ditto') $data->plans[$storyID] = isset($prev['plan']) ? $prev['plan'] : ''; + if($data->sources[$storyID] == 'ditto') $data->sources[$storyID] = isset($prev['source']) ? $prev['source'] : ''; + if(isset($data->stages[$storyID]) and ($data->stages[$storyID] == 'ditto')) $data->stages[$storyID] = isset($prev['stage']) ? $prev['stage'] : ''; + if(isset($data->closedBys[$storyID]) and ($data->closedBys[$storyID] == 'ditto')) $data->closedBys[$storyID] = isset($prev['closedBy']) ? $prev['closedBy'] : ''; + if(isset($data->closedReasons[$storyID]) and ($data->closedReasons[$storyID] == 'ditto')) $data->closedReasons[$storyID] = isset($prev['closedReason']) ? $prev['closedReason'] : ''; + + $prev['pri'] = $data->pris[$storyID]; + $prev['branch'] = isset($data->branches[$storyID]) ? $data->branches[$storyID] : 0; + $prev['module'] = $data->modules[$storyID]; + $prev['plan'] = $data->plans[$storyID]; + $prev['source'] = $data->sources[$storyID]; + if(isset($data->stages[$storyID])) $prev['stage'] = $data->stages[$storyID]; + if(isset($data->closedBys[$storyID])) $prev['closedBy'] = $data->closedBys[$storyID]; + if(isset($data->closedReasons[$storyID])) $prev['closedReason'] = $data->closedReasons[$storyID]; + } + + $extendFields = $this->getFlowExtendFields(); + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + + $story = new stdclass(); + $story->id = $storyID; + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->status = $oldStory->status; + $story->color = $data->colors[$storyID]; + $story->title = $data->titles[$storyID]; + $story->estimate = $data->estimates[$storyID]; + $story->category = $data->category[$storyID]; + $story->pri = $data->pris[$storyID]; + $story->assignedTo = $data->assignedTo[$storyID]; + $story->assignedDate = $oldStory == $data->assignedTo[$storyID] ? $oldStory->assignedDate : $now; + $story->branch = isset($data->branches[$storyID]) ? $data->branches[$storyID] : 0; + $story->module = $data->modules[$storyID]; + $story->plan = $oldStories[$storyID]->parent < 0 ? '' : $data->plans[$storyID]; + $story->source = $data->sources[$storyID]; + $story->sourceNote = $data->sourceNote[$storyID]; + $story->keywords = $data->keywords[$storyID]; + $story->stage = isset($data->stages[$storyID]) ? $data->stages[$storyID] : $oldStory->stage; + $story->closedBy = isset($data->closedBys[$storyID]) ? $data->closedBys[$storyID] : $oldStory->closedBy; + $story->closedReason = isset($data->closedReasons[$storyID]) ? $data->closedReasons[$storyID] : $oldStory->closedReason; + $story->duplicateStory = isset($data->duplicateStories[$storyID]) ? $data->duplicateStories[$storyID] : $oldStory->duplicateStory; + $story->childStories = isset($data->childStoriesIDList[$storyID]) ? $data->childStoriesIDList[$storyID] : $oldStory->childStories; + $story->version = $story->title == $oldStory->title ? $oldStory->version : $oldStory->version + 1; + if($story->stage != $oldStory->stage) $story->stagedBy = (strpos('tested|verified|released|closed', $story->stage) !== false) ? $this->app->user->account : ''; + + if($story->title != $oldStory->title and $story->status != 'draft') $story->status = 'changed'; + if($story->closedBy != false and $oldStory->closedDate == '') $story->closedDate = $now; + if($story->closedReason != false and $oldStory->closedDate == '') $story->closedDate = $now; + if($story->closedBy != false or $story->closedReason != false) $story->status = 'closed'; + if($story->closedReason != false and $story->closedBy == false) $story->closedBy = $this->app->user->account; + + foreach($extendFields as $extendField) + { + $story->{$extendField->field} = $this->post->{$extendField->field}[$storyID]; + if(is_array($story->{$extendField->field})) $story->{$extendField->field} = join(',', $story->{$extendField->field}); + + $story->{$extendField->field} = htmlSpecialString($story->{$extendField->field}); + } + + $stories[$storyID] = $story; + } + + foreach($stories as $storyID => $story) + { + $oldStory = $oldStories[$storyID]; + + $this->dao->update(TABLE_STORY)->data($story) + ->autoCheck() + ->checkIF($story->closedBy, 'closedReason', 'notempty') + ->checkIF($story->closedReason == 'done', 'stage', 'notempty') + ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkFlow() + ->where('id')->eq((int)$storyID) + ->exec(); + if($story->title != $oldStory->title) + { + $data = new stdclass(); + $data->story = $storyID; + $data->version = $story->version; + $data->title = $story->title; + $data->spec = $oldStory->spec; + $data->verify = $oldStory->verify; + $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); + } + + if(!dao::isError()) + { + /* Update story sort of plan when story plan has changed. */ + if($oldStory->plan != $story->plan) $this->updateStoryOrderOfPlan($storyID, $story->plan, $oldStory->plan); + + $this->executeHooks($storyID); + if($story->type == 'story') $this->batchChangeStage(array($storyID), $story->stage); + if($story->closedReason == 'done') $this->loadModel('score')->create('story', 'close'); + $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + else + { + return print(js::error('story#' . $storyID . dao::getError(true))); + } + } + } + if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchEdit'); + return $allChanges; + } + + /** + * Review a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function review($storyID) + { + if($this->post->result == 'revert' and $this->post->preVersion == false) return print(js::alert($this->lang->story->mustChoosePreVersion)); + + if(strpos($this->config->story->review->requiredFields, 'comment') !== false and !$this->post->comment) + { + dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->comment); + return false; + } + + if($this->post->result == false) + { + dao::$errors[] = $this->lang->story->mustChooseResult; + return false; + } + + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $date = helper::today(); + $story = fixer::input('post') + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', $now) + ->setDefault('status', $oldStory->status) + ->setDefault('reviewedDate', $date) + ->stripTags($this->config->story->editor->review['id'], $this->config->allowedTags) + ->setIF(!$this->post->assignedTo, 'assignedTo', '') + ->setIF($this->post->result == 'revert', 'version', $this->post->preVersion) + ->setIF($this->post->result == 'clarify', 'assignedTo', $oldStory->lastEditedBy ? $oldStory->lastEditedBy : $oldStory->openedBy) + ->removeIF($this->post->result != 'reject', 'closedReason, duplicateStory, childStories') + ->removeIF($this->post->result == 'reject' and $this->post->closedReason != 'duplicate', 'duplicateStory') + ->removeIF($this->post->result == 'reject' and $this->post->closedReason != 'subdivided', 'childStories') + ->add('reviewedBy', $oldStory->reviewedBy . ',' . $this->app->user->account) + ->add('id', $storyID) + ->remove('result,preVersion,comment') + ->get(); + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->review['id'], $this->post->uid); + + /* Fix bug #671. */ + $this->lang->story->closedReason = $this->lang->story->rejectedReason; + + $this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($this->post->result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec(); + + $story = $this->updateStoryByReview($storyID, $oldStory, $story); + + $skipFields = ''; + $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); + if($isSuperReviewer === false) + { + $reviewers = $this->getReviewerPairs($storyID, $oldStory->version); + if(count($reviewers) > 1) $skipFields = 'closedReason'; + } + + $this->dao->update(TABLE_STORY)->data($story, $skipFields) + ->autoCheck() + ->batchCheck($this->config->story->review->requiredFields, 'notempty') + ->checkIF($this->post->result == 'reject', 'closedReason', 'notempty') + ->checkIF($this->post->result == 'reject' and $this->post->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkFlow() + ->where('id')->eq($storyID)->exec(); + if($this->post->result == 'revert') + { + $preTitle = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($this->post->preVersion)->fetch('title'); + $this->dao->update(TABLE_STORY)->set('title')->eq($preTitle)->where('id')->eq($storyID)->exec(); + + /* Delete versions that is after this version. */ + $deleteVersion = array(); + for($version = $oldStory->version; $version > $story->version; $version --) $deleteVersion[] = $version; + if($deleteVersion) + { + $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->in($deleteVersion)->exec(); + $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->in($deleteVersion)->exec(); + } + + $this->dao->delete()->from(TABLE_FILE)->where('objectType')->eq('story')->andWhere('objectID')->eq($storyID)->andWhere('extra')->eq($oldStory->version)->exec(); + } + if($this->post->result != 'reject') $this->setStage($storyID); + + if(isset($story->closedReason) and $isSuperReviewer === false) unset($story->closedReason); + return common::createChanges($oldStory, $story); + } + + /** + * Batch review stories. + * + * @param array $storyIdList + * @param string $result + * @param string $reason + * @access public + * @return array + */ + public function batchReview($storyIdList, $result, $reason) + { + $now = helper::now(); + $actions = array(); + $this->loadModel('action'); + + $oldStories = $this->getByList($storyIdList); + $hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story'); + $reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer'); + foreach($storyIdList as $storyID) + { + if(!$storyID) continue; + $oldStory = $oldStories[$storyID]; + $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); + + if($oldStory->status != 'draft' and $oldStory->status != 'changed') continue; + if(!in_array($this->app->user->account, array_keys($reviewerList[$storyID])) and $isSuperReviewer === false) continue; + if(isset($hasResult[$storyID]) and $hasResult[$storyID]->version == $oldStories[$storyID]->version) continue; + + $story = new stdClass(); + $story->reviewedDate = $now; + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->reviewedBy = $oldStory->reviewedBy . ',' . $this->app->user->account; + $story->status = $oldStory->status; + + $this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec(); + + /* Update the story status by review rules. */ + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + if($isSuperReviewer !== false) + { + $story = $this->superReview($storyID, $oldStory, $story, $result, $reason); + } + if(!array_diff(array_keys($reviewerList[$storyID]), $reviewedBy)) + { + $reviewerPairs = array(); + foreach($reviewerList[$storyID] as $reviewer => $reviewInfo) $reviewerPairs[$reviewer] = $reviewInfo->result; + $reviewerPairs[$this->app->user->account] = $result; + + $status = $this->setStatusByReviewRules($reviewerPairs); + $story->status = $status ? $status : $oldStory->status; + + if($story->status == 'closed') + { + $story->closedBy = $this->app->user->account; + $story->closedDate = $now; + $story->assignedTo = 'closed'; + $story->stage = 'closed'; + if($reason == 'done') $story->stage = 'released'; + } + } + + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq($storyID)->exec(); + $this->setStage($storyID); + + $story->id = $storyID; + $story->version = $oldStory->version; + $actions[$storyID] = $this->recordReviewAction($story, $result, $reason); + } + + return $actions; + } + + /** + * Recall the story review. + * + * @param int $storyID + * @access public + * @return void + */ + public function recall($storyID) + { + $story = $this->getById($storyID); + $this->dao->update(TABLE_STORY)->set('status')->eq('draft')->where('id')->eq($storyID)->exec(); + $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->exec(); + } + + /** + * Subdivide story + * + * @param int $storyID + * @param array $stories + * @access public + * @return void + */ + public function subdivide($storyID, $stories) + { + $now = helper::now(); + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + if($oldStory->type == 'requirement') + { + foreach($stories as $id) + { + $data = new stdclass(); + $data->product = $oldStory->product; + $data->AType = 'requirement'; + $data->relation = 'subdivideinto'; + $data->BType = 'story'; + $data->AID = $storyID; + $data->BID = $id; + $data->AVersion = $oldStory->version; + $data->BVersion = 1; + $data->extra = 1; + + $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); + + $data->AType = 'story'; + $data->relation = 'subdividedfrom'; + $data->BType = 'requirement'; + $data->AID = $id; + $data->BID = $storyID; + $data->AVersion = 1; + $data->BVersion = $oldStory->version; + + $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); + } + + if(dao::isError()) return print(js::error(dao::getError())); + + $isonlybody = isonlybody(); + unset($_GET['onlybody']); + return print(js::locate(helper::createLink('product', 'browse', "productID=$oldStory->product&branch=0&browseType=unclosed&queryID=0&type=story"), $isonlybody ? 'parent.parent' : 'parent')); + } + else + { + /* Set parent to child story. */ + $this->dao->update(TABLE_STORY)->set('parent')->eq($storyID)->where('id')->in($stories)->exec(); + $this->computeEstimate($storyID); + + /* Set childStories. */ + $childStories = join(',', $stories); + + $newStory = new stdClass(); + $newStory->parent = '-1'; + $newStory->plan = ''; + $newStory->lastEditedBy = $this->app->user->account; + $newStory->lastEditedDate = $now; + $newStory->childStories = trim($oldStory->childStories . ',' . $childStories, ','); + + /* Subdivide story. */ + $this->dao->update(TABLE_STORY)->data($newStory)->autoCheck()->where('id')->eq($storyID)->exec(); + + $changes = common::createChanges($oldStory, $newStory); + if($changes) + { + $actionID = $this->loadModel('action')->create('story', $storyID, 'createChildrenStory', '', $childStories); + $this->action->logHistory($actionID, $changes); + } + } + } + + /** + * Close a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function close($storyID) + { + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $story = fixer::input('post') + ->add('id', $storyID) + ->add('assignedTo', 'closed') + ->add('status', 'closed') + ->add('stage', 'closed') + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', $now) + ->setDefault('closedDate', $now) + ->setDefault('closedBy', $this->app->user->account) + ->setDefault('assignedDate', $now) + ->stripTags($this->config->story->editor->close['id'], $this->config->allowedTags) + ->removeIF($this->post->closedReason != 'duplicate', 'duplicateStory') + ->removeIF($this->post->closedReason != 'subdivided', 'childStories') + ->get(); + + if(!empty($story->duplicateStory)) + { + $duplicateStoryID = $this->dao->select('id')->from(TABLE_STORY)->where('id')->eq($story->duplicateStory)->fetch(); + if(empty($duplicateStoryID)) + { + dao::$errors[] = sprintf($this->lang->story->errorDuplicateStory, $story->duplicateStory); + return false; + } + } + + $this->lang->story->comment = $this->lang->comment; + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->close['id'], $this->post->uid); + $this->dao->update(TABLE_STORY)->data($story, 'comment') + ->autoCheck() + ->batchCheck($this->config->story->close->requiredFields, 'notempty') + ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->checkFlow() + ->where('id')->eq($storyID)->exec(); + + /* Update parent story status and stage. */ + if($oldStory->parent > 0) + { + $this->updateParentStatus($storyID, $oldStory->parent); + $this->setStage($oldStory->parent); + } + if(!dao::isError()) + { + $this->setStage($storyID); + $this->loadModel('score')->create('story', 'close', $storyID); + } + return common::createChanges($oldStory, $story); + } + + /** + * Batch close story. + * + * @access public + * @return void + */ + public function batchClose() + { + /* Init vars. */ + $stories = array(); + $allChanges = array(); + $now = helper::now(); + $data = fixer::input('post')->get(); + $storyIdList = $data->storyIdList ? $data->storyIdList : array(); + + $oldStories = $this->getByList($storyIdList); + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + if($oldStory->parent == -1) continue; + if($oldStory->status == 'closed') continue; + + $story = new stdclass(); + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->closedBy = $this->app->user->account; + $story->closedDate = $now; + $story->assignedTo = 'closed'; + $story->assignedDate = $now; + $story->status = 'closed'; + $story->stage = 'closed'; + + $story->closedReason = $data->closedReasons[$storyID]; + $story->duplicateStory = $data->duplicateStoryIDList[$storyID] ? $data->duplicateStoryIDList[$storyID] : $oldStory->duplicateStory; + $story->childStories = $data->childStoriesIDList[$storyID] ? $data->childStoriesIDList[$storyID] : $oldStory->childStories; + + if($story->closedReason != 'done') $story->plan = ''; + + $stories[$storyID] = $story; + unset($story); + } + + foreach($stories as $storyID => $story) + { + if(!$story->closedReason) continue; + + $oldStory = $oldStories[$storyID]; + + $this->dao->update(TABLE_STORY)->data($story) + ->autoCheck() + ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') + ->where('id')->eq($storyID)->exec(); + + if(!dao::isError()) + { + /* Update parent story status. */ + if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); + $this->setStage($storyID); + $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + else + { + return print(js::error('story#' . $storyID . dao::getError(true))); + } + if(!dao::isError()) $this->loadModel('score')->create('story', 'close', $storyID); + } + + return $allChanges; + } + + /** + * Batch change the module of story. + * + * @param array $storyIdList + * @param int $moduleID + * @access public + * @return array + */ + public function batchChangeModule($storyIdList, $moduleID) + { + $now = helper::now(); + $allChanges = array(); + $oldStories = $this->getByList($storyIdList); + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + if($moduleID == $oldStory->module) continue; + + $story = new stdclass(); + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->module = $moduleID; + + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); + if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + return $allChanges; + } + + /** + * Batch change the plan of story. + * + * @param array $storyIdList + * @param int $planID + * @access public + * @return array + */ + public function batchChangePlan($storyIdList, $planID, $oldPlanID = 0) + { + /* Prepare data. */ + $now = helper::now(); + $allChanges = array(); + $oldStories = $this->getByList($storyIdList); + $plan = $this->loadModel('productplan')->getById($planID); + $oldStoryStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in($storyIdList)->fetchGroup('story', 'branch'); + + /* Cycle every story and process it's plan and stage. */ + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + if($oldStory->branch != BRANCH_MAIN and $oldStory->branch != $plan->branch and $plan->branch != BRANCH_MAIN) continue; + + /* Ignore parent story, closed story and story linked to this plan already. */ + if($oldStory->parent < 0) continue; + if($oldStory->status == 'closed') continue; + if(strpos(",{$oldStory->plan},", ",$planID,") !== false) continue; + + /* Init story and set last edited data. */ + $story = new stdclass(); + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + + /* Remove old plan from the plan field. */ + if($oldPlanID) $story->plan = trim(str_replace(",$oldPlanID,", ',', ",$oldStory->plan,"), ','); + + /* Update the order of the story in the plan. */ + $this->updateStoryOrderOfPlan($storyID, $planID, $oldStory->plan); + + /* Replace plan field if product is normal or not linked to plan or story linked to a branch. */ + if($this->session->currentProductType == 'normal') $story->plan = $planID; + if(empty($oldPlanID)) $story->plan = $planID; + if($oldStory->branch) $story->plan = $planID; + + /* Append the plan id to plan field if product is multi and story is all branch. */ + if($this->session->currentProductType != 'normal' and empty($story->branch) and $planID != 0) + { + $branchPlanPairs = $this->dao->select('branch, id as plan')->from(TABLE_PRODUCTPLAN) + ->where('product')->eq($oldStory->product) + ->andWhere('id')->in($oldStory->plan) + ->fetchPairs(); + if(isset($branchPlanPairs[$plan->branch])) $branchPlanPairs[$plan->branch] = $planID; + + $story->plan = $oldStory->plan ? ',' . implode(',', $branchPlanPairs) : ",$planID"; + } + + /* Change stage. */ + if($planID) + { + if($oldStory->stage == 'wait') $story->stage = 'planned'; + if($this->session->currentProductType and $this->session->currentProductType != 'normal' and $oldStory->branch == 0) + { + if(!isset($oldStoryStages[$storyID][$plan->branch])) + { + $story->stage = 'planned'; + $newStoryStage = new stdclass(); + $newStoryStage->story = $storyID; + $newStoryStage->branch = $plan->branch; + $newStoryStage->stage = $story->stage; + $this->dao->insert(TABLE_STORYSTAGE)->data($newStoryStage)->autoCheck()->exec(); + } + } + } + + /* Update story and recompute stage. */ + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); + + if(!$planID) $this->setStage($storyID); + + if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + return $allChanges; + } + + /** + * Batch change branch. + * + * @param array $storyIdList + * @param int $branchID + * @param string $confirm + * @param array $plans + * @access public + * @return array + */ + public function batchChangeBranch($storyIdList, $branchID, $confirm = '', $plans = array()) + { + $now = helper::now(); + $allChanges = array(); + $oldStories = $this->getByList($storyIdList); + $story = current($oldStories); + $productID = $story->product; + $mainModules = $this->dao->select('id')->from(TABLE_MODULE) + ->where('root')->eq($productID) + ->andWhere('branch')->eq(0) + ->andWhere('type')->eq('story') + ->fetchPairs('id'); + + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + + $story = new stdclass(); + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->branch = $branchID; + $story->module = ($oldStory->branch != $branchID and !in_array($oldStory->module, $mainModules)) ? 0 : $oldStory->module; + + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); + if(!dao::isError()) + { + if($confirm == 'yes') + { + $planIdList = ''; + $conflictPlanIdList = ''; + + /* Determine whether there is a conflict between the branch of the story and the linked plan. */ + if($oldStory->branch != $branchID and $branchID != BRANCH_MAIN and isset($plans[$storyID])) + { + foreach($plans[$storyID] as $planID => $plan) + { + if($plan->branch != $branchID) + { + $conflictPlanIdList .= $planID . ','; + } + else + { + $planIdList .= $planID . ','; + } + } + + /* If there is a conflict in the linked plan when the branch story to be modified, the linked with the conflicting plan will be removed. */ + if($conflictPlanIdList) + { + $story->plan = $planIdList; + $this->dao->delete()->from(TABLE_PLANSTORY)->where('story')->eq($storyID)->andWhere('plan')->in($conflictPlanIdList)->exec(); + $this->dao->update(TABLE_STORY)->set('plan')->eq($planIdList)->where('id')->eq($storyID)->exec(); + } + } + } + $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + } + return $allChanges; + } + + /** + * Batch change the stage of story. + * + * @param $storyIdList + * @param $stage + * + * @access public + * @return array + */ + public function batchChangeStage($storyIdList, $stage) + { + $now = helper::now(); + $allChanges = array(); + $account = $this->app->user->account; + $oldStories = $this->getByList($storyIdList); + $ignoreStories = ''; + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + if($oldStory->status == 'draft' or $oldStory->status == 'closed') + { + $ignoreStories .= "#{$storyID} "; + continue; + } + + $story = new stdclass(); + $story->lastEditedBy = $account; + $story->lastEditedDate = $now; + $story->stage = $stage; + $story->stagedBy = $account; + + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); + $this->dao->update(TABLE_STORYSTAGE)->set('stage')->eq($stage)->set('stagedBy')->eq($account)->where('story')->eq((int)$storyID)->exec(); + if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + if($ignoreStories) echo js::alert(sprintf($this->lang->story->ignoreChangeStage, $ignoreStories)); + return $allChanges; + } + + /** + * Batch to task. + * + * @param int $executionID + * @param int $projectID + * @access public + * @return bool|array + */ + public function batchToTask($executionID, $projectID = 0) + { + /* load Module and get the data from the post and get the current time. */ + $this->loadModel('action'); + $data = fixer::input('post')->get(); + $now = helper::now(); + + /* Judgment of required items. */ + if(empty($data->type)) + { + dao::$errors['type'] = sprintf($this->lang->error->notempty, $this->lang->task->type); + } + + if(isset($data->hourPointValue) and empty($data->hourPointValue)) + { + dao::$errors['hourPointValue'] = sprintf($this->lang->error->notempty, $this->lang->story->convertRelations); + } + elseif(isset($data->hourPointValue) and !is_numeric($data->hourPointValue)) + { + dao::$errors['hourPointValue'] = sprintf($this->lang->error->float, $this->lang->story->convertRelations); + } + if(dao::isError()) return false; + + /* Create tasks. */ + $tasks = array(); + $stories = empty($data->storyIdList) ? array() : $this->getByList($data->storyIdList); + foreach($stories as $story) + { + if(strpos('draft,closed', $story->status) !== false) continue; + + $task = new stdclass(); + $task->execution = $executionID; + $task->project = $projectID ? $projectID : 0; + $task->name = $story->title; + $task->story = $story->id; + $task->type = $data->type; + $task->estimate = isset($data->hourPointValue) ? ($story->estimate * $data->hourPointValue) : $story->estimate; + $task->left = $task->estimate; + $task->openedBy = $this->app->user->account; + $task->openedDate = $now; + $task->storyVersion = $story->version; + + if(isset($data->fields)) + { + foreach($data->fields as $field) + { + $task->$field = $story->$field; + + if($field == 'assignedTo') $task->assignedDate = $now; + if($field == 'spec') + { + unset($task->$field); + $task->desc = $story->$field; + } + } + } + + $this->dao->insert(TABLE_TASK)->data($task) + ->autoCheck() + ->checkIF($task->estimate != '', 'estimate', 'float') + ->exec(); + + if(dao::isError()) return false; + $taskID = $this->dao->lastInsertID(); + $tasks[] = $taskID; + $this->action->create('task', $taskID, 'Opened', ''); + } + + $this->loadModel('kanban')->updateLane($executionID, 'task'); + return $tasks; + } + + /** + * Assign story. + * + * @param int $storyID + * @access public + * @return array + */ + public function assign($storyID) + { + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $assignedTo = $this->post->assignedTo; + if($assignedTo == $oldStory->assignedTo) return array(); + + $story = fixer::input('post') + ->add('id', $storyID) + ->add('lastEditedBy', $this->app->user->account) + ->add('lastEditedDate', $now) + ->add('assignedDate', $now) + ->stripTags($this->config->story->editor->assignto['id'], $this->config->allowedTags) + ->remove('comment') + ->get(); + + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->assignto['id'], $this->post->uid); + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq((int)$storyID)->exec(); + if(!dao::isError()) return common::createChanges($oldStory, $story); + return false; + } + + /** + * Batch assign to. + * + * @access public + * @return array + */ + public function batchAssignTo() + { + $now = helper::now(); + $allChanges = array(); + $storyIdList = $this->post->storyIdList; + $assignedTo = $this->post->assignedTo; + $oldStories = $this->getByList($storyIdList); + foreach($storyIdList as $storyID) + { + $oldStory = $oldStories[$storyID]; + if($assignedTo == $oldStory->assignedTo) continue; + + $story = new stdclass(); + $story->lastEditedBy = $this->app->user->account; + $story->lastEditedDate = $now; + $story->assignedTo = $assignedTo; + $story->assignedDate = $now; + + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); + + $allChanges[$storyID] = common::createChanges($oldStory, $story); + } + return $allChanges; + } + + /** + * Activate a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function activate($storyID) + { + $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + $now = helper::now(); + $story = fixer::input('post') + ->add('id', $storyID) + ->add('closedBy', '') + ->add('closedReason', '') + ->add('closedDate', '0000-00-00') + ->add('reviewedBy', '') + ->add('reviewedDate', '0000-00-00') + ->add('duplicateStory', 0) + ->add('childStories', '') + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', $now) + ->setDefault('assignedDate', $now) + ->setDefault('activatedDate', $now) + ->stripTags($this->config->story->editor->activate['id'], $this->config->allowedTags) + ->remove('comment') + ->get(); + $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->activate['id'], $this->post->uid); + $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq($storyID)->exec(); + + if($this->post->status == 'active') $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->exec(); + + $this->setStage($storyID); + + /* Update parent story status. */ + if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); + + return common::createChanges($oldStory, $story); + } + + /** + * Set stage of a story. + * + * @param int $storyID + * @access public + * @return bool + */ + public function setStage($storyID) + { + $this->loadModel('kanban'); + + $storyID = (int)$storyID; + $account = $this->app->user->account; + + /* Get projects which status is doing. */ + $oldStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchAll('branch'); + $this->dao->delete()->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->exec(); + + $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + if(!empty($story->stagedBy) and $story->status != 'closed') return false; + + $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fetch(); + $executions = $this->dao->select('t1.project,t3.branch')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t3')->on('t1.project = t3.project') + ->where('t1.story')->eq($storyID) + ->andWhere('t2.deleted')->eq(0) + ->fetchPairs('project', 'branch'); + + $hasBranch = ($product and $product->type != 'normal' and empty($story->branch)); + $stages = array(); + if($hasBranch and $story->plan) + { + $plans = $this->dao->select('*')->from(TABLE_PRODUCTPLAN)->where('id')->in($story->plan)->fetchPairs('branch', 'branch'); + foreach($plans as $branch) $stages[$branch] = 'planned'; + } + + /* When the status is closed, stage is also changed to closed. */ + if($story->status == 'closed') + { + $this->dao->update(TABLE_STORY)->set('stage')->eq('closed')->where('id')->eq($storyID)->exec(); + foreach($stages as $branch => $stage) $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq('closed')->exec(); + foreach($executions as $execution => $branch) + { + $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq('closed')->exec(); + $this->kanban->updateLane($execution, 'story', $storyID); + } + return false; + } + + /* If no executions, in plan, stage is planned. No plan, wait. */ + if(!$executions) + { + $this->dao->update(TABLE_STORY)->set('stage')->eq('wait')->where('id')->eq($storyID)->andWhere('plan')->eq('')->exec(); + + foreach($stages as $branch => $stage) + { + if(isset($oldStages[$branch])) + { + $oldStage = $oldStages[$branch]; + if(!empty($oldStage->stagedBy)) + { + $this->dao->replace(TABLE_STORYSTAGE)->data($oldStage)->exec(); + continue; + } + } + $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq($stage)->exec(); + } + $this->dao->update(TABLE_STORY)->set('stage')->eq('planned')->where('id')->eq($storyID)->andWhere("(plan != '' AND plan != '0')")->exec(); + } + + if($hasBranch) + { + foreach($executions as $executionID => $branch) $stages[$branch] = 'projected'; + } + + /* Search related tasks. */ + $tasks = $this->dao->select('type,execution,status')->from(TABLE_TASK) + ->where('execution')->in(array_keys($executions)) + ->andWhere('type')->in('devel,test') + ->andWhere('story')->eq($storyID) + ->andWhere('deleted')->eq(0) + ->andWhere('status')->ne('cancel') + ->andWhere('closedReason')->ne('cancel') + ->fetchGroup('type'); + + /* No tasks, then the stage is projected. */ + if(!$tasks and $executions) + { + foreach($stages as $branch => $stage) + { + if(isset($oldStages[$branch])) + { + $oldStage = $oldStages[$branch]; + if(!empty($oldStage->stagedBy)) + { + $this->dao->replace(TABLE_STORYSTAGE)->data($oldStage)->exec(); + continue; + } + } + $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq('projected')->exec(); + } + $this->dao->update(TABLE_STORY)->set('stage')->eq('projected')->where('id')->eq($storyID)->exec(); + } + + /* Get current stage and set as default value. */ + $currentStage = $story->stage; + $stage = $currentStage; + + /* Cycle all tasks, get counts of every type and every status. */ + $branchStatusList = array(); + $branchDevelTasks = array(); + $branchTestTasks = array(); + $statusList['devel'] = array('wait' => 0, 'doing' => 0, 'done'=> 0, 'pause' => 0); + $statusList['test'] = array('wait' => 0, 'doing' => 0, 'done'=> 0, 'pause' => 0); + foreach($tasks as $type => $typeTasks) + { + foreach($typeTasks as $task) + { + $status = $task->status ? $task->status : 'wait'; + $status = $status == 'closed' ? 'done' : $status; + + $branch = $executions[$task->execution]; + if(!isset($branchStatusList[$branch])) $branchStatusList[$branch] = $statusList; + if(!isset($branchStatusList[$branch][$task->type])) $branchStatusList[$branch][$task->type] = array(); + if(!isset($branchStatusList[$branch][$task->type][$status])) $branchStatusList[$branch][$task->type][$status] = 0; + $branchStatusList[$branch][$task->type][$status] ++; + if($type == 'devel') + { + if(!isset($branchDevelTasks[$branch])) $branchDevelTasks[$branch] = 0; + $branchDevelTasks[$branch] ++; + } + elseif($type == 'test') + { + if(!isset($branchTestTasks[$branch])) $branchTestTasks[$branch] = 0; + $branchTestTasks[$branch] ++; + } + } + } + + /** + * Judge stage according to the devel and test tasks' status. + * + * 1. one doing devel task, all test tasks waiting, set stage as developing. + * 2. some devel tasks done, all test tasks not done, set stage as developing. + * 3. all devel tasks done, all test tasks waiting, set stage as developed. + * 4. one test task doing, set stage as testing. + * 5. all test tasks done, still some devel tasks not done(wait, doing), set stage as testing. + * 6. all test tasks done, all devel tasks done, set stage as tested. + */ + foreach($branchStatusList as $branch => $statusList) + { + $stage = 'projected'; + $testTasks = isset($branchTestTasks[$branch]) ? $branchTestTasks[$branch] : 0; + $develTasks = isset($branchDevelTasks[$branch]) ? $branchDevelTasks[$branch] : 0; + if($statusList['devel']['doing'] > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developing'; + if($statusList['devel']['wait'] > 0 and $statusList['devel']['done'] > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developing'; + if(($statusList['devel']['doing'] > 0 or ($statusList['devel']['wait'] > 0 and $statusList['devel']['done'] > 0)) and $statusList['test']['wait'] > 0 and $statusList['test']['done'] > 0) $stage = 'developing'; + if($statusList['devel']['done'] == $develTasks and $develTasks > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developed'; + if($statusList['devel']['done'] == $develTasks and $develTasks > 0 and $statusList['test']['wait'] > 0 and $statusList['test']['done'] > 0) $stage = 'testing'; + if($statusList['test']['doing'] > 0 or $statusList['test']['pause'] > 0) $stage = 'testing'; + if(($statusList['devel']['wait'] > 0 or $statusList['devel']['doing'] > 0) and $statusList['test']['done'] == $testTasks and $testTasks > 0) $stage = 'testing'; + if($statusList['devel']['done'] == $develTasks and $statusList['test']['done'] == $testTasks and $testTasks > 0) $stage = 'tested'; + + $stages[$branch] = $stage; + } + + $releases = $this->dao->select('*')->from(TABLE_RELEASE)->where("CONCAT(',', stories, ',')")->like("%,$storyID,%")->andWhere('deleted')->eq(0)->fetchPairs('branch', 'branch'); + foreach($releases as $branch) $stages[$branch] = 'released'; + + $currentStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + if($story->stage != $currentStory->stage) + { + foreach($executions as $executionID => $branch) + { + $this->kanban->updateLane($executionID, 'story', $storyID); + } + } + + if(empty($stages)) return; + if($hasBranch) + { + $stageList = join(',', array_keys($this->lang->story->stageList)); + $minStagePos = strlen($stageList); + $minStage = ''; + foreach($stages as $branch => $stage) + { + $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq($stage)->exec(); + if(isset($oldStages[$branch])) + { + $oldStage = $oldStages[$branch]; + if(!empty($oldStage->stagedBy)) + { + $this->dao->replace(TABLE_STORYSTAGE)->data($oldStage)->exec(); + $stage = $oldStage->$stage; + } + } + if(strpos($stageList, $stage) !== false and strpos($stageList, $stage) < $minStagePos) + { + $minStage = $stage; + $minStagePos = strpos($stageList, $stage); + } + } + $this->dao->update(TABLE_STORY)->set('stage')->eq($minStage)->where('id')->eq($storyID)->exec(); + } + else + { + $this->dao->update(TABLE_STORY)->set('stage')->eq(current($stages))->where('id')->eq($storyID)->exec(); + } + + $currentStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); + if($story->stage != $currentStory->stage) + { + foreach($executions as $executionID => $branch) + { + $this->kanban->updateLane($executionID, 'story', $storyID); + } + } + + return; + } + + /** + * Get stories to link. + * + * @param int $storyID + * @param string $type + * @param string $browseType + * @param int $queryID + * @param varchar $storyType + * @access public + * @return array + */ + public function getStories2Link($storyID, $type = 'linkStories', $browseType = 'bySearch', $queryID = 0, $storyType = 'story') + { + $story = $this->getById($storyID); + $linkedStories = $this->getRelation($storyID, $story->type); + $linkedStories = empty($linkedStories) ? array() : $linkedStories; + $tmpStoryType = $storyType == 'story' ? 'requirement' : 'story'; + + if($browseType == 'bySearch') + { + $stories2Link = $this->getBySearch($story->product, $story->branch, $queryID, 'id', '', $tmpStoryType); + foreach($stories2Link as $key => $story2Link) + { + if($story2Link->id == $storyID) unset($stories2Link[$key]); + if(in_array($story2Link->id, explode(',', $story->$type))) unset($stories2Link[$key]); + } + } + else + { + $status = $storyType == 'story' ? 'active' : 'all'; + $stories2Link = $this->getProductStories($story->product, $story->branch, 0, $status, $tmpStoryType, $orderBy = 'id_desc'); + } + + foreach($stories2Link as $id => $story) + { + if(in_array($story->id, array_keys($linkedStories))) unset($stories2Link[$id]); + if($storyType == 'story' && $story->status == 'draft') unset($stories2Link[$id]); + } + + return $stories2Link; + } + + /** + * Get stories list of a product. + * + * @param int $productID + * @param int $branch + * @param array|string $moduleIdList + * @param string $status + * @param string $type requirement|story + * @param string $orderBy + * @param array|string $excludeStories + * @param object $pager + * @param bool $hasParent + * + * @access public + * @return array + */ + public function getProductStories($productID = 0, $branch = 0, $moduleIdList = 0, $status = 'all', $type = 'story', $orderBy = 'id_desc', $hasParent = true, $excludeStories = '', $pager = null) + { + if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getStories(); + + $stories = array(); + $branchProducts = array(); + $normalProducts = array(); + $productList = $this->dao->select('*')->from(TABLE_PRODUCT)->where('id')->in($productID)->fetchAll('id'); + foreach($productList as $product) + { + if($product->type != 'normal') + { + $branchProducts[$product->id] = $product->id; + continue; + } + + $normalProducts[$product->id] = $product->id; + } + + $productQuery = '('; + if(!empty($normalProducts)) $productQuery .= '`product` ' . helper::dbIN(array_keys($normalProducts)); + if(!empty($branchProducts)) + { + if(!empty($normalProducts)) $productQuery .= " OR "; + $productQuery .= "(`product` " . helper::dbIN(array_keys($branchProducts)); + + if($branch !== 'all') + { + if(is_array($branch)) $branch = join(',', $branch); + $productQuery .= " AND `branch` " . helper::dbIN($branch); + } + $productQuery .= ')'; + } + if(empty($normalProducts) and empty($branchProducts)) $productQuery .= '1 = 1'; + $productQuery .= ') '; + + $stories = $this->dao->select('*')->from(TABLE_STORY) + ->where('product')->in($productID) + ->andWhere($productQuery) + ->beginIF(!$hasParent)->andWhere("parent")->ge(0)->fi() + ->beginIF(!empty($moduleIdList))->andWhere('module')->in($moduleIdList)->fi() + ->beginIF(!empty($excludeStories))->andWhere('id')->notIN($excludeStories)->fi() + ->beginIF($status and $status != 'all')->andWhere('status')->in($status)->fi() + ->andWhere('type')->eq($type) + ->andWhere('deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + + return $this->mergePlanTitle($productID, $stories, $branch, $type); + } + + /** + * Get stories pairs of a product. + * + * @param int $productID + * @param string|int $branch + * @param array|string $moduleIdList + * @param string $status + * @param string $order + * @param int $limit + * @param string $type + * @param string $storyType requirement|story + * @param bool $hasParent + * @access public + * @return array + */ + public function getProductStoryPairs($productID = 0, $branch = 'all', $moduleIdList = 0, $status = 'all', $order = 'id_desc', $limit = 0, $type = 'full', $storyType = 'story', $hasParent = true) + { + $stories = $this->dao->select('t1.id, t1.title, t1.module, t1.pri, t1.estimate, t2.name AS product') + ->from(TABLE_STORY)->alias('t1')->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') + ->where('1=1') + ->beginIF($productID)->andWhere('t1.product')->in($productID)->fi() + ->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi() + ->beginIF($branch !== 'all')->andWhere('t1.branch')->in("0,$branch")->fi() + ->beginIF(!$hasParent)->andWhere('t1.parent')->ge(0)->fi() + ->beginIF($status and $status != 'all')->andWhere('t1.status')->in($status)->fi() + ->andWhere('t1.type')->eq($storyType) + ->andWhere('t1.deleted')->eq(0) + ->orderBy($order) + ->fetchAll(); + if(!$stories) return array(); + return $this->formatStories($stories, $type, $limit); + } + + /** + * Get stories by assignedTo. + * + * @param int $productID + * @param string $branch + * @param string $account + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByAssignedTo($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'assignedTo', $account, $type, $orderBy, $pager); + } + + /** + * Get stories by openedBy. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $account + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByOpenedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'openedBy', $account, $type, $orderBy, $pager); + } + + /** + * Get stories by reviewedBy. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $account + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByReviewedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'reviewedBy', $account, $type, $orderBy, $pager, 'include'); + } + + /** + * Get stories which need to review. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $account + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByReviewBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'reviewBy', $account, $type, $orderBy, $pager); + } + + /** + * Get stories by closedBy. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $account + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @return array + */ + public function getByClosedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'closedBy', $account, $type, $orderBy, $pager); + } + + /** + * Get stories by status. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $status + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByStatus($productID, $branch, $modules, $status, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'status', $status, $type, $orderBy, $pager); + } + + /** + * Get stories by plan. + * + * @param int $productID + * @param int $branch + * @param array $modules + * @param int $plan + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * + * @access public + * @return array + */ + public function getByPlan($productID, $branch, $modules, $plan, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'plan', $plan, $type, $orderBy, $pager); + } + + /** + * Get stories by assignedBy. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $account + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getByAssignedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) + { + return $this->getByField($productID, $branch, $modules, 'assignedBy', $account, $type, $orderBy, $pager); + } + + /** + * Get stories by a field. + * + * @param int $productID + * @param int|string $branch + * @param string $modules + * @param string $fieldName + * @param mixed $fieldValue + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @param string $operator equal|include + * @access public + * @return array + */ + public function getByField($productID, $branch, $modules, $fieldName, $fieldValue, $type = 'story', $orderBy = '', $pager = null, $operator = 'equal') + { + if(!$this->loadModel('common')->checkField(TABLE_STORY, $fieldName) and $fieldName != 'reviewBy' and $fieldName != 'assignedBy') return array(); + + $actionIDList = array(); + if($fieldName == 'assignedBy') $actionIDList = $this->dao->select('objectID')->from(TABLE_ACTION)->where('objectType')->eq('story')->andWhere('action')->eq('assigned')->andWhere('actor')->eq($fieldValue)->fetchPairs('objectID', 'objectID'); + + $sql = $this->dao->select('t1.*')->from(TABLE_STORY)->alias('t1'); + if($fieldName == 'reviewBy') $sql = $sql->leftJoin(TABLE_STORYREVIEW)->alias('t2')->on('t1.id = t2.story and t1.version = t2.version'); + + $stories = $sql->where('t1.product')->in($productID) + ->andWhere('t1.deleted')->eq(0) + ->andWhere('t1.type')->eq($type) + ->beginIF($branch != 'all')->andWhere("t1.branch")->eq($branch)->fi() + ->beginIF($modules)->andWhere("t1.module")->in($modules)->fi() + ->beginIF($operator == 'equal' and $fieldName != 'reviewBy' and $fieldName != 'assignedBy')->andWhere('t1.' . $fieldName)->eq($fieldValue)->fi() + ->beginIF($operator == 'include' and $fieldName != 'reviewBy' and $fieldName != 'assignedBy')->andWhere('t1.' . $fieldName)->like("%$fieldValue%")->fi() + ->beginIF($fieldName == 'reviewBy') + ->andWhere('t2.reviewer')->eq($this->app->user->account) + ->andWhere('t2.result')->eq('') + ->andWhere('t1.status')->in('draft,changed') + ->fi() + ->beginIF($fieldName == 'assignedBy')->andWhere('t1.id')->in($actionIDList)->andWhere('t1.status')->ne('closed')->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + return $this->mergePlanTitle($productID, $stories, $branch, $type); + } + + /** + * Get to be closed stories. + * + * @param int $productID + * @param int $branch + * @param string $modules + * @param string $type requirement|story + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function get2BeClosed($productID, $branch, $modules, $type = 'story', $orderBy = '', $pager = null) + { + $stories = $this->dao->select('*')->from(TABLE_STORY) + ->where('product')->in($productID) + ->andWhere('type')->eq($type) + ->beginIF($branch)->andWhere("branch")->eq($branch)->fi() + ->beginIF($modules)->andWhere("module")->in($modules)->fi() + ->andWhere('deleted')->eq(0) + ->andWhere('stage')->in('developed,released') + ->andWhere('status')->ne('closed') + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + return $this->mergePlanTitle($productID, $stories, $branch, $type); + } + + /** + * Get stories through search. + * + * @access public + * @param int $productID + * @param int|string $branch + * @param int $queryID + * @param string $orderBy + * @param string $executionID + * @param string $type requirement|story + * @param string $excludeStories + * @param object $pager + * @access public + * @return array + */ + public function getBySearch($productID, $branch = '', $queryID = 0, $orderBy = '', $executionID = '', $type = 'story', $excludeStories = '', $pager = null) + { + $this->loadModel('product'); + $executionID = empty($executionID) ? 0 : $executionID; + $products = empty($executionID) ? $this->product->getList() : $this->product->getProducts($executionID); + + $query = $queryID ? $this->loadModel('search')->getQuery($queryID) : ''; + + /* Get the sql and form status from the query. */ + if($query) + { + $this->session->set('storyQuery', $query->sql); + $this->session->set('storyForm', $query->form); + } + if($this->session->storyQuery == false) $this->session->set('storyQuery', ' 1 = 1'); + + $allProduct = "`product` = 'all'"; + $storyQuery = $this->session->storyQuery; + $queryProductID = $productID; + if(strpos($storyQuery, $allProduct) !== false) + { + $storyQuery = str_replace($allProduct, '1', $storyQuery); + $queryProductID = 'all'; + } + + $storyQuery = $storyQuery . ' AND `product` ' . helper::dbIN(array_keys($products)); + + if($excludeStories) $storyQuery = $storyQuery . ' AND `id` NOT ' . helper::dbIN($excludeStories); + if($this->app->moduleName == 'productplan') $storyQuery .= " AND `status` NOT IN ('closed') AND `parent` >= 0 "; + $allBranch = "`branch` = 'all'"; + if($executionID != '') + { + $normalProducts = array(); + $branchProducts = array(); + foreach($products as $product) + { + if($product->type != 'normal') + { + $branchProducts[$product->id] = $product; + continue; + } + + $normalProducts[$product->id] = $product; + } + + $storyQuery .= ' AND ('; + if(!empty($normalProducts)) $storyQuery .= '`product` ' . helper::dbIN(array_keys($normalProducts)); + if(!empty($branchProducts)) + { + $branches = array(BRANCH_MAIN => BRANCH_MAIN); + if($branch === '') + { + foreach($branchProducts as $product) + { + foreach($product->branches as $branchID) $branches[$branchID] = $branchID; + } + } + else + { + $branches[$branch] = $branch; + } + + $branches = join(',', $branches); + if(!empty($normalProducts)) $storyQuery .= " OR "; + $storyQuery .= "(`product` " . helper::dbIN(array_keys($branchProducts)) . " AND `branch` " . helper::dbIN($branches) . ")"; + } + if(empty($normalProducts) and empty($branchProducts)) $storyQuery .= '1 = 1'; + $storyQuery .= ') '; + + if($this->app->moduleName == 'release' or $this->app->moduleName == 'build') + { + $storyQuery .= " AND `status` NOT IN ('draft')"; // Fix bug #990. + } + else + { + $storyQuery .= " AND `status` NOT IN ('draft', 'closed')"; + } + + if($this->app->rawModule == 'build' and $this->app->rawMethod == 'linkstory') $storyQuery .= " AND `parent` != '-1'"; + } + elseif(strpos($storyQuery, $allBranch) !== false) + { + $storyQuery = str_replace($allBranch, '1', $storyQuery); + } + elseif($branch !== 'all' and $queryProductID != 'all') + { + if($branch and strpos($storyQuery, '`branch` =') === false) $storyQuery .= " AND `branch` in($branch)"; + } + $storyQuery = preg_replace("/`plan` +LIKE +'%([0-9]+)%'/i", "CONCAT(',', `plan`, ',') LIKE '%,$1,%'", $storyQuery); + + return $this->getBySQL($queryProductID, $storyQuery, $orderBy, $pager, $type); + } + + /** + * Get stories by a sql. + * + * @param int $productID + * @param string $sql + * @param string $orderBy + * @param object $pager + * @param string $type requirement|story + * @access public + * @return array + */ + public function getBySQL($productID, $sql, $orderBy, $pager = null, $type = 'story') + { + /* Get plans. */ + $plans = $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) + ->where('deleted')->eq('0') + ->beginIF($productID != 'all' and $productID != '')->andWhere('product')->eq((int)$productID)->fi() + ->fetchPairs(); + + $sql = str_replace(array('`product`', '`version`', '`branch`'), array('t1.`product`', 't1.`version`', 't1.`branch`'), $sql); + $tmpStories = $this->dao->select('DISTINCT t1.*')->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story') + ->where($sql) + ->beginIF($productID != 'all' and $productID != '')->andWhere('t1.`product`')->eq((int)$productID)->fi() + ->andWhere('t1.deleted')->eq(0) + ->andWhere('t1.type')->eq($type) + ->orderBy($orderBy) + ->page($pager, 't1.id') + ->fetchAll('id'); + + if(!$tmpStories) return array(); + + /* Process plans. */ + $stories = array(); + foreach($tmpStories as $story) + { + $story->planTitle = ''; + $storyPlans = explode(',', trim($story->plan, ',')); + foreach($storyPlans as $planID) $story->planTitle .= zget($plans, $planID, '') . ' '; + $stories[$story->id] = $story; + } + return $stories; + } + + /** + * Get stories list of a execution. + * + * @param int $executionID + * @param int $productID + * @param int $branch + * @param string $orderBy + * @param string $type + * @param int $param + * @param string $storyType + * @param string $excludeStories + * @param object $pager + * @access public + * @return array + */ + public function getExecutionStories($executionID = 0, $productID = 0, $branch = 0, $orderBy = 't1.`order`_desc', $type = 'byModule', $param = 0, $storyType = 'story', $excludeStories = '', $pager = null) + { + if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getExecutionStories(); + + if(!$executionID) return array(); + $execution = $this->dao->findById($executionID)->from(TABLE_PROJECT)->fetch(); + + $type = strtolower($type); + if($type == 'bysearch') + { + $queryID = (int)$param; + $products = $this->loadModel('product')->getProducts($executionID); + + if($this->session->executionStoryQuery == false) $this->session->set('executionStoryQuery', ' 1 = 1'); + if($queryID) + { + $query = $this->loadModel('search')->getQuery($queryID); + if($query) + { + if($this->app->rawModule == 'projectstory') + { + $this->session->set('storyQuery', $query->sql); + $this->session->set('storyForm', $query->form); + } + else + { + $this->session->set('executionStoryQuery', $query->sql); + $this->session->set('executionStoryForm', $query->form); + } + } + } + + if($this->app->rawModule == 'projectstory') $this->session->executionStoryQuery = $this->session->storyQuery; + + $allProduct = "`product` = 'all'"; + $storyQuery = $this->session->executionStoryQuery; + if(strpos($this->session->executionStoryQuery, $allProduct) !== false) + { + $storyQuery = str_replace($allProduct, '1', $this->session->executionStoryQuery); + } + $storyQuery = preg_replace('/`(\w+)`/', 't2.`$1`', $storyQuery); + + $stories = $this->dao->select('distinct t1.*, t2.*, t3.type as productType, t2.version as version')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id') + ->where($storyQuery) + ->andWhere('t1.project')->eq((int)$executionID) + ->andWhere('t2.deleted')->eq(0) + ->andWhere('t3.deleted')->eq(0) + ->andWhere('t2.type')->eq($storyType) + ->beginIF($excludeStories)->andWhere('t2.id')->notIN($excludeStories)->fi() + ->orderBy($orderBy) + ->page($pager, 't2.id') + ->fetchAll('id'); + } + else + { + $productParam = ($type == 'byproduct' and $param) ? $param : $this->cookie->storyProductParam; + $branchParam = ($type == 'bybranch' and $param !== '') ? $param : $this->cookie->storyBranchParam; + $moduleParam = ($type == 'bymodule' and $param !== '') ? $param : $this->cookie->storyModuleParam; + + $modules = array(); + if(!empty($moduleParam) or strpos('allstory,unclosed,bymodule', $type) !== false) + { + $modules = $this->dao->select('id')->from(TABLE_MODULE)->where('path')->like("%,$moduleParam,%")->andWhere('type')->eq('story')->andWhere('deleted')->eq(0)->fetchPairs(); + } + + if(strpos($branchParam, ',') !== false) list($productParam, $branchParam) = explode(',', $branchParam); + + $unclosedStatus = $this->lang->story->statusList; + unset($unclosedStatus['closed']); + + /* Get story id list of linked executions. */ + $storyIdList = array(); + if($type == 'linkedexecution' or $type == 'unlinkedexecution') + { + $executions = $this->loadModel('execution')->getPairs($executionID); + $storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in(array_keys($executions))->fetchPairs(); + } + + $type = (strpos('bymodule|byproduct', $type) !== false and $this->session->storyBrowseType) ? $this->session->storyBrowseType : $type; + + $stories = $this->dao->select('distinct t1.*, t2.*,t3.type as productType,t2.version as version')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id') + ->where('t1.project')->eq((int)$executionID) + ->andWhere('t2.type')->eq($storyType) + ->beginIF($excludeStories)->andWhere('t2.id')->notIN($excludeStories)->fi() + ->beginIF($execution->type == 'project') + ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID) + ->beginIF($type == 'bybranch' and $branchParam !== '')->andWhere('t2.branch')->in("0,$branchParam")->fi() + ->beginIF(strpos('changed|closed', $type) !== false)->andWhere('t2.status')->eq($type)->fi() + ->beginIF($type == 'unclosed')->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() + ->beginIF($type == 'linkedexecution')->andWhere('t2.id')->in($storyIdList)->fi() + ->beginIF($type == 'unlinkedexecution')->andWhere('t2.id')->notIn($storyIdList)->fi() + ->fi() + ->beginIF($execution->type != 'project') + ->beginIF(!empty($productParam))->andWhere('t1.product')->eq($productParam)->fi() + ->beginIF($this->session->executionStoryBrowseType and strpos('changed|', $this->session->executionStoryBrowseType) !== false)->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() + ->fi() + ->beginIF($this->session->storyBrowseType and strpos('changed|', $this->session->storyBrowseType) !== false)->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() + ->beginIF($modules)->andWhere('t2.module')->in($modules)->fi() + ->andWhere('t2.deleted')->eq(0) + ->andWhere('t3.deleted')->eq(0) + ->orderBy($orderBy) + ->page($pager, 't2.id') + ->fetchAll('id'); + } + + $query = $this->dao->get(); + + /* Get the stories of main branch. */ + $branchStoryList = $this->dao->select('t1.*,t2.branch as productBranch')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.project = t2.project') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->where('t1.story')->in(array_keys($stories)) + ->andWhere('t1.branch')->eq(BRANCH_MAIN) + ->andWhere('t3.type')->ne('normal') + ->fetchAll(); + + $branches = array(); + $stageOrderList = 'wait,planned,projected,developing,developed,testing,tested,verified,released,closed'; + + foreach($branchStoryList as $story) $branches[$story->productBranch][$story->story] = $story->story; + + /* Set up story stage. */ + foreach($branches as $branchID => $storyIdList) + { + $stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in($storyIdList)->andWhere('branch')->eq($branchID)->fetchPairs('story', 'stage'); + + /* Take the earlier stage. */ + foreach($stages as $storyID => $stage) if(strpos($stageOrderList, $stories[$storyID]->stage) > strpos($stageOrderList, $stage)) $stories[$storyID]->stage = $stage; + } + + $this->dao->sqlobj->sql = $query; + return $this->mergePlanTitle($productID, $stories, $branch, $type); + } + + /** + * Get stories pairs of a execution. + * + * @param int $executionID + * @param int $productID + * @param int $branch + * @param array|string $moduleIdList + * @param string $type full|short + * @param string $status all|unclosed|review + * @access public + * @return array + */ + public function getExecutionStoryPairs($executionID = 0, $productID = 0, $branch = 'all', $moduleIdList = 0, $type = 'full', $status = 'all') + { + if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getExecutionStoryPairs(); + $stories = $this->dao->select('t2.id, t2.title, t2.module, t2.pri, t2.estimate, t3.name AS product') + ->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') + ->where('t1.project')->eq((int)$executionID) + ->andWhere('t2.deleted')->eq(0) + ->beginIF($productID)->andWhere('t2.product')->eq((int)$productID)->fi() + ->beginIF($branch !== 'all')->andWhere('t2.branch')->in("0,$branch")->fi() + ->beginIF($moduleIdList)->andWhere('t2.module')->in($moduleIdList)->fi() + ->beginIF($status == 'unclosed')->andWhere('t2.status')->ne('closed')->fi() + ->beginIF($status == 'review')->andWhere('t2.status')->in('draft,changed')->fi() + ->beginIF($status == 'active')->andWhere('t2.status')->eq('active')->fi() + ->orderBy('t1.`order` desc, t1.`story` desc') + ->fetchAll('id'); + return empty($stories) ? array() : $this->formatStories($stories, $type); + } + + /** + * Get stories list of a plan. + * + * @param int $planID + * @param string $status + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getPlanStories($planID, $status = 'all', $orderBy = 'id_desc', $pager = null) + { + if(strpos($orderBy, 'module') !== false) + { + $orderBy = (strpos($orderBy, 'module_asc') !== false) ? 't3.path asc' : 't3.path desc'; + $stories = $this->dao->select('distinct t1.story, t1.plan, t1.order, t2.*') + ->from(TABLE_PLANSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->leftJoin(TABLE_MODULE)->alias('t3')->on('t2.module = t3.id') + ->where('t1.plan')->eq($planID) + ->beginIF($status and $status != 'all')->andWhere('t2.status')->in($status)->fi() + ->andWhere('t2.deleted')->eq(0) + ->orderBy($orderBy)->page($pager) + ->fetchAll('id'); + } + else + { + $stories = $this->dao->select('distinct t1.story, t1.plan, t1.order, t2.*') + ->from(TABLE_PLANSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') + ->where('t1.plan')->eq($planID) + ->beginIF($status and $status != 'all')->andWhere('t2.status')->in($status)->fi() + ->andWhere('t2.deleted')->eq(0) + ->orderBy($orderBy)->page($pager) + ->fetchAll('id'); + } + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false); + + return $stories; + } + + /** + * Get stories pairs of a plan. + * + * @param int $planID + * @param string $status + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getPlanStoryPairs($planID, $status = 'all', $orderBy = 'id_desc', $pager = null) + { + return $this->dao->select('*')->from(TABLE_STORY) + ->where('plan')->eq($planID) + ->beginIF($status and $status != 'all')->andWhere('status')->in($status)->fi() + ->andWhere('deleted')->eq(0) + ->fetchAll(); + } + + /** + * Get stories by plan id list. + * + * @param string|array $planIdList + * @access public + * @return array + */ + public function getStoriesByPlanIdList($planIdList = '') + { + return $this->dao->select('t1.plan as planID, t2.*')->from(TABLE_PLANSTORY)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id') + ->where('t2.deleted')->eq(0) + ->beginIF($planIdList)->andWhere('t1.plan')->in($planIdList)->fi() + ->fetchGroup('planID', 'id'); + } + + /** + * Get parent story pairs. + * + * @param int $productID + * @param string $append + * @access public + * @return void + */ + public function getParentStoryPairs($productID, $append = '') + { + $stories = $this->dao->select('id, title')->from(TABLE_STORY) + ->where('deleted')->eq(0) + ->andWhere('parent')->le(0) + ->andWhere('type')->eq('story') + ->andWhere('stage')->eq('wait') + ->andWhere('status')->notin('closed,draft') + ->andWhere('product')->eq($productID) + ->andWhere('plan')->in('0,') + ->beginIF($append)->orWhere('id')->in($append)->fi() + ->fetchPairs(); + return array(0 => '') + $stories ; + } + + /** + * Close requirement if all son story for segmentation has been closed. + * + * @param int $storyID + * @access public + * @return void + */ + public function closeParentRequirement($storyID) + { + $parentID = $this->dao->select('BID')->from(TABLE_RELATION)->where('AID')->eq($storyID)->fetch(); + if(empty($parentID)) return; + + $stories = $this->dao->select('t2.id, t2.status')->from(TABLE_RELATION)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on('t2.id=t1.AID') + ->where('t1.BType')->eq('requirement') + ->andWhere('t2.status')->ne('closed') + ->andWhere('t2.type')->eq('story') + ->fetchPairs(); + if(empty($stories)) $this->close($parentID->BID); + } + + /** + * Get stories of a user. + * + * @param string $account + * @param string $type the query type + * @param string $orderBy + * @param object $pager + * @param string $storyType requirement|story + * @access public + * @return array + */ + public function getUserStories($account, $type = 'assignedTo', $orderBy = 'id_desc', $pager = null, $storyType = 'story', $includeLibStories = true) + { + $sql = $this->dao->select('t1.*, t2.name as productTitle')->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id'); + if($type == 'reviewBy') $sql = $sql->leftJoin(TABLE_STORYREVIEW)->alias('t3')->on('t1.id = t3.story and t1.version = t3.version'); + + $stories = $sql->where('t1.deleted')->eq(0) + ->andWhere('t2.deleted')->eq('0') + ->andWhere('t1.type')->eq($storyType) + ->andWhere('t1.vision')->eq($this->config->vision) + ->beginIF($type != 'closedBy' and $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi() + ->beginIF($type != 'all') + ->beginIF($type == 'assignedTo')->andWhere('t1.assignedTo')->eq($account)->fi() + ->beginIF($type == 'reviewBy')->andWhere('t3.reviewer')->eq($account)->andWhere('t3.result')->eq('')->andWhere('t1.status')->in('draft,changed')->fi() + ->beginIF($type == 'openedBy')->andWhere('t1.openedBy')->eq($account)->fi() + ->beginIF($type == 'reviewedBy')->andWhere("CONCAT(',', t1.reviewedBy, ',')")->like("%,$account,%")->fi() + ->beginIF($type == 'closedBy')->andWhere('t1.closedBy')->eq($account)->fi() + ->fi() + ->beginIF($includeLibStories == false and $this->config->edition == 'max')->andWhere('t1.lib')->eq('0')->fi() + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false); + $productIdList = array(); + foreach($stories as $story) $productIdList[$story->product] = $story->product; + + return $this->mergePlanTitle($productIdList, $stories, 0, $storyType); + } + + /** + * Get story pairs of a user. + * + * @param string $account + * @param string $limit + * @param string $type requirement|story + * @param array $skipProductIDList + * @param int|array $appendStoryID + * @access public + * @return array + */ + public function getUserStoryPairs($account, $limit = 10, $type = 'story', $skipProductIDList = array(), $appendStoryID = 0) + { + return $this->dao->select('id, title') + ->from(TABLE_STORY) + ->where('deleted')->eq(0) + ->andWhere('status')->ne('closed') + ->andWhere('type')->eq($type) + ->andWhere('vision')->eq($this->config->vision) + ->andWhere('assignedTo')->eq($account) + ->andWhere('product')->ne(0) + ->beginIF(!empty($skipProductIDList))->andWhere('product')->notin($skipProductIDList)->fi() + ->beginIF(!empty($appendStoryID))->orWhere('id')->in($appendStoryID)->fi() + ->orderBy('id_desc') + ->limit($limit) + ->fetchPairs('id', 'title'); + } + + /** + * Get the story ID list of the linked to task. + * + * @param int $executionID + * @access public + * @return array + */ + public function getIdListWithTask($executionID) + { + return $this->dao->select('story')->from(TABLE_TASK) + ->where('execution')->eq($executionID) + ->andWhere('story')->ne(0) + ->andWhere('deleted')->eq(0) + ->fetchPairs(); + } + + /** + * Get team members for a project or execution. + * + * @param int $storyID + * @param string $actionType + * @access public + * @return array + */ + public function getTeamMembers($storyID, $actionType) + { + $teamMembers = array(); + if($actionType == 'changed') + { + $executions = $this->dao->select('execution')->from(TABLE_TASK) + ->where('story')->eq($storyID) + ->andWhere('status')->ne('cancel') + ->andWhere('deleted')->eq(0) + ->fetchPairs(); + if($executions) $teamMembers = $this->dao->select('account')->from(TABLE_TEAM)->where('root')->in($executions)->andWhere('type')->eq('execution')->fetchPairs('account'); + } + else + { + $projects = $this->dao->select('t1.project') + ->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') + ->where('t1.story')->eq((int)$storyID) + ->andWhere('t2.status')->eq('doing') + ->andWhere('t2.deleted')->eq(0) + ->fetchPairs(); + if($projects) $teamMembers = $this->dao->select('account')->from(TABLE_TEAM)->where('root')->in($projects)->andWhere('type')->eq('project')->fetchPairs('account'); + } + return $teamMembers; + } + + /** + * Get version of a story. + * + * @param int $storyID + * @access public + * @return int + */ + public function getVersion($storyID) + { + return (int)$this->dao->select('version')->from(TABLE_STORY)->where('id')->eq((int)$storyID)->fetch('version'); + } + + /** + * Get versions of some stories. + * + * @param array|string story id list + * @access public + * @return array + */ + public function getVersions($storyID) + { + return $this->dao->select('id, version')->from(TABLE_STORY)->where('id')->in($storyID)->fetchPairs(); + } + + /** + * Get zero case. + * + * @param int $productID + * @param int $branchID + * @param string $orderBy + * @access public + * @return array + */ + public function getZeroCase($productID, $branchID = 0, $orderBy = 'id_desc') + { + $allStories = $this->getProductStories($productID, $branchID, 0, 'all', 'story', $orderBy, $hasParent = false, '', null); + $casedStories = $this->dao->select('DISTINCT story')->from(TABLE_CASE)->where('product')->eq($productID)->andWhere('story')->ne(0)->andWhere('deleted')->eq(0)->fetchAll('story'); + + foreach($allStories as $key => $story) + { + if(isset($casedStories[$story->id])) unset($allStories[$key]); + } + + return $allStories; + } + + /** + * Get changed stories. + * + * @param object $story + * @access public + * @return void + */ + public function getChangedStories($story) + { + if($story->type == 'requirement') return array(); + + $relations = $this->dao->select('*')->from(TABLE_RELATION) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere('BID')->eq($story->id) + ->fetchAll('AID'); + + if(empty($relations)) return array(); + + $stories = $this->getByList(array_keys($relations)); + foreach($stories as $id => $story) + { + $version = $relations[$story->id]->AVersion; + if($version > $story->version) unset($stories[$id]); + } + + return $stories; + } + + public function getAllStorySort($planID, $planOrder) + { + $orderBy = $this->post->orderBy; + if(strpos($orderBy, 'order') !== false) $orderBy = str_replace('order', 'id', $orderBy); + + $stories = $this->loadModel('story')->getPlanStories($planID, 'all'); + $storyIDList = array_keys($stories); + + if(strpos($this->post->orderBy, 'order') !== false and !empty($planOrder)) $stories = $this->sortPlanStory($stories, $planOrder, $orderBy); + + $frontCount = (int)$this->post->recPerPage * ((int)$this->post->pageID - 1); + $behindCount = (int)$this->post->recPerPage * (int)$this->post->pageID; + $frontIDList = array_slice($storyIDList, 0, $frontCount); + $behindIDList = array_slice($storyIDList, $behindCount, count($storyIDList) - $behindCount); + + $frontIDList = !empty($frontIDList) ? implode(',', $frontIDList) . ',' : ''; + $behindIDList = !empty($behindIDList) ? implode(',', $behindIDList) : ''; + return $frontIDList . $this->post->stories . $behindIDList; + } + + /** + * Batch get story stage. + * + * @param array $stories + * @access public + * @return array + */ + public function batchGetStoryStage($stories) + { + return $this->dao->select('*')->from(TABLE_STORYSTAGE) + ->where('story')->in($stories) + ->fetchGroup('story', 'branch'); + } + + /** + * Check need confirm. + * + * @param array|object $object + * @access public + * @return array|object + */ + public function checkNeedConfirm($data) + { + $objectList = is_object($data) ? array($data->id => $data) : $data; + + $storyIdList = array(); + $storyVersionList = array(); + + foreach($objectList as $key => $object) + { + $object->needconfirm = false; + if($object->story) + { + $storyIdList[$key] = $object->story; + $storyVersionList[$key] = $object->storyVersion; + } + } + + $stories = $this->dao->select('id,version')->from(TABLE_STORY)->where('id')->in($storyIdList)->andWhere('status')->eq('active')->fetchPairs('id', 'version'); + foreach($storyIdList as $key => $storyID) + { + if(isset($stories[$storyID]) and $stories[$storyID] > $storyVersionList[$key]) $objectList[$key]->needconfirm = true; + } + + return is_object($data) ? reset($objectList) : $objectList; + } + + /** + * Format stories + * + * @param array $stories + * @param string $type + * @param int $limit + * @access public + * @return void + */ + public function formatStories($stories, $type = 'full', $limit = 0) + { + /* Format these stories. */ + $storyPairs = array(0 => ''); + $i = 0; + foreach($stories as $story) + { + if($type == 'short') + { + $property = '[p' . (!empty($this->lang->story->priList[$story->pri]) ? $this->lang->story->priList[$story->pri] : 0) . ', ' . $story->estimate . "{$this->config->hourUnit}]"; + } + elseif($type == 'full') + { + $property = '(' . $this->lang->story->pri . ':' . (!empty($this->lang->story->priList[$story->pri]) ? $this->lang->story->priList[$story->pri] : 0) . ',' . $this->lang->story->estimate . ':' . $story->estimate . ')'; + } + else + { + $property = ''; + } + $storyPairs[$story->id] = $story->id . ':' . $story->title . ' ' . $property; + + if($limit > 0 && ++$i > $limit) + { + $storyPairs['showmore'] = $this->lang->more . $this->lang->ellipsis; + break; + } + } + return $storyPairs; + } + + /** + * Extract accounts from some stories. + * + * @param array $stories + * @access public + * @return array + */ + public function extractAccountsFromList($stories) + { + $accounts = array(); + foreach($stories as $story) + { + if(!empty($story->openedBy)) $accounts[] = $story->openedBy; + if(!empty($story->assignedTo)) $accounts[] = $story->assignedTo; + if(!empty($story->closedBy)) $accounts[] = $story->closedBy; + if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy; + } + return array_unique($accounts); + } + + /** + * Extract accounts from a story. + * + * @param object $story + * @access public + * @return array + */ + public function extractAccountsFromSingle($story) + { + $accounts = array(); + if(!empty($story->openedBy)) $accounts[] = $story->openedBy; + if(!empty($story->assignedTo)) $accounts[] = $story->assignedTo; + if(!empty($story->closedBy)) $accounts[] = $story->closedBy; + if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy; + return array_unique($accounts); + } + + /** + * Merge the default chart settings and the settings of current chart. + * + * @param string $chartType + * @access public + * @return void + */ + public function mergeChartOption($chartType) + { + $chartOption = $this->lang->story->report->$chartType; + $commonOption = $this->lang->story->report->options; + + $chartOption->graph->caption = $this->lang->story->report->charts[$chartType]; + if(!isset($chartOption->type)) $chartOption->type = $commonOption->type; + if(!isset($chartOption->width)) $chartOption->width = $commonOption->width; + if(!isset($chartOption->height)) $chartOption->height = $commonOption->height; + + foreach($commonOption->graph as $key => $value) if(!isset($chartOption->graph->$key)) $chartOption->graph->$key = $value; + } + + /** + * Get report data of storys per product + * + * @access public + * @return array + */ + public function getDataOfStorysPerProduct() + { + $datas = $this->dao->select('product as name, count(product) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('product')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + $products = $this->loadModel('product')->getPairs(); + foreach($datas as $productID => $data) $data->name = isset($products[$productID]) ? $products[$productID] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per module + * + * @access public + * @return array + */ + public function getDataOfStorysPerModule() + { + $datas = $this->dao->select('module as name, count(module) as value, product, branch')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('module')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + + $branchIDList = array(); + foreach($datas as $key => $project) + { + if(!$project->branch) continue; + $branchIDList[$project->branch] = $project->branch; + } + + $branchs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($branchIDList)->andWhere('deleted')->eq(0)->fetchALL('id'); + $modules = $this->loadModel('tree')->getModulesName(array_keys($datas)); + + foreach($datas as $moduleID => $data) + { + $branch = ''; + if(isset($branchs[$data->branch]->name)) + { + $branch = '/' . $branchs[$data->branch]->name; + } + + $data->name = $branch . (isset($modules[$moduleID]) ? $modules[$moduleID] : '/'); + } + + return $datas; + } + + /** + * Get report data of storys per source + * + * @access public + * @return array + */ + public function getDataOfStorysPerSource() + { + $datas = $this->dao->select('source as name, count(source) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('source')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + $this->lang->story->sourceList[''] = $this->lang->report->undefined; + foreach($datas as $key => $data) $data->name = isset($this->lang->story->sourceList[$key]) ? $this->lang->story->sourceList[$key] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per plan + * + * @access public + * @return array + */ + public function getDataOfStorysPerPlan() + { + $datas = $this->dao->select('plan as name, count(plan) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('plan')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + + /* Separate for multi-plan key. */ + foreach($datas as $planID => $data) + { + if(strpos($planID, ',') !== false) + { + $planIdList = explode(',', $planID); + foreach($planIdList as $multiPlanID) + { + if(empty($datas[$multiPlanID])) + { + $datas[$multiPlanID] = new stdclass(); + $datas[$multiPlanID]->name = $multiPlanID; + $datas[$multiPlanID]->value = 0; + } + $datas[$multiPlanID]->value += $data->value; + } + unset($datas[$planID]); + } + } + + /* Fix bug #2697. */ + if(isset($datas[''])) + { + if(empty($datas[0])) + { + $datas[0] = new stdclass(); + $datas[0]->name = 0; + $datas[0]->value = 0; + } + $datas[0]->value += $datas['']->value; + unset($datas['']); + } + + $plans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in(array_keys($datas))->fetchPairs(); + foreach($datas as $planID => $data) $data->name = isset($plans[$planID]) ? $plans[$planID] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per status + * + * @access public + * @return array + */ + public function getDataOfStorysPerStatus() + { + $datas = $this->dao->select('status as name, count(status) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('status')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $status => $data) if(isset($this->lang->story->statusList[$status])) $data->name = $this->lang->story->statusList[$status]; + return $datas; + } + + /** + * Get report data of storys per stage + * + * @access public + * @return array + */ + public function getDataOfStorysPerStage() + { + $datas = $this->dao->select('stage as name, count(stage) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('stage')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $stage => $data) $data->name = $this->lang->story->stageList[$stage] != '' ? $this->lang->story->stageList[$stage] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per pri + * + * @access public + * @return array + */ + public function getDataOfStorysPerPri() + { + $datas = $this->dao->select('pri as name, count(pri) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('pri')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $pri => $data) $data->name = $this->lang->story->priList[$pri] != '' ? $this->lang->story->priList[$pri] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per estimate + * + * @access public + * @return array + */ + public function getDataOfStorysPerEstimate() + { + return $this->dao->select('estimate as name, count(estimate) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('estimate')->orderBy('value')->fetchAll(); + } + + /** + * Get report data of storys per openedBy + * + * @access public + * @return array + */ + public function getDataOfStorysPerOpenedBy() + { + $datas = $this->dao->select('openedBy as name, count(openedBy) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('openedBy')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); + foreach($datas as $account => $data) $data->name = isset($this->users[$account]) ? $this->users[$account] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per assignedTo + * + * @access public + * @return array + */ + public function getDataOfStorysPerAssignedTo() + { + $datas = $this->dao->select('assignedTo as name, count(assignedTo) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('assignedTo')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); + foreach($datas as $account => $data) $data->name = (isset($this->users[$account]) and $this->users[$account] != '') ? $this->users[$account] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per closedReason + * + * @access public + * @return array + */ + public function getDataOfStorysPerClosedReason() + { + $datas = $this->dao->select('closedReason as name, count(closedReason) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('closedReason')->orderBy('value DESC')->fetchAll('name'); + if(!$datas) return array(); + foreach($datas as $reason => $data) $data->name = $this->lang->story->reasonList[$reason] != '' ? $this->lang->story->reasonList[$reason] : $this->lang->report->undefined; + return $datas; + } + + /** + * Get report data of storys per change + * + * @access public + * @return array + */ + public function getDataOfStorysPerChange() + { + return $this->dao->select('(version-1) as name, count(*) as value')->from(TABLE_STORY) + ->where($this->reportCondition()) + ->groupBy('version')->orderBy('value')->fetchAll(); + } + + /** + * Get kanban group data. + * + * @param array $stories + * @access public + * @return array + */ + public function getKanbanGroupData($stories) + { + $storyGroup = array(); + foreach($stories as $story) $storyGroup[$story->stage][$story->id] = $story; + + return $storyGroup; + } + + /** + * Get toList and ccList. + * + * @param object $story + * @param string $actionType + * @access public + * @return bool|array + */ + public function getToAndCcList($story, $actionType) + { + /* Set toList and ccList. */ + $toList = $story->assignedTo; + $ccList = str_replace(' ', '', trim($story->mailto, ',')); + + /* If the action is changed or reviewed, mail to the project or execution team. */ + if(strtolower($actionType) == 'changed' or strtolower($actionType) == 'reviewed') + { + $teamMembers = $this->getTeamMembers($story->id, $actionType); + if($teamMembers) + { + $ccList .= ',' . join(',', $teamMembers); + $ccList = ltrim($ccList, ','); + } + } + + if(strtolower($actionType) == 'changed' or strtolower($actionType) == 'opened') + { + $reviewerList = $this->getReviewerPairs($story->id, $story->version); + unset($reviewerList[$story->assignedTo]); + + $ccList .= ',' . join(',', array_keys($reviewerList)); + } + + if(empty($toList)) + { + if(empty($ccList)) return false; + if(strpos($ccList, ',') === false) + { + $toList = $ccList; + $ccList = ''; + } + else + { + $commaPos = strpos($ccList, ','); + $toList = substr($ccList, 0, $commaPos); + $ccList = substr($ccList, $commaPos + 1); + } + } + elseif($toList == 'closed') + { + $toList = $story->openedBy; + } + + return array($toList, $ccList); + } + + /** + * Adjust the action clickable. + * + * @param object $story + * @param string $action + * @access public + * @return void + */ + public static function isClickable($story, $action) + { + $action = strtolower($action); + + global $app, $config; + + if($story->parent < 0 and strpos($config->story->list->actionsOpratedParentStory, ",$action,") === false) return false; + + $story->reviewer = isset($story->reviewer) ? $story->reviewer : array(); + $story->notReview = isset($story->notReview) ? $story->notReview : array(); + + $isSuperReviewer = strpos(',' . trim(zget($config->story, 'superReviewers', ''), ',') . ',', ',' . $app->user->account . ','); + + if($action == 'change') return ($isSuperReviewer !== false or count($story->reviewer) == 0 or count($story->notReview) == 0) and $story->status != 'closed'; + if($action == 'review') return (($isSuperReviewer !== false or in_array($app->user->account, $story->notReview)) and ($story->status == 'draft' or $story->status == 'changed')); + if($action == 'recall') return empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) and ($app->user->account == $story->openedBy); + if($action == 'close') return $story->status != 'closed'; + if($action == 'activate') return $story->status == 'closed'; + if($action == 'assignto') return $story->status != 'closed'; + if($action == 'createcase') return $story->type != 'requirement'; + if($action == 'batchcreate' and $story->parent > 0) return false; + if($action == 'batchcreate' and $story->type == 'requirement' and $story->status != 'closed') return $story->status != 'draft'; + if($action == 'batchcreate' and $config->vision == 'lite' and ($story->status == 'active' and ($story->stage == 'wait' or $story->stage == 'projected'))) return true; + if($action == 'batchcreate' and ($story->status != 'active' or $story->stage != 'wait' or !empty($story->plan))) return false; + + return true; + } + + /** + * Build operate menu. + * + * @param object $story + * @param string $type + * @access public + * @return string + */ + public function buildOperateMenu($story, $type = 'view', $execution = '') + { + $menu = ''; + $params = "storyID=$story->id"; + + if($type == 'browse') + { + if(common::canBeChanged('story', $story)) + { + $storyReviewer = isset($story->reviewer) ? $story->reviewer : array(); + + if($story->URChanged) return $this->buildMenu('story', 'processStoryChange', $params, $story, $type, 'search', '', 'iframe', true, '', $this->lang->confirm); + + $isClick = $this->isClickable($story, 'change'); + $title = (!$isClick and $story->status != 'closed') ? $this->lang->story->changeTip : ''; + $menu .= $this->buildMenu('story', 'change', $params . "&from=$story->from", $story, $type, 'alter', '', '', false, '', $title); + + $isClick = $this->isClickable($story, 'review'); + $title = $this->lang->story->review; + if(!$isClick and $story->status != 'closed') + { + if($story->status == 'active') + { + $title = $this->lang->story->reviewTip['active']; + } + elseif($storyReviewer and in_array($this->app->user->account, $storyReviewer)) + { + $title = $this->lang->story->reviewTip['reviewed']; + } + elseif($storyReviewer and !in_array($this->app->user->account, $storyReviewer)) + { + $title = $this->lang->story->reviewTip['notReviewer']; + } + elseif(empty($storyReviewer)) + { + $title = $this->lang->story->reviewTip['recalled']; + } + } + $menu .= $this->buildMenu('story', 'review', $params . "&from=$story->from", $story, $type, 'search', '', '', false, '', $title); + + $title = $this->lang->story->recall; + $isClick = $this->isClickable($story, 'recall'); + if(!$isClick and $story->status != 'closed') + { + if($story->status == 'draft' and empty($storyReviewer)) $title = $this->lang->story->recallTip['recalled']; + if($story->status == 'draft' and !empty($storyReviewer)) $title = $this->lang->story->recallTip['reviewed']; + if(empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) and $this->app->user->account != $story->openedBy) $title = $this->lang->story->recallTip['notOpenedBy']; + if($story->status == 'active' and empty($story->reviewer)) $title = $this->lang->story->recallTip['actived']; + } + $menu .= $this->buildMenu('story', 'recall', $params, $story, $type, 'undo', 'hiddenwin', '', '', '', $title); + + $menu .= $this->buildMenu('story', 'close', $params, $story, $type, '', '', 'iframe', true); + $menu .= $this->buildMenu('story', 'edit', $params . "&from=$story->from", $story, $type); + $tab = $this->app->tab == 'project' ? 'project' : 'qa'; + if($story->type != 'requirement' and $this->config->vision != 'lite') $menu .= $this->buildMenu('story', 'createCase', "productID=$story->product&branch=$story->branch&module=0&from=¶m=0&$params", $story, $type, 'sitemap', '', '', false, "data-app='$tab'"); + + if($this->app->rawModule != 'projectstory' OR $this->config->vision == 'lite') + { + $isClick = $this->isClickable($story, 'batchcreate'); + $title = $this->lang->story->subdivide; + if(!$isClick and $story->status != 'closed') + { + if($story->parent > 0) + { + $title = $this->lang->story->subDivideTip['subStory']; + } + else + { + if($story->status != 'active') $title = $this->lang->story->subDivideTip['notActive']; + if(!empty($story->plan)) $title = $this->lang->story->subDivideTip['planned']; + if($story->stage == 'projected') $title = $this->lang->story->subDivideTip['projected']; + } + } + $menu .= $this->buildMenu('story', 'batchCreate', "productID=$story->product&branch=$story->branch&module=$story->module&$params&executionID={$this->session->project}", $story, $type, 'split', '', '', '', '', $title); + } + if($this->app->rawModule == 'projectstory' and $this->config->vision != 'lite') $menu .= $this->buildMenu('projectstory', 'unlinkStory', "projectID={$this->session->project}&$params", $story, $type, 'unlink', 'hiddenwin'); + } + else + { + return $this->buildMenu('story', 'close', $params, $story, 'list', '', '', 'iframe', true); + } + } + + if($type == 'view') + { + $menu .= $this->buildMenu('story', 'change', $params, $story, $type, 'alter', '', 'showinonlybody'); + $menu .= $this->buildMenu('story', 'recall', $params, $story, $type, 'undo', '', 'showinonlybody'); + $menu .= $this->buildMenu('story', 'review', $params, $story, $type, 'search', '', 'showinonlybody'); + + if(!isonlybody()) $menu .= $this->buildMenu('story', 'batchCreate', "productID=$story->product&branch=$story->branch&moduleID=$story->module&$params", $story, $type, 'split', '', 'divideStory', true, "data-toggle='modal' data-type='iframe' data-width='95%'", $this->lang->story->subdivide); + + $menu .= $this->buildMenu('story', 'assignTo', $params, $story, $type, '', '', 'iframe showinonlybody', true); + $menu .= $this->buildMenu('story', 'close', $params, $story, $type, '', '', 'iframe showinonlybody', true); + $menu .= $this->buildMenu('story', 'activate', $params, $story, $type, '', '', 'iframe showinonlybody', true); + + if($this->config->edition == 'max' and $this->app->tab == 'project' and common::hasPriv('story', 'importToLib')) $menu .= html::a('#importToLib', " " . $this->lang->story->importToLib, '', 'class="btn" data-toggle="modal"'); + + /* Print testcate actions. */ + if($story->parent >= 0 and $story->type != 'requirement' and (common::hasPriv('testcase', 'create', $story) or common::hasPriv('testcase', 'batchCreate', $story))) + { + $this->app->loadLang('testcase'); + $menu .= "
    "; + $menu .= ""; + $menu .= "
    "; + } + + if($this->app->tab == 'execution' and strpos('draft,closed', $story->status) === false) $menu .= $this->buildMenu('task', 'create', "execution={$this->session->execution}&{$params}&moduleID=$story->module", $story, $type, 'plus', '', 'showinonly body'); + + $menu .= "
    "; + $menu .= $this->buildFlowMenu('story', $story, $type, 'direct'); + $menu .= "
    "; + + $menu .= $this->buildMenu('story', 'edit', $params, $story, $type); + $menu .= $this->buildMenu('story', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&{$params}&executionID=0&bugID=0&planID=0&todoID=0&extra=&type=$story->type", $story, $type, 'copy', '', '', '', "data-width='1050'"); + $menu .= $this->buildMenu('story', 'delete', $params, $story, 'button', 'trash', 'hiddenwin', 'showinonlybody', true); + } + + if($type == 'execution') + { + $hasDBPriv = common::hasDBPriv($execution, 'execution'); + $canBeChanged = common::canModify('execution', $execution); + if($canBeChanged) + { + $executionID = empty($execution) ? $this->session->execution : $execution->id; + $param = "executionID=$executionID&story={$story->id}&moduleID={$story->module}"; + + $story->reviewer = isset($story->reviewer) ? $story->reviewer : array(); + $story->notReview = isset($story->notReview) ? $story->notReview : array(); + + if(common::hasPriv('story', 'review')) + { + $reviewDisabled = in_array($this->app->user->account, $story->notReview) and ($story->status == 'draft' or $story->status == 'changed') ? '' : 'disabled'; + $story->from = 'execution'; + $menu .= common::printIcon('story', 'review', "story={$story->id}&from=story", $story, 'list', 'search', '', $reviewDisabled, false, "data-group=execution"); + } + + if(common::hasPriv('story', 'recall')) + { + $recallDisabled = empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) ? '' : 'disabled'; + $menu .= common::printIcon('story', 'recall', "story={$story->id}", $story, 'list', 'undo', 'hiddenwin', $recallDisabled, '', '', $this->lang->story->recall); + } + + $this->lang->task->create = $this->lang->execution->wbs; + $toTaskDisabled = strpos('draft,closed', $story->status) !== false ? 'disabled' : ''; + if(commonModel::isTutorialMode()) + { + $wizardParams = helper::safe64Encode($param); + $menu .= html::a(helper::createLink('tutorial', 'wizard', "module=task&method=create¶ms=$wizardParams"), "",'', "class='btn btn-task-create' title='{$this->lang->execution->wbs}' data-app='execution'"); + } + else + { + if($hasDBPriv) $menu .= common::printIcon('task', 'create', $param, '', 'list', 'plus', '', 'btn-task-create ' . $toTaskDisabled); + } + + $this->lang->task->batchCreate = $this->lang->execution->batchWBS; + if($hasDBPriv) $menu .= common::printIcon('task', 'batchCreate', "executionID=$executionID&story={$story->id}", '', 'list', 'pluses', '', $toTaskDisabled); + + $this->lang->testcase->batchCreate = $this->lang->testcase->create; + if($hasDBPriv and common::hasPriv('testcase', 'create')) + { + $menu .= html::a(helper::createLink('testcase', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&form=¶m=0&storyID=$story->id"), '', '', "class='btn' title='{$this->lang->testcase->create}' data-app='qa'"); + } + + if($canBeChanged and common::hasPriv('execution', 'storyEstimate', $execution)) + { + $menu .= common::printIcon('execution', 'storyEstimate', "executionID=$executionID&storyID=$story->id", '', 'list', 'estimate', '', 'iframe', true, "data-width='470px'"); + } + + if($canBeChanged and common::hasPriv('execution', 'unlinkStory', $execution)) + { + $menu .= common::printIcon('execution', 'unlinkStory', "executionID=$executionID&storyID=$story->id&confirm=no", '', 'list', 'unlink', 'hiddenwin'); + } + } + } + + return $menu; + } + + /** + * Merge plan title. + * + * @param int|array $productID + * @param array $stories + * @param int $branch + * + * @access public + * @return array + */ + public function mergePlanTitle($productID, $stories, $branch = 0, $type = 'story') + { + $query = $this->dao->get(); + if(is_array($branch)) + { + unset($branch[0]); + $branch = join(',', $branch); + if($branch) $branch = "0,$branch"; + } + $plans = $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) + ->Where('deleted')->eq(0) + ->beginIF($productID)->andWhere('product')->in($productID)->fi() + ->fetchPairs('id', 'title'); + + /* For requirement children. */ + if($type == 'requirement') + { + $relations = $this->dao->select('DISTINCT AID, BID')->from(TABLE_RELATION) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere('AID')->in(array_keys($stories)) + ->fetchAll(); + + $group = array(); + foreach($relations as $relation) $group[$relation->AID][] = $relation->BID; + + foreach($stories as $story) + { + if(!isset($group[$story->id])) continue; + $story->children = $this->getByList($group[$story->id]); + } + } + + $parents = array(); + $tmpStories = array(); + foreach($stories as $story) + { + $tmpStories[$story->id] = $story; + if($story->parent > 0) $parents[$story->parent] = $story->parent; + } + $parents = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($parents)->fetchAll('id'); + + foreach($stories as $storyID => $story) + { + if($story->parent > 0) + { + if(isset($stories[$story->parent])) + { + $stories[$story->parent]->children[$story->id] = $story; + unset($stories[$storyID]); + } + else + { + $parent = $parents[$story->parent]; + $story->parentName = $parent->title; + } + } + + $story->planTitle = ''; + $storyPlans = explode(',', trim($story->plan, ',')); + foreach($storyPlans as $planID) $story->planTitle .= zget($plans, $planID, '') . ' '; + } + + /* For save session query. */ + $this->dao->sqlobj->sql = $query; + return $stories; + } + + /** + * Merge story reviewers. + * + * @param array|object $stories + * @param bool $isObject + * @access public + * @return array|object + */ + public function mergeReviewer($stories, $isObject = false) + { + if($isObject) + { + $story = $stories; + $stories = (array)$stories; + $stories[$story->id] = $story; + } + + /* Set child story id into array. */ + $storyIdList = isset($stories['id']) ? array($stories['id'] => $stories['id']) : array_keys($stories); + if(isset($stories['id']) and isset($story->children)) $storyIdList = array_merge($storyIdList, array_keys($story->children)); + if(!isset($stories['id'])) + { + foreach($stories as $story) + { + if(isset($story->children)) $storyIdList = array_merge($storyIdList, array_keys($story->children)); + } + } + + $allReviewers = $this->dao->select('story,reviewer,result')->from(TABLE_STORY)->alias('t1') + ->leftJoin(TABLE_STORYREVIEW)->alias('t2')->on('t1.version=t2.version and t1.id=t2.story') + ->where('story')->in($storyIdList) + ->fetchGroup('story', 'reviewer'); + + foreach($allReviewers as $storyID => $reviewerList) + { + if(isset($stories[$storyID])) + { + $stories[$storyID]->reviewer = array_keys($reviewerList); + $stories[$storyID]->notReview = array(); + foreach($reviewerList as $reviewer => $reviewInfo) + { + if($reviewInfo->result == '') $stories[$storyID]->notReview[] = $reviewer; + } + } + else + { + foreach($stories as $id => $story) + { + if(!isset($story->children)) continue; + if(isset($story->children[$storyID])) + { + $story->children[$storyID]->reviewer = array_keys($reviewerList); + $story->children[$storyID]->notReview = array(); + foreach($reviewerList as $reviewer => $reviewInfo) + { + if($reviewInfo->result == '') $story->children[$storyID]->notReview[] = $reviewer; + } + } + } + } + } + + if($isObject) return $stories[$story->id]; + return $stories; + } + + /** + * Print cell data + * + * @param object $col + * @param object $story + * @param array $users + * @param array $branches + * @param array $storyStages + * @param array $modulePairs + * @param array $storyTasks + * @param array $storyBugs + * @param array $storyCases + * @access public + * @return void + */ + public function printCell($col, $story, $users, $branches, $storyStages, $modulePairs = array(), $storyTasks = array(), $storyBugs = array(), $storyCases = array(), $mode = 'datatable', $storyType = 'story', $execution = '', $isShowBranch = '') + { + $tab = $this->app->tab; + $executionID = empty($execution) ? $this->session->execution : $execution->id; + $account = $this->app->user->account; + $storyLink = helper::createLink('story', 'view', "storyID=$story->id"); + $canView = common::hasPriv('story', 'view'); + + if($tab == 'project') + { + $storyLink = helper::createLink('projectstory', 'view', "storyID=$story->id"); + $canView = common::hasPriv('projectstory', 'view'); + } + elseif($tab == 'execution') + { + $storyLink = helper::createLink('execution', 'storyView', "storyID=$story->id"); + $canView = common::hasPriv('execution', 'storyView'); + } + + /* Check the product is closed. */ + $canBeChanged = common::canBeChanged('story', $story); + $canOrder = common::hasPriv('execution', 'storySort'); + + $canBatchEdit = common::hasPriv('story', 'batchEdit'); + $canBatchClose = common::hasPriv('story', 'batchClose'); + $canBatchReview = common::hasPriv('story', 'batchReview'); + $canBatchChangeStage = common::hasPriv('story', 'batchChangeStage'); + $canBatchChangeBranch = common::hasPriv('story', 'batchChangeBranch'); + $canBatchChangeModule = common::hasPriv('story', 'batchChangeModule'); + $canBatchChangePlan = common::hasPriv('story', 'batchChangePlan'); + $canBatchAssignTo = common::hasPriv('story', 'batchAssignTo'); + $canBatchUnlinkStory = common::hasPriv('projectstory', 'batchUnlinkStory'); + $canBatchUnlink = common::hasPriv('execution', 'batchUnlinkStory'); + + if($tab == 'execution') + { + $checkObject = new stdclass(); + $checkObject->execution = $executionID; + + $canBatchToTask = common::hasPriv('story', 'batchToTask', $checkObject); + } + + if($tab == 'execution') + { + $canBatchAction = ($canBeChanged and ($canBatchEdit or $canBatchClose or $canBatchChangeStage or $canBatchUnlink or $canBatchToTask)); + } + else + { + $canBatchAction = ($canBatchEdit or $canBatchClose or $canBatchReview or $canBatchChangeStage or $canBatchChangeBranch or $canBatchChangeModule or $canBatchChangePlan or $canBatchAssignTo or $canBatchUnlinkStory); + } + + $id = $col->id; + if($col->show) + { + $class = "c-{$id}"; + $title = ''; + $style = ''; + + if($id == 'assignedTo') + { + $title = zget($users, $story->assignedTo, $story->assignedTo); + if($story->assignedTo == $account) $class .= ' red'; + } + elseif($id == 'openedBy') + { + $title = zget($users, $story->openedBy, $story->openedBy); + } + elseif($id == 'title') + { + $title = $story->title; + if(!empty($story->children)) $class .= ' has-child'; + } + elseif($id == 'plan') + { + $title = isset($story->planTitle) ? $story->planTitle : ''; + $class .= ' text-ellipsis'; + } + elseif($id == 'sourceNote') + { + $title = $story->sourceNote; + $class .= ' text-ellipsis'; + } + elseif($id == 'category') + { + $title = zget($this->lang->story->categoryList, $story->category); + } + elseif($id == 'estimate') + { + $title = $story->estimate . ' ' . $this->lang->hourCommon; + } + elseif($id == 'reviewedBy') + { + $reviewedBy = ''; + foreach(explode(',', $story->reviewedBy) as $user) $reviewedBy .= zget($users, $user) . ' '; + $story->reviewedBy = trim($reviewedBy); + + $title = $reviewedBy; + $class .= ' text-ellipsis'; + } + elseif($id == 'stage') + { + $style .= 'overflow: visible;'; + if(isset($storyStages[$story->id])) + { + foreach($storyStages[$story->id] as $storyBranch => $storyStage) + { + if(isset($branches[$storyBranch])) $title .= $branches[$storyBranch] . ": " . $this->lang->story->stageList[$storyStage->stage] . "\n"; + } + } + } + elseif($id == 'feedbackBy') + { + $title = $story->feedbackBy; + } + elseif($id == 'notifyEmail') + { + $title = $story->notifyEmail; + } + elseif($id == 'actions') + { + $class .= ' text-center'; + } + elseif($id == 'order') + { + $class = 'sort-handler c-sort'; + } + + echo ""; + if($this->config->edition != 'open') $this->loadModel('flow')->printFlowCell('story', $story, $id); + switch($id) + { + case 'id': + if($canBatchAction) + { + echo html::checkbox('storyIdList', array($story->id => '')) . html::a($storyLink, sprintf('%03d', $story->id), '', "data-app='$tab'"); + } + else + { + printf('%03d', $story->id); + } + break; + case 'order': + echo ""; + break; + case 'pri': + echo ""; + echo zget($this->lang->story->priList, $story->pri, $story->pri); + echo ""; + break; + case 'title': + if($tab == 'project') + { + $showBranch = isset($this->config->projectstory->story->showBranch) ? $this->config->projectstory->story->showBranch : 1; + } + elseif($tab == 'execution') + { + if($isShowBranch) $showBranch = isset($this->config->execution->story->showBranch) ? $this->config->execution->story->showBranch : 1; + } + else + { + $showBranch = isset($this->config->product->browse->showBranch) ? $this->config->product->browse->showBranch : 1; + } + if($storyType == 'requirement') echo 'SR '; + if($story->parent > 0 and isset($story->parentName)) $story->title = "{$story->parentName} / {$story->title}"; + if(isset($branches[$story->branch]) and $showBranch and $this->config->vision == 'rnd') echo "branch]}>{$branches[$story->branch]} "; + if($story->module and isset($modulePairs[$story->module])) echo "{$modulePairs[$story->module]} "; + if($story->parent > 0) echo '' . $this->lang->story->childrenAB . ' '; + echo $canView ? html::a($storyLink, $story->title, '', "style='color: $story->color' data-app='$tab'") : "{$story->title}"; + if(!empty($story->children)) echo ''; + break; + case 'plan': + echo isset($story->planTitle) ? $story->planTitle : ''; + break; + case 'branch': + echo zget($branches, $story->branch, ''); + break; + case 'keywords': + echo $story->keywords; + break; + case 'source': + echo zget($this->lang->story->sourceList, $story->source, $story->source); + break; + case 'sourceNote': + echo $story->sourceNote; + break; + case 'category': + echo zget($this->lang->story->categoryList, $story->category); + break; + case 'status': + if($story->URChanged) + { + print("{$this->lang->story->URChanged}"); + break; + } + echo ""; + echo $this->processStatus('story', $story); + echo ''; + break; + case 'estimate': + echo $story->estimate . $this->config->hourUnit; + break; + case 'stage': + if(isset($storyStages[$story->id]) and !empty($branches)) + { + echo "'; + } + else + { + echo $this->lang->story->stageList[$story->stage]; + } + break; + case 'taskCount': + $tasksLink = helper::createLink('story', 'tasks', "storyID=$story->id", '', 'class="iframe"'); + $storyTasks[$story->id] > 0 ? print(html::a($tasksLink, $storyTasks[$story->id], '', 'class="iframe"')) : print(0); + break; + case 'bugCount': + $bugsLink = helper::createLink('story', 'bugs', "storyID=$story->id"); + $storyBugs[$story->id] > 0 ? print(html::a($bugsLink, $storyBugs[$story->id], '', 'class="iframe"')) : print(0); + break; + case 'caseCount': + $casesLink = helper::createLink('story', 'cases', "storyID=$story->id"); + $storyCases[$story->id] > 0 ? print(html::a($casesLink, $storyCases[$story->id], '', 'class="iframe"')) : print(0); + break; + case 'openedBy': + echo zget($users, $story->openedBy, $story->openedBy); + break; + case 'openedDate': + echo substr($story->openedDate, 5, 11); + break; + case 'assignedTo': + $this->printAssignedHtml($story, $users); + break; + case 'assignedDate': + echo substr($story->assignedDate, 5, 11); + break; + case 'reviewedBy': + echo $story->reviewedBy; + break; + case 'reviewedDate': + echo substr($story->reviewedDate, 5, 11); + break; + case 'closedBy': + echo zget($users, $story->closedBy, $story->closedBy); + break; + case 'closedDate': + echo substr($story->closedDate, 5, 11); + break; + case 'closedReason': + echo zget($this->lang->story->reasonList, $story->closedReason, $story->closedReason); + break; + case 'lastEditedBy': + echo zget($users, $story->lastEditedBy, $story->lastEditedBy); + break; + case 'lastEditedDate': + echo substr($story->lastEditedDate, 5, 11); + break; + case 'feedbackBy': + echo $story->feedbackBy; + break; + case 'notifyEmail': + echo $story->notifyEmail; + break; + case 'mailto': + $mailto = explode(',', $story->mailto); + foreach($mailto as $account) + { + $account = trim($account); + if(empty($account)) continue; + echo zget($users, $account) . '  '; + } + break; + case 'version': + echo $story->version; + break; + case 'actions': + if($tab == 'execution') + { + $menuType = 'execution'; + } + else + { + $menuType = 'browse'; + $execution = ''; + } + echo $this->buildOperateMenu($story, $menuType, $execution); + break; + } + echo ''; + } + } + + /** + * Product module story page add assignment function. + * + * @param object $story + * @param array $users + * @access public + * @return string + */ + public function printAssignedHtml($story, $users) + { + $btnTextClass = ''; + $assignedToText = zget($users, $story->assignedTo); + + if(empty($story->assignedTo)) + { + $btnTextClass = 'text-primary'; + $assignedToText = $this->lang->task->noAssigned; + } + if($story->assignedTo == $this->app->user->account) $btnTextClass = 'text-red'; + + $btnClass = $story->assignedTo == 'closed' ? ' disabled' : ''; + $btnClass = "iframe btn btn-icon-left btn-sm {$btnClass}"; + $assignToLink = helper::createLink('story', 'assignTo', "storyID=$story->id", '', true); + $assignToHtml = html::a($assignToLink, " {$assignedToText}", '', "class='$btnClass'"); + + echo !common::hasPriv('story', 'assignTo', $story) ? "{$assignedToText}" : $assignToHtml; + } + + /** + * Set report condition. + * + * @access public + * @return string + */ + public function reportCondition() + { + if(isset($_SESSION['storyQueryCondition'])) + { + if(!$this->session->storyOnlyCondition) + { + preg_match_all('/' . TABLE_STORY .' AS ([\w]+) /', $this->session->storyQueryCondition, $matches); + if(isset($matches[1][0])) return 'id in (' . preg_replace('/SELECT .* FROM/', "SELECT {$matches[1][0]}.id FROM", $this->session->storyQueryCondition) . ')'; + } + return $this->session->storyQueryCondition; + } + return true; + } + + /** + * Check force review for user. + * + * @access public + * @return bool + */ + public function checkForceReview() + { + $forceReview = false; + + $forceField = $this->config->story->needReview == 0 ? 'forceReview' : 'forceNotReview'; + $forceReviewRoles = !empty($this->config->story->{$forceField . 'Roles'}) ? $this->config->story->{$forceField . 'Roles'} : ''; + $forceReviewDepts = !empty($this->config->story->{$forceField . 'Depts'}) ? $this->config->story->{$forceField . 'Depts'} : ''; + + $forceUsers = ''; + if(!empty($this->config->story->{$forceField})) $forceUsers = $this->config->story->{$forceField}; + + if(!empty($forceReviewRoles) or !empty($forceReviewDepts)) + { + $users = $this->dao->select('account')->from(TABLE_USER) + ->where('deleted')->eq(0) + ->andWhere(0, true) + ->beginIF(!empty($forceReviewRoles)) + ->orWhere('(role', true)->in($forceReviewRoles) + ->andWhere('role')->ne('') + ->markRight(1) + ->fi() + ->beginIF(!empty($forceReviewDepts))->orWhere('dept')->in($forceReviewDepts)->fi() + ->markRight(1) + ->fetchAll('account'); + + $forceUsers .= "," . implode(',', array_keys($users)); + } + + $forceReview = $this->config->story->needReview == 0 ? strpos(",{$forceUsers},", ",{$this->app->user->account},") !== false : strpos(",{$forceUsers},", ",{$this->app->user->account},") === false; + + return $forceReview; + } + + /** + * Get tracks. + * + * @param int $productID + * @param int $branch + * @param int $projectID + * @param object $pager + * @access public + * @return bool|array + */ + public function getTracks($productID = 0, $branch = 0, $projectID = 0, $pager = null) + { + $tracks = array(); + $sourcePageID = $pager->pageID; + $excludeStories = false; + + if($this->config->URAndSR) + { + $projectStories = array(); + if($projectID) + { + $requirements = $this->dao->select('t3.*')->from(TABLE_PROJECTSTORY)->alias('t1') + ->leftJoin(TABLE_RELATION)->alias('t2')->on("t1.story=t2.AID && t2.AType='story'") + ->leftJoin(TABLE_STORY)->alias('t3')->on("t2.BID=t3.id && t2.BType='requirement' && t3.deleted='0'") + ->where('t1.project')->eq($projectID) + ->andWhere('t1.product')->eq($productID) + ->andWhere('t3.id')->ne('') + ->page($pager, 't3.id') + ->fetchAll('id'); + $projectStories = $this->getExecutionStories($projectID, $productID, $branch, '`order`_desc', 'all', 0, 'story'); + } + else + { + $requirements = $this->getProductStories($productID, $branch, 0, 'all', 'requirement', 'id_desc', true, '', $pager); + } + + if($pager->pageID != $sourcePageID) + { + $requirements = array(); + $pager->pageID = $sourcePageID; + } + + foreach($requirements as $requirement) + { + $stories = $this->getRelation($requirement->id, 'requirement'); + $stories = empty($stories) ? array() : $stories; + foreach($stories as $id => $title) + { + if($projectStories and !isset($projectStories[$id])) + { + unset($stories[$id]); + continue; + } + + $stories[$id] = new stdclass(); + $stories[$id]->title = $title; + $stories[$id]->cases = $this->loadModel('testcase')->getStoryCases($id); + $stories[$id]->bugs = $this->loadModel('bug')->getStoryBugs($id); + $stories[$id]->tasks = $this->loadModel('task')->getStoryTasks($id); + if($this->config->edition == 'max') + { + $stories[$id]->designs = $this->dao->select('id, name')->from(TABLE_DESIGN) + ->where('story')->eq($id) + ->andWhere('deleted')->eq('0') + ->fetchAll('id'); + $stories[$id]->revisions = $this->dao->select('BID, t2.comment')->from(TABLE_RELATION)->alias('t1') + ->leftjoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.BID = t2.id') + ->where('t1.AType')->eq('design') + ->andWhere('t1.BType')->eq('commit') + ->andWhere('t1.AID')->in(array_keys($stories[$id]->designs)) + ->fetchPairs(); + } + } + + $requirement->track = $stories; + } + + $tracks = $requirements; + + /* Get no requirements story. */ + $excludeStories = $this->dao->select('t1.BID')->from(TABLE_RELATION)->alias('t1') + ->leftJoin(TABLE_STORY)->alias('t2')->on("t1.AID=t2.id") + ->where('t2.deleted')->eq('0') + ->andWhere('t1.AType')->eq('requirement') + ->andWhere('t1.BType')->eq('story') + ->andWhere('t1.relation')->eq('subdivideinto') + ->andWhere('t1.product')->eq($productID) + ->fetchPairs('BID', 'BID'); + if($projectID) + { + $stories = $this->getExecutionStories($projectID, $productID, $branch, '`order`_desc', 'all', 0, 'story', $excludeStories); + } + else + { + $stories = $this->getProductStories($productID, $branch, 0, 'all', 'story', 'id_desc', true, $excludeStories); + } + } + else + { + if($projectID) + { + $stories = $this->getExecutionStories($projectID, $productID, $branch, '`order`_desc', 'all', 0, 'story', $excludeStories, $pager); + } + else + { + $stories = $this->getProductStories($productID, $branch, 0, 'all', 'story', 'id_desc', true, $excludeStories, $pager); + } + } + + if(count($tracks) < $pager->recPerPage) + { + /* Show sub stories. */ + $storiesCopy = array(); + foreach($stories as $id => $story) + { + $storiesCopy[$id] = $story; + + if(!isset($story->children) or count($story->children) == 0) continue; + foreach($story->children as $childID => $children) + { + $storiesCopy[$childID] = $children; + } + } + $stories = $storiesCopy; + + foreach($stories as $id => $story) + { + $stories[$id] = new stdclass(); + $stories[$id]->parent = $story->parent; + $stories[$id]->title = $story->title; + $stories[$id]->cases = $this->loadModel('testcase')->getStoryCases($id); + $stories[$id]->bugs = $this->loadModel('bug')->getStoryBugs($id); + $stories[$id]->tasks = $this->loadModel('task')->getStoryTasks($id); + if($this->config->edition == 'max') + { + $stories[$id]->designs = $this->dao->select('id, name')->from(TABLE_DESIGN) + ->where('story')->eq($id) + ->andWhere('deleted')->eq('0') + ->fetchAll('id'); + $stories[$id]->revisions = $this->dao->select('BID, t2.comment')->from(TABLE_RELATION)->alias('t1') + ->leftjoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.BID = t2.id') + ->where('t1.AType')->eq('design') + ->andWhere('t1.BType')->eq('commit') + ->andWhere('t1.AID')->in(array_keys($stories[$id]->designs)) + ->fetchPairs(); + } + } + + $tracks['noRequirement'] = $stories; + if($this->config->URAndSR) $pager->recTotal += 1; + } + + return $tracks; + } + + /** + * Get track by id. + * + * @param int $storyID + * @access public + * @return bool|array + */ + public function getTrackByID($storyID) + { + $requirement = $this->getByID($storyID); + + $stories = $this->getRelation($requirement->id, 'requirement'); + $track = array(); + $stories = empty($stories) ? array() : $stories; + foreach($stories as $id => $title) + { + $track[$id] = new stdclass(); + $track[$id]->title = $title; + $track[$id]->case = $this->loadModel('testcase')->getStoryCases($id); + $track[$id]->bug = $this->loadModel('bug')->getStoryBugs($id); + $track[$id]->story = $this->getByID($id); + $track[$id]->task = $this->loadModel('task')->getStoryTasks($id); + if($this->config->edition == 'max') + { + $track[$id]->design = $this->dao->select('id, name')->from(TABLE_DESIGN) + ->where('story')->eq($id) + ->andWhere('deleted')->eq('0') + ->fetchAll('id'); + $track[$id]->revision = $this->dao->select('BID, extra')->from(TABLE_RELATION)->where('AType')->eq('design')->andWhere('BType')->eq('commit')->andWhere('AID')->in(array_keys($track[$id]->design))->fetchPairs(); + } + } + + return $track; + } + + /** + * Obtain the direct relationship between UR and SR. + * + * @param int $storyID + * @param string $storyType + * @param array $fields + * @access public + * @return array + */ + public function getStoryRelation($storyID, $storyType, $fields = array()) + { + $conditionField = $storyType == 'story' ? 'BID' : 'AID'; + $storyType = $storyType == 'story' ? 'AID' : 'BID'; + + $relations = $this->dao->select($storyType)->from(TABLE_RELATION) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere($conditionField)->eq($storyID) + ->fetchPairs(); + + if(empty($relations)) return array(); + + $fields = empty($fields) ? '*' : implode(',', $fields); + $story = $this->dao->select($fields)->from(TABLE_STORY) + ->where('id')->in($relations) + ->andWhere('deleted')->eq(0) + ->orderBy('id_desc') + ->fetchAll(); + + return $story; + } + + /** + * Get story relation by Ids. + * + * @param array $storyIdList + * @param string $storyType + * @access public + * @return array + */ + public function getStoryRelationByIds($storyIdList, $storyType) + { + $conditionField = $storyType == 'story' ? 'BID' : 'AID'; + $storyType = $storyType == 'story' ? 'BID, GROUP_CONCAT(`AID` SEPARATOR ",")' : 'AID, GROUP_CONCAT(`BID` SEPARATOR ",")'; + + $relations = $this->dao->select($storyType)->from(TABLE_RELATION) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere($conditionField)->in($storyIdList) + ->groupBy($conditionField) + ->fetchPairs(); + + return $relations; + } + + /** + * Link a story. + * + * @param int $executionID + * @param int $productID + * @param int $storyID + * @access public + * @return void + */ + public function linkStory($executionID, $productID, $storyID) + { + $lastOrder = (int)$this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->orderBy('order_desc')->limit(1)->fetch('order'); + $this->dao->insert(TABLE_PROJECTSTORY) + ->set('project')->eq($executionID) + ->set('product')->eq($productID) + ->set('story')->eq($storyID) + ->set('version')->eq(1) + ->set('order')->eq($lastOrder + 1) + ->exec(); + } + + /** + * Link stories. + * + * @param int $storyID + * @access public + * @return void + */ + public function linkStories($storyID) + { + $story = $this->getByID($storyID); + $stories = $this->post->stories; + $isStory = ($story->type == 'story'); + + foreach($stories as $id) + { + $requirement = $this->getByID($id); + $data = new stdclass(); + $data->AType = 'requirement'; + $data->BType = 'story'; + $data->product = $story->product; + $data->relation = 'subdivideinto'; + $data->AID = $isStory ? $id : $storyID; + $data->BID = $isStory ? $storyID : $id; + $data->AVersion = $isStory ? $requirement->version : $story->version; + $data->BVersion = $isStory ? $story->version : $requirement->version; + + $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); + + $data->AType = 'story'; + $data->BType = 'requirement'; + $data->relation = 'subdividedfrom'; + $data->product = $story->product; + $data->AID = $isStory ? $storyID : $id; + $data->BID = $isStory ? $id : $storyID; + $data->AVersion = $isStory ? $story->version : $requirement->version; + $data->BVersion = $isStory ? $requirement->version : $story->version; + + $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); + } + } + + /** + * Unlink story. + * + * @param int $storyID + * @param int $linkedStoryID + * @access public + * @return void + */ + public function unlinkStory($storyID, $linkedStoryID) + { + $idList = "$storyID,$linkedStoryID"; + + $this->dao->delete()->from(TABLE_RELATION) + ->where('AType')->in('story,requirement') + ->andWhere('BType')->in('story,requirement') + ->andWhere('relation')->in('subdivideinto,subdividedfrom') + ->andWhere('AID')->in($idList) + ->andWhere('BID')->in($idList) + ->exec(); + + return !dao::isError(); + } + + /** + * Get associated requirements. + * + * @param int $storyID + * @param string $storyType + * @param array $fields + * @access public + * @return array + */ + public function getRelation($storyID, $storyType, $fields = array()) + { + $BType = $storyType == 'story' ? 'requirement' : 'story'; + $relation = $storyType == 'story' ? 'subdividedfrom' : 'subdivideinto'; + + $relations = $this->dao->select('BID')->from(TABLE_RELATION) + ->where('AType')->eq($storyType) + ->andWhere('BType')->eq($BType) + ->andWhere('relation')->eq($relation) + ->andWhere('AID')->eq($storyID) + ->fetchPairs(); + + if(empty($relations)) return array(); + + $queryFields = empty($fields) ? 'id,title' : implode(',', $fields); + if(!empty($fields)) return $this->dao->select($queryFields)->from(TABLE_STORY)->where('id')->in($relations)->andWhere('deleted')->eq(0)->fetchAll('id'); + return $this->dao->select($queryFields)->from(TABLE_STORY)->where('id')->in($relations)->andWhere('deleted')->eq(0)->fetchPairs(); + } + + /** + * Get software requirements associated with user needs. + * + * @param array $storyID + * @param string $storyType + * @access public + * @return int + */ + public function getStoryRelationCounts($storyID, $storyType = '') + { + $selectField = ($storyType == 'story') ? 'AID' : 'BID'; + $conditionField = ($storyType == 'story') ? 'BID' : 'AID'; + + $relations = $this->dao->select('count('. $selectField .') as id')->from(TABLE_RELATION) + ->where('AType')->eq('requirement') + ->andWhere('BType')->eq('story') + ->andWhere('relation')->eq('subdivideinto') + ->andWhere($conditionField)->eq($storyID) + ->fetch('id'); + + return $relations; + } + + /** + * Get estimate info + * + * @param int $storyID + * @param int $round + * @access public + * @return bool|array + */ + public function getEstimateInfo($storyID, $round = 0) + { + $estimateInfo = $this->dao->select('*')->from(TABLE_STORYESTIMATE) + ->where('story')->eq($storyID) + ->beginIf($round)->andWhere('round')->eq($round)->fi() + ->orderBy('round_desc') + ->fetch(); + + if(!empty($estimateInfo)) $estimateInfo->estimate = json_decode($estimateInfo->estimate); + return $estimateInfo; + } + + /** + * Get estimate rounds. + * + * @param int $storyID + * @access public + * @return array + */ + public function getEstimateRounds($storyID) + { + $lastRound = $this->dao->select('round')->from(TABLE_STORYESTIMATE) + ->where('story')->eq($storyID) + ->orderBy('round_desc') + ->fetch('round'); + if(!$lastRound) return array(); + + $rounds = array(); + for($i = 1; $i <= $lastRound; $i++) + { + $rounds[$i] = sprintf($this->lang->story->storyRound, $i); + } + + return $rounds; + } + + /** + * Save estimate information. + * + * @param int $storyID + * @access public + * @return void + */ + public function saveEstimateInfo($storyID) + { + $data = fixer::input('post')->get(); + + $lastRound = $this->dao->select('round')->from(TABLE_STORYESTIMATE) + ->where('story')->eq($storyID) + ->orderBy('round_desc') + ->fetch('round'); + + $estimates = array(); + foreach($data->account as $key => $account) + { + $estimates[$account]['account'] = $account; + if(!empty($data->estimate[$key]) and !is_numeric($data->estimate[$key])) + { + dao::$errors[] = $this->lang->story->estimateMustBeNumber; + return false; + } + + if(!empty($data->estimate[$key]) and $data->estimate[$key] < 0) + { + dao::$errors[] = $this->lang->story->estimateMustBePlus; + return false; + } + $estimates[$account]['estimate'] = strpos($data->estimate[$key], '-') !== false ? (int)$data->estimate[$key] : (float)$data->estimate[$key]; + } + + + $storyEstimate = new stdclass(); + $storyEstimate->story = $storyID; + $storyEstimate->round = empty($lastRound) ? 1 : $lastRound + 1; + $storyEstimate->estimate = json_encode($estimates); + $storyEstimate->average = $data->average; + $storyEstimate->openedBy = $this->app->user->account; + $storyEstimate->openedDate = helper::now(); + + $this->dao->insert(TABLE_STORYESTIMATE)->data($storyEstimate)->exec(); + } + + /** + * Update the story order according to the plan. + * + * @param int $planID + * @param array $sortIDList + * @param string $orderBy + * @param int $pageID + * @param int $recPerPage + * @access public + * @return void + */ + public function sortStoriesOfPlan($planID, $sortIDList, $orderBy = 'id_desc', $pageID = 1, $recPerPage = 100) + { + /* Append id for secend sort. */ + $orderBy = common::appendOrder($orderBy); + + /* Get all stories by plan. */ + $stories = $this->getPlanStories($planID, 'all', $orderBy); + $storyIDList = array_keys($stories); + + /* Calculate how many numbers there are before the sort list and after the sort list. */ + $frontStoryCount = $recPerPage * ($pageID - 1); + $behindStoryCount = $recPerPage * $pageID; + $frontStoryIDList = array_slice($storyIDList, 0, $frontStoryCount); + $behindStoryIDList = array_slice($storyIDList, $behindStoryCount, count($storyIDList) - $behindStoryCount); + + /* Merge to get a new sort list. */ + $newSortIDList = array_merge($frontStoryIDList, $sortIDList, $behindStoryIDList); + if(strpos($orderBy, 'order_desc') !== false) $newSortIDList = array_reverse($newSortIDList); + + /* Loop update the story order of plan. */ + $order = 1; + foreach($newSortIDList as $storyID) + { + $this->dao->update(TABLE_PLANSTORY)->set('`order`')->eq($order)->where('story')->eq($storyID)->andWhere('plan')->eq($planID)->exec(); + $order++; + } + } + + /** + * Replace story lang to requirement. + * + * @param string $type + * @access public + * @return void + */ + public function replaceURLang($type) + { + if($type == 'requirement') + { + $storyLang = $this->lang->story; + $SRCommon = $this->lang->SRCommon; + $URCommon = $this->lang->URCommon; + + $storyLang->create = str_replace($SRCommon, $URCommon, $storyLang->create); + $storyLang->changeAction = str_replace($SRCommon, $URCommon, $storyLang->changeAction); + $storyLang->changed = str_replace($SRCommon, $URCommon, $storyLang->changed); + $storyLang->assignAction = str_replace($SRCommon, $URCommon, $storyLang->assignAction); + $storyLang->reviewAction = str_replace($SRCommon, $URCommon, $storyLang->reviewAction); + $storyLang->subdivideAction = str_replace($SRCommon, $URCommon, $storyLang->subdivideAction); + $storyLang->closeAction = str_replace($SRCommon, $URCommon, $storyLang->closeAction); + $storyLang->activateAction = str_replace($SRCommon, $URCommon, $storyLang->activateAction); + $storyLang->deleteAction = str_replace($SRCommon, $URCommon, $storyLang->deleteAction); + $storyLang->view = str_replace($SRCommon, $URCommon, $storyLang->view); + $storyLang->linkStory = str_replace($SRCommon, $URCommon, $storyLang->linkStory); + $storyLang->unlinkStory = str_replace($SRCommon, $URCommon, $storyLang->unlinkStory); + $storyLang->exportAction = str_replace($SRCommon, $URCommon, $storyLang->exportAction); + $storyLang->zeroCase = str_replace($SRCommon, $URCommon, $storyLang->zeroCase); + $storyLang->zeroTask = str_replace($SRCommon, $URCommon, $storyLang->zeroTask); + $storyLang->copyTitle = str_replace($SRCommon, $URCommon, $storyLang->copyTitle); + $storyLang->common = str_replace($SRCommon, $URCommon, $storyLang->common); + $storyLang->title = str_replace($SRCommon, $URCommon, $storyLang->title); + $storyLang->spec = str_replace($SRCommon, $URCommon, $storyLang->spec); + $storyLang->children = str_replace($SRCommon, $URCommon, $storyLang->children); + $storyLang->linkStories = str_replace($SRCommon, $URCommon, $storyLang->linkStories); + $storyLang->childStories = str_replace($SRCommon, $URCommon, $storyLang->childStories); + $storyLang->duplicateStory = str_replace($SRCommon, $URCommon, $storyLang->duplicateStory); + $storyLang->newStory = str_replace($SRCommon, $URCommon, $storyLang->newStory); + $storyLang->copy = str_replace($SRCommon, $URCommon, $storyLang->copy); + $storyLang->total = str_replace($SRCommon, $URCommon, $storyLang->total); + $storyLang->released = str_replace($SRCommon, $URCommon, $storyLang->released); + $storyLang->legendLifeTime = str_replace($SRCommon, $URCommon, $storyLang->legendLifeTime); + $storyLang->legendLinkStories = str_replace($SRCommon, $URCommon, $storyLang->legendLinkStories); + $storyLang->legendChildStories = str_replace($SRCommon, $URCommon, $storyLang->legendChildStories); + $storyLang->legendSpec = str_replace($SRCommon, $URCommon, $storyLang->legendSpec); + + $storyLang->report->charts['storysPerProduct'] = str_replace($SRCommon, $URCommon, $storyLang->report->charts['storysPerProduct']); + $storyLang->report->charts['storysPerModule'] = str_replace($SRCommon, $URCommon, $storyLang->report->charts['storysPerModule']); + $storyLang->report->charts['storysPerSource'] = str_replace($SRCommon, $URCommon, $storyLang->report->charts['storysPerSource']); + } + } + + /** + * Get story reviewer pairs. + * + * @param int $storyID + * @param int version + * @access public + * @return array + */ + public function getReviewerPairs($storyID, $version) + { + return $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($version)->fetchPairs('reviewer', 'result'); + } + + /** + * Set story status by review rules. + * + * @param array $reviewerList + * @access public + * @return string + */ + public function setStatusByReviewRules($reviewerList) + { + $status = ''; + $passCount = 0; + $rejectCount = 0; + $reviewRule = $this->config->story->reviewRules; + foreach($reviewerList as $reviewer => $reviewResult) + { + $passCount = $reviewResult == 'pass' ? $passCount + 1 : $passCount; + $rejectCount = $reviewResult == 'reject' ? $rejectCount + 1 : $rejectCount; + } + + if($reviewRule == 'allpass') + { + if($passCount == count($reviewerList)) $status = 'active'; + if($rejectCount == count($reviewerList)) $status = 'closed'; + } + + if($reviewRule == 'halfpass') + { + if($passCount >= floor(count($reviewerList) / 2) + 1) $status = 'active'; + if($rejectCount >= floor(count($reviewerList) / 2) + 1) $status = 'closed'; + } + + return $status; + } + + /** + * Record story review actions. + * + * @param object $story + * @param string $result + * @param string $reason + * @access public + * @return int|string + */ + public function recordReviewAction($story, $result = '', $reason = '') + { + $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); + + $comment = isset($_POST['comment']) ? $this->post->comment : ''; + + if($isSuperReviewer !== false and $this->app->rawMethod != 'edit') + { + $actionID = $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . '|superReviewer'); + return $actionID; + } + + $reasonParam = $result == 'reject' ? ',' . $reason : ''; + $reviewers = $this->getReviewerPairs($story->id, $story->version); + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + + $actionID = !empty($result) ? $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . $reasonParam) : ''; + + if($result != 'revert') + { + if($story->status == 'closed') $this->action->create('story', $story->id, 'ReviewRejected'); + if($story->status == 'active') $this->action->create('story', $story->id, 'ReviewPassed'); + if(!array_diff(array_keys($reviewers), $reviewedBy) and ($story->status == 'draft' or $story->status == 'changed')) $this->action->create('story', $story->id, 'ReviewClarified'); + } + + return $actionID; + } + + /** + * Update the story fields value by review. + * + * @param int $storyID + * @param object $oldStory + * @param object $story + * @access public + * @return object + */ + public function updateStoryByReview($storyID, $oldStory, $story) + { + $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); + if($isSuperReviewer !== false) return $this->superReview($storyID, $oldStory, $story); + + $now = helper::now(); + $reviewerList = $this->getReviewerPairs($storyID, $oldStory->version); + $reviewedBy = explode(',', trim($story->reviewedBy, ',')); + if(!array_diff(array_keys($reviewerList), $reviewedBy)) + { + $status = $this->post->result == 'revert' ? 'active' : $this->setStatusByReviewRules($reviewerList); + $story->status = $status ? $status : $oldStory->status; + if($story->status == 'closed') + { + $story->closedBy = $this->app->user->account; + $story->closedDate = $now; + $story->assignedTo = 'closed'; + $story->assignedDate = $now; + $story->stage = 'closed'; + if($this->post->closedReason == 'done') $story->stage = 'released'; + } + } + + return $story; + } + + /** + * To review for super reviewer. + * + * @param int $storyID + * @param object $oldStory + * @param object $story + * @param string $result + * @param string $reason + * @access public + * @return object + */ + public function superReview($storyID, $oldStory, $story, $result = '', $reason = '') + { + $now = helper::now(); + $status = ''; + $result = isset($_POST['result']) ? $this->post->result : $result; + if(strpos('revert,pass', $result) !== false) $status = 'active'; + if($result == 'reject') $status = 'closed'; + if($result == 'clarify' or empty($status)) $status = $oldStory->status; + + $story->status = $status; + + if($story->status == 'closed') + { + $story->closedBy = $this->app->user->account; + $story->closedDate = $now; + $story->assignedTo = 'closed'; + $story->assignedDate = $now; + $story->stage = 'closed'; + $story->closedReason = isset($_POST['closedReason']) ? $_POST['closedReason'] : $reason; + if($_POST['closedReason'] == 'done') $story->stage = 'released'; + } + + $this->dao->delete()->from(TABLE_STORYREVIEW) + ->where('story')->eq($storyID) + ->andWhere('version')->eq($oldStory->version) + ->andWhere('result')->eq('') + ->exec(); + + return $story; + } +} diff --git a/module/story:q/view/activate.html.php b/module/story:q/view/activate.html.php new file mode 100644 index 0000000000..18c08dfe88 --- /dev/null +++ b/module/story:q/view/activate.html.php @@ -0,0 +1,60 @@ + + * @package story + * @version $Id: activate.html.php 4129 2013-01-18 01:58:14Z wwccss $ + * @link http://www.zentao.net + */ +?> + +
    +
    +
    +

    + id;?> + title'>" . $story->title . '') : html::a($this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> + + arrow . $lang->story->activate;?> + +

    +
    +
    + + + + + + + + + + printExtendFields($story, 'table');?> + + + + + + + +
    story->assignedTo;?>closedBy, 'class="form-control chosen"');?>
    story->status;?> + story->statusList['']); + unset($lang->story->statusList['closed']); + echo html::select('status', $lang->story->statusList, 'active', 'class="form-control chosen"'); + ?> +
    story->comment;?>
    + story->activate, '', 'btn btn-wide btn-primary'); + echo html::linkButton($lang->goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id"), 'self', '', 'btn btn-wide'); + ?> +
    +
    +
    +
    +
    +
    + diff --git a/module/story:q/view/affected.html.php b/module/story:q/view/affected.html.php new file mode 100644 index 0000000000..b47ed04ea6 --- /dev/null +++ b/module/story:q/view/affected.html.php @@ -0,0 +1,102 @@ +
    + +
    +
    + executions as $executionID => $execution):?> +
    name ?>   + teams[$executionID])):?> + teams[$executionID] as $member) echo zget($users, $member->account) . ' ';?> + +
    + + + + + + + + + + + + tasks[$executionID])):?> + + tasks[$executionID] as $task):?> + + + + + + + + + + + +
    task->id;?>task->name;?>task->assignedTo;?>task->status;?>task->consumed;?>task->left;?>
    id;?>createLink('task', 'view', "taskID=$task->id"), $task->name, '_blank');?>assignedTo);?> + processStatus('task', $task);?> + consumed;?>left;?>
    + +
    +
    + + + + + + + + + + + + + + bugs as $bug):?> + + + + + + + + + + + +
    idAB;?>bug->title;?>statusAB;?>bug->openedBy;?>bug->resolvedBy;?>bug->resolution;?>bug->lastEditedBy;?>
    id;?>createLink('bug', 'view', "bugID=$bug->id"), $bug->title, '_blank');?> + processStatus('bug', $bug);?> + openedBy);?>resolvedBy);?>bug->resolutionList[$bug->resolution];?>lastEditedBy);?>
    +
    +
    + + + + + + + + + + + + cases as $case):?> + + + + + + + + + +
    idAB;?>testcase->title;?>statusAB;?>testcase->openedBy;?>testcase->lastEditedBy;?>
    id;?>createLink('testcase', 'view', "caseID=$case->id"), $case->title, '_blank');?> + processStatus('testcase', $case);?> + openedBy);?>lastEditedBy);?>
    +
    +
    +
    diff --git a/module/story:q/view/assignto.html.php b/module/story:q/view/assignto.html.php new file mode 100644 index 0000000000..8db1b45f73 --- /dev/null +++ b/module/story:q/view/assignto.html.php @@ -0,0 +1,53 @@ + + * @package story + * @version $Id: complete.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + +
    +
    +
    +

    + id;?> + title'>" . $story->title . '') : html::a($this->createLink('story', 'view', 'story=' . $story->id), $story->title);?> + + arrow . $lang->story->assignTo;?> + +

    +
    +
    + + + + + + + + + + printExtendFields($story, 'table');?> + + + + + + + +
    story->assign;?>assignedTo, "class='form-control chosen'");?>
    story->status;?>status);?>
    comment;?>
    + + goback, $this->session->storyList, '', '', 'btn btn-wide');?> +
    +
    +
    + +
    +
    + diff --git a/module/story:q/view/batchclose.html.php b/module/story:q/view/batchclose.html.php new file mode 100755 index 0000000000..471f4ae061 --- /dev/null +++ b/module/story:q/view/batchclose.html.php @@ -0,0 +1,68 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + + +tab);?> +
    +
    +

    story->common . $lang->colon . $lang->story->batchClose;?>

    +
    + +
    + +
    "> + + + + + + + + + + + $story):?> + + + + + + + + + + + +
    idAB;?>story->title;?>story->status;?>story->closedReason;?>story->comment;?>
    title;?>processStatus('story', $story);?> + status == 'draft') unset($reasonList['cancel']);?> + + + + + + +
    + + ' closedReason != 'duplicate') echo "style='display:none'";?>> + idAB}'");?> + ' closedReason != 'subdivided') echo "style='display:none'";?>> + idAB}'");?> +
    +
    + + +
    +
    + +
    + diff --git a/module/story:q/view/batchcreate.html.php b/module/story:q/view/batchcreate.html.php new file mode 100755 index 0000000000..108a37739d --- /dev/null +++ b/module/story:q/view/batchcreate.html.php @@ -0,0 +1,196 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
    +
    +

    lang->story->subdivide : $this->lang->story->batchCreate;?>

    +
    + createLink('file', 'uploadImages', 'module=story¶ms=' . helper::safe64Encode("productID=$productID&branch=$branch&moduleID=$moduleID&storyID=$storyID&executionID=$executionID&plan=&type=$type")), $lang->uploadImages, '', "data-toggle='modal' data-type='iframe' class='btn btn-primary' data-width='70%'")?> + + createLink('custom', 'ajaxSaveCustomFields', 'module=story§ion=custom&key=batchCreateFields')?> + + +
    + + +
    +
    + story->create->requiredFields) as $field) + { + if($field) + { + $requiredFields[$field] = ''; + if(strpos(",{$config->story->list->customBatchCreateFields},", ",{$field},") !== false) $visibleFields[$field] = ''; + } + } + unset($visibleFields['module']); + ?> +
    +
    + + + + + + + type == 'kanban'):?> + + + + + + + + + + + + + + story->getFlowExtendFields(); + foreach($extendFields as $extendField) + { + $required = strpos(",$extendField->rules,", ',1,') !== false ? 'required' : ''; + echo ""; + } + ?> + + + + + + + + type == 'kanban'):?> + + + + + + + + + + + loadModel('flow'); + foreach($extendFields as $extendField) echo "control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, '', $extendField->field . '[$id]') . ""; + ?> + + + + + + + +
    '>product->branch;?>'>story->module;?>'>story->plan;?>kanbancard->region;?>kanbancard->lane;?>story->title;?>'>story->spec;?>'>story->source;?>'>story->sourceNote;?>'>story->verify;?>story->category;?>'>story->pri;?>'>story->estimate;?>' style="width: 200px !important">story->reviewedBy;?>'>story->keywords;?>{$extendField->name}
    '>' style='overflow:visible'> + + + +
    +
    + +
    + + + +
    +
    + + + +
    +
    '>'>'>'>story->categoryList, 'feature', "class='form-control chosen'");?>' style='overflow:visible'>'>'> +
    + + story->ditto;?> +
    +
    '>
    + save);?> + +
    +
    +
    +
    + + + + + + + + + diff --git a/module/story:q/view/batchedit.html.php b/module/story:q/view/batchedit.html.php new file mode 100644 index 0000000000..bd6870752e --- /dev/null +++ b/module/story:q/view/batchedit.html.php @@ -0,0 +1,159 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +lang->story->dittoNotice);?> + +app->tab);?> + +
    +
    +

    + story->common . $lang->colon . $lang->story->batchEdit;?> +

    +
    + createLink('custom', 'ajaxSaveCustomFields', 'module=story§ion=custom&key=batchEditFields')?> + +
    +
    + +
    + + +
    " id="batchEditForm"> +
    + + + + + + + + + + + + + + + + + + + + + + story->getFlowExtendFields(); + foreach($extendFields as $extendField) echo ""; + ?> + + + + $story):?> + + + + + + + + + + + + + + + source == 'meeting' or $story->source == 'researchreport') + { + $objects = $story->source == 'meeting' ? $meetings : $researchReports; + $html = html::select("sourceNote[$storyID]", $objects, $story->sourceNote, "class='form-control picker-select' data-drop-width='auto' id='sourceNote_$storyID'"); + } + else + { + $html = html::input("sourceNote[$storyID]", $story->sourceNote, "class='form-control' id='sourceNote_$storyID'"); + } + ?> + + + + + + status == 'closed'):?> + + + + + + control == 'select' or $extendField->control == 'multi-select') ? " style='overflow: visible'" : '') . ">" . $this->loadModel('flow')->getFieldControl($extendField, $story, $extendField->field . "[{$storyID}]") . "";?> + + + + + + + + +
    idAB;?>'>story->branch;?>story->module;?> col-plan'>story->planAB;?>story->title;?>'> story->estimateAB;?>story->category;?>' title=story->pri;?>> priAB;?>'> story->assignedTo;?>'> story->source;?>'> story->sourceNote;?>story->status;?>'> story->stageAB;?>'>story->closedBy;?>'> story->closedReason;?>'>story->keywords;?>{$extendField->name}
    '> + product]->type == 'normal' ? "disabled='disabled'" : '';?> + product]->type == 'normal') $branchTagOption[$story->product] = array();?> + product], $story->branch, "class='form-control picker-select' data-drop-width='auto' onchange='loadBranches($story->product, this.value, $storyID);' $disabled");?> + '> + id, array(0 => '/')), $story->module, "class='form-control picker-select' data-drop-width='auto'");?> + '> + product][$story->branch]) ? array('' => '') + $plans[$story->product][$story->branch] : '', $story->plan, "class='form-control picker-select' data-drop-width='auto'");?> + +
    +
    + title, "class='form-control input-story-title' disabled"); ?> + title); ?> + +
    + + + color, "class='colorpicker' data-wrapper='input-control-icon-right' data-icon='color' data-btn-tip='{$lang->story->colorTag}' data-update-color='#titles\\[{$storyID}\\]'");?> +
    +
    +
    +
    >estimate, "class='form-control'"); ?>story->categoryList, $story->category, 'class="form-control picker-select" data-drop-width="auto"');?>>pri, 'class=form-control');?>'>assignedTo, "class='form-control picker-select' data-drop-width='auto'");?>>source, "class='form-control picker-select' data-drop-width='auto' id='source_$storyID'");?>' data-id="sourceNote;?>">processStatus('story', $story);?>>stage, 'class="form-control"' . ($story->status == 'draft' ? ' disabled="disabled"' : ''));?>'>closedBy, "class='form-control" . ($story->status == 'closed' ? " picker-select' data-drop-width='auto'" : "' disabled='disabled'"));?>> + + + + + + +
    + closedReason, "class=form-control onchange=setDuplicateAndChild(this.value,$storyID) style='min-width: 70px'");?> + ' closedReason != 'duplicate') echo "style='display: none'";?>> + idAB}'");?> + ' closedReason != 'subdivided') echo "style='display: none'";?>> + idAB}'");?> +
    +
    >closedReason, 'class="form-control" disabled="disabled"');?>>keywords, 'class="form-control"');?>
    + + app->tab == 'product' or $this->app->tab == 'execution') ? html::a($this->session->storyList, $lang->goback, '', "class='btn btn-back btn-wide'") : html::backButton();?> +
    +
    +
    + + diff --git a/module/story:q/view/bugs.html.php b/module/story:q/view/bugs.html.php new file mode 100644 index 0000000000..8101b47228 --- /dev/null +++ b/module/story:q/view/bugs.html.php @@ -0,0 +1,40 @@ + +
    +
    +

    + icons['report']);?> + story->bugs;?> +

    +
    +
    + + + + + + + + + + + + + + + $bug):?> + + + + + + + + + + + + +
    idAB;?> bug->title;?>story->pri;?>>priAB;?> bug->type;?> statusAB;?> bug->assignedTo;?> bug->resolvedBy;?>bug->resolution;?>
    id;?>title;?>bug->priList, $bug->pri, $bug->pri)?>'>pri == '0' ? '' : zget($lang->bug->priList, $bug->pri, $bug->pri);?>bug->typeList[$bug->type];?>processStatus('bug',$bug);?>assignedTo, $bug->assignedTo);?>resolvedBy, $bug->resolvedBy);?>bug->resolutionList[$bug->resolution];?>
    +
    +
    + diff --git a/module/story:q/view/cases.html.php b/module/story:q/view/cases.html.php new file mode 100644 index 0000000000..d36a419aef --- /dev/null +++ b/module/story:q/view/cases.html.php @@ -0,0 +1,40 @@ + +
    +
    +

    + icons['report']);?> + story->cases;?> +

    +
    +
    + + + + + + + + + + + + + + + $case):?> + + + + + + + + + + + + +
    idAB;?> testcase->title;?>story->pri;?>>priAB;?> testcase->type;?>statusAB;?> testcase->lastRunner;?> testcase->lastRunDate;?>testcase->lastRunResult;?>
    id;?>title;?>testcase->priList, $case->pri, $case->pri)?>'>pri == '0' ? '' : zget($lang->case->priList, $case->pri, $case->pri);?>testcase->typeList[$case->type];?>processStatus('testcase', $case);?>lastRunner, $case->lastRunner);?>lastRunDate)) echo date(DT_MONTHTIME1, strtotime($case->lastRunDate));?>lastRunResult ? $lang->testcase->resultList[$case->lastRunResult] : '';?>
    +
    +
    + diff --git a/module/story:q/view/change.html.php b/module/story:q/view/change.html.php new file mode 100644 index 0000000000..2504454e81 --- /dev/null +++ b/module/story:q/view/change.html.php @@ -0,0 +1,107 @@ + + * @package story + * @version $Id: change.html.php 4129 2013-01-18 01:58:14Z wwccss $ + * @link http://www.zentao.net + */ +?> + +
    +
    +
    +

    + id;?> + createLink('story', 'view', "storyID=$story->id"), $story->title, '', 'class="story-title"');?> + arrow . ' ' . $lang->story->change;?> +

    +
    +
    + + + + + + + + + + + printExtendFields($story, 'table');?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    story->reviewedBy;?> +
    + story->checkForceReview() ? ' required' : ''));?> + story->checkForceReview()):?> + + story->needNotReview, '', "id='needNotReview' {$needReview}");?> + + +
    +
    story->status;?>status);?>
    story->title;?> +
    + + + +
    + title, 'class="form-control story-title"');?> +
    story->spec;?>spec), 'rows=8 class="form-control"');?>story->specTemplate;?>
    story->verify;?>verify), 'rows=6 class="form-control"');?>
    story->comment;?>
    attatch;?>fetch('file', 'buildform');?>
    story->checkAffection;?>
    + lastEditedDate); + echo html::submitButton(); + if(!isonlybody()) echo html::backButton(); + ?> +
    +
    +
    +
    + +
    +
    +
    +id);?> +title);?> +spec);?> +verify);?> + +type);?> +app->rawModule);?> + diff --git a/module/story:q/view/close.html.php b/module/story:q/view/close.html.php new file mode 100644 index 0000000000..f27d1c9d12 --- /dev/null +++ b/module/story:q/view/close.html.php @@ -0,0 +1,60 @@ + + * @package story + * @version $Id: close.html.php 4129 2013-01-18 01:58:14Z wwccss $ + * @link http://www.zentao.net + */ +?> + +
    +
    +
    +

    + id;?> + title'>" . $story->title . '') : html::a($this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> + + arrow . $lang->story->close;?> + +

    +
    +
    + + + + + + + + + + + + + + + + + + printExtendFields($story, 'table');?> + + + + + + + +
    story->closedReason;?>
    story->status;?>
    story->comment;?>
    + + session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id"), $lang->goback, '', 'class="btn btn-wide"');?> +
    +
    +
    +
    +
    +
    + diff --git a/module/story:q/view/create.html.php b/module/story:q/view/create.html.php new file mode 100644 index 0000000000..ef6586f763 --- /dev/null +++ b/module/story:q/view/create.html.php @@ -0,0 +1,321 @@ + + * @package story + * @version $Id: create.html.php 4902 2013-06-26 05:25:58Z wyd621@gmail.com $ + * @link http://www.zentao.net + */ +?> + + + +story->placeholder); ?> + +story->feedbackSource); ?> + + + + +
    +
    +
    +

    story->create;?>

    +
    + createLink('custom', 'ajaxSaveCustomFields', 'module=story§ion=custom&key=createFields')?> + +
    +
    + story->create->requiredFields) as $field) + { + if($field and strpos($showFields, $field) === false) $showFields .= ',' . $field; + } + ?> +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + config->URAndSR):?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + printExtendFields('', 'table', 'columns=4');?> + + + + + + + + + + + + + + + + + + + + + + +
    story->product;?> +
    + + + +
    +
    +
    +
    story->module;?>
    + "; + echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=story¤tModuleID=0&branch=$branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='90%'"); + echo '  '; + echo html::a("javascript:void(0)", $lang->refresh, '', "class='refresh' onclick='loadProductModules($productID)'"); + echo '
    '; + } + ?> + +
    story->planAB;?> +
    + "; + echo html::a($this->createLink('productplan', 'create', "productID=$productID&branch=$branch", '', true), "", '', "class='btn btn-icon' data-toggle='modal' data-type='iframe' data-width='95%' title='{$lang->productplan->create}'"); + echo '
    '; + echo "
    "; + echo html::a("javascript:void(0)", "", '', "class='btn btn-icon refresh' data-toggle='tooltip' title='{$lang->refresh}' onclick='loadProductPlans($productID)'"); + echo '
    '; + } + ?> + +
    +
    +
    +
    story->source;?>
    + story->sourceList, $source, "class='form-control chosen'");?> + story->sourceNote;?> + + +
    +
    +
    story->reviewedBy;?>' id='reviewerBox'> +
    + story->checkForceReview()):?> +
    + PO : '', "class='form-control picker-select' multiple");?> +
    +
    + +
    + /> + +
    +
    +
    + +
    + PO : '', "class='form-control picker-select' multiple required");?> +
    + +
    +
    ' id='assignedToBox' class='hidden'> +
    +
    story->assignedTo;?>
    + +
    +
    +
    +
    +
    story->source;?>
    + story->sourceList, $source, "class='form-control chosen'");?> + story->sourceNote;?> + +
    +
    +
    story->requirement;?> +
    +
    story->parent;?>
    + +
    +
    story->parent;?>
    kanbancard->region;?> + +
    +
    kanbancard->lane;?>
    + +
    +
    story->title;?> +
    +
    +
    + +
    + + + +
    +
    +
    + +
    +
    + story->category;?> + story->categoryList, 'feature', "class='form-control chosen'");?> +
    +
    +
    +
    + story->pri;?> + story->priList as $priKey => $priValue) + { + if(!empty($priKey) and (string)$priKey != (string)$priValue) + { + $hasCustomPri = true; + break; + } + } + + $priList = $lang->story->priList; + if(end($priList)) unset($priList[0]); + if(!isset($priList[$pri])) + { + reset($priList); + $pri = key($priList); + } + ?> + + + +
    + + +
    + +
    +
    + + +
    +
    + story->estimateAB;?> + +
    +
    + +
    +
    story->spec;?> + fetch('user', 'ajaxPrintTemplates', 'type=story&link=spec');?> + story->specTemplate . "\n" . $lang->noticePasteImg) . "'");?> +
    story->verify;?>
    story->status;?>
    story->legendAttatch;?>fetch('file', 'buildform');?>
    story->mailto;?> +
    + chooseUsersToMail}' multiple");?> + fetch('my', 'buildContactLists');?> +
    +
    story->keywords;?> + +
    + + goback, '', 'class="btn btn-wide"') : html::backButton('', $source == 'bug' ? 'data-app=qa' : '');?> +
    +
    +
    +
    + +story->module);?> + + + diff --git a/module/story:q/view/edit.html.php b/module/story:q/view/edit.html.php new file mode 100644 index 0000000000..9b0f5bc552 --- /dev/null +++ b/module/story:q/view/edit.html.php @@ -0,0 +1,292 @@ + + * @package story + * @version $Id: edit.html.php 4645 2013-04-11 08:32:09Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +?> + + +product);?> +children));?> +story->moveChildrenTips);?> +app->rawModule);?> + +story->module);?> + +story->notice->reviewerNotEmpty);?> +story->feedbackSource); ?> +
    +
    +
    +

    + id;?> + createLink('story', 'view', "storyID=$story->id"), $story->title, '', 'class="story-title"');?> + arrow . ' ' . $lang->story->edit;?> +

    +
    +
    +
    +
    +
    +
    +
    + + + +
    + title, 'class="form-control disabled story-title" disabled="disabled"');?> +
    +
    +
    +
    story->legendSpec;?>
    +
    spec;?>
    +
    +
    +
    story->verify;?>
    +
    verify;?>
    +
    + printExtendFields($story, 'div', 'position=left');?> +
    +
    story->comment;?>
    +
    + +
    +
    +
    + lastEditedDate); + echo html::submitButton($lang->save); + if(!isonlybody()) echo html::backButton(); + ?> +
    +
    + +
    +
    +
    +
    +
    +
    story->legendBasicInfo;?>
    + + parent <= 0):?> + + + + + type != 'normal'):?> + + + + + + + + + + parent >= 0 and $story->type == 'story'):?> + + + + + + + + + + + + + + + + + + + + + + status != 'draft' and $story->type == 'story'):?> + + + + + + + + + + + + + + + + + + '> + + + + '> + + + + + + + + + + + +
    story->product;?> +
    + product, "onchange='loadProduct(this.value);' class='form-control chosen control-product'");?> + + type != 'normal') echo html::select('branch', $branchTagOption, $story->branch, "onchange='loadBranch();' class='form-control chosen control-branch'");?> +
    +
    product->branch = sprintf($lang->product->branch, $lang->product->branchName['branch']);?> +
    type != 'normal') echo html::select('branch', $branchTagOption, $story->branch, "onchange='loadBranch();' class='form-control chosen control-branch'");?>
    +
    story->module;?> +
    + module, "class='form-control chosen'"); + if(count($moduleOptionMenu) == 1) + { + echo ""; + echo html::a($this->createLink('tree', 'browse', "rootID=$story->product&view=story¤tModuleID=0&branch=$story->branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'"); + echo '  '; + echo html::a("javascript:void(0)", $lang->refresh, '', "class='refresh' onclick='loadProductModules($story->product)'"); + echo ''; + } + ?> +
    +
    story->parent;?>parent, "class='form-control chosen'");?>
    story->plan;?> +
    + session->currentProductType != 'normal' and empty($story->branch)) ? true : false;?> + plan, "class='form-control chosen'" . ($multiple ? ' multiple' : '')); + if(count($plans) == 1) + { + echo ""; + echo html::a($this->createLink('productplan', 'create', "productID=$story->product&branch=$story->branch", '', true), $lang->productplan->create, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'"); + echo html::a("javascript:void(0)", $lang->refresh, '', "class='refresh' onclick='loadProductPlans($story->product)'"); + echo ''; + } + ?> +
    +
    story->source;?>story->sourceList, $story->source, "class='form-control chosen'");?>
    story->sourceNote;?>sourceNote, "class='form-control'");?> +
    story->status;?> + processStatus('story', $story);?> + status);?> +
    story->stage;?> + stages and $branchTagOption) + { + foreach($story->stages as $branch => $stage) + { + if(isset($branchTagOption[$branch])) echo '

    ' . $branchTagOption[$branch] . html::select("stages[$branch]", $lang->story->stageList, $stage, "class='form-control chosen'") . '

    '; + } + } + else + { + echo html::select('stage', $lang->story->stageList, $story->stage, "class='form-control chosen'"); + } + ?> +
    story->category;?>story->categoryList, $story->category, "class='form-control chosen'");?>
    story->pri;?>story->priList, $story->pri, "class='form-control chosen'");?>
    story->estimate;?>parent >= 0 ? html::input('estimate', $story->estimate, "class='form-control'") : $story->estimate;?>
    story->feedbackBy;?>feedbackBy, "class='form-control'");?>
    story->notifyEmail;?>notifyEmail, "class='form-control'");?>
    story->keywords;?>keywords, "class='form-control'");?>
    story->mailto;?> +
    + mailto), "class='form-control picker-select' multiple");?> + fetch('my', 'buildContactLists');?> +
    +
    +
    +
    +
    story->legendLifeTime;?>
    + + + + + + + + + + + + + + + + status == 'closed'):?> + + + + + + + + + +
    story->openedBy;?>openedBy);?>
    story->assignedTo;?>assignedTo, 'class="form-control chosen"');?>
    story->reviewers;?>
    story->closedBy;?>closedBy, 'class="form-control chosen"');?>
    story->closedReason;?>story->reasonList, $story->closedReason, "class='form-control' onchange='setStory(this.value)'");?>
    +
    + + printExtendFields($story, 'div', 'position=right');?> + +
    +
    story->legendMisc;?>
    + + status == 'closed'):?> + + + + + + status == 'closed'):?> + + + + + + + + + +
    story->duplicateStory;?>duplicateStory == 0 ? '' : $story->duplicateStory, "class='form-control'");?>
    story->childStories;?> + createLink('story', 'linkStory', "storyID=$story->id&type=childStories", '', true), $lang->story->linkStory, '', "data-toggle='modal' data-type='iframe' data-width='95%'");?> +
    +
      + childStories) + { + $childStories = explode(',', $story->childStories); + foreach($childStories as $childStoryID) + { + if(isset($story->extraStories[$childStoryID])) + { + echo "
    • "; + echo ""; + echo ""; + echo '
    • '; + } + } + } + ?> + +
    +
    +
    +
    +
    +
    +
    +
    +type);?> + + diff --git a/module/story:q/view/export.html.php b/module/story:q/view/export.html.php new file mode 100644 index 0000000000..ebe1453b9e --- /dev/null +++ b/module/story:q/view/export.html.php @@ -0,0 +1,13 @@ + + * @package story + * @version $Id$ + * @link http://www.zentao.net + */ +?> + diff --git a/module/story:q/view/header.html.php b/module/story:q/view/header.html.php new file mode 100644 index 0000000000..a5c56e71e5 --- /dev/null +++ b/module/story:q/view/header.html.php @@ -0,0 +1,174 @@ + + +app->rawMethod);?> + diff --git a/module/story:q/view/linkstory.html.php b/module/story:q/view/linkstory.html.php new file mode 100644 index 0000000000..62d0ce8431 --- /dev/null +++ b/module/story:q/view/linkstory.html.php @@ -0,0 +1,92 @@ + + * @package story + * @version $Id: linkstory.html.php 4129 2016-03-09 08:58:13Z chenfei $ + * @link http://www.zentao.net + */ +?> + + + +
    +
    +

    + id;?> + title'>" . $story->title . '') : html::a($this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> + + arrow . $lang->story->linkStory;?> + +

    +
    +
    +
    + + + + + + + + + type == 'requirement'):?> + + + + + + + + + + createLink('story', 'view', "storyID=$story2Link->id");?> + + + + + + type == 'requirement'):?> + + + + + + + + +
    +
    + +
    + idAB;?> +
    story->pri;?>>priAB;?>story->title;?>story->status;?>story->stage;?>openedByAB;?>story->estimateAB;?>
    +
    + + +
    + id));?> +
    pri?>' title='story->priList, $story2Link->pri, $story2Link->pri);?>'>story->priList, $story2Link->pri, $story2Link->pri);?>title, '_blank');?>processStatus('story', $story2Link);?>story->stageList, $story2Link->stage);?>openedBy);?>estimate;?>
    + + +
    +
    + + diff --git a/module/story:q/view/processstorychange.html.php b/module/story:q/view/processstorychange.html.php new file mode 100644 index 0000000000..b23c873843 --- /dev/null +++ b/module/story:q/view/processstorychange.html.php @@ -0,0 +1,47 @@ + + +
    +
    +
    +

    + id;?> + title . '">' . $story->title . '';?> + arrow . $lang->story->URChanged;?> +

    +
    + + + + + + + + + + + + + + + + + + + + + +
    story->id;?>story->title;?>story->status;?>story->version;?>story->openedBy;?>
    id;?>title;?>story->changed;?>version;?>openedBy);?>
    +
    + story->changeTips;?> +
    +
    + story->changeList['no'], '', "class='btn btn-secondary'");?> + story->changeList['yes'], '', "class='btn btn-primary showinonlybody'");?> +
    +
    +
    + diff --git a/module/story:q/view/report.html.php b/module/story:q/view/report.html.php new file mode 100644 index 0000000000..2b663c0aa0 --- /dev/null +++ b/module/story:q/view/report.html.php @@ -0,0 +1,92 @@ + + * @package product + * @version $Id: report.html.php 1594 2011-03-13 07:27:55Z wwccss $ + * @link http://www.zentao.net + */ +?> + +
    +
    +
    +
    +
    +
    story->report->select;?>
    +
    +
    +
    +
    + story->report->charts['storysPerPlan']);?> + story->report->charts, $checkedCharts, '', 'block');?> +
    +
    + + story->report->create, '', 'btn btn-primary');?> +
    +
    +
    +
    +
    +
    +
    +
    + report->typeList['default']);?> + report->typeList as $type => $typeName):?> + " . $typeName, '', "class='btn btn-link " . ($type == $chartType ? 'btn-active-line' : '') . "'")?> + +
    +
    product->unclosed . $lang->story->common, $lang->report->notice->help);?>
    + $chartOption):?> +
    +
    +
    +

    story->report->charts[$chartType];?>

    +
    +
    +
    +
    +
    + + + + + + + + + $data):?> + + + + + + + +
    story->report->$chartType->item;?>story->report->value;?>report->percent;?>
    name;?>value;?>percent * 100) . '%';?>
    +
    +
    +
    + +
    +
    +
    + + + + + + diff --git a/module/story:q/view/review.html.php b/module/story:q/view/review.html.php new file mode 100644 index 0000000000..73cfdc29d5 --- /dev/null +++ b/module/story:q/view/review.html.php @@ -0,0 +1,93 @@ + + * @package story + * @version $Id: review.html.php 4129 2013-01-18 01:58:14Z wwccss $ + * @link http://www.zentao.net + */ +?> + + +
    +
    +
    +

    + id;?> + createLink('story', 'view', "storyID=$story->id"), $story->title);?> + arrow . $lang->story->review;?> +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status == 'changed' or ($story->status == 'draft' and $story->version > 1)):?> + + + + + + + + + + printExtendFields($story, 'table');?> + + + + + + + + + + + +
    story->reviewedDate;?>
    story->reviewResult;?>story->resultList, '', 'class="form-control chosen" onchange="switchShow(this.value)"');?>
    story->assignedTo;?>
    story->rejectedReason;?>story->reasonList, '', 'class=form-control onchange="setStory(this.value)"');?>
    story->pri;?>story->priList, $story->pri,"class='form-control'");?>
    story->estimate;?>estimate, "class='form-control'");?>
    story->duplicateStory;?>
    story->childStories;?>
    story->preVersion;?>version - 1, 1), range($story->version - 1, 1)), $story->version - 1);?>
    story->status;?>status);?>
    story->comment;?>
    story->checkAffection;?>
    + + +
    +
    +
    +
    +
    +
    +id);?> +type);?> +app->rawModule);?> + + diff --git a/module/story:q/view/sendmail.html.php b/module/story:q/view/sendmail.html.php new file mode 100644 index 0000000000..8189cbd958 --- /dev/null +++ b/module/story:q/view/sendmail.html.php @@ -0,0 +1,35 @@ + + * @package bug + * @version $Id: sendmail.html.php 4129 2013-01-18 01:58:14Z wwccss $ + * @link http://www.zentao.net + */ +?> +id . ' ' . $object->title;?> +app->getModuleRoot() . 'common/view/mail.header.html.php';?> + + + + + + +
    + color) ? '#333' : $object->color;?> + id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?> +
    + + + + +
    + lang->story->legendSpec;?> +
    spec;?>
    +
    + + +app->getModuleRoot() . 'common/view/mail.footer.html.php';?> diff --git a/module/story:q/view/tasks.html.php b/module/story:q/view/tasks.html.php new file mode 100644 index 0000000000..a1f9e2e247 --- /dev/null +++ b/module/story:q/view/tasks.html.php @@ -0,0 +1,77 @@ + +
    +
    +

    + icons['report']);?> + story->tasks;?> +

    +
    +
    + + + + + + + + + + + + + + + + $task):?> + + + + + + + + + + + + children)):?> + + children as $key => $child):?> + + children)) ? ' table-child-bottom' : '';?> + + + + + + + + + + + + + + + + + + + + + +
    idAB;?> task->name;?>story->pri;?>>priAB;?> statusAB;?> task->assignedToAB;?>task->estimateAB;?>task->consumedAB;?> task->leftAB;?>task->progress;?>
    id;?>name;?>task->priList, $task->pri, $task->pri)?>'>pri == '0' ? '' : zget($lang->task->priList, $task->pri, $task->pri);?>processStatus('task', $task);?>assignedTo, $task->assignedTo);?>estimate;?>consumed;?>left;?> +
    +
    progress;?>
    +
    +
    id;?>lang->task->children . '">' . $this->lang->task->childrenAB . '' ?>name;?>task->priList, $child->pri, $child->pri)?>'>pri == '0' ? '' : zget($lang->task->priList, $child->pri, $child->pri);?>processStatus('task', $child);?>assignedTo, $child->assignedTo);?>estimate;?>consumed;?>left;?> +
    +
    progress;?>
    +
    +
    +
    +
    + diff --git a/module/story:q/view/track.html.php b/module/story:q/view/track.html.php new file mode 100644 index 0000000000..54c5a4aea8 --- /dev/null +++ b/module/story:q/view/track.html.php @@ -0,0 +1,120 @@ + + +app->rawModule);?> + +
    +
    + +
    +

    + noData;?> +

    +
    + + app->rawModule == 'projectstory' ? "style='overflow: unset; text-align: left'" : '';?> + app->rawModule == 'projectstory' ? 'project' : 'product';?> + app->rawModule == 'projectstory' ? 'projectstory' : 'story';?> +
    + + + + URAndSR):?> + + + + + edition == 'max'):?> + + + + edition == 'max'):?> + + + + + + + $requirement):?> + track;?> + + + URAndSR):?> + + + + + $story):?> + ';?> + + + edition == 'max'):?> + + + + edition == 'max'):?> + + + + ';?> + + + + + + +
    > + app->rawModule == 'projectstory'): ?> + + + story->requirement;?> + + story->story;?>story->tasks;?>story->design;?>story->case;?>story->repoCommit;?>story->bug;?>
    story->statusList, $requirement->status);?> + + story->noRequirement : html::a($this->createLink($module, 'view', "storyID=$requirement->id"), $requirement->title, '', "title=$requirement->title data-app='$tab'");?> + + parent) and $story->parent > 0):?>lang->story->childrenAB;?> + createLink($module, 'view', "storyID=$storyID"), $story->title, '',"title='$story->title' data-app='$tab'");?> + + tasks as $taskID => $task):?> + createLink('task', 'view', "taskID=$taskID"), $task->name, '', "title='$task->name'") . '
    ';?> + +
    + designs as $designID => $design):?> + createLink('design', 'view', "designID=$designID"), $design->name, '', "title='$design->name'") . '
    ';?> + +
    + cases as $caseID => $case):?> + createLink('testcase', 'view', "caseID=$caseID"), $case->title, '', "title='$case->title'") . '
    ';?> + +
    + revisions as $revision => $repoComment):?> + createLink('design', 'revision', "repoID=$revision"), '#'. $revision . '-' . $repoComment, '', "data-app='devops'") . '
    '; + ?> + +
    + bugs as $bugID => $bug):?> + createLink('bug', 'view', "bugID=$bugID"), $bug->title, '', "title='$bug->title'") . '
    ';?> + +
    + +
    + +
    +
    + diff --git a/module/story:q/view/view.html.php b/module/story:q/view/view.html.php new file mode 100644 index 0000000000..14ff9d6f1c --- /dev/null +++ b/module/story:q/view/view.html.php @@ -0,0 +1,587 @@ + + * @package story + * @version $Id: view.html.php 4952 2013-07-02 01:14:58Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +?> + + +session->storyList ? $app->session->storyList : $this->createLink('product', 'browse', "productID=$story->product");?> + + + +app->getViewType() == 'xhtml'):?> +
    + + +
    +
    +
    +
    +
    story->legendSpec;?>
    +
    spec;?>
    +
    +
    +
    story->legendVerify;?>
    +
    verify;?>
    +
    + + fetch('file', 'printFiles', array('files' => $story->files, 'fieldset' => 'true', 'object' => $story));?> + createLink('action', 'comment', "objectType=story&objectID=$story->id"); + ?> + children)):?> +
    +
    lang->story->children;?>
    +
    + + + + + + + + + + + + + + children as $child):?> + + + + + + + + + + + +
    story->id;?>story->pri;?>>priAB;?> story->title;?>story->assignedTo;?> story->estimate;?> story->status;?>actions;?>
    id;?> + pri . "'>"; + echo $child->pri == '0' ? '' : zget($this->lang->story->priList, $child->pri, $child->pri); + echo ""; + ?> + id", '', true); ?>">title;?>assignedTo);?>estimate . $config->hourUnit;?>processStatus('story', $child);?> + id", $child, 'list', 'alter'); + common::printIcon('story', 'review', "storyID=$child->id", $child, 'list', 'search', '', 'iframe showinonlybody', true); + common::printIcon('story', 'assignTo', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true); + common::printIcon('story', 'close', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true); + common::printIcon('story', 'activate', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true); + common::printIcon('story', 'edit', "storyID=$child->id", $child, 'list'); + common::printIcon('story', 'createCase', "productID=$child->product&branch=$child->branch&module=0&from=¶m=0&story={$child->id}", $child, 'list', 'sitemap'); + ?> +
    +
    +
    + +
    + printExtendFields($story, 'div', "position=left&inForm=0&inCell=1");?> + app->getViewType() != 'xhtml'):?> +
    + +
    +
    + +
    ";?> + deleted) echo $this->story->buildOperateMenu($story, 'view');?> +
    +
    +
    +
    +
    +
    + +
    +
    + + + + + + + type != 'normal'):?> + + + + + + + + branch and isset($branches[$storyModule->branch])) + { + $moduleTitle .= $branches[$storyModule->branch] . '/'; + echo $branches[$storyModule->branch] . $lang->arrow; + } + + foreach($modulePath as $key => $module) + { + $moduleTitle .= $module->name; + if(!common::printLink('product', 'browse', "productID=$story->product&branch=$story->branch&browseType=byModule¶m=$module->id", $module->name, '', "data-app='product'")) echo $module->name; + if(isset($modulePath[$key + 1])) + { + $moduleTitle .= '/'; + echo $lang->arrow; + } + } + } + $printModule = ob_get_contents(); + ob_end_clean(); + ?> + + + type != 'requirement' and $story->parent != -1):?> + + + + + + + + + + + + + + + + + + type != 'requirement'):?> + + + + + + + + + + + + + + + + + + source, $config->story->feedbackSource)):?> + + + + + + + + + + + + + + + + + + +
    story->product;?>createLink('product', 'view', "productID=$story->product"), $product->name);?>
    product->branch;?>product&branch=$story->branch", $branches[$story->branch]);?>
    story->module;?>
    story->plan;?> + planTitle)) + { + foreach($story->planTitle as $planID => $planTitle) + { + if(!common::printLink('productplan', 'view', "planID=$planID", $planTitle, '', "data-app='product'")) echo $planTitle; + echo '
    '; + } + } + ?> +
    story->source;?>story->sourceList[$story->source];?>
    story->sourceNote;?>sourceNote;?>
    story->status;?> processStatus('story', $story);?>
    story->stage;?> + stages and $branches) + { + foreach($story->stages as $branch => $stage) if(isset($branches[$branch])) echo $branches[$branch] . ' : ' . $lang->story->stageList[$stage] . '
    '; + } + else + { + echo $lang->story->stageList[$story->stage]; + } + ?> +
    story->category;?>story->categoryList, $story->category, $story->category)?>
    story->pri;?>pri;?>' title='story->priList, $story->pri)?>'>story->priList, $story->pri)?>
    story->estimate;?>estimate . $config->hourUnit;?>
    story->feedbackBy;?>feedbackBy;?>
    story->notifyEmail;?>notifyEmail;?>
    story->keywords;?>keywords;?>
    story->legendMailto;?>mailto); foreach($mailto as $account) {if(empty($account)) continue; echo "" . zget($users, trim($account)) . '  '; }?>
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    story->openedBy;?>openedBy) . $lang->at . $story->openedDate;?>
    story->assignedTo;?>assignedTo) echo zget($users, $story->assignedTo) . $lang->at . $story->assignedDate;?>
    story->reviewers;?> + $result) + { + echo !empty($result) ? ' ' . zget($users, $reviewer) . '' : ' ' . zget($users, $reviewer) . ''; + } + } + ?> +
    story->reviewedDate;?>reviewedBy) echo $story->reviewedDate;?>
    story->closedBy;?>closedBy) echo zget($users, $story->closedBy) . $lang->at . $story->closedDate;?>
    story->closedReason;?> + closedReason) echo $lang->story->reasonList[$story->closedReason]; + if(isset($story->extraStories[$story->duplicateStory])) + { + echo html::a(inlink('view', "storyID=$story->duplicateStory"), '#' . $story->duplicateStory . ' ' . $story->extraStories[$story->duplicateStory]); + } + ?> +
    story->lastEditedBy;?>lastEditedBy) echo zget($users, $story->lastEditedBy) . $lang->at . $story->lastEditedDate;?>
    +
    +
    +
    +
    +
    +
    + +
    + config->URAndSR):?> +
    +
      + id] = $item->title; + foreach($relation as $id => $title) + { + echo "
    • " . html::a($this->createLink('story', 'view', "id=$id", '', true), "#$id $title", '', "class='iframe' data-width='80%'"); + echo html::a($this->createLink('story', 'linkStory', "storyID=$story->id&type=remove&linkedID=$id"), '', 'hiddenwin', "class='deleter hide removeButton'"); + } + ?> + type == 'story') ? $lang->story->requirement : $lang->story->story;?> +
    • createLink('story', 'linkStory', "storyID=$story->id", '', true), $lang->story->link . $linkLang, '', "class='btn btn-info iframe' data-width='95%' id='linkButton'");?> + story->unlink . $linkLang, '', "class='btn btn-info' id='unlinkStory'");?>
    • +
    +
    + + + type == 'story'):?> +
    +
      + tasks as $executionTasks) + { + foreach($executionTasks as $task) + { + if(!isset($executions[$task->execution])) continue; + $executionName = $executions[$task->execution]; + $taskInfo = $task->id . ' ' . $this->lang->task->statusList[$task->status] . ' ' . $task->name; + $class = isonlybody() ? 'showinonlybody' : 'iframe'; + $execution = isset($story->executions[$task->execution]) ? $story->executions[$task->execution] : ''; + $execName = (isset($execution->type) and $execution->type == 'kanban' and isonlybody()) ? $executionName : html::a($this->createLink('execution', 'view', "executionID=$task->execution"), $executionName, '', "class='text-muted'"); + echo "
    • " . $execName . html::a($this->createLink('task', 'view', "taskID=$task->id", '', true), $taskInfo, '', "class=$class data-width='80%'") . '
    • '; + } + } + foreach($story->executions as $executionID => $execution) + { + if(isset($story->tasks[$executionID])) continue; + $execName = ($execution->type == 'kanban' and isonlybody()) ? $execution->name : html::a($this->createLink('execution', 'view', "executionID=$executionID"), $execution->name, '', "class='text-muted'"); + echo "
    • " . $execName . '
    • '; + } + ?> +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + +
    story->legendFromBug;?> +
      + id $fromBug->title'>" . html::a($this->createLink('bug', 'view', "bugID=$fromBug->id", '', true), "#$fromBug->id $fromBug->title", '', "class='iframe' data-width='80%'") . '';?> +
    +
    story->legendBugs;?> +
      + id $bug->title'>" . html::a($this->createLink('bug', 'view', "bugID=$bug->id", '', true), "[B] #$bug->id $bug->title", '', "class='iframe' data-width='80%'") . ''; + } + ?> +
    +
    story->legendCases;?> +
      + id $case->title'>" . html::a($this->createLink('testcase', 'view', "caseID=$case->id", '', true), "[C] #$case->id $case->title", '', $misc) . ''; + } + ?> +
    +
    story->linkMR;?> +
      + $linkMRTitle) + { + if($mrPriv) + { + echo "
    • " . html::a($this->createLink('mr', 'view', "MRID=$MRID"), "#$MRID $linkMRTitle") . '
    • '; + } + else + { + echo "
    • " . "#$MRID $linkMRTitle" . '
    • '; + } + } + ?> +
    +
    +
    +
    +
    +
    + printExtendFields($story, 'div', "position=right&inForm=0&inCell=1");?> +
    +
    +app->getViewType() == 'xhtml'):?> +
    + + + + +
    + +
    +story->create); +js::set('productID', $story->product); +js::set('branch', $story->branch); +js::set('moduleID', $story->module); +js::set('storyType', $story->type); +js::set('unlink', $lang->story->unlink); +js::set('cancel', $lang->cancel); +js::set('rawModule', $this->app->rawModule); +?> + + + diff --git a/module/story:q/view/zerocase.html.php b/module/story:q/view/zerocase.html.php new file mode 100644 index 0000000000..e9302c0db0 --- /dev/null +++ b/module/story:q/view/zerocase.html.php @@ -0,0 +1,155 @@ + + * @package product + * @version $Id: browse.html.php 4909 2013-06-26 07:23:50Z chencongzhi520@gmail.com $ + * @link http://www.zentao.net + */ +?> + + + +
    + +
    +

    story->noStory;?>

    +
    + +
    + + + + app->rawModule = 'story'; + $vars = "productID=$productID&branchID=$branchID&orderBy=%s"; + ?> + + + + + + + + + + + + + + + $story):?> + app->tab == 'project') $param = $this->session->project; + if($this->app->tab == 'execution') $param = $this->session->execution; + $viewLink = $this->createLink('story', 'view', "storyID=$story->id&version=0¶m=$param"); + $canView = common::hasPriv('story', 'view'); + ?> + + + + + + + + + + + + + + + +
    +
    + +
    + idAB);?> +
    story->pri;?>>priAB);?> story->title);?> story->planAB);?> story->source);?> openedByAB);?> assignedToAB);?>story->estimateAB);?> statusAB);?> story->stageAB);?>actions;?>
    +
    + + +
    + id);?> +
    pri;?>' title='story->priList, $story->pri);?>'>story->priList, $story->pri)?>title, '', "data-app='product'");?>planTitle;?>story->sourceList[$story->source];?>openedBy);?>assignedTo);?>estimate;?>processStatus('story', $story);?>story->stageList, $story->stage);?> + id}"; + $this->app->tab = 'product'; + common::printIcon('story', 'change', $vars, $story, 'list', 'fork'); + common::printIcon('story', 'review', $vars, $story, 'list', 'glasses'); + common::printIcon('story', 'close', $vars, $story, 'list', 'off', '', 'iframe', 'yes'); + common::printIcon('story', 'edit', $vars, $story, 'list'); + $this->app->tab = 'qa'; + common::printIcon('story', 'createCase', "productID=$story->product&branch=0&module=0&from=¶m=0&$vars", $story, 'list', 'sitemap'); + ?> +
    + +
    + + +
    + From d09ea56ed1d642a98643460df1b0ed2e40e86a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E8=BE=B0=E8=BD=A9?= Date: Fri, 8 Jul 2022 10:56:31 +0800 Subject: [PATCH 2/3] * Delete. --- module/story:q/config.php | 239 - module/story:q/control.php | 2660 --------- module/story:q/css/affected.css | 2 - module/story:q/css/batchclose.css | 2 - module/story:q/css/batchcreate.css | 12 - module/story:q/css/batchedit.css | 11 - module/story:q/css/bugs.css | 4 - module/story:q/css/cases.css | 4 - module/story:q/css/change.css | 5 - module/story:q/css/close.en.css | 1 - module/story:q/css/close.vi.css | 1 - module/story:q/css/close.zh-cn.css | 1 - module/story:q/css/close.zh-tw.css | 1 - module/story:q/css/common.css | 19 - module/story:q/css/create.css | 9 - module/story:q/css/create.en.css | 1 - module/story:q/css/create.vi.css | 0 module/story:q/css/create.zh-cn.css | 0 module/story:q/css/edit.css | 1 - module/story:q/css/edit.en.css | 2 - module/story:q/css/edit.vi.css | 2 - module/story:q/css/edit.zh-cn.css | 2 - module/story:q/css/edit.zh-tw.css | 2 - module/story:q/css/linkstory.css | 1 - module/story:q/css/report.css | 8 - module/story:q/css/review.en.css | 1 - module/story:q/css/review.vi.css | 1 - module/story:q/css/review.zh-cn.css | 1 - module/story:q/css/review.zh-tw.css | 1 - module/story:q/css/tasks.css | 4 - module/story:q/css/track.css | 1 - module/story:q/css/view.css | 5 - module/story:q/css/view.en.css | 2 - module/story:q/css/view.vi.css | 2 - module/story:q/css/view.zh-cn.css | 2 - module/story:q/css/view.zh-tw.css | 2 - module/story:q/css/x.change.css | 2 - module/story:q/css/x.create.css | 2 - module/story:q/css/x.edit.css | 7 - module/story:q/css/x.review.css | 2 - module/story:q/css/x.view.css | 17 - module/story:q/css/zerocase.en.css | 1 - module/story:q/css/zerocase.vi.css | 1 - module/story:q/css/zerocase.zh-cn.css | 1 - module/story:q/css/zerocase.zh-tw.css | 1 - module/story:q/js/batchclose.js | 26 - module/story:q/js/batchcreate.js | 138 - module/story:q/js/batchedit.js | 101 - module/story:q/js/change.js | 51 - module/story:q/js/close.js | 18 - module/story:q/js/common.js | 22 - module/story:q/js/create.js | 108 - module/story:q/js/edit.js | 35 - module/story:q/js/report.js | 19 - module/story:q/js/review.js | 94 - module/story:q/js/track.js | 0 module/story:q/js/view.js | 25 - module/story:q/js/x.create.js | 8 - module/story:q/js/x.view.js | 46 - module/story:q/js/zerocase.js | 4 - module/story:q/lang/de.php | 441 -- module/story:q/lang/en.php | 441 -- module/story:q/lang/fr.php | 441 -- module/story:q/lang/vi.php | 441 -- module/story:q/lang/zh-cn.php | 441 -- module/story:q/lang/zh-tw.php | 416 -- module/story:q/model.php | 5309 ----------------- module/story:q/view/activate.html.php | 60 - module/story:q/view/affected.html.php | 102 - module/story:q/view/assignto.html.php | 53 - module/story:q/view/batchclose.html.php | 68 - module/story:q/view/batchcreate.html.php | 196 - module/story:q/view/batchedit.html.php | 159 - module/story:q/view/bugs.html.php | 40 - module/story:q/view/cases.html.php | 40 - module/story:q/view/change.html.php | 107 - module/story:q/view/close.html.php | 60 - module/story:q/view/create.html.php | 321 - module/story:q/view/edit.html.php | 292 - module/story:q/view/export.html.php | 13 - module/story:q/view/header.html.php | 174 - module/story:q/view/linkstory.html.php | 92 - .../story:q/view/processstorychange.html.php | 47 - module/story:q/view/report.html.php | 92 - module/story:q/view/review.html.php | 93 - module/story:q/view/sendmail.html.php | 35 - module/story:q/view/tasks.html.php | 77 - module/story:q/view/track.html.php | 120 - module/story:q/view/view.html.php | 587 -- module/story:q/view/zerocase.html.php | 155 - 90 files changed, 14654 deletions(-) delete mode 100644 module/story:q/config.php delete mode 100644 module/story:q/control.php delete mode 100644 module/story:q/css/affected.css delete mode 100644 module/story:q/css/batchclose.css delete mode 100644 module/story:q/css/batchcreate.css delete mode 100644 module/story:q/css/batchedit.css delete mode 100644 module/story:q/css/bugs.css delete mode 100644 module/story:q/css/cases.css delete mode 100644 module/story:q/css/change.css delete mode 100644 module/story:q/css/close.en.css delete mode 100644 module/story:q/css/close.vi.css delete mode 100644 module/story:q/css/close.zh-cn.css delete mode 100644 module/story:q/css/close.zh-tw.css delete mode 100644 module/story:q/css/common.css delete mode 100644 module/story:q/css/create.css delete mode 100644 module/story:q/css/create.en.css delete mode 100644 module/story:q/css/create.vi.css delete mode 100644 module/story:q/css/create.zh-cn.css delete mode 100644 module/story:q/css/edit.css delete mode 100644 module/story:q/css/edit.en.css delete mode 100644 module/story:q/css/edit.vi.css delete mode 100644 module/story:q/css/edit.zh-cn.css delete mode 100644 module/story:q/css/edit.zh-tw.css delete mode 100644 module/story:q/css/linkstory.css delete mode 100644 module/story:q/css/report.css delete mode 100644 module/story:q/css/review.en.css delete mode 100644 module/story:q/css/review.vi.css delete mode 100644 module/story:q/css/review.zh-cn.css delete mode 100644 module/story:q/css/review.zh-tw.css delete mode 100644 module/story:q/css/tasks.css delete mode 100644 module/story:q/css/track.css delete mode 100644 module/story:q/css/view.css delete mode 100644 module/story:q/css/view.en.css delete mode 100644 module/story:q/css/view.vi.css delete mode 100644 module/story:q/css/view.zh-cn.css delete mode 100644 module/story:q/css/view.zh-tw.css delete mode 100644 module/story:q/css/x.change.css delete mode 100644 module/story:q/css/x.create.css delete mode 100644 module/story:q/css/x.edit.css delete mode 100644 module/story:q/css/x.review.css delete mode 100644 module/story:q/css/x.view.css delete mode 100644 module/story:q/css/zerocase.en.css delete mode 100644 module/story:q/css/zerocase.vi.css delete mode 100644 module/story:q/css/zerocase.zh-cn.css delete mode 100644 module/story:q/css/zerocase.zh-tw.css delete mode 100755 module/story:q/js/batchclose.js delete mode 100644 module/story:q/js/batchcreate.js delete mode 100644 module/story:q/js/batchedit.js delete mode 100644 module/story:q/js/change.js delete mode 100644 module/story:q/js/close.js delete mode 100644 module/story:q/js/common.js delete mode 100644 module/story:q/js/create.js delete mode 100644 module/story:q/js/edit.js delete mode 100644 module/story:q/js/report.js delete mode 100644 module/story:q/js/review.js delete mode 100644 module/story:q/js/track.js delete mode 100644 module/story:q/js/view.js delete mode 100644 module/story:q/js/x.create.js delete mode 100644 module/story:q/js/x.view.js delete mode 100644 module/story:q/js/zerocase.js delete mode 100644 module/story:q/lang/de.php delete mode 100644 module/story:q/lang/en.php delete mode 100644 module/story:q/lang/fr.php delete mode 100644 module/story:q/lang/vi.php delete mode 100644 module/story:q/lang/zh-cn.php delete mode 100644 module/story:q/lang/zh-tw.php delete mode 100644 module/story:q/model.php delete mode 100644 module/story:q/view/activate.html.php delete mode 100644 module/story:q/view/affected.html.php delete mode 100644 module/story:q/view/assignto.html.php delete mode 100755 module/story:q/view/batchclose.html.php delete mode 100755 module/story:q/view/batchcreate.html.php delete mode 100644 module/story:q/view/batchedit.html.php delete mode 100644 module/story:q/view/bugs.html.php delete mode 100644 module/story:q/view/cases.html.php delete mode 100644 module/story:q/view/change.html.php delete mode 100644 module/story:q/view/close.html.php delete mode 100644 module/story:q/view/create.html.php delete mode 100644 module/story:q/view/edit.html.php delete mode 100644 module/story:q/view/export.html.php delete mode 100644 module/story:q/view/header.html.php delete mode 100644 module/story:q/view/linkstory.html.php delete mode 100644 module/story:q/view/processstorychange.html.php delete mode 100644 module/story:q/view/report.html.php delete mode 100644 module/story:q/view/review.html.php delete mode 100644 module/story:q/view/sendmail.html.php delete mode 100644 module/story:q/view/tasks.html.php delete mode 100644 module/story:q/view/track.html.php delete mode 100644 module/story:q/view/view.html.php delete mode 100644 module/story:q/view/zerocase.html.php diff --git a/module/story:q/config.php b/module/story:q/config.php deleted file mode 100644 index 13992a4a8c..0000000000 --- a/module/story:q/config.php +++ /dev/null @@ -1,239 +0,0 @@ -story = new stdclass(); - -$config->story->batchCreate = 10; -$config->story->affectedFixedNum = 7; -$config->story->needReview = 1; -$config->story->removeFields = 'objectTypeList,productList,executionList,execution'; -$config->story->feedbackSource = array('customer', 'user', 'market', 'service', 'operation', 'support', 'forum'); - -$config->story->batchClose = new stdclass(); -$config->story->batchClose->columns = 10; -$config->story->create = new stdclass(); -$config->story->edit = new stdclass(); -$config->story->change = new stdclass(); -$config->story->close = new stdclass(); -$config->story->review = new stdclass(); -$config->story->create->requiredFields = 'title'; -$config->story->change->requiredFields = 'title'; -$config->story->close->requiredFields = 'closedReason'; -$config->story->review->requiredFields = ''; - -$config->story->editor = new stdclass(); -$config->story->editor->create = array('id' => 'spec,verify', 'tools' => 'simpleTools'); -$config->story->editor->change = array('id' => 'spec,verify,comment', 'tools' => 'simpleTools'); -$config->story->editor->edit = array('id' => 'comment', 'tools' => 'simpleTools'); -$config->story->editor->view = array('id' => 'comment,lastComment', 'tools' => 'simpleTools'); -$config->story->editor->close = array('id' => 'comment', 'tools' => 'simpleTools'); -$config->story->editor->review = array('id' => 'comment', 'tools' => 'simpleTools'); -$config->story->editor->activate = array('id' => 'comment', 'tools' => 'simpleTools'); -$config->story->editor->assignto = array('id' => 'comment', 'tools' => 'simpleTools'); - -$config->story->list = new stdclass(); -$config->story->list->exportFields = ' - id, product, branch, module, plan, source, sourceNote, title, spec, verify, keywords, - pri, estimate, status, stage, taskCountAB, bugCountAB, caseCountAB, - openedBy, openedDate, assignedTo, assignedDate, mailto, - reviewedBy, reviewedDate, - closedBy, closedDate, closedReason, - lastEditedBy, lastEditedDate, - childStories, linkStories, duplicateStory, files'; - -$config->story->list->customCreateFields = 'source,verify,pri,estimate,mailto,keywords'; -$config->story->list->customBatchCreateFields = 'plan,spec,source,verify,pri,estimate,review,keywords'; -$config->story->list->customBatchEditFields = 'branch,plan,estimate,pri,assignedTo,source,stage,closedBy,closedReason,keywords'; - -$config->story->list->actionsOpratedParentStory = ',edit,batchcreate,change,review,recall,'; - -$config->story->custom = new stdclass(); -$config->story->custom->createFields = $config->story->list->customCreateFields; -$config->story->custom->batchCreateFields = 'module,plan,spec,pri,estimate,review,%s'; -$config->story->custom->batchEditFields = 'branch,module,plan,estimate,pri,source,stage,closedBy,closedReason'; - -global $lang, $app; -$config->story->datatable = new stdclass(); -if($app->tab == 'execution') -{ - $config->story->datatable->defaultField = array('id','order', 'pri', 'title', 'plan', 'openedBy', 'assignedTo', 'estimate', 'status', 'stage', 'taskCount', 'actions'); -} -else -{ - $config->story->datatable->defaultField = array('id', 'pri', 'title', 'plan', 'openedBy', 'assignedTo', 'estimate', 'status', 'stage', 'taskCount', 'actions'); -} - -$config->story->datatable->fieldList['id']['title'] = 'idAB'; -$config->story->datatable->fieldList['id']['fixed'] = 'left'; -$config->story->datatable->fieldList['id']['width'] = '60'; -$config->story->datatable->fieldList['id']['required'] = 'yes'; - -if($app->tab == 'execution') -{ - $config->story->datatable->fieldList['order']['title'] = 'order'; - $config->story->datatable->fieldList['order']['fixed'] = 'left'; - $config->story->datatable->fieldList['order']['width'] = '60'; - $config->story->datatable->fieldList['order']['sort'] = 'no'; - $config->story->datatable->fieldList['order']['required'] = 'no'; - $config->story->datatable->fieldList['order']['name'] = $this->lang->story->order; -} - -$config->story->datatable->fieldList['pri']['title'] = 'priAB'; -$config->story->datatable->fieldList['pri']['fixed'] = 'left'; -$config->story->datatable->fieldList['pri']['width'] = '50'; -$config->story->datatable->fieldList['pri']['required'] = 'no'; -$config->story->datatable->fieldList['pri']['name'] = $this->lang->story->pri; - -$config->story->datatable->fieldList['title']['title'] = 'title'; -$config->story->datatable->fieldList['title']['fixed'] = 'left'; -$config->story->datatable->fieldList['title']['width'] = 'auto'; -$config->story->datatable->fieldList['title']['required'] = 'yes'; - -$config->story->datatable->fieldList['branch']['title'] = 'branch'; -$config->story->datatable->fieldList['branch']['fixed'] = 'no'; -$config->story->datatable->fieldList['branch']['width'] = '100'; -$config->story->datatable->fieldList['branch']['required'] = 'no'; - -$config->story->datatable->fieldList['keywords']['title'] = 'keywords'; -$config->story->datatable->fieldList['keywords']['fixed'] = 'no'; -$config->story->datatable->fieldList['keywords']['width'] = '100'; -$config->story->datatable->fieldList['keywords']['required'] = 'no'; - -$config->story->datatable->fieldList['plan']['title'] = 'planAB'; -$config->story->datatable->fieldList['plan']['fixed'] = 'no'; -$config->story->datatable->fieldList['plan']['width'] = '90'; -$config->story->datatable->fieldList['plan']['required'] = 'no'; - -$config->story->datatable->fieldList['source']['title'] = 'source'; -$config->story->datatable->fieldList['source']['fixed'] = 'no'; -$config->story->datatable->fieldList['source']['width'] = '90'; -$config->story->datatable->fieldList['source']['required'] = 'no'; - -$config->story->datatable->fieldList['sourceNote']['title'] = 'sourceNote'; -$config->story->datatable->fieldList['sourceNote']['fixed'] = 'no'; -$config->story->datatable->fieldList['sourceNote']['width'] = '90'; -$config->story->datatable->fieldList['sourceNote']['required'] = 'no'; - -$config->story->datatable->fieldList['category']['title'] = 'category'; -$config->story->datatable->fieldList['category']['fixed'] = 'no'; -$config->story->datatable->fieldList['category']['width'] = '60'; -$config->story->datatable->fieldList['category']['required'] = 'no'; - -$config->story->datatable->fieldList['status']['title'] = 'statusAB'; -$config->story->datatable->fieldList['status']['fixed'] = 'no'; -$config->story->datatable->fieldList['status']['width'] = '60'; -$config->story->datatable->fieldList['status']['required'] = 'no'; - -$config->story->datatable->fieldList['estimate']['title'] = 'estimateAB'; -$config->story->datatable->fieldList['estimate']['fixed'] = 'no'; -$config->story->datatable->fieldList['estimate']['width'] = '50'; -$config->story->datatable->fieldList['estimate']['required'] = 'no'; - -$config->story->datatable->fieldList['stage']['title'] = 'stageAB'; -$config->story->datatable->fieldList['stage']['fixed'] = 'no'; -$config->story->datatable->fieldList['stage']['width'] = '70'; -$config->story->datatable->fieldList['stage']['required'] = 'no'; - -$config->story->datatable->fieldList['openedBy']['title'] = 'openedByAB'; -$config->story->datatable->fieldList['openedBy']['fixed'] = 'no'; -$config->story->datatable->fieldList['openedBy']['width'] = '60'; -$config->story->datatable->fieldList['openedBy']['required'] = 'no'; - -$config->story->datatable->fieldList['openedDate']['title'] = 'openedDate'; -$config->story->datatable->fieldList['openedDate']['fixed'] = 'no'; -$config->story->datatable->fieldList['openedDate']['width'] = '90'; -$config->story->datatable->fieldList['openedDate']['required'] = 'no'; - -$config->story->datatable->fieldList['assignedTo']['title'] = 'assignedToAB'; -$config->story->datatable->fieldList['assignedTo']['fixed'] = 'no'; -$config->story->datatable->fieldList['assignedTo']['width'] = '90'; -$config->story->datatable->fieldList['assignedTo']['required'] = 'no'; - -$config->story->datatable->fieldList['assignedDate']['title'] = 'assignedDate'; -$config->story->datatable->fieldList['assignedDate']['fixed'] = 'no'; -$config->story->datatable->fieldList['assignedDate']['width'] = '90'; -$config->story->datatable->fieldList['assignedDate']['required'] = 'no'; - -$config->story->datatable->fieldList['reviewedBy']['title'] = 'reviewedBy'; -$config->story->datatable->fieldList['reviewedBy']['fixed'] = 'no'; -$config->story->datatable->fieldList['reviewedBy']['width'] = '80'; -$config->story->datatable->fieldList['reviewedBy']['required'] = 'no'; - -$config->story->datatable->fieldList['reviewedDate']['title'] = 'reviewedDate'; -$config->story->datatable->fieldList['reviewedDate']['fixed'] = 'no'; -$config->story->datatable->fieldList['reviewedDate']['width'] = '90'; -$config->story->datatable->fieldList['reviewedDate']['required'] = 'no'; - -$config->story->datatable->fieldList['closedBy']['title'] = 'closedBy'; -$config->story->datatable->fieldList['closedBy']['fixed'] = 'no'; -$config->story->datatable->fieldList['closedBy']['width'] = '80'; -$config->story->datatable->fieldList['closedBy']['required'] = 'no'; - -$config->story->datatable->fieldList['closedDate']['title'] = 'closedDate'; -$config->story->datatable->fieldList['closedDate']['fixed'] = 'no'; -$config->story->datatable->fieldList['closedDate']['width'] = '90'; -$config->story->datatable->fieldList['closedDate']['required'] = 'no'; - -$config->story->datatable->fieldList['closedReason']['title'] = 'closedReason'; -$config->story->datatable->fieldList['closedReason']['fixed'] = 'no'; -$config->story->datatable->fieldList['closedReason']['width'] = '90'; -$config->story->datatable->fieldList['closedReason']['required'] = 'no'; - -$config->story->datatable->fieldList['lastEditedBy']['title'] = 'lastEditedBy'; -$config->story->datatable->fieldList['lastEditedBy']['fixed'] = 'no'; -$config->story->datatable->fieldList['lastEditedBy']['width'] = '80'; -$config->story->datatable->fieldList['lastEditedBy']['required'] = 'no'; - -$config->story->datatable->fieldList['lastEditedDate']['title'] = 'lastEditedDate'; -$config->story->datatable->fieldList['lastEditedDate']['fixed'] = 'no'; -$config->story->datatable->fieldList['lastEditedDate']['width'] = '90'; -$config->story->datatable->fieldList['lastEditedDate']['required'] = 'no'; - -$config->story->datatable->fieldList['activatedDate']['title'] = 'activatedDate'; -$config->story->datatable->fieldList['activatedDate']['fixed'] = 'no'; -$config->story->datatable->fieldList['activatedDate']['width'] = '90'; -$config->story->datatable->fieldList['activatedDate']['required'] = 'no'; - -$config->story->datatable->fieldList['feedbackBy']['title'] = 'feedbackBy'; -$config->story->datatable->fieldList['feedbackBy']['fixed'] = 'no'; -$config->story->datatable->fieldList['feedbackBy']['width'] = '100'; -$config->story->datatable->fieldList['feedbackBy']['required'] = 'no'; - -$config->story->datatable->fieldList['notifyEmail']['title'] = 'notifyEmail'; -$config->story->datatable->fieldList['notifyEmail']['fixed'] = 'no'; -$config->story->datatable->fieldList['notifyEmail']['width'] = '100'; -$config->story->datatable->fieldList['notifyEmail']['required'] = 'no'; - -$config->story->datatable->fieldList['mailto']['title'] = 'mailto'; -$config->story->datatable->fieldList['mailto']['fixed'] = 'no'; -$config->story->datatable->fieldList['mailto']['width'] = '100'; -$config->story->datatable->fieldList['mailto']['required'] = 'no'; - -$config->story->datatable->fieldList['version']['title'] = 'version'; -$config->story->datatable->fieldList['version']['fixed'] = 'no'; -$config->story->datatable->fieldList['version']['width'] = '60'; -$config->story->datatable->fieldList['version']['required'] = 'no'; - -$config->story->datatable->fieldList['taskCount']['title'] = 'T'; -$config->story->datatable->fieldList['taskCount']['fixed'] = 'no'; -$config->story->datatable->fieldList['taskCount']['width'] = '30'; -$config->story->datatable->fieldList['taskCount']['required'] = 'no'; -$config->story->datatable->fieldList['taskCount']['sort'] = 'no'; -$config->story->datatable->fieldList['taskCount']['name'] = $lang->story->taskCount; - -$config->story->datatable->fieldList['bugCount']['title'] = 'B'; -$config->story->datatable->fieldList['bugCount']['fixed'] = 'no'; -$config->story->datatable->fieldList['bugCount']['width'] = '30'; -$config->story->datatable->fieldList['bugCount']['required'] = 'no'; -$config->story->datatable->fieldList['bugCount']['sort'] = 'no'; -$config->story->datatable->fieldList['bugCount']['name'] = $lang->story->bugCount; - -$config->story->datatable->fieldList['caseCount']['title'] = 'C'; -$config->story->datatable->fieldList['caseCount']['fixed'] = 'no'; -$config->story->datatable->fieldList['caseCount']['width'] = '30'; -$config->story->datatable->fieldList['caseCount']['required'] = 'no'; -$config->story->datatable->fieldList['caseCount']['sort'] = 'no'; -$config->story->datatable->fieldList['caseCount']['name'] = $lang->story->caseCount; - -$config->story->datatable->fieldList['actions']['title'] = 'actions'; -$config->story->datatable->fieldList['actions']['fixed'] = 'right'; -$config->story->datatable->fieldList['actions']['width'] = $app->tab == 'project' ? '220' : '200'; -$config->story->datatable->fieldList['actions']['required'] = 'yes'; diff --git a/module/story:q/control.php b/module/story:q/control.php deleted file mode 100644 index 6b3a43444e..0000000000 --- a/module/story:q/control.php +++ /dev/null @@ -1,2660 +0,0 @@ - - * @package story - * @version $Id: control.php 5145 2013-07-15 06:47:26Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -class story extends control -{ - /** - * The construct function, load product, tree, user auto. - * - * @access public - * @return void - */ - public function __construct($module = '', $method = '') - { - parent::__construct($module, $method); - $this->loadModel('product'); - $this->loadModel('project'); - $this->loadModel('execution'); - $this->loadModel('tree'); - $this->loadModel('user'); - $this->loadModel('action'); - } - - /** - * Create a story. - * - * @param int $productID - * @param int $branch - * @param int $moduleID - * @param int $storyID - * @param int $objectID projectID|executionID - * @param int $bugID - * @param int $planID - * @param int $todoID - * @param string $extra for example feedbackID=0 - * @param string $type requirement|story - * @access public - * @return void - */ - public function create($productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $objectID = 0, $bugID = 0, $planID = 0, $todoID = 0, $extra = '', $type = 'story') - { - /* Whether there is a object to transfer story, for example feedback. */ - $extra = str_replace(array(',', ' '), array('&', ''), $extra); - parse_str($extra, $output); - - if($productID == 0 and $objectID == 0) $this->locate($this->createLink('product', 'create')); - - /* Get product id according to the project id when lite vision todo transfer story */ - if($this->config->vision == 'lite' and $productID == 0) - { - $product = $this->loadModel('product')->getProductPairsByProject($objectID); - if(!empty($project)) $productID = key($product); - } - - $this->story->replaceURLang($type); - if($this->app->tab == 'product') - { - $this->product->setMenu($productID); - } - else if($this->app->tab == 'project') - { - $objectID = empty($objectID) ? $this->session->project : $objectID; - $objects = $this->project->getPairsByProgram(); - $objectID = $this->project->saveState($objectID, $objects); - $this->project->setMenu($objectID); - } - else if($this->app->tab == 'execution') - { - $objectID = empty($objectID) ? $this->session->execution : $objectID; - $this->execution->setMenu($objectID); - $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); - if($execution->type == 'kanban') - { - $this->loadModel('kanban'); - $regionPairs = $this->kanban->getRegionPairs($execution->id, 0, 'execution'); - $regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs); - $lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'story'); - $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs); - - $this->view->executionType = $execution->type; - $this->view->regionID = $regionID; - $this->view->laneID = $laneID; - $this->view->regionPairs = $regionPairs; - $this->view->lanePairs = $lanePairs; - } - } - - foreach($output as $paramKey => $paramValue) - { - if(isset($this->config->story->fromObjects[$paramKey])) - { - $fromObjectIDKey = $paramKey; - $fromObjectID = $paramValue; - $fromObjectName = $this->config->story->fromObjects[$fromObjectIDKey]['name']; - $fromObjectAction = $this->config->story->fromObjects[$fromObjectIDKey]['action']; - break; - } - } - - /* If there is a object to transfer story, get it by getById function and set objectID,object in views. */ - if(isset($fromObjectID)) - { - $fromObject = $this->loadModel($fromObjectName)->getById($fromObjectID); - if(!$fromObject) return print(js::error($this->lang->notFound) . js::locate('back', 'parent')); - - $this->view->$fromObjectIDKey = $fromObjectID; - $this->view->$fromObjectName = $fromObject; - } - - if(!empty($_POST)) - { - $response['result'] = 'success'; - - setcookie('lastStoryModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false); - $storyResult = $this->story->create($objectID, $bugID, $from = isset($fromObjectIDKey) ? $fromObjectIDKey : '', $extra); - if(!$storyResult or dao::isError()) - { - $response['result'] = 'fail'; - $response['message'] = dao::getError(); - return $this->send($response); - } - - $storyID = $storyResult['id']; - $productID = $this->post->product ? $this->post->product : $productID; - - if($storyResult['status'] == 'exists') - { - $response['message'] = sprintf($this->lang->duplicate, $this->lang->story->common); - if($objectID == 0) - { - $response['locate'] = $this->createLink('story', 'view', "storyID={$storyID}"); - } - else - { - $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); - $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; - $param = $execution->type == 'project' ? "projectID=$objectID&productID=$productID" : "executionID=$objectID"; - $response['locate'] = $this->createLink($moduleName, 'story', $param); - } - return $this->send($response); - } - - $action = $bugID == 0 ? 'Opened' : 'Frombug'; - $extra = $bugID == 0 ? '' : $bugID; - /* Record related action, for example FromFeedback. */ - if(isset($fromObjectID)) - { - $action = $fromObjectAction; - $extra = $fromObjectID; - } - $actionID = $this->action->create('story', $storyID, $action, '', $extra); - - if($objectID != 0) - { - $object = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch(); - if($object->type != 'project') - { - $actionType = $object->type == 'kanban' ? 'linked2kanban' : 'linked2execution'; - $this->loadModel('action')->create('story', $storyID, $actionType, '', $objectID); - } - else - { - $this->loadModel('action')->create('story', $storyID, 'linked2project', '', $objectID); - } - } - - if($todoID > 0) - { - $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec(); - $this->action->create('todo', $todoID, 'finished', '', "STORY:$storyID"); - } - - $message = $this->executeHooks($storyID); - if($message) $this->lang->saveSuccess = $message; - $response['message'] = $this->lang->saveSuccess; - - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $storyID)); - - /* If link from no head then reload. */ - if(isonlybody()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent')); - - if($this->post->newStory) - { - $response['message'] = $this->lang->story->successSaved . $this->lang->story->newStory; - $response['locate'] = $this->createLink('story', 'create', "productID=$productID&branch=$branch&moduleID=$moduleID&story=0&objectID=$objectID&bugID=$bugID"); - return $this->send($response); - } - - $moduleID = $this->post->module ? $this->post->module : 0; - if($objectID == 0) - { - setcookie('storyModule', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); - $branchID = $this->post->branch ? $this->post->branch : $branch; - $response['locate'] = $this->createLink('product', 'browse', "productID=$productID&branch=$branchID&browseType=¶m=0&type=$type&orderBy=id_desc"); - if($this->session->storyList) $response['locate'] = $this->session->storyList; - } - else - { - setcookie('storyModuleParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, true); - $execution = $this->dao->findById((int)$objectID)->from(TABLE_EXECUTION)->fetch(); - $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; - $param = $execution->type == 'project' ? "projectID=$objectID&productID=$productID" : "executionID=$objectID&orderBy=order_desc&browseType=unclosed"; - $response['locate'] = $this->createLink($moduleName, 'story', $param); - } - if($this->app->getViewType() == 'xhtml') $response['locate'] = $this->createLink('story', 'view', "storyID=$storyID", 'html'); - return $this->send($response); - } - - /* Set products, users and module. */ - if($objectID != 0) - { - $onlyNoClosed = empty($this->config->CRProduct) ? 'noclosed' : ''; - $products = $this->product->getProductPairsByProject($objectID, $onlyNoClosed); - $productID = empty($productID) ? key($products) : $productID; - $product = $this->product->getById(($productID and array_key_exists($productID, $products)) ? $productID : key($products)); - $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct($productID, $objectID, 'noclosed|withMain') : array(); - $branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array(); - $branch = key($branches); - } - else - { - $products = array(); - $productList = $this->product->getOrderedProducts('noclosed'); - foreach($productList as $product) $products[$product->id] = $product->name; - $product = $this->product->getById($productID ? $productID : key($products)); - if(!isset($products[$product->id])) $products[$product->id] = $product->name; - $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array(); - } - - $users = $this->user->getPairs('pdfirst|noclosed|nodeleted'); - $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch); - if(empty($moduleOptionMenu)) return print(js::locate(helper::createLink('tree', 'browse', "productID=$productID&view=story"))); - - /* Init vars. */ - $source = ''; - $sourceNote = ''; - $pri = 3; - $estimate = ''; - $title = ''; - $spec = ''; - $verify = ''; - $keywords = ''; - $mailto = ''; - $color = ''; - - if($storyID > 0) - { - $story = $this->story->getByID($storyID); - $planID = $story->plan; - $source = $story->source; - $sourceNote = $story->sourceNote; - $color = $story->color; - $pri = $story->pri; - $productID = $story->product; - $moduleID = $story->module; - $estimate = $story->estimate; - $title = $story->title; - $spec = htmlSpecialString($story->spec); - $verify = htmlSpecialString($story->verify); - $keywords = $story->keywords; - $mailto = $story->mailto; - } - - if($bugID > 0) - { - $oldBug = $this->loadModel('bug')->getById($bugID); - $productID = $oldBug->product; - $source = 'bug'; - $title = $oldBug->title; - $keywords = $oldBug->keywords; - $spec = $oldBug->steps; - $pri = !empty($oldBug->pri) ? $oldBug->pri : '3'; - if(strpos($oldBug->mailto, $oldBug->openedBy) === false) - { - $mailto = $oldBug->mailto . $oldBug->openedBy . ','; - } - else - { - $mailto = $oldBug->mailto; - } - } - - if($todoID > 0) - { - $todo = $this->loadModel('todo')->getById($todoID); - $source = 'todo'; - $title = $todo->name; - $spec = $todo->desc; - $pri = $todo->pri; - } - - /* Replace the value of story that needs to be replaced with the value of the object that is transferred to story. */ - if(isset($fromObject)) - { - if(isset($this->config->story->fromObjects[$fromObjectIDKey]['source'])) - { - $sourceField = $this->config->story->fromObjects[$fromObjectIDKey]['source']; - $sourceUser = $this->loadModel('user')->getById($fromObject->{$sourceField}); - $source = $sourceUser->role; - $sourceNote = $sourceUser->realname; - } - else - { - $source = $fromObjectName; - $sourceNote = $fromObjectID; - } - - foreach($this->config->story->fromObjects[$fromObjectIDKey]['fields'] as $storyField => $fromObjectField) - { - $$storyField = $fromObject->{$fromObjectField}; - } - } - - /* Get block id of assinge to me. */ - $blockID = 0; - if(isonlybody()) - { - $blockID = $this->dao->select('id')->from(TABLE_BLOCK) - ->where('block')->eq('assingtome') - ->andWhere('module')->eq('my') - ->andWhere('account')->eq($this->app->user->account) - ->orderBy('order_desc') - ->fetch('id'); - } - - /* Get reviewers. */ - $reviewers = $product->reviewer; - if(!$reviewers and $product->acl != 'open') $reviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); - - /* Set Custom. */ - foreach(explode(',', $this->config->story->list->customCreateFields) as $field) $customFields[$field] = $this->lang->story->$field; - $this->view->customFields = $customFields; - $this->view->showFields = $this->config->story->custom->createFields; - - $this->view->title = $product->name . $this->lang->colon . $this->lang->story->create; - $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID&branch=$branch"), $product->name); - $this->view->position[] = $this->lang->story->common; - $this->view->position[] = $this->lang->story->create; - $this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('product', 'browse', "productID=$productID") : ''; - $this->view->products = $products; - $this->view->users = $users; - $this->view->moduleID = $moduleID ? $moduleID : (int)$this->cookie->lastStoryModule; - $this->view->moduleOptionMenu = $moduleOptionMenu; - $this->view->plans = str_replace('2030-01-01', $this->lang->story->undetermined, $this->loadModel('productplan')->getPairsForStory($productID, $branch, 'skipParent|unexpired|noclosed')); - $this->view->planID = $planID; - $this->view->source = $source; - $this->view->sourceNote = $sourceNote; - $this->view->color = $color; - $this->view->pri = $pri; - $this->view->branch = $branch; - $this->view->branches = $branches; - $this->view->stories = $this->story->getParentStoryPairs($productID); - $this->view->productID = $productID; - $this->view->product = $product; - $this->view->reviewers = $this->user->getPairs('noclosed|nodeleted', '', 0, $reviewers); - $this->view->objectID = $objectID; - $this->view->estimate = $estimate; - $this->view->storyTitle = $title; - $this->view->spec = $spec; - $this->view->verify = $verify; - $this->view->keywords = $keywords; - $this->view->mailto = $mailto; - $this->view->blockID = $blockID; - $this->view->URS = $type == 'story' ? $this->story->getRequirements($productID) : ''; - $this->view->needReview = ($this->app->user->account == $product->PO || $objectID > 0 || $this->config->story->needReview == 0) ? "checked='checked'" : ""; - $this->view->type = $type; - - $this->display(); - } - - /** - * Create a batch stories. - * - * @param int $productID - * @param int $branch - * @param int $moduleID - * @param int $storyID - * @param int $executionID - * @param int $plan - * @param string $type requirement|story - * @param string $extra for example feedbackID=0 - * @access public - * @return void - */ - public function batchCreate($productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $executionID = 0, $plan = 0, $type = 'story', $extra = '') - { - /* Set menu. */ - if($executionID) - { - $execution = $this->dao->findById((int)$executionID)->from(TABLE_EXECUTION)->fetch(); - if($execution->type == 'project') - { - $this->project->setMenu($executionID); - $this->app->rawModule = 'projectstory'; - $this->lang->navGroup->story = 'project'; - $this->lang->product->menu = $this->lang->{$execution->model}->menu; - } - else - { - if($execution->type == 'kanban') - { - $this->loadModel('kanban'); - $extra = str_replace(array(',', ' '), array('&', ''), $extra); - parse_str($extra, $output); - - $regionPairs = $this->kanban->getRegionPairs($executionID, 0, 'execution'); - $regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs); - $lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'story'); - $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs); - - $this->view->regionID = $regionID; - $this->view->laneID = $laneID; - $this->view->regionPairs = $regionPairs; - $this->view->lanePairs = $lanePairs; - } - - $this->execution->setMenu($executionID); - $this->app->rawModule = 'execution'; - $this->lang->navGroup->story = 'execution'; - } - $this->view->execution = $execution; - } - else - { - $this->product->setMenu($productID, $branch); - } - - /* Clear title when switching products and set the session for the current product. */ - if($productID != $this->cookie->preProductID) unset($_SESSION['storyImagesFile']); - setcookie('preProductID', $productID, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true); - - $this->story->replaceURLang($type); - - /* Check can subdivide or not. */ - if($storyID) - { - $story = $this->story->getById($storyID); - if(($story->status != 'active' or $story->stage != 'wait' or $story->parent > 0) and $this->config->vision != 'lite') return print(js::alert($this->lang->story->errorNotSubdivide) . js::locate('back')); - } - - if(!empty($_POST)) - { - $mails = $this->story->batchCreate($productID, $branch, $type); - if(dao::isError()) return print(js::error(dao::getError())); - - $stories = array(); - foreach($mails as $mail) $stories[] = $mail->storyID; - - $lanes = array(); - if(isset($_POST['lanes'])) - { - foreach($mails as $i => $mail) $lanes[$mail->storyID] = $_POST['lanes'][$i]; - } - - /* Project or execution linked stories. */ - if($executionID) - { - $products = array(); - foreach($mails as $story) $products[$story->storyID] = $productID; - $this->execution->linkStory($executionID, $stories, $products, $extra, $lanes); - if($executionID != $this->session->project) $this->execution->linkStory($this->session->project, $stories, $products); - } - - /* If storyID not equal zero, subdivide this story to child stories and close it. */ - if($storyID and !empty($mails)) - { - $this->story->subdivide($storyID, $stories); - if(dao::isError()) return print(js::error(dao::getError())); - } - - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $stories)); - - if(isonlybody()) return print(js::closeModal('parent.parent', 'this')); - - if($storyID) - { - return print(js::locate(inlink('view', "storyID=$storyID"), 'parent')); - } - elseif($executionID) - { - setcookie('storyModuleParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); - $moduleName = $execution->type == 'project' ? 'projectstory' : 'execution'; - $param = $execution->type == 'project' ? "projectID=$executionID&productID=$productID" : "executionID=$executionID&orderBy=id_desc&browseType=unclosed"; - $link = $this->createLink($moduleName, 'story', $param); - return print(js::locate($link, 'parent')); - } - else - { - setcookie('storyModule', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false); - $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID=$productID&branch=$branch&browseType=unclosed&queryID=0&type=$type"); - return print(js::locate($locateLink, 'parent')); - } - } - - /* Set branch and module. */ - $product = $this->product->getById($productID); - $products = $this->product->getPairs(); - if($product) $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]); - - if($executionID != 0) - { - $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct($productID, $executionID, 'noclosed|withMain') : array(); - $branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array(); - $branch = key($branches); - } - else - { - $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array(); - } - - $moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story', 0, $branch === 'all' ? 0 : $branch); - $moduleOptionMenu['ditto'] = $this->lang->story->ditto; - - /* Get reviewers. */ - $reviewers = $product->reviewer; - if(!$reviewers and $product->acl != 'open') $reviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); - - /* Init vars. */ - $planID = $plan; - $pri = 0; - $estimate = ''; - $title = ''; - $spec = ''; - - /* Process upload images. */ - if($this->session->storyImagesFile) - { - $files = $this->session->storyImagesFile; - foreach($files as $fileName => $file) - { - $title = $file['title']; - $titles[$title] = $fileName; - } - $this->view->titles = $titles; - } - $plans = $this->loadModel('productplan')->getPairsForStory($productID, ($branch === 'all' or !in_array($branch, array_keys($branches))) ? 0 : $branch, 'skipParent|unexpired|noclosed'); - $plans['ditto'] = $this->lang->story->ditto; - - $priList = (array)$this->lang->story->priList; - $priList['ditto'] = $this->lang->story->ditto; - - $sourceList = (array)$this->lang->story->sourceList; - $sourceList['ditto'] = $this->lang->story->ditto; - - /* Set Custom*/ - foreach(explode(',', $this->config->story->list->customBatchCreateFields) as $field) - { - if($product->type != 'normal') $customFields[$product->type] = $this->lang->product->branchName[$product->type]; - $customFields[$field] = $this->lang->story->$field; - } - - if($product->type != 'normal') - { - $this->config->story->custom->batchCreateFields = sprintf($this->config->story->custom->batchCreateFields, $product->type); - } - else - { - $this->config->story->custom->batchCreateFields = trim(sprintf($this->config->story->custom->batchCreateFields, ''), ','); - } - - $showFields = $this->config->story->custom->batchCreateFields; - if($product->type == 'normal') - { - $showFields = str_replace(array(0 => ",branch,", 1 => ",platform,"), '', ",$showFields,"); - $showFields = trim($showFields, ','); - } - if($type == 'requirement') - { - unset($customFields['plan']); - $showFields = str_replace('plan', '', $showFields); - } - - $this->view->customFields = $customFields; - $this->view->showFields = $showFields; - - $this->view->title = $product->name . $this->lang->colon . ($storyID ? $this->lang->story->subdivide : $this->lang->story->batchCreate); - $this->view->productName = $product->name; - $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$productID&branch=$branch"), $product->name); - $this->view->position[] = $this->lang->story->common; - $this->view->position[] = $storyID ? $this->lang->story->subdivide : $this->lang->story->batchCreate; - $this->view->storyID = $storyID; - $this->view->products = $products; - $this->view->product = $product; - $this->view->moduleID = $moduleID; - $this->view->moduleOptionMenu = $moduleOptionMenu; - $this->view->plans = $plans; - $this->view->reviewers = $this->user->getPairs('noclosed|nodeleted', '', 0, $reviewers); - $this->view->users = $this->user->getPairs('pdfirst|noclosed|nodeleted'); - $this->view->priList = $priList; - $this->view->sourceList = $sourceList; - $this->view->planID = $planID; - $this->view->pri = $pri; - $this->view->productID = $productID; - $this->view->estimate = $estimate; - $this->view->storyTitle = isset($story->title) ? $story->title : ''; - $this->view->spec = $spec; - $this->view->type = $type; - $this->view->branch = $branch; - $this->view->branches = $branches; - /* When the user is product owner or add story in project or not set review, the default is not to review. */ - $this->view->needReview = ($this->app->user->account == $product->PO || $executionID > 0 || $this->config->story->needReview == 0) ? 0 : 1; - $this->view->forceReview = $this->story->checkForceReview(); - $this->view->executionID = $executionID; - - $this->display(); - } - - /** - * The common action when edit or change a story. - * - * @param int $storyID - * @access public - * @return void - */ - public function commonAction($storyID) - { - /* Get datas. */ - $story = $this->story->getById($storyID); - $product = $this->product->getById($story->product); - $products = $this->product->getPairs(); - $moduleOptionMenu = $this->tree->getOptionMenu($product->id, $viewType = 'story', 0, $story->branch); - - /* Set menu. */ - if($this->app->tab == 'project') - { - $this->loadModel('project')->setMenu($this->session->project); - } - elseif($this->app->tab == 'product') - { - $this->product->setMenu($product->id, $story->branch); - } - elseif($this->app->tab == 'execution') - { - $this->loadModel('execution')->setMenu($this->session->execution); - } - - $this->story->replaceURLang($story->type); - - /* Assign. */ - $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); - $this->view->position[] = $this->lang->story->common; - $this->view->product = $product; - $this->view->products = $products; - $this->view->story = $story; - $this->view->moduleOptionMenu = $moduleOptionMenu; - $this->view->plans = $this->loadModel('productplan')->getPairs($product->id, 0, '', true); - $this->view->actions = $this->action->getList('story', $storyID); - } - - /** - * Edit a story. - * - * @param int $storyID - * @param string $kanbanGroup - * @access public - * @return void - */ - public function edit($storyID, $kanbanGroup = 'default') - { - if(!empty($_POST)) - { - $changes = $this->story->update($storyID); - if(dao::isError()) return print(js::error(dao::getError())); - if($this->post->comment != '' or !empty($changes)) - { - $action = !empty($changes) ? 'Edited' : 'Commented'; - $actionID = $this->action->create('story', $storyID, $action, $this->post->comment); - $this->action->logHistory($actionID, $changes); - - $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - if(isset($_POST['reviewer'])) $this->story->recordReviewAction($story); - } - - $this->executeHooks($storyID); - - if(isonlybody()) - { - $execution = $this->execution->getByID($this->session->execution); - if($this->app->tab == 'execution' and $execution->type == 'kanban') - { - $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; - $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', 0, $kanbanGroup, $rdSearchValue); - $kanbanData = json_encode($kanbanData); - - return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); - } - else - { - return print(js::reload('parent.parent')); - } - } - if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $storyID)); - return print(js::locate($this->createLink($this->app->rawModule, 'view', "storyID=$storyID"), 'parent')); - } - - $this->commonAction($storyID); - - /* Sort products. */ - $myProducts = array(); - $othersProducts = array(); - $products = $this->loadModel('product')->getList(); - - foreach($products as $product) - { - if($product->status == 'normal' and $product->PO == $this->app->user->account) $myProducts[$product->id] = $product->name; - if($product->status == 'normal' and !($product->PO == $this->app->user->account)) $othersProducts[$product->id] = $product->name; - if($product->status == 'closed') continue; - } - $products = $myProducts + $othersProducts; - - /* Assign. */ - $story = $this->story->getById($storyID, 0, true); - $product = $this->product->getById($story->product); - $stories = $this->story->getParentStoryPairs($story->product, $story->parent); - if(isset($stories[$storyID])) unset($stories[$storyID]); - - /* Get users. */ - $users = $this->user->getPairs('pofirst|nodeleted|noclosed', "$story->assignedTo,$story->openedBy,$story->closedBy"); - - $isShowReviewer = false; - $reviewerList = $this->story->getReviewerPairs($story->id, $story->version); - $reviewerList = array_keys($reviewerList); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - if(array_diff($reviewerList, $reviewedBy) and strpos('draft,changed', $story->status) !== false) $isShowReviewer = true; - - $reviewedReviewer = array(); - foreach($reviewedBy as $reviewer) $reviewedReviewer[] = zget($users, $reviewer); - - if($this->app->tab == 'project' or $this->app->tab == 'execution') - { - $objectID = $this->app->tab == 'project' ? $this->session->project : $this->session->execution; - $products = $this->product->getProductPairsByProject($objectID); - $this->view->objectID = $objectID; - } - - /* Display status of branch. */ - $branches = $this->loadModel('branch')->getList($product->id, isset($objectID) ? $objectID : 0, 'all'); - $branchOption = array(); - $branchTagOption = array(); - foreach($branches as $branchInfo) - { - $branchOption[$branchInfo->id] = $branchInfo->name; - $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); - } - - /* Get product reviewers. */ - $productReviewers = $product->reviewer; - if(!$productReviewers and $product->acl != 'open') $productReviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); - - /* Process the module when branch products are switched to normal products. */ - if($product->type == 'normal' and !empty($story->branch)) $this->view->moduleOptionMenu += $this->tree->getModulesName($story->module); - - $this->view->title = $this->lang->story->edit . "STORY" . $this->lang->colon . $this->view->story->title; - $this->view->position[] = $this->lang->story->edit; - $this->view->story = $story; - $this->view->stories = $stories; - $this->view->users = $users; - $this->view->product = $product; - $this->view->plans = $this->loadModel('productplan')->getPairsForStory($story->product, $story->branch == 0 ? 'all' : $story->branch, 'skipParent'); - $this->view->products = $products; - $this->view->branchOption = $branchOption; - $this->view->branchTagOption = $branchTagOption; - $this->view->reviewers = implode(',', $reviewerList); - $this->view->reviewedReviewer = $reviewedReviewer; - $this->view->productReviewers = $this->user->getPairs('noclosed|nodeleted', $reviewerList, 0, $productReviewers); - $this->view->isShowReviewer = $isShowReviewer; - $this->display(); - } - - /** - * Batch edit story. - * - * @param int $productID - * @param int $executionID - * @param int $branch - * @param string $storyType - * @param string $from - * @access public - * @return void - */ - public function batchEdit($productID = 0, $executionID = 0, $branch = 0, $storyType = 'story', $from = '') - { - $this->story->replaceURLang($storyType); - - if($this->app->tab == 'product') - { - $this->product->setMenu($productID); - } - else if($this->app->tab == 'project') - { - $this->project->setMenu($executionID); - } - else if($this->app->tab == 'execution') - { - $this->execution->setMenu($executionID); - } - else if($this->app->tab == 'qa') - { - $this->loadModel('qa')->setMenu('', $productID); - } - else if($this->app->tab == 'my') - { - $this->loadModel('my'); - if($from == 'work') $this->lang->my->menu->work['subModule'] = 'story'; - if($from == 'contribute') $this->lang->my->menu->contribute['subModule'] = 'story'; - } - - /* Load model. */ - $this->loadModel('productplan'); - - if($this->post->titles) - { - $allChanges = $this->story->batchUpdate(); - - if($allChanges) - { - foreach($allChanges as $storyID => $changes) - { - if(empty($changes)) continue; - - $actionID = $this->action->create('story', $storyID, 'Edited'); - $this->action->logHistory($actionID, $changes); - } - } - return print(js::locate($this->session->storyList, 'parent')); - } - - if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent')); - $storyIdList = $this->post->storyIdList; - $storyIdList = array_unique($storyIdList); - - /* Get edited stories. */ - $stories = $this->story->getByList($storyIdList); - - $this->loadModel('branch'); - if($productID and !$executionID) - { - $product = $this->product->getByID($productID); - $branchProduct = $product->type == 'normal' ? false : true; - - $branches = 0; - $branchTagOption = array(); - if($branchProduct) - { - $branches = $this->branch->getList($productID, $executionID, 'all'); - foreach($branches as $branchInfo) $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); - $branches = array_keys($branches); - } - - $modulePairs = $this->tree->getOptionMenu($productID, 'story', 0, $branches); - $moduleList = $branchProduct ? $modulePairs : array(0 => $modulePairs); - - $modules = array($productID => $moduleList); - $plans = array($productID => $this->productplan->getBranchPlanPairs($productID, '', true)); - $products = array($productID => $product); - $branchTagOption = array($productID => $branchTagOption); - } - else - { - $branchProduct = false; - $modules = array(); - $branchTagOption = array(); - $products = array(); - - if($executionID) - { - /* The stories of project or execution. */ - $execution = $this->execution->getByID($executionID); - $products = $this->loadModel('product')->getProducts($executionID); - } - else - { - /* The stories of my. */ - $productIdList = array(); - foreach($stories as $story) $productIdList[$story->product] = $story->product; - $products = $this->product->getByIdList($productIdList); - } - - foreach($products as $storyProduct) - { - $branches = 0; - if($storyProduct->type != 'normal') - { - $branches = $this->branch->getList($storyProduct->id, $executionID, 'all'); - foreach($branches as $branchInfo) $branches[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : ''); - $branchTagOption[$storyProduct->id] = array(BRANCH_MAIN => $this->lang->branch->main) + $branches; - - $branches = array_keys($branches); - } - - $modulePairs = $this->tree->getOptionMenu($storyProduct->id, 'story', 0, $branches); - $modules[$storyProduct->id] = $storyProduct->type != 'normal' ? $modulePairs : array(0 => $modulePairs); - - $plans[$storyProduct->id] = $this->productplan->getBranchPlanPairs($storyProduct->id, $branches, true); - if(empty($plans[$storyProduct->id])) $plans[$storyProduct->id][0] = $plans[$storyProduct->id]; - - if($storyProduct->type != 'normal') $branchProduct = true; - } - } - - /* Set ditto option for users. */ - $users = $this->loadModel('user')->getPairs('nodeleted'); - $users = array('' => '', 'ditto' => $this->lang->story->ditto) + $users; - - /* Set Custom*/ - foreach(explode(',', $this->config->story->list->customBatchEditFields) as $field) $customFields[$field] = $this->lang->story->$field; - $showFields = $this->config->story->custom->batchEditFields; - if($storyType == 'requirement') - { - unset($customFields['plan']); - unset($customFields['stage']); - $showFields = str_replace('plan', '', $showFields); - $showFields = str_replace('stage', '', $showFields); - } - $this->view->customFields = $customFields; - $this->view->showFields = $showFields; - - /* Judge whether the editedStories is too large and set session. */ - $countInputVars = count($stories) * (count(explode(',', $this->config->story->custom->batchEditFields)) + 3); - $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); - if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); - - /* Append module when change product type. */ - $moduleList = array(0 => '/'); - foreach($stories as $story) - { - if(isset($modules[$story->product][$story->branch])) - { - $moduleList[$story->id] = $modules[$story->product][$story->branch]; - } - else - { - $moduleList[$story->id] = $modules[$story->product][0] + $this->tree->getModulesName($story->module); - } - } - - $this->view->position[] = $this->lang->story->common; - $this->view->position[] = $this->lang->story->batchEdit; - $this->view->title = $this->lang->story->batchEdit; - $this->view->users = $users; - $this->view->priList = array('0' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->priList; - $this->view->sourceList = array('' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->sourceList; - $this->view->reasonList = array('' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->reasonList; - $this->view->stageList = array('' => '', 'ditto' => $this->lang->story->ditto) + $this->lang->story->stageList; - $this->view->productID = $productID; - $this->view->products = $products; - $this->view->branchProduct = $branchProduct; - $this->view->storyIdList = $storyIdList; - $this->view->branch = $branch; - $this->view->plans = array('' => '') + $plans; - $this->view->storyType = $storyType; - $this->view->stories = $stories; - $this->view->executionID = $executionID; - $this->view->branchTagOption = $branchTagOption; - $this->view->moduleList = $moduleList; - $this->display(); - } - - /** - * Change a story. - * - * @param int $storyID - * @access public - * @return void - */ - public function change($storyID) - { - if(!empty($_POST)) - { - $changes = $this->story->change($storyID); - if(dao::isError()) - { - if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'fail', 'message' => dao::getError())); - return print(js::error(dao::getError())); - } - $story = $this->story->getByID($storyID); - $version = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch('version'); - $files = $this->loadModel('file')->saveUpload($story->type, $storyID, $version); - - if(empty($files) and $this->post->uid != '' and isset($_SESSION['album']['used'][$this->post->uid])) $files = $this->file->getPairs($_SESSION['album']['used'][$this->post->uid]); - - if($this->post->comment != '' or !empty($changes) or !empty($files)) - { - $action = (!empty($changes) or !empty($files)) ? 'Changed' : 'Commented'; - $fileAction = ''; - if(!empty($files)) $fileAction = $this->lang->addFiles . join(',', $files) . "\n" ; - $actionID = $this->action->create('story', $storyID, $action, $fileAction . $this->post->comment); - $this->action->logHistory($actionID, $changes); - } - - $this->executeHooks($storyID); - - $module = $this->app->tab == 'project' ? 'projectstory' : 'story'; - - if(isonlybody()) return print(js::reload('parent.parent')); - - if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); - return print(js::locate($this->createLink($module, 'view', "storyID=$storyID"), 'parent')); - } - - $this->commonAction($storyID); - $this->story->getAffectedScope($this->view->story); - $this->app->loadLang('task'); - $this->app->loadLang('bug'); - $this->app->loadLang('testcase'); - $this->app->loadLang('execution'); - - $story = $this->story->getById($storyID); - $reviewer = $this->story->getReviewerPairs($storyID, $story->version); - $product = $this->loadModel('product')->getByID($story->product); - - /* Get product reviewers. */ - $productReviewers = $product->reviewer; - if(!$productReviewers and $product->acl != 'open') $productReviewers = $this->loadModel('user')->getProductViewListUsers($product, '', '', '', ''); - - /* Assign. */ - $this->view->title = $this->lang->story->change . "STORY" . $this->lang->colon . $this->view->story->title; - $this->view->users = $this->user->getPairs('pofirst|nodeleted|noclosed', $this->view->story->assignedTo); - $this->view->position[] = $this->lang->story->change; - $this->view->needReview = ($this->app->user->account == $this->view->product->PO || $this->config->story->needReview == 0) ? "checked='checked'" : ""; - $this->view->reviewer = implode(',', array_keys($reviewer)); - $this->view->productReviewers = $this->user->getPairs('noclosed|nodeleted', $reviewer, 0, $productReviewers); - - $this->display(); - } - - /** - * Activate a story. - * - * @param int $storyID - * @access public - * @return void - */ - public function activate($storyID) - { - if(!empty($_POST)) - { - $changes = $this->story->activate($storyID); - if(dao::isError()) return print(js::error(dao::getError())); - - if($changes) - { - $actionID = $this->action->create('story', $storyID, 'Activated', $this->post->comment); - $this->action->logHistory($actionID, $changes); - } - - $this->executeHooks($storyID); - - if(isonlybody()) - { - $execution = $this->execution->getByID($this->session->execution); - if($this->app->tab == 'execution' and $execution->type == 'kanban') - { - $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; - $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; - $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; - $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue); - $kanbanData = json_encode($kanbanData); - - return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); - } - else - { - return print(js::closeModal('parent.parent', 'this')); - } - } - return print(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); - } - - $this->commonAction($storyID); - - /* Assign. */ - $this->view->title = $this->lang->story->activate . "STORY" . $this->lang->colon . $this->view->story->title; - $this->view->users = $this->user->getPairs('pofirst|nodeleted', $this->view->story->closedBy); - $this->view->position[] = $this->lang->story->activate; - $this->display(); - } - - /** - * View a story. - * - * @param int $storyID - * @param int $version - * @param int $param - * @access public - * @return void - */ - public function view($storyID, $version = 0, $param = 0) - { - $uri = $this->app->getURI(true); - $this->session->set('productList', $uri . "#app={$this->app->tab}", 'product'); - $this->session->set('productPlanList', $uri, 'product'); - - $storyID = (int)$storyID; - $story = $this->story->getById($storyID, $version, true); - $linkModuleName = $this->config->vision == 'lite' ? 'project' : 'product'; - if(!$story) return print(js::error($this->lang->notFound) . js::locate($this->createLink($linkModuleName, 'index'))); - - $story = $this->story->mergeReviewer($story, true); - - $this->story->replaceURLang($story->type); - - $story->files = $this->loadModel('file')->getByObject($story->type, $storyID); - $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id, type, status')->fetch(); - $plan = $this->dao->findById($story->plan)->from(TABLE_PRODUCTPLAN)->fetch('title'); - $bugs = $this->dao->select('id,title,status,pri,severity')->from(TABLE_BUG)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll(); - $fromBug = $this->dao->select('id,title')->from(TABLE_BUG)->where('toStory')->eq($storyID)->fetch(); - $cases = $this->dao->select('id,title,status,pri')->from(TABLE_CASE)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll(); - $linkedMRs = $this->loadModel('mr')->getLinkedMRPairs($storyID, 'story'); - $modulePath = $this->tree->getParents($story->module); - $storyModule = empty($story->module) ? '' : $this->tree->getById($story->module); - - /* Set the menu. */ - $from = $this->app->tab; - if($from == 'execution') - { - $this->execution->setMenu($param); - } - elseif($from == 'project') - { - $this->loadModel('project')->setMenu($this->session->project); - } - elseif($from == 'qa') - { - $products = $this->product->getProductPairsByProject(0, 'noclosed'); - $this->loadModel('qa')->setMenu($products, $story->product); - } - else - { - $this->product->setMenu($story->product, $story->branch); - } - - if($product->type != 'normal') $this->lang->product->branch = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]); - - $reviewers = $this->story->getReviewerPairs($storyID, $story->version); - $reviewedBy = trim($story->reviewedBy, ','); - - $this->executeHooks($storyID); - - $title = "STORY #$story->id $story->title - $product->name"; - $position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); - $position[] = $this->lang->story->common; - $position[] = $this->lang->story->view; - - $this->view->title = $title; - $this->view->position = $position; - $this->view->product = $product; - $this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id); - $this->view->plan = $plan; - $this->view->bugs = $bugs; - $this->view->fromBug = $fromBug; - $this->view->cases = $cases; - $this->view->story = $story; - $this->view->linkedMRs = $linkedMRs; - $this->view->track = $this->story->getTrackByID($story->id); - $this->view->users = $this->user->getPairs('noletter'); - $this->view->reviewers = $reviewers; - $this->view->relations = $this->story->getStoryRelation($story->id, $story->type); - $this->view->executions = $this->execution->getPairs(0, 'all', 'nocode'); - $this->view->execution = empty($story->execution) ? array() : $this->dao->findById($story->execution)->from(TABLE_EXECUTION)->fetch(); - $this->view->actions = $this->action->getList('story', $storyID); - $this->view->storyModule = $storyModule; - $this->view->modulePath = $modulePath; - $this->view->version = $version == 0 ? $story->version : $version; - $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('story', $storyID); - $this->view->from = $from; - $this->view->param = $param; - - $this->display(); - } - - /** - * Delete a story. - * - * @param int $storyID - * @param string $confirm yes|no - * @param string $from taskkanban - * @access public - * @return void - */ - public function delete($storyID, $confirm = 'no', $from = '') - { - $story = $this->story->getById($storyID); - if($story->parent < 0) return print(js::alert($this->lang->story->cannotDeleteParent)); - - if($confirm == 'no') - { - return print(js::confirm($this->lang->story->confirmDelete, $this->createLink('story', 'delete', "story=$storyID&confirm=yes&from=$from"), '')); - } - else - { - $this->dao->update(TABLE_STORY)->set('deleted')->eq(1)->where('id')->eq($storyID)->exec(); - $this->loadModel('action')->create($story->type, $storyID, 'deleted', '', ACTIONMODEL::CAN_UNDELETED); - - if($story->parent > 0) - { - $this->story->updateParentStatus($story->id); - $this->loadModel('action')->create('story', $task->parent, 'deleteChildrenStory', '', $storyID); - } - - $this->executeHooks($storyID); - - if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success')); - - if($this->app->tab == 'execution' and $from == 'taskkanban') return print(js::reload('parent')); - - if(isonlybody()) return print(js::reload('parent.parent')); - - $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID={$story->product}"); - return print(js::locate($locateLink, 'parent')); - } - } - - /** - * Review a story. - * - * @param int $storyID - * @param string $from product|project - * @access public - * @return void - */ - public function review($storyID, $from = 'product') - { - if(!empty($_POST)) - { - $changes = $this->story->review($storyID); - if(dao::isError()) return print(js::error(dao::getError())); - - if($changes) - { - $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - $actionID = $this->story->recordReviewAction($story, $this->post->result, $this->post->closedReason); - $this->action->logHistory($actionID, $changes); - } - - $this->executeHooks($storyID); - - if(isonlybody()) return print(js::reload('parent.parent')); - - $module = $from == 'project' ? 'projectstory' : 'story'; - if(defined('RUN_MODE') and RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $storyID)); - return print(js::locate($this->createLink($module, 'view', "storyID=$storyID"), 'parent')); - } - - /* Get story and product. */ - $story = $this->story->getById($storyID); - $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); - - $this->story->replaceURLang($story->type); - - /* Set menu. */ - if($this->app->tab == 'project') - { - $this->loadModel('project')->setMenu($this->session->project); - } - elseif($this->app->tab == 'product') - { - $this->product->setMenu($product->id, $story->branch); - } - elseif($this->app->tab == 'execution') - { - $this->loadModel('execution')->setMenu($this->session->execution); - } - - /* Set the review result options. */ - $reviewers = $this->story->getReviewerPairs($storyID, $story->version); - $this->lang->story->resultList = $this->lang->story->reviewResultList; - - if($story->status == 'draft' and $story->version == 1) unset($this->lang->story->resultList['revert']); - - if($story->status == 'changed') unset($this->lang->story->resultList['reject']); - - if(count($reviewers) > 1) unset($this->lang->story->resultList['revert']); - - $this->view->title = $this->lang->story->review . "STORY" . $this->lang->colon . $story->title; - $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); - $this->view->position[] = $this->lang->story->common; - $this->view->position[] = $this->lang->story->review; - - $this->view->product = $product; - $this->view->story = $story; - $this->view->actions = $this->action->getList('story', $storyID); - $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noletter', "$story->lastEditedBy,$story->openedBy"); - $this->view->reviewers = $reviewers; - - /* Get the affcected things. */ - $this->story->getAffectedScope($this->view->story); - $this->app->loadLang('task'); - $this->app->loadLang('bug'); - $this->app->loadLang('testcase'); - $this->app->loadLang('execution'); - - $this->display(); - } - - /** - * Batch review stories. - * - * @param string $result - * @param string $reason - * @access public - * @return void - */ - public function batchReview($result, $reason = '') - { - if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent')); - $storyIdList = $this->post->storyIdList; - $storyIdList = array_unique($storyIdList); - $actions = $this->story->batchReview($storyIdList, $result, $reason); - - if(dao::isError()) return print(js::error(dao::getError())); - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - echo js::reload('parent'); - } - - /** - * Recall the story review. - * - * @param int $storyID - * @access public - * @return void - */ - public function recall($storyID) - { - $story = $this->story->getById($storyID); - $this->story->recall($storyID); - $this->loadModel('action')->create('story', $storyID, 'Recalled'); - - $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('product', 'browse', "productID={$story->product}"); - echo js::locate($locateLink, 'parent'); - } - - /** - * Close a story. - * - * @param int $storyID - * @access public - * @return void - */ - public function close($storyID) - { - if(!empty($_POST)) - { - $changes = $this->story->close($storyID); - if(dao::isError()) return print(js::error(dao::getError())); - $this->story->closeParentRequirement($storyID); - - if($changes) - { - $actionID = $this->action->create('story', $storyID, 'Closed', $this->post->comment, ucfirst($this->post->closedReason) . ($this->post->duplicateStory ? ':' . (int)$this->post->duplicateStory : '')); - $this->action->logHistory($actionID, $changes); - } - - $this->executeHooks($storyID); - - if(isonlybody()) - { - $execution = $this->execution->getByID($this->session->execution); - if($this->app->tab == 'execution' and $execution->type == 'kanban') - { - $this->loadModel('kanban')->updateLane($this->session->execution, 'story', $storyID); - $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; - $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default'; - $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; - $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue); - $kanbanData = json_encode($kanbanData); - - return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); - } - else - { - return print(js::closeModal('parent.parent', 'this', "function(){parent.parent.location.reload();}")); - } - } - - if(defined('RUN_MODE') && RUN_MODE == 'api') - { - return $this->send(array('status' => 'success', 'data' => $storyID)); - } - else - { - return print(js::locate(inlink('view', "storyID=$storyID"), 'parent')); - } - } - - /* Get story and product. */ - $story = $this->story->getById($storyID); - $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id')->fetch(); - - $this->story->replaceURLang($story->type); - - /* Set menu. */ - $this->product->setMenu($product->id, $story->branch); - - /* Set the closed reason options and remove subdivided options. */ - if($story->status == 'draft') unset($this->lang->story->reasonList['cancel']); - unset($this->lang->story->reasonList['subdivided']); - - $this->view->title = $this->lang->story->close . "STORY" . $this->lang->colon . $story->title; - $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id&branch=$story->branch"), $product->name); - $this->view->position[] = $this->lang->story->common; - $this->view->position[] = $this->lang->story->close; - - $this->view->product = $product; - $this->view->story = $story; - $this->view->actions = $this->action->getList('story', $storyID); - $this->view->users = $this->loadModel('user')->getPairs(); - $this->view->reasonList = $this->lang->story->reasonList; - $this->display(); - } - - /** - * Batch close story. - * - * @param int $productID - * @param int $executionID - * @param string $storyType - * @param string $from - * @access public - * @return void - */ - public function batchClose($productID = 0, $executionID = 0, $storyType = 'story', $from = '') - { - if($this->post->comments) - { - $data = fixer::input('post')->get(); - $allChanges = $this->story->batchClose(); - - if($allChanges) - { - foreach($allChanges as $storyID => $changes) - { - $actionID = $this->action->create('story', $storyID, 'Closed', htmlSpecialString($this->post->comments[$storyID]), ucfirst($this->post->closedReasons[$storyID]) . ($this->post->duplicateStoryIDList[$storyID] ? ':' . (int)$this->post->duplicateStoryIDList[$storyID] : '')); - $this->action->logHistory($actionID, $changes); - } - } - - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - return print(js::locate($this->session->storyList, 'parent')); - } - - if(!$this->post->storyIdList) return print(js::locate($this->session->storyList, 'parent')); - $storyIdList = $this->post->storyIdList; - $storyIdList = array_unique($storyIdList); - - /* Get edited stories. */ - $stories = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($storyIdList)->fetchAll('id'); - foreach($stories as $story) - { - if($story->parent == -1) - { - $skipStory[] = $story->id; - unset($stories[$story->id]); - } - if($story->status == 'closed') - { - $closedStory[] = $story->id; - unset($stories[$story->id]); - } - } - - $errorTips = ''; - if(isset($closedStory)) $errorTips .= sprintf($this->lang->story->closedStory, join(',', $closedStory)); - if(isset($skipStory)) $errorTips .= sprintf($this->lang->story->skipStory, join(',', $skipStory)); - if(isset($skipStory) || isset($closedStory)) echo js::alert($errorTips); - - /* The stories of a product. */ - if($this->app->tab == 'product') - { - $this->product->setMenu($productID); - $product = $this->product->getByID($productID); - $this->view->position[] = html::a($this->createLink('product', 'browse', "product=$product->id"), $product->name); - $this->view->title = $product->name . $this->lang->colon . $this->lang->story->batchClose; - } - /* The stories of a execution. */ - elseif($executionID) - { - $this->lang->story->menu = $this->lang->execution->menu; - $this->lang->story->menuOrder = $this->lang->execution->menuOrder; - $this->execution->setMenu($executionID); - $execution = $this->execution->getByID($executionID); - $this->view->position[] = html::a($this->createLink('execution', 'story', "executionID=$execution->id"), $execution->name); - $this->view->title = $execution->name . $this->lang->colon . $this->lang->story->batchClose; - } - else - { - if($this->app->tab == 'project') - { - $this->project->setMenu($this->session->project); - $this->view->title = $this->lang->story->batchEdit; - } - else - { - $this->lang->story->menu = $this->lang->my->menu; - $this->lang->story->menuOrder = $this->lang->my->menuOrder; - - if($from == 'work') $this->lang->my->menu->work['subModule'] = 'story'; - if($from == 'contribute') $this->lang->my->menu->contribute['subModule'] = 'story'; - - $this->view->position[] = html::a($this->createLink('my', 'story'), $this->lang->my->story); - $this->view->title = $this->lang->story->batchEdit; - } - } - - /* Judge whether the editedStories is too large and set session. */ - $countInputVars = count($stories) * $this->config->story->batchClose->columns; - $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars); - if($showSuhosinInfo) $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars); - - unset($this->lang->story->reasonList['subdivided']); - - $this->view->position[] = $this->lang->story->common; - $this->view->position[] = $this->lang->story->batchClose; - $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, $viewType = 'story'); - $this->view->plans = $this->loadModel('productplan')->getPairs($productID); - $this->view->productID = $productID; - $this->view->stories = $stories; - $this->view->storyIdList = $storyIdList; - $this->view->storyType = $storyType; - $this->view->reasonList = $this->lang->story->reasonList; - - $this->display(); - } - - /** - * Batch change the module of story. - * - * @param int $moduleID - * @access public - * @return void - */ - public function batchChangeModule($moduleID) - { - if(empty($_POST['storyIdList'])) return print(js::locate($this->session->storyList, 'parent')); - $storyIdList = $this->post->storyIdList; - $storyIdList = array_unique($storyIdList); - $allChanges = $this->story->batchChangeModule($storyIdList, $moduleID); - if(dao::isError()) return print(js::error(dao::getError())); - foreach($allChanges as $storyID => $changes) - { - $actionID = $this->action->create('story', $storyID, 'Edited'); - $this->action->logHistory($actionID, $changes); - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - echo js::locate($this->session->storyList, 'parent'); - } - - /** - * Batch stories convert to tasks. - * - * @param int $executionID - * @access public - * @return void - */ - public function batchToTask($executionID = 0) - { - if(!empty($_POST)) - { - $response['result'] = 'success'; - $response['message'] = $this->lang->story->successToTask; - - $tasks = $this->story->batchToTask($executionID, $this->session->project); - - if(dao::isError()) - { - $response['result'] = 'fail'; - $response['message'] = dao::getError(); - return $this->send($response); - } - - if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $tasks)); - $response['locate'] = $this->createLink('execution', 'task', "executionID=$executionID"); - return $this->send($response); - } - } - - /** - * Batch change the plan of story. - * - * @param int $planID - * @access public - * @return void - */ - public function batchChangePlan($planID, $oldPlanID = 0) - { - if(empty($_POST['storyIdList'])) return print(js::locate($this->session->storyList, 'parent')); - $storyIdList = $this->post->storyIdList; - $storyIdList = array_unique($storyIdList); - $allChanges = $this->story->batchChangePlan($storyIdList, $planID, $oldPlanID); - if(dao::isError()) return print(js::error(dao::getError())); - foreach($allChanges as $storyID => $changes) - { - $actionID = $this->action->create('story', $storyID, 'Edited'); - $this->action->logHistory($actionID, $changes); - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - echo js::locate($this->session->storyList, 'parent'); - } - - /** - * Batch change branch. - * - * @param int $branchID - * @param string $confirm yes|no - * @param string $storyIdList - * @access public - * @return void - */ - public function batchChangeBranch($branchID, $confirm = '', $storyIdList = '') - { - if(empty($_POST['storyIdList'])) return print(js::locate($this->session->storyList, 'parent')); - $storyIdList = $this->post->storyIdList; - $plans = $this->loadModel('productplan')->getPlansByStories($storyIdList); - if(empty($confirm)) - { - $stories = $this->story->getByList($storyIdList); - $normalStotyIdList = ''; - $conflictStoryIdList = ''; - $conflictStoryArray = array(); - - /* Determine whether there is a conflict between the branch of the story and the linked plan. */ - foreach($storyIdList as $storyID) - { - if($stories[$storyID]->branch != $branchID and $branchID != BRANCH_MAIN and isset($plans[$storyID])) - { - foreach($plans[$storyID] as $plan) - { - if($plan->branch != $branchID) - { - $conflictStoryIdList .= '[' . $storyID . ']'; - $conflictStoryArray[] = $storyID; - break; - } - } - } - } - - /* Prompt the user whether to continue to modify the conflicting stories branch. */ - if($conflictStoryIdList) - { - $normalStotyIdList = array_diff($storyIdList, $conflictStoryArray); - $normalStotyIdList = implode(',', $normalStotyIdList); - $storyIdList = implode(',', $storyIdList); - $confirmURL = $this->createLink('story', 'batchChangeBranch', "branchID=$branchID&confirm=yes&storyIdList=$storyIdList"); - $cancelURL = $this->createLink('story', 'batchChangeBranch', "branchID=$branchID&confirm=no&storyIdList=$normalStotyIdList"); - return print(js::confirm(sprintf($this->lang->story->confirmChangeBranch, $conflictStoryIdList ), $confirmURL, $cancelURL)); - } - } - - if(is_string($storyIdList)) $storyIdList = array_filter(explode(',', $storyIdList)); - $storyIdList = array_unique($storyIdList); - $allChanges = $this->story->batchChangeBranch($storyIdList, $branchID, $confirm, $plans); - if(dao::isError()) return print(js::error(dao::getError())); - foreach($allChanges as $storyID => $changes) - { - $actionID = $this->action->create('story', $storyID, 'Edited'); - $this->action->logHistory($actionID, $changes); - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - echo js::reload('parent'); - } - - /** - * Batch change the stage of story. - * - * @param string $stage - * @access public - * @return void - */ - public function batchChangeStage($stage) - { - $storyIdList = $this->post->storyIdList; - if(empty($storyIdList)) return print(js::locate($this->session->storyList, 'parent')); - - $storyIdList = array_unique($storyIdList); - $allChanges = $this->story->batchChangeStage($storyIdList, $stage); - if(dao::isError()) return print(js::error(dao::getError())); - - $action = $stage == 'verified' ? 'Verified' : 'Edited'; - foreach($allChanges as $storyID => $changes) - { - $actionID = $this->action->create('story', $storyID, $action); - $this->action->logHistory($actionID, $changes); - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - echo js::locate($this->session->storyList, 'parent'); - } - - /** - * Assign to. - * - * @param int $storyID - * @param string $kanbanGroup - * @access public - * @return void - */ - public function assignTo($storyID, $kanbanGroup = 'default') - { - if(!empty($_POST)) - { - $changes = $this->story->assign($storyID); - if(dao::isError()) return print(js::error(dao::getError())); - if($changes) - { - $actionID = $this->loadModel('action')->create('story', $storyID, 'Assigned', $this->post->comment, $this->post->assignedTo); - $this->action->logHistory($actionID, $changes); - } - - $this->executeHooks($storyID); - - if(isonlybody()) - { - $execution = $this->execution->getByID($this->session->execution); - if($this->app->tab == 'execution' and $execution->type == 'kanban') - { - $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all'; - $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : ''; - $kanbanData = $this->loadModel('kanban')->getRDKanban($this->session->execution, $execLaneType, 'id_desc', 0, $kanbanGroup, $rdSearchValue); - $kanbanData = json_encode($kanbanData); - - return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); - } - else - { - return print(js::closeModal('parent.parent', 'this', 'function(){parent.parent.$(\'[data-ride="searchList"]\').searchList();}')); - } - } - return print(js::locate($this->createLink('story', 'view', "storyID=$storyID"), 'parent')); - } - - /* Get story and product. */ - $story = $this->story->getById($storyID); - $products = $this->product->getPairs(); - - /* Set menu. */ - $this->product->setMenu($story->product, $story->branch); - - $this->view->title = zget($products, $story->product, '') . $this->lang->colon . $this->lang->story->assign; - $this->view->position[] = $this->lang->story->assign; - $this->view->story = $story; - $this->view->actions = $this->action->getList('story', $storyID); - $this->view->users = ($this->config->vision == 'lite') ? $this->loadModel('user')->getTeamMemberPairs($this->session->project) : $this->loadModel('user')->getPairs('nodeleted|noclosed|pofirst|noletter'); - $this->display(); - } - - /** - * Batch assign to. - * - * @access public - * @return void - */ - public function batchAssignTo() - { - if(!empty($_POST) && isset($_POST['storyIdList'])) - { - $allChanges = $this->story->batchAssignTo(); - if(dao::isError()) return print(js::error(dao::getError())); - foreach($allChanges as $storyID => $changes) - { - $actionID = $this->action->create('story', $storyID, 'Assigned', '', $this->post->assignedTo); - $this->action->logHistory($actionID, $changes); - } - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther'); - echo js::locate($this->session->storyList, 'parent'); - } - - /** - * Story track. - * - * @param int $productID - * @param int|string $branch - * @param int $projectID - * @param int $recTotal - * @param int $recPerPage - * @param int $pageID - * @access public - * @return void - */ - public function track($productID, $branch = '', $projectID = 0, $recTotal = 0, $recPerPage = 20, $pageID = 1) - { - $branch = ($this->cookie->preBranch !== '' and $branch === '') ? $this->cookie->preBranch : $branch; - setcookie('preBranch', $branch, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true); - - /* Set menu. The projectstory module does not execute. */ - if(!$projectID) - { - $products = $this->product->getPairs(); - $productID = $this->product->saveState($productID, $products); - $this->product->products = $this->product->saveState($productID, $products); - $this->product->setMenu($productID, $branch); - } - - /* Save session. */ - $this->session->set('storyList', $this->app->getURI(true), 'product'); - $this->session->set('taskList', $this->app->getURI(true), 'execution'); - $this->session->set('designList', $this->app->getURI(true), 'project'); - $this->session->set('bugList', $this->app->getURI(true), 'qa'); - $this->session->set('caseList', $this->app->getURI(true), 'qa'); - $this->session->set('revisionList', $this->app->getURI(true), 'repo'); - - /* Load pager and get tracks. */ - $this->app->loadClass('pager', $static = true); - $pager = new pager($recTotal, $recPerPage, $pageID); - $tracks = $this->story->getTracks($productID, $branch, $projectID, $pager); - - if($projectID) - { - $this->loadModel('project')->setMenu($projectID); - $projectProducts = $this->product->getProducts($projectID); - } - - $this->view->title = $this->lang->story->track; - $this->view->position[] = $this->lang->story->track; - - $this->view->tracks = $tracks; - $this->view->pager = $pager; - $this->view->productID = $productID; - $this->view->projectProducts = isset($projectProducts) ? $projectProducts : array(); - $this->display(); - } - - /** - * Tasks of a story. - * - * @param int $storyID - * @param int $executionID - * @access public - * @return void - */ - public function tasks($storyID, $executionID = 0) - { - $this->loadModel('task'); - $tasks = $this->task->getStoryTasks($storyID, $executionID); - $this->view->tasks = $tasks; - $this->view->users = $this->user->getPairs('noletter'); - $this->view->summary = $this->execution->summary($tasks); - $this->display(); - } - - /** - * Bugs of a story. - * - * @param int $storyID - * @param int $executionID - * @access public - * @return void - */ - public function bugs($storyID, $executionID = 0) - { - $this->loadModel('bug'); - $this->view->bugs = $this->bug->getStoryBugs($storyID, $executionID); - $this->view->users = $this->user->getPairs('noletter'); - $this->display(); - } - - /** - * Cases of a story. - * - * @param int $storyID - * @access public - * @return void - */ - public function cases($storyID) - { - $this->loadModel('testcase'); - $this->view->cases = $this->testcase->getStoryCases($storyID); - $this->view->users = $this->user->getPairs('noletter'); - $this->view->resultList = array('' => '') + $this->lang->testcase->resultList; - $this->display(); - } - - /** - * Show zero case story. - * - * @param int $productID - * @param int $branchID - * @param string $orderBy - * @access public - * @return void - */ - public function zeroCase($productID = 0, $branchID = 0, $orderBy = 'id_desc', $projectID = 0) - { - $orderBy = empty($orderBy) ? 'id_desc' : $orderBy; - $this->session->set('storyList', $this->app->getURI(true) . '#app=' . $this->app->tab, 'product'); - $this->session->set('caseList', $this->app->getURI(true), $this->app->tab); - - $this->loadModel('testcase'); - if($this->app->tab == 'project') - { - $this->loadModel('project')->setMenu($this->session->project); - $this->app->rawModule = 'qa'; - $this->lang->project->menu->qa['subMenu']->testcase['subModule'] = 'story'; - $products = $this->product->getProducts($this->session->project, 'all', '', false); - $productID = $this->product->saveState($productID, $products); - $this->lang->modulePageNav = $this->product->select($products, $productID, 'story', 'zeroCase', "projectID=$projectID", $branchID); - } - else - { - $products = $this->product->getPairs(); - $productID = $this->product->saveState($productID, $products); - $this->loadModel('qa'); - $this->app->rawModule = 'testcase'; - foreach($this->config->qa->menuList as $module) $this->lang->navGroup->$module = 'qa'; - $this->qa->setMenu($products, $productID, $branchID); - } - - /* Append id for secend sort. */ - $sort = common::appendOrder($orderBy); - - $this->view->title = $this->lang->story->zeroCase; - $this->view->position[] = html::a($this->createLink('testcase', 'browse', "productID=$productID"), $products[$productID]); - $this->view->position[] = $this->lang->story->zeroCase; - - $this->view->stories = $this->story->getZeroCase($productID, $branchID, $sort); - $this->view->users = $this->user->getPairs('noletter'); - $this->view->projectID = $projectID; - $this->view->productID = $productID; - $this->view->branchID = $branchID; - $this->view->orderBy = $orderBy; - $this->view->suiteList = $this->loadModel('testsuite')->getSuites($productID); - $this->view->browseType = ''; - $this->view->product = $this->product->getByID($productID); - $this->display(); - } - - /** - * If type is linkStories, link related stories else link child stories. - * - * @param int $storyID - * @param string $type - * @param string $browseType - * @param int $param - * @access public - * @return void - */ - public function linkStory($storyID, $type = 'linkStories', $linkedStoryID = 0, $browseType = '', $queryID = 0) - { - $this->commonAction($storyID); - - if($type == 'remove') - { - $result = $this->story->unlinkStory($storyID, $linkedStoryID); - return print(js::reload('parent')); - } - - if($_POST) - { - $this->story->linkStories($storyID); - - if(dao::isError()) return print(js::error(dao::getError())); - return print(js::closeModal('parent.parent', 'this')); - } - - /* Get story, product, products, and queryID. */ - $story = $this->story->getById($storyID); - $products = $this->product->getPairs(); - $queryID = 0; - - /* Change for requirement story title. */ - if($story->type == 'story') - { - $this->lang->story->title = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->story->title); - $this->lang->story->create = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->story->create); - $this->config->product->search['fields']['title'] = $this->lang->story->title; - unset($this->config->product->search['fields']['plan']); - unset($this->config->product->search['fields']['stage']); - } - - /* Build search form. */ - $actionURL = $this->createLink('story', 'linkStory', "storyID=$storyID&type=$type&linkedStoryID=$linkedStoryID&browseType=bySearch&queryID=myQueryID", '', true); - $this->product->buildSearchForm($story->product, $products, $queryID, $actionURL); - - /* Get stories to link. */ - $storyType = $story->type; - $stories2Link = $this->story->getStories2Link($storyID, $type, $browseType, $queryID, $storyType); - - /* Assign. */ - $this->view->title = $this->lang->story->linkStory . "STORY" . $this->lang->colon .$this->lang->story->linkStory; - $this->view->position[] = $this->lang->story->linkStory; - $this->view->type = $type; - $this->view->stories2Link = $stories2Link; - $this->view->users = $this->loadModel('user')->getPairs('noletter'); - - $this->display(); - } - - /** - * Process story change. - * - * @param int $storyID - * @param string $result yes|no - * @access public - * @return void - */ - public function processStoryChange($storyID, $result = 'yes') - { - $this->commonAction($storyID); - $story = $this->story->getByID($storyID); - - if($result == 'no') - { - $this->dao->update(TABLE_STORY)->set('URChanged')->eq(0)->where('id')->eq($storyID)->exec(); - return print(js::reload('parent', 'this')); - } - - $this->view->changedStories = $this->story->getChangedStories($story); - $this->view->users = $this->loadModel('user')->getPairs('noletter'); - $this->view->storyID = $storyID; - $this->display(); - } - - /** - * AJAX: get stories of a execution in html select. - * - * @param int $executionID - * @param int $productID - * @param int $branch - * @param int $moduleID - * @param int $storyID - * @param string $number - * @param string $type full - * @param string $status all|unclosed - * @param string $from bug - * @access public - * @return void - */ - public function ajaxGetExecutionStories($executionID, $productID = 0, $branch = 0, $moduleID = 0, $storyID = 0, $number = '', $type = 'full', $status = 'all', $from = '') - { - if($moduleID) - { - $moduleID = $this->loadModel('tree')->getStoryModule($moduleID); - $moduleID = $this->tree->getAllChildID($moduleID); - } - $stories = $this->story->getExecutionStoryPairs($executionID, $productID, $branch, $moduleID, $type, $status); - if($this->app->getViewType() === 'json') - { - return print(json_encode($stories)); - } - elseif($this->app->getViewType() == 'mhtml') - { - return print(html::select('story', empty($stories) ? array('' => '') : $stories, $storyID, 'onchange=setStoryRelated()')); - } - else - { - $storyName = $number === '' ? 'story' : "story[$number]"; - $misc = $from === 'bug' ? 'class=form-control' : 'class=form-control onchange=setStoryRelated(' . $number . ');'; - return print(html::select($storyName, empty($stories) ? array('' => '') : $stories, $storyID, $misc)); - } - } - - /** - * AJAX: get stories of a product in html select. - * - * @param int $productID - * @param int $branch - * @param int $moduleID - * @param int $storyID - * @param string $onlyOption - * @param string $status - * @param int $limit - * @param string $type - * @param bool $hasParent - * @param int $executionID - * @param int $number - * @access public - * @return void - */ - public function ajaxGetProductStories($productID, $branch = 0, $moduleID = 0, $storyID = 0, $onlyOption = 'false', $status = '', $limit = 0, $type = 'full', $hasParent = 1, $executionID = 0, $number = '') - { - if($moduleID) - { - $moduleID = $this->loadModel('tree')->getStoryModule($moduleID); - $moduleID = $this->tree->getAllChildID($moduleID); - } - - $storyStatus = ''; - if($status == 'noclosed') - { - $storyStatus = $this->lang->story->statusList; - unset($storyStatus['closed']); - $storyStatus = array_keys($storyStatus); - } - - if($executionID) - { - $stories = $this->story->getExecutionStoryPairs($executionID, $productID, $branch, $moduleID, $type); - } - else - { - $stories = $this->story->getProductStoryPairs($productID, $branch, $moduleID, $storyStatus, 'id_desc', $limit, $type, 'story', $hasParent); - } - - if(!in_array($this->app->tab, array('execution', 'project')) and empty($stories)) $stories = $this->story->getProductStoryPairs($productID, $branch, 0, $storyStatus, 'id_desc', $limit, $type, 'story', $hasParent); - - $storyID = isset($stories[$storyID]) ? $storyID : 0; - $select = html::select('story' . $number, empty($stories) ? array('' => '') : $stories, $storyID, "class='form-control'"); - - /* If only need options, remove select wrap. */ - if($onlyOption == 'true') return print(substr($select, strpos($select, '>') + 1, -10)); - echo $select; - } - - /** - * AJAX: search stories of a product as json - * - * @param string $key - * @param int $productID - * @param int $moduleID - * @param int $storyID - * @param string $status - * @param int $limit - * @access public - * @return void - */ - public function ajaxSearchProductStories($key, $productID, $branch = 0, $moduleID = 0, $storyID = 0, $status = 'noclosed', $limit = 50) - { - if($moduleID) - { - $moduleID = $this->loadModel('tree')->getStoryModule($moduleID); - $moduleID = $this->tree->getAllChildID($moduleID); - } - - $storyStatus = ''; - if($status == 'noclosed') - { - $storyStatus = $this->lang->story->statusList; - unset($storyStatus['closed']); - $storyStatus = array_keys($storyStatus); - } - - $stories = $this->story->getProductStoryPairs($productID, $branch, $moduleID, $storyStatus, 'id_desc'); - $result = array(); - $i = 0; - foreach ($stories as $id => $story) - { - if($limit > 0 && $i > $limit) break; - if(('#' . $id) === $key || stripos($story, $key) !== false) - { - $result[$id] = $story; - $i++; - } - } - if($i < 1) - { - $result['info'] = $this->lang->noResultsMatch; - } - - echo json_encode($result); - } - - /** - * AJAX: get spec and verify of a story. for web app. - * - * @param int $storyID - * @access public - * @return void - */ - public function ajaxGetDetail($storyID) - { - $this->view->actions = $this->action->getList('story', $storyID); - $this->view->story = $this->story->getByID($storyID); - $this->display(); - } - - /** - * AJAX: get module of a story. - * - * @param int $storyID - * @access public - * @return string - */ - public function ajaxGetInfo($storyID) - { - $story = $this->story->getByID($storyID); - if(empty($story)) return; - - $storyInfo['moduleID'] = $story->module; - $storyInfo['estimate'] = $story->estimate; - $storyInfo['pri'] = $story->pri; - $storyInfo['spec'] = html_entity_decode($story->spec, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'); - - echo json_encode($storyInfo); - } - - /** - * The report page. - * - * @param int $productID - * @param int $branchID - * @param string $storyType - * @param string $browseType - * @param int $moduleID - * @param string $chartType - * @access public - * @return void - */ - public function report($productID, $branchID, $storyType = 'story', $browseType = 'unclosed', $moduleID = 0, $chartType = 'pie') - { - $this->loadModel('report'); - $this->view->charts = array(); - - if(!empty($_POST)) - { - foreach($this->post->charts as $chart) - { - $chartFunc = 'getDataOf' . $chart; - $chartData = $this->story->$chartFunc(); - $chartOption = $this->lang->story->report->$chart; - if(!empty($chartType)) $chartOption->type = $chartType; - $this->story->mergeChartOption($chart); - - $this->view->charts[$chart] = $chartOption; - $this->view->datas[$chart] = $this->report->computePercent($chartData); - } - } - - $this->story->replaceURLang($storyType); - $this->products = $this->product->getPairs(); - $this->product->setMenu($productID, $branchID); - - $this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->story->reportChart; - $this->view->position[] = $this->products[$productID]; - $this->view->position[] = $this->lang->story->reportChart; - $this->view->productID = $productID; - $this->view->branchID = $branchID; - $this->view->browseType = $browseType; - $this->view->storyType = $storyType; - $this->view->moduleID = $moduleID; - $this->view->chartType = $chartType; - $this->view->checkedCharts = $this->post->charts ? join(',', $this->post->charts) : ''; - $this->display(); - } - - /** - * get data to export - * - * @param int $productID - * @param string $orderBy - * @param int $executionID - * @param string $browseType - * @param string $type requirement|story - * @access public - * @return void - */ - public function export($productID, $orderBy, $executionID = 0, $browseType = '', $type = 'story') - { - /* format the fields of every story in order to export data. */ - if($_POST) - { - $this->loadModel('file'); - $this->loadModel('branch'); - $storyLang = $this->lang->story; - $storyConfig = $this->config->story; - - /* Create field lists. */ - $fields = $this->post->exportFields ? $this->post->exportFields : explode(',', $storyConfig->list->exportFields); - foreach($fields as $key => $fieldName) - { - $fieldName = trim($fieldName); - $fields[$fieldName] = isset($storyLang->$fieldName) ? $storyLang->$fieldName : $fieldName; - unset($fields[$key]); - } - - /* Get stories. */ - $stories = array(); - if($this->session->storyOnlyCondition) - { - if($this->post->exportType == 'selected') - { - $stories = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($this->cookie->checkedItem)->orderBy($orderBy)->fetchAll('id'); - } - else - { - $stories = $this->dao->select('*')->from(TABLE_STORY)->where($this->session->storyQueryCondition)->orderBy($orderBy)->fetchAll('id'); - } - } - else - { - $field = $executionID ? 't2.id' : 't1.id'; - if($this->post->exportType == 'selected') - { - $stmt = $this->dbh->query("SELECT * FROM " . TABLE_STORY . "WHERE `id` IN({$this->cookie->checkedItem})" . " ORDER BY " . strtr($orderBy, '_', ' ')); - } - else - { - $stmt = $this->dbh->query($this->session->storyQueryCondition . " ORDER BY " . strtr($orderBy, '_', ' ')); - } - while($row = $stmt->fetch()) $stories[$row->id] = $row; - } - $storyIdList = array_keys($stories); - - if($stories) - { - $children = array(); - foreach($stories as $story) - { - if($story->parent > 0 and isset($stories[$story->parent])) - { - $children[$story->parent][$story->id] = $story; - unset($stories[$story->id]); - } - } - - if(!empty($children)) - { - $reorderStories = array(); - foreach($stories as $story) - { - $reorderStories[$story->id] = $story; - if(isset($children[$story->id])) - { - foreach($children[$story->id] as $childrenID => $childrenStory) - { - $reorderStories[$childrenID] = $childrenStory; - } - } - unset($stories[$story->id]); - } - $stories = $reorderStories; - } - } - - /* Get users, products and relations. */ - $users = $this->loadModel('user')->getPairs('noletter'); - $products = $this->product->getPairs('nocode'); - $relations = $this->story->getStoryRelationByIds($storyIdList, $type); - - /* Get related objects id lists. */ - $relatedProductIdList = array(); - $relatedStoryIdList = array(); - $relatedPlanIdList = array(); - $relatedBranchIdList = array(); - $relatedStoryIds = array(); - - foreach($stories as $story) - { - $relatedProductIdList[$story->product] = $story->product; - $relatedPlanIdList[$story->plan] = $story->plan; - $relatedBranchIdList[$story->branch] = $story->branch; - $relatedStoryIds[$story->id] = $story->id; - - if(isset($relations[$story->id])) $story->childStories = $story->childStories . ',' . $relations[$story->id]; - - /* Process related stories. */ - $relatedStories = $story->childStories . ',' . $story->linkStories . ',' . $story->duplicateStory; - $relatedStories = explode(',', $relatedStories); - foreach($relatedStories as $storyID) - { - if($storyID) $relatedStoryIdList[$storyID] = trim($storyID); - } - } - - $storyTasks = $this->loadModel('task')->getStoryTaskCounts($relatedStoryIds); - $storyBugs = $this->loadModel('bug')->getStoryBugCounts($relatedStoryIds); - $storyCases = $this->loadModel('testcase')->getStoryCaseCounts($relatedStoryIds); - - /* Get related objects title or names. */ - $productsType = $this->dao->select('id, type')->from(TABLE_PRODUCT)->where('id')->in($relatedProductIdList)->fetchPairs(); - $relatedPlans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in(join(',', $relatedPlanIdList))->fetchPairs(); - $relatedStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($relatedStoryIdList)->fetchPairs(); - $relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq($type)->andWhere('objectID')->in($storyIdList)->andWhere('extra')->ne('editor')->fetchGroup('objectID'); - $relatedSpecs = $this->dao->select('*')->from(TABLE_STORYSPEC)->where('`story`')->in($storyIdList)->orderBy('version desc')->fetchGroup('story'); - $relatedBranch = array('0' => $this->lang->branch->main) + $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($relatedBranchIdList)->fetchPairs(); - $relatedModules = $this->loadModel('tree')->getAllModulePairs(); - - foreach($stories as $story) - { - $story->spec = ''; - $story->verify = ''; - if(isset($relatedSpecs[$story->id])) - { - $storySpec = $relatedSpecs[$story->id][0]; - $story->title = $storySpec->title; - $story->spec = $storySpec->spec; - $story->verify = $storySpec->verify; - } - - if($this->post->fileType == 'csv') - { - $story->spec = htmlspecialchars_decode($story->spec); - $story->spec = str_replace("
    ", "\n", $story->spec); - $story->spec = str_replace('"', '""', $story->spec); - $story->spec = str_replace(' ', ' ', $story->spec); - - $story->verify = htmlspecialchars_decode($story->verify); - $story->verify = str_replace("
    ", "\n", $story->verify); - $story->verify = str_replace('"', '""', $story->verify); - $story->verify = str_replace(' ', ' ', $story->verify); - } - /* fill some field with useful value. */ - if(isset($products[$story->product])) $story->product = $this->post->fileType == 'word' ? $products[$story->product] : $products[$story->product] . "(#$story->product)"; - if(isset($relatedModules[$story->module])) $story->module = $this->post->fileType == 'word' ? $relatedModules[$story->module] : $relatedModules[$story->module] . "(#$story->module)"; - if(isset($relatedBranch[$story->branch])) $story->branch = $relatedBranch[$story->branch] . "(#$story->branch)"; - if(isset($story->plan)) - { - $plans = ''; - foreach(explode(',', $story->plan) as $planID) - { - if(empty($planID)) continue; - if(isset($relatedPlans[$planID])) $plans .= $this->post->fileType == 'word' ? $relatedPlans[$planID] : $relatedPlans[$planID] . "(#$planID)"; - } - $story->plan = $plans; - } - if(isset($relatedStories[$story->duplicateStory]) and $story->closedReason != 'duplicate') $story->duplicateStory = $relatedStories[$story->duplicateStory]; - - if(isset($storyLang->priList[$story->pri])) $story->pri = $storyLang->priList[$story->pri]; - if(isset($storyLang->statusList[$story->status])) $story->status = $this->processStatus('story', $story); - if(isset($storyLang->stageList[$story->stage])) $story->stage = $storyLang->stageList[$story->stage]; - if(isset($storyLang->reasonList[$story->closedReason])) $story->closedReason = $storyLang->reasonList[$story->closedReason]; - if(isset($storyLang->sourceList[$story->source])) $story->source = $storyLang->sourceList[$story->source]; - if(isset($storyLang->sourceList[$story->sourceNote])) $story->sourceNote = $storyLang->sourceList[$story->sourceNote]; - - if(isset($users[$story->openedBy])) $story->openedBy = $users[$story->openedBy]; - if(isset($users[$story->assignedTo])) $story->assignedTo = $users[$story->assignedTo] . "(#$story->assignedTo)"; - if(isset($users[$story->lastEditedBy])) $story->lastEditedBy = $users[$story->lastEditedBy]; - if(isset($users[$story->closedBy])) $story->closedBy = $users[$story->closedBy]; - - if(isset($storyTasks[$story->id])) $story->taskCountAB = $storyTasks[$story->id]; - if(isset($storyBugs[$story->id])) $story->bugCountAB = $storyBugs[$story->id]; - if(isset($storyCases[$story->id])) $story->caseCountAB = $storyCases[$story->id]; - - $story->openedDate = substr($story->openedDate, 0, 10); - $story->assignedDate = substr($story->assignedDate, 0, 10); - $story->lastEditedDate = substr($story->lastEditedDate, 0, 10); - $story->closedDate = substr($story->closedDate, 0, 10); - - if($story->linkStories) - { - $tmpLinkStories = array(); - $linkStoriesIdList = explode(',', $story->linkStories); - foreach($linkStoriesIdList as $linkStoryID) - { - $linkStoryID = trim($linkStoryID); - $tmpLinkStories[] = isset($relatedStories[$linkStoryID]) ? $relatedStories[$linkStoryID] : $linkStoryID; - } - $story->linkStories = join("; \n", $tmpLinkStories); - } - - if($story->childStories) - { - $tmpChildStories = array(); - $childStoriesIdList = explode(',', $story->childStories); - foreach($childStoriesIdList as $childStoryID) - { - if(empty($childStoryID)) continue; - - $childStoryID = trim($childStoryID); - $tmpChildStories[] = isset($relatedStories[$childStoryID]) ? $relatedStories[$childStoryID] : $childStoryID; - } - $story->childStories = join("; \n", $tmpChildStories); - } - - /* Set related files. */ - $story->files = ''; - if(isset($relatedFiles[$story->id])) - { - foreach($relatedFiles[$story->id] as $file) - { - $fileURL = common::getSysURL() . helper::createLink('file', 'download', "fileID=$file->id"); - $story->files .= html::a($fileURL, $file->title, '_blank') . '
    '; - } - } - - $story->mailto = trim(trim($story->mailto), ','); - $mailtos = explode(',', $story->mailto); - $story->mailto = ''; - foreach($mailtos as $mailto) - { - $mailto = trim($mailto); - if(isset($users[$mailto])) $story->mailto .= $users[$mailto] . ','; - } - $story->mailto = rtrim($story->mailto, ','); - - $story->reviewedBy = trim(trim($story->reviewedBy), ','); - $reviewedBys = explode(',', $story->reviewedBy); - $story->reviewedBy = ''; - foreach($reviewedBys as $reviewedBy) - { - $reviewedBy = trim($reviewedBy); - if(isset($users[$reviewedBy])) $story->reviewedBy .= $users[$reviewedBy] . ','; - } - $story->reviewedBy = rtrim($story->reviewedBy, ','); - - /* Set child story title. */ - if($story->parent > 0 && strpos($story->title, htmlentities('>', ENT_COMPAT | ENT_HTML401, 'UTF-8')) !== 0) $story->title = '>' . $story->title; - } - - if($executionID) - { - $header = new stdclass(); - $header->name = 'execution'; - $header->tableName = TABLE_EXECUTION; - - $this->post->set('header', $header); - } - if(!(in_array('platform', $productsType) or in_array('branch', $productsType))) unset($fields['branch']);// If products's type are normal, unset branch field. - - if($this->config->edition != 'open') list($fields, $stories) = $this->loadModel('workflowfield')->appendDataFromFlow($fields, $stories); - - $this->post->set('fields', $fields); - $this->post->set('rows', $stories); - $this->post->set('kind', 'story'); - $this->fetch('file', 'export2' . $this->post->fileType, $_POST); - } - - $fileName = $type == 'requirement' ? $this->lang->URCommon : $this->lang->SRCommon; - if($executionID) - { - $executionName = $this->dao->findById($executionID)->from(TABLE_PROJECT)->fetch('name'); - $fileName = $executionName . $this->lang->dash . $fileName; - } - else - { - $productName = $productID ? $this->dao->findById($productID)->from(TABLE_PRODUCT)->fetch('name') : $this->lang->product->all; - if(isset($this->lang->product->featureBar['browse'][$browseType])) - { - $browseType = $this->lang->product->featureBar['browse'][$browseType]; - } - else - { - $browseType = isset($this->lang->product->moreSelects[$browseType]) ? $this->lang->product->moreSelects[$browseType] : ''; - } - - $fileName = $productName . $this->lang->dash . $browseType . $fileName; - } - - $this->view->fileName = $fileName; - $this->view->allExportFields = $this->config->story->list->exportFields; - $this->view->customExport = true; - $this->display(); - } - - /** - * AJAX: get stories of a user in html select. - * - * @param int $userID - * @param string $id the id of the select control. - * @param int $appendID - * @access public - * @return string - */ - public function ajaxGetUserStories($userID = '', $id = '', $appendID = 0) - { - if($userID == '') $userID = $this->app->user->id; - $user = $this->loadModel('user')->getById($userID, 'id'); - $stories = $this->story->getUserStoryPairs($user->account, 10, 'story', '', $appendID); - - if($id) return print(html::select("stories[$id]", $stories, '', 'class="form-control"')); - echo html::select('story', $stories, '', 'class=form-control'); - } - - /** - * Ajax get story status. - * - * @param string $method - * @param string $params - * @access public - * @return void - */ - public function ajaxGetStatus($method, $params = '') - { - parse_str(str_replace(',', '&', $params), $params); - $status = ''; - if($method == 'create') - { - $status = 'draft'; - if(!empty($params['needNotReview'])) $status = 'active'; - if(!empty($params['project'])) $status = 'active'; - if($this->story->checkForceReview()) $status = 'draft'; - } - elseif($method == 'change') - { - $oldStory = $this->dao->findById((int)$params['storyID'])->from(TABLE_STORY)->fetch(); - $status = $oldStory->status; - if($params['changed'] and $oldStory->status == 'active' and empty($params['needNotReview'])) $status = 'changed'; - if($params['changed'] and $oldStory->status == 'active' and $this->story->checkForceReview()) $status = 'changed'; - if($params['changed'] and $oldStory->status == 'draft' and $params['needNotReview']) $status = 'active'; - } - elseif($method == 'review') - { - $oldStory = $this->dao->findById((int)$params['storyID'])->from(TABLE_STORY)->fetch(); - $status = $oldStory->status; - if($params['result'] == 'revert') $status = 'active'; - } - echo $status; - } - - /** - * Ajax get story assignee. - * - * @param string $type create|review|change - * @param int $storyID - * @param array $assignees - * - * @access public - * @return void - */ - public function ajaxGetAssignedTo($type = '', $storyID = 0, $assignees = '') - { - $users = $this->loadModel('user')->getPairs('noletter|noclosed'); - - if($type == 'create') - { - $selectUser = is_array($assignees) ? current($assignees) : ''; - - return print(html::select('assignedTo', $users, $selectUser, "class='from-control picker-select'")); - } - - if($type == 'review') - { - $story = $this->story->getByID($storyID); - $reviewers = $this->story->getReviewerPairs($storyID, $story->version); - $isChanged = $story->changedBy ? true : false; - $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); - - if(count($reviewers) == 1) - { - $selectUser = $isChanged ? $story->changedBy : $story->openedBy; - } - else - { - unset($reviewers[$this->app->user->account]); - foreach($reviewers as $account => $result) - { - if(!$reviewers[$account]) - { - $selectUser = $account; - break; - } - else - { - $selectUser = $isChanged ? $story->changedBy : $story->openedBy; - } - } - } - - if($isSuperReviewer !== false) $selectUser = $isChanged ? $story->changedBy : $story->openedBy; - - return print(html::select('assignedTo', $users, $selectUser, "class='from-control picker-select'")); - } - - if($type == 'change') - { - $selectUser = is_array($assignees) ? current($assignees) : ''; - - return print(html::select('assignedTo', $users, $selectUser, "class='from-control picker-select'")); - } - - return false; - - } -} diff --git a/module/story:q/css/affected.css b/module/story:q/css/affected.css deleted file mode 100644 index 95b64370eb..0000000000 --- a/module/story:q/css/affected.css +++ /dev/null @@ -1,2 +0,0 @@ -.c-consumed {width :100px;} -.c-left {width :90px;} diff --git a/module/story:q/css/batchclose.css b/module/story:q/css/batchclose.css deleted file mode 100644 index 9b0816cf80..0000000000 --- a/module/story:q/css/batchclose.css +++ /dev/null @@ -1,2 +0,0 @@ -.c-id {width: 50px;} -.c-reason {width: 150px;} diff --git a/module/story:q/css/batchcreate.css b/module/story:q/css/batchcreate.css deleted file mode 100644 index b748ec13a0..0000000000 --- a/module/story:q/css/batchcreate.css +++ /dev/null @@ -1,12 +0,0 @@ -#importLinesModal .modal-dialog {width: 80%;} - -#batchCreateForm .c-id {width: 50px; text-align: center; color: #838A9D;} -#batchCreateForm .c-module, -#batchCreateForm .c-plan {width: 180px;} -#batchCreateForm .c-name {width: 180px;} -#batchCreateForm .c-pri {width: 100px;} -#batchCreateForm .c-review {width: 95px;} -#batchCreateForm .c-estimate {width: 90px;} -#batchCreateForm .c-verify {width: 140px;} -#batchCreateForm .c-keywords {width: 140px;} -.body-modal .main-header > h2 {max-width: 80%;} diff --git a/module/story:q/css/batchedit.css b/module/story:q/css/batchedit.css deleted file mode 100644 index 27f97a560b..0000000000 --- a/module/story:q/css/batchedit.css +++ /dev/null @@ -1,11 +0,0 @@ -#suhosinInfo {margin-bottom: 0;} -.c-id {width: 40px;} -.c-estimate {width: 50px;} -.c-branch, .c-module {width: 120px;} -.c-title {width: 150px;} -.c-plan {width: 170px;} -.c-category {width: 90px;} -.c-pri {width: 70px;} -.c-source {width: 130px;} -.c-note, .c-stage, .c-reason {width: 100px;} -.c-keywords {width: 80px;} diff --git a/module/story:q/css/bugs.css b/module/story:q/css/bugs.css deleted file mode 100644 index 32fcae10c7..0000000000 --- a/module/story:q/css/bugs.css +++ /dev/null @@ -1,4 +0,0 @@ -body {background: white;} -.bugsList {padding: 10px;} -.bugsList .table {border: 1px solid #ddd;} -.c-id {width: 40px} diff --git a/module/story:q/css/cases.css b/module/story:q/css/cases.css deleted file mode 100644 index f4783dc8df..0000000000 --- a/module/story:q/css/cases.css +++ /dev/null @@ -1,4 +0,0 @@ -body {background: white;} -.casesList {padding: 10px;} -.casesList .table {border: 1px solid #ddd;} -.c-id, .c-result {width: 50px;} diff --git a/module/story:q/css/change.css b/module/story:q/css/change.css deleted file mode 100644 index 3fec429184..0000000000 --- a/module/story:q/css/change.css +++ /dev/null @@ -1,5 +0,0 @@ -.input-group .chosen-container-single .chosen-single {border-radius: 2px 0 0 2px;} -#affectedProjects .table + h6 {border-top: 1px solid #ddd;} -.nav-tabs>li>a {height: 36px;} -.table-form>tbody>tr>th {width: 100px !important;} -.colorpicker {right: 7px; top: 7px; opacity: 1;} \ No newline at end of file diff --git a/module/story:q/css/close.en.css b/module/story:q/css/close.en.css deleted file mode 100644 index 7e102351a5..0000000000 --- a/module/story:q/css/close.en.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 150px !important;} diff --git a/module/story:q/css/close.vi.css b/module/story:q/css/close.vi.css deleted file mode 100644 index 7e102351a5..0000000000 --- a/module/story:q/css/close.vi.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 150px !important;} diff --git a/module/story:q/css/close.zh-cn.css b/module/story:q/css/close.zh-cn.css deleted file mode 100644 index 76da72e24c..0000000000 --- a/module/story:q/css/close.zh-cn.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 80px !important;} diff --git a/module/story:q/css/close.zh-tw.css b/module/story:q/css/close.zh-tw.css deleted file mode 100644 index 76da72e24c..0000000000 --- a/module/story:q/css/close.zh-tw.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 80px !important;} diff --git a/module/story:q/css/common.css b/module/story:q/css/common.css deleted file mode 100644 index c4fb6f3f82..0000000000 --- a/module/story:q/css/common.css +++ /dev/null @@ -1,19 +0,0 @@ -.linkbox {height: 180px; overflow-y: auto;} -.tab-pane h6 {margin: 0px; padding: 10px;} -.tab-pane h6, .tab-pane table {border: 1px solid #ddd; border-top: none;} -.tab-pane .table-data.table-borderless {border: none !important;} - -.input-group .control-product + .chosen-container-single .chosen-single {border-radius: 2px 0 0 2px; border-right-width: 0; padding-right: 1px;} -.input-group .control-product + .chosen-container-active .chosen-single {border-right-width: 1px; padding-right: 0;} -.input-group .control-branch + .chosen-container-single .chosen-single {border-radius: 0 2px 2px 0;} - -#batchCreateForm .input-group, #batchEditForm .input-group, -#batchCreateForm .input-group .form-control, #batchEditForm .input-group .form-control {position: static;} -#batchCreateForm .input-group .colorpicker, #batchEditForm .input-group .colorpicker {z-index: 2;} -#batchCreateForm .input-group .colorpicker.open, #batchEditForm .input-group .colorpicker.open {z-index: 5;} -#branch_chosen .chosen-single span {word-break: break-all;} - -.c-branch, .c-spec {width: 150px} -.c-source {width: 130px} -.c-note {width: 100px} -.c-category {width: 90px} diff --git a/module/story:q/css/create.css b/module/story:q/css/create.css deleted file mode 100644 index fa75e956fd..0000000000 --- a/module/story:q/css/create.css +++ /dev/null @@ -1,9 +0,0 @@ -.input-control.required:after {display: block; right: 29px; top: 5px;} -#pri + .chosen-container > .chosen-single {border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important;} - -.pri-selector > .btn {padding: 5px 8px !important; width: 100%;} -.pri-selector > .dropdown-menu {padding: 10px;} - -#source_chosen {min-width: 50px !important;} -#feedbackBox > .input-group + #notifyEmailLabel {position: relative; width: 50%; left: 48%} -#product + .picker{min-width: 400px} diff --git a/module/story:q/css/create.en.css b/module/story:q/css/create.en.css deleted file mode 100644 index b56a63b71b..0000000000 --- a/module/story:q/css/create.en.css +++ /dev/null @@ -1 +0,0 @@ -.input-size {width: 525px;} diff --git a/module/story:q/css/create.vi.css b/module/story:q/css/create.vi.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/module/story:q/css/create.zh-cn.css b/module/story:q/css/create.zh-cn.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/module/story:q/css/edit.css b/module/story:q/css/edit.css deleted file mode 100644 index 325971cb2f..0000000000 --- a/module/story:q/css/edit.css +++ /dev/null @@ -1 +0,0 @@ -#product_chosen .chosen-single{border-right-width:1px} diff --git a/module/story:q/css/edit.en.css b/module/story:q/css/edit.en.css deleted file mode 100644 index 55736d2890..0000000000 --- a/module/story:q/css/edit.en.css +++ /dev/null @@ -1,2 +0,0 @@ -.thWidth {width: 100px !important;} -.linkThWidth {width: 110px !important;} diff --git a/module/story:q/css/edit.vi.css b/module/story:q/css/edit.vi.css deleted file mode 100644 index 55736d2890..0000000000 --- a/module/story:q/css/edit.vi.css +++ /dev/null @@ -1,2 +0,0 @@ -.thWidth {width: 100px !important;} -.linkThWidth {width: 110px !important;} diff --git a/module/story:q/css/edit.zh-cn.css b/module/story:q/css/edit.zh-cn.css deleted file mode 100644 index 637f071167..0000000000 --- a/module/story:q/css/edit.zh-cn.css +++ /dev/null @@ -1,2 +0,0 @@ -.thWidth {width: 80px !important;} -.linkThWidth {width: 70px !important;} diff --git a/module/story:q/css/edit.zh-tw.css b/module/story:q/css/edit.zh-tw.css deleted file mode 100644 index 637f071167..0000000000 --- a/module/story:q/css/edit.zh-tw.css +++ /dev/null @@ -1,2 +0,0 @@ -.thWidth {width: 80px !important;} -.linkThWidth {width: 70px !important;} diff --git a/module/story:q/css/linkstory.css b/module/story:q/css/linkstory.css deleted file mode 100644 index 17e2033b52..0000000000 --- a/module/story:q/css/linkstory.css +++ /dev/null @@ -1 +0,0 @@ -#linkStoryForm {min-height: 300px;} diff --git a/module/story:q/css/report.css b/module/story:q/css/report.css deleted file mode 100644 index 3f6d52bed1..0000000000 --- a/module/story:q/css/report.css +++ /dev/null @@ -1,8 +0,0 @@ -.table-bordered caption {border: 1px solid #ddd;} -.table-bordered tr > th:first-child, .table-bordered tr > td:first-child {border-left: 1px solid #ddd !important;} -.table-bordered tr > th:last-child, .table-bordered tr > td:last-child {border-right: 1px solid #ddd !important;} - -.panel-heading > strong > span {float: right; color: #29a8cd;} - -.checkboxes {margin-bottom: 10px;} -.checkboxes .checkbox-primary {margin-bottom: 5px;} diff --git a/module/story:q/css/review.en.css b/module/story:q/css/review.en.css deleted file mode 100644 index 51f62d77b9..0000000000 --- a/module/story:q/css/review.en.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 110px !important;} diff --git a/module/story:q/css/review.vi.css b/module/story:q/css/review.vi.css deleted file mode 100644 index 51f62d77b9..0000000000 --- a/module/story:q/css/review.vi.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 110px !important;} diff --git a/module/story:q/css/review.zh-cn.css b/module/story:q/css/review.zh-cn.css deleted file mode 100644 index c50aad163d..0000000000 --- a/module/story:q/css/review.zh-cn.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 120px !important;} diff --git a/module/story:q/css/review.zh-tw.css b/module/story:q/css/review.zh-tw.css deleted file mode 100644 index 76da72e24c..0000000000 --- a/module/story:q/css/review.zh-tw.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 80px !important;} diff --git a/module/story:q/css/tasks.css b/module/story:q/css/tasks.css deleted file mode 100644 index 254f5276ee..0000000000 --- a/module/story:q/css/tasks.css +++ /dev/null @@ -1,4 +0,0 @@ -body {background: white;} -.tasksList {padding: 10px;} -.tasksList .table {border: 1px solid #ddd;} -.c-id, .c-estiamte, .c-consumed, .c-left, .c-progress {width: 40px;} diff --git a/module/story:q/css/track.css b/module/story:q/css/track.css deleted file mode 100644 index 7408dc06fc..0000000000 --- a/module/story:q/css/track.css +++ /dev/null @@ -1 +0,0 @@ -.table td {text-align: center;} diff --git a/module/story:q/css/view.css b/module/story:q/css/view.css deleted file mode 100644 index 191a5b1f34..0000000000 --- a/module/story:q/css/view.css +++ /dev/null @@ -1,5 +0,0 @@ -.side-col .cell {padding: 0px;} -.side-col #legendProjectAndTask .list-unstyled {padding: 10px; border-top: 0px; margin: 0px;} -#legendStories ul li {padding: 10px 0 0 12px; line-height: 20px;} -.main-actions .btn-toolbar .btn {padding-right: 6px; padding-left: 6px;} -.btn-edit-comment {z-index: 100;} diff --git a/module/story:q/css/view.en.css b/module/story:q/css/view.en.css deleted file mode 100644 index 1c5a496e3c..0000000000 --- a/module/story:q/css/view.en.css +++ /dev/null @@ -1,2 +0,0 @@ -#legendLifeTime .thWidth {width: 100px !important;} -#legendRelated .thWidth {width: 110px !important;} diff --git a/module/story:q/css/view.vi.css b/module/story:q/css/view.vi.css deleted file mode 100644 index 1c5a496e3c..0000000000 --- a/module/story:q/css/view.vi.css +++ /dev/null @@ -1,2 +0,0 @@ -#legendLifeTime .thWidth {width: 100px !important;} -#legendRelated .thWidth {width: 110px !important;} diff --git a/module/story:q/css/view.zh-cn.css b/module/story:q/css/view.zh-cn.css deleted file mode 100644 index 974243e6af..0000000000 --- a/module/story:q/css/view.zh-cn.css +++ /dev/null @@ -1,2 +0,0 @@ -#legendLifeTime .thWidth {width: 70px !important;} -#legendRelated .thWidth {width: 70px !important;} diff --git a/module/story:q/css/view.zh-tw.css b/module/story:q/css/view.zh-tw.css deleted file mode 100644 index 974243e6af..0000000000 --- a/module/story:q/css/view.zh-tw.css +++ /dev/null @@ -1,2 +0,0 @@ -#legendLifeTime .thWidth {width: 70px !important;} -#legendRelated .thWidth {width: 70px !important;} diff --git a/module/story:q/css/x.change.css b/module/story:q/css/x.change.css deleted file mode 100644 index 287413342d..0000000000 --- a/module/story:q/css/x.change.css +++ /dev/null @@ -1,2 +0,0 @@ -.main-header {display: none;} -.form-actions button:last-child {display: none;} diff --git a/module/story:q/css/x.create.css b/module/story:q/css/x.create.css deleted file mode 100644 index 502dabc8aa..0000000000 --- a/module/story:q/css/x.create.css +++ /dev/null @@ -1,2 +0,0 @@ -a.btn-back {display: none;} -#mailto ~ span.input-group-btn {display: none;} diff --git a/module/story:q/css/x.edit.css b/module/story:q/css/x.edit.css deleted file mode 100644 index d7f6148b4c..0000000000 --- a/module/story:q/css/x.edit.css +++ /dev/null @@ -1,7 +0,0 @@ -.form-actions {margin-top: 0px;} -.form-actions button:last-child {display: none;} -#mailto ~ .input-group-btn {display: none;} -#mainContent .main-row .col-8 {width: 55%;} -#mainContent .main-row .col-4 {width: 45%;} -.main-header {display: none;} -.main-row .side-col.col-4 .detail:last-child {display: none;} diff --git a/module/story:q/css/x.review.css b/module/story:q/css/x.review.css deleted file mode 100644 index 287413342d..0000000000 --- a/module/story:q/css/x.review.css +++ /dev/null @@ -1,2 +0,0 @@ -.main-header {display: none;} -.form-actions button:last-child {display: none;} diff --git a/module/story:q/css/x.view.css b/module/story:q/css/x.view.css deleted file mode 100644 index dccc99b6bf..0000000000 --- a/module/story:q/css/x.view.css +++ /dev/null @@ -1,17 +0,0 @@ -body * {font-size: 13px !important; line-height: 1.42857143; color: rgb(51, 51, 51);} -#main {margin-bottom: 40px; min-width: unset;} -#mainMenu {position: fixed; width: 100%; z-index: 999;} -#mainMenu .pull-left>a, #mainMenu .pull-left .divider, #mainMenu .pull-right, #mainContent .col-4, .main-actions {display: none;} -#scrollContent {margin-top: 35px; height: calc(100% - 70px); display: block; overflow: hidden;} -#scrollContent:hover {overflow: overlay;} -html, body, #main, .container { height: 100%;} -.btn span {line-height: 16px; vertical-align: middle;} -.col-8>.cell {min-height: 300px;} -.modal-dialog {width: 90%;} -.page-title {width: 100%;} -.page-title > .label-id {min-width: 25px; line-height: 13px;} -.page-title > .text {display: inline-block; width: calc(100% - 90px); text-overflow: ellipsis; white-space: nowrap; overflow: hidden; line-height: 13px;} -.linkButton {float: right; padding-right: 10px; cursor: pointer;} -.linkButton i {font-size: 17px !important;} -.linkButton span {line-height: 34px; display: block;} -.btn-toolbar {width: calc(100% - 30px);} diff --git a/module/story:q/css/zerocase.en.css b/module/story:q/css/zerocase.en.css deleted file mode 100644 index c50aad163d..0000000000 --- a/module/story:q/css/zerocase.en.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 120px !important;} diff --git a/module/story:q/css/zerocase.vi.css b/module/story:q/css/zerocase.vi.css deleted file mode 100644 index c50aad163d..0000000000 --- a/module/story:q/css/zerocase.vi.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 120px !important;} diff --git a/module/story:q/css/zerocase.zh-cn.css b/module/story:q/css/zerocase.zh-cn.css deleted file mode 100644 index b72a60e0e5..0000000000 --- a/module/story:q/css/zerocase.zh-cn.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 90px !important;} diff --git a/module/story:q/css/zerocase.zh-tw.css b/module/story:q/css/zerocase.zh-tw.css deleted file mode 100644 index b72a60e0e5..0000000000 --- a/module/story:q/css/zerocase.zh-tw.css +++ /dev/null @@ -1 +0,0 @@ -.thWidth {width: 90px !important;} diff --git a/module/story:q/js/batchclose.js b/module/story:q/js/batchclose.js deleted file mode 100755 index 45150d3088..0000000000 --- a/module/story:q/js/batchclose.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Set duplicate field. - * - * @param string $resolution - * @param int $storyID - * @access public - * @return void - */ -function setDuplicateAndChild(resolution, storyID) -{ - if(resolution == 'duplicate') - { - $('#childStoryBox' + storyID).hide(); - $('#duplicateStoryBox' + storyID).show(); - } - else if(resolution == 'subdivided') - { - $('#duplicateStoryBox' + storyID).hide(); - $('#childStoryBox' + storyID).show(); - } - else - { - $('#duplicateStoryBox' + storyID).hide(); - $('#childStoryBox' + storyID).hide(); - } -} diff --git a/module/story:q/js/batchcreate.js b/module/story:q/js/batchcreate.js deleted file mode 100644 index 5660e14ee6..0000000000 --- a/module/story:q/js/batchcreate.js +++ /dev/null @@ -1,138 +0,0 @@ -$(function() -{ - if($('#batchCreateForm table thead tr th.col-name').width() < 200) $('#batchCreateForm table thead tr th.col-name').width(200); -}); -$(document).on('click', '.chosen-with-drop', function() -{ - var select = $(this).prev('select'); - if($(select).val() == 'ditto') - { - var index = $(select).closest('td').index(); - var row = $(select).closest('tr').index(); - var table = $(select).closest('tr').parent(); - var value = ''; - for(i = row - 1; i >= 0; i--) - { - value = $(table).find('tr').eq(i).find('td').eq(index).find('select').val(); - if(value != 'ditto') break; - } - $(select).val(value); - $(select).trigger("chosen:updated"); - } -}); -$(document).on('mousedown', 'select', function() -{ - if($(this).val() == 'ditto') - { - var index = $(this).closest('td').index(); - var row = $(this).closest('tr').index(); - var table = $(this).closest('tr').parent(); - var value = ''; - for(i = row - 1; i >= 0; i--) - { - value = $(table).find('tr').eq(i).find('td').eq(index).find('select').val(); - if(value != 'ditto') break; - } - $(this).val(value); - } -}); - -/** - * Set modules and plans. - * - * @param int $branchID - * @param int $productID - * @param int $num - * @access public - * @return void - */ -function setModuleAndPlan(branchID, productID, num) -{ - moduleLink = createLink('tree', 'ajaxGetModules', 'productID=' + productID + '&viewType=story&branch=' + branchID + '&num=' + num); - $.get(moduleLink, function(modules) - { - if(!modules) modules = ''; - $('#module' + num).replaceWith(modules); - $("#module" + num + "_chosen").remove(); - $("#module" + num).next('.picker').remove(); - $("#module" + num).chosen(); - }); - - planLink = createLink('productPlan', 'ajaxGetProductPlans', 'productID=' + productID + '&branch=' + branchID + '&num=' + num + '&expired=unexpired'); - $.get(planLink, function(plans) - { - if(!plans) plans = ''; - $('#plan' + num).replaceWith(plans); - $("#plan" + num + "_chosen").remove(); - $("#plan" + num).next('.picker').remove(); - $("#plan" + num).chosen(); - }); - - /* If the branch of the current row is inconsistent with the one below, clear the module and plan of the nex row. */ - var nextBranchID = $('#branch' + (num + 1)).val(); - if(nextBranchID != branchID) - { - $('#module' + (num + 1)).find("option[value='ditto']").remove(); - $('#module' + (num + 1)).trigger("chosen:updated"); - - $('#plan' + (num + 1)).find("option[value='ditto']").remove(); - $('#plan' + (num + 1)).trigger("chosen:updated"); - } -} - -/* Copy story title as story spec. */ -function copyTitle(num) -{ - var title = $('#title\\[' + num + '\\]').val(); - $('#spec\\[' + num + '\\]').val(title); -} - -$(document).on('change', "[name*='reviewer']", function() -{ - toggleCheck($(this)); -}) - -/** - * Toggle checkbox. - * - * @param obj $obj - * @access public - * @return void - */ -function toggleCheck(obj) -{ - var $this = $(obj); - var data = $this.val(); - var $ditto = $this.closest('div').find("input[name*='reviewDitto']"); - if(data == '') - { - $ditto.attr('checked', true); - $ditto.closest('.input-group-addon').show(); - } - else - { - $ditto.removeAttr('checked'); - $ditto.closest('.input-group-addon').hide(); - } -} - -/** - * Set lane. - * - * @param int $regionID - * @param int $num - * @access public - * @return void - */ -function setLane(regionID, num) -{ - laneLink = createLink('kanban', 'ajaxGetLanes', 'regionID=' + regionID + '&type=story&field=lanes&i=' + num); - $.get(laneLink, function(lanes) - { - if(!lanes) lanes = ''; - $('#lanes' + num).replaceWith(lanes); - $("#lanes" + num + "_chosen").remove(); - $("#lanes" + num).next('.picker').remove(); - $("#lanes" + num).chosen(); - }); -} diff --git a/module/story:q/js/batchedit.js b/module/story:q/js/batchedit.js deleted file mode 100644 index 6847a40766..0000000000 --- a/module/story:q/js/batchedit.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Set duplicate field. - * - * @param string $resolution - * @param int $storyID - * @access public - * @return void - */ -function setDuplicateAndChild(resolution, storyID) -{ - if(resolution == 'duplicate') - { - $('#childStoryBox' + storyID).hide(); - $('#duplicateStoryBox' + storyID).show(); - } - else if(resolution == 'subdivided') - { - $('#duplicateStoryBox' + storyID).hide(); - $('#childStoryBox' + storyID).show(); - } - else - { - $('#duplicateStoryBox' + storyID).hide(); - $('#childStoryBox' + storyID).hide(); - } -} - -function loadBranches(product, branch, storyID) -{ - if(typeof(branch) == 'undefined') branch = 0; - if(!branch) branch = 0; - - var currentModuleID = $('#modules' + storyID).val(); - moduleLink = createLink('tree', 'ajaxGetOptionMenu', 'productID=' + product + '&viewtype=story&branch=' + branch + '&rootModuleID=0&returnType=html&fieldID=' + storyID + '&needManage=false&extra=¤tModuleID=' + currentModuleID); - $('#modules' + storyID).parent('td').load(moduleLink, function(){$('#modules' + storyID).chosen();}); - - planID = $('#plans' + storyID).val(); - planLink = createLink('product', 'ajaxGetPlans', 'productID=' + product + '&branch=' + branch + '&planID=' + planID + '&fieldID=' + storyID + '&needCreate=false&expired=¶m=skipParent'); - $('#plans' + storyID).parent('td').load(planLink, function(){$('#plans' + storyID).chosen();}); -} - -$(function() -{ - //Remove 'ditto' in first row. - removeDitto(); - - // Init bactch action form - $('#batchEditForm').batchActionForm(); -}); - -$(document).on('click', '.chosen-with-drop', function(){oldValue = $(this).prev('select').val();})//Save old value. - -/* Set ditto value. */ -$(document).on('change', 'select', function() -{ - if($(this).val() == 'ditto') - { - var index = $(this).closest('td').index(); - var row = $(this).closest('tr').index(); - var tbody = $(this).closest('tr').parent(); - - if($(this).attr('name').indexOf('closedReasons') != -1) - { - index = $(this).closest('tr').closest('td').index(); - row = $(this).closest('tr').closest('td').parent().index(); - tbody = $(this).closest('tr').closest('td').parent().parent(); - } - - var value = ''; - for(i = row - 1; i >= 0; i--) - { - value = tbody.children('tr').eq(i).find('td').eq(index).find('select').val(); - if(value != 'ditto') break; - } - - isModules = $(this).attr('name').indexOf('modules') != -1; - isPlans = $(this).attr('name').indexOf('plans') != -1; - - if(isModules || isPlans) - { - - var valueStr = ',' + $(this).find('option').map(function(){return $(this).val();}).get().join(',') + ','; - if(valueStr.indexOf(',' + value + ',') != -1) - { - $(this).val(value); - } - else - { - alert(dittoNotice); - $(this).val(oldValue); - } - } - else - { - $(this).val(value); - } - - $(this).trigger("chosen:updated"); - $(this).trigger("change"); - } -}); diff --git a/module/story:q/js/change.js b/module/story:q/js/change.js deleted file mode 100644 index 3d075f1112..0000000000 --- a/module/story:q/js/change.js +++ /dev/null @@ -1,51 +0,0 @@ -$(function() -{ - $('#needNotReview').on('change', function() - { - $('#reviewer').attr('disabled', $(this).is(':checked') ? 'disabled' : null).trigger('chosen:updated'); - if($(this).is(':checked')) - { - $('.input-group-addon').removeClass('required'); - } - else - { - $('.input-group-addon').addClass('required'); - } - - loadAssignedTo(); - }); - $('#needNotReview').change(); - - if($('#reviewer').val()) loadAssignedTo(); - - if($('.tabs .tab-content .tab-pane.active').children().length == 0) $('.tabs .nav-tabs li.active').css('border-bottom', '1px solid #ccc'); -}); - -/** - * Load assignedTo. - * - * @access public - * @return void - */ -function loadAssignedTo() -{ - var assignees = $('#reviewer').val(); - var link = createLink('story', 'ajaxGetAssignedTo', 'type=change&storyID=' + storyID + '&assignees=' + assignees); - $.post(link, function(data) - { - $('#assignedTo').replaceWith(data); - $('#assignedToBox .picker').remove(); - $('#assignedTo').picker(); - }); - - if($('#needNotReview').is(':checked')) - { - $('#assignedToBox').removeClass('hidden'); - $('#reviewerBox').attr('colspan', 1); - } - else - { - $('#assignedToBox').addClass('hidden'); - $('#reviewerBox').attr('colspan', 2); - } -} diff --git a/module/story:q/js/close.js b/module/story:q/js/close.js deleted file mode 100644 index 73b0e1988a..0000000000 --- a/module/story:q/js/close.js +++ /dev/null @@ -1,18 +0,0 @@ -function setStory(reason) -{ - if(reason == 'duplicate') - { - $('#duplicateStoryBox').show(); - $('#childStoriesBox').hide(); - } - else if(reason == 'subdivided') - { - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').show(); - } - else - { - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').hide(); - } -} diff --git a/module/story:q/js/common.js b/module/story:q/js/common.js deleted file mode 100644 index 8cc11344f3..0000000000 --- a/module/story:q/js/common.js +++ /dev/null @@ -1,22 +0,0 @@ -$(function() -{ - if(typeof(resetActive) != 'undefined') return false; - if(typeof(storyType) == 'undefined') storyType = ''; - if(typeof(rawModule) == 'undefined') rawModule = 'product'; - if(typeof(app) == 'undefined') app = ''; - if(typeof(execution) != 'undefined') rawModule = 'projectstory'; - if(['project', 'projectstory'].indexOf(rawModule) === -1 && app != 'qa') - { - if(app != 'my') $('#navbar .nav li').removeClass('active'); - $("#navbar .nav li[data-id=" + storyType + ']').addClass('active'); - $('#subNavbar li[data-id="' + storyType + '"]').addClass('active'); - } -}) - -function getStatus(method, params) -{ - $.get(createLink('story', 'ajaxGetStatus', "method=" + method + '¶ms=' + params), function(status) - { - $('form #status').val(status).change(); - }); -} diff --git a/module/story:q/js/create.js b/module/story:q/js/create.js deleted file mode 100644 index 150abcd111..0000000000 --- a/module/story:q/js/create.js +++ /dev/null @@ -1,108 +0,0 @@ -$(function() -{ - $('#needNotReview').on('change', function() - { - $('#reviewer').attr('disabled', $(this).is(':checked') ? 'disabled' : null).trigger('chosen:updated'); - if($(this).is(':checked')) - { - $('#reviewerBox').removeClass('required'); - } - else - { - $('#reviewerBox').addClass('required'); - } - loadAssignedTo(); - - getStatus('create', "product=" + $('#product').val() + ",execution=" + executionID + ",needNotReview=" + ($(this).prop('checked') ? 1 : 0)); - }); - $('#needNotReview').change(); - - if($('#reviewer').val()) loadAssignedTo(); - - // init pri selector - $('#pri').on('change', function() - { - var $select = $(this); - var $selector = $select.closest('.pri-selector'); - var value = $select.val(); - $selector.find('.pri-text').html('' + value + ''); - }); - - $('#source').on('change', function() - { - if(storyType == 'requirement') return false; - - var source = $(this).val(); - if($.inArray(source, feedbackSource) != -1) - { - $('#feedbackBox').removeClass('hidden'); - $('#reviewerBox').attr('colspan', $('#assignedToBox').hasClass('hidden') ? 2 : 1); - $('#assignedToBox').attr('colspan', 1); - } - else - { - $('#feedbackBox').addClass('hidden'); - $('#reviewerBox').attr('colspan', $('#assignedToBox').hasClass('hidden') ? 4 : 2); - $('#assignedToBox').attr('colspan', 2); - } - }); -}); - -/** - * Load assignedTo. - * - * @access public - * @return void - */ -function loadAssignedTo() -{ - var assignees = $('#reviewer').val(); - var link = createLink('story', 'ajaxGetAssignedTo', 'type=create&storyID=0&assignees=' + assignees); - $.post(link, function(data) - { - $('#assignedTo').replaceWith(data); - $('#assignedToBox .picker').remove(); - $('#assignedTo').picker(); - }); - - var colspan = $('#assignedToBox').attr('colspan'); - if($('#needNotReview').is(':checked')) - { - $('#assignedToBox').removeClass('hidden'); - $('#reviewerBox').attr('colspan', colspan); - } - else - { - $('#assignedToBox').addClass('hidden'); - $('#reviewerBox').attr('colspan', colspan * 2); - } -} - -function refreshPlan() -{ - loadProductPlans($('#product').val(), $('#branch').val()); -} - -/** - * Set lane. - * - * @param int $regionID - * @access public - * @return void - */ -function setLane(regionID) -{ - laneLink = createLink('kanban', 'ajaxGetLanes', 'regionID=' + regionID + '&type=story&field=lane'); - $.get(laneLink, function(lane) - { - if(!lane) lane = ""; - $('#lane').replaceWith(lane); - $('#lane' + "_chosen").remove(); - $('#lane').next('.picker').remove(); - $('#lane').chosen(); - }); -} - -$(window).unload(function(){ - if(blockID) window.parent.refreshBlock($('#block' + blockID)); -}); diff --git a/module/story:q/js/edit.js b/module/story:q/js/edit.js deleted file mode 100644 index f234363a3e..0000000000 --- a/module/story:q/js/edit.js +++ /dev/null @@ -1,35 +0,0 @@ -$(function() -{ - $('.main-side #branch').closest('td').find('#product_chosen .chosen-single').css('width', '153px'); - $('#reviewer_chosen .search-choice').each(function() - { - if(reviewedReviewer.indexOf($(this).find('span').text()) != -1) $(this).css('pointer-events', 'none'); - }) - - $('#reviewer').change(function() - { - if($('#reviewer_chosen .search-choice').length === 1) - { - alert(reviewerNotEmpty); - $('#reviewer').val(reviewers); - $('#reviewer').trigger('chosen:updated'); - } - else - { - reviewers = $('#reviewer').val(); - } - }) - - $('#source').on('change', function() - { - var source = $(this).val(); - if($.inArray(source, feedbackSource) != -1) - { - $('.feedbackBox').removeClass('hidden'); - } - else - { - $('.feedbackBox').addClass('hidden'); - } - }); -}) diff --git a/module/story:q/js/report.js b/module/story:q/js/report.js deleted file mode 100644 index ccc0b1fa55..0000000000 --- a/module/story:q/js/report.js +++ /dev/null @@ -1,19 +0,0 @@ -$(function() -{ - var resizeChartTable = function() - { - $('.table-wrapper').each(function() - { - var $this = $(this); - $this.css('max-height', $this.closest('.table').find('.chart-wrapper').outerHeight()); - }); - }; - resizeChartTable(); - $(window).resize(resizeChartTable); -}); - -function changeChartType(type) -{ - $('form').attr('action', createLink('story', 'report', 'productID=' + productID + '&branchID=' + branchID + '&storyType=' + storyType + '&browseType=' + browseType + '&moduleID=' + moduleID + '&chartType=' + type)); - $('form').find('#submit').click(); -} diff --git a/module/story:q/js/review.js b/module/story:q/js/review.js deleted file mode 100644 index afac5139c7..0000000000 --- a/module/story:q/js/review.js +++ /dev/null @@ -1,94 +0,0 @@ -function switchShow(result) -{ - $('#priBox').hide(); - $('#estimateBox').hide(); - if(result == 'reject') - { - $('#rejectedReasonBox').show(); - $('#preVersionBox').hide(); - $('#assignedToBox').hide(); - if(isMultiple) - { - $('#assignedToBox').show(); - loadAssignedTo(); - } - } - else if(result == 'revert') - { - $('#preVersionBox').show(); - $('#rejectedReasonBox').hide(); - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').hide(); - $('#assignedToBox').show(); - loadAssignedTo(); - } - else if(result == 'clarify') - { - $('#preVersionBox').hide(); - $('#rejectedReasonBox').hide(); - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').hide(); - $('#rejectedReasonBox').hide(); - $('#assignedToBox').show(); - loadAssignedTo(); - } - else - { - $('#preVersionBox').hide(); - $('#rejectedReasonBox').hide(); - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').hide(); - $('#rejectedReasonBox').hide(); - $('#assignedToBox').hide(); - if(result == 'pass') - { - $('#priBox').show(); - $('#estimateBox').show(); - $('#assignedToBox').show(); - loadAssignedTo(); - } - } - - getStatus('review', "storyID=" + storyID + ",result=" + result); -} - -function setStory(reason) -{ - if(reason == 'duplicate') - { - $('#duplicateStoryBox').show(); - $('#childStoriesBox').hide(); - } - else if(reason == 'subdivided') - { - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').show(); - } - else - { - $('#duplicateStoryBox').hide(); - $('#childStoriesBox').hide(); - } -} - -/** - * Load assignedTo. - * - * @access public - * @return void - */ -function loadAssignedTo() -{ - var link = createLink('story', 'ajaxGetAssignedTo', 'type=review&storyID=' + storyID); - $.post(link, function(data) - { - $('#assignedTo').replaceWith(data); - $('#assignedToBox .picker').remove(); - $('#assignedTo').picker(); - }); -} - -$(function() -{ - if($('.tabs .tab-content .tab-pane.active').children().length == 0) $('.tabs .nav-tabs li.active').css('border-bottom', '1px solid #ccc'); -}) diff --git a/module/story:q/js/track.js b/module/story:q/js/track.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/module/story:q/js/view.js b/module/story:q/js/view.js deleted file mode 100644 index 9a215855b9..0000000000 --- a/module/story:q/js/view.js +++ /dev/null @@ -1,25 +0,0 @@ -$(function() -{ - if(canCreate) - { - link = createLink('story', 'create', 'productID=' + productID + "&branch=" + branch + '&moduleID=' + moduleID); - $('#modulemenu ul.nav li.right:first').before("
  • " + createStory + "
  • "); - } - - /* Add for task #5385. */ - if(config.onlybody == 'yes') $('.main-actions').css('width', '100%') - - $('#unlinkStory').click(function() - { - if($('.removeButton').hasClass('hide')) - { - $('.removeButton').removeClass('hide'); - $(this).text(cancel); - } - else - { - $('.removeButton').addClass('hide'); - $(this).text(unlink); - } - }) -}); diff --git a/module/story:q/js/x.create.js b/module/story:q/js/x.create.js deleted file mode 100644 index f107b0e075..0000000000 --- a/module/story:q/js/x.create.js +++ /dev/null @@ -1,8 +0,0 @@ -$(function() -{ - $('#sourceNote').prev('span').remove(); - $('#sourceNote').remove(); - $('table tbody tr th').addClass('w-100px'); - - $('#tplBoxWrapper .btn-toolbar .btn-group').remove(); -}) diff --git a/module/story:q/js/x.view.js b/module/story:q/js/x.view.js deleted file mode 100644 index 6e946f6ebc..0000000000 --- a/module/story:q/js/x.view.js +++ /dev/null @@ -1,46 +0,0 @@ -$(function() -{ - $('.main-actions-holder').css('height', '0px'); - var xuanAction = "
    "; - $('.main-col div.main-actions .btn-toolbar a').each(function(){ - var $that = $(this); - - if($that.attr('id') == 'back') return true; - - var href = $that.attr('href'); - var title = $that.attr('title') == undefined ? '' : " title='" + $that.attr('title') + "'"; - var btnClass = " class='" + $that.attr('class') + "'"; - var action = $that.html(); - - if(href.indexOf('browse') >= 0 || href.indexOf('batchCreate') >= 0 - || href.indexOf('create') >= 0 || href.indexOf('delete') >= 0) return true; - - if($that.hasClass('iframe')) - { - url = href; - target = ''; - } - else - { - url = 'xxc:openUrlInDialog/' + encodeURIComponent(sysurl + href); - target = " target='_blank'"; - } - - xuanAction += "' + action + ""; - }); - - if(xuanAction != "
    ") - { - var params = window.location.search; - if(params.indexOf('isNotice=1') == -1) - { - xuanAction += '
    '; - $('body').append(xuanAction); - } - } - else - { - $('#scrollContent').css('height', 'calc(100% - 36px)'); - } - $('.xuancard-actions a.iframe').modalTrigger(); -}) diff --git a/module/story:q/js/zerocase.js b/module/story:q/js/zerocase.js deleted file mode 100644 index 618b5d612c..0000000000 --- a/module/story:q/js/zerocase.js +++ /dev/null @@ -1,4 +0,0 @@ -$(document).ready(function() -{ - $('#zerocaseTab').addClass('btn-active-text'); -}); diff --git a/module/story:q/lang/de.php b/module/story:q/lang/de.php deleted file mode 100644 index 78ed708a0f..0000000000 --- a/module/story:q/lang/de.php +++ /dev/null @@ -1,441 +0,0 @@ - - * @package story - * @version $Id: en.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -global $config; -$lang->story->create = "Story hinzufügen"; - -$lang->story->requirement = zget($lang, 'URCommon', "Requirement"); -$lang->story->story = zget($lang, 'SRCommon', "Story"); -$lang->story->createStory = 'Create ' . $lang->story->story; -$lang->story->createRequirement = 'Create ' . $lang->story->requirement; -$lang->story->affectedStories = "Affected {$lang->story->story}"; - -$lang->story->batchCreate = "Mehere hinzufügen"; -$lang->story->change = "Ändern"; -$lang->story->changed = 'Geändert'; -$lang->story->assignTo = 'Assign'; -$lang->story->review = 'Prüfen'; -$lang->story->recall = 'Revoke'; -$lang->story->needReview = 'Need Review'; -$lang->story->batchReview = 'Mehere prüfen'; -$lang->story->edit = "Bearbeiten"; -$lang->story->batchEdit = "Mehere bearbeiten"; -$lang->story->subdivide = 'Aufteilen'; -$lang->story->link = 'Link'; -$lang->story->unlink = 'Unlink'; -$lang->story->track = 'Track'; -$lang->story->trackAB = 'Track'; -$lang->story->processStoryChange = 'Confirm Story Change'; -$lang->story->splitRequirent = 'Decompose'; -$lang->story->close = 'Schließen'; -$lang->story->batchClose = 'Mehere schließen'; -$lang->story->activate = 'Aktivieren'; -$lang->story->delete = "Löschen"; -$lang->story->view = "Storydetails"; -$lang->story->setting = "Einstellungen"; -$lang->story->tasks = "Verknüpfte Aufgaben"; -$lang->story->bugs = "Verknüpfte Bugs"; -$lang->story->cases = "Verknüpfte Fälle"; -$lang->story->taskCount = 'Aufgaben'; -$lang->story->bugCount = 'Bugs'; -$lang->story->caseCount = 'Fälle'; -$lang->story->taskCountAB = 'T'; -$lang->story->bugCountAB = 'B'; -$lang->story->caseCountAB = 'C'; -$lang->story->linkStory = 'Verküpfen'; -$lang->story->unlinkStory = 'Verknüpfung aufheben'; -$lang->story->export = "Daten exportieren"; -$lang->story->zeroCase = "Storys ohne Fälle"; -$lang->story->zeroTask = "Storys ohne Aufgaben anzeigen"; -$lang->story->reportChart = "Bericht"; -$lang->story->copyTitle = "Titel kopieren"; -$lang->story->batchChangePlan = "Mehere Pläne ändern"; -$lang->story->batchChangeBranch = "Mehere Branches ändern"; -$lang->story->batchChangeStage = "Mehere Phasen ändern"; -$lang->story->batchAssignTo = "Mehere zuordnen"; -$lang->story->batchChangeModule = "Mehere Module ändern"; -$lang->story->viewAll = "Alle"; -$lang->story->toTask = 'Convert to Task'; -$lang->story->batchToTask = 'Batch Convert to Task'; -$lang->story->convertRelations = 'Convert Relations'; -$lang->story->undetermined = 'undetermined'; -$lang->story->order = 'Order'; - -$lang->story->editAction = "Edit {$lang->SRCommon}"; -$lang->story->changeAction = "Change Story"; -$lang->story->assignAction = 'Assign Story'; -$lang->story->reviewAction = 'Review Story'; -$lang->story->subdivideAction = 'Decompose Story'; -$lang->story->closeAction = 'Close Story'; -$lang->story->activateAction = 'Activate Story'; -$lang->story->deleteAction = "Delete Story"; -$lang->story->exportAction = "Export Story"; -$lang->story->reportAction = "Story Report"; - -$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; -$lang->story->closedStory = 'Story %s is closed and will not be closed.'; -$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; -$lang->story->successToTask = "Converted to task."; -$lang->story->storyRound = '%s time estimation'; - -$lang->story->id = 'ID'; -$lang->story->parent = 'Parent'; -$lang->story->product = $lang->productCommon; -$lang->story->project = 'Project'; -$lang->story->branch = "Branch/Platform"; -$lang->story->module = 'Module'; -$lang->story->moduleAB = 'Module'; -$lang->story->source = 'Von'; -$lang->story->sourceNote = 'Hinweis'; -$lang->story->fromBug = 'Von Bug'; -$lang->story->title = 'Titel'; -$lang->story->type = "Story/Requirement"; -$lang->story->category = 'Category'; -$lang->story->color = 'Color'; -$lang->story->toBug = 'ToBug'; -$lang->story->spec = 'Beschreibung'; -$lang->story->assign = 'Assign'; -$lang->story->verify = 'Akzeptanz'; -$lang->story->pri = 'Priorität'; -$lang->story->estimate = 'Schätzung(h)'; -$lang->story->estimateAB = 'Schätzung(h)'; -$lang->story->hour = 'Stunde'; -$lang->story->status = 'Status'; -$lang->story->subStatus = 'Sub Status'; -$lang->story->stage = 'Phase'; -$lang->story->stageAB = 'Phase'; -$lang->story->stagedBy = 'SetBy'; -$lang->story->mailto = 'Mail an'; -$lang->story->openedBy = 'Ersteller'; -$lang->story->openedDate = 'Erstellt am'; -$lang->story->assignedTo = 'Zuständiger'; -$lang->story->assignedDate = 'Zugewisen am'; -$lang->story->lastEditedBy = 'Letzte Bearbeitung'; -$lang->story->lastEditedDate = 'Bearbeitet am'; -$lang->story->closedBy = 'Geschlossen von'; -$lang->story->closedDate = 'Geschlossen am'; -$lang->story->closedReason = 'Geschlossen weil'; -$lang->story->rejectedReason = 'Abgelehnt weil'; -$lang->story->reviewedBy = 'Prüfer'; -$lang->story->reviewer = $lang->story->reviewedBy; -$lang->story->reviewers = 'Reviewers'; -$lang->story->reviewedDate = 'Geprüft am'; -$lang->story->activatedDate = 'Activated Date'; -$lang->story->version = 'Version'; -$lang->story->feedbackBy = 'From Name'; -$lang->story->notifyEmail = 'From Email'; -$lang->story->plan = 'Plan'; -$lang->story->planAB = 'Plan'; -$lang->story->comment = 'Kommentar'; -$lang->story->children = "Child {$lang->SRCommon}"; -$lang->story->childrenAB = "C"; -$lang->story->linkStories = 'Story verknüpfen'; -$lang->story->childStories = 'Story aufteilen'; -$lang->story->duplicateStory = 'Story ID kopieren'; -$lang->story->reviewResult = 'Ergbnis prüfen'; -$lang->story->preVersion = 'Frühere Version'; -$lang->story->keywords = 'Tags'; -$lang->story->newStory = 'Weitere Story'; -$lang->story->colorTag = 'Farb-Tag'; -$lang->story->files = 'Dateien'; -$lang->story->copy = "Story kopieren"; -$lang->story->total = "Total Storys"; -$lang->story->draft = 'Entwurf'; -$lang->story->unclosed = 'Nicht geschlossen'; -$lang->story->deleted = 'Gelöscht'; -$lang->story->released = 'Released Linked Stories'; -$lang->story->URChanged = 'Requirement Changed'; -$lang->story->design = 'Designs'; -$lang->story->case = 'Cases'; -$lang->story->bug = 'Bugs'; -$lang->story->repoCommit = 'Commits'; -$lang->story->noRequirement = 'No Requirements'; -$lang->story->one = 'One'; -$lang->story->field = 'Synchronized fields'; -$lang->story->completeRate = 'Completion Rate'; -$lang->story->reviewed = 'Reviewed'; -$lang->story->toBeReviewed = 'To Be Reviewed'; -$lang->story->linkMR = 'Related MRs'; - -$lang->story->ditto = 'Dito'; -$lang->story->dittoNotice = 'Die Story gehört nicht zum Projekt wie die vorherige!'; - -$lang->story->needNotReviewList[0] = 'Need Review'; -$lang->story->needNotReviewList[1] = 'Need Not Review'; - -$lang->story->useList[0] = 'Ja'; -$lang->story->useList[1] = 'Nein'; - -$lang->story->statusList[''] = ''; -$lang->story->statusList['draft'] = 'Entwurf'; -$lang->story->statusList['active'] = 'Aktiv'; -$lang->story->statusList['closed'] = 'Geschlossen'; -$lang->story->statusList['changed'] = 'Geändert'; - -$lang->story->stageList[''] = ''; -$lang->story->stageList['wait'] = 'Wartend'; -$lang->story->stageList['planned'] = 'Geplant'; -$lang->story->stageList['projected'] = 'Projektiert'; -$lang->story->stageList['developing'] = 'Entwicklung'; -$lang->story->stageList['developed'] = 'Entwickelt'; -$lang->story->stageList['testing'] = 'Testen'; -$lang->story->stageList['tested'] = 'Getestet'; -$lang->story->stageList['verified'] = 'Akzepiert'; -$lang->story->stageList['released'] = 'Freigegeben'; -$lang->story->stageList['closed'] = 'Geschlossen'; - -$lang->story->reasonList[''] = ''; -$lang->story->reasonList['done'] = 'Erledigt'; -$lang->story->reasonList['subdivided'] = 'Aufgeteilt'; -$lang->story->reasonList['duplicate'] = 'Dublette'; -$lang->story->reasonList['postponed'] = 'Verschoben'; -$lang->story->reasonList['willnotdo'] = "Ignoriert"; -$lang->story->reasonList['cancel'] = 'Abgebrochen'; -$lang->story->reasonList['bydesign'] = 'Kein Fehler'; -//$lang->story->reasonList['isbug'] = 'Bug!'; - -$lang->story->reviewResultList[''] = ''; -$lang->story->reviewResultList['pass'] = 'Gültig'; -$lang->story->reviewResultList['revert'] = 'Umkehren'; -$lang->story->reviewResultList['clarify'] = 'Klären'; -$lang->story->reviewResultList['reject'] = 'Ablehnen'; - -$lang->story->reviewList[0] = 'Nein'; -$lang->story->reviewList[1] = 'Ja'; - -$lang->story->sourceList[''] = ''; -$lang->story->sourceList['customer'] = 'Kunde'; -$lang->story->sourceList['user'] = 'Benutzer'; -$lang->story->sourceList['po'] = $lang->productCommon . ' Eigentümer'; -$lang->story->sourceList['market'] = 'Marketing'; -$lang->story->sourceList['service'] = 'Service'; -$lang->story->sourceList['operation'] = 'Operative'; -$lang->story->sourceList['support'] = 'Support'; -$lang->story->sourceList['competitor'] = 'Wettbewerber'; -$lang->story->sourceList['partner'] = 'Partner'; -$lang->story->sourceList['dev'] = 'Entwickler'; -$lang->story->sourceList['tester'] = 'QS-Team'; -$lang->story->sourceList['bug'] = 'Bug'; -$lang->story->sourceList['forum'] = 'Forum'; -$lang->story->sourceList['other'] = 'Andere'; - -$lang->story->priList[] = ''; -$lang->story->priList[1] = '1'; -$lang->story->priList[2] = '2'; -$lang->story->priList[3] = '3'; -$lang->story->priList[4] = '4'; - -$lang->story->changeList = array(); -$lang->story->changeList['no'] = 'Cancel'; -$lang->story->changeList['yes'] = 'Confirm'; - -$lang->story->legendBasicInfo = 'Basis Infos'; -$lang->story->legendLifeTime = 'Story Leben '; -$lang->story->legendRelated = 'Weitere Infos'; -$lang->story->legendMailto = 'Mail an'; -$lang->story->legendAttatch = 'Dateien'; -$lang->story->legendProjectAndTask = $lang->executionCommon . ' Aufgaben'; -$lang->story->legendBugs = 'Verküpfte Bugs'; -$lang->story->legendFromBug = 'Verküpfte Formular Bugs'; -$lang->story->legendCases = 'Verküpfte Fälle'; -$lang->story->legendLinkStories = 'Verküpfte Storys'; -$lang->story->legendChildStories = 'Story auteien'; -$lang->story->legendSpec = 'Beschreibung'; -$lang->story->legendVerify = 'Akzeptanz'; -$lang->story->legendMisc = 'Sonstiges '; -$lang->story->legendInformation = 'Story Information'; - -$lang->story->lblChange = 'Ändern'; -$lang->story->lblReview = 'Prüfen'; -$lang->story->lblActivate = 'Aktivieren'; -$lang->story->lblClose = 'Close'; -$lang->story->lblTBC = 'Task/Bug/Case'; - -$lang->story->checkAffection = 'Impact'; -$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; -$lang->story->affectedBugs = 'Bug'; -$lang->story->affectedCases = 'Fall'; - -$lang->story->specTemplate = "Als ein < type of user >, möchte ich < some goal > dass < some reason >."; -$lang->story->needNotReview = 'Keine Prüfung'; -$lang->story->successSaved = "Story wurde gespeichrt!"; -$lang->story->confirmDelete = "Möchten Sie diese Story löschen?"; -$lang->story->confirmRecall = "Do you want to recall this story?"; -$lang->story->errorEmptyChildStory = '『Unterteilte Story』 darf nicht leer sein.'; -$lang->story->errorNotSubdivide = "If the status is not active, or the stage is not wait, or a sub story, it cannot be subdivided."; -$lang->story->errorEmptyReviewedBy = "『ReviewedBy』darf nicht leer sein."; -$lang->story->mustChooseResult = 'Ergebnis wählen'; -$lang->story->mustChoosePreVersion = 'Version wählen um es umzukhren.'; -$lang->story->noStory = 'Keine Storys. '; -$lang->story->noRequirement = 'No Requirements'; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; -$lang->story->cannotDeleteParent = "Can not delete parent {$lang->SRCommon}"; -$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; -$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; -$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; -$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; -$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; -$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; -$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; - -$lang->story->form = new stdclass(); -$lang->story->form->area = 'Story Bereich'; -$lang->story->form->desc = 'Welche Story ist es? Was sind die Abnahmebedingungen?'; -$lang->story->form->resource = 'Wer plant die Resourcen? Wie lange wird das dauern?'; -$lang->story->form->file = 'Wenn Dateien zu dieser Story gehören, laden Sie diese bIttr hoch.'; - -$lang->story->action = new stdclass(); -$lang->story->action->reviewed = array('main' => '$date, geprüft von $actor. Das Ergebnis ist $extra.', 'extra' => 'reviewResultList'); -$lang->story->action->rejectreviewed = array('main' => '$date, geprüft von $actor. Das Ergebnis ist $extra, Der Grund ist $reason.', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); -$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); -$lang->story->action->closed = array('main' => '$date, geschlossen von $actor. Der Grund ist $extra $appendLink.', 'extra' => 'reasonList'); -$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); -$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); -$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); -$lang->story->action->linked2plan = array('main' => '$date, verknüpft von $actor mit Plan $extra'); -$lang->story->action->unlinkedfromplan = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Plan $extra.'); -$lang->story->action->linked2execution = array('main' => '$date, verknüpft von $actor mit ' . $lang->executionCommon . ' $extra.'); -$lang->story->action->unlinkedfromexecution = array('main' => '$date, Verknüpfung aufgelöst durch $actor mit ' . $lang->executionCommon . ' $extra.'); -$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); -$lang->story->action->linked2project = array('main' => '$date, verknüpft von $actor mit project $extra.'); -$lang->story->action->unlinkedfromproject = array('main' => '$date, Verknüpfung aufgelöst durch $actor mit project $extra.'); -$lang->story->action->linked2build = array('main' => '$date, verknüpft von $actor mit Build $extra'); -$lang->story->action->unlinkedfrombuild = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Build $extra.'); -$lang->story->action->linked2release = array('main' => '$date, verknüpft von $actor mit Release $extra'); -$lang->story->action->unlinkedfromrelease = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Release $extra.'); -$lang->story->action->linkrelatedstory = array('main' => '$date, verknüpft von $actor mit Story $extra.'); -$lang->story->action->subdividestory = array('main' => '$date, aufgeteilt von $actor mit Story $extra.'); -$lang->story->action->unlinkrelatedstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor von Story $extra.'); -$lang->story->action->unlinkchildstory = array('main' => '$date, Verknüpfung aufgelöst durch $actor Story $extra.'); - -/* Statistical statement. */ -$lang->story->report = new stdclass(); -$lang->story->report->common = 'Bericht'; -$lang->story->report->select = 'Storys gruppieren nach'; -$lang->story->report->create = 'Erzeugen'; -$lang->story->report->value = 'Story Anzahl'; - -$lang->story->report->charts['storysPerProduct'] = 'nach ' . $lang->productCommon; -$lang->story->report->charts['storysPerModule'] = 'nach Modul'; -$lang->story->report->charts['storysPerSource'] = 'nach Source'; -$lang->story->report->charts['storysPerPlan'] = 'nach Plan'; -$lang->story->report->charts['storysPerStatus'] = 'nach Status'; -$lang->story->report->charts['storysPerStage'] = 'nach Phase'; -$lang->story->report->charts['storysPerPri'] = 'nach Priorität'; -$lang->story->report->charts['storysPerEstimate'] = 'nach Stunden'; -$lang->story->report->charts['storysPerOpenedBy'] = 'nach Ersteller'; -$lang->story->report->charts['storysPerAssignedTo'] = 'nach Zuständiger'; -$lang->story->report->charts['storysPerClosedReason'] = 'nach Grund'; -$lang->story->report->charts['storysPerChange'] = 'nach Änderungszeit'; - -$lang->story->report->options = new stdclass(); -$lang->story->report->options->graph = new stdclass(); -$lang->story->report->options->type = 'pie'; -$lang->story->report->options->width = 500; -$lang->story->report->options->height = 140; - -$lang->story->report->storysPerProduct = new stdclass(); -$lang->story->report->storysPerModule = new stdclass(); -$lang->story->report->storysPerSource = new stdclass(); -$lang->story->report->storysPerPlan = new stdclass(); -$lang->story->report->storysPerStatus = new stdclass(); -$lang->story->report->storysPerStage = new stdclass(); -$lang->story->report->storysPerPri = new stdclass(); -$lang->story->report->storysPerOpenedBy = new stdclass(); -$lang->story->report->storysPerAssignedTo = new stdclass(); -$lang->story->report->storysPerClosedReason = new stdclass(); -$lang->story->report->storysPerEstimate = new stdclass(); -$lang->story->report->storysPerChange = new stdclass(); - -$lang->story->report->storysPerProduct->item = $lang->productCommon; -$lang->story->report->storysPerModule->item = 'Modul'; -$lang->story->report->storysPerSource->item = 'Source'; -$lang->story->report->storysPerPlan->item = 'Plan'; -$lang->story->report->storysPerStatus->item = 'Status'; -$lang->story->report->storysPerStage->item = 'Phase'; -$lang->story->report->storysPerPri->item = 'Priorität'; -$lang->story->report->storysPerOpenedBy->item = 'Konto'; -$lang->story->report->storysPerAssignedTo->item = 'Benutzer'; -$lang->story->report->storysPerClosedReason->item = 'Grund'; -$lang->story->report->storysPerEstimate->item = 'Stunden'; -$lang->story->report->storysPerChange->item = 'Änderung'; - -$lang->story->report->storysPerProduct->graph = new stdclass(); -$lang->story->report->storysPerModule->graph = new stdclass(); -$lang->story->report->storysPerSource->graph = new stdclass(); -$lang->story->report->storysPerPlan->graph = new stdclass(); -$lang->story->report->storysPerStatus->graph = new stdclass(); -$lang->story->report->storysPerStage->graph = new stdclass(); -$lang->story->report->storysPerPri->graph = new stdclass(); -$lang->story->report->storysPerOpenedBy->graph = new stdclass(); -$lang->story->report->storysPerAssignedTo->graph = new stdclass(); -$lang->story->report->storysPerClosedReason->graph = new stdclass(); -$lang->story->report->storysPerEstimate->graph = new stdclass(); -$lang->story->report->storysPerChange->graph = new stdclass(); - -$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; -$lang->story->report->storysPerModule->graph->xAxisName = 'Modul'; -$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; -$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; -$lang->story->report->storysPerStatus->graph->xAxisName = 'Status'; -$lang->story->report->storysPerStage->graph->xAxisName = 'Phase'; -$lang->story->report->storysPerPri->graph->xAxisName = 'Priorität'; -$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Ersteller'; -$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Zuständiger'; -$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Grund'; -$lang->story->report->storysPerEstimate->graph->xAxisName = 'Stunden '; -$lang->story->report->storysPerChange->graph->xAxisName = 'Änderung'; - -$lang->story->placeholder = new stdclass(); -$lang->story->placeholder->estimate = $lang->story->hour; - -$lang->story->chosen = new stdClass(); -$lang->story->chosen->reviewedBy = 'Prüfer wählen'; - -$lang->story->notice = new stdClass(); -$lang->story->notice->closed = 'Die ausgewählten Storys wurden bereits geschlossen!'; -$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; - -$lang->story->convertToTask = new stdClass(); -$lang->story->convertToTask->fieldList = array(); -$lang->story->convertToTask->fieldList['module'] = 'Module'; -$lang->story->convertToTask->fieldList['spec'] = "Description"; -$lang->story->convertToTask->fieldList['pri'] = 'Priority'; -$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; -$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; - -$lang->story->categoryList['feature'] = 'Feature'; -$lang->story->categoryList['interface'] = 'Interface'; -$lang->story->categoryList['performance'] = 'Performance'; -$lang->story->categoryList['safe'] = 'Safe'; -$lang->story->categoryList['experience'] = 'Experience'; -$lang->story->categoryList['improve'] = 'Improve'; -$lang->story->categoryList['other'] = 'Other'; - -$lang->story->changeTip = 'This Story is under review and cannot be changed.'; - -$lang->story->reviewTip = array(); -$lang->story->reviewTip['active'] = 'The Story is already active,no review requirements.'; -$lang->story->reviewTip['notReviewer'] = 'You are not the reviewer of this Story and cannot perform review operations.'; -$lang->story->reviewTip['reviewed'] = 'Reviewed'; -$lang->story->reviewTip['recalled'] = 'This Story has been withdrawn from review and cannot be reviewed.'; - -$lang->story->recallTip = array(); -$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.'; -$lang->story->recallTip['recalled'] = 'This Story has been withdrawn from review and cannot be cancelled.'; -$lang->story->recallTip['reviewed'] = 'The review process has been started and cannot be cancelled.'; -$lang->story->recallTip['notOpenedBy'] = 'You are not the Story creator and cannot cancel the operation.'; - -$lang->story->subDivideTip = array(); -$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.'; -$lang->story->subDivideTip['planned'] = 'The Story has been planned and cannot be subdivided.'; -$lang->story->subDivideTip['projected'] = 'The Story has been initiated and cannot be subdivided.'; -$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.'; diff --git a/module/story:q/lang/en.php b/module/story:q/lang/en.php deleted file mode 100644 index 0261736f59..0000000000 --- a/module/story:q/lang/en.php +++ /dev/null @@ -1,441 +0,0 @@ - - * @package story - * @version $Id: en.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -global $config; -$lang->story->create = "Create Story"; - -$lang->story->requirement = zget($lang, 'URCommon', "Requirement"); -$lang->story->story = zget($lang, 'SRCommon', "Story"); -$lang->story->createStory = 'Create ' . $lang->story->story; -$lang->story->createRequirement = 'Create ' . $lang->story->requirement; -$lang->story->affectedStories = "Affected {$lang->story->story}"; - -$lang->story->batchCreate = "Batch Create"; -$lang->story->change = "Change"; -$lang->story->changed = 'Change'; -$lang->story->assignTo = 'Assign'; -$lang->story->review = 'Review'; -$lang->story->recall = 'Revoke'; -$lang->story->needReview = 'Need Review'; -$lang->story->batchReview = 'Batch Review'; -$lang->story->edit = "Edit Story"; -$lang->story->batchEdit = "Batch Edit"; -$lang->story->subdivide = 'Decompose'; -$lang->story->link = 'Link'; -$lang->story->unlink = 'Unlink'; -$lang->story->track = 'Track'; -$lang->story->trackAB = 'Track'; -$lang->story->processStoryChange = 'Confirm Story Change'; -$lang->story->splitRequirent = 'Decompose'; -$lang->story->close = 'Close'; -$lang->story->batchClose = 'Batch Close'; -$lang->story->activate = 'Activate'; -$lang->story->delete = "Delete"; -$lang->story->view = "Story Detail"; -$lang->story->setting = "Settings"; -$lang->story->tasks = "Linked Tasks"; -$lang->story->bugs = "Linked Bugs"; -$lang->story->cases = "Linked Cases"; -$lang->story->taskCount = 'Tasks'; -$lang->story->bugCount = 'Bugs'; -$lang->story->caseCount = 'Cases'; -$lang->story->taskCountAB = 'T'; -$lang->story->bugCountAB = 'B'; -$lang->story->caseCountAB = 'C'; -$lang->story->linkStory = 'Link Story'; -$lang->story->unlinkStory = 'UnLinked'; -$lang->story->export = "Export Data"; -$lang->story->zeroCase = "Stories without cases"; -$lang->story->zeroTask = "Only list stories without tasks"; -$lang->story->reportChart = "Report"; -$lang->story->copyTitle = "Copy Title"; -$lang->story->batchChangePlan = "Batch Change Plans"; -$lang->story->batchChangeBranch = "Batch Change Branches"; -$lang->story->batchChangeStage = "Batch Change Phases"; -$lang->story->batchAssignTo = "Batch Assign"; -$lang->story->batchChangeModule = "Batch Change Modules"; -$lang->story->viewAll = "See All"; -$lang->story->toTask = 'Convert to Task'; -$lang->story->batchToTask = 'Batch Convert to Task'; -$lang->story->convertRelations = 'Convert Relations'; -$lang->story->undetermined = 'undetermined'; -$lang->story->order = 'Order'; - -$lang->story->editAction = "Edit {$lang->SRCommon}"; -$lang->story->changeAction = "Change {$lang->SRCommon}"; -$lang->story->assignAction = "Assign {$lang->SRCommon}"; -$lang->story->reviewAction = "Review {$lang->SRCommon}"; -$lang->story->subdivideAction = "Subdivide {$lang->SRCommon}"; -$lang->story->closeAction = "Close {$lang->SRCommon}"; -$lang->story->activateAction = "Activate {$lang->SRCommon}"; -$lang->story->deleteAction = "Delete {$lang->SRCommon}"; -$lang->story->exportAction = "Export {$lang->SRCommon}"; -$lang->story->reportAction = "Report"; - -$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; -$lang->story->closedStory = 'Story %s is closed and will not be closed.'; -$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; -$lang->story->successToTask = "Converted to task."; -$lang->story->storyRound = '%s time estimation'; - -$lang->story->id = 'ID'; -$lang->story->parent = 'Parent'; -$lang->story->product = $lang->productCommon; -$lang->story->project = 'Project'; -$lang->story->branch = "Branch/Platform"; -$lang->story->module = 'Module'; -$lang->story->moduleAB = 'Module'; -$lang->story->source = 'From'; -$lang->story->sourceNote = 'Note'; -$lang->story->fromBug = 'From Bug'; -$lang->story->title = 'Title'; -$lang->story->type = "Story/Requirement"; -$lang->story->category = 'Category'; -$lang->story->color = 'Color'; -$lang->story->toBug = 'ToBug'; -$lang->story->spec = 'Description'; -$lang->story->assign = 'Assign'; -$lang->story->verify = 'Acceptance'; -$lang->story->pri = 'Priority'; -$lang->story->estimate = "Estimates"; -$lang->story->estimateAB = 'Est.'; -$lang->story->hour = $lang->hourCommon; -$lang->story->status = 'Status'; -$lang->story->subStatus = 'Sub Status'; -$lang->story->stage = 'Phase'; -$lang->story->stageAB = 'Phase'; -$lang->story->stagedBy = 'SetBy'; -$lang->story->mailto = 'Mailto'; -$lang->story->openedBy = 'Created By'; -$lang->story->openedDate = 'Created Date'; -$lang->story->assignedTo = 'AssignTo'; -$lang->story->assignedDate = 'AssignedDate'; -$lang->story->lastEditedBy = 'EditedBy'; -$lang->story->lastEditedDate = 'EditedDate'; -$lang->story->closedBy = 'ClosedBy'; -$lang->story->closedDate = 'ClosedDate'; -$lang->story->closedReason = 'Reason'; -$lang->story->rejectedReason = 'Reject Reason'; -$lang->story->reviewedBy = 'ReviewedBy'; -$lang->story->reviewer = $lang->story->reviewedBy; -$lang->story->reviewers = 'Reviewers'; -$lang->story->reviewedDate = 'ReviewedDate'; -$lang->story->activatedDate = 'Activated Date'; -$lang->story->version = 'Version'; -$lang->story->feedbackBy = 'From Name'; -$lang->story->notifyEmail = 'From Email'; -$lang->story->plan = 'Linked Plan'; -$lang->story->planAB = 'Plan'; -$lang->story->comment = 'Comment'; -$lang->story->children = "Child {$lang->SRCommon}"; -$lang->story->childrenAB = "C"; -$lang->story->linkStories = 'Linked Story'; -$lang->story->childStories = 'Decomposed Story'; -$lang->story->duplicateStory = 'Duplicated Story ID'; -$lang->story->reviewResult = 'Review Result'; -$lang->story->preVersion = 'Last Version'; -$lang->story->keywords = 'Tags'; -$lang->story->newStory = 'Continue adding'; -$lang->story->colorTag = 'Color'; -$lang->story->files = 'Files'; -$lang->story->copy = "Copy Story"; -$lang->story->total = "Total Stories"; -$lang->story->draft = 'Draft'; -$lang->story->unclosed = 'Unclosed'; -$lang->story->deleted = 'Deleted'; -$lang->story->released = 'Released Stories'; -$lang->story->URChanged = 'Requirement Changed'; -$lang->story->design = 'Design'; -$lang->story->case = 'Cases'; -$lang->story->bug = 'Bugs'; -$lang->story->repoCommit = 'Commits'; -$lang->story->noRequirement = 'No Requirements'; -$lang->story->one = 'One'; -$lang->story->field = 'Sync Field'; -$lang->story->completeRate = 'Completion Rate'; -$lang->story->reviewed = 'Reviewed'; -$lang->story->toBeReviewed = 'To Be Reviewed'; -$lang->story->linkMR = 'Related MRs'; - -$lang->story->ditto = 'Ditto'; -$lang->story->dittoNotice = 'This story is not linked to the same product as the last one is!'; - -$lang->story->needNotReviewList[0] = 'Need Review'; -$lang->story->needNotReviewList[1] = 'No Review'; - -$lang->story->useList[0] = 'Yes'; -$lang->story->useList[1] = 'No'; - -$lang->story->statusList[''] = ''; -$lang->story->statusList['draft'] = 'Draft'; -$lang->story->statusList['active'] = 'Active'; -$lang->story->statusList['closed'] = 'Closed'; -$lang->story->statusList['changed'] = 'Changed'; - -$lang->story->stageList[''] = ''; -$lang->story->stageList['wait'] = 'Waiting'; -$lang->story->stageList['planned'] = 'Planned'; -$lang->story->stageList['projected'] = 'Projected'; -$lang->story->stageList['developing'] = 'Developing'; -$lang->story->stageList['developed'] = 'Developed'; -$lang->story->stageList['testing'] = 'Testing'; -$lang->story->stageList['tested'] = 'Tested'; -$lang->story->stageList['verified'] = 'Accepted'; -$lang->story->stageList['released'] = 'Released'; -$lang->story->stageList['closed'] = 'Closed'; - -$lang->story->reasonList[''] = ''; -$lang->story->reasonList['done'] = 'Already Done'; -$lang->story->reasonList['subdivided'] = 'Decomposed'; -$lang->story->reasonList['duplicate'] = 'Duplicate'; -$lang->story->reasonList['postponed'] = 'Postponed'; -$lang->story->reasonList['willnotdo'] = "Won't Do"; -$lang->story->reasonList['cancel'] = 'Cancelled'; -$lang->story->reasonList['bydesign'] = 'As Designed'; -//$lang->story->reasonList['isbug'] = 'Bug!'; - -$lang->story->reviewResultList[''] = ''; -$lang->story->reviewResultList['pass'] = 'Pass'; -$lang->story->reviewResultList['revert'] = 'Revert'; -$lang->story->reviewResultList['clarify'] = 'To Be Clarified'; -$lang->story->reviewResultList['reject'] = 'Reject'; - -$lang->story->reviewList[0] = 'No'; -$lang->story->reviewList[1] = 'Yes'; - -$lang->story->sourceList[''] = ''; -$lang->story->sourceList['customer'] = 'Customer'; -$lang->story->sourceList['user'] = 'User'; -$lang->story->sourceList['po'] = $lang->productCommon . ' Owner'; -$lang->story->sourceList['market'] = 'Marketing'; -$lang->story->sourceList['service'] = 'Customer Service'; -$lang->story->sourceList['operation'] = 'Operations'; -$lang->story->sourceList['support'] = 'Technical Support'; -$lang->story->sourceList['competitor'] = 'Competitor'; -$lang->story->sourceList['partner'] = 'Partner'; -$lang->story->sourceList['dev'] = 'Dev Team'; -$lang->story->sourceList['tester'] = 'QA Team'; -$lang->story->sourceList['bug'] = 'Bug'; -$lang->story->sourceList['forum'] = 'Forum'; -$lang->story->sourceList['other'] = 'Others'; - -$lang->story->priList[] = ''; -$lang->story->priList[1] = '1'; -$lang->story->priList[2] = '2'; -$lang->story->priList[3] = '3'; -$lang->story->priList[4] = '4'; - -$lang->story->changeList = array(); -$lang->story->changeList['no'] = 'Cancel'; -$lang->story->changeList['yes'] = 'Confirm'; - -$lang->story->legendBasicInfo = 'Basic Info'; -$lang->story->legendLifeTime = 'Story Life '; -$lang->story->legendRelated = 'Related Info'; -$lang->story->legendMailto = 'Mailto'; -$lang->story->legendAttatch = 'Files'; -$lang->story->legendProjectAndTask = $lang->executionCommon . ' And Task'; -$lang->story->legendBugs = 'Linked Bugs'; -$lang->story->legendFromBug = 'From Bug'; -$lang->story->legendCases = 'Linked Cases'; -$lang->story->legendLinkStories = 'Linked Stories'; -$lang->story->legendChildStories = 'Child Stories'; -$lang->story->legendSpec = 'Description'; -$lang->story->legendVerify = 'Acceptance'; -$lang->story->legendMisc = 'Misc.'; -$lang->story->legendInformation = 'Story Information'; - -$lang->story->lblChange = 'Change'; -$lang->story->lblReview = 'Review'; -$lang->story->lblActivate = 'Activate'; -$lang->story->lblClose = 'Close'; -$lang->story->lblTBC = 'Task/Bug/Case'; - -$lang->story->checkAffection = 'Influence'; -$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; -$lang->story->affectedBugs = 'Bugs'; -$lang->story->affectedCases = 'Cases'; - -$lang->story->specTemplate = "As a < type of user >, I want < some goal > so that < some reason >."; -$lang->story->needNotReview = 'No Review'; -$lang->story->successSaved = "Story is saved!"; -$lang->story->confirmDelete = "Do you want to delete this story?"; -$lang->story->confirmRecall = "Do you want to recall this story?"; -$lang->story->errorEmptyChildStory = '『Decomposed Stories』canot be blank.'; -$lang->story->errorNotSubdivide = "If the status is not active, or the stage is not wait, or a sub story, it cannot be subdivided."; -$lang->story->errorEmptyReviewedBy = "『ReviewedBy』canot be blank."; -$lang->story->mustChooseResult = 'Select Result'; -$lang->story->mustChoosePreVersion = 'Select a version to revert to.'; -$lang->story->noStory = 'No stories yet. '; -$lang->story->noRequirement = 'No requirements yet. '; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; -$lang->story->cannotDeleteParent = "Can not delete parent {$lang->SRCommon}"; -$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; -$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; -$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; -$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; -$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; -$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; -$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; - -$lang->story->form = new stdclass(); -$lang->story->form->area = 'Scope'; -$lang->story->form->desc = 'What story is it? What is the acceptance?'; -$lang->story->form->resource = 'Who will allocate resources? How long does it take?'; -$lang->story->form->file = 'If any file that is linked to a story, please click Here to upload it.'; - -$lang->story->action = new stdclass(); -$lang->story->action->reviewed = array('main' => '$date, recorded by $actor. The result is $extra.', 'extra' => 'reviewResultList'); -$lang->story->action->rejectreviewed = array('main' => '$date, recorded by $actor. The result is $extra. The reasion is $reason.', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); -$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); -$lang->story->action->closed = array('main' => '$date, closed by $actor. The reasion is $extra $appendLink.', 'extra' => 'reasonList'); -$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); -$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); -$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); -$lang->story->action->linked2plan = array('main' => '$date, linked by $actor to Plan $extra'); -$lang->story->action->unlinkedfromplan = array('main' => '$date, unlinked by $actor from Plan $extra.'); -$lang->story->action->linked2execution = array('main' => '$date, linked by $actor to ' . $lang->executionCommon . ' $extra.'); -$lang->story->action->unlinkedfromexecution = array('main' => '$date, unlinked by $actor from ' . $lang->executionCommon . ' $extra.'); -$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); -$lang->story->action->linked2project = array('main' => '$date, linked by $actor to project $extra.'); -$lang->story->action->unlinkedfromproject = array('main' => '$date, unlinked by $actor from project $extra.'); -$lang->story->action->linked2build = array('main' => '$date, linked by $actor to Build $extra'); -$lang->story->action->unlinkedfrombuild = array('main' => '$date, unlinked by $actor from Build $extra.'); -$lang->story->action->linked2release = array('main' => '$date, linked by $actor to Release $extra'); -$lang->story->action->unlinkedfromrelease = array('main' => '$date, unlinked by $actor from Release $extra.'); -$lang->story->action->linkrelatedstory = array('main' => '$date, linked by $actor to Story $extra.'); -$lang->story->action->subdividestory = array('main' => '$date, decomposed by $actor to Story $extra.'); -$lang->story->action->unlinkrelatedstory = array('main' => '$date, unlinked by $actor from Story $extra.'); -$lang->story->action->unlinkchildstory = array('main' => '$date, unlinked by $actor Decomposed Story $extra.'); - -/* Statistical statement. */ -$lang->story->report = new stdclass(); -$lang->story->report->common = 'Report'; -$lang->story->report->select = 'Select Report Type'; -$lang->story->report->create = 'Create Report'; -$lang->story->report->value = 'Reports'; - -$lang->story->report->charts['storysPerProduct'] = 'Group by ' . $lang->productCommon . ' Story'; -$lang->story->report->charts['storysPerModule'] = 'Group by Module Story'; -$lang->story->report->charts['storysPerSource'] = 'Group by Story Source'; -$lang->story->report->charts['storysPerPlan'] = 'Group by Plan'; -$lang->story->report->charts['storysPerStatus'] = 'Group by Status'; -$lang->story->report->charts['storysPerStage'] = 'Group by Phase'; -$lang->story->report->charts['storysPerPri'] = 'Group by Priority'; -$lang->story->report->charts['storysPerEstimate'] = 'Group by Estimates'; -$lang->story->report->charts['storysPerOpenedBy'] = 'Group by CreatedBy'; -$lang->story->report->charts['storysPerAssignedTo'] = 'Group by AssignedTo'; -$lang->story->report->charts['storysPerClosedReason'] = 'Group by Closed Reason'; -$lang->story->report->charts['storysPerChange'] = 'Group by Changed Story'; - -$lang->story->report->options = new stdclass(); -$lang->story->report->options->graph = new stdclass(); -$lang->story->report->options->type = 'pie'; -$lang->story->report->options->width = 500; -$lang->story->report->options->height = 140; - -$lang->story->report->storysPerProduct = new stdclass(); -$lang->story->report->storysPerModule = new stdclass(); -$lang->story->report->storysPerSource = new stdclass(); -$lang->story->report->storysPerPlan = new stdclass(); -$lang->story->report->storysPerStatus = new stdclass(); -$lang->story->report->storysPerStage = new stdclass(); -$lang->story->report->storysPerPri = new stdclass(); -$lang->story->report->storysPerOpenedBy = new stdclass(); -$lang->story->report->storysPerAssignedTo = new stdclass(); -$lang->story->report->storysPerClosedReason = new stdclass(); -$lang->story->report->storysPerEstimate = new stdclass(); -$lang->story->report->storysPerChange = new stdclass(); - -$lang->story->report->storysPerProduct->item = $lang->productCommon; -$lang->story->report->storysPerModule->item = 'Module'; -$lang->story->report->storysPerSource->item = 'Source'; -$lang->story->report->storysPerPlan->item = 'Plan'; -$lang->story->report->storysPerStatus->item = 'Status'; -$lang->story->report->storysPerStage->item = 'Phase'; -$lang->story->report->storysPerPri->item = 'Priority'; -$lang->story->report->storysPerOpenedBy->item = 'OpenedBy'; -$lang->story->report->storysPerAssignedTo->item = 'AssignedTo'; -$lang->story->report->storysPerClosedReason->item = 'Reason'; -$lang->story->report->storysPerEstimate->item = 'Estimates'; -$lang->story->report->storysPerChange->item = 'Changed Story'; - -$lang->story->report->storysPerProduct->graph = new stdclass(); -$lang->story->report->storysPerModule->graph = new stdclass(); -$lang->story->report->storysPerSource->graph = new stdclass(); -$lang->story->report->storysPerPlan->graph = new stdclass(); -$lang->story->report->storysPerStatus->graph = new stdclass(); -$lang->story->report->storysPerStage->graph = new stdclass(); -$lang->story->report->storysPerPri->graph = new stdclass(); -$lang->story->report->storysPerOpenedBy->graph = new stdclass(); -$lang->story->report->storysPerAssignedTo->graph = new stdclass(); -$lang->story->report->storysPerClosedReason->graph = new stdclass(); -$lang->story->report->storysPerEstimate->graph = new stdclass(); -$lang->story->report->storysPerChange->graph = new stdclass(); - -$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; -$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; -$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; -$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; -$lang->story->report->storysPerStatus->graph->xAxisName = 'Status'; -$lang->story->report->storysPerStage->graph->xAxisName = 'Phase'; -$lang->story->report->storysPerPri->graph->xAxisName = 'Priority'; -$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'CreatedBy'; -$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'AssignedTo'; -$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Close Reason'; -$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimates '; -$lang->story->report->storysPerChange->graph->xAxisName = 'Change Times'; - -$lang->story->placeholder = new stdclass(); -$lang->story->placeholder->estimate = $lang->story->hour; - -$lang->story->chosen = new stdClass(); -$lang->story->chosen->reviewedBy = 'Choose ReviewedBy'; - -$lang->story->notice = new stdClass(); -$lang->story->notice->closed = 'Story that you select is closed!'; -$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; - -$lang->story->convertToTask = new stdClass(); -$lang->story->convertToTask->fieldList = array(); -$lang->story->convertToTask->fieldList['module'] = 'Module'; -$lang->story->convertToTask->fieldList['spec'] = "Description"; -$lang->story->convertToTask->fieldList['pri'] = 'Priority'; -$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; -$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; - -$lang->story->categoryList['feature'] = 'Feature'; -$lang->story->categoryList['interface'] = 'Interface'; -$lang->story->categoryList['performance'] = 'Performance'; -$lang->story->categoryList['safe'] = 'Safe'; -$lang->story->categoryList['experience'] = 'Experience'; -$lang->story->categoryList['improve'] = 'Improve'; -$lang->story->categoryList['other'] = 'Other'; - -$lang->story->changeTip = 'This Story is under review and cannot be changed.'; - -$lang->story->reviewTip = array(); -$lang->story->reviewTip['active'] = 'The Story is already active,no review requirements.'; -$lang->story->reviewTip['notReviewer'] = 'You are not the reviewer of this Story and cannot perform review operations.'; -$lang->story->reviewTip['reviewed'] = 'Reviewed'; -$lang->story->reviewTip['recalled'] = 'This Story has been withdrawn from review and cannot be reviewed.'; - -$lang->story->recallTip = array(); -$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.'; -$lang->story->recallTip['recalled'] = 'This Story has been withdrawn from review and cannot be cancelled.'; -$lang->story->recallTip['reviewed'] = 'The review process has been started and cannot be cancelled.'; -$lang->story->recallTip['notOpenedBy'] = 'You are not the Story creator and cannot cancel the operation.'; - -$lang->story->subDivideTip = array(); -$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.'; -$lang->story->subDivideTip['planned'] = 'The Story has been planned and cannot be subdivided.'; -$lang->story->subDivideTip['projected'] = 'The Story has been initiated and cannot be subdivided.'; -$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.'; diff --git a/module/story:q/lang/fr.php b/module/story:q/lang/fr.php deleted file mode 100644 index 47ef083382..0000000000 --- a/module/story:q/lang/fr.php +++ /dev/null @@ -1,441 +0,0 @@ - - * @package story - * @version $Id: en.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -global $config; -$lang->story->create = "Créer Story"; - -$lang->story->requirement = zget($lang, 'URCommon', "Requirement"); -$lang->story->story = zget($lang, 'SRCommon', "Story"); -$lang->story->createStory = 'Create ' . $lang->story->story; -$lang->story->createRequirement = 'Create ' . $lang->story->requirement; -$lang->story->affectedStories = "Affected {$lang->story->story}"; - -$lang->story->batchCreate = "Créer par Lot"; -$lang->story->change = "Changer"; -$lang->story->changed = 'Changée'; -$lang->story->assignTo = 'Affecter'; -$lang->story->review = 'Valider'; -$lang->story->recall = 'Revoke'; -$lang->story->needReview = 'Validation requise'; -$lang->story->batchReview = 'Validation par lot'; -$lang->story->edit = "Editer Story"; -$lang->story->batchEdit = "Editer par Lot"; -$lang->story->subdivide = 'Décomposer'; -$lang->story->link = 'Link'; -$lang->story->unlink = 'Unlink'; -$lang->story->track = 'Track'; -$lang->story->trackAB = 'Track'; -$lang->story->processStoryChange = 'Confirm Story Change'; -$lang->story->splitRequirent = 'Décompose'; -$lang->story->close = 'Fermer'; -$lang->story->batchClose = 'Fermer par Lot'; -$lang->story->activate = 'Activer'; -$lang->story->delete = "Supprimer"; -$lang->story->view = "Détail Story"; -$lang->story->setting = "Paramétrage"; -$lang->story->tasks = "Tâches Associées"; -$lang->story->bugs = "Bugs Associés"; -$lang->story->cases = "CasTest Associés"; -$lang->story->taskCount = 'Tâches'; -$lang->story->bugCount = 'Bugs'; -$lang->story->caseCount = 'CasTests'; -$lang->story->taskCountAB = 'T'; -$lang->story->bugCountAB = 'B'; -$lang->story->caseCountAB = 'C'; -$lang->story->linkStory = 'Lier Story'; -$lang->story->unlinkStory = 'Dissocier'; -$lang->story->export = "Exporter Données"; -$lang->story->zeroCase = "Stories sans CasTests"; -$lang->story->zeroTask = "Seulement liste des stories sans tâches"; -$lang->story->reportChart = "Rapport"; -$lang->story->copyTitle = "Copier Titre"; -$lang->story->batchChangePlan = "Changer Plans par lot"; -$lang->story->batchChangeBranch = "Changer Branches par lot"; -$lang->story->batchChangeStage = "Changer Phases par lot"; -$lang->story->batchAssignTo = "Affecter par lot"; -$lang->story->batchChangeModule = "Changer Modules par lot"; -$lang->story->viewAll = "Voir Tout"; -$lang->story->toTask = 'Convert to Task'; -$lang->story->batchToTask = 'Batch Convert to Task'; -$lang->story->convertRelations = 'Convert Relations'; -$lang->story->undetermined = 'undetermined'; -$lang->story->order = 'Order'; - -$lang->story->editAction = "Edit {$lang->SRCommon}"; -$lang->story->changeAction = "Changer Story"; -$lang->story->assignAction = 'Affecter Story'; -$lang->story->reviewAction = 'Valider Story'; -$lang->story->subdivideAction = 'Décomposer Story'; -$lang->story->closeAction = 'Fermer Story'; -$lang->story->activateAction = 'Activer Story'; -$lang->story->deleteAction = "Supprimer Story"; -$lang->story->exportAction = "Exporter Story"; -$lang->story->reportAction = "Rapport de Story"; - -$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; -$lang->story->closedStory = 'Story %s is closed and will not be closed.'; -$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; -$lang->story->successToTask = "Converted to task."; -$lang->story->storyRound = '%s time estimation'; - -$lang->story->id = 'ID'; -$lang->story->parent = 'Parent'; -$lang->story->product = $lang->productCommon; -$lang->story->project = 'Project'; -$lang->story->branch = "Branche/Plateforme"; -$lang->story->module = 'Module'; -$lang->story->moduleAB = 'Module'; -$lang->story->source = 'De'; -$lang->story->sourceNote = 'Note'; -$lang->story->fromBug = 'Depuis Bug'; -$lang->story->title = 'Titre'; -$lang->story->type = "Story/Requirement"; -$lang->story->category = 'Category'; -$lang->story->color = 'Couleur'; -$lang->story->toBug = 'Vers Bug'; -$lang->story->spec = 'Description'; -$lang->story->assign = 'Affecter'; -$lang->story->verify = 'Acceptance'; -$lang->story->pri = 'Priorité'; -$lang->story->estimate = 'Estimation'; -$lang->story->estimateAB = 'Esti.(h)'; -$lang->story->hour = 'Heures'; -$lang->story->status = 'Statut'; -$lang->story->subStatus = 'Sous-statut'; -$lang->story->stage = 'Phase'; -$lang->story->stageAB = 'Phase'; -$lang->story->stagedBy = 'Fixé par'; -$lang->story->mailto = 'Mailto'; -$lang->story->openedBy = 'Créée par'; -$lang->story->openedDate = 'Date Création'; -$lang->story->assignedTo = 'Assignée à'; -$lang->story->assignedDate = 'Date Assignation'; -$lang->story->lastEditedBy = 'Editée par'; -$lang->story->lastEditedDate = 'Date Edition'; -$lang->story->closedBy = 'Fermée par'; -$lang->story->closedDate = 'Date Fermeture'; -$lang->story->closedReason = 'Raison'; -$lang->story->rejectedReason = 'Raison du Rejet'; -$lang->story->reviewedBy = 'Validée par'; -$lang->story->reviewer = $lang->story->reviewedBy; -$lang->story->reviewers = 'Reviewers'; -$lang->story->reviewedDate = 'Date Validation'; -$lang->story->activatedDate = 'Activated Date'; -$lang->story->version = 'Version'; -$lang->story->feedbackBy = 'From Name'; -$lang->story->notifyEmail = 'From Email'; -$lang->story->plan = 'Intégrée Plans'; -$lang->story->planAB = 'Plan'; -$lang->story->comment = 'Commentaire'; -$lang->story->children = "Enfant {$lang->SRCommon}"; -$lang->story->childrenAB = "C"; -$lang->story->linkStories = 'Stories Liées'; -$lang->story->childStories = 'Stories Décomposées'; -$lang->story->duplicateStory = 'Story ID Dupliquées'; -$lang->story->reviewResult = 'Résultat Validation'; -$lang->story->preVersion = 'Dernière Version'; -$lang->story->keywords = 'Tags'; -$lang->story->newStory = "Continuer d'ajouter"; -$lang->story->colorTag = 'Couleur'; -$lang->story->files = 'Fichiers'; -$lang->story->copy = "Copier Story"; -$lang->story->total = "Stories Total"; -$lang->story->draft = 'Brouillon'; -$lang->story->unclosed = 'Non Fermées'; -$lang->story->deleted = 'Supprimée'; -$lang->story->released = 'Stories Versionnées'; -$lang->story->URChanged = 'Requirement Changed'; -$lang->story->design = 'Designs'; -$lang->story->case = 'Cases'; -$lang->story->bug = 'Bugs'; -$lang->story->repoCommit = 'Commits'; -$lang->story->noRequirement = 'No Requirements'; -$lang->story->one = 'One'; -$lang->story->field = 'Synchronized fields'; -$lang->story->completeRate = 'Completion Rate'; -$lang->story->reviewed = 'Reviewed'; -$lang->story->toBeReviewed = 'To Be Reviewed'; -$lang->story->linkMR = 'Related MRs'; - -$lang->story->ditto = 'Idem'; -$lang->story->dittoNotice = "La story n'est pas associée au même product que la précédente !"; - -$lang->story->needNotReviewList[0] = 'Need Review'; -$lang->story->needNotReviewList[1] = 'Need Not Review'; - -$lang->story->useList[0] = 'Oui'; -$lang->story->useList[1] = 'Non'; - -$lang->story->statusList[''] = ''; -$lang->story->statusList['draft'] = 'Brouillon'; -$lang->story->statusList['active'] = 'Active'; -$lang->story->statusList['closed'] = 'Fermée'; -$lang->story->statusList['changed'] = 'Changée'; - -$lang->story->stageList[''] = ''; -$lang->story->stageList['wait'] = 'En Attente'; -$lang->story->stageList['planned'] = 'Plannifée'; -$lang->story->stageList['projected'] = 'Projetée'; -$lang->story->stageList['developing'] = 'En Dév.'; -$lang->story->stageList['developed'] = 'Développée'; -$lang->story->stageList['testing'] = 'En Test'; -$lang->story->stageList['tested'] = 'Testée'; -$lang->story->stageList['verified'] = 'Acceptée'; -$lang->story->stageList['released'] = 'Versionnée'; -$lang->story->stageList['closed'] = 'Fermée'; - -$lang->story->reasonList[''] = ''; -$lang->story->reasonList['done'] = 'Déjà Fait'; -$lang->story->reasonList['subdivided'] = 'Décomposée'; -$lang->story->reasonList['duplicate'] = 'Doublon'; -$lang->story->reasonList['postponed'] = 'Reportée'; -$lang->story->reasonList['willnotdo'] = "On ne fera pas"; -$lang->story->reasonList['cancel'] = 'Annulée'; -$lang->story->reasonList['bydesign'] = 'Du Design'; -//$lang->story->reasonList['isbug'] = 'Bug!'; - -$lang->story->reviewResultList[''] = ''; -$lang->story->reviewResultList['pass'] = 'Accepté'; -$lang->story->reviewResultList['revert'] = 'Retour Arrière'; -$lang->story->reviewResultList['clarify'] = 'A Clarifier'; -$lang->story->reviewResultList['reject'] = 'Refusé'; - -$lang->story->reviewList[0] = 'Non'; -$lang->story->reviewList[1] = 'Oui'; - -$lang->story->sourceList[''] = ''; -$lang->story->sourceList['customer'] = 'Client'; -$lang->story->sourceList['user'] = 'Utilisateur'; -$lang->story->sourceList['po'] = $lang->productCommon . ' Owner'; -$lang->story->sourceList['market'] = 'Marketing'; -$lang->story->sourceList['service'] = 'Service Client'; -$lang->story->sourceList['operation'] = 'Opérations'; -$lang->story->sourceList['support'] = 'Support'; -$lang->story->sourceList['competitor'] = 'Concurrence'; -$lang->story->sourceList['partner'] = 'Partenaire'; -$lang->story->sourceList['dev'] = 'Equipe de Dev'; -$lang->story->sourceList['tester'] = 'Equipe de Test'; -$lang->story->sourceList['bug'] = 'Bug'; -$lang->story->sourceList['forum'] = 'Forum'; -$lang->story->sourceList['other'] = 'Autre'; - -$lang->story->priList[] = ''; -$lang->story->priList[1] = '1'; -$lang->story->priList[2] = '2'; -$lang->story->priList[3] = '3'; -$lang->story->priList[4] = '4'; - -$lang->story->changeList = array(); -$lang->story->changeList['no'] = 'Cancel'; -$lang->story->changeList['yes'] = 'Confirm'; - -$lang->story->legendBasicInfo = 'Infos de Base'; -$lang->story->legendLifeTime = 'Vie de la Story '; -$lang->story->legendRelated = 'Info Connexes'; -$lang->story->legendMailto = 'Mailto'; -$lang->story->legendAttatch = 'Fichiers'; -$lang->story->legendProjectAndTask = $lang->executionCommon . ' et Tâches'; -$lang->story->legendBugs = 'Bugs Liés'; -$lang->story->legendFromBug = 'du Bug'; -$lang->story->legendCases = 'CasTests Liés'; -$lang->story->legendLinkStories = 'Stories Liées'; -$lang->story->legendChildStories = 'Sous-Stories'; -$lang->story->legendSpec = 'Description'; -$lang->story->legendVerify = 'Acceptance'; -$lang->story->legendMisc = 'Divers'; -$lang->story->legendInformation = 'Story Information'; - -$lang->story->lblChange = 'Changer'; -$lang->story->lblReview = 'Valider'; -$lang->story->lblActivate = 'Activer'; -$lang->story->lblClose = 'Fermer'; -$lang->story->lblTBC = 'Tâche/Bug/CasTest'; - -$lang->story->checkAffection = 'Influence'; -$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; -$lang->story->affectedBugs = 'Bugs'; -$lang->story->affectedCases = 'CasTests'; - -$lang->story->specTemplate = "En tant que < type utilisateur >, je veux < différents objectifs > pour < différentes raisons >."; -$lang->story->needNotReview = 'Aucune Validation Requise'; -$lang->story->successSaved = "Story est sauvegardée !"; -$lang->story->confirmDelete = "Voulez-vous vraiment supprimer cette story ?"; -$lang->story->confirmRecall = "Do you want to recall this story?"; -$lang->story->errorEmptyChildStory = '『Decomposed Stories』ne peuvent être vides.'; -$lang->story->errorNotSubdivide = "Si le statut n'est pas actif, ou l'étape n'est pas en attente, ou si c'est une sous-story, elle ne peut pas être subdivisée."; -$lang->story->errorEmptyReviewedBy = "『ReviewedBy』canot be blank."; -$lang->story->mustChooseResult = 'Sélect Résultat'; -$lang->story->mustChoosePreVersion = 'Sélect une version pour revenir en arrière.'; -$lang->story->noStory = "Aucune story pour l'instant. "; -$lang->story->noRequirement = 'No Requirements'; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; -$lang->story->cannotDeleteParent = "Impossible de supprimer {$lang->SRCommon} parent"; -$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; -$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; -$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; -$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; -$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; -$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; -$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; - -$lang->story->form = new stdclass(); -$lang->story->form->area = 'Périmètre'; -$lang->story->form->desc = "Quelle story est-ce ? Quel est son état d'acceptance ?"; -$lang->story->form->resource = 'Qui va allouer des resources ? Combien de temps cela va-t-il prendre ?'; -$lang->story->form->file = 'Si certains fichiers sont associés à la story, cliquez ici pour les uploader.'; - -$lang->story->action = new stdclass(); -$lang->story->action->reviewed = array('main' => '$date, validée par $actor. Le résultat est $extra.', 'extra' => 'reviewResultList'); -$lang->story->action->rejectreviewed = array('main' => '$date, validée par $actor. Le résultat est $extra. La raison est $reason.', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); -$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); -$lang->story->action->closed = array('main' => '$date, Fermée par $actor. La raison est $extra $appendLink.', 'extra' => 'reasonList'); -$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); -$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); -$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); -$lang->story->action->linked2plan = array('main' => '$date, planifiée par $actor au Plan $extra'); -$lang->story->action->unlinkedfromplan = array('main' => '$date, déplanifiée par $actor du Plan $extra.'); -$lang->story->action->linked2execution = array('main' => '$date, associée au ' . $lang->executionCommon . ' $extra par $actor.'); -$lang->story->action->unlinkedfromexecution = array('main' => '$date, dissociée du ' . $lang->executionCommon . ' $extra par $actor.'); -$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); -$lang->story->action->linked2project = array('main' => '$date, associée au project $extra par $actor.'); -$lang->story->action->unlinkedfromproject = array('main' => '$date, dissociée d uproject $extra par $actor.'); -$lang->story->action->linked2build = array('main' => '$date, intégrée par $actor au Build $extra'); -$lang->story->action->unlinkedfrombuild = array('main' => '$date, retirée par $actor du Build $extra.'); -$lang->story->action->linked2release = array('main' => '$date, ajoutée par $actor à la Release $extra'); -$lang->story->action->unlinkedfromrelease = array('main' => '$date, retirée par $actor de la Release $extra.'); -$lang->story->action->linkrelatedstory = array('main' => '$date, associée par $actor à la Story $extra.'); -$lang->story->action->subdividestory = array('main' => '$date, décomposée par $actor en Story $extra.'); -$lang->story->action->unlinkrelatedstory = array('main' => '$date, dissociée par $actor de la Story $extra.'); -$lang->story->action->unlinkchildstory = array('main' => '$date, dissociée par $actor de la sous-Story $extra.'); - -/* Statistical statement. */ -$lang->story->report = new stdclass(); -$lang->story->report->common = 'Rapport'; -$lang->story->report->select = 'Choix Type de Rapport'; -$lang->story->report->create = 'Créer Rapport'; -$lang->story->report->value = 'Rapports'; - -$lang->story->report->charts['storysPerProduct'] = 'Regroupé par ' . $lang->productCommon . ' Story'; -$lang->story->report->charts['storysPerModule'] = 'Regroupé par Module Story'; -$lang->story->report->charts['storysPerSource'] = 'Regroupé par Source'; -$lang->story->report->charts['storysPerPlan'] = 'Regroupé par Plan'; -$lang->story->report->charts['storysPerStatus'] = 'Regroupé par Statut'; -$lang->story->report->charts['storysPerStage'] = 'Regroupé par Phase'; -$lang->story->report->charts['storysPerPri'] = 'Regroupé par Priorité'; -$lang->story->report->charts['storysPerEstimate'] = 'Regroupé par Estimés'; -$lang->story->report->charts['storysPerOpenedBy'] = 'Regroupé par Créateur'; -$lang->story->report->charts['storysPerAssignedTo'] = 'Regroupé par Détenteur'; -$lang->story->report->charts['storysPerClosedReason'] = 'Par Raison de Fermeture'; -$lang->story->report->charts['storysPerChange'] = 'Regroupé par Modif de Story'; - -$lang->story->report->options = new stdclass(); -$lang->story->report->options->graph = new stdclass(); -$lang->story->report->options->type = 'pie'; -$lang->story->report->options->width = 500; -$lang->story->report->options->height = 140; - -$lang->story->report->storysPerProduct = new stdclass(); -$lang->story->report->storysPerModule = new stdclass(); -$lang->story->report->storysPerSource = new stdclass(); -$lang->story->report->storysPerPlan = new stdclass(); -$lang->story->report->storysPerStatus = new stdclass(); -$lang->story->report->storysPerStage = new stdclass(); -$lang->story->report->storysPerPri = new stdclass(); -$lang->story->report->storysPerOpenedBy = new stdclass(); -$lang->story->report->storysPerAssignedTo = new stdclass(); -$lang->story->report->storysPerClosedReason = new stdclass(); -$lang->story->report->storysPerEstimate = new stdclass(); -$lang->story->report->storysPerChange = new stdclass(); - -$lang->story->report->storysPerProduct->item = $lang->productCommon; -$lang->story->report->storysPerModule->item = 'Module'; -$lang->story->report->storysPerSource->item = 'Source'; -$lang->story->report->storysPerPlan->item = 'Plan'; -$lang->story->report->storysPerStatus->item = 'Statut'; -$lang->story->report->storysPerStage->item = 'Phase'; -$lang->story->report->storysPerPri->item = 'Priorité'; -$lang->story->report->storysPerOpenedBy->item = 'Créateur'; -$lang->story->report->storysPerAssignedTo->item = 'Détenteur'; -$lang->story->report->storysPerClosedReason->item = 'Raison'; -$lang->story->report->storysPerEstimate->item = 'Estimation'; -$lang->story->report->storysPerChange->item = 'Chang. Story'; - -$lang->story->report->storysPerProduct->graph = new stdclass(); -$lang->story->report->storysPerModule->graph = new stdclass(); -$lang->story->report->storysPerSource->graph = new stdclass(); -$lang->story->report->storysPerPlan->graph = new stdclass(); -$lang->story->report->storysPerStatus->graph = new stdclass(); -$lang->story->report->storysPerStage->graph = new stdclass(); -$lang->story->report->storysPerPri->graph = new stdclass(); -$lang->story->report->storysPerOpenedBy->graph = new stdclass(); -$lang->story->report->storysPerAssignedTo->graph = new stdclass(); -$lang->story->report->storysPerClosedReason->graph = new stdclass(); -$lang->story->report->storysPerEstimate->graph = new stdclass(); -$lang->story->report->storysPerChange->graph = new stdclass(); - -$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; -$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; -$lang->story->report->storysPerSource->graph->xAxisName = 'Source'; -$lang->story->report->storysPerPlan->graph->xAxisName = 'Plan'; -$lang->story->report->storysPerStatus->graph->xAxisName = 'Statut'; -$lang->story->report->storysPerStage->graph->xAxisName = 'Phase'; -$lang->story->report->storysPerPri->graph->xAxisName = 'Priorité'; -$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Créateur'; -$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Détenteur'; -$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Raison Fermeture'; -$lang->story->report->storysPerEstimate->graph->xAxisName = 'Estimattion '; -$lang->story->report->storysPerChange->graph->xAxisName = 'Change Times'; - -$lang->story->placeholder = new stdclass(); -$lang->story->placeholder->estimate = $lang->story->hour; - -$lang->story->chosen = new stdClass(); -$lang->story->chosen->reviewedBy = 'Choisir valideur'; - -$lang->story->notice = new stdClass(); -$lang->story->notice->closed = 'La Story que vous avez sélectionnée est malheureusement fermée !'; -$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; - -$lang->story->convertToTask = new stdClass(); -$lang->story->convertToTask->fieldList = array(); -$lang->story->convertToTask->fieldList['module'] = 'Module'; -$lang->story->convertToTask->fieldList['spec'] = "Description"; -$lang->story->convertToTask->fieldList['pri'] = 'Priority'; -$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; -$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; - -$lang->story->categoryList['feature'] = 'Feature'; -$lang->story->categoryList['interface'] = 'Interface'; -$lang->story->categoryList['performance'] = 'Performance'; -$lang->story->categoryList['safe'] = 'Safe'; -$lang->story->categoryList['experience'] = 'Experience'; -$lang->story->categoryList['improve'] = 'Improve'; -$lang->story->categoryList['other'] = 'Other'; - -$lang->story->changeTip = 'Cette Story est en cours de vérification et ne peut pas être modifiée.'; - -$lang->story->reviewTip = array(); -$lang->story->reviewTip['active'] = "Cette Story est déjà active, il n'y a pas de demande de révision."; -$lang->story->reviewTip['notReviewer'] = "Vous n'êtes pas l'évaluateur de cette Story et ne pouvez pas effectuer d'opérations d'évaluation."; -$lang->story->reviewTip['reviewed'] = "Révisé"; -$lang->story->reviewTip['recalled'] = "Cette Story a été retirée pour vérification, et l'opération de révision ne peut pas être effectuée."; - -$lang->story->recallTip = array(); -$lang->story->recallTip['actived'] = "Aucun processus de révision n'a été lancé pour cette Story, et il n'est pas nécessaire de révoquer l'opération."; -$lang->story->recallTip['recalled'] = "Cette Story a été retirée pour vérification, et l'opération ne peut pas être annulée."; -$lang->story->recallTip['reviewed'] = "Il est entré dans le processus de révision, et l'opération ne peut pas être annulée."; -$lang->story->recallTip['notOpenedBy'] = "Vous n'êtes pas l'initiateur de la révision et vous ne pouvez pas annuler l'opération."; - -$lang->story->subDivideTip = array(); -$lang->story->subDivideTip['subStory'] = 'Les sous-stories ne peuvent pas être décomposées.'; -$lang->story->subDivideTip['planned'] = 'Cette Story est planifiée et ne peut être subdivisée.'; -$lang->story->subDivideTip['projected'] = 'Cette Story a été créée et ne peut être subdivisée.'; -$lang->story->subDivideTip['notActive'] = "Cette Story n'est pas active et ne peut être subdivisée."; diff --git a/module/story:q/lang/vi.php b/module/story:q/lang/vi.php deleted file mode 100644 index fcd4ee08ac..0000000000 --- a/module/story:q/lang/vi.php +++ /dev/null @@ -1,441 +0,0 @@ - - * @package story - * @version $Id: vi.php 5141 2013-07-15 05:57:15Z quocnho@gmail.com $ - * @link http://www.zentao.net - */ -global $config; -$lang->story->create = "Create Story"; - -$lang->story->requirement = zget($lang, 'UrCommon', "Requirement"); -$lang->story->story = zget($lang, 'SrCommon', "Story"); -$lang->story->createStory = 'Create ' . $lang->story->story; -$lang->story->createRequirement = 'Create ' . $lang->story->requirement; -$lang->story->affectedStories = "Affected {$lang->story->story}"; - -$lang->story->batchCreate = "Tạo hàng loạt"; -$lang->story->change = "Thay đổi"; -$lang->story->changed = "Đã đổi"; -$lang->story->assignTo = 'Giao cho'; -$lang->story->review = 'Xét duyệt'; -$lang->story->recall = 'Revoke'; -$lang->story->needReview = 'Duyệt nhu cầu'; -$lang->story->batchReview = 'Duyệt hàng loạt'; -$lang->story->edit = "Sửa câu chuyện"; -$lang->story->batchEdit = "Sửa hàng loạt"; -$lang->story->subdivide = 'Phân rã'; -$lang->story->link = 'Link'; -$lang->story->unlink = 'Unlink'; -$lang->story->track = 'Track'; -$lang->story->trackAB = 'Track'; -$lang->story->processStoryChange= 'Process Story Change'; -$lang->story->splitRequirent = 'Phân rã'; -$lang->story->close = 'Đóng'; -$lang->story->batchClose = 'Đóng hàng loạt'; -$lang->story->activate = 'Kích hoạt'; -$lang->story->delete = "Xóa"; -$lang->story->view = "Chi tiết câu chuyện"; -$lang->story->setting = "Thiết lập"; -$lang->story->tasks = "Nhiệm vụ liên kết"; -$lang->story->bugs = "Bugs liên kết"; -$lang->story->cases = "Tình huống liên kết"; -$lang->story->taskCount = 'Nhiệm vụ'; -$lang->story->bugCount = 'Bugs'; -$lang->story->caseCount = 'Tình huống'; -$lang->story->taskCountAB = 'T'; -$lang->story->bugCountAB = 'B'; -$lang->story->caseCountAB = 'C'; -$lang->story->linkStory = 'Liên kết câu chuyện'; -$lang->story->unlinkStory = 'Hủy liên kết'; -$lang->story->export = "Xuất dữ liệu"; -$lang->story->zeroCase = "Câu chuyện không tình huống"; -$lang->story->zeroTask = "Chỉ liệt kê câu chuyện không nhiệm vụ"; -$lang->story->reportChart = "Báo cáo"; -$lang->story->copyTitle = "Sao chép tiêu đề"; -$lang->story->batchChangePlan = "Thay đổi kế hoạch hàng loạt"; -$lang->story->batchChangeBranch = "Thay đổi chi nhánh hàng loạt"; -$lang->story->batchChangeStage = "Thay đổi giai đoạn hàng loạt"; -$lang->story->batchAssignTo = "Bàn giao hàng loạt"; -$lang->story->batchChangeModule = "Thay đổi Module hàng loạt"; -$lang->story->viewAll = "Xem tất cả"; -$lang->story->toTask = 'Convert to Task'; -$lang->story->batchToTask = 'Batch Convert to Task'; -$lang->story->convertRelations = 'Convert Relations'; -$lang->story->undetermined = 'undetermined'; -$lang->story->order = 'Order'; - -$lang->story->editAction = "Edit {$lang->SRCommon}"; -$lang->story->changeAction = "Change {$lang->SRCommon}"; -$lang->story->assignAction = "Assign {$lang->SRCommon}"; -$lang->story->reviewAction = "Review {$lang->SRCommon}"; -$lang->story->subdivideAction = "Subdivide {$lang->SRCommon}"; -$lang->story->closeAction = "Close {$lang->SRCommon}"; -$lang->story->activateAction = "Activate {$lang->SRCommon}"; -$lang->story->deleteAction = "Delete {$lang->SRCommon}"; -$lang->story->exportAction = "Export {$lang->SRCommon}"; -$lang->story->reportAction = "Report"; - -$lang->story->skipStory = '%s is a parent story. It cannot be closed.'; -$lang->story->closedStory = 'Story %s is closed and will not be closed.'; -$lang->story->batchToTaskTips = "This action will create a task with the same name as the selected {$lang->SRCommon} and link {$lang->SRCommon} to the task. The closed {$lang->SRCommon} will not be converted into tasks."; -$lang->story->successToTask = "Converted to task."; -$lang->story->storyRound = '%s time estimation'; - -$lang->story->id = 'ID'; -$lang->story->parent = 'Mẹ'; -$lang->story->product = $lang->productCommon; -$lang->story->project = 'Project'; -$lang->story->branch = "Branch/Platform"; -$lang->story->module = 'Module'; -$lang->story->moduleAB = 'Module'; -$lang->story->source = 'Từ'; -$lang->story->sourceNote = 'Ghi chú'; -$lang->story->fromBug = 'Từ Bug'; -$lang->story->title = 'Tiêu đề'; -$lang->story->type = "Story/Requirement"; -$lang->story->category = 'Category'; -$lang->story->color = 'Màu'; -$lang->story->toBug = 'Tới Bug'; -$lang->story->spec = 'Mô tả'; -$lang->story->assign = 'Giao cho'; -$lang->story->verify = 'Chấp thuận'; -$lang->story->pri = 'Ưu tiên'; -$lang->story->estimate = "{$lang->hourCommon} dự tính"; -$lang->story->estimateAB = 'DT.' . $lang->hourCommon == 'giờ' ? '(giờ)' : '(SP)'; -$lang->story->hour = $lang->hourCommon; -$lang->story->status = 'Tình trạng'; -$lang->story->subStatus = 'Tình trạng con'; -$lang->story->stage = 'Giai đoạn'; -$lang->story->stageAB = 'Giai đoạn'; -$lang->story->stagedBy = 'Thiết lập bởi'; -$lang->story->mailto = 'Mail tới'; -$lang->story->openedBy = 'Người tạo'; -$lang->story->openedDate = 'Ngày tạo'; -$lang->story->assignedTo = 'Giao cho'; -$lang->story->assignedDate = 'Ngày giao'; -$lang->story->lastEditedBy = 'Người sửa'; -$lang->story->lastEditedDate = 'Ngày sửa'; -$lang->story->closedBy = 'Người đóng'; -$lang->story->closedDate = 'Ngày đóng'; -$lang->story->closedReason = 'Lý do'; -$lang->story->rejectedReason = 'Lý do từ chối'; -$lang->story->reviewedBy = 'Người duyệt'; -$lang->story->reviewer = $lang->story->reviewedBy; -$lang->story->reviewers = 'Reviewers'; -$lang->story->reviewedDate = 'Ngày duyệt'; -$lang->story->activatedDate = 'Activated Date'; -$lang->story->version = 'Phiên bản'; -$lang->story->feedbackBy = 'From Name'; -$lang->story->notifyEmail = 'From Email'; -$lang->story->plan = 'Kế hoạch liên kết'; -$lang->story->planAB = 'Kế hoạch'; -$lang->story->comment = 'Nhận xét'; -$lang->story->children = "$lang->SRCommon} con"; -$lang->story->childrenAB = "C"; -$lang->story->linkStories = 'Câu chuyện liên kết'; -$lang->story->childStories = 'Câu chuyện được phân rã'; -$lang->story->duplicateStory = 'ID câu chuyện được nhân bản'; -$lang->story->reviewResult = 'Duyệt kết quả'; -$lang->story->preVersion = 'Phiên bản mới nhất'; -$lang->story->keywords = 'Tags'; -$lang->story->newStory = 'Tiếp tục thêm'; -$lang->story->colorTag = 'Màu'; -$lang->story->files = 'Files'; -$lang->story->copy = "Copy"; -$lang->story->total = "Tổng câu chuyện"; -$lang->story->draft = 'Nháp'; -$lang->story->unclosed = 'Chưa đóng'; -$lang->story->deleted = 'Đã xóa'; -$lang->story->released = 'Đã phát hành'; -$lang->story->URChanged = 'Requirement Changed'; -$lang->story->design = 'Designs'; -$lang->story->case = 'Cases'; -$lang->story->bug = 'Bugs'; -$lang->story->repoCommit = 'Commits'; -$lang->story->noRequirement = 'No Requirements'; -$lang->story->one = 'One'; -$lang->story->field = 'Synchronized fields'; -$lang->story->completeRate = 'Completion Rate'; -$lang->story->reviewed = 'Reviewed'; -$lang->story->toBeReviewed = 'To Be Reviewed'; -$lang->story->linkMR = 'Related MRs'; - -$lang->story->ditto = 'Như trên'; -$lang->story->dittoNotice = 'Câu chuyện này chưa liên kết tới cùng sản phẩm bởi bởi vì nó là cuối cùng!'; - -$lang->story->needNotReviewList[0] = 'Need Review'; -$lang->story->needNotReviewList[1] = 'Need Not Review'; - -$lang->story->useList[0] = 'Có'; -$lang->story->useList[1] = 'Không'; - -$lang->story->statusList[''] = ''; -$lang->story->statusList['draft'] = 'Nháp'; -$lang->story->statusList['active'] = 'Kích hoạt'; -$lang->story->statusList['closed'] = 'Đã đóng'; -$lang->story->statusList['changed'] = 'Đã thay đổi'; - -$lang->story->stageList[''] = ''; -$lang->story->stageList['wait'] = 'Đang đợi'; -$lang->story->stageList['planned'] = 'Kế hoạch'; -$lang->story->stageList['projected'] = 'Đã dự án'; -$lang->story->stageList['developing'] = 'Đang phát triển'; -$lang->story->stageList['developed'] = 'Đang phát triển'; -$lang->story->stageList['testing'] = 'Đang test'; -$lang->story->stageList['tested'] = 'Đã test'; -$lang->story->stageList['verified'] = 'Đã chấp thuận'; -$lang->story->stageList['released'] = 'Đã phát hành'; -$lang->story->stageList['closed'] = 'Đã đóng'; - -$lang->story->reasonList[''] = ''; -$lang->story->reasonList['done'] = 'Đã hoàn thành'; -$lang->story->reasonList['subdivided'] = 'Đã phân rã'; -$lang->story->reasonList['duplicate'] = 'Đã nhân bản'; -$lang->story->reasonList['postponed'] = 'Đã hoãn lại'; -$lang->story->reasonList['willnotdo'] = "Sẽ không làm"; -$lang->story->reasonList['cancel'] = 'Đã hủy'; -$lang->story->reasonList['bydesign'] = 'Như thiết kế'; -//$lang->story->reasonList['isbug'] = 'Bug!'; - -$lang->story->reviewResultList[''] = ''; -$lang->story->reviewResultList['pass'] = 'Đạt'; -$lang->story->reviewResultList['revert'] = 'Hoàn lại'; -$lang->story->reviewResultList['clarify'] = 'Đã làm rõ'; -$lang->story->reviewResultList['reject'] = 'Từ chối'; - -$lang->story->reviewList[0] = 'Không'; -$lang->story->reviewList[1] = 'Có'; - -$lang->story->sourceList[''] = ''; -$lang->story->sourceList['customer'] = 'Khách hàng'; -$lang->story->sourceList['user'] = 'Người dùng'; -$lang->story->sourceList['po'] = 'Sở hữu '.$lang->productCommon; -$lang->story->sourceList['market'] = 'Marketing'; -$lang->story->sourceList['service'] = 'Dịch vụ khách hàng'; -$lang->story->sourceList['operation'] = 'Vận hành'; -$lang->story->sourceList['support'] = 'Hỗ trợ kỹ thuật'; -$lang->story->sourceList['competitor'] = 'Đối thủ'; -$lang->story->sourceList['partner'] = 'Đối tác'; -$lang->story->sourceList['dev'] = 'Dev đội nhóm'; -$lang->story->sourceList['tester'] = 'Đội QA'; -$lang->story->sourceList['bug'] = 'Bug'; -$lang->story->sourceList['forum'] = 'Forum'; -$lang->story->sourceList['other'] = 'Khác'; - -$lang->story->priList[] = ''; -$lang->story->priList[1] = '1'; -$lang->story->priList[2] = '2'; -$lang->story->priList[3] = '3'; -$lang->story->priList[4] = '4'; - -$lang->story->changeList = array(); -$lang->story->changeList['no'] = 'Cancel'; -$lang->story->changeList['yes'] = 'Confirm'; - -$lang->story->legendBasicInfo = 'Thông tin cơ bản'; -$lang->story->legendLifeTime = 'Tổng quan'; -$lang->story->legendRelated = 'Thông tin liên quan'; -$lang->story->legendMailto = 'Mail tới'; -$lang->story->legendAttatch = 'Files'; -$lang->story->legendProjectAndTask = $lang->executionCommon . ' và nhiệm vụ'; -$lang->story->legendBugs = 'Bugs liên kết'; -$lang->story->legendFromBug = 'Từ Bug'; -$lang->story->legendCases = 'Tình huống liên kết'; -$lang->story->legendLinkStories = 'Câu chuyện liên kết'; -$lang->story->legendChildStories = 'Câu chuyện con'; -$lang->story->legendSpec = 'Mô tả'; -$lang->story->legendVerify = 'Chấp thuận'; -$lang->story->legendMisc = 'Khác'; -$lang->story->legendInformation = 'Story Information'; - -$lang->story->lblChange = "Đã đổi"; -$lang->story->lblReview = 'Xét duyệt'; -$lang->story->lblActivate = 'Kích hoạt'; -$lang->story->lblClose = 'Đóng'; -$lang->story->lblTBC = 'Nhiệm vụ/Bug/Tình huống'; - -$lang->story->checkAffection = 'Ảnh hưởng'; -$lang->story->affectedProjects = $config->systemMode == 'new' ? "{$lang->project->common}s/{$lang->execution->common}s" : "{$lang->project->common}s"; -$lang->story->affectedBugs = 'Bugs'; -$lang->story->affectedCases = 'Tình huống'; - -$lang->story->specTemplate = "Theo , Tôi muốn ra bởi đó là ."; -$lang->story->needNotReview = 'Không có xét duyệt được yêu cầu'; -$lang->story->successSaved = "Câu chuyện đã lưu lại!"; -$lang->story->confirmDelete = "Bạn có muốn xóa câu chuyện này?"; -$lang->story->confirmRecall = "Do you want to recall this story?"; -$lang->story->errorEmptyChildStory = '『Câu chuyện đã phân rã』 không thể để trống.'; -$lang->story->errorNotSubdivide = "Nếu tình trạng này là chưa kích hoạt, hoặc giai đoạn này chưa đợi, hoặc một câu chuyện con, nó không thể bị chia nhỏ."; -$lang->story->errorEmptyReviewedBy = "『ReviewedBy』 không thể để trống."; -$lang->story->mustChooseResult = 'Chọn kết quả'; -$lang->story->mustChoosePreVersion = 'Chọn một phiên bản để chuyển thành.'; -$lang->story->noStory = 'Không có câu chuyện nào'; -$lang->story->noRequirement = 'Không có câu chuyện nào'; -$lang->story->ignoreChangeStage = 'Story %s is in Draft or Closed status. Please review it..'; -$lang->story->cannotDeleteParent = "Không thể xóa {$lang->SRCommon} mẹ"; -$lang->story->moveChildrenTips = "Its Child {$lang->SRCommon} will be moved to the selected product when editing the linked product of Parent {$lang->SRCommon}."; -$lang->story->changeTips = 'The story associated with the requirements to change, click "Cancel" ignore this change, click "Confirm" to change the story.'; -$lang->story->estimateMustBeNumber = 'Estimate value must be number.'; -$lang->story->estimateMustBePlus = 'Estimated value cannot be negative'; -$lang->story->confirmChangeBranch = $lang->SRCommon . ' %s is linked to the plan of its linked branch. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan of its linked branch. Do you want to continue edit ' . $lang->SRCommon . '?'; -$lang->story->confirmChangePlan = $lang->SRCommon . ' %s is linked to the branch of its plan. If the branch is edited, ' . $lang->SRCommon . ' will be removed from the plan. Do you want to continue edit branch ?'; -$lang->story->errorDuplicateStory = $lang->SRCommon . '%s not exist'; - -$lang->story->form = new stdclass(); -$lang->story->form->area = 'Phạm vi'; -$lang->story->form->desc = 'Nó là câu chuyện gì? Điều gì chấp nhận được?'; -$lang->story->form->resource = 'Ai sẽ phân bổ nguồn lực? Bao lâu để thực hiện nó?'; -$lang->story->form->file = 'Nếu bất kỳ tập tin mà liên kết tới một câu chuyện, Vui lòng click tại đây để tải nó lên.'; - -$lang->story->action = new stdclass(); -$lang->story->action->reviewed = array('main' => '$date, ghi nhận bởi $actor. Kết quả là $extra.', 'extra' => 'reviewResultList'); -$lang->story->action->rejectreviewed = array('main' => '$date, ghi nhận bởi $actor. Kết quả là $extra. Lý do là $reason', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); -$lang->story->action->recalled = array('main' => '$date, recalled by $actor.'); -$lang->story->action->closed = array('main' => '$date, được đóng bởi $actor. Lý do là $extra $appendLink.', 'extra' => 'reasonList'); -$lang->story->action->reviewpassed = array('main' => '$date, determined by the System. The result is Pass.'); -$lang->story->action->reviewrejected = array('main' => '$date, closed by System. The reasion is Rejection.'); -$lang->story->action->reviewclarified = array('main' => '$date, determined by the System. The result is To Be Clarified. Please re-initiate the review after changing.'); -$lang->story->action->linked2plan = array('main' => '$date, liên kết bởi $actor tới kế hoạch $extra'); -$lang->story->action->unlinkedfromplan = array('main' => '$date, bị hủy bởi $actor từ kế hoạch $extra.'); -$lang->story->action->linked2execution = array('main' => '$date, liên kết bởi $actor tới ' . $lang->executionCommon . ' $extra.'); -$lang->story->action->unlinkedfromexecution = array('main' => '$date, bị hủy bởi $actor từ ' . $lang->executionCommon . ' $extra.'); -$lang->story->action->linked2kanban = array('main' => '$date, linked by $actor to Kanban $extra.'); -$lang->story->action->linked2project = array('main' => '$date, liên kết bởi $actor tớ project $extra.'); -$lang->story->action->unlinkedfromproject = array('main' => '$date, bị hủy bởi $actor từ project $extra.'); -$lang->story->action->linked2build = array('main' => '$date, liên kết bởi $actor tới Bản dựng $extra'); -$lang->story->action->unlinkedfrombuild = array('main' => '$date, bị hủy bởi $actor từ bản dựng $extra.'); -$lang->story->action->linked2release = array('main' => '$date, liên kết bởi $actor tới Phát hành $extra'); -$lang->story->action->unlinkedfromrelease = array('main' => '$date, bị hủy bởi $actor từ Phát hành $extra.'); -$lang->story->action->linkrelatedstory = array('main' => '$date, liên kết bởi $actor tới Câu chuyện $extra.'); -$lang->story->action->subdividestory = array('main' => '$date, được phân rã bởi $actor tới Câu chuyện $extra.'); -$lang->story->action->unlinkrelatedstory = array('main' => '$date, bị hủy bởi $actor từ Câu chuyện $extra.'); -$lang->story->action->unlinkchildstory = array('main' => '$date, bị hủy bởi $actor Câu chuyện được phân rã $extra.'); - -/* Statistical statement. */ -$lang->story->report = new stdclass(); -$lang->story->report->common = 'Báo cáo'; -$lang->story->report->select = 'Chọn loại báo cáo'; -$lang->story->report->create = 'Tạo báo cáo'; -$lang->story->report->value = 'Báo cáo'; - -$lang->story->report->charts['storysPerProduct'] = '' . $lang->productCommon . ' câu chuyện'; -$lang->story->report->charts['storysPerModule'] = 'Module câu chuyện'; -$lang->story->report->charts['storysPerSource'] = 'Nguồn câu chuyện'; -$lang->story->report->charts['storysPerPlan'] = 'kế hoạch'; -$lang->story->report->charts['storysPerStatus'] = 'tình trạng'; -$lang->story->report->charts['storysPerStage'] = 'Giai đoạn'; -$lang->story->report->charts['storysPerPri'] = 'ưu tiên'; -$lang->story->report->charts['storysPerEstimate'] = 'Dự tính'; -$lang->story->report->charts['storysPerOpenedBy'] = 'tạo bởi'; -$lang->story->report->charts['storysPerAssignedTo'] = 'giao cho'; -$lang->story->report->charts['storysPerClosedReason'] = 'Lý do đóng'; -$lang->story->report->charts['storysPerChange'] = 'Câu chuyện đã thay đổi'; - -$lang->story->report->options = new stdclass(); -$lang->story->report->options->graph = new stdclass(); -$lang->story->report->options->type = 'pie'; -$lang->story->report->options->width = 500; -$lang->story->report->options->height = 140; - -$lang->story->report->storysPerProduct = new stdclass(); -$lang->story->report->storysPerModule = new stdclass(); -$lang->story->report->storysPerSource = new stdclass(); -$lang->story->report->storysPerPlan = new stdclass(); -$lang->story->report->storysPerStatus = new stdclass(); -$lang->story->report->storysPerStage = new stdclass(); -$lang->story->report->storysPerPri = new stdclass(); -$lang->story->report->storysPerOpenedBy = new stdclass(); -$lang->story->report->storysPerAssignedTo = new stdclass(); -$lang->story->report->storysPerClosedReason = new stdclass(); -$lang->story->report->storysPerEstimate = new stdclass(); -$lang->story->report->storysPerChange = new stdclass(); - -$lang->story->report->storysPerProduct->item = $lang->productCommon; -$lang->story->report->storysPerModule->item = 'Module'; -$lang->story->report->storysPerSource->item = 'Nguồn'; -$lang->story->report->storysPerPlan->item = 'Kế hoạch'; -$lang->story->report->storysPerStatus->item = 'Tình trạng'; -$lang->story->report->storysPerStage->item = 'Giai đoạn'; -$lang->story->report->storysPerPri->item = 'Ưu tiên'; -$lang->story->report->storysPerOpenedBy->item = 'Mở bởi'; -$lang->story->report->storysPerAssignedTo->item = 'Giao cho'; -$lang->story->report->storysPerClosedReason->item = 'Lý do'; -$lang->story->report->storysPerEstimate->item = 'Dự tính'; -$lang->story->report->storysPerChange->item = 'Đã thay đổi câu chuyện'; - -$lang->story->report->storysPerProduct->graph = new stdclass(); -$lang->story->report->storysPerModule->graph = new stdclass(); -$lang->story->report->storysPerSource->graph = new stdclass(); -$lang->story->report->storysPerPlan->graph = new stdclass(); -$lang->story->report->storysPerStatus->graph = new stdclass(); -$lang->story->report->storysPerStage->graph = new stdclass(); -$lang->story->report->storysPerPri->graph = new stdclass(); -$lang->story->report->storysPerOpenedBy->graph = new stdclass(); -$lang->story->report->storysPerAssignedTo->graph = new stdclass(); -$lang->story->report->storysPerClosedReason->graph = new stdclass(); -$lang->story->report->storysPerEstimate->graph = new stdclass(); -$lang->story->report->storysPerChange->graph = new stdclass(); - -$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; -$lang->story->report->storysPerModule->graph->xAxisName = 'Module'; -$lang->story->report->storysPerSource->graph->xAxisName = 'Nguồn'; -$lang->story->report->storysPerPlan->graph->xAxisName = 'Kế hoạch'; -$lang->story->report->storysPerStatus->graph->xAxisName = 'Tình trạng'; -$lang->story->report->storysPerStage->graph->xAxisName = 'Giai đoạn'; -$lang->story->report->storysPerPri->graph->xAxisName = 'Ưu tiên'; -$lang->story->report->storysPerOpenedBy->graph->xAxisName = 'Người tạo'; -$lang->story->report->storysPerAssignedTo->graph->xAxisName = 'Giao cho'; -$lang->story->report->storysPerClosedReason->graph->xAxisName = 'Lý do đóng'; -$lang->story->report->storysPerEstimate->graph->xAxisName = 'Dự tính '; -$lang->story->report->storysPerChange->graph->xAxisName = 'Số lần thay đổi'; - -$lang->story->placeholder = new stdclass(); -$lang->story->placeholder->estimate = $lang->story->hour; - -$lang->story->chosen = new stdClass(); -$lang->story->chosen->reviewedBy = 'Chọn xét duyệt bởi'; - -$lang->story->notice = new stdClass(); -$lang->story->notice->closed = 'Câu chuyện mà bạn chọn đã đóng!'; -$lang->story->notice->reviewerNotEmpty = 'This requirement needs to be reviewed, and the reviewedby is required.'; - -$lang->story->convertToTask = new stdClass(); -$lang->story->convertToTask->fieldList = array(); -$lang->story->convertToTask->fieldList['module'] = 'Module'; -$lang->story->convertToTask->fieldList['spec'] = "Description"; -$lang->story->convertToTask->fieldList['pri'] = 'Priority'; -$lang->story->convertToTask->fieldList['mailto'] = 'Mailto'; -$lang->story->convertToTask->fieldList['assignedTo'] = 'AssignTo'; - -$lang->story->categoryList['feature'] = 'Feature'; -$lang->story->categoryList['interface'] = 'Interface'; -$lang->story->categoryList['performance'] = 'Performance'; -$lang->story->categoryList['safe'] = 'Safe'; -$lang->story->categoryList['experience'] = 'Experience'; -$lang->story->categoryList['improve'] = 'Improve'; -$lang->story->categoryList['other'] = 'Other'; - -$lang->story->changeTip = 'This Story is under review and cannot be changed.'; - -$lang->story->reviewTip = array(); -$lang->story->reviewTip['active'] = 'The Story is already active,no review requirements.'; -$lang->story->reviewTip['notReviewer'] = 'You are not the reviewer of this Story and cannot perform review operations.'; -$lang->story->reviewTip['reviewed'] = 'Reviewed'; -$lang->story->reviewTip['recalled'] = 'This Story has been withdrawn from review and cannot be reviewed.'; - -$lang->story->recallTip = array(); -$lang->story->recallTip['actived'] = 'The Story has not initiated a review process and no undo action is required.'; -$lang->story->recallTip['recalled'] = 'This Story has been withdrawn from review and cannot be cancelled.'; -$lang->story->recallTip['reviewed'] = 'The review process has been started and cannot be cancelled.'; -$lang->story->recallTip['notOpenedBy'] = 'You are not the Story creator and cannot cancel the operation.'; - -$lang->story->subDivideTip = array(); -$lang->story->subDivideTip['subStory'] = 'The Sub-stories cannot be subdivided.'; -$lang->story->subDivideTip['planned'] = 'The Story has been planned and cannot be subdivided.'; -$lang->story->subDivideTip['projected'] = 'The Story has been initiated and cannot be subdivided.'; -$lang->story->subDivideTip['notActive'] = 'The Story is not active and cannot be subdivided.'; diff --git a/module/story:q/lang/zh-cn.php b/module/story:q/lang/zh-cn.php deleted file mode 100644 index a55f5559d7..0000000000 --- a/module/story:q/lang/zh-cn.php +++ /dev/null @@ -1,441 +0,0 @@ - - * @package story - * @version $Id: zh-cn.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -global $config; -$lang->story->create = "提{$lang->SRCommon}"; - -$lang->story->requirement = zget($lang, 'URCommon', "需求"); -$lang->story->story = zget($lang, 'SRCommon', "故事"); -$lang->story->createStory = '添加' . $lang->story->story; -$lang->story->createRequirement = '添加' . $lang->story->requirement; -$lang->story->affectedStories = "影响的{$lang->story->story}"; - -$lang->story->batchCreate = "批量创建"; -$lang->story->change = "变更"; -$lang->story->changed = "{$lang->SRCommon}变更"; -$lang->story->assignTo = '指派'; -$lang->story->review = '评审'; -$lang->story->recall = '撤销评审'; -$lang->story->needReview = '需要评审'; -$lang->story->batchReview = '批量评审'; -$lang->story->edit = "编辑"; -$lang->story->batchEdit = "批量编辑"; -$lang->story->subdivide = '细分'; -$lang->story->link = '关联'; -$lang->story->unlink = '移除'; -$lang->story->track = '跟踪矩阵'; -$lang->story->trackAB = '矩阵'; -$lang->story->processStoryChange = '确认需求变动'; -$lang->story->splitRequirent = '拆分'; -$lang->story->close = '关闭'; -$lang->story->batchClose = '批量关闭'; -$lang->story->activate = '激活'; -$lang->story->delete = "删除"; -$lang->story->view = "{$lang->SRCommon}详情"; -$lang->story->setting = "设置"; -$lang->story->tasks = "相关任务"; -$lang->story->bugs = "相关Bug"; -$lang->story->cases = "相关用例"; -$lang->story->taskCount = '任务数'; -$lang->story->bugCount = 'Bug数'; -$lang->story->caseCount = '用例数'; -$lang->story->taskCountAB = 'T'; -$lang->story->bugCountAB = 'B'; -$lang->story->caseCountAB = 'C'; -$lang->story->linkStory = "关联{$lang->SRCommon}"; -$lang->story->unlinkStory = "移除相关{$lang->SRCommon}"; -$lang->story->export = "导出数据"; -$lang->story->zeroCase = "零用例{$lang->SRCommon}"; -$lang->story->zeroTask = "只列零任务{$lang->SRCommon}"; -$lang->story->reportChart = "统计报表"; -$lang->story->copyTitle = "同{$lang->SRCommon}名称"; -$lang->story->batchChangePlan = "批量修改计划"; -$lang->story->batchChangeBranch = "批量修改分支"; -$lang->story->batchChangeStage = "批量修改阶段"; -$lang->story->batchAssignTo = "批量指派"; -$lang->story->batchChangeModule = "批量修改模块"; -$lang->story->viewAll = '查看全部'; -$lang->story->toTask = '转任务'; -$lang->story->batchToTask = '批量转任务'; -$lang->story->convertRelations = '换算关系'; -$lang->story->undetermined = '待定'; -$lang->story->order = '排序'; - -$lang->story->editAction = "编辑{$lang->SRCommon}"; -$lang->story->changeAction = "变更{$lang->SRCommon}"; -$lang->story->assignAction = "指派{$lang->SRCommon}"; -$lang->story->reviewAction = "评审{$lang->SRCommon}"; -$lang->story->subdivideAction = "细分{$lang->SRCommon}"; -$lang->story->closeAction = "关闭{$lang->SRCommon}"; -$lang->story->activateAction = "激活{$lang->SRCommon}"; -$lang->story->deleteAction = "删除{$lang->SRCommon}"; -$lang->story->exportAction = "导出{$lang->SRCommon}"; -$lang->story->reportAction = "统计报表"; - -$lang->story->skipStory = '需求:%s 为父需求,将不会被关闭。'; -$lang->story->closedStory = '需求:%s 已关闭,将不会被关闭。'; -$lang->story->batchToTaskTips = "此操作会创建与所选{$lang->SRCommon}同名的任务,并将{$lang->SRCommon}关联到任务中,已关闭的需求不会转为任务。"; -$lang->story->successToTask = '批量转任务成功'; -$lang->story->storyRound = '第 %s 轮估算'; - -$lang->story->id = '编号'; -$lang->story->parent = '父需求'; -$lang->story->product = "所属{$lang->productCommon}"; -$lang->story->project = "所属项目"; -$lang->story->branch = "分支/平台"; -$lang->story->module = '所属模块'; -$lang->story->moduleAB = '模块'; -$lang->story->source = "来源"; -$lang->story->sourceNote = '来源备注'; -$lang->story->fromBug = '来源Bug'; -$lang->story->title = "{$lang->SRCommon}名称"; -$lang->story->type = "需求类型"; -$lang->story->category = "类别"; -$lang->story->color = '标题颜色'; -$lang->story->toBug = '转Bug'; -$lang->story->spec = "描述"; -$lang->story->assign = '指派给'; -$lang->story->verify = '验收标准'; -$lang->story->pri = '优先级'; -$lang->story->estimate = "预计{$lang->hourCommon}"; -$lang->story->estimateAB = '预计'; -$lang->story->hour = $lang->hourCommon; -$lang->story->status = '当前状态'; -$lang->story->subStatus = '子状态'; -$lang->story->stage = '所处阶段'; -$lang->story->stageAB = '阶段'; -$lang->story->stagedBy = '设置阶段者'; -$lang->story->mailto = '抄送给'; -$lang->story->openedBy = '由谁创建'; -$lang->story->openedDate = '创建日期'; -$lang->story->assignedTo = '指派给'; -$lang->story->assignedDate = '指派日期'; -$lang->story->lastEditedBy = '最后修改'; -$lang->story->lastEditedDate = '最后修改日期'; -$lang->story->closedBy = '由谁关闭'; -$lang->story->closedDate = '关闭日期'; -$lang->story->closedReason = '关闭原因'; -$lang->story->rejectedReason = '拒绝原因'; -$lang->story->reviewedBy = '由谁评审'; -$lang->story->reviewer = $lang->story->reviewedBy; -$lang->story->reviewers = '评审人员'; -$lang->story->reviewedDate = '评审时间'; -$lang->story->activatedDate = '激活日期'; -$lang->story->version = '版本号'; -$lang->story->feedbackBy = '反馈者'; -$lang->story->notifyEmail = '通知邮箱'; -$lang->story->plan = "所属计划"; -$lang->story->planAB = '计划'; -$lang->story->comment = '备注'; -$lang->story->children = "子需求"; -$lang->story->childrenAB = "子"; -$lang->story->linkStories = "相关{$lang->SRCommon}"; -$lang->story->childStories = "细分需求"; -$lang->story->duplicateStory = "重复需求ID"; -$lang->story->reviewResult = '评审意见'; -$lang->story->preVersion = '之前版本'; -$lang->story->keywords = '关键词'; -$lang->story->newStory = "继续添加{$lang->SRCommon}"; -$lang->story->colorTag = '颜色标签'; -$lang->story->files = '附件'; -$lang->story->copy = "复制{$lang->SRCommon}"; -$lang->story->total = "总{$lang->SRCommon}"; -$lang->story->draft = '草稿'; -$lang->story->unclosed = '未关闭'; -$lang->story->deleted = '已删除'; -$lang->story->released = "已发布{$lang->SRCommon}数"; -$lang->story->URChanged = '用需变更'; -$lang->story->design = '相关设计'; -$lang->story->case = '相关用例'; -$lang->story->bug = '相关Bug'; -$lang->story->repoCommit = '相关提交'; -$lang->story->noRequirement = '无需求'; -$lang->story->one = '一个'; -$lang->story->field = '同步的字段'; -$lang->story->completeRate = '完成率'; -$lang->story->reviewed = '已评审'; -$lang->story->toBeReviewed = '待评审'; -$lang->story->linkMR = '相关合并请求'; - -$lang->story->ditto = '同上'; -$lang->story->dittoNotice = "该{$lang->SRCommon}与上一{$lang->SRCommon}不属于同一产品!"; - -$lang->story->needNotReviewList[0] = '需要评审'; -$lang->story->needNotReviewList[1] = '不需要评审'; - -$lang->story->useList[0] = '不使用'; -$lang->story->useList[1] = '使用'; - -$lang->story->statusList[''] = ''; -$lang->story->statusList['draft'] = '草稿'; -$lang->story->statusList['active'] = '激活'; -$lang->story->statusList['closed'] = '已关闭'; -$lang->story->statusList['changed'] = '已变更'; - -$lang->story->stageList[''] = ''; -$lang->story->stageList['wait'] = '未开始'; -$lang->story->stageList['planned'] = "已计划"; -$lang->story->stageList['projected'] = '已立项'; -$lang->story->stageList['developing'] = '研发中'; -$lang->story->stageList['developed'] = '研发完毕'; -$lang->story->stageList['testing'] = '测试中'; -$lang->story->stageList['tested'] = '测试完毕'; -$lang->story->stageList['verified'] = '已验收'; -$lang->story->stageList['released'] = '已发布'; -$lang->story->stageList['closed'] = '已关闭'; - -$lang->story->reasonList[''] = ''; -$lang->story->reasonList['done'] = '已完成'; -$lang->story->reasonList['subdivided'] = '已细分'; -$lang->story->reasonList['duplicate'] = '重复'; -$lang->story->reasonList['postponed'] = '延期'; -$lang->story->reasonList['willnotdo'] = '不做'; -$lang->story->reasonList['cancel'] = '已取消'; -$lang->story->reasonList['bydesign'] = '设计如此'; -//$lang->story->reasonList['isbug'] = '是个Bug'; - -$lang->story->reviewResultList[''] = ''; -$lang->story->reviewResultList['pass'] = '确认通过'; -$lang->story->reviewResultList['revert'] = '撤销变更'; -$lang->story->reviewResultList['clarify'] = '有待明确'; -$lang->story->reviewResultList['reject'] = '拒绝'; - -$lang->story->reviewList[0] = '否'; -$lang->story->reviewList[1] = '是'; - -$lang->story->sourceList[''] = ''; -$lang->story->sourceList['customer'] = '客户'; -$lang->story->sourceList['user'] = '用户'; -$lang->story->sourceList['po'] = $lang->productCommon . '经理'; -$lang->story->sourceList['market'] = '市场'; -$lang->story->sourceList['service'] = '客服'; -$lang->story->sourceList['operation'] = '运营'; -$lang->story->sourceList['support'] = '技术支持'; -$lang->story->sourceList['competitor'] = '竞争对手'; -$lang->story->sourceList['partner'] = '合作伙伴'; -$lang->story->sourceList['dev'] = '开发人员'; -$lang->story->sourceList['tester'] = '测试人员'; -$lang->story->sourceList['bug'] = 'Bug'; -$lang->story->sourceList['forum'] = '论坛'; -$lang->story->sourceList['other'] = '其他'; - -$lang->story->priList[] = ''; -$lang->story->priList[1] = '1'; -$lang->story->priList[2] = '2'; -$lang->story->priList[3] = '3'; -$lang->story->priList[4] = '4'; - -$lang->story->changeList = array(); -$lang->story->changeList['no'] = '不变更'; -$lang->story->changeList['yes'] = '变更'; - -$lang->story->legendBasicInfo = '基本信息'; -$lang->story->legendLifeTime = "需求的一生"; -$lang->story->legendRelated = '相关信息'; -$lang->story->legendMailto = '抄送给'; -$lang->story->legendAttatch = '附件'; -$lang->story->legendProjectAndTask = $lang->executionCommon . '任务'; -$lang->story->legendBugs = '相关Bug'; -$lang->story->legendFromBug = '来源Bug'; -$lang->story->legendCases = '相关用例'; -$lang->story->legendLinkStories = "相关{$lang->SRCommon}"; -$lang->story->legendChildStories = "细分{$lang->SRCommon}"; -$lang->story->legendSpec = "需求描述"; -$lang->story->legendVerify = '验收标准'; -$lang->story->legendMisc = '其他相关'; -$lang->story->legendInformation = '需求信息'; - -$lang->story->lblChange = "变更{$lang->SRCommon}"; -$lang->story->lblReview = "评审{$lang->SRCommon}"; -$lang->story->lblActivate = "激活{$lang->SRCommon}"; -$lang->story->lblClose = "关闭{$lang->SRCommon}"; -$lang->story->lblTBC = '任务Bug用例'; - -$lang->story->checkAffection = '影响范围'; -$lang->story->affectedProjects = $config->systemMode == 'new' ? "影响的{$lang->project->common}或{$lang->execution->common}" : "影响的{$lang->project->common}"; -$lang->story->affectedBugs = '影响的Bug'; -$lang->story->affectedCases = '影响的用例'; - -$lang->story->specTemplate = "建议参考的模板:作为一名<某种类型的用户>,我希望<达成某些目的>,这样可以<开发的价值>。"; -$lang->story->needNotReview = '不需要评审'; -$lang->story->successSaved = "{$lang->SRCommon}成功添加,"; -$lang->story->confirmDelete = "您确认删除该{$lang->SRCommon}吗?"; -$lang->story->confirmRecall = "您确认撤销该{$lang->SRCommon}吗?"; -$lang->story->errorEmptyChildStory = "『细分{$lang->SRCommon}』不能为空。"; -$lang->story->errorNotSubdivide = "状态不是激活,或者阶段不是未开始的{$lang->SRCommon},或者是子需求,则不能细分。"; -$lang->story->errorEmptyReviewedBy = "『由谁评审』不能为空。"; -$lang->story->mustChooseResult = '必须选择评审意见'; -$lang->story->mustChoosePreVersion = '必须选择回溯的版本'; -$lang->story->noStory = "暂时没有{$lang->SRCommon}。"; -$lang->story->noRequirement = "暂时没有{$lang->URCommon}。"; -$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 为草稿状态或已关闭状态,没有修改其阶段。"; -$lang->story->cannotDeleteParent = "不能删除父{$lang->SRCommon}"; -$lang->story->moveChildrenTips = "修改父{$lang->SRCommon}的所属产品会将其下的子{$lang->SRCommon}也移动到所选产品下。"; -$lang->story->changeTips = '该软件需求关联的用户需求有变更,点击“不变更”忽略此条变更,点击“变更”来进行该软件需求的变更。'; -$lang->story->estimateMustBeNumber = '估算值必须是数字'; -$lang->story->estimateMustBePlus = '估算值不能是负数'; -$lang->story->confirmChangeBranch = $lang->SRCommon . '%s已关联在之前所属分支的计划中,调整分支后,' . $lang->SRCommon . '将从之前所属分支的计划中移除,请确认是否继续修改上述' . $lang->SRCommon . '的分支。'; -$lang->story->confirmChangePlan = $lang->SRCommon . '%s已关联在之前计划的所属分支中,调整分支后,' . $lang->SRCommon . '将会从计划中移除,请确认是否继续修改计划的所属分支。'; -$lang->story->errorDuplicateStory = $lang->SRCommon . '%s不存在'; - -$lang->story->form = new stdclass(); -$lang->story->form->area = "该{$lang->SRCommon}所属范围"; -$lang->story->form->desc = "描述及标准,什么{$lang->SRCommon}?如何验收?"; -$lang->story->form->resource = '资源分配,有谁完成?需要多少时间?'; -$lang->story->form->file = "附件,如果该{$lang->SRCommon}有相关文件,请点此上传。"; - -$lang->story->action = new stdclass(); -$lang->story->action->reviewed = array('main' => '$date, 由 $actor 记录评审意见,评审意见为 $extra。', 'extra' => 'reviewResultList'); -$lang->story->action->rejectreviewed = array('main' => '$date, 由 $actor 记录评审意见,评审意见为 $extra,原因为 $reason。', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); -$lang->story->action->recalled = array('main' => '$date, 由 $actor 撤销评审。'); -$lang->story->action->closed = array('main' => '$date, 由 $actor 关闭,原因为 $extra $appendLink。', 'extra' => 'reasonList'); -$lang->story->action->reviewpassed = array('main' => '$date, 由 系统 判定,结果为 确认通过。'); -$lang->story->action->reviewrejected = array('main' => '$date, 由 系统 关闭,原因为 拒绝。'); -$lang->story->action->reviewclarified = array('main' => '$date, 由 系统 判定,结果为 有待明确,请变更后重新发起评审。'); -$lang->story->action->linked2plan = array('main' => '$date, 由 $actor 关联到计划 $extra。'); -$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 从计划 $extra 移除。'); -$lang->story->action->linked2execution = array('main' => '$date, 由 $actor 关联到' . $lang->executionCommon . ' $extra。'); -$lang->story->action->unlinkedfromexecution = array('main' => '$date, 由 $actor 从' . $lang->executionCommon . ' $extra 移除。'); -$lang->story->action->linked2kanban = array('main' => '$date, 由 $actor 关联到看板 $extra。'); -$lang->story->action->linked2project = array('main' => '$date, 由 $actor 关联到项目 $extra。'); -$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 从项目 $extra 移除。'); -$lang->story->action->linked2build = array('main' => '$date, 由 $actor 关联到版本 $extra。'); -$lang->story->action->unlinkedfrombuild = array('main' => '$date, 由 $actor 从版本 $extra 移除。'); -$lang->story->action->linked2release = array('main' => '$date, 由 $actor 关联到发布 $extra。'); -$lang->story->action->unlinkedfromrelease = array('main' => '$date, 由 $actor 从发布 $extra 移除。'); -$lang->story->action->linkrelatedstory = array('main' => "\$date, 由 \$actor 关联相关{$lang->SRCommon} \$extra。"); -$lang->story->action->subdividestory = array('main' => "\$date, 由 \$actor 细分为{$lang->SRCommon} \$extra。"); -$lang->story->action->unlinkrelatedstory = array('main' => "\$date, 由 \$actor 移除相关{$lang->SRCommon} \$extra。"); -$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 \$actor 移除细分{$lang->SRCommon} \$extra。"); - -/* 统计报表。*/ -$lang->story->report = new stdclass(); -$lang->story->report->common = '报表'; -$lang->story->report->select = '请选择报表类型'; -$lang->story->report->create = '生成报表'; -$lang->story->report->value = "需求数"; - -$lang->story->report->charts['storysPerProduct'] = $lang->productCommon . "{$lang->SRCommon}数量"; -$lang->story->report->charts['storysPerModule'] = "模块{$lang->SRCommon}数量"; -$lang->story->report->charts['storysPerSource'] = "按{$lang->SRCommon}来源统计"; -$lang->story->report->charts['storysPerPlan'] = "按计划进行统计"; -$lang->story->report->charts['storysPerStatus'] = '按状态进行统计'; -$lang->story->report->charts['storysPerStage'] = '按所处阶段进行统计'; -$lang->story->report->charts['storysPerPri'] = '按优先级进行统计'; -$lang->story->report->charts['storysPerEstimate'] = "按预计{$lang->hourCommon}进行统计"; -$lang->story->report->charts['storysPerOpenedBy'] = '按由谁创建来进行统计'; -$lang->story->report->charts['storysPerAssignedTo'] = '按当前指派来进行统计'; -$lang->story->report->charts['storysPerClosedReason'] = '按关闭原因来进行统计'; -$lang->story->report->charts['storysPerChange'] = '按变更次数来进行统计'; - -$lang->story->report->options = new stdclass(); -$lang->story->report->options->graph = new stdclass(); -$lang->story->report->options->type = 'pie'; -$lang->story->report->options->width = 500; -$lang->story->report->options->height = 140; - -$lang->story->report->storysPerProduct = new stdclass(); -$lang->story->report->storysPerModule = new stdclass(); -$lang->story->report->storysPerSource = new stdclass(); -$lang->story->report->storysPerPlan = new stdclass(); -$lang->story->report->storysPerStatus = new stdclass(); -$lang->story->report->storysPerStage = new stdclass(); -$lang->story->report->storysPerPri = new stdclass(); -$lang->story->report->storysPerOpenedBy = new stdclass(); -$lang->story->report->storysPerAssignedTo = new stdclass(); -$lang->story->report->storysPerClosedReason = new stdclass(); -$lang->story->report->storysPerEstimate = new stdclass(); -$lang->story->report->storysPerChange = new stdclass(); - -$lang->story->report->storysPerProduct->item = $lang->productCommon; -$lang->story->report->storysPerModule->item = '模块'; -$lang->story->report->storysPerSource->item = '来源'; -$lang->story->report->storysPerPlan->item = '计划'; -$lang->story->report->storysPerStatus->item = '状态'; -$lang->story->report->storysPerStage->item = '阶段'; -$lang->story->report->storysPerPri->item = '优先级'; -$lang->story->report->storysPerOpenedBy->item = '由谁创建'; -$lang->story->report->storysPerAssignedTo->item = '指派给'; -$lang->story->report->storysPerClosedReason->item = '原因'; -$lang->story->report->storysPerEstimate->item = "预计{$lang->hourCommon}"; -$lang->story->report->storysPerChange->item = '变更次数'; - -$lang->story->report->storysPerProduct->graph = new stdclass(); -$lang->story->report->storysPerModule->graph = new stdclass(); -$lang->story->report->storysPerSource->graph = new stdclass(); -$lang->story->report->storysPerPlan->graph = new stdclass(); -$lang->story->report->storysPerStatus->graph = new stdclass(); -$lang->story->report->storysPerStage->graph = new stdclass(); -$lang->story->report->storysPerPri->graph = new stdclass(); -$lang->story->report->storysPerOpenedBy->graph = new stdclass(); -$lang->story->report->storysPerAssignedTo->graph = new stdclass(); -$lang->story->report->storysPerClosedReason->graph = new stdclass(); -$lang->story->report->storysPerEstimate->graph = new stdclass(); -$lang->story->report->storysPerChange->graph = new stdclass(); - -$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; -$lang->story->report->storysPerModule->graph->xAxisName = '模块'; -$lang->story->report->storysPerSource->graph->xAxisName = '来源'; -$lang->story->report->storysPerPlan->graph->xAxisName = '计划'; -$lang->story->report->storysPerStatus->graph->xAxisName = '状态'; -$lang->story->report->storysPerStage->graph->xAxisName = '所处阶段'; -$lang->story->report->storysPerPri->graph->xAxisName = '优先级'; -$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由谁创建'; -$lang->story->report->storysPerAssignedTo->graph->xAxisName = '当前指派'; -$lang->story->report->storysPerClosedReason->graph->xAxisName = '关闭原因'; -$lang->story->report->storysPerEstimate->graph->xAxisName = '预计时间'; -$lang->story->report->storysPerChange->graph->xAxisName = '变更次数'; - -$lang->story->placeholder = new stdclass(); -$lang->story->placeholder->estimate = $lang->story->hour; - -$lang->story->chosen = new stdClass(); -$lang->story->chosen->reviewedBy = '选择评审人...'; - -$lang->story->notice = new stdClass(); -$lang->story->notice->closed = "您选择的{$lang->SRCommon}已经被关闭了!"; -$lang->story->notice->reviewerNotEmpty = '该需求需要评审,评审人员不能为空。'; - -$lang->story->convertToTask = new stdClass(); -$lang->story->convertToTask->fieldList = array(); -$lang->story->convertToTask->fieldList['module'] = '所属模块'; -$lang->story->convertToTask->fieldList['spec'] = "{$lang->SRCommon}描述"; -$lang->story->convertToTask->fieldList['pri'] = '优先级'; -$lang->story->convertToTask->fieldList['mailto'] = '抄送给'; -$lang->story->convertToTask->fieldList['assignedTo'] = '指派给'; - -$lang->story->categoryList['feature'] = '功能'; -$lang->story->categoryList['interface'] = '接口'; -$lang->story->categoryList['performance'] = '性能'; -$lang->story->categoryList['safe'] = '安全'; -$lang->story->categoryList['experience'] = '体验'; -$lang->story->categoryList['improve'] = '改进'; -$lang->story->categoryList['other'] = '其他'; - -$lang->story->changeTip = '该需求正在评审中,无法进行变更'; - -$lang->story->reviewTip = array(); -$lang->story->reviewTip['active'] = '该需求已是激活状态,无需评审'; -$lang->story->reviewTip['notReviewer'] = '您不是该需求的评审人员,无法进行评审操作'; -$lang->story->reviewTip['reviewed'] = '您已评审'; -$lang->story->reviewTip['recalled'] = '该需求已撤销评审,无法进行评审操作'; - -$lang->story->recallTip = array(); -$lang->story->recallTip['actived'] = '该需求未发起评审流程,无需撤销操作'; -$lang->story->recallTip['recalled'] = '该需求已经撤销评审,无法进行撤销操作'; -$lang->story->recallTip['reviewed'] = '已进入评审流程,无法进行撤销操作'; -$lang->story->recallTip['notOpenedBy'] = '您不是需求创建者,无法进行撤销操作'; - -$lang->story->subDivideTip = array(); -$lang->story->subDivideTip['subStory'] = '子需求无法细分'; -$lang->story->subDivideTip['planned'] = '该需求已计划,无法进行细分操作'; -$lang->story->subDivideTip['projected'] = '该需求已立项,无法进行细分操作'; -$lang->story->subDivideTip['notActive'] = '需求不是激活状态,无法进行细分操作'; diff --git a/module/story:q/lang/zh-tw.php b/module/story:q/lang/zh-tw.php deleted file mode 100644 index ae595fa813..0000000000 --- a/module/story:q/lang/zh-tw.php +++ /dev/null @@ -1,416 +0,0 @@ - - * @package story - * @version $Id: zh-tw.php 5141 2013-07-15 05:57:15Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -global $config; -$lang->story->create = "提{$lang->SRCommon}"; - -$lang->story->requirement = zget($lang, 'URCommon', "需求"); -$lang->story->story = zget($lang, 'SRCommon', "故事"); -$lang->story->createStory = '添加' . $lang->story->story; -$lang->story->createRequirement = '添加' . $lang->story->requirement; -$lang->story->affectedStories = "影響的{$lang->story->story}"; - -$lang->story->batchCreate = "批量創建"; -$lang->story->change = "變更"; -$lang->story->changed = "{$lang->SRCommon}變更"; -$lang->story->assignTo = '指派'; -$lang->story->review = '評審'; -$lang->story->recall = '撤銷評審'; -$lang->story->needReview = '需要評審'; -$lang->story->batchReview = '批量評審'; -$lang->story->edit = "編輯"; -$lang->story->batchEdit = "批量編輯"; -$lang->story->subdivide = '細分'; -$lang->story->link = '關聯'; -$lang->story->unlink = '移除'; -$lang->story->track = '跟蹤矩陣'; -$lang->story->trackAB = '矩陣'; -$lang->story->processStoryChange = '處理需求變更'; -$lang->story->splitRequirent = '拆分'; -$lang->story->close = '關閉'; -$lang->story->batchClose = '批量關閉'; -$lang->story->activate = '激活'; -$lang->story->delete = "刪除"; -$lang->story->view = "{$lang->SRCommon}詳情"; -$lang->story->setting = "設置"; -$lang->story->tasks = "相關任務"; -$lang->story->bugs = "相關Bug"; -$lang->story->cases = "相關用例"; -$lang->story->taskCount = '任務數'; -$lang->story->bugCount = 'Bug數'; -$lang->story->caseCount = '用例數'; -$lang->story->taskCountAB = 'T'; -$lang->story->bugCountAB = 'B'; -$lang->story->caseCountAB = 'C'; -$lang->story->linkStory = "關聯{$lang->SRCommon}"; -$lang->story->unlinkStory = "移除相關{$lang->SRCommon}"; -$lang->story->export = "導出數據"; -$lang->story->zeroCase = "零用例{$lang->SRCommon}"; -$lang->story->zeroTask = "只列零任務{$lang->SRCommon}"; -$lang->story->reportChart = "統計報表"; -$lang->story->copyTitle = "同{$lang->SRCommon}名稱"; -$lang->story->batchChangePlan = "批量修改計劃"; -$lang->story->batchChangeBranch = "批量修改分支"; -$lang->story->batchChangeStage = "批量修改階段"; -$lang->story->batchAssignTo = "批量指派"; -$lang->story->batchChangeModule = "批量修改模組"; -$lang->story->viewAll = '查看全部'; -$lang->story->toTask = '轉任務'; -$lang->story->batchToTask = '批量轉任務'; -$lang->story->convertRelations = '換算關係'; - -$lang->story->editAction = "編輯{$lang->SRCommon}"; -$lang->story->changeAction = "變更{$lang->SRCommon}"; -$lang->story->assignAction = "指派{$lang->SRCommon}"; -$lang->story->reviewAction = "評審{$lang->SRCommon}"; -$lang->story->subdivideAction = "細分{$lang->SRCommon}"; -$lang->story->closeAction = "關閉{$lang->SRCommon}"; -$lang->story->activateAction = "激活{$lang->SRCommon}"; -$lang->story->deleteAction = "刪除{$lang->SRCommon}"; -$lang->story->exportAction = "導出{$lang->SRCommon}"; -$lang->story->reportAction = "統計報表"; - -$lang->story->skipStory = '需求:%s 為父需求,將不會被關閉。'; -$lang->story->closedStory = '需求:%s 已關閉,將不會被關閉。'; -$lang->story->batchToTaskTips = "此操作會創建與所選{$lang->SRCommon}同名的任務,並將{$lang->SRCommon}關聯到任務中,已關閉的需求不會轉為任務。"; -$lang->story->successToTask = '批量轉任務成功'; -$lang->story->storyRound = '第 %s 輪估算'; - -$lang->story->common = $lang->SRCommon; -$lang->story->id = '編號'; -$lang->story->parent = '父需求'; -$lang->story->product = "所屬{$lang->productCommon}"; -$lang->story->project = "所屬項目"; -$lang->story->branch = "分支/平台"; -$lang->story->module = '所屬模組'; -$lang->story->moduleAB = '模組'; -$lang->story->source = "來源"; -$lang->story->sourceNote = '來源備註'; -$lang->story->fromBug = '來源Bug'; -$lang->story->title = "{$lang->SRCommon}名稱"; -$lang->story->type = "需求類型"; -$lang->story->category = "類型"; -$lang->story->color = '標題顏色'; -$lang->story->toBug = '轉Bug'; -$lang->story->spec = "描述"; -$lang->story->assign = '指派給'; -$lang->story->verify = '驗收標準'; -$lang->story->pri = '優先順序'; -$lang->story->estimate = "預計{$lang->hourCommon}"; -$lang->story->estimateAB = '預計'; -$lang->story->hour = $lang->hourCommon; -$lang->story->status = '當前狀態'; -$lang->story->subStatus = '子狀態'; -$lang->story->stage = '所處階段'; -$lang->story->stageAB = '階段'; -$lang->story->stagedBy = '設置階段者'; -$lang->story->mailto = '抄送給'; -$lang->story->openedBy = '由誰創建'; -$lang->story->openedDate = '創建日期'; -$lang->story->assignedTo = '指派給'; -$lang->story->assignedDate = '指派日期'; -$lang->story->lastEditedBy = '最後修改'; -$lang->story->lastEditedDate = '最後修改日期'; -$lang->story->closedBy = '由誰關閉'; -$lang->story->closedDate = '關閉日期'; -$lang->story->closedReason = '關閉原因'; -$lang->story->rejectedReason = '拒絶原因'; -$lang->story->reviewedBy = '由誰評審'; -$lang->story->reviewers = '評審人員'; -$lang->story->reviewedDate = '評審時間'; -$lang->story->activatedDate = '激活日期'; -$lang->story->version = '版本號'; -$lang->story->feedbackBy = '反饋者'; -$lang->story->notifyEmail = '通知郵箱'; -$lang->story->plan = "所屬計劃"; -$lang->story->planAB = '計劃'; -$lang->story->comment = '備註'; -$lang->story->children = "子需求"; -$lang->story->childrenAB = "子"; -$lang->story->linkStories = "相關{$lang->SRCommon}"; -$lang->story->childStories = "細分需求"; -$lang->story->duplicateStory = "重複需求ID"; -$lang->story->reviewResult = '評審意見'; -$lang->story->preVersion = '之前版本'; -$lang->story->keywords = '關鍵詞'; -$lang->story->newStory = "繼續添加{$lang->SRCommon}"; -$lang->story->colorTag = '顏色標籤'; -$lang->story->files = '附件'; -$lang->story->copy = "複製{$lang->SRCommon}"; -$lang->story->total = "總{$lang->SRCommon}"; -$lang->story->allStories = "所有{$lang->SRCommon}"; -$lang->story->draft = '草稿'; -$lang->story->unclosed = '未關閉'; -$lang->story->deleted = '已刪除'; -$lang->story->released = "已發佈{$lang->SRCommon}數"; -$lang->story->URChanged = '用需變更'; -$lang->story->design = '相關設計'; -$lang->story->case = '相關用例'; -$lang->story->bug = '相關Bug'; -$lang->story->repoCommit = '相關提交'; -$lang->story->noRequirement = '無需求'; -$lang->story->one = '一個'; -$lang->story->field = '同步的欄位'; -$lang->story->completeRate = '完成率'; -$lang->story->reviewed = '已評審'; -$lang->story->toBeReviewed = '待評審'; - -$lang->story->ditto = '同上'; -$lang->story->dittoNotice = "該{$lang->SRCommon}與上一{$lang->SRCommon}不屬於同一產品!"; - -$lang->story->needNotReviewList[0] = '需要評審'; -$lang->story->needNotReviewList[1] = '不需要評審'; - -$lang->story->useList[0] = '不使用'; -$lang->story->useList[1] = '使用'; - -$lang->story->statusList[''] = ''; -$lang->story->statusList['draft'] = '草稿'; -$lang->story->statusList['active'] = '激活'; -$lang->story->statusList['closed'] = '已關閉'; -$lang->story->statusList['changed'] = '已變更'; - -$lang->story->stageList[''] = ''; -$lang->story->stageList['wait'] = '未開始'; -$lang->story->stageList['planned'] = "已計劃"; -$lang->story->stageList['projected'] = '已立項'; -$lang->story->stageList['developing'] = '研發中'; -$lang->story->stageList['developed'] = '研發完畢'; -$lang->story->stageList['testing'] = '測試中'; -$lang->story->stageList['tested'] = '測試完畢'; -$lang->story->stageList['verified'] = '已驗收'; -$lang->story->stageList['released'] = '已發佈'; -$lang->story->stageList['closed'] = '已關閉'; - -$lang->story->reasonList[''] = ''; -$lang->story->reasonList['done'] = '已完成'; -$lang->story->reasonList['subdivided'] = '已細分'; -$lang->story->reasonList['duplicate'] = '重複'; -$lang->story->reasonList['postponed'] = '延期'; -$lang->story->reasonList['willnotdo'] = '不做'; -$lang->story->reasonList['cancel'] = '已取消'; -$lang->story->reasonList['bydesign'] = '設計如此'; -//$lang->story->reasonList['isbug'] = '是個Bug'; - -$lang->story->reviewResultList[''] = ''; -$lang->story->reviewResultList['pass'] = '確認通過'; -$lang->story->reviewResultList['revert'] = '撤銷變更'; -$lang->story->reviewResultList['clarify'] = '有待明確'; -$lang->story->reviewResultList['reject'] = '拒絶'; - -$lang->story->reviewList[0] = '否'; -$lang->story->reviewList[1] = '是'; - -$lang->story->sourceList[''] = ''; -$lang->story->sourceList['customer'] = '客戶'; -$lang->story->sourceList['user'] = '用戶'; -$lang->story->sourceList['po'] = $lang->productCommon . '經理'; -$lang->story->sourceList['market'] = '市場'; -$lang->story->sourceList['service'] = '客服'; -$lang->story->sourceList['operation'] = '運營'; -$lang->story->sourceList['support'] = '技術支持'; -$lang->story->sourceList['competitor'] = '競爭對手'; -$lang->story->sourceList['partner'] = '合作夥伴'; -$lang->story->sourceList['dev'] = '開發人員'; -$lang->story->sourceList['tester'] = '測試人員'; -$lang->story->sourceList['bug'] = 'Bug'; -$lang->story->sourceList['forum'] = '論壇'; -$lang->story->sourceList['other'] = '其他'; - -$lang->story->priList[] = ''; -$lang->story->priList[1] = '1'; -$lang->story->priList[2] = '2'; -$lang->story->priList[3] = '3'; -$lang->story->priList[4] = '4'; - -$lang->story->changeList = array(); -$lang->story->changeList['no'] = '不變更'; -$lang->story->changeList['yes'] = '變更'; - -$lang->story->legendBasicInfo = '基本信息'; -$lang->story->legendLifeTime = "需求的一生"; -$lang->story->legendRelated = '相關信息'; -$lang->story->legendMailto = '抄送給'; -$lang->story->legendAttatch = '附件'; -$lang->story->legendProjectAndTask = $lang->executionCommon . '任務'; -$lang->story->legendBugs = '相關Bug'; -$lang->story->legendFromBug = '來源Bug'; -$lang->story->legendCases = '相關用例'; -$lang->story->legendLinkStories = "相關{$lang->SRCommon}"; -$lang->story->legendChildStories = "細分{$lang->SRCommon}"; -$lang->story->legendSpec = "需求描述"; -$lang->story->legendVerify = '驗收標準'; -$lang->story->legendMisc = '其他相關'; -$lang->story->legendInformation = '需求信息'; - -$lang->story->lblChange = "變更{$lang->SRCommon}"; -$lang->story->lblReview = "評審{$lang->SRCommon}"; -$lang->story->lblActivate = "激活{$lang->SRCommon}"; -$lang->story->lblClose = "關閉{$lang->SRCommon}"; -$lang->story->lblTBC = '任務Bug用例'; - -$lang->story->checkAffection = '影響範圍'; -$lang->story->affectedProjects = $config->systemMode == 'new' ? "影響的{$lang->project->common}或{$lang->execution->common}" : "影響的{$lang->project->common}"; -$lang->story->affectedBugs = '影響的Bug'; -$lang->story->affectedCases = '影響的用例'; - -$lang->story->specTemplate = "建議參考的模板:作為一名<某種類型的用戶>,我希望<達成某些目的>,這樣可以<開發的價值>。"; -$lang->story->needNotReview = '不需要評審'; -$lang->story->successSaved = "{$lang->SRCommon}成功添加,"; -$lang->story->confirmDelete = "您確認刪除該{$lang->SRCommon}嗎?"; -$lang->story->errorEmptyChildStory = "『細分{$lang->SRCommon}』不能為空。"; -$lang->story->errorNotSubdivide = "狀態不是激活,或者階段不是未開始的{$lang->SRCommon},或者是子需求,則不能細分。"; -$lang->story->errorEmptyReviewedBy = "『由誰評審』不能為空。"; -$lang->story->mustChooseResult = '必須選擇評審意見'; -$lang->story->mustChoosePreVersion = '必須選擇回溯的版本'; -$lang->story->noStory = "暫時沒有{$lang->SRCommon}。"; -$lang->story->noRequirement = "暫時沒有{$lang->URCommon}。"; -$lang->story->ignoreChangeStage = "{$lang->SRCommon} %s 為草稿狀態或已關閉狀態,沒有修改其階段。"; -$lang->story->cannotDeleteParent = "不能刪除父{$lang->SRCommon}"; -$lang->story->moveChildrenTips = "修改父{$lang->SRCommon}的所屬產品會將其下的子{$lang->SRCommon}也移動到所選產品下。"; -$lang->story->changeTips = '該軟件需求關聯的用戶需求有變更,點擊“不變更”忽略此條變更,點擊“變更”來進行該軟件需求的變更。'; -$lang->story->estimateMustBeNumber = '估算值必須是數字'; -$lang->story->estimateMustBePlus = '估算值不能是負數'; -$lang->story->confirmChangeBranch = $lang->SRCommon . '%s已關聯在之前所屬分支的計劃中,調整分支後,' . $lang->SRCommon . '將從之前所屬分支的計劃中移除,請確認是否繼續修改上述' . $lang->SRCommon . '的分支。'; -$lang->story->confirmChangePlan = $lang->SRCommon . '%s已關聯在之前計劃的所屬分支中,調整分支後,' . $lang->SRCommon . '將會從計劃中移除,請確認是否繼續修改計劃的所屬分支。'; - -$lang->story->form = new stdclass(); -$lang->story->form->area = "該{$lang->SRCommon}所屬範圍"; -$lang->story->form->desc = "描述及標準,什麼{$lang->SRCommon}?如何驗收?"; -$lang->story->form->resource = '資源分配,有誰完成?需要多少時間?'; -$lang->story->form->file = "附件,如果該{$lang->SRCommon}有相關檔案,請點此上傳。"; - -$lang->story->action = new stdclass(); -$lang->story->action->reviewed = array('main' => '$date, 由 $actor 記錄評審意見,評審意見為 $extra。', 'extra' => 'reviewResultList'); -$lang->story->action->rejectreviewed = array('main' => '$date, 由 $actor 記錄評審意見,評審意見為 $extra,原因為 $reason。', 'extra' => 'reviewResultList', 'reason' => 'reasonList'); -$lang->story->action->recalled = array('main' => '$date, 由 $actor 撤銷評審。'); -$lang->story->action->closed = array('main' => '$date, 由 $actor 關閉,原因為 $extra $appendLink。', 'extra' => 'reasonList'); -$lang->story->action->reviewpassed = array('main' => '$date, 由 系統 判定,結果為 確認通過。'); -$lang->story->action->reviewrejected = array('main' => '$date, 由 系統 關閉,原因為 拒絶。'); -$lang->story->action->reviewclarified = array('main' => '$date, 由 系統 判定,結果為 有待明確,請變更後重新發起評審。'); -$lang->story->action->linked2plan = array('main' => '$date, 由 $actor 關聯到計劃 $extra。'); -$lang->story->action->unlinkedfromplan = array('main' => '$date, 由 $actor 從計劃 $extra 移除。'); -$lang->story->action->linked2execution = array('main' => '$date, 由 $actor 關聯到' . $lang->executionCommon . ' $extra。'); -$lang->story->action->unlinkedfromexecution = array('main' => '$date, 由 $actor 從' . $lang->executionCommon . ' $extra 移除。'); -$lang->story->action->linked2project = array('main' => '$date, 由 $actor 關聯到項目 $extra。'); -$lang->story->action->unlinkedfromproject = array('main' => '$date, 由 $actor 從項目 $extra 移除。'); -$lang->story->action->linked2build = array('main' => '$date, 由 $actor 關聯到版本 $extra。'); -$lang->story->action->unlinkedfrombuild = array('main' => '$date, 由 $actor 從版本 $extra 移除。'); -$lang->story->action->linked2release = array('main' => '$date, 由 $actor 關聯到發佈 $extra。'); -$lang->story->action->unlinkedfromrelease = array('main' => '$date, 由 $actor 從發佈 $extra 移除。'); -$lang->story->action->linkrelatedstory = array('main' => "\$date, 由 \$actor 關聯相關{$lang->SRCommon} \$extra。"); -$lang->story->action->subdividestory = array('main' => "\$date, 由 \$actor 細分為{$lang->SRCommon} \$extra。"); -$lang->story->action->unlinkrelatedstory = array('main' => "\$date, 由 \$actor 移除相關{$lang->SRCommon} \$extra。"); -$lang->story->action->unlinkchildstory = array('main' => "\$date, 由 \$actor 移除細分{$lang->SRCommon} \$extra。"); - -/* 統計報表。*/ -$lang->story->report = new stdclass(); -$lang->story->report->common = '報表'; -$lang->story->report->select = '請選擇報表類型'; -$lang->story->report->create = '生成報表'; -$lang->story->report->value = "需求數"; - -$lang->story->report->charts['storysPerProduct'] = $lang->productCommon . "{$lang->SRCommon}數量"; -$lang->story->report->charts['storysPerModule'] = "模組{$lang->SRCommon}數量"; -$lang->story->report->charts['storysPerSource'] = "按{$lang->SRCommon}來源統計"; -$lang->story->report->charts['storysPerPlan'] = "按計划進行統計"; -$lang->story->report->charts['storysPerStatus'] = '按狀態進行統計'; -$lang->story->report->charts['storysPerStage'] = '按所處階段進行統計'; -$lang->story->report->charts['storysPerPri'] = '按優先順序進行統計'; -$lang->story->report->charts['storysPerEstimate'] = "按預計{$lang->hourCommon}進行統計"; -$lang->story->report->charts['storysPerOpenedBy'] = '按由誰創建來進行統計'; -$lang->story->report->charts['storysPerAssignedTo'] = '按當前指派來進行統計'; -$lang->story->report->charts['storysPerClosedReason'] = '按關閉原因來進行統計'; -$lang->story->report->charts['storysPerChange'] = '按變更次數來進行統計'; - -$lang->story->report->options = new stdclass(); -$lang->story->report->options->graph = new stdclass(); -$lang->story->report->options->type = 'pie'; -$lang->story->report->options->width = 500; -$lang->story->report->options->height = 140; - -$lang->story->report->storysPerProduct = new stdclass(); -$lang->story->report->storysPerModule = new stdclass(); -$lang->story->report->storysPerSource = new stdclass(); -$lang->story->report->storysPerPlan = new stdclass(); -$lang->story->report->storysPerStatus = new stdclass(); -$lang->story->report->storysPerStage = new stdclass(); -$lang->story->report->storysPerPri = new stdclass(); -$lang->story->report->storysPerOpenedBy = new stdclass(); -$lang->story->report->storysPerAssignedTo = new stdclass(); -$lang->story->report->storysPerClosedReason = new stdclass(); -$lang->story->report->storysPerEstimate = new stdclass(); -$lang->story->report->storysPerChange = new stdclass(); - -$lang->story->report->storysPerProduct->item = $lang->productCommon; -$lang->story->report->storysPerModule->item = '模組'; -$lang->story->report->storysPerSource->item = '來源'; -$lang->story->report->storysPerPlan->item = '計劃'; -$lang->story->report->storysPerStatus->item = '狀態'; -$lang->story->report->storysPerStage->item = '階段'; -$lang->story->report->storysPerPri->item = '優先順序'; -$lang->story->report->storysPerOpenedBy->item = '由誰創建'; -$lang->story->report->storysPerAssignedTo->item = '指派給'; -$lang->story->report->storysPerClosedReason->item = '原因'; -$lang->story->report->storysPerEstimate->item = "預計{$lang->hourCommon}"; -$lang->story->report->storysPerChange->item = '變更次數'; - -$lang->story->report->storysPerProduct->graph = new stdclass(); -$lang->story->report->storysPerModule->graph = new stdclass(); -$lang->story->report->storysPerSource->graph = new stdclass(); -$lang->story->report->storysPerPlan->graph = new stdclass(); -$lang->story->report->storysPerStatus->graph = new stdclass(); -$lang->story->report->storysPerStage->graph = new stdclass(); -$lang->story->report->storysPerPri->graph = new stdclass(); -$lang->story->report->storysPerOpenedBy->graph = new stdclass(); -$lang->story->report->storysPerAssignedTo->graph = new stdclass(); -$lang->story->report->storysPerClosedReason->graph = new stdclass(); -$lang->story->report->storysPerEstimate->graph = new stdclass(); -$lang->story->report->storysPerChange->graph = new stdclass(); - -$lang->story->report->storysPerProduct->graph->xAxisName = $lang->productCommon; -$lang->story->report->storysPerModule->graph->xAxisName = '模組'; -$lang->story->report->storysPerSource->graph->xAxisName = '來源'; -$lang->story->report->storysPerPlan->graph->xAxisName = '計劃'; -$lang->story->report->storysPerStatus->graph->xAxisName = '狀態'; -$lang->story->report->storysPerStage->graph->xAxisName = '所處階段'; -$lang->story->report->storysPerPri->graph->xAxisName = '優先順序'; -$lang->story->report->storysPerOpenedBy->graph->xAxisName = '由誰創建'; -$lang->story->report->storysPerAssignedTo->graph->xAxisName = '當前指派'; -$lang->story->report->storysPerClosedReason->graph->xAxisName = '關閉原因'; -$lang->story->report->storysPerEstimate->graph->xAxisName = '預計時間'; -$lang->story->report->storysPerChange->graph->xAxisName = '變更次數'; - -$lang->story->placeholder = new stdclass(); -$lang->story->placeholder->estimate = $lang->story->hour; - -$lang->story->chosen = new stdClass(); -$lang->story->chosen->reviewedBy = '選擇評審人...'; - -$lang->story->notice = new stdClass(); -$lang->story->notice->closed = "您選擇的{$lang->SRCommon}已經被關閉了!"; -$lang->story->notice->reviewerNotEmpty = '該需求需要評審,評審人員不能為空。'; - -$lang->story->convertToTask = new stdClass(); -$lang->story->convertToTask->fieldList = array(); -$lang->story->convertToTask->fieldList['module'] = '所屬模組'; -$lang->story->convertToTask->fieldList['spec'] = "{$lang->SRCommon}描述"; -$lang->story->convertToTask->fieldList['pri'] = '優先順序'; -$lang->story->convertToTask->fieldList['mailto'] = '抄送給'; -$lang->story->convertToTask->fieldList['assignedTo'] = '指派給'; - -$lang->story->categoryList['feature'] = '功能'; -$lang->story->categoryList['interface'] = '介面'; -$lang->story->categoryList['performance'] = '性能'; -$lang->story->categoryList['safe'] = '安全'; -$lang->story->categoryList['experience'] = '體驗'; -$lang->story->categoryList['improve'] = '改進'; -$lang->story->categoryList['other'] = '其他'; diff --git a/module/story:q/model.php b/module/story:q/model.php deleted file mode 100644 index 605f04d858..0000000000 --- a/module/story:q/model.php +++ /dev/null @@ -1,5309 +0,0 @@ - - * @package story - * @version $Id: model.php 5145 2013-07-15 06:47:26Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -?> -dao->select('*')->from(TABLE_STORY) - ->where('id')->eq($storyID) - ->andWhere('vision')->eq($this->config->vision) - ->fetch(); - if(!$story) return false; - - if(helper::isZeroDate($story->closedDate)) $story->closedDate = ''; - if($version == 0) $version = $story->version; - $spec = $this->dao->select('title,spec,verify')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWhere('version')->eq($version)->fetch(); - $story->title = isset($spec->title) ? $spec->title : ''; - $story->spec = isset($spec->spec) ? $spec->spec : ''; - $story->verify = isset($spec->verify) ? $spec->verify : ''; - if(!empty($story->fromStory)) $story->sourceName = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($story->fromStory)->fetch('title'); - - /* Check parent story. */ - if($story->parent > 0) $story->parentName = $this->dao->findById($story->parent)->from(TABLE_STORY)->fetch('title'); - - $story = $this->loadModel('file')->replaceImgURL($story, 'spec,verify'); - if($setImgSize) $story->spec = $this->file->setImgSize($story->spec); - if($setImgSize) $story->verify = $this->file->setImgSize($story->verify); - - $story->executions = $this->dao->select('t1.project, t2.name, t2.status, t2.type')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project = t2.id') - ->where('t1.story')->eq($storyID) - ->andWhere('t2.type')->in('sprint,stage,kanban') - ->orderBy('t1.`order` DESC') - ->fetchAll('project'); - $story->tasks = $this->dao->select('id, name, assignedTo, execution, status, consumed, `left`,type')->from(TABLE_TASK)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->orderBy('id DESC')->fetchGroup('execution'); - $story->stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchPairs('branch', 'stage'); - //$story->bugCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_BUG)->where('story')->eq($storyID)->fetch('count'); - //$story->caseCount = $this->dao->select('COUNT(*)')->alias('count')->from(TABLE_CASE)->where('story')->eq($storyID)->fetch('count'); - if($story->toBug) $story->toBugTitle = $this->dao->findById($story->toBug)->from(TABLE_BUG)->fetch('title'); - if($story->plan) - { - $plans = $this->dao->select('id,title,branch')->from(TABLE_PRODUCTPLAN)->where('id')->in($story->plan)->fetchAll('id'); - foreach($plans as $planID => $plan) - { - $story->planTitle[$planID] = $plan->title; - if($plan->branch and !isset($story->stages[$plan->branch]) and empty($story->branch)) $story->stages[$plan->branch] = 'planned'; - } - } - $extraStories = array(); - if($story->duplicateStory) $extraStories = array($story->duplicateStory); - if($story->linkStories) $extraStories = explode(',', $story->linkStories); - if($story->childStories) $extraStories = array_merge($extraStories, explode(',', $story->childStories)); - $extraStories = array_unique($extraStories); - if(!empty($extraStories)) $story->extraStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($extraStories)->fetchPairs(); - - $story->children = array(); - if($story->parent == '-1') $story->children = $this->dao->select('*')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll('id'); - - return $story; - } - - /** - * Get stories by idList. - * - * @param int|array|string $storyIdList - * @param string $type requirement|story - * @access public - * @return array - */ - public function getByList($storyIdList = 0, $type = 'story') - { - return $this->dao->select('t1.*, t2.spec, t2.verify, t3.name as productTitle') - ->from(TABLE_STORY)->alias('t1') - ->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product=t3.id') - ->where('t1.deleted')->eq(0) - ->andWhere('t1.version=t2.version') - ->beginIF($storyIdList)->andWhere('t1.id')->in($storyIdList)->fi() - ->beginIF(!$storyIdList)->andWhere('t1.type')->eq($type)->fi() - ->fetchAll('id'); - } - - /** - * Get test stories. - * - * @param array $storyIdList - * @param int $executionID - * @access public - * @return array - */ - public function getTestStories($storyIdList, $executionID) - { - return $this->dao->select('story')->from(TABLE_TASK)->where('execution')->eq($executionID)->andWhere('type')->eq('test')->andWhere('story')->in($storyIdList)->andWhere('deleted')->eq(0)->fetchPairs('story', 'story'); - } - - /** - * Get story specs. - * - * @param array $storyIdList - * @access public - * @return array - */ - public function getStorySpecs($storyIdList) - { - return $this->dao->select('story,spec,verify')->from(TABLE_STORYSPEC) - ->where('story')->in($storyIdList) - ->orderBy('version') - ->fetchAll('story'); - } - - /** - * Get affected things. - * - * @param object $story - * @access public - * @return object - */ - public function getAffectedScope($story) - { - /* Remove closed executions. */ - if($story->executions) - { - foreach($story->executions as $executionID => $execution) if($execution->status == 'done') unset($story->executions[$executionID]); - } - - /* Get team members. */ - if($story->executions) - { - $story->teams = $this->dao->select('account, root') - ->from(TABLE_TEAM) - ->where('root')->in(array_keys($story->executions)) - ->andWhere('type')->eq('project') - ->fetchGroup('root'); - } - - /* Get affected bugs. */ - $story->bugs = $this->dao->findByStory($story->id)->from(TABLE_BUG) - ->andWhere('status')->ne('closed') - ->andWhere('deleted')->eq(0) - ->orderBy('id desc')->fetchAll(); - - /* Get affected cases. */ - $story->cases = $this->dao->findByStory($story->id)->from(TABLE_CASE)->andWhere('deleted')->eq(0)->fetchAll(); - - return $story; - } - - /** - * Get requirements for story. - * - * @param int $productID - * @access public - * @return void - */ - public function getRequirements($productID) - { - return $this->dao->select('id,title')->from(TABLE_STORY) - ->where('deleted')->eq(0) - ->andWhere('product')->eq($productID) - ->andWhere('type')->eq('requirement') - ->andWhere('status')->notIN('draft,closed') - ->fetchPairs(); - } - - /** - * Create a story. - * - * @param int $executionID - * @param int $bugID - * @param string $from - * @param string $extra - * @access public - * @return int|bool the id of the created story or false when error. - */ - public function create($executionID = 0, $bugID = 0, $from = '', $extra = '') - { - $extra = str_replace(array(',', ' '), array('&', ''), $extra); - parse_str($extra, $output); - - if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']); - if(!$this->post->needNotReview and empty($_POST['reviewer'])) - { - dao::$errors[] = $this->lang->story->errorEmptyReviewedBy; - return false; - } - - $now = helper::now(); - $story = fixer::input('post') - ->cleanInt('product,module,pri,plan') - ->callFunc('title', 'trim') - ->add('version', 1) - ->add('status', 'draft') - ->setDefault('plan,verify,notifyEmail', '') - ->setDefault('openedBy', $this->app->user->account) - ->setDefault('openedDate', $now) - ->setIF($this->post->needNotReview, 'status', 'active') - ->setIF($this->post->plan > 0, 'stage', 'planned') - ->setIF($this->post->estimate, 'estimate', (float)$this->post->estimate) - ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'feedbackBy', '') - ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'notifyEmail', '') - ->setIF($executionID > 0, 'stage', 'projected') - ->setIF($bugID > 0, 'fromBug', $bugID) - ->join('assignedTo', '') - ->join('mailto', ',') - ->stripTags($this->config->story->editor->create['id'], $this->config->allowedTags) - ->remove('files,labels,reviewer,needNotReview,newStory,uid,contactListMenu,URS,region,lane') - ->get(); - - /* Check repeat story. */ - $result = $this->loadModel('common')->removeDuplicate('story', $story, "product={$story->product}"); - if(isset($result['stop']) and $result['stop']) return array('status' => 'exists', 'id' => $result['duplicate']); - - if($this->checkForceReview()) $story->status = 'draft'; - if($story->status == 'draft') $story->stage = $this->post->plan > 0 ? 'planned' : 'wait'; - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->create['id'], $this->post->uid); - - $requiredFields = "," . $this->config->story->create->requiredFields . ","; - - if($story->type == 'requirement') $requiredFields = str_replace(',plan,', ',', $requiredFields); - if(strpos($requiredFields, ',estimate,') !== false) - { - if(strlen(trim($story->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->story->estimate); - $requiredFields = str_replace(',estimate,', ',', $requiredFields); - } - - $requiredFields = trim($requiredFields, ','); - - $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify') - ->autoCheck() - ->checkIF($story->notifyEmail, 'notifyEmail', 'email') - ->batchCheck($requiredFields, 'notempty') - ->checkFlow() - ->exec(); - - if(!dao::isError()) - { - $storyID = $this->dao->lastInsertID(); - - /* Fix bug #21992, user story have no parent story. */ - if(isset($story->parent) and $story->parent) - { - $stories = array($storyID); - $this->subdivide($story->parent, $stories); - } - - $this->file->updateObjectID($this->post->uid, $storyID, $story->type); - $this->file->saveUpload($story->type, $storyID, $extra = 1); - - if(!empty($story->plan)) $this->updateStoryOrderOfPlan($storyID, $story->plan); // Set story order in this plan. - - $data = new stdclass(); - $data->story = $storyID; - $data->version = 1; - $data->title = $story->title; - $data->spec = $story->spec; - $data->verify = $story->verify; - $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); - - /* Save the story reviewer to storyreview table. */ - if(isset($_POST['reviewer'])) - { - $assignedTo = ''; - foreach($this->post->reviewer as $reviewer) - { - if(empty($reviewer)) continue; - - $reviewData = new stdclass(); - $reviewData->story = $storyID; - $reviewData->version = 1; - $reviewData->reviewer = $reviewer; - $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); - - if(empty($assignedTo)) $assignedTo = $reviewer; - } - if($assignedTo) $this->dao->update(TABLE_STORY)->set('assignedTo')->eq($assignedTo)->set('assignedDate')->eq(helper::now())->where('id')->eq($storyID)->exec(); - } - - /* Project or execution linked story. */ - if($executionID != 0) - { - $this->linkStory($executionID, $this->post->product, $storyID); - if($this->config->systemMode == 'new' and $executionID != $this->session->project) $this->linkStory($this->session->project, $this->post->product, $storyID); - - $this->loadModel('kanban'); - - $laneID = isset($output['laneID']) ? $output['laneID'] : 0; - if(isset($_POST['lane'])) $laneID = $_POST['lane']; - - $columnID = $this->kanban->getColumnIDByLaneID($laneID, 'backlog'); - if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0; - - if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'story', $storyID); - if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($executionID, 'story'); - } - - if(is_array($this->post->URS)) - { - foreach($this->post->URS as $URID) - { - $requirement = $this->getByID($URID); - $data = new stdclass(); - $data->product = $story->product; - $data->AType = 'requirement'; - $data->relation = 'subdivideinto'; - $data->BType = 'story'; - $data->AID = $URID; - $data->BID = $storyID; - $data->AVersion = $requirement->version; - $data->BVersion = 1; - $data->extra = 1; - - $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); - - $data->AType = 'story'; - $data->relation = 'subdividedfrom'; - $data->BType = 'requirement'; - $data->AID = $storyID; - $data->BID = $URID; - $data->AVersion = 1; - $data->BVersion = $requirement->version; - - $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); - } - } - - if($bugID > 0) - { - $bug = new stdclass(); - $bug->toStory = $storyID; - $bug->status = 'closed'; - $bug->resolution = 'tostory'; - $bug->resolvedBy = $this->app->user->account; - $bug->resolvedDate = $now; - $bug->closedBy = $this->app->user->account; - $bug->closedDate = $now; - $bug->assignedTo = 'closed'; - $bug->assignedDate = $now; - $this->dao->update(TABLE_BUG)->data($bug)->where('id')->eq($bugID)->exec(); - - $this->loadModel('action')->create('bug', $bugID, 'ToStory', '', $storyID); - $this->action->create('bug', $bugID, 'Closed'); - - /* add files to story from bug. */ - $files = $this->dao->select('*')->from(TABLE_FILE) - ->where('objectType')->eq('bug') - ->andWhere('objectID')->eq($bugID) - ->fetchAll(); - if(!empty($files)) - { - foreach($files as $file) - { - $file->objectType = 'story'; - $file->objectID = $storyID; - unset($file->id); - $this->dao->insert(TABLE_FILE)->data($file)->exec(); - } - } - } - $this->setStage($storyID); - if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID); - - /* Callback the callable method to process the related data for object that is transfered to story. */ - if($from && is_callable(array($this, $this->config->story->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->story->fromObjects[$from]['callback']), $storyID); - - return array('status' => 'created', 'id' => $storyID); - } - return false; - } - - /** - * Create story from gitlab issue. - * - * @param object $story - * @param int $executionID - * @access public - * @return int - */ - public function createStoryFromGitlabIssue($story, $executionID) - { - $story->status = 'active'; - $story->stage = 'projected'; - $story->openedBy = $this->app->user->account; - $story->version = 1; - $story->pri = 3; - $story->assignedDate = isset($story->assignedTo) ? helper::now() : 0; - - if(isset($story->execution)) unset($story->execution); - - $requiredFields = $this->config->story->create->requiredFields; - $this->dao->insert(TABLE_STORY)->data($story, 'spec,verify,gitlab,gitlabProject')->autoCheck()->batchCheck($requiredFields, 'notempty')->exec(); - if(!dao::isError()) - { - $storyID = $this->dao->lastInsertID(); - - $data = new stdclass(); - $data->story = $storyID; - $data->version = 1; - $data->title = $story->title; - $data->spec = $story->spec; - $data->verify = $story->spec; - $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); - - /* Link story to execution. */ - $this->linkStory($executionID, $story->product, $storyID); - - return $storyID; - } - - return false; - } - - /** - * Batch create stories. - * - * @access public - * @return int|bool the id of the created story or false when error. - * @return type requirement|story - */ - public function batchCreate($productID = 0, $branch = 0, $type = 'story') - { - $forceReview = $this->checkForceReview(); - - $reviewers = ''; - foreach($_POST['title'] as $index => $value) - { - if($_POST['title'][$index] and isset($_POST['reviewer'][$index]) and empty($_POST['reviewDitto'][$index])) $reviewers = $_POST['reviewer'][$index] = array_filter($_POST['reviewer'][$index]); - if($_POST['title'][$index] and isset($_POST['reviewer'][$index]) and !empty($_POST['reviewDitto'][$index])) $_POST['reviewer'][$index] = $reviewers; - if($_POST['title'][$index] and empty($_POST['reviewer'][$index]) and $forceReview) - { - dao::$errors[] = $this->lang->story->errorEmptyReviewedBy; - return false; - } - } - - $this->loadModel('action'); - $branch = (int)$branch; - $productID = (int)$productID; - $now = helper::now(); - $mails = array(); - $stories = fixer::input('post')->get(); - - $result = $this->loadModel('common')->removeDuplicate('story', $stories, "product={$productID}"); - $stories = $result['data']; - - $module = 0; - $plan = ''; - $pri = 0; - $source = ''; - - foreach($stories->title as $i => $title) - { - $module = $stories->module[$i] == 'ditto' ? $module : $stories->module[$i]; - $plan = isset($stories->plan[$i]) ? ($stories->plan[$i] == 'ditto' ? $plan : $stories->plan[$i]) : ''; - $pri = $stories->pri[$i] == 'ditto' ? $pri : $stories->pri[$i]; - $source = $stories->source[$i] == 'ditto' ? $source : $stories->source[$i]; - $stories->module[$i] = (int)$module; - $stories->plan[$i] = $plan; - $stories->pri[$i] = (int)$pri; - $stories->source[$i] = $source; - } - - if(isset($stories->uploadImage)) $this->loadModel('file'); - - $extendFields = $this->getFlowExtendFields(); - $data = array(); - foreach($stories->title as $i => $title) - { - if(empty($title)) continue; - - if(empty($stories->reviewer[$i]) and empty($stories->reviewerDitto[$i])) $stories->reviewer[$i] = array(); - - $story = new stdclass(); - $story->type = $type; - $story->branch = isset($stories->branch[$i]) ? $stories->branch[$i] : 0; - $story->module = $stories->module[$i]; - $story->plan = $stories->plan[$i]; - $story->color = $stories->color[$i]; - $story->title = $stories->title[$i]; - $story->source = $stories->source[$i]; - $story->category = $stories->category[$i]; - $story->pri = $stories->pri[$i]; - $story->estimate = $stories->estimate[$i]; - $story->status = (empty($stories->reviewer[$i]) and !$forceReview) ? 'active' : 'draft'; - $story->stage = ($this->app->tab == 'project' or $this->app->tab == 'execution') ? 'projected' : 'wait'; - $story->keywords = $stories->keywords[$i]; - $story->sourceNote = $stories->sourceNote[$i]; - $story->product = $productID; - $story->openedBy = $this->app->user->account; - $story->vision = $this->config->vision; - $story->openedDate = $now; - $story->version = 1; - - foreach($extendFields as $extendField) - { - $story->{$extendField->field} = $this->post->{$extendField->field}[$i]; - if(is_array($story->{$extendField->field})) $story->{$extendField->field} = join(',', $story->{$extendField->field}); - - $story->{$extendField->field} = htmlSpecialString($story->{$extendField->field}); - } - - foreach(explode(',', $this->config->story->create->requiredFields) as $field) - { - $field = trim($field); - if(empty($field)) continue; - if($type == 'requirement' and $field == 'plan') continue; - - if(!isset($story->$field)) continue; - if(!empty($story->$field)) continue; - if($field == 'estimate' and strlen(trim($story->estimate)) != 0) continue; - - dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->story->$field); - return false; - } - $data[$i] = $story; - } - - $planStories = array(); - - foreach($data as $i => $story) - { - $this->dao->insert(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->exec(); - if(dao::isError()) - { - echo js::error(dao::getError()); - return print(js::reload('parent')); - } - - $storyID = $this->dao->lastInsertID(); - $this->setStage($storyID); - - /* Update product plan stories order. */ - if($story->plan) $this->updateStoryOrderOfPlan($storyID, $story->plan); - - $specData = new stdclass(); - $specData->story = $storyID; - $specData->version = 1; - $specData->title = $stories->title[$i]; - $specData->spec = ''; - $specData->verify = ''; - if(!empty($stories->spec[$i])) $specData->spec = nl2br($stories->spec[$i]); - if(!empty($stories->verify[$i]))$specData->verify = nl2br($stories->verify[$i]); - - if(!empty($stories->uploadImage[$i])) - { - $fileName = $stories->uploadImage[$i]; - $file = $this->session->storyImagesFile[$fileName]; - - $realPath = $file['realpath']; - unset($file['realpath']); - - if(!is_dir($this->file->savePath)) mkdir($this->file->savePath, 0777, true); - if($realPath and rename($realPath, $this->file->savePath . $this->file->getSaveName($file['pathname']))) - { - $file['addedBy'] = $this->app->user->account; - $file['addedDate'] = $now; - $file['objectType'] = 'story'; - $file['objectID'] = $storyID; - if(in_array($file['extension'], $this->config->file->imageExtensions)) - { - $file['extra'] = 'editor'; - $this->dao->insert(TABLE_FILE)->data($file)->exec(); - - $fileID = $this->dao->lastInsertID(); - $specData->spec .= ''; - } - else - { - $this->dao->insert(TABLE_FILE)->data($file)->exec(); - } - } - } - - $this->dao->insert(TABLE_STORYSPEC)->data($specData)->exec(); - - /* Save the story reviewer to storyreview table. */ - $assignedTo = ''; - foreach($_POST['reviewer'][$i] as $reviewer) - { - if(empty($reviewer)) continue; - - $reviewData = new stdclass(); - $reviewData->story = $storyID; - $reviewData->version = 1; - $reviewData->reviewer = $reviewer; - $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); - - if(empty($assignedTo)) $assignedTo = $reviewer; - } - if($assignedTo) $this->dao->update(TABLE_STORY)->set('assignedTo')->eq($assignedTo)->set('assignedDate')->eq($now)->where('id')->eq($storyID)->exec(); - - $this->executeHooks($storyID); - - $actionID = $this->action->create('story', $storyID, 'Opened', ''); - if(!dao::isError()) $this->loadModel('score')->create('story', 'create',$storyID); - $mails[$i] = new stdclass(); - $mails[$i]->storyID = $storyID; - $mails[$i]->actionID = $actionID; - } - - /* Remove upload image file and session. */ - if(!empty($stories->uploadImage) and $this->session->storyImagesFile) - { - $classFile = $this->app->loadClass('zfile'); - $file = current($_SESSION['storyImagesFile']); - $realPath = dirname($file['realpath']); - if(is_dir($realPath)) $classFile->removeDir($realPath); - unset($_SESSION['storyImagesFile']); - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchCreate'); - return $mails; - } - - /** - * Change a story. - * - * @param int $storyID - * @access public - * @return array the change of the story. - */ - public function change($storyID) - { - $specChanged = false; - $oldStory = $this->dao->findById((int)$storyID)->from(TABLE_STORY)->fetch(); - $oldSpec = $this->dao->select('title,spec,verify')->from(TABLE_STORYSPEC)->where('story')->eq((int)$storyID)->andWhere('version')->eq($oldStory->version)->fetch(); - $oldStory->title = isset($oldSpec->title) ? $oldSpec->title : ''; - $oldStory->spec = isset($oldSpec->spec) ? $oldSpec->spec : ''; - $oldStory->verify = isset($oldSpec->verify) ? $oldSpec->verify : ''; - - if(!empty($_POST['lastEditedDate']) and $oldStory->lastEditedDate != $this->post->lastEditedDate) - { - dao::$errors[] = $this->lang->error->editedByOther; - return false; - } - - if(strpos($this->config->story->change->requiredFields, 'comment') !== false and !$this->post->comment) - { - dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->comment); - return false; - } - - if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']); - if(!$this->post->needNotReview and empty($_POST['reviewer'])) - { - dao::$errors[] = $this->lang->story->errorEmptyReviewedBy; - return false; - } - - $story = fixer::input('post')->stripTags($this->config->story->editor->change['id'], $this->config->allowedTags)->get(); - - $oldStoryReviewers = $this->getReviewerPairs($storyID, $oldStory->version); - $_POST['reviewer'] = isset($_POST['reviewer']) ? $_POST['reviewer'] : array(); - $reviewerHasChanged = (array_diff(array_keys($oldStoryReviewers), $_POST['reviewer']) or array_diff($_POST['reviewer'], array_keys($oldStoryReviewers))); - if($story->spec != $oldStory->spec or $story->verify != $oldStory->verify or $story->title != $oldStory->title or $this->loadModel('file')->getCount() or $reviewerHasChanged) $specChanged = true; - - $now = helper::now(); - $story = fixer::input('post') - ->callFunc('title', 'trim') - ->setDefault('lastEditedBy', $this->app->user->account) - ->add('id', $storyID) - ->add('lastEditedDate', $now) - ->setIF(!$this->post->assignedTo, 'assignedTo', '') - ->setIF($specChanged, 'version', $oldStory->version + 1) - ->setIF($specChanged and $oldStory->status == 'active' and $this->post->needNotReview == false, 'status', 'changed') - ->setIF($oldStory->status == 'draft' and $this->post->needNotReview, 'status', 'active') - ->setIF($specChanged, 'reviewedBy', '') - ->setIF($specChanged, 'changedBy', $this->app->user->account) - ->setIF($specChanged, 'changedDate', $now) - ->setIF($specChanged, 'closedBy', '') - ->setIF($specChanged, 'closedReason', '') - ->setIF($specChanged and $oldStory->reviewedBy, 'reviewedDate', '0000-00-00') - ->setIF($specChanged and $oldStory->closedBy, 'closedDate', '0000-00-00') - ->stripTags($this->config->story->editor->change['id'], $this->config->allowedTags) - ->remove('files,labels,reviewer,comment,needNotReview,uid') - ->get(); - if($specChanged and isset($story->status) && $story->status == 'active' and $this->checkForceReview()) $story->status = 'changed'; - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->change['id'], $this->post->uid); - $this->dao->update(TABLE_STORY)->data($story, 'spec,verify') - ->autoCheck() - ->batchCheck($this->config->story->change->requiredFields, 'notempty') - ->checkFlow() - ->where('id')->eq((int)$storyID)->exec(); - - if(!dao::isError()) - { - if($specChanged) - { - $data = new stdclass(); - $data->story = $storyID; - $data->version = $oldStory->version + 1; - $data->title = $story->title; - $data->spec = $story->spec; - $data->verify = $story->verify; - $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); - - $story = $this->getById($storyID); - /* IF is story and has changed, update its relation version to new. */ - if($oldStory->type == 'story') - { - $this->dao->update(TABLE_STORY)->set('URChanged')->eq(0)->where('id')->eq($oldStory->id)->exec(); - $this->updateStoryVersion($story); - } - else - { - /* IF is requirement changed, notify its relation. */ - $relations = $this->dao->select('BID')->from(TABLE_RELATION) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere('AID')->eq($story->id) - ->fetchPairs(); - - foreach($relations as $relationID) $this->dao->update(TABLE_STORY)->set('URChanged')->eq(1)->where('id')->eq($relationID)->exec(); - } - - /* Update the reviewer. */ - $assignedTo = ''; - foreach($_POST['reviewer'] as $reviewer) - { - $reviewData = new stdclass(); - $reviewData->story = $storyID; - $reviewData->version = $story->version; - $reviewData->reviewer = $reviewer; - $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); - - if(empty($assignedTo)) $assignedTo = $reviewer; - } - if($assignedTo and $assignedTo != $oldStory->assignedTo) $this->dao->update(TABLE_STORY)->set('assignedTo')->eq($assignedTo)->set('assignedDate')->eq(helper::now())->where('id')->eq($storyID)->exec(); - } - - $this->file->updateObjectID($this->post->uid, $storyID, 'story'); - - $oldStory->reviewers = implode(',', array_keys($oldStoryReviewers)); - $story->reviewers = implode(',', $_POST['reviewer']); - return common::createChanges($oldStory, $story); - } - } - - /** - * Update a story. - * - * @param int $storyID - * @access public - * @return array the changes of the story. - */ - public function update($storyID) - { - $now = helper::now(); - $oldStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($storyID)->fetch(); - if(!empty($_POST['lastEditedDate']) and $oldStory->lastEditedDate != $this->post->lastEditedDate) - { - dao::$errors[] = $this->lang->error->editedByOther; - return false; - } - - $story = fixer::input('post') - ->cleanInt('product,module,pri,duplicateStory') - ->cleanFloat('estimate') - ->setDefault('assignedDate', $oldStory->assignedDate) - ->setDefault('lastEditedBy', $this->app->user->account) - ->setDefault('reviewedBy', '') - ->setDefault('mailto', '') - ->add('id', $storyID) - ->add('lastEditedDate', $now) - ->setDefault('plan,notifyEmail', '') - ->setDefault('status', $oldStory->status) - ->setDefault('product', $oldStory->product) - ->setDefault('branch', $oldStory->branch) - ->setIF(!$this->post->linkStories, 'linkStories', '') - ->setIF($this->post->assignedTo != $oldStory->assignedTo, 'assignedDate', $now) - ->setIF($this->post->closedBy != false and $oldStory->closedDate == '', 'closedDate', $now) - ->setIF($this->post->closedReason != false and $oldStory->closedDate == '', 'closedDate', $now) - ->setIF($this->post->closedBy != false or $this->post->closedReason != false, 'status', 'closed') - ->setIF($this->post->closedReason != false and $this->post->closedBy == false, 'closedBy', $this->app->user->account) - ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'feedbackBy', '') - ->setIF(!in_array($this->post->source, $this->config->story->feedbackSource), 'notifyEmail', '') - ->setIF(!empty($_POST['plan'][0]) and $oldStory->stage == 'wait', 'stage', 'planned') - ->stripTags($this->config->story->editor->edit['id'], $this->config->allowedTags) - ->join('reviewedBy', ',') - ->join('mailto', ',') - ->join('linkStories', ',') - ->join('childStories', ',') - ->remove('files,labels,comment,contactListMenu,stages,reviewer') - ->get(); - - if(isset($story->plan) and is_array($story->plan)) $story->plan = trim(join(',', $story->plan), ','); - if(isset($_POST['branch']) and $_POST['branch'] == 0) $story->branch = 0; - if(!empty($_POST['stages'])) - { - $oldStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchAll('branch'); - $this->dao->delete()->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->exec(); - - $stageList = join(',', array_keys($this->lang->story->stageList)); - $minStagePos = strlen($stageList); - $minStage = ''; - foreach($this->post->stages as $branch => $stage) - { - $newStage = new stdclass(); - $newStage->story = $storyID; - $newStage->branch = $branch; - $newStage->stage = $stage; - if(isset($oldStages[$branch])) - { - $oldStage = $oldStages[$branch]; - $newStage->stagedBy = $oldStage->stagedBy; - if($stage != $oldStage->stage) $newStage->stagedBy = (strpos('tested|verified|released|closed', $stage) !== false) ? $this->app->user->account : ''; - } - if($story->branch == 0) $this->dao->insert(TABLE_STORYSTAGE)->data($newStage)->exec(); - if(strpos($stageList, $stage) !== false and strpos($stageList, $stage) < $minStagePos) - { - $minStage = $stage; - $minStagePos = strpos($stageList, $stage); - } - } - $story->stage = $minStage; - } - - if(isset($story->stage) and $oldStory->stage != $story->stage) $story->stagedBy = (strpos('tested|verified|released|closed', $story->stage) !== false) ? $this->app->user->account : ''; - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->edit['id'], $this->post->uid); - - if(isset($_POST['reviewer'])) - { - $_POST['reviewer'] = array_filter($_POST['reviewer']); - $oldReviewer = $this->getReviewerPairs($storyID, $oldStory->version); - if(array_diff($_POST['reviewer'], array_keys($oldReviewer)) or array_diff(array_keys($oldReviewer), $_POST['reviewer'])) - { - /* Update story reviewer. */ - $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->notin(implode(',', $_POST['reviewer']))->exec(); - foreach($_POST['reviewer'] as $reviewer) - { - if(in_array($reviewer, array_keys($oldReviewer))) continue; - - $reviewData = new stdclass(); - $reviewData->story = $storyID; - $reviewData->version = $oldStory->version; - $reviewData->reviewer = $reviewer; - $this->dao->insert(TABLE_STORYREVIEW)->data($reviewData)->exec(); - } - - $story->reviewedBy = $oldStory->reviewedBy; - $story = $this->updateStoryByReview($storyID, $oldStory, $story); - } - - $oldStory->reviewers = implode(',', array_keys($oldReviewer)); - $story->reviewers = implode(',', array_keys($this->getReviewerPairs($storyID, $oldStory->version))); - } - - $this->dao->update(TABLE_STORY) - ->data($story, 'reviewers') - ->autoCheck() - ->checkIF(isset($story->closedBy), 'closedReason', 'notempty') - ->checkIF(isset($story->closedReason) and $story->closedReason == 'done', 'stage', 'notempty') - ->checkIF(isset($story->closedReason) and $story->closedReason == 'duplicate', 'duplicateStory', 'notempty') - ->checkIF($story->notifyEmail, 'notifyEmail', 'email') - ->checkFlow() - ->where('id')->eq((int)$storyID)->exec(); - - if(!dao::isError()) - { - if($story->product != $oldStory->product) - { - $this->updateStoryProduct($storyID, $story->product); - if($oldStory->parent == '-1') - { - $childStories = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchPairs('id'); - foreach($childStories as $childStoryID) $this->updateStoryProduct($childStoryID, $story->product); - } - } - - $this->loadModel('action'); - $changed = $story->parent != $oldStory->parent; - if($oldStory->parent > 0) - { - $oldParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($oldStory->parent)->fetch(); - $this->updateParentStatus($storyID, $oldStory->parent, !$changed); - - if($changed) - { - $oldChildren = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($oldStory->parent)->andWhere('deleted')->eq(0)->fetchPairs('id', 'id'); - if(empty($oldChildren)) $this->dao->update(TABLE_STORY)->set('parent')->eq(0)->where('id')->eq($oldStory->parent)->exec(); - $this->dao->update(TABLE_STORY)->set('childStories')->eq(join(',', $oldChildren))->set('lastEditedBy')->eq($this->app->user->account)->set('lastEditedDate')->eq(helper::now())->where('id')->eq($oldStory->parent)->exec(); - $this->action->create('story', $storyID, 'unlinkParentStory', '', $oldStory->parent, '', false); - - $actionID = $this->action->create('story', $oldStory->parent, 'unLinkChildrenStory', '', $storyID, '', false); - - $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($oldStory->parent)->fetch(); - $changes = common::createChanges($oldParentStory, $newParentStory); - if(!empty($changes)) $this->action->logHistory($actionID, $changes); - } - } - - if($story->parent > 0) - { - $parentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($story->parent)->fetch(); - $this->dao->update(TABLE_STORY)->set('parent')->eq(-1)->where('id')->eq($story->parent)->exec(); - $this->updateParentStatus($storyID, $story->parent, !$changed); - - if($changed) - { - $children = $this->dao->select('id')->from(TABLE_STORY)->where('parent')->eq($story->parent)->andWhere('deleted')->eq(0)->fetchPairs('id', 'id'); - $this->dao->update(TABLE_STORY) - ->set('parent')->eq('-1') - ->set('childStories')->eq(join(',', $children)) - ->set('lastEditedBy')->eq($this->app->user->account) - ->set('lastEditedDate')->eq(helper::now()) - ->where('id')->eq($story->parent) - ->exec(); - - $this->action->create('story', $storyID, 'linkParentStory', '', $story->parent, '', false); - $actionID = $this->action->create('story', $story->parent, 'linkChildStory', '', $storyID, '', false); - - $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($story->parent)->fetch(); - $changes = common::createChanges($parentStory, $newParentStory); - if(!empty($changes)) $this->action->logHistory($actionID, $changes); - } - } - - if(isset($story->closedReason) and $story->closedReason == 'done') $this->loadModel('score')->create('story', 'close'); - - /* Set new stage and update story sort of plan when story plan has changed. */ - if($oldStory->plan != $story->plan) - { - $this->updateStoryOrderOfPlan($storyID, $story->plan, $oldStory->plan); // Insert a new story sort in this plan. - - if(empty($oldStory->plan) or empty($story->plan)) $this->setStage($storyID); // Set new stage for this story. - } - - if($oldStory->stage != $story->stage) - { - $executionIdList = $this->dao->select('t1.project')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') - ->where('t1.story')->eq($storyID) - ->andWhere('t2.deleted')->eq(0) - ->andWhere('t2.type')->in('sprint,stage,kanban') - ->fetchPairs(); - - $this->loadModel('kanban'); - foreach($executionIdList as $executionID) $this->kanban->updateLane($executionID, 'story', $storyID); - } - - unset($oldStory->parent); - unset($story->parent); - return common::createChanges($oldStory, $story); - } - } - - /** - * Update story product. - * - * @param int $storyID - * @param int $productID - * @access public - * @return void - */ - public function updateStoryProduct($storyID, $productID) - { - $this->dao->update(TABLE_STORY)->set('product')->eq($productID)->where('id')->eq($storyID)->exec(); - $this->dao->update(TABLE_PROJECTSTORY)->set('product')->eq($productID)->where('story')->eq($storyID)->exec(); - $storyProjects = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($storyID)->orderBy('project')->fetchPairs('project', 'project'); - $linkedProjects = $this->dao->select('project')->from(TABLE_PROJECTPRODUCT)->where('project')->in($storyProjects)->andWhere('product')->eq($productID)->orderBy('project')->fetchPairs('project','project'); - $unlinkedProjects = array_diff($storyProjects, $linkedProjects); - foreach($unlinkedProjects as $projectID) - { - $data = new stdclass(); - $data->project = $projectID; - $data->product = $productID; - $this->dao->replace(TABLE_PROJECTPRODUCT)->data($data)->exec(); - } - } - - /** - * Update parent status. - * - * @param int $storyID - * @param int $parentID - * @param bool $createAction - * @access public - * @return mixed - */ - public function updateParentStatus($storyID, $parentID = 0, $createAction = true) - { - $childStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($storyID)->fetch(); - if(empty($parentID)) $parentID = $childStory->parent; - if($parentID <= 0) return true; - - $oldParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->andWhere('deleted')->eq(0)->fetch(); - if(empty($oldParentStory)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($storyID)->exec(); - if($oldParentStory->parent != '-1') $this->dao->update(TABLE_STORY)->set('parent')->eq('-1')->where('id')->eq($parentID)->exec(); - $this->computeEstimate($parentID); - - $childrenStatus = $this->dao->select('id,status')->from(TABLE_STORY)->where('parent')->eq($parentID)->andWhere('deleted')->eq(0)->fetchPairs('status', 'status'); - if(empty($childrenStatus)) return $this->dao->update(TABLE_STORY)->set('parent')->eq('0')->where('id')->eq($parentID)->exec(); - - $status = $oldParentStory->status; - if(count($childrenStatus) == 1 and $oldParentStory->status != 'changed') - { - $status = current($childrenStatus); - if($status == 'draft' or $status == 'changed') $status = 'active'; - } - elseif(count($childrenStatus) != 1 and $oldParentStory->status == 'closed') - { - $status = 'active'; - } - - if($status and $oldParentStory->status != $status) - { - $now = helper::now(); - $story = new stdclass(); - $story->status = $status; - $story->stage = 'wait'; - if($status == 'active') - { - $story->assignedTo = $oldParentStory->openedBy; - $story->assignedDate = $now; - $story->closedBy = ''; - $story->closedReason = ''; - $story->closedDate = '0000-00-00'; - $story->reviewedBy = ''; - $story->reviewedDate = '0000-00-00'; - } - - if($status == 'closed') - { - $story->assignedTo = 'closed'; - $story->assignedDate = $now; - $story->closedBy = $this->app->user->account; - $story->closedDate = $now; - $story->closedReason = 'done'; - $story->closedReason = 'done'; - } - - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->parent = '-1'; - $this->dao->update(TABLE_STORY)->data($story)->where('id')->eq($parentID)->exec(); - if(!dao::isError()) - { - if(!$createAction) return $story; - - $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->fetch(); - $changes = common::createChanges($oldParentStory, $newParentStory); - $action = ''; - if($status == 'active') $action = 'Activated'; - if($status == 'closed') $action = 'Closed'; - if($action) - { - $actionID = $this->loadModel('action')->create('story', $parentID, $action, '', '', '', false); - $this->action->logHistory($actionID, $changes); - } - } - } - else - { - if(!dao::isError()) - { - $newParentStory = $this->dao->select('*')->from(TABLE_STORY)->where('id')->eq($parentID)->fetch(); - $changes = common::createChanges($oldParentStory, $newParentStory); - if($changes) - { - $actionID = $this->loadModel('action')->create('story', $parentID, 'Edited', '', '', '', false); - $this->action->logHistory($actionID, $changes); - } - } - } - } - - /** - * If story changed, update relation table version filed. - * - * @param object $story - * @access public - * @return void - */ - public function updateStoryVersion($story) - { - $changedStories = $this->getChangedStories($story); - - if(!empty($changedStories)) - { - foreach($changedStories as $changedStory) - { - $this->dao->update(TABLE_RELATION) - ->set('AVersion')->eq($changedStory->version) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere('AID')->eq($changedStory->id) - ->andWhere('BID')->eq($story->id) - ->exec(); - } - } - } - - /** - * Update the story order of plan. - * - * @param int $storyID - * @param string $oldPlanIDList - * @param string $planIDList - * @access public - * @return void - */ - public function updateStoryOrderOfPlan($storyID, $planIDList = '', $oldPlanIDList = '') - { - $planIDList = $planIDList ? explode(',', $planIDList) : array(); - $oldPlanIDList = $oldPlanIDList ? explode(',', $oldPlanIDList) : array(); - - /* Get the ids to be inserted and deleted by comparing plan ids. */ - $plansTobeInsert = array_diff($planIDList, $oldPlanIDList); - $plansTobeDelete = array_diff($oldPlanIDList, $planIDList); - - /* Delete old story sort of plan. */ - if(!empty($plansTobeDelete)) $this->dao->delete()->from(TABLE_PLANSTORY)->where('story')->eq($storyID)->andWhere('plan')->in($plansTobeDelete)->exec(); - - if(!empty($plansTobeInsert)) - { - /* Get last story order of plan list. */ - $maxOrders = $this->dao->select('plan, max(`order`) as `order`')->from(TABLE_PLANSTORY)->where('plan')->in($plansTobeInsert)->groupBy('plan')->fetchPairs(); - - foreach($plansTobeInsert as $planID) - { - /* Set story order in new plan. */ - $data = new stdClass(); - $data->plan = $planID; - $data->story = $storyID; - $data->order = zget($maxOrders, $planID, 0) + 1; - - $this->dao->replace(TABLE_PLANSTORY)->data($data)->exec(); - } - } - } - - /** - * Compute parent story estimate. - * - * @param int $storyID - * @access public - * @return bool - */ - public function computeEstimate($storyID) - { - if(!$storyID) return true; - - $stories = $this->dao->select('`id`,`estimate`,status')->from(TABLE_STORY)->where('parent')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll('id'); - if(empty($stories)) return true; - - $estimate = 0; - foreach($stories as $story) $estimate += $story->estimate; - $this->dao->update(TABLE_STORY)->set('estimate')->eq($estimate)->autoCheck()->where('id')->eq($storyID)->exec(); - return !dao::isError(); - } - - /** - * Batch update stories. - * - * @access public - * @return array. - */ - public function batchUpdate() - { - /* Init vars. */ - $stories = array(); - $allChanges = array(); - $now = helper::now(); - $data = fixer::input('post')->get(); - $storyIdList = $this->post->storyIdList ? $this->post->storyIdList : array(); - - /* Init $stories. */ - if(!empty($storyIdList)) - { - $oldStories = $this->getByList($storyIdList); - - /* Process the data if the value is 'ditto'. */ - foreach($storyIdList as $storyID) - { - if($data->pris[$storyID] == 'ditto') $data->pris[$storyID] = isset($prev['pri']) ? $prev['pri'] : 0; - if(isset($data->branches) and $data->branches[$storyID] == 'ditto') $data->branches[$storyID] = isset($prev['branch']) ? $prev['branch'] : 0; - if($data->modules[$storyID] == 'ditto') $data->modules[$storyID] = isset($prev['module']) ? $prev['module'] : 0; - if($data->plans[$storyID] == 'ditto') $data->plans[$storyID] = isset($prev['plan']) ? $prev['plan'] : ''; - if($data->sources[$storyID] == 'ditto') $data->sources[$storyID] = isset($prev['source']) ? $prev['source'] : ''; - if(isset($data->stages[$storyID]) and ($data->stages[$storyID] == 'ditto')) $data->stages[$storyID] = isset($prev['stage']) ? $prev['stage'] : ''; - if(isset($data->closedBys[$storyID]) and ($data->closedBys[$storyID] == 'ditto')) $data->closedBys[$storyID] = isset($prev['closedBy']) ? $prev['closedBy'] : ''; - if(isset($data->closedReasons[$storyID]) and ($data->closedReasons[$storyID] == 'ditto')) $data->closedReasons[$storyID] = isset($prev['closedReason']) ? $prev['closedReason'] : ''; - - $prev['pri'] = $data->pris[$storyID]; - $prev['branch'] = isset($data->branches[$storyID]) ? $data->branches[$storyID] : 0; - $prev['module'] = $data->modules[$storyID]; - $prev['plan'] = $data->plans[$storyID]; - $prev['source'] = $data->sources[$storyID]; - if(isset($data->stages[$storyID])) $prev['stage'] = $data->stages[$storyID]; - if(isset($data->closedBys[$storyID])) $prev['closedBy'] = $data->closedBys[$storyID]; - if(isset($data->closedReasons[$storyID])) $prev['closedReason'] = $data->closedReasons[$storyID]; - } - - $extendFields = $this->getFlowExtendFields(); - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - - $story = new stdclass(); - $story->id = $storyID; - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->status = $oldStory->status; - $story->color = $data->colors[$storyID]; - $story->title = $data->titles[$storyID]; - $story->estimate = $data->estimates[$storyID]; - $story->category = $data->category[$storyID]; - $story->pri = $data->pris[$storyID]; - $story->assignedTo = $data->assignedTo[$storyID]; - $story->assignedDate = $oldStory == $data->assignedTo[$storyID] ? $oldStory->assignedDate : $now; - $story->branch = isset($data->branches[$storyID]) ? $data->branches[$storyID] : 0; - $story->module = $data->modules[$storyID]; - $story->plan = $oldStories[$storyID]->parent < 0 ? '' : $data->plans[$storyID]; - $story->source = $data->sources[$storyID]; - $story->sourceNote = $data->sourceNote[$storyID]; - $story->keywords = $data->keywords[$storyID]; - $story->stage = isset($data->stages[$storyID]) ? $data->stages[$storyID] : $oldStory->stage; - $story->closedBy = isset($data->closedBys[$storyID]) ? $data->closedBys[$storyID] : $oldStory->closedBy; - $story->closedReason = isset($data->closedReasons[$storyID]) ? $data->closedReasons[$storyID] : $oldStory->closedReason; - $story->duplicateStory = isset($data->duplicateStories[$storyID]) ? $data->duplicateStories[$storyID] : $oldStory->duplicateStory; - $story->childStories = isset($data->childStoriesIDList[$storyID]) ? $data->childStoriesIDList[$storyID] : $oldStory->childStories; - $story->version = $story->title == $oldStory->title ? $oldStory->version : $oldStory->version + 1; - if($story->stage != $oldStory->stage) $story->stagedBy = (strpos('tested|verified|released|closed', $story->stage) !== false) ? $this->app->user->account : ''; - - if($story->title != $oldStory->title and $story->status != 'draft') $story->status = 'changed'; - if($story->closedBy != false and $oldStory->closedDate == '') $story->closedDate = $now; - if($story->closedReason != false and $oldStory->closedDate == '') $story->closedDate = $now; - if($story->closedBy != false or $story->closedReason != false) $story->status = 'closed'; - if($story->closedReason != false and $story->closedBy == false) $story->closedBy = $this->app->user->account; - - foreach($extendFields as $extendField) - { - $story->{$extendField->field} = $this->post->{$extendField->field}[$storyID]; - if(is_array($story->{$extendField->field})) $story->{$extendField->field} = join(',', $story->{$extendField->field}); - - $story->{$extendField->field} = htmlSpecialString($story->{$extendField->field}); - } - - $stories[$storyID] = $story; - } - - foreach($stories as $storyID => $story) - { - $oldStory = $oldStories[$storyID]; - - $this->dao->update(TABLE_STORY)->data($story) - ->autoCheck() - ->checkIF($story->closedBy, 'closedReason', 'notempty') - ->checkIF($story->closedReason == 'done', 'stage', 'notempty') - ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') - ->checkFlow() - ->where('id')->eq((int)$storyID) - ->exec(); - if($story->title != $oldStory->title) - { - $data = new stdclass(); - $data->story = $storyID; - $data->version = $story->version; - $data->title = $story->title; - $data->spec = $oldStory->spec; - $data->verify = $oldStory->verify; - $this->dao->insert(TABLE_STORYSPEC)->data($data)->exec(); - } - - if(!dao::isError()) - { - /* Update story sort of plan when story plan has changed. */ - if($oldStory->plan != $story->plan) $this->updateStoryOrderOfPlan($storyID, $story->plan, $oldStory->plan); - - $this->executeHooks($storyID); - if($story->type == 'story') $this->batchChangeStage(array($storyID), $story->stage); - if($story->closedReason == 'done') $this->loadModel('score')->create('story', 'close'); - $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - else - { - return print(js::error('story#' . $storyID . dao::getError(true))); - } - } - } - if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchEdit'); - return $allChanges; - } - - /** - * Review a story. - * - * @param int $storyID - * @access public - * @return bool - */ - public function review($storyID) - { - if($this->post->result == 'revert' and $this->post->preVersion == false) return print(js::alert($this->lang->story->mustChoosePreVersion)); - - if(strpos($this->config->story->review->requiredFields, 'comment') !== false and !$this->post->comment) - { - dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->comment); - return false; - } - - if($this->post->result == false) - { - dao::$errors[] = $this->lang->story->mustChooseResult; - return false; - } - - $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - $now = helper::now(); - $date = helper::today(); - $story = fixer::input('post') - ->setDefault('lastEditedBy', $this->app->user->account) - ->setDefault('lastEditedDate', $now) - ->setDefault('status', $oldStory->status) - ->setDefault('reviewedDate', $date) - ->stripTags($this->config->story->editor->review['id'], $this->config->allowedTags) - ->setIF(!$this->post->assignedTo, 'assignedTo', '') - ->setIF($this->post->result == 'revert', 'version', $this->post->preVersion) - ->setIF($this->post->result == 'clarify', 'assignedTo', $oldStory->lastEditedBy ? $oldStory->lastEditedBy : $oldStory->openedBy) - ->removeIF($this->post->result != 'reject', 'closedReason, duplicateStory, childStories') - ->removeIF($this->post->result == 'reject' and $this->post->closedReason != 'duplicate', 'duplicateStory') - ->removeIF($this->post->result == 'reject' and $this->post->closedReason != 'subdivided', 'childStories') - ->add('reviewedBy', $oldStory->reviewedBy . ',' . $this->app->user->account) - ->add('id', $storyID) - ->remove('result,preVersion,comment') - ->get(); - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->review['id'], $this->post->uid); - - /* Fix bug #671. */ - $this->lang->story->closedReason = $this->lang->story->rejectedReason; - - $this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($this->post->result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec(); - - $story = $this->updateStoryByReview($storyID, $oldStory, $story); - - $skipFields = ''; - $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); - if($isSuperReviewer === false) - { - $reviewers = $this->getReviewerPairs($storyID, $oldStory->version); - if(count($reviewers) > 1) $skipFields = 'closedReason'; - } - - $this->dao->update(TABLE_STORY)->data($story, $skipFields) - ->autoCheck() - ->batchCheck($this->config->story->review->requiredFields, 'notempty') - ->checkIF($this->post->result == 'reject', 'closedReason', 'notempty') - ->checkIF($this->post->result == 'reject' and $this->post->closedReason == 'duplicate', 'duplicateStory', 'notempty') - ->checkFlow() - ->where('id')->eq($storyID)->exec(); - if($this->post->result == 'revert') - { - $preTitle = $this->dao->select('title')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->eq($this->post->preVersion)->fetch('title'); - $this->dao->update(TABLE_STORY)->set('title')->eq($preTitle)->where('id')->eq($storyID)->exec(); - - /* Delete versions that is after this version. */ - $deleteVersion = array(); - for($version = $oldStory->version; $version > $story->version; $version --) $deleteVersion[] = $version; - if($deleteVersion) - { - $this->dao->delete()->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->andWHere('version')->in($deleteVersion)->exec(); - $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->in($deleteVersion)->exec(); - } - - $this->dao->delete()->from(TABLE_FILE)->where('objectType')->eq('story')->andWhere('objectID')->eq($storyID)->andWhere('extra')->eq($oldStory->version)->exec(); - } - if($this->post->result != 'reject') $this->setStage($storyID); - - if(isset($story->closedReason) and $isSuperReviewer === false) unset($story->closedReason); - return common::createChanges($oldStory, $story); - } - - /** - * Batch review stories. - * - * @param array $storyIdList - * @param string $result - * @param string $reason - * @access public - * @return array - */ - public function batchReview($storyIdList, $result, $reason) - { - $now = helper::now(); - $actions = array(); - $this->loadModel('action'); - - $oldStories = $this->getByList($storyIdList); - $hasResult = $this->dao->select('story,version,result')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->andWhere('reviewer')->eq($this->app->user->account)->andWhere('result')->ne('')->orderBy('version')->fetchAll('story'); - $reviewerList = $this->dao->select('story,reviewer,result,version')->from(TABLE_STORYREVIEW)->where('story')->in($storyIdList)->orderBy('version')->fetchGroup('story', 'reviewer'); - foreach($storyIdList as $storyID) - { - if(!$storyID) continue; - $oldStory = $oldStories[$storyID]; - $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); - - if($oldStory->status != 'draft' and $oldStory->status != 'changed') continue; - if(!in_array($this->app->user->account, array_keys($reviewerList[$storyID])) and $isSuperReviewer === false) continue; - if(isset($hasResult[$storyID]) and $hasResult[$storyID]->version == $oldStories[$storyID]->version) continue; - - $story = new stdClass(); - $story->reviewedDate = $now; - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->reviewedBy = $oldStory->reviewedBy . ',' . $this->app->user->account; - $story->status = $oldStory->status; - - $this->dao->update(TABLE_STORYREVIEW)->set('result')->eq($result)->set('reviewDate')->eq($now)->where('story')->eq($storyID)->andWhere('version')->eq($oldStory->version)->andWhere('reviewer')->eq($this->app->user->account)->exec(); - - /* Update the story status by review rules. */ - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - if($isSuperReviewer !== false) - { - $story = $this->superReview($storyID, $oldStory, $story, $result, $reason); - } - if(!array_diff(array_keys($reviewerList[$storyID]), $reviewedBy)) - { - $reviewerPairs = array(); - foreach($reviewerList[$storyID] as $reviewer => $reviewInfo) $reviewerPairs[$reviewer] = $reviewInfo->result; - $reviewerPairs[$this->app->user->account] = $result; - - $status = $this->setStatusByReviewRules($reviewerPairs); - $story->status = $status ? $status : $oldStory->status; - - if($story->status == 'closed') - { - $story->closedBy = $this->app->user->account; - $story->closedDate = $now; - $story->assignedTo = 'closed'; - $story->stage = 'closed'; - if($reason == 'done') $story->stage = 'released'; - } - } - - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq($storyID)->exec(); - $this->setStage($storyID); - - $story->id = $storyID; - $story->version = $oldStory->version; - $actions[$storyID] = $this->recordReviewAction($story, $result, $reason); - } - - return $actions; - } - - /** - * Recall the story review. - * - * @param int $storyID - * @access public - * @return void - */ - public function recall($storyID) - { - $story = $this->getById($storyID); - $this->dao->update(TABLE_STORY)->set('status')->eq('draft')->where('id')->eq($storyID)->exec(); - $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($story->version)->exec(); - } - - /** - * Subdivide story - * - * @param int $storyID - * @param array $stories - * @access public - * @return void - */ - public function subdivide($storyID, $stories) - { - $now = helper::now(); - $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - if($oldStory->type == 'requirement') - { - foreach($stories as $id) - { - $data = new stdclass(); - $data->product = $oldStory->product; - $data->AType = 'requirement'; - $data->relation = 'subdivideinto'; - $data->BType = 'story'; - $data->AID = $storyID; - $data->BID = $id; - $data->AVersion = $oldStory->version; - $data->BVersion = 1; - $data->extra = 1; - - $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); - - $data->AType = 'story'; - $data->relation = 'subdividedfrom'; - $data->BType = 'requirement'; - $data->AID = $id; - $data->BID = $storyID; - $data->AVersion = 1; - $data->BVersion = $oldStory->version; - - $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); - } - - if(dao::isError()) return print(js::error(dao::getError())); - - $isonlybody = isonlybody(); - unset($_GET['onlybody']); - return print(js::locate(helper::createLink('product', 'browse', "productID=$oldStory->product&branch=0&browseType=unclosed&queryID=0&type=story"), $isonlybody ? 'parent.parent' : 'parent')); - } - else - { - /* Set parent to child story. */ - $this->dao->update(TABLE_STORY)->set('parent')->eq($storyID)->where('id')->in($stories)->exec(); - $this->computeEstimate($storyID); - - /* Set childStories. */ - $childStories = join(',', $stories); - - $newStory = new stdClass(); - $newStory->parent = '-1'; - $newStory->plan = ''; - $newStory->lastEditedBy = $this->app->user->account; - $newStory->lastEditedDate = $now; - $newStory->childStories = trim($oldStory->childStories . ',' . $childStories, ','); - - /* Subdivide story. */ - $this->dao->update(TABLE_STORY)->data($newStory)->autoCheck()->where('id')->eq($storyID)->exec(); - - $changes = common::createChanges($oldStory, $newStory); - if($changes) - { - $actionID = $this->loadModel('action')->create('story', $storyID, 'createChildrenStory', '', $childStories); - $this->action->logHistory($actionID, $changes); - } - } - } - - /** - * Close a story. - * - * @param int $storyID - * @access public - * @return bool - */ - public function close($storyID) - { - $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - $now = helper::now(); - $story = fixer::input('post') - ->add('id', $storyID) - ->add('assignedTo', 'closed') - ->add('status', 'closed') - ->add('stage', 'closed') - ->setDefault('lastEditedBy', $this->app->user->account) - ->setDefault('lastEditedDate', $now) - ->setDefault('closedDate', $now) - ->setDefault('closedBy', $this->app->user->account) - ->setDefault('assignedDate', $now) - ->stripTags($this->config->story->editor->close['id'], $this->config->allowedTags) - ->removeIF($this->post->closedReason != 'duplicate', 'duplicateStory') - ->removeIF($this->post->closedReason != 'subdivided', 'childStories') - ->get(); - - if(!empty($story->duplicateStory)) - { - $duplicateStoryID = $this->dao->select('id')->from(TABLE_STORY)->where('id')->eq($story->duplicateStory)->fetch(); - if(empty($duplicateStoryID)) - { - dao::$errors[] = sprintf($this->lang->story->errorDuplicateStory, $story->duplicateStory); - return false; - } - } - - $this->lang->story->comment = $this->lang->comment; - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->close['id'], $this->post->uid); - $this->dao->update(TABLE_STORY)->data($story, 'comment') - ->autoCheck() - ->batchCheck($this->config->story->close->requiredFields, 'notempty') - ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') - ->checkFlow() - ->where('id')->eq($storyID)->exec(); - - /* Update parent story status and stage. */ - if($oldStory->parent > 0) - { - $this->updateParentStatus($storyID, $oldStory->parent); - $this->setStage($oldStory->parent); - } - if(!dao::isError()) - { - $this->setStage($storyID); - $this->loadModel('score')->create('story', 'close', $storyID); - } - return common::createChanges($oldStory, $story); - } - - /** - * Batch close story. - * - * @access public - * @return void - */ - public function batchClose() - { - /* Init vars. */ - $stories = array(); - $allChanges = array(); - $now = helper::now(); - $data = fixer::input('post')->get(); - $storyIdList = $data->storyIdList ? $data->storyIdList : array(); - - $oldStories = $this->getByList($storyIdList); - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - if($oldStory->parent == -1) continue; - if($oldStory->status == 'closed') continue; - - $story = new stdclass(); - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->closedBy = $this->app->user->account; - $story->closedDate = $now; - $story->assignedTo = 'closed'; - $story->assignedDate = $now; - $story->status = 'closed'; - $story->stage = 'closed'; - - $story->closedReason = $data->closedReasons[$storyID]; - $story->duplicateStory = $data->duplicateStoryIDList[$storyID] ? $data->duplicateStoryIDList[$storyID] : $oldStory->duplicateStory; - $story->childStories = $data->childStoriesIDList[$storyID] ? $data->childStoriesIDList[$storyID] : $oldStory->childStories; - - if($story->closedReason != 'done') $story->plan = ''; - - $stories[$storyID] = $story; - unset($story); - } - - foreach($stories as $storyID => $story) - { - if(!$story->closedReason) continue; - - $oldStory = $oldStories[$storyID]; - - $this->dao->update(TABLE_STORY)->data($story) - ->autoCheck() - ->checkIF($story->closedReason == 'duplicate', 'duplicateStory', 'notempty') - ->where('id')->eq($storyID)->exec(); - - if(!dao::isError()) - { - /* Update parent story status. */ - if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); - $this->setStage($storyID); - $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - else - { - return print(js::error('story#' . $storyID . dao::getError(true))); - } - if(!dao::isError()) $this->loadModel('score')->create('story', 'close', $storyID); - } - - return $allChanges; - } - - /** - * Batch change the module of story. - * - * @param array $storyIdList - * @param int $moduleID - * @access public - * @return array - */ - public function batchChangeModule($storyIdList, $moduleID) - { - $now = helper::now(); - $allChanges = array(); - $oldStories = $this->getByList($storyIdList); - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - if($moduleID == $oldStory->module) continue; - - $story = new stdclass(); - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->module = $moduleID; - - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - return $allChanges; - } - - /** - * Batch change the plan of story. - * - * @param array $storyIdList - * @param int $planID - * @access public - * @return array - */ - public function batchChangePlan($storyIdList, $planID, $oldPlanID = 0) - { - /* Prepare data. */ - $now = helper::now(); - $allChanges = array(); - $oldStories = $this->getByList($storyIdList); - $plan = $this->loadModel('productplan')->getById($planID); - $oldStoryStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in($storyIdList)->fetchGroup('story', 'branch'); - - /* Cycle every story and process it's plan and stage. */ - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - if($oldStory->branch != BRANCH_MAIN and $oldStory->branch != $plan->branch and $plan->branch != BRANCH_MAIN) continue; - - /* Ignore parent story, closed story and story linked to this plan already. */ - if($oldStory->parent < 0) continue; - if($oldStory->status == 'closed') continue; - if(strpos(",{$oldStory->plan},", ",$planID,") !== false) continue; - - /* Init story and set last edited data. */ - $story = new stdclass(); - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - - /* Remove old plan from the plan field. */ - if($oldPlanID) $story->plan = trim(str_replace(",$oldPlanID,", ',', ",$oldStory->plan,"), ','); - - /* Update the order of the story in the plan. */ - $this->updateStoryOrderOfPlan($storyID, $planID, $oldStory->plan); - - /* Replace plan field if product is normal or not linked to plan or story linked to a branch. */ - if($this->session->currentProductType == 'normal') $story->plan = $planID; - if(empty($oldPlanID)) $story->plan = $planID; - if($oldStory->branch) $story->plan = $planID; - - /* Append the plan id to plan field if product is multi and story is all branch. */ - if($this->session->currentProductType != 'normal' and empty($story->branch) and $planID != 0) - { - $branchPlanPairs = $this->dao->select('branch, id as plan')->from(TABLE_PRODUCTPLAN) - ->where('product')->eq($oldStory->product) - ->andWhere('id')->in($oldStory->plan) - ->fetchPairs(); - if(isset($branchPlanPairs[$plan->branch])) $branchPlanPairs[$plan->branch] = $planID; - - $story->plan = $oldStory->plan ? ',' . implode(',', $branchPlanPairs) : ",$planID"; - } - - /* Change stage. */ - if($planID) - { - if($oldStory->stage == 'wait') $story->stage = 'planned'; - if($this->session->currentProductType and $this->session->currentProductType != 'normal' and $oldStory->branch == 0) - { - if(!isset($oldStoryStages[$storyID][$plan->branch])) - { - $story->stage = 'planned'; - $newStoryStage = new stdclass(); - $newStoryStage->story = $storyID; - $newStoryStage->branch = $plan->branch; - $newStoryStage->stage = $story->stage; - $this->dao->insert(TABLE_STORYSTAGE)->data($newStoryStage)->autoCheck()->exec(); - } - } - } - - /* Update story and recompute stage. */ - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - - if(!$planID) $this->setStage($storyID); - - if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - return $allChanges; - } - - /** - * Batch change branch. - * - * @param array $storyIdList - * @param int $branchID - * @param string $confirm - * @param array $plans - * @access public - * @return array - */ - public function batchChangeBranch($storyIdList, $branchID, $confirm = '', $plans = array()) - { - $now = helper::now(); - $allChanges = array(); - $oldStories = $this->getByList($storyIdList); - $story = current($oldStories); - $productID = $story->product; - $mainModules = $this->dao->select('id')->from(TABLE_MODULE) - ->where('root')->eq($productID) - ->andWhere('branch')->eq(0) - ->andWhere('type')->eq('story') - ->fetchPairs('id'); - - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - - $story = new stdclass(); - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->branch = $branchID; - $story->module = ($oldStory->branch != $branchID and !in_array($oldStory->module, $mainModules)) ? 0 : $oldStory->module; - - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - if(!dao::isError()) - { - if($confirm == 'yes') - { - $planIdList = ''; - $conflictPlanIdList = ''; - - /* Determine whether there is a conflict between the branch of the story and the linked plan. */ - if($oldStory->branch != $branchID and $branchID != BRANCH_MAIN and isset($plans[$storyID])) - { - foreach($plans[$storyID] as $planID => $plan) - { - if($plan->branch != $branchID) - { - $conflictPlanIdList .= $planID . ','; - } - else - { - $planIdList .= $planID . ','; - } - } - - /* If there is a conflict in the linked plan when the branch story to be modified, the linked with the conflicting plan will be removed. */ - if($conflictPlanIdList) - { - $story->plan = $planIdList; - $this->dao->delete()->from(TABLE_PLANSTORY)->where('story')->eq($storyID)->andWhere('plan')->in($conflictPlanIdList)->exec(); - $this->dao->update(TABLE_STORY)->set('plan')->eq($planIdList)->where('id')->eq($storyID)->exec(); - } - } - } - $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - } - return $allChanges; - } - - /** - * Batch change the stage of story. - * - * @param $storyIdList - * @param $stage - * - * @access public - * @return array - */ - public function batchChangeStage($storyIdList, $stage) - { - $now = helper::now(); - $allChanges = array(); - $account = $this->app->user->account; - $oldStories = $this->getByList($storyIdList); - $ignoreStories = ''; - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - if($oldStory->status == 'draft' or $oldStory->status == 'closed') - { - $ignoreStories .= "#{$storyID} "; - continue; - } - - $story = new stdclass(); - $story->lastEditedBy = $account; - $story->lastEditedDate = $now; - $story->stage = $stage; - $story->stagedBy = $account; - - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - $this->dao->update(TABLE_STORYSTAGE)->set('stage')->eq($stage)->set('stagedBy')->eq($account)->where('story')->eq((int)$storyID)->exec(); - if(!dao::isError()) $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - if($ignoreStories) echo js::alert(sprintf($this->lang->story->ignoreChangeStage, $ignoreStories)); - return $allChanges; - } - - /** - * Batch to task. - * - * @param int $executionID - * @param int $projectID - * @access public - * @return bool|array - */ - public function batchToTask($executionID, $projectID = 0) - { - /* load Module and get the data from the post and get the current time. */ - $this->loadModel('action'); - $data = fixer::input('post')->get(); - $now = helper::now(); - - /* Judgment of required items. */ - if(empty($data->type)) - { - dao::$errors['type'] = sprintf($this->lang->error->notempty, $this->lang->task->type); - } - - if(isset($data->hourPointValue) and empty($data->hourPointValue)) - { - dao::$errors['hourPointValue'] = sprintf($this->lang->error->notempty, $this->lang->story->convertRelations); - } - elseif(isset($data->hourPointValue) and !is_numeric($data->hourPointValue)) - { - dao::$errors['hourPointValue'] = sprintf($this->lang->error->float, $this->lang->story->convertRelations); - } - if(dao::isError()) return false; - - /* Create tasks. */ - $tasks = array(); - $stories = empty($data->storyIdList) ? array() : $this->getByList($data->storyIdList); - foreach($stories as $story) - { - if(strpos('draft,closed', $story->status) !== false) continue; - - $task = new stdclass(); - $task->execution = $executionID; - $task->project = $projectID ? $projectID : 0; - $task->name = $story->title; - $task->story = $story->id; - $task->type = $data->type; - $task->estimate = isset($data->hourPointValue) ? ($story->estimate * $data->hourPointValue) : $story->estimate; - $task->left = $task->estimate; - $task->openedBy = $this->app->user->account; - $task->openedDate = $now; - $task->storyVersion = $story->version; - - if(isset($data->fields)) - { - foreach($data->fields as $field) - { - $task->$field = $story->$field; - - if($field == 'assignedTo') $task->assignedDate = $now; - if($field == 'spec') - { - unset($task->$field); - $task->desc = $story->$field; - } - } - } - - $this->dao->insert(TABLE_TASK)->data($task) - ->autoCheck() - ->checkIF($task->estimate != '', 'estimate', 'float') - ->exec(); - - if(dao::isError()) return false; - $taskID = $this->dao->lastInsertID(); - $tasks[] = $taskID; - $this->action->create('task', $taskID, 'Opened', ''); - } - - $this->loadModel('kanban')->updateLane($executionID, 'task'); - return $tasks; - } - - /** - * Assign story. - * - * @param int $storyID - * @access public - * @return array - */ - public function assign($storyID) - { - $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - $now = helper::now(); - $assignedTo = $this->post->assignedTo; - if($assignedTo == $oldStory->assignedTo) return array(); - - $story = fixer::input('post') - ->add('id', $storyID) - ->add('lastEditedBy', $this->app->user->account) - ->add('lastEditedDate', $now) - ->add('assignedDate', $now) - ->stripTags($this->config->story->editor->assignto['id'], $this->config->allowedTags) - ->remove('comment') - ->get(); - - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->assignto['id'], $this->post->uid); - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq((int)$storyID)->exec(); - if(!dao::isError()) return common::createChanges($oldStory, $story); - return false; - } - - /** - * Batch assign to. - * - * @access public - * @return array - */ - public function batchAssignTo() - { - $now = helper::now(); - $allChanges = array(); - $storyIdList = $this->post->storyIdList; - $assignedTo = $this->post->assignedTo; - $oldStories = $this->getByList($storyIdList); - foreach($storyIdList as $storyID) - { - $oldStory = $oldStories[$storyID]; - if($assignedTo == $oldStory->assignedTo) continue; - - $story = new stdclass(); - $story->lastEditedBy = $this->app->user->account; - $story->lastEditedDate = $now; - $story->assignedTo = $assignedTo; - $story->assignedDate = $now; - - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->where('id')->eq((int)$storyID)->exec(); - - $allChanges[$storyID] = common::createChanges($oldStory, $story); - } - return $allChanges; - } - - /** - * Activate a story. - * - * @param int $storyID - * @access public - * @return bool - */ - public function activate($storyID) - { - $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - $now = helper::now(); - $story = fixer::input('post') - ->add('id', $storyID) - ->add('closedBy', '') - ->add('closedReason', '') - ->add('closedDate', '0000-00-00') - ->add('reviewedBy', '') - ->add('reviewedDate', '0000-00-00') - ->add('duplicateStory', 0) - ->add('childStories', '') - ->setDefault('lastEditedBy', $this->app->user->account) - ->setDefault('lastEditedDate', $now) - ->setDefault('assignedDate', $now) - ->setDefault('activatedDate', $now) - ->stripTags($this->config->story->editor->activate['id'], $this->config->allowedTags) - ->remove('comment') - ->get(); - $story = $this->loadModel('file')->processImgURL($story, $this->config->story->editor->activate['id'], $this->post->uid); - $this->dao->update(TABLE_STORY)->data($story)->autoCheck()->checkFlow()->where('id')->eq($storyID)->exec(); - - if($this->post->status == 'active') $this->dao->delete()->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->exec(); - - $this->setStage($storyID); - - /* Update parent story status. */ - if($oldStory->parent > 0) $this->updateParentStatus($storyID, $oldStory->parent); - - return common::createChanges($oldStory, $story); - } - - /** - * Set stage of a story. - * - * @param int $storyID - * @access public - * @return bool - */ - public function setStage($storyID) - { - $this->loadModel('kanban'); - - $storyID = (int)$storyID; - $account = $this->app->user->account; - - /* Get projects which status is doing. */ - $oldStages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->fetchAll('branch'); - $this->dao->delete()->from(TABLE_STORYSTAGE)->where('story')->eq($storyID)->exec(); - - $story = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - if(!empty($story->stagedBy) and $story->status != 'closed') return false; - - $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fetch(); - $executions = $this->dao->select('t1.project,t3.branch')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') - ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t3')->on('t1.project = t3.project') - ->where('t1.story')->eq($storyID) - ->andWhere('t2.deleted')->eq(0) - ->fetchPairs('project', 'branch'); - - $hasBranch = ($product and $product->type != 'normal' and empty($story->branch)); - $stages = array(); - if($hasBranch and $story->plan) - { - $plans = $this->dao->select('*')->from(TABLE_PRODUCTPLAN)->where('id')->in($story->plan)->fetchPairs('branch', 'branch'); - foreach($plans as $branch) $stages[$branch] = 'planned'; - } - - /* When the status is closed, stage is also changed to closed. */ - if($story->status == 'closed') - { - $this->dao->update(TABLE_STORY)->set('stage')->eq('closed')->where('id')->eq($storyID)->exec(); - foreach($stages as $branch => $stage) $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq('closed')->exec(); - foreach($executions as $execution => $branch) - { - $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq('closed')->exec(); - $this->kanban->updateLane($execution, 'story', $storyID); - } - return false; - } - - /* If no executions, in plan, stage is planned. No plan, wait. */ - if(!$executions) - { - $this->dao->update(TABLE_STORY)->set('stage')->eq('wait')->where('id')->eq($storyID)->andWhere('plan')->eq('')->exec(); - - foreach($stages as $branch => $stage) - { - if(isset($oldStages[$branch])) - { - $oldStage = $oldStages[$branch]; - if(!empty($oldStage->stagedBy)) - { - $this->dao->replace(TABLE_STORYSTAGE)->data($oldStage)->exec(); - continue; - } - } - $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq($stage)->exec(); - } - $this->dao->update(TABLE_STORY)->set('stage')->eq('planned')->where('id')->eq($storyID)->andWhere("(plan != '' AND plan != '0')")->exec(); - } - - if($hasBranch) - { - foreach($executions as $executionID => $branch) $stages[$branch] = 'projected'; - } - - /* Search related tasks. */ - $tasks = $this->dao->select('type,execution,status')->from(TABLE_TASK) - ->where('execution')->in(array_keys($executions)) - ->andWhere('type')->in('devel,test') - ->andWhere('story')->eq($storyID) - ->andWhere('deleted')->eq(0) - ->andWhere('status')->ne('cancel') - ->andWhere('closedReason')->ne('cancel') - ->fetchGroup('type'); - - /* No tasks, then the stage is projected. */ - if(!$tasks and $executions) - { - foreach($stages as $branch => $stage) - { - if(isset($oldStages[$branch])) - { - $oldStage = $oldStages[$branch]; - if(!empty($oldStage->stagedBy)) - { - $this->dao->replace(TABLE_STORYSTAGE)->data($oldStage)->exec(); - continue; - } - } - $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq('projected')->exec(); - } - $this->dao->update(TABLE_STORY)->set('stage')->eq('projected')->where('id')->eq($storyID)->exec(); - } - - /* Get current stage and set as default value. */ - $currentStage = $story->stage; - $stage = $currentStage; - - /* Cycle all tasks, get counts of every type and every status. */ - $branchStatusList = array(); - $branchDevelTasks = array(); - $branchTestTasks = array(); - $statusList['devel'] = array('wait' => 0, 'doing' => 0, 'done'=> 0, 'pause' => 0); - $statusList['test'] = array('wait' => 0, 'doing' => 0, 'done'=> 0, 'pause' => 0); - foreach($tasks as $type => $typeTasks) - { - foreach($typeTasks as $task) - { - $status = $task->status ? $task->status : 'wait'; - $status = $status == 'closed' ? 'done' : $status; - - $branch = $executions[$task->execution]; - if(!isset($branchStatusList[$branch])) $branchStatusList[$branch] = $statusList; - if(!isset($branchStatusList[$branch][$task->type])) $branchStatusList[$branch][$task->type] = array(); - if(!isset($branchStatusList[$branch][$task->type][$status])) $branchStatusList[$branch][$task->type][$status] = 0; - $branchStatusList[$branch][$task->type][$status] ++; - if($type == 'devel') - { - if(!isset($branchDevelTasks[$branch])) $branchDevelTasks[$branch] = 0; - $branchDevelTasks[$branch] ++; - } - elseif($type == 'test') - { - if(!isset($branchTestTasks[$branch])) $branchTestTasks[$branch] = 0; - $branchTestTasks[$branch] ++; - } - } - } - - /** - * Judge stage according to the devel and test tasks' status. - * - * 1. one doing devel task, all test tasks waiting, set stage as developing. - * 2. some devel tasks done, all test tasks not done, set stage as developing. - * 3. all devel tasks done, all test tasks waiting, set stage as developed. - * 4. one test task doing, set stage as testing. - * 5. all test tasks done, still some devel tasks not done(wait, doing), set stage as testing. - * 6. all test tasks done, all devel tasks done, set stage as tested. - */ - foreach($branchStatusList as $branch => $statusList) - { - $stage = 'projected'; - $testTasks = isset($branchTestTasks[$branch]) ? $branchTestTasks[$branch] : 0; - $develTasks = isset($branchDevelTasks[$branch]) ? $branchDevelTasks[$branch] : 0; - if($statusList['devel']['doing'] > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developing'; - if($statusList['devel']['wait'] > 0 and $statusList['devel']['done'] > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developing'; - if(($statusList['devel']['doing'] > 0 or ($statusList['devel']['wait'] > 0 and $statusList['devel']['done'] > 0)) and $statusList['test']['wait'] > 0 and $statusList['test']['done'] > 0) $stage = 'developing'; - if($statusList['devel']['done'] == $develTasks and $develTasks > 0 and $statusList['test']['wait'] == $testTasks) $stage = 'developed'; - if($statusList['devel']['done'] == $develTasks and $develTasks > 0 and $statusList['test']['wait'] > 0 and $statusList['test']['done'] > 0) $stage = 'testing'; - if($statusList['test']['doing'] > 0 or $statusList['test']['pause'] > 0) $stage = 'testing'; - if(($statusList['devel']['wait'] > 0 or $statusList['devel']['doing'] > 0) and $statusList['test']['done'] == $testTasks and $testTasks > 0) $stage = 'testing'; - if($statusList['devel']['done'] == $develTasks and $statusList['test']['done'] == $testTasks and $testTasks > 0) $stage = 'tested'; - - $stages[$branch] = $stage; - } - - $releases = $this->dao->select('*')->from(TABLE_RELEASE)->where("CONCAT(',', stories, ',')")->like("%,$storyID,%")->andWhere('deleted')->eq(0)->fetchPairs('branch', 'branch'); - foreach($releases as $branch) $stages[$branch] = 'released'; - - $currentStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - if($story->stage != $currentStory->stage) - { - foreach($executions as $executionID => $branch) - { - $this->kanban->updateLane($executionID, 'story', $storyID); - } - } - - if(empty($stages)) return; - if($hasBranch) - { - $stageList = join(',', array_keys($this->lang->story->stageList)); - $minStagePos = strlen($stageList); - $minStage = ''; - foreach($stages as $branch => $stage) - { - $this->dao->replace(TABLE_STORYSTAGE)->set('story')->eq($storyID)->set('branch')->eq($branch)->set('stage')->eq($stage)->exec(); - if(isset($oldStages[$branch])) - { - $oldStage = $oldStages[$branch]; - if(!empty($oldStage->stagedBy)) - { - $this->dao->replace(TABLE_STORYSTAGE)->data($oldStage)->exec(); - $stage = $oldStage->$stage; - } - } - if(strpos($stageList, $stage) !== false and strpos($stageList, $stage) < $minStagePos) - { - $minStage = $stage; - $minStagePos = strpos($stageList, $stage); - } - } - $this->dao->update(TABLE_STORY)->set('stage')->eq($minStage)->where('id')->eq($storyID)->exec(); - } - else - { - $this->dao->update(TABLE_STORY)->set('stage')->eq(current($stages))->where('id')->eq($storyID)->exec(); - } - - $currentStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch(); - if($story->stage != $currentStory->stage) - { - foreach($executions as $executionID => $branch) - { - $this->kanban->updateLane($executionID, 'story', $storyID); - } - } - - return; - } - - /** - * Get stories to link. - * - * @param int $storyID - * @param string $type - * @param string $browseType - * @param int $queryID - * @param varchar $storyType - * @access public - * @return array - */ - public function getStories2Link($storyID, $type = 'linkStories', $browseType = 'bySearch', $queryID = 0, $storyType = 'story') - { - $story = $this->getById($storyID); - $linkedStories = $this->getRelation($storyID, $story->type); - $linkedStories = empty($linkedStories) ? array() : $linkedStories; - $tmpStoryType = $storyType == 'story' ? 'requirement' : 'story'; - - if($browseType == 'bySearch') - { - $stories2Link = $this->getBySearch($story->product, $story->branch, $queryID, 'id', '', $tmpStoryType); - foreach($stories2Link as $key => $story2Link) - { - if($story2Link->id == $storyID) unset($stories2Link[$key]); - if(in_array($story2Link->id, explode(',', $story->$type))) unset($stories2Link[$key]); - } - } - else - { - $status = $storyType == 'story' ? 'active' : 'all'; - $stories2Link = $this->getProductStories($story->product, $story->branch, 0, $status, $tmpStoryType, $orderBy = 'id_desc'); - } - - foreach($stories2Link as $id => $story) - { - if(in_array($story->id, array_keys($linkedStories))) unset($stories2Link[$id]); - if($storyType == 'story' && $story->status == 'draft') unset($stories2Link[$id]); - } - - return $stories2Link; - } - - /** - * Get stories list of a product. - * - * @param int $productID - * @param int $branch - * @param array|string $moduleIdList - * @param string $status - * @param string $type requirement|story - * @param string $orderBy - * @param array|string $excludeStories - * @param object $pager - * @param bool $hasParent - * - * @access public - * @return array - */ - public function getProductStories($productID = 0, $branch = 0, $moduleIdList = 0, $status = 'all', $type = 'story', $orderBy = 'id_desc', $hasParent = true, $excludeStories = '', $pager = null) - { - if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getStories(); - - $stories = array(); - $branchProducts = array(); - $normalProducts = array(); - $productList = $this->dao->select('*')->from(TABLE_PRODUCT)->where('id')->in($productID)->fetchAll('id'); - foreach($productList as $product) - { - if($product->type != 'normal') - { - $branchProducts[$product->id] = $product->id; - continue; - } - - $normalProducts[$product->id] = $product->id; - } - - $productQuery = '('; - if(!empty($normalProducts)) $productQuery .= '`product` ' . helper::dbIN(array_keys($normalProducts)); - if(!empty($branchProducts)) - { - if(!empty($normalProducts)) $productQuery .= " OR "; - $productQuery .= "(`product` " . helper::dbIN(array_keys($branchProducts)); - - if($branch !== 'all') - { - if(is_array($branch)) $branch = join(',', $branch); - $productQuery .= " AND `branch` " . helper::dbIN($branch); - } - $productQuery .= ')'; - } - if(empty($normalProducts) and empty($branchProducts)) $productQuery .= '1 = 1'; - $productQuery .= ') '; - - $stories = $this->dao->select('*')->from(TABLE_STORY) - ->where('product')->in($productID) - ->andWhere($productQuery) - ->beginIF(!$hasParent)->andWhere("parent")->ge(0)->fi() - ->beginIF(!empty($moduleIdList))->andWhere('module')->in($moduleIdList)->fi() - ->beginIF(!empty($excludeStories))->andWhere('id')->notIN($excludeStories)->fi() - ->beginIF($status and $status != 'all')->andWhere('status')->in($status)->fi() - ->andWhere('type')->eq($type) - ->andWhere('deleted')->eq(0) - ->orderBy($orderBy) - ->page($pager) - ->fetchAll('id'); - - return $this->mergePlanTitle($productID, $stories, $branch, $type); - } - - /** - * Get stories pairs of a product. - * - * @param int $productID - * @param string|int $branch - * @param array|string $moduleIdList - * @param string $status - * @param string $order - * @param int $limit - * @param string $type - * @param string $storyType requirement|story - * @param bool $hasParent - * @access public - * @return array - */ - public function getProductStoryPairs($productID = 0, $branch = 'all', $moduleIdList = 0, $status = 'all', $order = 'id_desc', $limit = 0, $type = 'full', $storyType = 'story', $hasParent = true) - { - $stories = $this->dao->select('t1.id, t1.title, t1.module, t1.pri, t1.estimate, t2.name AS product') - ->from(TABLE_STORY)->alias('t1')->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id') - ->where('1=1') - ->beginIF($productID)->andWhere('t1.product')->in($productID)->fi() - ->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi() - ->beginIF($branch !== 'all')->andWhere('t1.branch')->in("0,$branch")->fi() - ->beginIF(!$hasParent)->andWhere('t1.parent')->ge(0)->fi() - ->beginIF($status and $status != 'all')->andWhere('t1.status')->in($status)->fi() - ->andWhere('t1.type')->eq($storyType) - ->andWhere('t1.deleted')->eq(0) - ->orderBy($order) - ->fetchAll(); - if(!$stories) return array(); - return $this->formatStories($stories, $type, $limit); - } - - /** - * Get stories by assignedTo. - * - * @param int $productID - * @param string $branch - * @param string $account - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getByAssignedTo($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'assignedTo', $account, $type, $orderBy, $pager); - } - - /** - * Get stories by openedBy. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $account - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getByOpenedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'openedBy', $account, $type, $orderBy, $pager); - } - - /** - * Get stories by reviewedBy. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $account - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getByReviewedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'reviewedBy', $account, $type, $orderBy, $pager, 'include'); - } - - /** - * Get stories which need to review. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $account - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getByReviewBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'reviewBy', $account, $type, $orderBy, $pager); - } - - /** - * Get stories by closedBy. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $account - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @return array - */ - public function getByClosedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'closedBy', $account, $type, $orderBy, $pager); - } - - /** - * Get stories by status. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $status - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getByStatus($productID, $branch, $modules, $status, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'status', $status, $type, $orderBy, $pager); - } - - /** - * Get stories by plan. - * - * @param int $productID - * @param int $branch - * @param array $modules - * @param int $plan - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * - * @access public - * @return array - */ - public function getByPlan($productID, $branch, $modules, $plan, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'plan', $plan, $type, $orderBy, $pager); - } - - /** - * Get stories by assignedBy. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $account - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getByAssignedBy($productID, $branch, $modules, $account, $type = 'story', $orderBy = '', $pager = null) - { - return $this->getByField($productID, $branch, $modules, 'assignedBy', $account, $type, $orderBy, $pager); - } - - /** - * Get stories by a field. - * - * @param int $productID - * @param int|string $branch - * @param string $modules - * @param string $fieldName - * @param mixed $fieldValue - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @param string $operator equal|include - * @access public - * @return array - */ - public function getByField($productID, $branch, $modules, $fieldName, $fieldValue, $type = 'story', $orderBy = '', $pager = null, $operator = 'equal') - { - if(!$this->loadModel('common')->checkField(TABLE_STORY, $fieldName) and $fieldName != 'reviewBy' and $fieldName != 'assignedBy') return array(); - - $actionIDList = array(); - if($fieldName == 'assignedBy') $actionIDList = $this->dao->select('objectID')->from(TABLE_ACTION)->where('objectType')->eq('story')->andWhere('action')->eq('assigned')->andWhere('actor')->eq($fieldValue)->fetchPairs('objectID', 'objectID'); - - $sql = $this->dao->select('t1.*')->from(TABLE_STORY)->alias('t1'); - if($fieldName == 'reviewBy') $sql = $sql->leftJoin(TABLE_STORYREVIEW)->alias('t2')->on('t1.id = t2.story and t1.version = t2.version'); - - $stories = $sql->where('t1.product')->in($productID) - ->andWhere('t1.deleted')->eq(0) - ->andWhere('t1.type')->eq($type) - ->beginIF($branch != 'all')->andWhere("t1.branch")->eq($branch)->fi() - ->beginIF($modules)->andWhere("t1.module")->in($modules)->fi() - ->beginIF($operator == 'equal' and $fieldName != 'reviewBy' and $fieldName != 'assignedBy')->andWhere('t1.' . $fieldName)->eq($fieldValue)->fi() - ->beginIF($operator == 'include' and $fieldName != 'reviewBy' and $fieldName != 'assignedBy')->andWhere('t1.' . $fieldName)->like("%$fieldValue%")->fi() - ->beginIF($fieldName == 'reviewBy') - ->andWhere('t2.reviewer')->eq($this->app->user->account) - ->andWhere('t2.result')->eq('') - ->andWhere('t1.status')->in('draft,changed') - ->fi() - ->beginIF($fieldName == 'assignedBy')->andWhere('t1.id')->in($actionIDList)->andWhere('t1.status')->ne('closed')->fi() - ->orderBy($orderBy) - ->page($pager) - ->fetchAll('id'); - return $this->mergePlanTitle($productID, $stories, $branch, $type); - } - - /** - * Get to be closed stories. - * - * @param int $productID - * @param int $branch - * @param string $modules - * @param string $type requirement|story - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function get2BeClosed($productID, $branch, $modules, $type = 'story', $orderBy = '', $pager = null) - { - $stories = $this->dao->select('*')->from(TABLE_STORY) - ->where('product')->in($productID) - ->andWhere('type')->eq($type) - ->beginIF($branch)->andWhere("branch")->eq($branch)->fi() - ->beginIF($modules)->andWhere("module")->in($modules)->fi() - ->andWhere('deleted')->eq(0) - ->andWhere('stage')->in('developed,released') - ->andWhere('status')->ne('closed') - ->orderBy($orderBy) - ->page($pager) - ->fetchAll('id'); - return $this->mergePlanTitle($productID, $stories, $branch, $type); - } - - /** - * Get stories through search. - * - * @access public - * @param int $productID - * @param int|string $branch - * @param int $queryID - * @param string $orderBy - * @param string $executionID - * @param string $type requirement|story - * @param string $excludeStories - * @param object $pager - * @access public - * @return array - */ - public function getBySearch($productID, $branch = '', $queryID = 0, $orderBy = '', $executionID = '', $type = 'story', $excludeStories = '', $pager = null) - { - $this->loadModel('product'); - $executionID = empty($executionID) ? 0 : $executionID; - $products = empty($executionID) ? $this->product->getList() : $this->product->getProducts($executionID); - - $query = $queryID ? $this->loadModel('search')->getQuery($queryID) : ''; - - /* Get the sql and form status from the query. */ - if($query) - { - $this->session->set('storyQuery', $query->sql); - $this->session->set('storyForm', $query->form); - } - if($this->session->storyQuery == false) $this->session->set('storyQuery', ' 1 = 1'); - - $allProduct = "`product` = 'all'"; - $storyQuery = $this->session->storyQuery; - $queryProductID = $productID; - if(strpos($storyQuery, $allProduct) !== false) - { - $storyQuery = str_replace($allProduct, '1', $storyQuery); - $queryProductID = 'all'; - } - - $storyQuery = $storyQuery . ' AND `product` ' . helper::dbIN(array_keys($products)); - - if($excludeStories) $storyQuery = $storyQuery . ' AND `id` NOT ' . helper::dbIN($excludeStories); - if($this->app->moduleName == 'productplan') $storyQuery .= " AND `status` NOT IN ('closed') AND `parent` >= 0 "; - $allBranch = "`branch` = 'all'"; - if($executionID != '') - { - $normalProducts = array(); - $branchProducts = array(); - foreach($products as $product) - { - if($product->type != 'normal') - { - $branchProducts[$product->id] = $product; - continue; - } - - $normalProducts[$product->id] = $product; - } - - $storyQuery .= ' AND ('; - if(!empty($normalProducts)) $storyQuery .= '`product` ' . helper::dbIN(array_keys($normalProducts)); - if(!empty($branchProducts)) - { - $branches = array(BRANCH_MAIN => BRANCH_MAIN); - if($branch === '') - { - foreach($branchProducts as $product) - { - foreach($product->branches as $branchID) $branches[$branchID] = $branchID; - } - } - else - { - $branches[$branch] = $branch; - } - - $branches = join(',', $branches); - if(!empty($normalProducts)) $storyQuery .= " OR "; - $storyQuery .= "(`product` " . helper::dbIN(array_keys($branchProducts)) . " AND `branch` " . helper::dbIN($branches) . ")"; - } - if(empty($normalProducts) and empty($branchProducts)) $storyQuery .= '1 = 1'; - $storyQuery .= ') '; - - if($this->app->moduleName == 'release' or $this->app->moduleName == 'build') - { - $storyQuery .= " AND `status` NOT IN ('draft')"; // Fix bug #990. - } - else - { - $storyQuery .= " AND `status` NOT IN ('draft', 'closed')"; - } - - if($this->app->rawModule == 'build' and $this->app->rawMethod == 'linkstory') $storyQuery .= " AND `parent` != '-1'"; - } - elseif(strpos($storyQuery, $allBranch) !== false) - { - $storyQuery = str_replace($allBranch, '1', $storyQuery); - } - elseif($branch !== 'all' and $queryProductID != 'all') - { - if($branch and strpos($storyQuery, '`branch` =') === false) $storyQuery .= " AND `branch` in($branch)"; - } - $storyQuery = preg_replace("/`plan` +LIKE +'%([0-9]+)%'/i", "CONCAT(',', `plan`, ',') LIKE '%,$1,%'", $storyQuery); - - return $this->getBySQL($queryProductID, $storyQuery, $orderBy, $pager, $type); - } - - /** - * Get stories by a sql. - * - * @param int $productID - * @param string $sql - * @param string $orderBy - * @param object $pager - * @param string $type requirement|story - * @access public - * @return array - */ - public function getBySQL($productID, $sql, $orderBy, $pager = null, $type = 'story') - { - /* Get plans. */ - $plans = $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) - ->where('deleted')->eq('0') - ->beginIF($productID != 'all' and $productID != '')->andWhere('product')->eq((int)$productID)->fi() - ->fetchPairs(); - - $sql = str_replace(array('`product`', '`version`', '`branch`'), array('t1.`product`', 't1.`version`', 't1.`branch`'), $sql); - $tmpStories = $this->dao->select('DISTINCT t1.*')->from(TABLE_STORY)->alias('t1') - ->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.story') - ->where($sql) - ->beginIF($productID != 'all' and $productID != '')->andWhere('t1.`product`')->eq((int)$productID)->fi() - ->andWhere('t1.deleted')->eq(0) - ->andWhere('t1.type')->eq($type) - ->orderBy($orderBy) - ->page($pager, 't1.id') - ->fetchAll('id'); - - if(!$tmpStories) return array(); - - /* Process plans. */ - $stories = array(); - foreach($tmpStories as $story) - { - $story->planTitle = ''; - $storyPlans = explode(',', trim($story->plan, ',')); - foreach($storyPlans as $planID) $story->planTitle .= zget($plans, $planID, '') . ' '; - $stories[$story->id] = $story; - } - return $stories; - } - - /** - * Get stories list of a execution. - * - * @param int $executionID - * @param int $productID - * @param int $branch - * @param string $orderBy - * @param string $type - * @param int $param - * @param string $storyType - * @param string $excludeStories - * @param object $pager - * @access public - * @return array - */ - public function getExecutionStories($executionID = 0, $productID = 0, $branch = 0, $orderBy = 't1.`order`_desc', $type = 'byModule', $param = 0, $storyType = 'story', $excludeStories = '', $pager = null) - { - if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getExecutionStories(); - - if(!$executionID) return array(); - $execution = $this->dao->findById($executionID)->from(TABLE_PROJECT)->fetch(); - - $type = strtolower($type); - if($type == 'bysearch') - { - $queryID = (int)$param; - $products = $this->loadModel('product')->getProducts($executionID); - - if($this->session->executionStoryQuery == false) $this->session->set('executionStoryQuery', ' 1 = 1'); - if($queryID) - { - $query = $this->loadModel('search')->getQuery($queryID); - if($query) - { - if($this->app->rawModule == 'projectstory') - { - $this->session->set('storyQuery', $query->sql); - $this->session->set('storyForm', $query->form); - } - else - { - $this->session->set('executionStoryQuery', $query->sql); - $this->session->set('executionStoryForm', $query->form); - } - } - } - - if($this->app->rawModule == 'projectstory') $this->session->executionStoryQuery = $this->session->storyQuery; - - $allProduct = "`product` = 'all'"; - $storyQuery = $this->session->executionStoryQuery; - if(strpos($this->session->executionStoryQuery, $allProduct) !== false) - { - $storyQuery = str_replace($allProduct, '1', $this->session->executionStoryQuery); - } - $storyQuery = preg_replace('/`(\w+)`/', 't2.`$1`', $storyQuery); - - $stories = $this->dao->select('distinct t1.*, t2.*, t3.type as productType, t2.version as version')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id') - ->where($storyQuery) - ->andWhere('t1.project')->eq((int)$executionID) - ->andWhere('t2.deleted')->eq(0) - ->andWhere('t3.deleted')->eq(0) - ->andWhere('t2.type')->eq($storyType) - ->beginIF($excludeStories)->andWhere('t2.id')->notIN($excludeStories)->fi() - ->orderBy($orderBy) - ->page($pager, 't2.id') - ->fetchAll('id'); - } - else - { - $productParam = ($type == 'byproduct' and $param) ? $param : $this->cookie->storyProductParam; - $branchParam = ($type == 'bybranch' and $param !== '') ? $param : $this->cookie->storyBranchParam; - $moduleParam = ($type == 'bymodule' and $param !== '') ? $param : $this->cookie->storyModuleParam; - - $modules = array(); - if(!empty($moduleParam) or strpos('allstory,unclosed,bymodule', $type) !== false) - { - $modules = $this->dao->select('id')->from(TABLE_MODULE)->where('path')->like("%,$moduleParam,%")->andWhere('type')->eq('story')->andWhere('deleted')->eq(0)->fetchPairs(); - } - - if(strpos($branchParam, ',') !== false) list($productParam, $branchParam) = explode(',', $branchParam); - - $unclosedStatus = $this->lang->story->statusList; - unset($unclosedStatus['closed']); - - /* Get story id list of linked executions. */ - $storyIdList = array(); - if($type == 'linkedexecution' or $type == 'unlinkedexecution') - { - $executions = $this->loadModel('execution')->getPairs($executionID); - $storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in(array_keys($executions))->fetchPairs(); - } - - $type = (strpos('bymodule|byproduct', $type) !== false and $this->session->storyBrowseType) ? $this->session->storyBrowseType : $type; - - $stories = $this->dao->select('distinct t1.*, t2.*,t3.type as productType,t2.version as version')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t2.product = t3.id') - ->where('t1.project')->eq((int)$executionID) - ->andWhere('t2.type')->eq($storyType) - ->beginIF($excludeStories)->andWhere('t2.id')->notIN($excludeStories)->fi() - ->beginIF($execution->type == 'project') - ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID) - ->beginIF($type == 'bybranch' and $branchParam !== '')->andWhere('t2.branch')->in("0,$branchParam")->fi() - ->beginIF(strpos('changed|closed', $type) !== false)->andWhere('t2.status')->eq($type)->fi() - ->beginIF($type == 'unclosed')->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() - ->beginIF($type == 'linkedexecution')->andWhere('t2.id')->in($storyIdList)->fi() - ->beginIF($type == 'unlinkedexecution')->andWhere('t2.id')->notIn($storyIdList)->fi() - ->fi() - ->beginIF($execution->type != 'project') - ->beginIF(!empty($productParam))->andWhere('t1.product')->eq($productParam)->fi() - ->beginIF($this->session->executionStoryBrowseType and strpos('changed|', $this->session->executionStoryBrowseType) !== false)->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() - ->fi() - ->beginIF($this->session->storyBrowseType and strpos('changed|', $this->session->storyBrowseType) !== false)->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi() - ->beginIF($modules)->andWhere('t2.module')->in($modules)->fi() - ->andWhere('t2.deleted')->eq(0) - ->andWhere('t3.deleted')->eq(0) - ->orderBy($orderBy) - ->page($pager, 't2.id') - ->fetchAll('id'); - } - - $query = $this->dao->get(); - - /* Get the stories of main branch. */ - $branchStoryList = $this->dao->select('t1.*,t2.branch as productBranch')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.project = t2.project') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') - ->where('t1.story')->in(array_keys($stories)) - ->andWhere('t1.branch')->eq(BRANCH_MAIN) - ->andWhere('t3.type')->ne('normal') - ->fetchAll(); - - $branches = array(); - $stageOrderList = 'wait,planned,projected,developing,developed,testing,tested,verified,released,closed'; - - foreach($branchStoryList as $story) $branches[$story->productBranch][$story->story] = $story->story; - - /* Set up story stage. */ - foreach($branches as $branchID => $storyIdList) - { - $stages = $this->dao->select('*')->from(TABLE_STORYSTAGE)->where('story')->in($storyIdList)->andWhere('branch')->eq($branchID)->fetchPairs('story', 'stage'); - - /* Take the earlier stage. */ - foreach($stages as $storyID => $stage) if(strpos($stageOrderList, $stories[$storyID]->stage) > strpos($stageOrderList, $stage)) $stories[$storyID]->stage = $stage; - } - - $this->dao->sqlobj->sql = $query; - return $this->mergePlanTitle($productID, $stories, $branch, $type); - } - - /** - * Get stories pairs of a execution. - * - * @param int $executionID - * @param int $productID - * @param int $branch - * @param array|string $moduleIdList - * @param string $type full|short - * @param string $status all|unclosed|review - * @access public - * @return array - */ - public function getExecutionStoryPairs($executionID = 0, $productID = 0, $branch = 'all', $moduleIdList = 0, $type = 'full', $status = 'all') - { - if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getExecutionStoryPairs(); - $stories = $this->dao->select('t2.id, t2.title, t2.module, t2.pri, t2.estimate, t3.name AS product') - ->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') - ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') - ->where('t1.project')->eq((int)$executionID) - ->andWhere('t2.deleted')->eq(0) - ->beginIF($productID)->andWhere('t2.product')->eq((int)$productID)->fi() - ->beginIF($branch !== 'all')->andWhere('t2.branch')->in("0,$branch")->fi() - ->beginIF($moduleIdList)->andWhere('t2.module')->in($moduleIdList)->fi() - ->beginIF($status == 'unclosed')->andWhere('t2.status')->ne('closed')->fi() - ->beginIF($status == 'review')->andWhere('t2.status')->in('draft,changed')->fi() - ->beginIF($status == 'active')->andWhere('t2.status')->eq('active')->fi() - ->orderBy('t1.`order` desc, t1.`story` desc') - ->fetchAll('id'); - return empty($stories) ? array() : $this->formatStories($stories, $type); - } - - /** - * Get stories list of a plan. - * - * @param int $planID - * @param string $status - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getPlanStories($planID, $status = 'all', $orderBy = 'id_desc', $pager = null) - { - if(strpos($orderBy, 'module') !== false) - { - $orderBy = (strpos($orderBy, 'module_asc') !== false) ? 't3.path asc' : 't3.path desc'; - $stories = $this->dao->select('distinct t1.story, t1.plan, t1.order, t2.*') - ->from(TABLE_PLANSTORY)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') - ->leftJoin(TABLE_MODULE)->alias('t3')->on('t2.module = t3.id') - ->where('t1.plan')->eq($planID) - ->beginIF($status and $status != 'all')->andWhere('t2.status')->in($status)->fi() - ->andWhere('t2.deleted')->eq(0) - ->orderBy($orderBy)->page($pager) - ->fetchAll('id'); - } - else - { - $stories = $this->dao->select('distinct t1.story, t1.plan, t1.order, t2.*') - ->from(TABLE_PLANSTORY)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id') - ->where('t1.plan')->eq($planID) - ->beginIF($status and $status != 'all')->andWhere('t2.status')->in($status)->fi() - ->andWhere('t2.deleted')->eq(0) - ->orderBy($orderBy)->page($pager) - ->fetchAll('id'); - } - - $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false); - - return $stories; - } - - /** - * Get stories pairs of a plan. - * - * @param int $planID - * @param string $status - * @param string $orderBy - * @param object $pager - * @access public - * @return array - */ - public function getPlanStoryPairs($planID, $status = 'all', $orderBy = 'id_desc', $pager = null) - { - return $this->dao->select('*')->from(TABLE_STORY) - ->where('plan')->eq($planID) - ->beginIF($status and $status != 'all')->andWhere('status')->in($status)->fi() - ->andWhere('deleted')->eq(0) - ->fetchAll(); - } - - /** - * Get stories by plan id list. - * - * @param string|array $planIdList - * @access public - * @return array - */ - public function getStoriesByPlanIdList($planIdList = '') - { - return $this->dao->select('t1.plan as planID, t2.*')->from(TABLE_PLANSTORY)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id') - ->where('t2.deleted')->eq(0) - ->beginIF($planIdList)->andWhere('t1.plan')->in($planIdList)->fi() - ->fetchGroup('planID', 'id'); - } - - /** - * Get parent story pairs. - * - * @param int $productID - * @param string $append - * @access public - * @return void - */ - public function getParentStoryPairs($productID, $append = '') - { - $stories = $this->dao->select('id, title')->from(TABLE_STORY) - ->where('deleted')->eq(0) - ->andWhere('parent')->le(0) - ->andWhere('type')->eq('story') - ->andWhere('stage')->eq('wait') - ->andWhere('status')->notin('closed,draft') - ->andWhere('product')->eq($productID) - ->andWhere('plan')->in('0,') - ->beginIF($append)->orWhere('id')->in($append)->fi() - ->fetchPairs(); - return array(0 => '') + $stories ; - } - - /** - * Close requirement if all son story for segmentation has been closed. - * - * @param int $storyID - * @access public - * @return void - */ - public function closeParentRequirement($storyID) - { - $parentID = $this->dao->select('BID')->from(TABLE_RELATION)->where('AID')->eq($storyID)->fetch(); - if(empty($parentID)) return; - - $stories = $this->dao->select('t2.id, t2.status')->from(TABLE_RELATION)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on('t2.id=t1.AID') - ->where('t1.BType')->eq('requirement') - ->andWhere('t2.status')->ne('closed') - ->andWhere('t2.type')->eq('story') - ->fetchPairs(); - if(empty($stories)) $this->close($parentID->BID); - } - - /** - * Get stories of a user. - * - * @param string $account - * @param string $type the query type - * @param string $orderBy - * @param object $pager - * @param string $storyType requirement|story - * @access public - * @return array - */ - public function getUserStories($account, $type = 'assignedTo', $orderBy = 'id_desc', $pager = null, $storyType = 'story', $includeLibStories = true) - { - $sql = $this->dao->select('t1.*, t2.name as productTitle')->from(TABLE_STORY)->alias('t1') - ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id'); - if($type == 'reviewBy') $sql = $sql->leftJoin(TABLE_STORYREVIEW)->alias('t3')->on('t1.id = t3.story and t1.version = t3.version'); - - $stories = $sql->where('t1.deleted')->eq(0) - ->andWhere('t2.deleted')->eq('0') - ->andWhere('t1.type')->eq($storyType) - ->andWhere('t1.vision')->eq($this->config->vision) - ->beginIF($type != 'closedBy' and $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi() - ->beginIF($type != 'all') - ->beginIF($type == 'assignedTo')->andWhere('t1.assignedTo')->eq($account)->fi() - ->beginIF($type == 'reviewBy')->andWhere('t3.reviewer')->eq($account)->andWhere('t3.result')->eq('')->andWhere('t1.status')->in('draft,changed')->fi() - ->beginIF($type == 'openedBy')->andWhere('t1.openedBy')->eq($account)->fi() - ->beginIF($type == 'reviewedBy')->andWhere("CONCAT(',', t1.reviewedBy, ',')")->like("%,$account,%")->fi() - ->beginIF($type == 'closedBy')->andWhere('t1.closedBy')->eq($account)->fi() - ->fi() - ->beginIF($includeLibStories == false and $this->config->edition == 'max')->andWhere('t1.lib')->eq('0')->fi() - ->orderBy($orderBy) - ->page($pager) - ->fetchAll('id'); - - $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false); - $productIdList = array(); - foreach($stories as $story) $productIdList[$story->product] = $story->product; - - return $this->mergePlanTitle($productIdList, $stories, 0, $storyType); - } - - /** - * Get story pairs of a user. - * - * @param string $account - * @param string $limit - * @param string $type requirement|story - * @param array $skipProductIDList - * @param int|array $appendStoryID - * @access public - * @return array - */ - public function getUserStoryPairs($account, $limit = 10, $type = 'story', $skipProductIDList = array(), $appendStoryID = 0) - { - return $this->dao->select('id, title') - ->from(TABLE_STORY) - ->where('deleted')->eq(0) - ->andWhere('status')->ne('closed') - ->andWhere('type')->eq($type) - ->andWhere('vision')->eq($this->config->vision) - ->andWhere('assignedTo')->eq($account) - ->andWhere('product')->ne(0) - ->beginIF(!empty($skipProductIDList))->andWhere('product')->notin($skipProductIDList)->fi() - ->beginIF(!empty($appendStoryID))->orWhere('id')->in($appendStoryID)->fi() - ->orderBy('id_desc') - ->limit($limit) - ->fetchPairs('id', 'title'); - } - - /** - * Get the story ID list of the linked to task. - * - * @param int $executionID - * @access public - * @return array - */ - public function getIdListWithTask($executionID) - { - return $this->dao->select('story')->from(TABLE_TASK) - ->where('execution')->eq($executionID) - ->andWhere('story')->ne(0) - ->andWhere('deleted')->eq(0) - ->fetchPairs(); - } - - /** - * Get team members for a project or execution. - * - * @param int $storyID - * @param string $actionType - * @access public - * @return array - */ - public function getTeamMembers($storyID, $actionType) - { - $teamMembers = array(); - if($actionType == 'changed') - { - $executions = $this->dao->select('execution')->from(TABLE_TASK) - ->where('story')->eq($storyID) - ->andWhere('status')->ne('cancel') - ->andWhere('deleted')->eq(0) - ->fetchPairs(); - if($executions) $teamMembers = $this->dao->select('account')->from(TABLE_TEAM)->where('root')->in($executions)->andWhere('type')->eq('execution')->fetchPairs('account'); - } - else - { - $projects = $this->dao->select('t1.project') - ->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id') - ->where('t1.story')->eq((int)$storyID) - ->andWhere('t2.status')->eq('doing') - ->andWhere('t2.deleted')->eq(0) - ->fetchPairs(); - if($projects) $teamMembers = $this->dao->select('account')->from(TABLE_TEAM)->where('root')->in($projects)->andWhere('type')->eq('project')->fetchPairs('account'); - } - return $teamMembers; - } - - /** - * Get version of a story. - * - * @param int $storyID - * @access public - * @return int - */ - public function getVersion($storyID) - { - return (int)$this->dao->select('version')->from(TABLE_STORY)->where('id')->eq((int)$storyID)->fetch('version'); - } - - /** - * Get versions of some stories. - * - * @param array|string story id list - * @access public - * @return array - */ - public function getVersions($storyID) - { - return $this->dao->select('id, version')->from(TABLE_STORY)->where('id')->in($storyID)->fetchPairs(); - } - - /** - * Get zero case. - * - * @param int $productID - * @param int $branchID - * @param string $orderBy - * @access public - * @return array - */ - public function getZeroCase($productID, $branchID = 0, $orderBy = 'id_desc') - { - $allStories = $this->getProductStories($productID, $branchID, 0, 'all', 'story', $orderBy, $hasParent = false, '', null); - $casedStories = $this->dao->select('DISTINCT story')->from(TABLE_CASE)->where('product')->eq($productID)->andWhere('story')->ne(0)->andWhere('deleted')->eq(0)->fetchAll('story'); - - foreach($allStories as $key => $story) - { - if(isset($casedStories[$story->id])) unset($allStories[$key]); - } - - return $allStories; - } - - /** - * Get changed stories. - * - * @param object $story - * @access public - * @return void - */ - public function getChangedStories($story) - { - if($story->type == 'requirement') return array(); - - $relations = $this->dao->select('*')->from(TABLE_RELATION) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere('BID')->eq($story->id) - ->fetchAll('AID'); - - if(empty($relations)) return array(); - - $stories = $this->getByList(array_keys($relations)); - foreach($stories as $id => $story) - { - $version = $relations[$story->id]->AVersion; - if($version > $story->version) unset($stories[$id]); - } - - return $stories; - } - - public function getAllStorySort($planID, $planOrder) - { - $orderBy = $this->post->orderBy; - if(strpos($orderBy, 'order') !== false) $orderBy = str_replace('order', 'id', $orderBy); - - $stories = $this->loadModel('story')->getPlanStories($planID, 'all'); - $storyIDList = array_keys($stories); - - if(strpos($this->post->orderBy, 'order') !== false and !empty($planOrder)) $stories = $this->sortPlanStory($stories, $planOrder, $orderBy); - - $frontCount = (int)$this->post->recPerPage * ((int)$this->post->pageID - 1); - $behindCount = (int)$this->post->recPerPage * (int)$this->post->pageID; - $frontIDList = array_slice($storyIDList, 0, $frontCount); - $behindIDList = array_slice($storyIDList, $behindCount, count($storyIDList) - $behindCount); - - $frontIDList = !empty($frontIDList) ? implode(',', $frontIDList) . ',' : ''; - $behindIDList = !empty($behindIDList) ? implode(',', $behindIDList) : ''; - return $frontIDList . $this->post->stories . $behindIDList; - } - - /** - * Batch get story stage. - * - * @param array $stories - * @access public - * @return array - */ - public function batchGetStoryStage($stories) - { - return $this->dao->select('*')->from(TABLE_STORYSTAGE) - ->where('story')->in($stories) - ->fetchGroup('story', 'branch'); - } - - /** - * Check need confirm. - * - * @param array|object $object - * @access public - * @return array|object - */ - public function checkNeedConfirm($data) - { - $objectList = is_object($data) ? array($data->id => $data) : $data; - - $storyIdList = array(); - $storyVersionList = array(); - - foreach($objectList as $key => $object) - { - $object->needconfirm = false; - if($object->story) - { - $storyIdList[$key] = $object->story; - $storyVersionList[$key] = $object->storyVersion; - } - } - - $stories = $this->dao->select('id,version')->from(TABLE_STORY)->where('id')->in($storyIdList)->andWhere('status')->eq('active')->fetchPairs('id', 'version'); - foreach($storyIdList as $key => $storyID) - { - if(isset($stories[$storyID]) and $stories[$storyID] > $storyVersionList[$key]) $objectList[$key]->needconfirm = true; - } - - return is_object($data) ? reset($objectList) : $objectList; - } - - /** - * Format stories - * - * @param array $stories - * @param string $type - * @param int $limit - * @access public - * @return void - */ - public function formatStories($stories, $type = 'full', $limit = 0) - { - /* Format these stories. */ - $storyPairs = array(0 => ''); - $i = 0; - foreach($stories as $story) - { - if($type == 'short') - { - $property = '[p' . (!empty($this->lang->story->priList[$story->pri]) ? $this->lang->story->priList[$story->pri] : 0) . ', ' . $story->estimate . "{$this->config->hourUnit}]"; - } - elseif($type == 'full') - { - $property = '(' . $this->lang->story->pri . ':' . (!empty($this->lang->story->priList[$story->pri]) ? $this->lang->story->priList[$story->pri] : 0) . ',' . $this->lang->story->estimate . ':' . $story->estimate . ')'; - } - else - { - $property = ''; - } - $storyPairs[$story->id] = $story->id . ':' . $story->title . ' ' . $property; - - if($limit > 0 && ++$i > $limit) - { - $storyPairs['showmore'] = $this->lang->more . $this->lang->ellipsis; - break; - } - } - return $storyPairs; - } - - /** - * Extract accounts from some stories. - * - * @param array $stories - * @access public - * @return array - */ - public function extractAccountsFromList($stories) - { - $accounts = array(); - foreach($stories as $story) - { - if(!empty($story->openedBy)) $accounts[] = $story->openedBy; - if(!empty($story->assignedTo)) $accounts[] = $story->assignedTo; - if(!empty($story->closedBy)) $accounts[] = $story->closedBy; - if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy; - } - return array_unique($accounts); - } - - /** - * Extract accounts from a story. - * - * @param object $story - * @access public - * @return array - */ - public function extractAccountsFromSingle($story) - { - $accounts = array(); - if(!empty($story->openedBy)) $accounts[] = $story->openedBy; - if(!empty($story->assignedTo)) $accounts[] = $story->assignedTo; - if(!empty($story->closedBy)) $accounts[] = $story->closedBy; - if(!empty($story->lastEditedBy)) $accounts[] = $story->lastEditedBy; - return array_unique($accounts); - } - - /** - * Merge the default chart settings and the settings of current chart. - * - * @param string $chartType - * @access public - * @return void - */ - public function mergeChartOption($chartType) - { - $chartOption = $this->lang->story->report->$chartType; - $commonOption = $this->lang->story->report->options; - - $chartOption->graph->caption = $this->lang->story->report->charts[$chartType]; - if(!isset($chartOption->type)) $chartOption->type = $commonOption->type; - if(!isset($chartOption->width)) $chartOption->width = $commonOption->width; - if(!isset($chartOption->height)) $chartOption->height = $commonOption->height; - - foreach($commonOption->graph as $key => $value) if(!isset($chartOption->graph->$key)) $chartOption->graph->$key = $value; - } - - /** - * Get report data of storys per product - * - * @access public - * @return array - */ - public function getDataOfStorysPerProduct() - { - $datas = $this->dao->select('product as name, count(product) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('product')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - $products = $this->loadModel('product')->getPairs(); - foreach($datas as $productID => $data) $data->name = isset($products[$productID]) ? $products[$productID] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per module - * - * @access public - * @return array - */ - public function getDataOfStorysPerModule() - { - $datas = $this->dao->select('module as name, count(module) as value, product, branch')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('module')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - - $branchIDList = array(); - foreach($datas as $key => $project) - { - if(!$project->branch) continue; - $branchIDList[$project->branch] = $project->branch; - } - - $branchs = $this->dao->select('id, name')->from(TABLE_BRANCH)->where('id')->in($branchIDList)->andWhere('deleted')->eq(0)->fetchALL('id'); - $modules = $this->loadModel('tree')->getModulesName(array_keys($datas)); - - foreach($datas as $moduleID => $data) - { - $branch = ''; - if(isset($branchs[$data->branch]->name)) - { - $branch = '/' . $branchs[$data->branch]->name; - } - - $data->name = $branch . (isset($modules[$moduleID]) ? $modules[$moduleID] : '/'); - } - - return $datas; - } - - /** - * Get report data of storys per source - * - * @access public - * @return array - */ - public function getDataOfStorysPerSource() - { - $datas = $this->dao->select('source as name, count(source) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('source')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - $this->lang->story->sourceList[''] = $this->lang->report->undefined; - foreach($datas as $key => $data) $data->name = isset($this->lang->story->sourceList[$key]) ? $this->lang->story->sourceList[$key] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per plan - * - * @access public - * @return array - */ - public function getDataOfStorysPerPlan() - { - $datas = $this->dao->select('plan as name, count(plan) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('plan')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - - /* Separate for multi-plan key. */ - foreach($datas as $planID => $data) - { - if(strpos($planID, ',') !== false) - { - $planIdList = explode(',', $planID); - foreach($planIdList as $multiPlanID) - { - if(empty($datas[$multiPlanID])) - { - $datas[$multiPlanID] = new stdclass(); - $datas[$multiPlanID]->name = $multiPlanID; - $datas[$multiPlanID]->value = 0; - } - $datas[$multiPlanID]->value += $data->value; - } - unset($datas[$planID]); - } - } - - /* Fix bug #2697. */ - if(isset($datas[''])) - { - if(empty($datas[0])) - { - $datas[0] = new stdclass(); - $datas[0]->name = 0; - $datas[0]->value = 0; - } - $datas[0]->value += $datas['']->value; - unset($datas['']); - } - - $plans = $this->dao->select('id, title')->from(TABLE_PRODUCTPLAN)->where('id')->in(array_keys($datas))->fetchPairs(); - foreach($datas as $planID => $data) $data->name = isset($plans[$planID]) ? $plans[$planID] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per status - * - * @access public - * @return array - */ - public function getDataOfStorysPerStatus() - { - $datas = $this->dao->select('status as name, count(status) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('status')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - foreach($datas as $status => $data) if(isset($this->lang->story->statusList[$status])) $data->name = $this->lang->story->statusList[$status]; - return $datas; - } - - /** - * Get report data of storys per stage - * - * @access public - * @return array - */ - public function getDataOfStorysPerStage() - { - $datas = $this->dao->select('stage as name, count(stage) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('stage')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - foreach($datas as $stage => $data) $data->name = $this->lang->story->stageList[$stage] != '' ? $this->lang->story->stageList[$stage] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per pri - * - * @access public - * @return array - */ - public function getDataOfStorysPerPri() - { - $datas = $this->dao->select('pri as name, count(pri) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('pri')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - foreach($datas as $pri => $data) $data->name = $this->lang->story->priList[$pri] != '' ? $this->lang->story->priList[$pri] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per estimate - * - * @access public - * @return array - */ - public function getDataOfStorysPerEstimate() - { - return $this->dao->select('estimate as name, count(estimate) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('estimate')->orderBy('value')->fetchAll(); - } - - /** - * Get report data of storys per openedBy - * - * @access public - * @return array - */ - public function getDataOfStorysPerOpenedBy() - { - $datas = $this->dao->select('openedBy as name, count(openedBy) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('openedBy')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); - foreach($datas as $account => $data) $data->name = isset($this->users[$account]) ? $this->users[$account] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per assignedTo - * - * @access public - * @return array - */ - public function getDataOfStorysPerAssignedTo() - { - $datas = $this->dao->select('assignedTo as name, count(assignedTo) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('assignedTo')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter'); - foreach($datas as $account => $data) $data->name = (isset($this->users[$account]) and $this->users[$account] != '') ? $this->users[$account] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per closedReason - * - * @access public - * @return array - */ - public function getDataOfStorysPerClosedReason() - { - $datas = $this->dao->select('closedReason as name, count(closedReason) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('closedReason')->orderBy('value DESC')->fetchAll('name'); - if(!$datas) return array(); - foreach($datas as $reason => $data) $data->name = $this->lang->story->reasonList[$reason] != '' ? $this->lang->story->reasonList[$reason] : $this->lang->report->undefined; - return $datas; - } - - /** - * Get report data of storys per change - * - * @access public - * @return array - */ - public function getDataOfStorysPerChange() - { - return $this->dao->select('(version-1) as name, count(*) as value')->from(TABLE_STORY) - ->where($this->reportCondition()) - ->groupBy('version')->orderBy('value')->fetchAll(); - } - - /** - * Get kanban group data. - * - * @param array $stories - * @access public - * @return array - */ - public function getKanbanGroupData($stories) - { - $storyGroup = array(); - foreach($stories as $story) $storyGroup[$story->stage][$story->id] = $story; - - return $storyGroup; - } - - /** - * Get toList and ccList. - * - * @param object $story - * @param string $actionType - * @access public - * @return bool|array - */ - public function getToAndCcList($story, $actionType) - { - /* Set toList and ccList. */ - $toList = $story->assignedTo; - $ccList = str_replace(' ', '', trim($story->mailto, ',')); - - /* If the action is changed or reviewed, mail to the project or execution team. */ - if(strtolower($actionType) == 'changed' or strtolower($actionType) == 'reviewed') - { - $teamMembers = $this->getTeamMembers($story->id, $actionType); - if($teamMembers) - { - $ccList .= ',' . join(',', $teamMembers); - $ccList = ltrim($ccList, ','); - } - } - - if(strtolower($actionType) == 'changed' or strtolower($actionType) == 'opened') - { - $reviewerList = $this->getReviewerPairs($story->id, $story->version); - unset($reviewerList[$story->assignedTo]); - - $ccList .= ',' . join(',', array_keys($reviewerList)); - } - - if(empty($toList)) - { - if(empty($ccList)) return false; - if(strpos($ccList, ',') === false) - { - $toList = $ccList; - $ccList = ''; - } - else - { - $commaPos = strpos($ccList, ','); - $toList = substr($ccList, 0, $commaPos); - $ccList = substr($ccList, $commaPos + 1); - } - } - elseif($toList == 'closed') - { - $toList = $story->openedBy; - } - - return array($toList, $ccList); - } - - /** - * Adjust the action clickable. - * - * @param object $story - * @param string $action - * @access public - * @return void - */ - public static function isClickable($story, $action) - { - $action = strtolower($action); - - global $app, $config; - - if($story->parent < 0 and strpos($config->story->list->actionsOpratedParentStory, ",$action,") === false) return false; - - $story->reviewer = isset($story->reviewer) ? $story->reviewer : array(); - $story->notReview = isset($story->notReview) ? $story->notReview : array(); - - $isSuperReviewer = strpos(',' . trim(zget($config->story, 'superReviewers', ''), ',') . ',', ',' . $app->user->account . ','); - - if($action == 'change') return ($isSuperReviewer !== false or count($story->reviewer) == 0 or count($story->notReview) == 0) and $story->status != 'closed'; - if($action == 'review') return (($isSuperReviewer !== false or in_array($app->user->account, $story->notReview)) and ($story->status == 'draft' or $story->status == 'changed')); - if($action == 'recall') return empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) and ($app->user->account == $story->openedBy); - if($action == 'close') return $story->status != 'closed'; - if($action == 'activate') return $story->status == 'closed'; - if($action == 'assignto') return $story->status != 'closed'; - if($action == 'createcase') return $story->type != 'requirement'; - if($action == 'batchcreate' and $story->parent > 0) return false; - if($action == 'batchcreate' and $story->type == 'requirement' and $story->status != 'closed') return $story->status != 'draft'; - if($action == 'batchcreate' and $config->vision == 'lite' and ($story->status == 'active' and ($story->stage == 'wait' or $story->stage == 'projected'))) return true; - if($action == 'batchcreate' and ($story->status != 'active' or $story->stage != 'wait' or !empty($story->plan))) return false; - - return true; - } - - /** - * Build operate menu. - * - * @param object $story - * @param string $type - * @access public - * @return string - */ - public function buildOperateMenu($story, $type = 'view', $execution = '') - { - $menu = ''; - $params = "storyID=$story->id"; - - if($type == 'browse') - { - if(common::canBeChanged('story', $story)) - { - $storyReviewer = isset($story->reviewer) ? $story->reviewer : array(); - - if($story->URChanged) return $this->buildMenu('story', 'processStoryChange', $params, $story, $type, 'search', '', 'iframe', true, '', $this->lang->confirm); - - $isClick = $this->isClickable($story, 'change'); - $title = (!$isClick and $story->status != 'closed') ? $this->lang->story->changeTip : ''; - $menu .= $this->buildMenu('story', 'change', $params . "&from=$story->from", $story, $type, 'alter', '', '', false, '', $title); - - $isClick = $this->isClickable($story, 'review'); - $title = $this->lang->story->review; - if(!$isClick and $story->status != 'closed') - { - if($story->status == 'active') - { - $title = $this->lang->story->reviewTip['active']; - } - elseif($storyReviewer and in_array($this->app->user->account, $storyReviewer)) - { - $title = $this->lang->story->reviewTip['reviewed']; - } - elseif($storyReviewer and !in_array($this->app->user->account, $storyReviewer)) - { - $title = $this->lang->story->reviewTip['notReviewer']; - } - elseif(empty($storyReviewer)) - { - $title = $this->lang->story->reviewTip['recalled']; - } - } - $menu .= $this->buildMenu('story', 'review', $params . "&from=$story->from", $story, $type, 'search', '', '', false, '', $title); - - $title = $this->lang->story->recall; - $isClick = $this->isClickable($story, 'recall'); - if(!$isClick and $story->status != 'closed') - { - if($story->status == 'draft' and empty($storyReviewer)) $title = $this->lang->story->recallTip['recalled']; - if($story->status == 'draft' and !empty($storyReviewer)) $title = $this->lang->story->recallTip['reviewed']; - if(empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) and $this->app->user->account != $story->openedBy) $title = $this->lang->story->recallTip['notOpenedBy']; - if($story->status == 'active' and empty($story->reviewer)) $title = $this->lang->story->recallTip['actived']; - } - $menu .= $this->buildMenu('story', 'recall', $params, $story, $type, 'undo', 'hiddenwin', '', '', '', $title); - - $menu .= $this->buildMenu('story', 'close', $params, $story, $type, '', '', 'iframe', true); - $menu .= $this->buildMenu('story', 'edit', $params . "&from=$story->from", $story, $type); - $tab = $this->app->tab == 'project' ? 'project' : 'qa'; - if($story->type != 'requirement' and $this->config->vision != 'lite') $menu .= $this->buildMenu('story', 'createCase', "productID=$story->product&branch=$story->branch&module=0&from=¶m=0&$params", $story, $type, 'sitemap', '', '', false, "data-app='$tab'"); - - if($this->app->rawModule != 'projectstory' OR $this->config->vision == 'lite') - { - $isClick = $this->isClickable($story, 'batchcreate'); - $title = $this->lang->story->subdivide; - if(!$isClick and $story->status != 'closed') - { - if($story->parent > 0) - { - $title = $this->lang->story->subDivideTip['subStory']; - } - else - { - if($story->status != 'active') $title = $this->lang->story->subDivideTip['notActive']; - if(!empty($story->plan)) $title = $this->lang->story->subDivideTip['planned']; - if($story->stage == 'projected') $title = $this->lang->story->subDivideTip['projected']; - } - } - $menu .= $this->buildMenu('story', 'batchCreate', "productID=$story->product&branch=$story->branch&module=$story->module&$params&executionID={$this->session->project}", $story, $type, 'split', '', '', '', '', $title); - } - if($this->app->rawModule == 'projectstory' and $this->config->vision != 'lite') $menu .= $this->buildMenu('projectstory', 'unlinkStory', "projectID={$this->session->project}&$params", $story, $type, 'unlink', 'hiddenwin'); - } - else - { - return $this->buildMenu('story', 'close', $params, $story, 'list', '', '', 'iframe', true); - } - } - - if($type == 'view') - { - $menu .= $this->buildMenu('story', 'change', $params, $story, $type, 'alter', '', 'showinonlybody'); - $menu .= $this->buildMenu('story', 'recall', $params, $story, $type, 'undo', '', 'showinonlybody'); - $menu .= $this->buildMenu('story', 'review', $params, $story, $type, 'search', '', 'showinonlybody'); - - if(!isonlybody()) $menu .= $this->buildMenu('story', 'batchCreate', "productID=$story->product&branch=$story->branch&moduleID=$story->module&$params", $story, $type, 'split', '', 'divideStory', true, "data-toggle='modal' data-type='iframe' data-width='95%'", $this->lang->story->subdivide); - - $menu .= $this->buildMenu('story', 'assignTo', $params, $story, $type, '', '', 'iframe showinonlybody', true); - $menu .= $this->buildMenu('story', 'close', $params, $story, $type, '', '', 'iframe showinonlybody', true); - $menu .= $this->buildMenu('story', 'activate', $params, $story, $type, '', '', 'iframe showinonlybody', true); - - if($this->config->edition == 'max' and $this->app->tab == 'project' and common::hasPriv('story', 'importToLib')) $menu .= html::a('#importToLib', " " . $this->lang->story->importToLib, '', 'class="btn" data-toggle="modal"'); - - /* Print testcate actions. */ - if($story->parent >= 0 and $story->type != 'requirement' and (common::hasPriv('testcase', 'create', $story) or common::hasPriv('testcase', 'batchCreate', $story))) - { - $this->app->loadLang('testcase'); - $menu .= "
    "; - $menu .= ""; - $menu .= "
    "; - } - - if($this->app->tab == 'execution' and strpos('draft,closed', $story->status) === false) $menu .= $this->buildMenu('task', 'create', "execution={$this->session->execution}&{$params}&moduleID=$story->module", $story, $type, 'plus', '', 'showinonly body'); - - $menu .= "
    "; - $menu .= $this->buildFlowMenu('story', $story, $type, 'direct'); - $menu .= "
    "; - - $menu .= $this->buildMenu('story', 'edit', $params, $story, $type); - $menu .= $this->buildMenu('story', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&{$params}&executionID=0&bugID=0&planID=0&todoID=0&extra=&type=$story->type", $story, $type, 'copy', '', '', '', "data-width='1050'"); - $menu .= $this->buildMenu('story', 'delete', $params, $story, 'button', 'trash', 'hiddenwin', 'showinonlybody', true); - } - - if($type == 'execution') - { - $hasDBPriv = common::hasDBPriv($execution, 'execution'); - $canBeChanged = common::canModify('execution', $execution); - if($canBeChanged) - { - $executionID = empty($execution) ? $this->session->execution : $execution->id; - $param = "executionID=$executionID&story={$story->id}&moduleID={$story->module}"; - - $story->reviewer = isset($story->reviewer) ? $story->reviewer : array(); - $story->notReview = isset($story->notReview) ? $story->notReview : array(); - - if(common::hasPriv('story', 'review')) - { - $reviewDisabled = in_array($this->app->user->account, $story->notReview) and ($story->status == 'draft' or $story->status == 'changed') ? '' : 'disabled'; - $story->from = 'execution'; - $menu .= common::printIcon('story', 'review', "story={$story->id}&from=story", $story, 'list', 'search', '', $reviewDisabled, false, "data-group=execution"); - } - - if(common::hasPriv('story', 'recall')) - { - $recallDisabled = empty($story->reviewedBy) and strpos('draft,changed', $story->status) !== false and !empty($story->reviewer) ? '' : 'disabled'; - $menu .= common::printIcon('story', 'recall', "story={$story->id}", $story, 'list', 'undo', 'hiddenwin', $recallDisabled, '', '', $this->lang->story->recall); - } - - $this->lang->task->create = $this->lang->execution->wbs; - $toTaskDisabled = strpos('draft,closed', $story->status) !== false ? 'disabled' : ''; - if(commonModel::isTutorialMode()) - { - $wizardParams = helper::safe64Encode($param); - $menu .= html::a(helper::createLink('tutorial', 'wizard', "module=task&method=create¶ms=$wizardParams"), "",'', "class='btn btn-task-create' title='{$this->lang->execution->wbs}' data-app='execution'"); - } - else - { - if($hasDBPriv) $menu .= common::printIcon('task', 'create', $param, '', 'list', 'plus', '', 'btn-task-create ' . $toTaskDisabled); - } - - $this->lang->task->batchCreate = $this->lang->execution->batchWBS; - if($hasDBPriv) $menu .= common::printIcon('task', 'batchCreate', "executionID=$executionID&story={$story->id}", '', 'list', 'pluses', '', $toTaskDisabled); - - $this->lang->testcase->batchCreate = $this->lang->testcase->create; - if($hasDBPriv and common::hasPriv('testcase', 'create')) - { - $menu .= html::a(helper::createLink('testcase', 'create', "productID=$story->product&branch=$story->branch&moduleID=$story->module&form=¶m=0&storyID=$story->id"), '', '', "class='btn' title='{$this->lang->testcase->create}' data-app='qa'"); - } - - if($canBeChanged and common::hasPriv('execution', 'storyEstimate', $execution)) - { - $menu .= common::printIcon('execution', 'storyEstimate', "executionID=$executionID&storyID=$story->id", '', 'list', 'estimate', '', 'iframe', true, "data-width='470px'"); - } - - if($canBeChanged and common::hasPriv('execution', 'unlinkStory', $execution)) - { - $menu .= common::printIcon('execution', 'unlinkStory', "executionID=$executionID&storyID=$story->id&confirm=no", '', 'list', 'unlink', 'hiddenwin'); - } - } - } - - return $menu; - } - - /** - * Merge plan title. - * - * @param int|array $productID - * @param array $stories - * @param int $branch - * - * @access public - * @return array - */ - public function mergePlanTitle($productID, $stories, $branch = 0, $type = 'story') - { - $query = $this->dao->get(); - if(is_array($branch)) - { - unset($branch[0]); - $branch = join(',', $branch); - if($branch) $branch = "0,$branch"; - } - $plans = $this->dao->select('id,title')->from(TABLE_PRODUCTPLAN) - ->Where('deleted')->eq(0) - ->beginIF($productID)->andWhere('product')->in($productID)->fi() - ->fetchPairs('id', 'title'); - - /* For requirement children. */ - if($type == 'requirement') - { - $relations = $this->dao->select('DISTINCT AID, BID')->from(TABLE_RELATION) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere('AID')->in(array_keys($stories)) - ->fetchAll(); - - $group = array(); - foreach($relations as $relation) $group[$relation->AID][] = $relation->BID; - - foreach($stories as $story) - { - if(!isset($group[$story->id])) continue; - $story->children = $this->getByList($group[$story->id]); - } - } - - $parents = array(); - $tmpStories = array(); - foreach($stories as $story) - { - $tmpStories[$story->id] = $story; - if($story->parent > 0) $parents[$story->parent] = $story->parent; - } - $parents = $this->dao->select('*')->from(TABLE_STORY)->where('id')->in($parents)->fetchAll('id'); - - foreach($stories as $storyID => $story) - { - if($story->parent > 0) - { - if(isset($stories[$story->parent])) - { - $stories[$story->parent]->children[$story->id] = $story; - unset($stories[$storyID]); - } - else - { - $parent = $parents[$story->parent]; - $story->parentName = $parent->title; - } - } - - $story->planTitle = ''; - $storyPlans = explode(',', trim($story->plan, ',')); - foreach($storyPlans as $planID) $story->planTitle .= zget($plans, $planID, '') . ' '; - } - - /* For save session query. */ - $this->dao->sqlobj->sql = $query; - return $stories; - } - - /** - * Merge story reviewers. - * - * @param array|object $stories - * @param bool $isObject - * @access public - * @return array|object - */ - public function mergeReviewer($stories, $isObject = false) - { - if($isObject) - { - $story = $stories; - $stories = (array)$stories; - $stories[$story->id] = $story; - } - - /* Set child story id into array. */ - $storyIdList = isset($stories['id']) ? array($stories['id'] => $stories['id']) : array_keys($stories); - if(isset($stories['id']) and isset($story->children)) $storyIdList = array_merge($storyIdList, array_keys($story->children)); - if(!isset($stories['id'])) - { - foreach($stories as $story) - { - if(isset($story->children)) $storyIdList = array_merge($storyIdList, array_keys($story->children)); - } - } - - $allReviewers = $this->dao->select('story,reviewer,result')->from(TABLE_STORY)->alias('t1') - ->leftJoin(TABLE_STORYREVIEW)->alias('t2')->on('t1.version=t2.version and t1.id=t2.story') - ->where('story')->in($storyIdList) - ->fetchGroup('story', 'reviewer'); - - foreach($allReviewers as $storyID => $reviewerList) - { - if(isset($stories[$storyID])) - { - $stories[$storyID]->reviewer = array_keys($reviewerList); - $stories[$storyID]->notReview = array(); - foreach($reviewerList as $reviewer => $reviewInfo) - { - if($reviewInfo->result == '') $stories[$storyID]->notReview[] = $reviewer; - } - } - else - { - foreach($stories as $id => $story) - { - if(!isset($story->children)) continue; - if(isset($story->children[$storyID])) - { - $story->children[$storyID]->reviewer = array_keys($reviewerList); - $story->children[$storyID]->notReview = array(); - foreach($reviewerList as $reviewer => $reviewInfo) - { - if($reviewInfo->result == '') $story->children[$storyID]->notReview[] = $reviewer; - } - } - } - } - } - - if($isObject) return $stories[$story->id]; - return $stories; - } - - /** - * Print cell data - * - * @param object $col - * @param object $story - * @param array $users - * @param array $branches - * @param array $storyStages - * @param array $modulePairs - * @param array $storyTasks - * @param array $storyBugs - * @param array $storyCases - * @access public - * @return void - */ - public function printCell($col, $story, $users, $branches, $storyStages, $modulePairs = array(), $storyTasks = array(), $storyBugs = array(), $storyCases = array(), $mode = 'datatable', $storyType = 'story', $execution = '', $isShowBranch = '') - { - $tab = $this->app->tab; - $executionID = empty($execution) ? $this->session->execution : $execution->id; - $account = $this->app->user->account; - $storyLink = helper::createLink('story', 'view', "storyID=$story->id"); - $canView = common::hasPriv('story', 'view'); - - if($tab == 'project') - { - $storyLink = helper::createLink('projectstory', 'view', "storyID=$story->id"); - $canView = common::hasPriv('projectstory', 'view'); - } - elseif($tab == 'execution') - { - $storyLink = helper::createLink('execution', 'storyView', "storyID=$story->id"); - $canView = common::hasPriv('execution', 'storyView'); - } - - /* Check the product is closed. */ - $canBeChanged = common::canBeChanged('story', $story); - $canOrder = common::hasPriv('execution', 'storySort'); - - $canBatchEdit = common::hasPriv('story', 'batchEdit'); - $canBatchClose = common::hasPriv('story', 'batchClose'); - $canBatchReview = common::hasPriv('story', 'batchReview'); - $canBatchChangeStage = common::hasPriv('story', 'batchChangeStage'); - $canBatchChangeBranch = common::hasPriv('story', 'batchChangeBranch'); - $canBatchChangeModule = common::hasPriv('story', 'batchChangeModule'); - $canBatchChangePlan = common::hasPriv('story', 'batchChangePlan'); - $canBatchAssignTo = common::hasPriv('story', 'batchAssignTo'); - $canBatchUnlinkStory = common::hasPriv('projectstory', 'batchUnlinkStory'); - $canBatchUnlink = common::hasPriv('execution', 'batchUnlinkStory'); - - if($tab == 'execution') - { - $checkObject = new stdclass(); - $checkObject->execution = $executionID; - - $canBatchToTask = common::hasPriv('story', 'batchToTask', $checkObject); - } - - if($tab == 'execution') - { - $canBatchAction = ($canBeChanged and ($canBatchEdit or $canBatchClose or $canBatchChangeStage or $canBatchUnlink or $canBatchToTask)); - } - else - { - $canBatchAction = ($canBatchEdit or $canBatchClose or $canBatchReview or $canBatchChangeStage or $canBatchChangeBranch or $canBatchChangeModule or $canBatchChangePlan or $canBatchAssignTo or $canBatchUnlinkStory); - } - - $id = $col->id; - if($col->show) - { - $class = "c-{$id}"; - $title = ''; - $style = ''; - - if($id == 'assignedTo') - { - $title = zget($users, $story->assignedTo, $story->assignedTo); - if($story->assignedTo == $account) $class .= ' red'; - } - elseif($id == 'openedBy') - { - $title = zget($users, $story->openedBy, $story->openedBy); - } - elseif($id == 'title') - { - $title = $story->title; - if(!empty($story->children)) $class .= ' has-child'; - } - elseif($id == 'plan') - { - $title = isset($story->planTitle) ? $story->planTitle : ''; - $class .= ' text-ellipsis'; - } - elseif($id == 'sourceNote') - { - $title = $story->sourceNote; - $class .= ' text-ellipsis'; - } - elseif($id == 'category') - { - $title = zget($this->lang->story->categoryList, $story->category); - } - elseif($id == 'estimate') - { - $title = $story->estimate . ' ' . $this->lang->hourCommon; - } - elseif($id == 'reviewedBy') - { - $reviewedBy = ''; - foreach(explode(',', $story->reviewedBy) as $user) $reviewedBy .= zget($users, $user) . ' '; - $story->reviewedBy = trim($reviewedBy); - - $title = $reviewedBy; - $class .= ' text-ellipsis'; - } - elseif($id == 'stage') - { - $style .= 'overflow: visible;'; - if(isset($storyStages[$story->id])) - { - foreach($storyStages[$story->id] as $storyBranch => $storyStage) - { - if(isset($branches[$storyBranch])) $title .= $branches[$storyBranch] . ": " . $this->lang->story->stageList[$storyStage->stage] . "\n"; - } - } - } - elseif($id == 'feedbackBy') - { - $title = $story->feedbackBy; - } - elseif($id == 'notifyEmail') - { - $title = $story->notifyEmail; - } - elseif($id == 'actions') - { - $class .= ' text-center'; - } - elseif($id == 'order') - { - $class = 'sort-handler c-sort'; - } - - echo ""; - if($this->config->edition != 'open') $this->loadModel('flow')->printFlowCell('story', $story, $id); - switch($id) - { - case 'id': - if($canBatchAction) - { - echo html::checkbox('storyIdList', array($story->id => '')) . html::a($storyLink, sprintf('%03d', $story->id), '', "data-app='$tab'"); - } - else - { - printf('%03d', $story->id); - } - break; - case 'order': - echo ""; - break; - case 'pri': - echo ""; - echo zget($this->lang->story->priList, $story->pri, $story->pri); - echo ""; - break; - case 'title': - if($tab == 'project') - { - $showBranch = isset($this->config->projectstory->story->showBranch) ? $this->config->projectstory->story->showBranch : 1; - } - elseif($tab == 'execution') - { - if($isShowBranch) $showBranch = isset($this->config->execution->story->showBranch) ? $this->config->execution->story->showBranch : 1; - } - else - { - $showBranch = isset($this->config->product->browse->showBranch) ? $this->config->product->browse->showBranch : 1; - } - if($storyType == 'requirement') echo 'SR '; - if($story->parent > 0 and isset($story->parentName)) $story->title = "{$story->parentName} / {$story->title}"; - if(isset($branches[$story->branch]) and $showBranch and $this->config->vision == 'rnd') echo "branch]}>{$branches[$story->branch]} "; - if($story->module and isset($modulePairs[$story->module])) echo "{$modulePairs[$story->module]} "; - if($story->parent > 0) echo '' . $this->lang->story->childrenAB . ' '; - echo $canView ? html::a($storyLink, $story->title, '', "style='color: $story->color' data-app='$tab'") : "{$story->title}"; - if(!empty($story->children)) echo ''; - break; - case 'plan': - echo isset($story->planTitle) ? $story->planTitle : ''; - break; - case 'branch': - echo zget($branches, $story->branch, ''); - break; - case 'keywords': - echo $story->keywords; - break; - case 'source': - echo zget($this->lang->story->sourceList, $story->source, $story->source); - break; - case 'sourceNote': - echo $story->sourceNote; - break; - case 'category': - echo zget($this->lang->story->categoryList, $story->category); - break; - case 'status': - if($story->URChanged) - { - print("{$this->lang->story->URChanged}"); - break; - } - echo ""; - echo $this->processStatus('story', $story); - echo ''; - break; - case 'estimate': - echo $story->estimate . $this->config->hourUnit; - break; - case 'stage': - if(isset($storyStages[$story->id]) and !empty($branches)) - { - echo "'; - } - else - { - echo $this->lang->story->stageList[$story->stage]; - } - break; - case 'taskCount': - $tasksLink = helper::createLink('story', 'tasks', "storyID=$story->id", '', 'class="iframe"'); - $storyTasks[$story->id] > 0 ? print(html::a($tasksLink, $storyTasks[$story->id], '', 'class="iframe"')) : print(0); - break; - case 'bugCount': - $bugsLink = helper::createLink('story', 'bugs', "storyID=$story->id"); - $storyBugs[$story->id] > 0 ? print(html::a($bugsLink, $storyBugs[$story->id], '', 'class="iframe"')) : print(0); - break; - case 'caseCount': - $casesLink = helper::createLink('story', 'cases', "storyID=$story->id"); - $storyCases[$story->id] > 0 ? print(html::a($casesLink, $storyCases[$story->id], '', 'class="iframe"')) : print(0); - break; - case 'openedBy': - echo zget($users, $story->openedBy, $story->openedBy); - break; - case 'openedDate': - echo substr($story->openedDate, 5, 11); - break; - case 'assignedTo': - $this->printAssignedHtml($story, $users); - break; - case 'assignedDate': - echo substr($story->assignedDate, 5, 11); - break; - case 'reviewedBy': - echo $story->reviewedBy; - break; - case 'reviewedDate': - echo substr($story->reviewedDate, 5, 11); - break; - case 'closedBy': - echo zget($users, $story->closedBy, $story->closedBy); - break; - case 'closedDate': - echo substr($story->closedDate, 5, 11); - break; - case 'closedReason': - echo zget($this->lang->story->reasonList, $story->closedReason, $story->closedReason); - break; - case 'lastEditedBy': - echo zget($users, $story->lastEditedBy, $story->lastEditedBy); - break; - case 'lastEditedDate': - echo substr($story->lastEditedDate, 5, 11); - break; - case 'feedbackBy': - echo $story->feedbackBy; - break; - case 'notifyEmail': - echo $story->notifyEmail; - break; - case 'mailto': - $mailto = explode(',', $story->mailto); - foreach($mailto as $account) - { - $account = trim($account); - if(empty($account)) continue; - echo zget($users, $account) . '  '; - } - break; - case 'version': - echo $story->version; - break; - case 'actions': - if($tab == 'execution') - { - $menuType = 'execution'; - } - else - { - $menuType = 'browse'; - $execution = ''; - } - echo $this->buildOperateMenu($story, $menuType, $execution); - break; - } - echo ''; - } - } - - /** - * Product module story page add assignment function. - * - * @param object $story - * @param array $users - * @access public - * @return string - */ - public function printAssignedHtml($story, $users) - { - $btnTextClass = ''; - $assignedToText = zget($users, $story->assignedTo); - - if(empty($story->assignedTo)) - { - $btnTextClass = 'text-primary'; - $assignedToText = $this->lang->task->noAssigned; - } - if($story->assignedTo == $this->app->user->account) $btnTextClass = 'text-red'; - - $btnClass = $story->assignedTo == 'closed' ? ' disabled' : ''; - $btnClass = "iframe btn btn-icon-left btn-sm {$btnClass}"; - $assignToLink = helper::createLink('story', 'assignTo', "storyID=$story->id", '', true); - $assignToHtml = html::a($assignToLink, " {$assignedToText}", '', "class='$btnClass'"); - - echo !common::hasPriv('story', 'assignTo', $story) ? "{$assignedToText}" : $assignToHtml; - } - - /** - * Set report condition. - * - * @access public - * @return string - */ - public function reportCondition() - { - if(isset($_SESSION['storyQueryCondition'])) - { - if(!$this->session->storyOnlyCondition) - { - preg_match_all('/' . TABLE_STORY .' AS ([\w]+) /', $this->session->storyQueryCondition, $matches); - if(isset($matches[1][0])) return 'id in (' . preg_replace('/SELECT .* FROM/', "SELECT {$matches[1][0]}.id FROM", $this->session->storyQueryCondition) . ')'; - } - return $this->session->storyQueryCondition; - } - return true; - } - - /** - * Check force review for user. - * - * @access public - * @return bool - */ - public function checkForceReview() - { - $forceReview = false; - - $forceField = $this->config->story->needReview == 0 ? 'forceReview' : 'forceNotReview'; - $forceReviewRoles = !empty($this->config->story->{$forceField . 'Roles'}) ? $this->config->story->{$forceField . 'Roles'} : ''; - $forceReviewDepts = !empty($this->config->story->{$forceField . 'Depts'}) ? $this->config->story->{$forceField . 'Depts'} : ''; - - $forceUsers = ''; - if(!empty($this->config->story->{$forceField})) $forceUsers = $this->config->story->{$forceField}; - - if(!empty($forceReviewRoles) or !empty($forceReviewDepts)) - { - $users = $this->dao->select('account')->from(TABLE_USER) - ->where('deleted')->eq(0) - ->andWhere(0, true) - ->beginIF(!empty($forceReviewRoles)) - ->orWhere('(role', true)->in($forceReviewRoles) - ->andWhere('role')->ne('') - ->markRight(1) - ->fi() - ->beginIF(!empty($forceReviewDepts))->orWhere('dept')->in($forceReviewDepts)->fi() - ->markRight(1) - ->fetchAll('account'); - - $forceUsers .= "," . implode(',', array_keys($users)); - } - - $forceReview = $this->config->story->needReview == 0 ? strpos(",{$forceUsers},", ",{$this->app->user->account},") !== false : strpos(",{$forceUsers},", ",{$this->app->user->account},") === false; - - return $forceReview; - } - - /** - * Get tracks. - * - * @param int $productID - * @param int $branch - * @param int $projectID - * @param object $pager - * @access public - * @return bool|array - */ - public function getTracks($productID = 0, $branch = 0, $projectID = 0, $pager = null) - { - $tracks = array(); - $sourcePageID = $pager->pageID; - $excludeStories = false; - - if($this->config->URAndSR) - { - $projectStories = array(); - if($projectID) - { - $requirements = $this->dao->select('t3.*')->from(TABLE_PROJECTSTORY)->alias('t1') - ->leftJoin(TABLE_RELATION)->alias('t2')->on("t1.story=t2.AID && t2.AType='story'") - ->leftJoin(TABLE_STORY)->alias('t3')->on("t2.BID=t3.id && t2.BType='requirement' && t3.deleted='0'") - ->where('t1.project')->eq($projectID) - ->andWhere('t1.product')->eq($productID) - ->andWhere('t3.id')->ne('') - ->page($pager, 't3.id') - ->fetchAll('id'); - $projectStories = $this->getExecutionStories($projectID, $productID, $branch, '`order`_desc', 'all', 0, 'story'); - } - else - { - $requirements = $this->getProductStories($productID, $branch, 0, 'all', 'requirement', 'id_desc', true, '', $pager); - } - - if($pager->pageID != $sourcePageID) - { - $requirements = array(); - $pager->pageID = $sourcePageID; - } - - foreach($requirements as $requirement) - { - $stories = $this->getRelation($requirement->id, 'requirement'); - $stories = empty($stories) ? array() : $stories; - foreach($stories as $id => $title) - { - if($projectStories and !isset($projectStories[$id])) - { - unset($stories[$id]); - continue; - } - - $stories[$id] = new stdclass(); - $stories[$id]->title = $title; - $stories[$id]->cases = $this->loadModel('testcase')->getStoryCases($id); - $stories[$id]->bugs = $this->loadModel('bug')->getStoryBugs($id); - $stories[$id]->tasks = $this->loadModel('task')->getStoryTasks($id); - if($this->config->edition == 'max') - { - $stories[$id]->designs = $this->dao->select('id, name')->from(TABLE_DESIGN) - ->where('story')->eq($id) - ->andWhere('deleted')->eq('0') - ->fetchAll('id'); - $stories[$id]->revisions = $this->dao->select('BID, t2.comment')->from(TABLE_RELATION)->alias('t1') - ->leftjoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.BID = t2.id') - ->where('t1.AType')->eq('design') - ->andWhere('t1.BType')->eq('commit') - ->andWhere('t1.AID')->in(array_keys($stories[$id]->designs)) - ->fetchPairs(); - } - } - - $requirement->track = $stories; - } - - $tracks = $requirements; - - /* Get no requirements story. */ - $excludeStories = $this->dao->select('t1.BID')->from(TABLE_RELATION)->alias('t1') - ->leftJoin(TABLE_STORY)->alias('t2')->on("t1.AID=t2.id") - ->where('t2.deleted')->eq('0') - ->andWhere('t1.AType')->eq('requirement') - ->andWhere('t1.BType')->eq('story') - ->andWhere('t1.relation')->eq('subdivideinto') - ->andWhere('t1.product')->eq($productID) - ->fetchPairs('BID', 'BID'); - if($projectID) - { - $stories = $this->getExecutionStories($projectID, $productID, $branch, '`order`_desc', 'all', 0, 'story', $excludeStories); - } - else - { - $stories = $this->getProductStories($productID, $branch, 0, 'all', 'story', 'id_desc', true, $excludeStories); - } - } - else - { - if($projectID) - { - $stories = $this->getExecutionStories($projectID, $productID, $branch, '`order`_desc', 'all', 0, 'story', $excludeStories, $pager); - } - else - { - $stories = $this->getProductStories($productID, $branch, 0, 'all', 'story', 'id_desc', true, $excludeStories, $pager); - } - } - - if(count($tracks) < $pager->recPerPage) - { - /* Show sub stories. */ - $storiesCopy = array(); - foreach($stories as $id => $story) - { - $storiesCopy[$id] = $story; - - if(!isset($story->children) or count($story->children) == 0) continue; - foreach($story->children as $childID => $children) - { - $storiesCopy[$childID] = $children; - } - } - $stories = $storiesCopy; - - foreach($stories as $id => $story) - { - $stories[$id] = new stdclass(); - $stories[$id]->parent = $story->parent; - $stories[$id]->title = $story->title; - $stories[$id]->cases = $this->loadModel('testcase')->getStoryCases($id); - $stories[$id]->bugs = $this->loadModel('bug')->getStoryBugs($id); - $stories[$id]->tasks = $this->loadModel('task')->getStoryTasks($id); - if($this->config->edition == 'max') - { - $stories[$id]->designs = $this->dao->select('id, name')->from(TABLE_DESIGN) - ->where('story')->eq($id) - ->andWhere('deleted')->eq('0') - ->fetchAll('id'); - $stories[$id]->revisions = $this->dao->select('BID, t2.comment')->from(TABLE_RELATION)->alias('t1') - ->leftjoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.BID = t2.id') - ->where('t1.AType')->eq('design') - ->andWhere('t1.BType')->eq('commit') - ->andWhere('t1.AID')->in(array_keys($stories[$id]->designs)) - ->fetchPairs(); - } - } - - $tracks['noRequirement'] = $stories; - if($this->config->URAndSR) $pager->recTotal += 1; - } - - return $tracks; - } - - /** - * Get track by id. - * - * @param int $storyID - * @access public - * @return bool|array - */ - public function getTrackByID($storyID) - { - $requirement = $this->getByID($storyID); - - $stories = $this->getRelation($requirement->id, 'requirement'); - $track = array(); - $stories = empty($stories) ? array() : $stories; - foreach($stories as $id => $title) - { - $track[$id] = new stdclass(); - $track[$id]->title = $title; - $track[$id]->case = $this->loadModel('testcase')->getStoryCases($id); - $track[$id]->bug = $this->loadModel('bug')->getStoryBugs($id); - $track[$id]->story = $this->getByID($id); - $track[$id]->task = $this->loadModel('task')->getStoryTasks($id); - if($this->config->edition == 'max') - { - $track[$id]->design = $this->dao->select('id, name')->from(TABLE_DESIGN) - ->where('story')->eq($id) - ->andWhere('deleted')->eq('0') - ->fetchAll('id'); - $track[$id]->revision = $this->dao->select('BID, extra')->from(TABLE_RELATION)->where('AType')->eq('design')->andWhere('BType')->eq('commit')->andWhere('AID')->in(array_keys($track[$id]->design))->fetchPairs(); - } - } - - return $track; - } - - /** - * Obtain the direct relationship between UR and SR. - * - * @param int $storyID - * @param string $storyType - * @param array $fields - * @access public - * @return array - */ - public function getStoryRelation($storyID, $storyType, $fields = array()) - { - $conditionField = $storyType == 'story' ? 'BID' : 'AID'; - $storyType = $storyType == 'story' ? 'AID' : 'BID'; - - $relations = $this->dao->select($storyType)->from(TABLE_RELATION) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere($conditionField)->eq($storyID) - ->fetchPairs(); - - if(empty($relations)) return array(); - - $fields = empty($fields) ? '*' : implode(',', $fields); - $story = $this->dao->select($fields)->from(TABLE_STORY) - ->where('id')->in($relations) - ->andWhere('deleted')->eq(0) - ->orderBy('id_desc') - ->fetchAll(); - - return $story; - } - - /** - * Get story relation by Ids. - * - * @param array $storyIdList - * @param string $storyType - * @access public - * @return array - */ - public function getStoryRelationByIds($storyIdList, $storyType) - { - $conditionField = $storyType == 'story' ? 'BID' : 'AID'; - $storyType = $storyType == 'story' ? 'BID, GROUP_CONCAT(`AID` SEPARATOR ",")' : 'AID, GROUP_CONCAT(`BID` SEPARATOR ",")'; - - $relations = $this->dao->select($storyType)->from(TABLE_RELATION) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere($conditionField)->in($storyIdList) - ->groupBy($conditionField) - ->fetchPairs(); - - return $relations; - } - - /** - * Link a story. - * - * @param int $executionID - * @param int $productID - * @param int $storyID - * @access public - * @return void - */ - public function linkStory($executionID, $productID, $storyID) - { - $lastOrder = (int)$this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->orderBy('order_desc')->limit(1)->fetch('order'); - $this->dao->insert(TABLE_PROJECTSTORY) - ->set('project')->eq($executionID) - ->set('product')->eq($productID) - ->set('story')->eq($storyID) - ->set('version')->eq(1) - ->set('order')->eq($lastOrder + 1) - ->exec(); - } - - /** - * Link stories. - * - * @param int $storyID - * @access public - * @return void - */ - public function linkStories($storyID) - { - $story = $this->getByID($storyID); - $stories = $this->post->stories; - $isStory = ($story->type == 'story'); - - foreach($stories as $id) - { - $requirement = $this->getByID($id); - $data = new stdclass(); - $data->AType = 'requirement'; - $data->BType = 'story'; - $data->product = $story->product; - $data->relation = 'subdivideinto'; - $data->AID = $isStory ? $id : $storyID; - $data->BID = $isStory ? $storyID : $id; - $data->AVersion = $isStory ? $requirement->version : $story->version; - $data->BVersion = $isStory ? $story->version : $requirement->version; - - $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); - - $data->AType = 'story'; - $data->BType = 'requirement'; - $data->relation = 'subdividedfrom'; - $data->product = $story->product; - $data->AID = $isStory ? $storyID : $id; - $data->BID = $isStory ? $id : $storyID; - $data->AVersion = $isStory ? $story->version : $requirement->version; - $data->BVersion = $isStory ? $requirement->version : $story->version; - - $this->dao->insert(TABLE_RELATION)->data($data)->autoCheck()->exec(); - } - } - - /** - * Unlink story. - * - * @param int $storyID - * @param int $linkedStoryID - * @access public - * @return void - */ - public function unlinkStory($storyID, $linkedStoryID) - { - $idList = "$storyID,$linkedStoryID"; - - $this->dao->delete()->from(TABLE_RELATION) - ->where('AType')->in('story,requirement') - ->andWhere('BType')->in('story,requirement') - ->andWhere('relation')->in('subdivideinto,subdividedfrom') - ->andWhere('AID')->in($idList) - ->andWhere('BID')->in($idList) - ->exec(); - - return !dao::isError(); - } - - /** - * Get associated requirements. - * - * @param int $storyID - * @param string $storyType - * @param array $fields - * @access public - * @return array - */ - public function getRelation($storyID, $storyType, $fields = array()) - { - $BType = $storyType == 'story' ? 'requirement' : 'story'; - $relation = $storyType == 'story' ? 'subdividedfrom' : 'subdivideinto'; - - $relations = $this->dao->select('BID')->from(TABLE_RELATION) - ->where('AType')->eq($storyType) - ->andWhere('BType')->eq($BType) - ->andWhere('relation')->eq($relation) - ->andWhere('AID')->eq($storyID) - ->fetchPairs(); - - if(empty($relations)) return array(); - - $queryFields = empty($fields) ? 'id,title' : implode(',', $fields); - if(!empty($fields)) return $this->dao->select($queryFields)->from(TABLE_STORY)->where('id')->in($relations)->andWhere('deleted')->eq(0)->fetchAll('id'); - return $this->dao->select($queryFields)->from(TABLE_STORY)->where('id')->in($relations)->andWhere('deleted')->eq(0)->fetchPairs(); - } - - /** - * Get software requirements associated with user needs. - * - * @param array $storyID - * @param string $storyType - * @access public - * @return int - */ - public function getStoryRelationCounts($storyID, $storyType = '') - { - $selectField = ($storyType == 'story') ? 'AID' : 'BID'; - $conditionField = ($storyType == 'story') ? 'BID' : 'AID'; - - $relations = $this->dao->select('count('. $selectField .') as id')->from(TABLE_RELATION) - ->where('AType')->eq('requirement') - ->andWhere('BType')->eq('story') - ->andWhere('relation')->eq('subdivideinto') - ->andWhere($conditionField)->eq($storyID) - ->fetch('id'); - - return $relations; - } - - /** - * Get estimate info - * - * @param int $storyID - * @param int $round - * @access public - * @return bool|array - */ - public function getEstimateInfo($storyID, $round = 0) - { - $estimateInfo = $this->dao->select('*')->from(TABLE_STORYESTIMATE) - ->where('story')->eq($storyID) - ->beginIf($round)->andWhere('round')->eq($round)->fi() - ->orderBy('round_desc') - ->fetch(); - - if(!empty($estimateInfo)) $estimateInfo->estimate = json_decode($estimateInfo->estimate); - return $estimateInfo; - } - - /** - * Get estimate rounds. - * - * @param int $storyID - * @access public - * @return array - */ - public function getEstimateRounds($storyID) - { - $lastRound = $this->dao->select('round')->from(TABLE_STORYESTIMATE) - ->where('story')->eq($storyID) - ->orderBy('round_desc') - ->fetch('round'); - if(!$lastRound) return array(); - - $rounds = array(); - for($i = 1; $i <= $lastRound; $i++) - { - $rounds[$i] = sprintf($this->lang->story->storyRound, $i); - } - - return $rounds; - } - - /** - * Save estimate information. - * - * @param int $storyID - * @access public - * @return void - */ - public function saveEstimateInfo($storyID) - { - $data = fixer::input('post')->get(); - - $lastRound = $this->dao->select('round')->from(TABLE_STORYESTIMATE) - ->where('story')->eq($storyID) - ->orderBy('round_desc') - ->fetch('round'); - - $estimates = array(); - foreach($data->account as $key => $account) - { - $estimates[$account]['account'] = $account; - if(!empty($data->estimate[$key]) and !is_numeric($data->estimate[$key])) - { - dao::$errors[] = $this->lang->story->estimateMustBeNumber; - return false; - } - - if(!empty($data->estimate[$key]) and $data->estimate[$key] < 0) - { - dao::$errors[] = $this->lang->story->estimateMustBePlus; - return false; - } - $estimates[$account]['estimate'] = strpos($data->estimate[$key], '-') !== false ? (int)$data->estimate[$key] : (float)$data->estimate[$key]; - } - - - $storyEstimate = new stdclass(); - $storyEstimate->story = $storyID; - $storyEstimate->round = empty($lastRound) ? 1 : $lastRound + 1; - $storyEstimate->estimate = json_encode($estimates); - $storyEstimate->average = $data->average; - $storyEstimate->openedBy = $this->app->user->account; - $storyEstimate->openedDate = helper::now(); - - $this->dao->insert(TABLE_STORYESTIMATE)->data($storyEstimate)->exec(); - } - - /** - * Update the story order according to the plan. - * - * @param int $planID - * @param array $sortIDList - * @param string $orderBy - * @param int $pageID - * @param int $recPerPage - * @access public - * @return void - */ - public function sortStoriesOfPlan($planID, $sortIDList, $orderBy = 'id_desc', $pageID = 1, $recPerPage = 100) - { - /* Append id for secend sort. */ - $orderBy = common::appendOrder($orderBy); - - /* Get all stories by plan. */ - $stories = $this->getPlanStories($planID, 'all', $orderBy); - $storyIDList = array_keys($stories); - - /* Calculate how many numbers there are before the sort list and after the sort list. */ - $frontStoryCount = $recPerPage * ($pageID - 1); - $behindStoryCount = $recPerPage * $pageID; - $frontStoryIDList = array_slice($storyIDList, 0, $frontStoryCount); - $behindStoryIDList = array_slice($storyIDList, $behindStoryCount, count($storyIDList) - $behindStoryCount); - - /* Merge to get a new sort list. */ - $newSortIDList = array_merge($frontStoryIDList, $sortIDList, $behindStoryIDList); - if(strpos($orderBy, 'order_desc') !== false) $newSortIDList = array_reverse($newSortIDList); - - /* Loop update the story order of plan. */ - $order = 1; - foreach($newSortIDList as $storyID) - { - $this->dao->update(TABLE_PLANSTORY)->set('`order`')->eq($order)->where('story')->eq($storyID)->andWhere('plan')->eq($planID)->exec(); - $order++; - } - } - - /** - * Replace story lang to requirement. - * - * @param string $type - * @access public - * @return void - */ - public function replaceURLang($type) - { - if($type == 'requirement') - { - $storyLang = $this->lang->story; - $SRCommon = $this->lang->SRCommon; - $URCommon = $this->lang->URCommon; - - $storyLang->create = str_replace($SRCommon, $URCommon, $storyLang->create); - $storyLang->changeAction = str_replace($SRCommon, $URCommon, $storyLang->changeAction); - $storyLang->changed = str_replace($SRCommon, $URCommon, $storyLang->changed); - $storyLang->assignAction = str_replace($SRCommon, $URCommon, $storyLang->assignAction); - $storyLang->reviewAction = str_replace($SRCommon, $URCommon, $storyLang->reviewAction); - $storyLang->subdivideAction = str_replace($SRCommon, $URCommon, $storyLang->subdivideAction); - $storyLang->closeAction = str_replace($SRCommon, $URCommon, $storyLang->closeAction); - $storyLang->activateAction = str_replace($SRCommon, $URCommon, $storyLang->activateAction); - $storyLang->deleteAction = str_replace($SRCommon, $URCommon, $storyLang->deleteAction); - $storyLang->view = str_replace($SRCommon, $URCommon, $storyLang->view); - $storyLang->linkStory = str_replace($SRCommon, $URCommon, $storyLang->linkStory); - $storyLang->unlinkStory = str_replace($SRCommon, $URCommon, $storyLang->unlinkStory); - $storyLang->exportAction = str_replace($SRCommon, $URCommon, $storyLang->exportAction); - $storyLang->zeroCase = str_replace($SRCommon, $URCommon, $storyLang->zeroCase); - $storyLang->zeroTask = str_replace($SRCommon, $URCommon, $storyLang->zeroTask); - $storyLang->copyTitle = str_replace($SRCommon, $URCommon, $storyLang->copyTitle); - $storyLang->common = str_replace($SRCommon, $URCommon, $storyLang->common); - $storyLang->title = str_replace($SRCommon, $URCommon, $storyLang->title); - $storyLang->spec = str_replace($SRCommon, $URCommon, $storyLang->spec); - $storyLang->children = str_replace($SRCommon, $URCommon, $storyLang->children); - $storyLang->linkStories = str_replace($SRCommon, $URCommon, $storyLang->linkStories); - $storyLang->childStories = str_replace($SRCommon, $URCommon, $storyLang->childStories); - $storyLang->duplicateStory = str_replace($SRCommon, $URCommon, $storyLang->duplicateStory); - $storyLang->newStory = str_replace($SRCommon, $URCommon, $storyLang->newStory); - $storyLang->copy = str_replace($SRCommon, $URCommon, $storyLang->copy); - $storyLang->total = str_replace($SRCommon, $URCommon, $storyLang->total); - $storyLang->released = str_replace($SRCommon, $URCommon, $storyLang->released); - $storyLang->legendLifeTime = str_replace($SRCommon, $URCommon, $storyLang->legendLifeTime); - $storyLang->legendLinkStories = str_replace($SRCommon, $URCommon, $storyLang->legendLinkStories); - $storyLang->legendChildStories = str_replace($SRCommon, $URCommon, $storyLang->legendChildStories); - $storyLang->legendSpec = str_replace($SRCommon, $URCommon, $storyLang->legendSpec); - - $storyLang->report->charts['storysPerProduct'] = str_replace($SRCommon, $URCommon, $storyLang->report->charts['storysPerProduct']); - $storyLang->report->charts['storysPerModule'] = str_replace($SRCommon, $URCommon, $storyLang->report->charts['storysPerModule']); - $storyLang->report->charts['storysPerSource'] = str_replace($SRCommon, $URCommon, $storyLang->report->charts['storysPerSource']); - } - } - - /** - * Get story reviewer pairs. - * - * @param int $storyID - * @param int version - * @access public - * @return array - */ - public function getReviewerPairs($storyID, $version) - { - return $this->dao->select('reviewer,result')->from(TABLE_STORYREVIEW)->where('story')->eq($storyID)->andWhere('version')->eq($version)->fetchPairs('reviewer', 'result'); - } - - /** - * Set story status by review rules. - * - * @param array $reviewerList - * @access public - * @return string - */ - public function setStatusByReviewRules($reviewerList) - { - $status = ''; - $passCount = 0; - $rejectCount = 0; - $reviewRule = $this->config->story->reviewRules; - foreach($reviewerList as $reviewer => $reviewResult) - { - $passCount = $reviewResult == 'pass' ? $passCount + 1 : $passCount; - $rejectCount = $reviewResult == 'reject' ? $rejectCount + 1 : $rejectCount; - } - - if($reviewRule == 'allpass') - { - if($passCount == count($reviewerList)) $status = 'active'; - if($rejectCount == count($reviewerList)) $status = 'closed'; - } - - if($reviewRule == 'halfpass') - { - if($passCount >= floor(count($reviewerList) / 2) + 1) $status = 'active'; - if($rejectCount >= floor(count($reviewerList) / 2) + 1) $status = 'closed'; - } - - return $status; - } - - /** - * Record story review actions. - * - * @param object $story - * @param string $result - * @param string $reason - * @access public - * @return int|string - */ - public function recordReviewAction($story, $result = '', $reason = '') - { - $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); - - $comment = isset($_POST['comment']) ? $this->post->comment : ''; - - if($isSuperReviewer !== false and $this->app->rawMethod != 'edit') - { - $actionID = $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . '|superReviewer'); - return $actionID; - } - - $reasonParam = $result == 'reject' ? ',' . $reason : ''; - $reviewers = $this->getReviewerPairs($story->id, $story->version); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - - $actionID = !empty($result) ? $this->loadModel('action')->create('story', $story->id, 'Reviewed', $comment, ucfirst($result) . $reasonParam) : ''; - - if($result != 'revert') - { - if($story->status == 'closed') $this->action->create('story', $story->id, 'ReviewRejected'); - if($story->status == 'active') $this->action->create('story', $story->id, 'ReviewPassed'); - if(!array_diff(array_keys($reviewers), $reviewedBy) and ($story->status == 'draft' or $story->status == 'changed')) $this->action->create('story', $story->id, 'ReviewClarified'); - } - - return $actionID; - } - - /** - * Update the story fields value by review. - * - * @param int $storyID - * @param object $oldStory - * @param object $story - * @access public - * @return object - */ - public function updateStoryByReview($storyID, $oldStory, $story) - { - $isSuperReviewer = strpos(',' . trim(zget($this->config->story, 'superReviewers', ''), ',') . ',', ',' . $this->app->user->account . ','); - if($isSuperReviewer !== false) return $this->superReview($storyID, $oldStory, $story); - - $now = helper::now(); - $reviewerList = $this->getReviewerPairs($storyID, $oldStory->version); - $reviewedBy = explode(',', trim($story->reviewedBy, ',')); - if(!array_diff(array_keys($reviewerList), $reviewedBy)) - { - $status = $this->post->result == 'revert' ? 'active' : $this->setStatusByReviewRules($reviewerList); - $story->status = $status ? $status : $oldStory->status; - if($story->status == 'closed') - { - $story->closedBy = $this->app->user->account; - $story->closedDate = $now; - $story->assignedTo = 'closed'; - $story->assignedDate = $now; - $story->stage = 'closed'; - if($this->post->closedReason == 'done') $story->stage = 'released'; - } - } - - return $story; - } - - /** - * To review for super reviewer. - * - * @param int $storyID - * @param object $oldStory - * @param object $story - * @param string $result - * @param string $reason - * @access public - * @return object - */ - public function superReview($storyID, $oldStory, $story, $result = '', $reason = '') - { - $now = helper::now(); - $status = ''; - $result = isset($_POST['result']) ? $this->post->result : $result; - if(strpos('revert,pass', $result) !== false) $status = 'active'; - if($result == 'reject') $status = 'closed'; - if($result == 'clarify' or empty($status)) $status = $oldStory->status; - - $story->status = $status; - - if($story->status == 'closed') - { - $story->closedBy = $this->app->user->account; - $story->closedDate = $now; - $story->assignedTo = 'closed'; - $story->assignedDate = $now; - $story->stage = 'closed'; - $story->closedReason = isset($_POST['closedReason']) ? $_POST['closedReason'] : $reason; - if($_POST['closedReason'] == 'done') $story->stage = 'released'; - } - - $this->dao->delete()->from(TABLE_STORYREVIEW) - ->where('story')->eq($storyID) - ->andWhere('version')->eq($oldStory->version) - ->andWhere('result')->eq('') - ->exec(); - - return $story; - } -} diff --git a/module/story:q/view/activate.html.php b/module/story:q/view/activate.html.php deleted file mode 100644 index 18c08dfe88..0000000000 --- a/module/story:q/view/activate.html.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @package story - * @version $Id: activate.html.php 4129 2013-01-18 01:58:14Z wwccss $ - * @link http://www.zentao.net - */ -?> - -
    -
    -
    -

    - id;?> - title'>" . $story->title . '') : html::a($this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> - - arrow . $lang->story->activate;?> - -

    -
    -
    - - - - - - - - - - printExtendFields($story, 'table');?> - - - - - - - -
    story->assignedTo;?>closedBy, 'class="form-control chosen"');?>
    story->status;?> - story->statusList['']); - unset($lang->story->statusList['closed']); - echo html::select('status', $lang->story->statusList, 'active', 'class="form-control chosen"'); - ?> -
    story->comment;?>
    - story->activate, '', 'btn btn-wide btn-primary'); - echo html::linkButton($lang->goback, $app->session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id"), 'self', '', 'btn btn-wide'); - ?> -
    -
    -
    -
    -
    -
    - diff --git a/module/story:q/view/affected.html.php b/module/story:q/view/affected.html.php deleted file mode 100644 index b47ed04ea6..0000000000 --- a/module/story:q/view/affected.html.php +++ /dev/null @@ -1,102 +0,0 @@ -
    - -
    -
    - executions as $executionID => $execution):?> -
    name ?>   - teams[$executionID])):?> - teams[$executionID] as $member) echo zget($users, $member->account) . ' ';?> - -
    - - - - - - - - - - - - tasks[$executionID])):?> - - tasks[$executionID] as $task):?> - - - - - - - - - - - -
    task->id;?>task->name;?>task->assignedTo;?>task->status;?>task->consumed;?>task->left;?>
    id;?>createLink('task', 'view', "taskID=$task->id"), $task->name, '_blank');?>assignedTo);?> - processStatus('task', $task);?> - consumed;?>left;?>
    - -
    -
    - - - - - - - - - - - - - - bugs as $bug):?> - - - - - - - - - - - -
    idAB;?>bug->title;?>statusAB;?>bug->openedBy;?>bug->resolvedBy;?>bug->resolution;?>bug->lastEditedBy;?>
    id;?>createLink('bug', 'view', "bugID=$bug->id"), $bug->title, '_blank');?> - processStatus('bug', $bug);?> - openedBy);?>resolvedBy);?>bug->resolutionList[$bug->resolution];?>lastEditedBy);?>
    -
    -
    - - - - - - - - - - - - cases as $case):?> - - - - - - - - - -
    idAB;?>testcase->title;?>statusAB;?>testcase->openedBy;?>testcase->lastEditedBy;?>
    id;?>createLink('testcase', 'view', "caseID=$case->id"), $case->title, '_blank');?> - processStatus('testcase', $case);?> - openedBy);?>lastEditedBy);?>
    -
    -
    -
    diff --git a/module/story:q/view/assignto.html.php b/module/story:q/view/assignto.html.php deleted file mode 100644 index 8db1b45f73..0000000000 --- a/module/story:q/view/assignto.html.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @package story - * @version $Id: complete.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ - * @link http://www.zentao.net - */ -?> - - -
    -
    -
    -

    - id;?> - title'>" . $story->title . '') : html::a($this->createLink('story', 'view', 'story=' . $story->id), $story->title);?> - - arrow . $lang->story->assignTo;?> - -

    -
    -
    - - - - - - - - - - printExtendFields($story, 'table');?> - - - - - - - -
    story->assign;?>assignedTo, "class='form-control chosen'");?>
    story->status;?>status);?>
    comment;?>
    - - goback, $this->session->storyList, '', '', 'btn btn-wide');?> -
    -
    -
    - -
    -
    - diff --git a/module/story:q/view/batchclose.html.php b/module/story:q/view/batchclose.html.php deleted file mode 100755 index 471f4ae061..0000000000 --- a/module/story:q/view/batchclose.html.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @package story - * @version $Id$ - * @link http://www.zentao.net - */ -?> - - -tab);?> -
    -
    -

    story->common . $lang->colon . $lang->story->batchClose;?>

    -
    - -
    - -
    "> - - - - - - - - - - - $story):?> - - - - - - - - - - - -
    idAB;?>story->title;?>story->status;?>story->closedReason;?>story->comment;?>
    title;?>processStatus('story', $story);?> - status == 'draft') unset($reasonList['cancel']);?> - - - - - - -
    - - ' closedReason != 'duplicate') echo "style='display:none'";?>> - idAB}'");?> - ' closedReason != 'subdivided') echo "style='display:none'";?>> - idAB}'");?> -
    -
    - - -
    -
    - -
    - diff --git a/module/story:q/view/batchcreate.html.php b/module/story:q/view/batchcreate.html.php deleted file mode 100755 index 108a37739d..0000000000 --- a/module/story:q/view/batchcreate.html.php +++ /dev/null @@ -1,196 +0,0 @@ - - * @package story - * @version $Id$ - * @link http://www.zentao.net - */ -?> - -
    -
    -

    lang->story->subdivide : $this->lang->story->batchCreate;?>

    -
    - createLink('file', 'uploadImages', 'module=story¶ms=' . helper::safe64Encode("productID=$productID&branch=$branch&moduleID=$moduleID&storyID=$storyID&executionID=$executionID&plan=&type=$type")), $lang->uploadImages, '', "data-toggle='modal' data-type='iframe' class='btn btn-primary' data-width='70%'")?> - - createLink('custom', 'ajaxSaveCustomFields', 'module=story§ion=custom&key=batchCreateFields')?> - - -
    - - -
    -
    - story->create->requiredFields) as $field) - { - if($field) - { - $requiredFields[$field] = ''; - if(strpos(",{$config->story->list->customBatchCreateFields},", ",{$field},") !== false) $visibleFields[$field] = ''; - } - } - unset($visibleFields['module']); - ?> -
    -
    - - - - - - - type == 'kanban'):?> - - - - - - - - - - - - - - story->getFlowExtendFields(); - foreach($extendFields as $extendField) - { - $required = strpos(",$extendField->rules,", ',1,') !== false ? 'required' : ''; - echo ""; - } - ?> - - - - - - - - type == 'kanban'):?> - - - - - - - - - - - loadModel('flow'); - foreach($extendFields as $extendField) echo "control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, '', $extendField->field . '[$id]') . ""; - ?> - - - - - - - -
    '>product->branch;?>'>story->module;?>'>story->plan;?>kanbancard->region;?>kanbancard->lane;?>story->title;?>'>story->spec;?>'>story->source;?>'>story->sourceNote;?>'>story->verify;?>story->category;?>'>story->pri;?>'>story->estimate;?>' style="width: 200px !important">story->reviewedBy;?>'>story->keywords;?>{$extendField->name}
    '>' style='overflow:visible'> - - - -
    -
    - -
    - - - -
    -
    - - - -
    -
    '>'>'>'>story->categoryList, 'feature', "class='form-control chosen'");?>' style='overflow:visible'>'>'> -
    - - story->ditto;?> -
    -
    '>
    - save);?> - -
    -
    -
    -
    - - - - - - - - - diff --git a/module/story:q/view/batchedit.html.php b/module/story:q/view/batchedit.html.php deleted file mode 100644 index bd6870752e..0000000000 --- a/module/story:q/view/batchedit.html.php +++ /dev/null @@ -1,159 +0,0 @@ - - * @package story - * @version $Id$ - * @link http://www.zentao.net - */ -?> - -lang->story->dittoNotice);?> - -app->tab);?> - -
    -
    -

    - story->common . $lang->colon . $lang->story->batchEdit;?> -

    -
    - createLink('custom', 'ajaxSaveCustomFields', 'module=story§ion=custom&key=batchEditFields')?> - -
    -
    - -
    - - -
    " id="batchEditForm"> -
    - - - - - - - - - - - - - - - - - - - - - - story->getFlowExtendFields(); - foreach($extendFields as $extendField) echo ""; - ?> - - - - $story):?> - - - - - - - - - - - - - - - source == 'meeting' or $story->source == 'researchreport') - { - $objects = $story->source == 'meeting' ? $meetings : $researchReports; - $html = html::select("sourceNote[$storyID]", $objects, $story->sourceNote, "class='form-control picker-select' data-drop-width='auto' id='sourceNote_$storyID'"); - } - else - { - $html = html::input("sourceNote[$storyID]", $story->sourceNote, "class='form-control' id='sourceNote_$storyID'"); - } - ?> - - - - - - status == 'closed'):?> - - - - - - control == 'select' or $extendField->control == 'multi-select') ? " style='overflow: visible'" : '') . ">" . $this->loadModel('flow')->getFieldControl($extendField, $story, $extendField->field . "[{$storyID}]") . "";?> - - - - - - - - -
    idAB;?>'>story->branch;?>story->module;?> col-plan'>story->planAB;?>story->title;?>'> story->estimateAB;?>story->category;?>' title=story->pri;?>> priAB;?>'> story->assignedTo;?>'> story->source;?>'> story->sourceNote;?>story->status;?>'> story->stageAB;?>'>story->closedBy;?>'> story->closedReason;?>'>story->keywords;?>{$extendField->name}
    '> - product]->type == 'normal' ? "disabled='disabled'" : '';?> - product]->type == 'normal') $branchTagOption[$story->product] = array();?> - product], $story->branch, "class='form-control picker-select' data-drop-width='auto' onchange='loadBranches($story->product, this.value, $storyID);' $disabled");?> - '> - id, array(0 => '/')), $story->module, "class='form-control picker-select' data-drop-width='auto'");?> - '> - product][$story->branch]) ? array('' => '') + $plans[$story->product][$story->branch] : '', $story->plan, "class='form-control picker-select' data-drop-width='auto'");?> - -
    -
    - title, "class='form-control input-story-title' disabled"); ?> - title); ?> - -
    - - - color, "class='colorpicker' data-wrapper='input-control-icon-right' data-icon='color' data-btn-tip='{$lang->story->colorTag}' data-update-color='#titles\\[{$storyID}\\]'");?> -
    -
    -
    -
    >estimate, "class='form-control'"); ?>story->categoryList, $story->category, 'class="form-control picker-select" data-drop-width="auto"');?>>pri, 'class=form-control');?>'>assignedTo, "class='form-control picker-select' data-drop-width='auto'");?>>source, "class='form-control picker-select' data-drop-width='auto' id='source_$storyID'");?>' data-id="sourceNote;?>">processStatus('story', $story);?>>stage, 'class="form-control"' . ($story->status == 'draft' ? ' disabled="disabled"' : ''));?>'>closedBy, "class='form-control" . ($story->status == 'closed' ? " picker-select' data-drop-width='auto'" : "' disabled='disabled'"));?>> - - - - - - -
    - closedReason, "class=form-control onchange=setDuplicateAndChild(this.value,$storyID) style='min-width: 70px'");?> - ' closedReason != 'duplicate') echo "style='display: none'";?>> - idAB}'");?> - ' closedReason != 'subdivided') echo "style='display: none'";?>> - idAB}'");?> -
    -
    >closedReason, 'class="form-control" disabled="disabled"');?>>keywords, 'class="form-control"');?>
    - - app->tab == 'product' or $this->app->tab == 'execution') ? html::a($this->session->storyList, $lang->goback, '', "class='btn btn-back btn-wide'") : html::backButton();?> -
    -
    -
    - - diff --git a/module/story:q/view/bugs.html.php b/module/story:q/view/bugs.html.php deleted file mode 100644 index 8101b47228..0000000000 --- a/module/story:q/view/bugs.html.php +++ /dev/null @@ -1,40 +0,0 @@ - -
    -
    -

    - icons['report']);?> - story->bugs;?> -

    -
    -
    - - - - - - - - - - - - - - - $bug):?> - - - - - - - - - - - - -
    idAB;?> bug->title;?>story->pri;?>>priAB;?> bug->type;?> statusAB;?> bug->assignedTo;?> bug->resolvedBy;?>bug->resolution;?>
    id;?>title;?>bug->priList, $bug->pri, $bug->pri)?>'>pri == '0' ? '' : zget($lang->bug->priList, $bug->pri, $bug->pri);?>bug->typeList[$bug->type];?>processStatus('bug',$bug);?>assignedTo, $bug->assignedTo);?>resolvedBy, $bug->resolvedBy);?>bug->resolutionList[$bug->resolution];?>
    -
    -
    - diff --git a/module/story:q/view/cases.html.php b/module/story:q/view/cases.html.php deleted file mode 100644 index d36a419aef..0000000000 --- a/module/story:q/view/cases.html.php +++ /dev/null @@ -1,40 +0,0 @@ - -
    -
    -

    - icons['report']);?> - story->cases;?> -

    -
    -
    - - - - - - - - - - - - - - - $case):?> - - - - - - - - - - - - -
    idAB;?> testcase->title;?>story->pri;?>>priAB;?> testcase->type;?>statusAB;?> testcase->lastRunner;?> testcase->lastRunDate;?>testcase->lastRunResult;?>
    id;?>title;?>testcase->priList, $case->pri, $case->pri)?>'>pri == '0' ? '' : zget($lang->case->priList, $case->pri, $case->pri);?>testcase->typeList[$case->type];?>processStatus('testcase', $case);?>lastRunner, $case->lastRunner);?>lastRunDate)) echo date(DT_MONTHTIME1, strtotime($case->lastRunDate));?>lastRunResult ? $lang->testcase->resultList[$case->lastRunResult] : '';?>
    -
    -
    - diff --git a/module/story:q/view/change.html.php b/module/story:q/view/change.html.php deleted file mode 100644 index 2504454e81..0000000000 --- a/module/story:q/view/change.html.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @package story - * @version $Id: change.html.php 4129 2013-01-18 01:58:14Z wwccss $ - * @link http://www.zentao.net - */ -?> - -
    -
    -
    -

    - id;?> - createLink('story', 'view', "storyID=$story->id"), $story->title, '', 'class="story-title"');?> - arrow . ' ' . $lang->story->change;?> -

    -
    -
    - - - - - - - - - - - printExtendFields($story, 'table');?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    story->reviewedBy;?> -
    - story->checkForceReview() ? ' required' : ''));?> - story->checkForceReview()):?> - - story->needNotReview, '', "id='needNotReview' {$needReview}");?> - - -
    -
    story->status;?>status);?>
    story->title;?> -
    - - - -
    - title, 'class="form-control story-title"');?> -
    story->spec;?>spec), 'rows=8 class="form-control"');?>story->specTemplate;?>
    story->verify;?>verify), 'rows=6 class="form-control"');?>
    story->comment;?>
    attatch;?>fetch('file', 'buildform');?>
    story->checkAffection;?>
    - lastEditedDate); - echo html::submitButton(); - if(!isonlybody()) echo html::backButton(); - ?> -
    -
    -
    -
    - -
    -
    -
    -id);?> -title);?> -spec);?> -verify);?> - -type);?> -app->rawModule);?> - diff --git a/module/story:q/view/close.html.php b/module/story:q/view/close.html.php deleted file mode 100644 index f27d1c9d12..0000000000 --- a/module/story:q/view/close.html.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @package story - * @version $Id: close.html.php 4129 2013-01-18 01:58:14Z wwccss $ - * @link http://www.zentao.net - */ -?> - -
    -
    -
    -

    - id;?> - title'>" . $story->title . '') : html::a($this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> - - arrow . $lang->story->close;?> - -

    -
    -
    - - - - - - - - - - - - - - - - - - printExtendFields($story, 'table');?> - - - - - - - -
    story->closedReason;?>
    story->status;?>
    story->comment;?>
    - - session->storyList ? $app->session->storyList : inlink('view', "storyID=$story->id"), $lang->goback, '', 'class="btn btn-wide"');?> -
    -
    -
    -
    -
    -
    - diff --git a/module/story:q/view/create.html.php b/module/story:q/view/create.html.php deleted file mode 100644 index ef6586f763..0000000000 --- a/module/story:q/view/create.html.php +++ /dev/null @@ -1,321 +0,0 @@ - - * @package story - * @version $Id: create.html.php 4902 2013-06-26 05:25:58Z wyd621@gmail.com $ - * @link http://www.zentao.net - */ -?> - - - -story->placeholder); ?> - -story->feedbackSource); ?> - - - - -
    -
    -
    -

    story->create;?>

    -
    - createLink('custom', 'ajaxSaveCustomFields', 'module=story§ion=custom&key=createFields')?> - -
    -
    - story->create->requiredFields) as $field) - { - if($field and strpos($showFields, $field) === false) $showFields .= ',' . $field; - } - ?> -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - config->URAndSR):?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - printExtendFields('', 'table', 'columns=4');?> - - - - - - - - - - - - - - - - - - - - - - -
    story->product;?> -
    - - - -
    -
    -
    -
    story->module;?>
    - "; - echo html::a($this->createLink('tree', 'browse', "rootID=$productID&view=story¤tModuleID=0&branch=$branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='90%'"); - echo '  '; - echo html::a("javascript:void(0)", $lang->refresh, '', "class='refresh' onclick='loadProductModules($productID)'"); - echo '
    '; - } - ?> - -
    story->planAB;?> -
    - "; - echo html::a($this->createLink('productplan', 'create', "productID=$productID&branch=$branch", '', true), "", '', "class='btn btn-icon' data-toggle='modal' data-type='iframe' data-width='95%' title='{$lang->productplan->create}'"); - echo '
    '; - echo "
    "; - echo html::a("javascript:void(0)", "", '', "class='btn btn-icon refresh' data-toggle='tooltip' title='{$lang->refresh}' onclick='loadProductPlans($productID)'"); - echo '
    '; - } - ?> - -
    -
    -
    -
    story->source;?>
    - story->sourceList, $source, "class='form-control chosen'");?> - story->sourceNote;?> - - -
    -
    -
    story->reviewedBy;?>' id='reviewerBox'> -
    - story->checkForceReview()):?> -
    - PO : '', "class='form-control picker-select' multiple");?> -
    -
    - -
    - /> - -
    -
    -
    - -
    - PO : '', "class='form-control picker-select' multiple required");?> -
    - -
    -
    ' id='assignedToBox' class='hidden'> -
    -
    story->assignedTo;?>
    - -
    -
    -
    -
    -
    story->source;?>
    - story->sourceList, $source, "class='form-control chosen'");?> - story->sourceNote;?> - -
    -
    -
    story->requirement;?> -
    -
    story->parent;?>
    - -
    -
    story->parent;?>
    kanbancard->region;?> - -
    -
    kanbancard->lane;?>
    - -
    -
    story->title;?> -
    -
    -
    - -
    - - - -
    -
    -
    - -
    -
    - story->category;?> - story->categoryList, 'feature', "class='form-control chosen'");?> -
    -
    -
    -
    - story->pri;?> - story->priList as $priKey => $priValue) - { - if(!empty($priKey) and (string)$priKey != (string)$priValue) - { - $hasCustomPri = true; - break; - } - } - - $priList = $lang->story->priList; - if(end($priList)) unset($priList[0]); - if(!isset($priList[$pri])) - { - reset($priList); - $pri = key($priList); - } - ?> - - - -
    - - -
    - -
    -
    - - -
    -
    - story->estimateAB;?> - -
    -
    - -
    -
    story->spec;?> - fetch('user', 'ajaxPrintTemplates', 'type=story&link=spec');?> - story->specTemplate . "\n" . $lang->noticePasteImg) . "'");?> -
    story->verify;?>
    story->status;?>
    story->legendAttatch;?>fetch('file', 'buildform');?>
    story->mailto;?> -
    - chooseUsersToMail}' multiple");?> - fetch('my', 'buildContactLists');?> -
    -
    story->keywords;?> - -
    - - goback, '', 'class="btn btn-wide"') : html::backButton('', $source == 'bug' ? 'data-app=qa' : '');?> -
    -
    -
    -
    - -story->module);?> - - - diff --git a/module/story:q/view/edit.html.php b/module/story:q/view/edit.html.php deleted file mode 100644 index 9b0f5bc552..0000000000 --- a/module/story:q/view/edit.html.php +++ /dev/null @@ -1,292 +0,0 @@ - - * @package story - * @version $Id: edit.html.php 4645 2013-04-11 08:32:09Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -?> - - -product);?> -children));?> -story->moveChildrenTips);?> -app->rawModule);?> - -story->module);?> - -story->notice->reviewerNotEmpty);?> -story->feedbackSource); ?> -
    -
    -
    -

    - id;?> - createLink('story', 'view', "storyID=$story->id"), $story->title, '', 'class="story-title"');?> - arrow . ' ' . $lang->story->edit;?> -

    -
    -
    -
    -
    -
    -
    -
    - - - -
    - title, 'class="form-control disabled story-title" disabled="disabled"');?> -
    -
    -
    -
    story->legendSpec;?>
    -
    spec;?>
    -
    -
    -
    story->verify;?>
    -
    verify;?>
    -
    - printExtendFields($story, 'div', 'position=left');?> -
    -
    story->comment;?>
    -
    - -
    -
    -
    - lastEditedDate); - echo html::submitButton($lang->save); - if(!isonlybody()) echo html::backButton(); - ?> -
    -
    - -
    -
    -
    -
    -
    -
    story->legendBasicInfo;?>
    - - parent <= 0):?> - - - - - type != 'normal'):?> - - - - - - - - - - parent >= 0 and $story->type == 'story'):?> - - - - - - - - - - - - - - - - - - - - - - status != 'draft' and $story->type == 'story'):?> - - - - - - - - - - - - - - - - - - '> - - - - '> - - - - - - - - - - - -
    story->product;?> -
    - product, "onchange='loadProduct(this.value);' class='form-control chosen control-product'");?> - - type != 'normal') echo html::select('branch', $branchTagOption, $story->branch, "onchange='loadBranch();' class='form-control chosen control-branch'");?> -
    -
    product->branch = sprintf($lang->product->branch, $lang->product->branchName['branch']);?> -
    type != 'normal') echo html::select('branch', $branchTagOption, $story->branch, "onchange='loadBranch();' class='form-control chosen control-branch'");?>
    -
    story->module;?> -
    - module, "class='form-control chosen'"); - if(count($moduleOptionMenu) == 1) - { - echo ""; - echo html::a($this->createLink('tree', 'browse', "rootID=$story->product&view=story¤tModuleID=0&branch=$story->branch", '', true), $lang->tree->manage, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'"); - echo '  '; - echo html::a("javascript:void(0)", $lang->refresh, '', "class='refresh' onclick='loadProductModules($story->product)'"); - echo ''; - } - ?> -
    -
    story->parent;?>parent, "class='form-control chosen'");?>
    story->plan;?> -
    - session->currentProductType != 'normal' and empty($story->branch)) ? true : false;?> - plan, "class='form-control chosen'" . ($multiple ? ' multiple' : '')); - if(count($plans) == 1) - { - echo ""; - echo html::a($this->createLink('productplan', 'create', "productID=$story->product&branch=$story->branch", '', true), $lang->productplan->create, '', "class='text-primary' data-toggle='modal' data-type='iframe' data-width='95%'"); - echo html::a("javascript:void(0)", $lang->refresh, '', "class='refresh' onclick='loadProductPlans($story->product)'"); - echo ''; - } - ?> -
    -
    story->source;?>story->sourceList, $story->source, "class='form-control chosen'");?>
    story->sourceNote;?>sourceNote, "class='form-control'");?> -
    story->status;?> - processStatus('story', $story);?> - status);?> -
    story->stage;?> - stages and $branchTagOption) - { - foreach($story->stages as $branch => $stage) - { - if(isset($branchTagOption[$branch])) echo '

    ' . $branchTagOption[$branch] . html::select("stages[$branch]", $lang->story->stageList, $stage, "class='form-control chosen'") . '

    '; - } - } - else - { - echo html::select('stage', $lang->story->stageList, $story->stage, "class='form-control chosen'"); - } - ?> -
    story->category;?>story->categoryList, $story->category, "class='form-control chosen'");?>
    story->pri;?>story->priList, $story->pri, "class='form-control chosen'");?>
    story->estimate;?>parent >= 0 ? html::input('estimate', $story->estimate, "class='form-control'") : $story->estimate;?>
    story->feedbackBy;?>feedbackBy, "class='form-control'");?>
    story->notifyEmail;?>notifyEmail, "class='form-control'");?>
    story->keywords;?>keywords, "class='form-control'");?>
    story->mailto;?> -
    - mailto), "class='form-control picker-select' multiple");?> - fetch('my', 'buildContactLists');?> -
    -
    -
    -
    -
    story->legendLifeTime;?>
    - - - - - - - - - - - - - - - - status == 'closed'):?> - - - - - - - - - -
    story->openedBy;?>openedBy);?>
    story->assignedTo;?>assignedTo, 'class="form-control chosen"');?>
    story->reviewers;?>
    story->closedBy;?>closedBy, 'class="form-control chosen"');?>
    story->closedReason;?>story->reasonList, $story->closedReason, "class='form-control' onchange='setStory(this.value)'");?>
    -
    - - printExtendFields($story, 'div', 'position=right');?> - -
    -
    story->legendMisc;?>
    - - status == 'closed'):?> - - - - - - status == 'closed'):?> - - - - - - - - - -
    story->duplicateStory;?>duplicateStory == 0 ? '' : $story->duplicateStory, "class='form-control'");?>
    story->childStories;?> - createLink('story', 'linkStory', "storyID=$story->id&type=childStories", '', true), $lang->story->linkStory, '', "data-toggle='modal' data-type='iframe' data-width='95%'");?> -
    -
      - childStories) - { - $childStories = explode(',', $story->childStories); - foreach($childStories as $childStoryID) - { - if(isset($story->extraStories[$childStoryID])) - { - echo "
    • "; - echo ""; - echo ""; - echo '
    • '; - } - } - } - ?> - -
    -
    -
    -
    -
    -
    -
    -
    -type);?> - - diff --git a/module/story:q/view/export.html.php b/module/story:q/view/export.html.php deleted file mode 100644 index ebe1453b9e..0000000000 --- a/module/story:q/view/export.html.php +++ /dev/null @@ -1,13 +0,0 @@ - - * @package story - * @version $Id$ - * @link http://www.zentao.net - */ -?> - diff --git a/module/story:q/view/header.html.php b/module/story:q/view/header.html.php deleted file mode 100644 index a5c56e71e5..0000000000 --- a/module/story:q/view/header.html.php +++ /dev/null @@ -1,174 +0,0 @@ - - -app->rawMethod);?> - diff --git a/module/story:q/view/linkstory.html.php b/module/story:q/view/linkstory.html.php deleted file mode 100644 index 62d0ce8431..0000000000 --- a/module/story:q/view/linkstory.html.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @package story - * @version $Id: linkstory.html.php 4129 2016-03-09 08:58:13Z chenfei $ - * @link http://www.zentao.net - */ -?> - - - -
    -
    -

    - id;?> - title'>" . $story->title . '') : html::a($this->createLink('story', 'view', "storyID=$story->id"), $story->title);?> - - arrow . $lang->story->linkStory;?> - -

    -
    -
    -
    - - - - - - - - - type == 'requirement'):?> - - - - - - - - - - createLink('story', 'view', "storyID=$story2Link->id");?> - - - - - - type == 'requirement'):?> - - - - - - - - -
    -
    - -
    - idAB;?> -
    story->pri;?>>priAB;?>story->title;?>story->status;?>story->stage;?>openedByAB;?>story->estimateAB;?>
    -
    - - -
    - id));?> -
    pri?>' title='story->priList, $story2Link->pri, $story2Link->pri);?>'>story->priList, $story2Link->pri, $story2Link->pri);?>title, '_blank');?>processStatus('story', $story2Link);?>story->stageList, $story2Link->stage);?>openedBy);?>estimate;?>
    - - -
    -
    - - diff --git a/module/story:q/view/processstorychange.html.php b/module/story:q/view/processstorychange.html.php deleted file mode 100644 index b23c873843..0000000000 --- a/module/story:q/view/processstorychange.html.php +++ /dev/null @@ -1,47 +0,0 @@ - - -
    -
    -
    -

    - id;?> - title . '">' . $story->title . '';?> - arrow . $lang->story->URChanged;?> -

    -
    - - - - - - - - - - - - - - - - - - - - - -
    story->id;?>story->title;?>story->status;?>story->version;?>story->openedBy;?>
    id;?>title;?>story->changed;?>version;?>openedBy);?>
    -
    - story->changeTips;?> -
    -
    - story->changeList['no'], '', "class='btn btn-secondary'");?> - story->changeList['yes'], '', "class='btn btn-primary showinonlybody'");?> -
    -
    -
    - diff --git a/module/story:q/view/report.html.php b/module/story:q/view/report.html.php deleted file mode 100644 index 2b663c0aa0..0000000000 --- a/module/story:q/view/report.html.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @package product - * @version $Id: report.html.php 1594 2011-03-13 07:27:55Z wwccss $ - * @link http://www.zentao.net - */ -?> - -
    -
    -
    -
    -
    -
    story->report->select;?>
    -
    -
    -
    -
    - story->report->charts['storysPerPlan']);?> - story->report->charts, $checkedCharts, '', 'block');?> -
    -
    - - story->report->create, '', 'btn btn-primary');?> -
    -
    -
    -
    -
    -
    -
    -
    - report->typeList['default']);?> - report->typeList as $type => $typeName):?> - " . $typeName, '', "class='btn btn-link " . ($type == $chartType ? 'btn-active-line' : '') . "'")?> - -
    -
    product->unclosed . $lang->story->common, $lang->report->notice->help);?>
    - $chartOption):?> -
    -
    -
    -

    story->report->charts[$chartType];?>

    -
    -
    -
    -
    -
    - - - - - - - - - $data):?> - - - - - - - -
    story->report->$chartType->item;?>story->report->value;?>report->percent;?>
    name;?>value;?>percent * 100) . '%';?>
    -
    -
    -
    - -
    -
    -
    - - - - - - diff --git a/module/story:q/view/review.html.php b/module/story:q/view/review.html.php deleted file mode 100644 index 73cfdc29d5..0000000000 --- a/module/story:q/view/review.html.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @package story - * @version $Id: review.html.php 4129 2013-01-18 01:58:14Z wwccss $ - * @link http://www.zentao.net - */ -?> - - -
    -
    -
    -

    - id;?> - createLink('story', 'view', "storyID=$story->id"), $story->title);?> - arrow . $lang->story->review;?> -

    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - status == 'changed' or ($story->status == 'draft' and $story->version > 1)):?> - - - - - - - - - - printExtendFields($story, 'table');?> - - - - - - - - - - - -
    story->reviewedDate;?>
    story->reviewResult;?>story->resultList, '', 'class="form-control chosen" onchange="switchShow(this.value)"');?>
    story->assignedTo;?>
    story->rejectedReason;?>story->reasonList, '', 'class=form-control onchange="setStory(this.value)"');?>
    story->pri;?>story->priList, $story->pri,"class='form-control'");?>
    story->estimate;?>estimate, "class='form-control'");?>
    story->duplicateStory;?>
    story->childStories;?>
    story->preVersion;?>version - 1, 1), range($story->version - 1, 1)), $story->version - 1);?>
    story->status;?>status);?>
    story->comment;?>
    story->checkAffection;?>
    - - -
    -
    -
    -
    -
    -
    -id);?> -type);?> -app->rawModule);?> - - diff --git a/module/story:q/view/sendmail.html.php b/module/story:q/view/sendmail.html.php deleted file mode 100644 index 8189cbd958..0000000000 --- a/module/story:q/view/sendmail.html.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @package bug - * @version $Id: sendmail.html.php 4129 2013-01-18 01:58:14Z wwccss $ - * @link http://www.zentao.net - */ -?> -id . ' ' . $object->title;?> -app->getModuleRoot() . 'common/view/mail.header.html.php';?> - - - - - - -
    - color) ? '#333' : $object->color;?> - id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?> -
    - - - - -
    - lang->story->legendSpec;?> -
    spec;?>
    -
    - - -app->getModuleRoot() . 'common/view/mail.footer.html.php';?> diff --git a/module/story:q/view/tasks.html.php b/module/story:q/view/tasks.html.php deleted file mode 100644 index a1f9e2e247..0000000000 --- a/module/story:q/view/tasks.html.php +++ /dev/null @@ -1,77 +0,0 @@ - -
    -
    -

    - icons['report']);?> - story->tasks;?> -

    -
    -
    - - - - - - - - - - - - - - - - $task):?> - - - - - - - - - - - - children)):?> - - children as $key => $child):?> - - children)) ? ' table-child-bottom' : '';?> - - - - - - - - - - - - - - - - - - - - - -
    idAB;?> task->name;?>story->pri;?>>priAB;?> statusAB;?> task->assignedToAB;?>task->estimateAB;?>task->consumedAB;?> task->leftAB;?>task->progress;?>
    id;?>name;?>task->priList, $task->pri, $task->pri)?>'>pri == '0' ? '' : zget($lang->task->priList, $task->pri, $task->pri);?>processStatus('task', $task);?>assignedTo, $task->assignedTo);?>estimate;?>consumed;?>left;?> -
    -
    progress;?>
    -
    -
    id;?>lang->task->children . '">' . $this->lang->task->childrenAB . '' ?>name;?>task->priList, $child->pri, $child->pri)?>'>pri == '0' ? '' : zget($lang->task->priList, $child->pri, $child->pri);?>processStatus('task', $child);?>assignedTo, $child->assignedTo);?>estimate;?>consumed;?>left;?> -
    -
    progress;?>
    -
    -
    -
    -
    - diff --git a/module/story:q/view/track.html.php b/module/story:q/view/track.html.php deleted file mode 100644 index 54c5a4aea8..0000000000 --- a/module/story:q/view/track.html.php +++ /dev/null @@ -1,120 +0,0 @@ - - -app->rawModule);?> - -
    -
    - -
    -

    - noData;?> -

    -
    - - app->rawModule == 'projectstory' ? "style='overflow: unset; text-align: left'" : '';?> - app->rawModule == 'projectstory' ? 'project' : 'product';?> - app->rawModule == 'projectstory' ? 'projectstory' : 'story';?> -
    - - - - URAndSR):?> - - - - - edition == 'max'):?> - - - - edition == 'max'):?> - - - - - - - $requirement):?> - track;?> - - - URAndSR):?> - - - - - $story):?> - ';?> - - - edition == 'max'):?> - - - - edition == 'max'):?> - - - - ';?> - - - - - - -
    > - app->rawModule == 'projectstory'): ?> - - - story->requirement;?> - - story->story;?>story->tasks;?>story->design;?>story->case;?>story->repoCommit;?>story->bug;?>
    story->statusList, $requirement->status);?> - - story->noRequirement : html::a($this->createLink($module, 'view', "storyID=$requirement->id"), $requirement->title, '', "title=$requirement->title data-app='$tab'");?> - - parent) and $story->parent > 0):?>lang->story->childrenAB;?> - createLink($module, 'view', "storyID=$storyID"), $story->title, '',"title='$story->title' data-app='$tab'");?> - - tasks as $taskID => $task):?> - createLink('task', 'view', "taskID=$taskID"), $task->name, '', "title='$task->name'") . '
    ';?> - -
    - designs as $designID => $design):?> - createLink('design', 'view', "designID=$designID"), $design->name, '', "title='$design->name'") . '
    ';?> - -
    - cases as $caseID => $case):?> - createLink('testcase', 'view', "caseID=$caseID"), $case->title, '', "title='$case->title'") . '
    ';?> - -
    - revisions as $revision => $repoComment):?> - createLink('design', 'revision', "repoID=$revision"), '#'. $revision . '-' . $repoComment, '', "data-app='devops'") . '
    '; - ?> - -
    - bugs as $bugID => $bug):?> - createLink('bug', 'view', "bugID=$bugID"), $bug->title, '', "title='$bug->title'") . '
    ';?> - -
    - -
    - -
    -
    - diff --git a/module/story:q/view/view.html.php b/module/story:q/view/view.html.php deleted file mode 100644 index 14ff9d6f1c..0000000000 --- a/module/story:q/view/view.html.php +++ /dev/null @@ -1,587 +0,0 @@ - - * @package story - * @version $Id: view.html.php 4952 2013-07-02 01:14:58Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -?> - - -session->storyList ? $app->session->storyList : $this->createLink('product', 'browse', "productID=$story->product");?> - - - -app->getViewType() == 'xhtml'):?> -
    - - -
    -
    -
    -
    -
    story->legendSpec;?>
    -
    spec;?>
    -
    -
    -
    story->legendVerify;?>
    -
    verify;?>
    -
    - - fetch('file', 'printFiles', array('files' => $story->files, 'fieldset' => 'true', 'object' => $story));?> - createLink('action', 'comment', "objectType=story&objectID=$story->id"); - ?> - children)):?> -
    -
    lang->story->children;?>
    -
    - - - - - - - - - - - - - - children as $child):?> - - - - - - - - - - - -
    story->id;?>story->pri;?>>priAB;?> story->title;?>story->assignedTo;?> story->estimate;?> story->status;?>actions;?>
    id;?> - pri . "'>"; - echo $child->pri == '0' ? '' : zget($this->lang->story->priList, $child->pri, $child->pri); - echo ""; - ?> - id", '', true); ?>">title;?>assignedTo);?>estimate . $config->hourUnit;?>processStatus('story', $child);?> - id", $child, 'list', 'alter'); - common::printIcon('story', 'review', "storyID=$child->id", $child, 'list', 'search', '', 'iframe showinonlybody', true); - common::printIcon('story', 'assignTo', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true); - common::printIcon('story', 'close', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true); - common::printIcon('story', 'activate', "storyID=$child->id", $child, 'list', '', '', 'iframe showinonlybody', true); - common::printIcon('story', 'edit', "storyID=$child->id", $child, 'list'); - common::printIcon('story', 'createCase', "productID=$child->product&branch=$child->branch&module=0&from=¶m=0&story={$child->id}", $child, 'list', 'sitemap'); - ?> -
    -
    -
    - -
    - printExtendFields($story, 'div', "position=left&inForm=0&inCell=1");?> - app->getViewType() != 'xhtml'):?> -
    - -
    -
    - -
    ";?> - deleted) echo $this->story->buildOperateMenu($story, 'view');?> -
    -
    -
    -
    -
    -
    - -
    -
    - - - - - - - type != 'normal'):?> - - - - - - - - branch and isset($branches[$storyModule->branch])) - { - $moduleTitle .= $branches[$storyModule->branch] . '/'; - echo $branches[$storyModule->branch] . $lang->arrow; - } - - foreach($modulePath as $key => $module) - { - $moduleTitle .= $module->name; - if(!common::printLink('product', 'browse', "productID=$story->product&branch=$story->branch&browseType=byModule¶m=$module->id", $module->name, '', "data-app='product'")) echo $module->name; - if(isset($modulePath[$key + 1])) - { - $moduleTitle .= '/'; - echo $lang->arrow; - } - } - } - $printModule = ob_get_contents(); - ob_end_clean(); - ?> - - - type != 'requirement' and $story->parent != -1):?> - - - - - - - - - - - - - - - - - - type != 'requirement'):?> - - - - - - - - - - - - - - - - - - source, $config->story->feedbackSource)):?> - - - - - - - - - - - - - - - - - - -
    story->product;?>createLink('product', 'view', "productID=$story->product"), $product->name);?>
    product->branch;?>product&branch=$story->branch", $branches[$story->branch]);?>
    story->module;?>
    story->plan;?> - planTitle)) - { - foreach($story->planTitle as $planID => $planTitle) - { - if(!common::printLink('productplan', 'view', "planID=$planID", $planTitle, '', "data-app='product'")) echo $planTitle; - echo '
    '; - } - } - ?> -
    story->source;?>story->sourceList[$story->source];?>
    story->sourceNote;?>sourceNote;?>
    story->status;?> processStatus('story', $story);?>
    story->stage;?> - stages and $branches) - { - foreach($story->stages as $branch => $stage) if(isset($branches[$branch])) echo $branches[$branch] . ' : ' . $lang->story->stageList[$stage] . '
    '; - } - else - { - echo $lang->story->stageList[$story->stage]; - } - ?> -
    story->category;?>story->categoryList, $story->category, $story->category)?>
    story->pri;?>pri;?>' title='story->priList, $story->pri)?>'>story->priList, $story->pri)?>
    story->estimate;?>estimate . $config->hourUnit;?>
    story->feedbackBy;?>feedbackBy;?>
    story->notifyEmail;?>notifyEmail;?>
    story->keywords;?>keywords;?>
    story->legendMailto;?>mailto); foreach($mailto as $account) {if(empty($account)) continue; echo "" . zget($users, trim($account)) . '  '; }?>
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    story->openedBy;?>openedBy) . $lang->at . $story->openedDate;?>
    story->assignedTo;?>assignedTo) echo zget($users, $story->assignedTo) . $lang->at . $story->assignedDate;?>
    story->reviewers;?> - $result) - { - echo !empty($result) ? ' ' . zget($users, $reviewer) . '' : ' ' . zget($users, $reviewer) . ''; - } - } - ?> -
    story->reviewedDate;?>reviewedBy) echo $story->reviewedDate;?>
    story->closedBy;?>closedBy) echo zget($users, $story->closedBy) . $lang->at . $story->closedDate;?>
    story->closedReason;?> - closedReason) echo $lang->story->reasonList[$story->closedReason]; - if(isset($story->extraStories[$story->duplicateStory])) - { - echo html::a(inlink('view', "storyID=$story->duplicateStory"), '#' . $story->duplicateStory . ' ' . $story->extraStories[$story->duplicateStory]); - } - ?> -
    story->lastEditedBy;?>lastEditedBy) echo zget($users, $story->lastEditedBy) . $lang->at . $story->lastEditedDate;?>
    -
    -
    -
    -
    -
    -
    - -
    - config->URAndSR):?> -
    -
      - id] = $item->title; - foreach($relation as $id => $title) - { - echo "
    • " . html::a($this->createLink('story', 'view', "id=$id", '', true), "#$id $title", '', "class='iframe' data-width='80%'"); - echo html::a($this->createLink('story', 'linkStory', "storyID=$story->id&type=remove&linkedID=$id"), '', 'hiddenwin', "class='deleter hide removeButton'"); - } - ?> - type == 'story') ? $lang->story->requirement : $lang->story->story;?> -
    • createLink('story', 'linkStory', "storyID=$story->id", '', true), $lang->story->link . $linkLang, '', "class='btn btn-info iframe' data-width='95%' id='linkButton'");?> - story->unlink . $linkLang, '', "class='btn btn-info' id='unlinkStory'");?>
    • -
    -
    - - - type == 'story'):?> -
    -
      - tasks as $executionTasks) - { - foreach($executionTasks as $task) - { - if(!isset($executions[$task->execution])) continue; - $executionName = $executions[$task->execution]; - $taskInfo = $task->id . ' ' . $this->lang->task->statusList[$task->status] . ' ' . $task->name; - $class = isonlybody() ? 'showinonlybody' : 'iframe'; - $execution = isset($story->executions[$task->execution]) ? $story->executions[$task->execution] : ''; - $execName = (isset($execution->type) and $execution->type == 'kanban' and isonlybody()) ? $executionName : html::a($this->createLink('execution', 'view', "executionID=$task->execution"), $executionName, '', "class='text-muted'"); - echo "
    • " . $execName . html::a($this->createLink('task', 'view', "taskID=$task->id", '', true), $taskInfo, '', "class=$class data-width='80%'") . '
    • '; - } - } - foreach($story->executions as $executionID => $execution) - { - if(isset($story->tasks[$executionID])) continue; - $execName = ($execution->type == 'kanban' and isonlybody()) ? $execution->name : html::a($this->createLink('execution', 'view', "executionID=$executionID"), $execution->name, '', "class='text-muted'"); - echo "
    • " . $execName . '
    • '; - } - ?> -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - -
    story->legendFromBug;?> -
      - id $fromBug->title'>" . html::a($this->createLink('bug', 'view', "bugID=$fromBug->id", '', true), "#$fromBug->id $fromBug->title", '', "class='iframe' data-width='80%'") . '';?> -
    -
    story->legendBugs;?> -
      - id $bug->title'>" . html::a($this->createLink('bug', 'view', "bugID=$bug->id", '', true), "[B] #$bug->id $bug->title", '', "class='iframe' data-width='80%'") . ''; - } - ?> -
    -
    story->legendCases;?> -
      - id $case->title'>" . html::a($this->createLink('testcase', 'view', "caseID=$case->id", '', true), "[C] #$case->id $case->title", '', $misc) . ''; - } - ?> -
    -
    story->linkMR;?> -
      - $linkMRTitle) - { - if($mrPriv) - { - echo "
    • " . html::a($this->createLink('mr', 'view', "MRID=$MRID"), "#$MRID $linkMRTitle") . '
    • '; - } - else - { - echo "
    • " . "#$MRID $linkMRTitle" . '
    • '; - } - } - ?> -
    -
    -
    -
    -
    -
    - printExtendFields($story, 'div', "position=right&inForm=0&inCell=1");?> -
    -
    -app->getViewType() == 'xhtml'):?> -
    - - - - -
    - -
    -story->create); -js::set('productID', $story->product); -js::set('branch', $story->branch); -js::set('moduleID', $story->module); -js::set('storyType', $story->type); -js::set('unlink', $lang->story->unlink); -js::set('cancel', $lang->cancel); -js::set('rawModule', $this->app->rawModule); -?> - - - diff --git a/module/story:q/view/zerocase.html.php b/module/story:q/view/zerocase.html.php deleted file mode 100644 index e9302c0db0..0000000000 --- a/module/story:q/view/zerocase.html.php +++ /dev/null @@ -1,155 +0,0 @@ - - * @package product - * @version $Id: browse.html.php 4909 2013-06-26 07:23:50Z chencongzhi520@gmail.com $ - * @link http://www.zentao.net - */ -?> - - - -
    - -
    -

    story->noStory;?>

    -
    - -
    - - - - app->rawModule = 'story'; - $vars = "productID=$productID&branchID=$branchID&orderBy=%s"; - ?> - - - - - - - - - - - - - - - $story):?> - app->tab == 'project') $param = $this->session->project; - if($this->app->tab == 'execution') $param = $this->session->execution; - $viewLink = $this->createLink('story', 'view', "storyID=$story->id&version=0¶m=$param"); - $canView = common::hasPriv('story', 'view'); - ?> - - - - - - - - - - - - - - - -
    -
    - -
    - idAB);?> -
    story->pri;?>>priAB);?> story->title);?> story->planAB);?> story->source);?> openedByAB);?> assignedToAB);?>story->estimateAB);?> statusAB);?> story->stageAB);?>actions;?>
    -
    - - -
    - id);?> -
    pri;?>' title='story->priList, $story->pri);?>'>story->priList, $story->pri)?>title, '', "data-app='product'");?>planTitle;?>story->sourceList[$story->source];?>openedBy);?>assignedTo);?>estimate;?>processStatus('story', $story);?>story->stageList, $story->stage);?> - id}"; - $this->app->tab = 'product'; - common::printIcon('story', 'change', $vars, $story, 'list', 'fork'); - common::printIcon('story', 'review', $vars, $story, 'list', 'glasses'); - common::printIcon('story', 'close', $vars, $story, 'list', 'off', '', 'iframe', 'yes'); - common::printIcon('story', 'edit', $vars, $story, 'list'); - $this->app->tab = 'qa'; - common::printIcon('story', 'createCase', "productID=$story->product&branch=0&module=0&from=¶m=0&$vars", $story, 'list', 'sitemap'); - ?> -
    - -
    - - -
    - From fce90ae22bd0d4054cf010537e0e004de15ed5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E8=BE=B0=E8=BD=A9?= Date: Fri, 8 Jul 2022 11:30:37 +0800 Subject: [PATCH 3/3] *Edit testreport lang. --- module/testreport/lang/en.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/testreport/lang/en.php b/module/testreport/lang/en.php index 7919e6550f..fd8d13c668 100644 --- a/module/testreport/lang/en.php +++ b/module/testreport/lang/en.php @@ -75,7 +75,7 @@ $lang->testreport->buildSummary = 'Tested %s builds.'; $lang->testreport->confirmDelete = 'Do you want to delete this report?'; $lang->testreport->moreNotice = 'More features can be extended with reference to the ZenTao extension manual, or you can contact us at renee@easysoft.ltd for customization.'; $lang->testreport->exportNotice = "Exported By ZenTao"; -$lang->testreport->noReport = "No report has been generated. Please check it later."; +$lang->testreport->noReport = "No report has been generated.Please select one request to generate one test report."; $lang->testreport->foundBugTip = "Bugs found in this build period and the affected build is in this test period."; $lang->testreport->legacyBugTip = "Active bugs, or bugs that are not resolved in the test period."; $lang->testreport->activatedBugTip = "Reactived bugs during the testtask.";