| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169 |
- <?php
- /**
- * The zen file of story module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
- * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Wang Yidong<yidong@easycorp.ltd>
- * @package story
- * @link https://www.zentao.net
- */
- class storyZen extends story
- {
- /**
- * 设置创建需求页面的导航。
- * Set menu for create story.
- *
- * @param int $productID
- * @param int $objectID
- * @param string $extra
- * @access protected
- * @return int[]
- */
- protected function setMenuForCreate($productID, $objectID, $extra = '')
- {
- /* Get product id according to the project id when lite vision todo transfer story */
- if($this->config->vision == 'lite' && $productID == 0)
- {
- $products = $this->product->getProductPairsByProject($objectID);
- if(!empty($products)) $productID = key($products);
- }
- /* Get objectID by tab. */
- if(empty($objectID))
- {
- if($this->app->tab == 'project') $objectID = (int)$this->session->project;
- if($this->app->tab == 'execution') $objectID = (int)$this->session->execution;
- }
- /* Set menu by tab. */
- if($this->app->tab == 'product')
- {
- $extra = str_replace(array(',', ' '), array('&', ''), $extra);
- parse_str($extra, $output);
- if(!empty($output['from']) && $output['from'] == 'global')
- {
- $product = $this->product->getById($productID);
- if(!$product || $product->deleted || $product->shadow)
- {
- $newProduct = $this->product->getOrderedProducts('noclosed');
- if(!empty($newProduct)) $productID = (int) key($newProduct);
- }
- }
- $this->product->setMenu($productID);
- }
- if($this->app->tab == 'execution')
- {
- $this->execution->setMenu($objectID);
- $this->view->executionID = $objectID;
- }
- if($this->app->tab == 'project')
- {
- $projectID = $objectID;
- if(!$this->session->multiple)
- {
- $projectID = $this->session->project;
- $objectID = $this->execution->getNoMultipleID($projectID);
- }
- $projects = $this->project->getPairsByProgram();
- $projectID = $this->project->checkAccess($projectID, $projects);
- $this->view->projectID = $projectID;
- $this->project->setMenu($projectID);
- }
- return array($productID, $objectID);
- }
- /**
- * 为批量创建需求页面设置导航。
- * Set menu for batch create.
- *
- * @param int $productID
- * @param string $branch
- * @param int $executionID
- * @param string $extra
- * @param string $storyType
- * @access protected
- * @return void
- */
- protected function setMenuForBatchCreate($productID, $branch = '', $executionID = 0, $extra = '', $storyType = 'story')
- {
- $this->view->hiddenProduct = false;
- $this->view->hiddenPlan = false;
- /* Set menu. */
- if($this->app->tab == 'project' and $this->config->vision == 'lite')
- {
- $this->project->setMenu($this->session->project);
- $this->view->projectID = $this->session->project;
- return;
- }
- if(empty($executionID))
- {
- if($this->app->tab == 'project')
- {
- $this->project->setMenu($this->session->project);
- $this->view->projectID = $this->session->project;
- }
- else
- {
- $this->product->setMenu($productID, $branch);
- }
- return;
- }
- $execution = $this->dao->findById((int)$executionID)->from(TABLE_EXECUTION)->fetch();
- $this->view->execution = $execution;
- if($execution->type == 'project')
- {
- $this->project->setMenu($executionID);
- $this->lang->navGroup->story = 'project';
- $model = $execution->model == 'waterfallplus' ? 'waterfall' : $execution->model;
- if($execution->model == 'agileplus') $model = 'scrum';
- $this->lang->product->menu = $this->lang->{$model}->menu;
- $this->view->projectID = $executionID;
- }
- else
- {
- $this->execution->setMenu($executionID);
- $this->lang->navGroup->story = 'execution';
- if($execution->type == 'kanban')
- {
- $this->loadModel('kanban');
- $output = $this->story->parseExtra($extra);
- $regionPairs = $this->kanban->getRegionPairs($executionID, 0, 'execution');
- $regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
- $lanePairs = $this->kanban->getLanePairsByRegion((int)$regionID, $storyType);
- $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs);
- $this->view->regionID = $regionID;
- $this->view->laneID = $laneID;
- $this->view->regionPairs = $regionPairs;
- $this->view->lanePairs = $lanePairs;
- }
- if($this->app->tab == 'execution') $this->view->executionID = $executionID;
- if($this->app->tab == 'project') $this->view->projectID = $executionID;
- }
- if($this->app->tab != 'project' && $this->app->tab != 'execution') return;
- /* Hidden some fields of projects without products. */
- $project = $this->dao->findById($executionID)->from(TABLE_PROJECT)->fetch();
- if($project->project) $project = $this->dao->findById((int)$project->project)->from(TABLE_PROJECT)->fetch();
- if($project->hasProduct) return;
- $this->view->hiddenProduct = true;
- if($project->model !== 'scrum') $this->view->hiddenPlan = true;
- if(!$project->multiple) $this->view->hiddenPlan = true;
- }
- /**
- * 为批量编辑页面设置导航。
- * Set menu for batch edit page.
- *
- * @param int $productID
- * @param int $executionID
- * @param string $storyType story|requirement
- * @param string $from work|contribute
- * @access protected
- * @return void
- */
- protected function setMenuForBatchEdit($productID, $executionID = 0, $storyType = 'story', $from = '')
- {
- $this->view->hiddenPlan = false;
- if($this->app->tab == 'product')
- {
- $this->product->setMenu($productID);
- return;
- }
- if($this->app->tab == 'execution')
- {
- $this->execution->setMenu($executionID);
- $this->view->executionID = $this->session->execution;
- return;
- }
- if($this->app->tab == 'qa')
- {
- $this->loadModel('qa')->setMenu($productID);
- return;
- }
- if($this->app->tab == 'my')
- {
- $this->loadModel('my');
- if($from == 'work' || $from == 'contribute')
- {
- $this->lang->my->menu->{$from}['subModule'] = $storyType;
- $this->lang->my->menu->{$from}['subMenu']->{$storyType}['subModule'] = 'story';
- }
- return;
- }
- if($this->app->tab == 'project')
- {
- $project = $this->dao->findByID($executionID)->from(TABLE_PROJECT)->fetch();
- if($project->type == 'project')
- {
- if(!($project->model == 'scrum' and !$project->hasProduct and $project->multiple)) $this->view->hiddenPlan = true;
- $this->project->setMenu($executionID);
- $this->view->projectID = $this->session->project;
- }
- else
- {
- if(!$project->hasProduct and !$project->multiple) $this->view->hiddenPlan = true;
- $this->execution->setMenu($executionID);
- $this->view->projectID = $this->session->execution;
- }
- }
- }
- /**
- * 设置批量关闭需求页面的导航。
- * Set menu for batch close.
- *
- * @param int $productID
- * @param int $executionID
- * @param string $storyType story|requirement
- * @param string $from work|contribute
- * @access protected
- * @return void
- */
- protected function setMenuForBatchClose($productID, $executionID = 0, $storyType = 'story', $from = '')
- {
- /* The stories of a product. */
- if($this->app->tab == 'product' && $productID)
- {
- $this->product->setMenu($productID);
- $product = $this->product->getByID($productID);
- $this->view->title = $product->name . $this->lang->hyphen . $this->lang->story->batchClose;
- }
- /* The stories of a execution. */
- elseif($this->app->tab == 'execution' && $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->title = $execution->name . $this->lang->hyphen . $this->lang->story->batchClose;
- $this->view->executionID = $executionID;
- }
- elseif($this->app->tab == 'project')
- {
- $this->project->setMenu(!empty($this->session->project) ? $this->session->project : $executionID);
- $this->view->title = $this->lang->story->batchClose;
- $this->view->projectID = $this->session->project;
- }
- else
- {
- if($from == 'work' || $from == 'contribute')
- {
- $this->lang->my->menu->{$from}['subModule'] = $storyType;
- $this->lang->my->menu->{$from}['subMenu']->{$storyType}['subModule'] = 'story';
- }
- $this->lang->story->menu = $this->lang->my->menu;
- $this->lang->story->menuOrder = $this->lang->my->menuOrder;
- $this->view->title = $this->lang->story->batchClose;
- }
- }
- /**
- * 如果是看板执行,设置界面中要用到关于看板的视图变量。
- * Set view vars for kanban.
- *
- * @param int $objectID
- * @param array $kanbanSetting
- * @param string $storyType
- * @access protected
- * @return void
- */
- protected function setViewVarsForKanban($objectID, $kanbanSetting, $storyType = 'story')
- {
- if(empty($objectID)) return;
- $execution = $this->dao->findById($objectID)->from(TABLE_EXECUTION)->fetch();
- if($execution->type != 'kanban') return ;
- /* 如果是看板执行,设置看板的view变量。 */
- $regionPairs = $this->loadModel('kanban')->getRegionPairs($execution->id, 0, 'execution');
- $regionID = !empty($kanbanSetting['regionID']) ? $kanbanSetting['regionID'] : key($regionPairs);
- $lanePairs = $this->kanban->getLanePairsByRegion((int)$regionID, $storyType);
- $laneID = !empty($kanbanSetting['laneID']) ? $kanbanSetting['laneID'] : key($lanePairs);
- $this->view->executionType = 'kanban';
- $this->config->story->form->create['region']['options'] = $regionPairs;
- $this->config->story->form->create['region']['default'] = $regionID;
- $this->config->story->form->create['region']['title'] = $this->lang->kanbancard->region;
- $this->config->story->form->create['lane']['options'] = $lanePairs;
- $this->config->story->form->create['lane']['default'] = $laneID;
- $this->config->story->form->create['lane']['title'] = $this->lang->kanbancard->lane;
- }
- /**
- * 初始化创建需求的一些字段的数据。
- * Init story for create.
- *
- * @param int $planID
- * @param int $storyID
- * @param int $bugID
- * @param int $todoID
- * @param string $extra feedback扩展使用
- * @access public
- * @return object
- */
- public function initStoryForCreate($planID, $storyID, $bugID, $todoID, $extra = '')
- {
- $initStory = new stdclass();
- $initStory->source = '';
- $initStory->sourceNote = '';
- $initStory->pri = 3;
- $initStory->estimate = '';
- $initStory->title = '';
- $initStory->spec = '';
- $initStory->verify = '';
- $initStory->keywords = '';
- $initStory->mailto = '';
- $initStory->color = '';
- $initStory->plan = $planID;
- if($storyID > 0) $initStory = $this->getInitStoryByStory($storyID, $initStory);
- if($bugID > 0) $initStory = $this->getInitStoryByBug($bugID, $initStory);
- if($todoID > 0) $initStory = $this->getInitStoryByTodo($todoID, $initStory);
- return $initStory;
- }
- /**
- * 根据复制的需求,初始化创建需求的一些字段数据。
- * Get init story by copied story.
- *
- * @param int $storyID
- * @param object $initStory
- * @access protected
- * @return object
- */
- protected function getInitStoryByStory($storyID, $initStory)
- {
- if(empty($storyID)) return $initStory;
- $story = $this->story->getByID($storyID);
- $initStory->plan = $story->plan;
- $initStory->module = $story->module;
- $initStory->source = $story->source;
- $initStory->sourceNote = $story->sourceNote;
- $initStory->color = $story->color;
- $initStory->pri = $story->pri;
- $initStory->estimate = $story->estimate;
- $initStory->title = $story->title;
- $initStory->spec = $story->spec;
- $initStory->grade = $story->grade;
- $initStory->verify = $story->verify;
- $initStory->keywords = $story->keywords;
- $initStory->mailto = $story->mailto;
- $initStory->category = $story->category;
- $initStory->feedbackBy = $story->feedbackBy;
- $initStory->notifyEmail = $story->notifyEmail;
- $initStory->parent = $story->parent;
- $initStory->files = $story->files;
- if($this->config->edition != 'open')
- {
- $extendFields = $this->loadModel('flow')->getExtendFields($story->type, 'create');
- foreach($extendFields as $field) $initStory->{$field->field} = $story->{$field->field};
- }
- foreach($initStory->files as $file)
- {
- $file->name = $file->title;
- $file->url = $this->createLink('file', 'download', "fileID={$file->id}");
- }
- return $initStory;
- }
- /**
- * 根据来源Bug,初始化创建需求的一些字段数据。
- * Get init story by bug.
- *
- * @param int $bugID
- * @param object $initStory
- * @access protected
- * @return object
- */
- protected function getInitStoryByBug($bugID, $initStory)
- {
- if(empty($bugID)) return $initStory;
- $bug = $this->loadModel('bug')->getByID($bugID);
- $initStory->source = 'bug';
- $initStory->title = $bug->title;
- $initStory->keywords = $bug->keywords;
- $initStory->spec = $bug->steps;
- $initStory->pri = !empty($bug->pri) ? $bug->pri : '3';
- $initStory->mailto = $bug->mailto;
- if($bug->mailto and strpos($bug->mailto, $bug->openedBy) === false) $initStory->mailto = $bug->mailto . $bug->openedBy . ',';
- if(!empty($bug->files)) $initStory->files = $bug->files;
- return $initStory;
- }
- /**
- * 根据来源待办,初始化创建产品的一些字段数据。
- * Get init story by todo.
- *
- * @param int $todoID
- * @param object $initStory
- * @access protected
- * @return object
- */
- protected function getInitStoryByTodo($todoID, $initStory)
- {
- if(empty($todoID)) return $initStory;
- $todo = $this->loadModel('todo')->getByID($todoID);
- $initStory->source = 'todo';
- $initStory->title = $todo->name;
- $initStory->spec = $todo->desc;
- $initStory->pri = $todo->pri;
- return $initStory;
- }
- /**
- * 获取产品和分支列表。
- * Get products and branches for create.
- *
- * @param int $productID
- * @param int $objectID
- * @access protected
- * @return array
- */
- protected function getProductsAndBranchesForCreate($productID, $objectID)
- {
- $products = array();
- $branches = array();
- if($objectID != 0)
- {
- $onlyNoClosed = empty($this->config->CRProduct) ? 'noclosed' : '';
- $products = $this->product->getProductPairsByProject($objectID, $onlyNoClosed);
- $productID = (!empty($productID) && isset($products[$productID])) ? $productID : key($products);
- $product = $this->product->getById((int)$productID);
- if($product && $product->type != 'normal')
- {
- $productBranches = $this->loadModel('execution')->getBranchByProduct(array($productID), $objectID, 'noclosed|withMain');
- if(isset($productBranches[$productID])) $branches = $productBranches[$productID];
- }
- }
- else
- {
- $productList = $this->product->getOrderedProducts('noclosed');
- foreach($productList as $product) $products[$product->id] = $product->name;
- $product = $this->product->getById($productID);
- if(!isset($products[$product->id])) $products[$product->id] = $product->name;
- if($product->type != 'normal') $branches = $this->loadModel('branch')->getPairs($productID, 'active');
- }
- $this->product->checkAccess((int)$productID, $products);
- return array($products, $branches);
- }
- /**
- * 获取产品列表,并排序,将我负责的产品排前面。
- * Get products for edit.
- *
- * @access protected
- * @return array
- */
- protected function getProductsForEdit()
- {
- $account = $this->app->user->account;
- $myProducts = array();
- $othersProducts = array();
- $products = $this->loadModel('product')->getList();
- foreach($products as $product)
- {
- if($product->status != 'closed' and $product->PO == $account) $myProducts[$product->id] = $product->name;
- if($product->status != 'closed' and $product->PO != $account) $othersProducts[$product->id] = $product->name;
- }
- $products = $myProducts + $othersProducts;
- return $products;
- }
- /**
- * 获取创建需求的表单字段。
- * Get form fields for create
- *
- * @param int $productID
- * @param string $branch
- * @param int $objectID
- * @param object $initStory
- * @param string $storyType
- * @access protected
- * @return array
- */
- protected function getFormFieldsForCreate($productID, $branch, $objectID, $initStory, $storyType = 'story')
- {
- $account = $this->app->user->account;
- $fields = $this->config->story->form->create;
- /* 准备数据。*/
- list($products, $branches) = $this->getProductsAndBranchesForCreate($productID, $objectID);
- if($objectID)
- {
- $branch = is_array($branches) && !empty($branches) ? (string)key($branches) : $branch;
- $productID = (!empty($productID) && isset($products[$productID])) ? $productID : key($products);
- }
- $branch = strpos($branch, ',') !== false ? current(explode(',', $branch)) : $branch;
- $product = $this->product->getByID($productID);
- $users = $this->user->getPairs('pdfirst|noclosed|nodeleted');
- $stories = $this->story->getParentStoryPairs($productID, '', $storyType);
- $grades = $this->story->getGradePairs($storyType);
- $plans = $this->loadModel('productplan')->getPairs($productID, $branch == 0 ? '' : $branch, 'unexpired|noclosed', true);
- $plans = array_map(function($planName){return str_replace(FUTURE_TIME, $this->lang->story->undetermined, $planName);}, $plans);
- $forceReview = $this->story->checkForceReview($storyType);
- $needReview = ($account == $product->PO || $objectID > 0 || $this->config->{$storyType}->needReview == 0 || !$forceReview);
- $reviewers = $this->story->getProductReviewers($productID);
- $requiredFields = $this->config->{$storyType}->create->requiredFields;
- /* 追加字段的name、title属性,展开user数据。 */
- foreach($fields as $field => $attr)
- {
- if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users;
- if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field;
- if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->story, $field);
- if(strpos($requiredFields, $field) !== false) $fields[$field]['required'] = true;
- }
- /* 设置下拉菜单内容。 */
- $fields['product']['options'] = $products;
- $fields['branch']['options'] = $branches;
- $fields['branches']['options'] = $branches;
- $fields['plan']['options'] = $plans;
- $fields['plans']['options'] = $plans;
- $fields['grade']['options'] = $grades;
- $fields['grade']['default'] = current($grades);
- $fields['reviewer']['options'] = $reviewers;
- $fields['parent']['options'] = array_filter($stories);
- /* 设置默认值。 */
- foreach($initStory as $field => $defaultValue)
- {
- if(isset($fields[$field])) $fields[$field]['default'] = $defaultValue;
- }
- if(empty($fields['product']['default'])) $fields['product']['default'] = $productID;
- if(empty($fields['branch']['default'])) $fields['branch']['default'] = (int)$branch;
- if(empty($fields['branches']['default'])) $fields['branches']['default'] = (int)$branch;
- if(empty($fields['plans']['default'])) $fields['plans']['default'] = zget($initStory, 'plan', 0);
- if(empty($needReview)) $fields['reviewer']['default'] = $product->PO;
- if($forceReview) $fields['reviewer']['required'] = true;
- /* 删除不需要的字段。 */
- if(empty($branches)) unset($fields['branch'], $fields['branches'], $fields['modules'], $fields['plans']);
- if($this->config->vision == 'or') unset($fields['plan']);
- $this->view->productID = $productID;
- $this->view->product = $product;
- $this->view->branch = $branch;
- $this->view->branches = $branches;
- $this->view->objectID = $objectID;
- $this->view->forceReview = $forceReview;
- $this->view->needReview = $needReview;
- $this->view->gradeRule = $this->config->{$storyType}->gradeRule;
- return $fields;
- }
- /**
- * 为编辑页面获取字段配置。
- * Get form fields for edit.
- *
- * @param int $storyID
- * @access protected
- * @return array
- */
- protected function getFormFieldsForEdit($storyID)
- {
- $fields = $this->config->story->form->edit;
- /* 准备数据。*/
- $story = $this->view->story;
- $parent = $this->story->fetchByID($story->parent);
- $grades = $this->story->getGradeOptions($parent, $story->type, (array)$story->grade);
- $product = $this->view->product;
- $users = $this->loadModel('user')->getPairs('pofirst|nodeleted|noclosed', "$story->assignedTo,$story->openedBy,$story->closedBy");
- $stories = $this->story->getParentStoryPairs($story->product, $story->parent, $story->type, $storyID);
- $plans = $this->loadModel('productplan')->getPairs($story->product, $story->branch == 0 ? 'all' : $story->branch, '', true);
- $reviewerList = $this->story->getReviewerPairs($story->id, $story->version);
- $reviewers = $product->reviewer;
- if(!$reviewers and $product->acl != 'open') $reviewers = $this->user->getProductViewListUsers($product);
- $reviewers = $this->user->getPairs('noclosed|nodeleted', array_keys($reviewerList), 0, $reviewers);
- $products = $this->getProductsForEdit();
- 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);
- /* 如果是无产品项目,则所属产品显示需求本身的所属产品。*/
- /* If there is no product item, the product shows the product of story itself.*/
- $object = $this->loadModel('execution')->getByID($objectID);
- if(!$object->hasProduct) $products = array($product->id => $product->name);
- $this->view->objectID = $objectID;
- }
- $branches = $this->loadModel('branch')->getList($product->id, isset($objectID) ? $objectID : 0, 'all');
- $branchTagOption = array();
- foreach($branches as $branchInfo) $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
- $moduleOptionMenu = $this->loadModel('tree')->getOptionMenu($story->product, 'story', 0, (string)$story->branch);
- if($product->type == 'normal' and !empty($story->branch)) $moduleOptionMenu += $this->tree->getModulesName(array($story->module));
- $storyBranch = $story->branch > 0 ? $story->branch : '0';
- $branch = $product->type == 'branch' ? $storyBranch : 'all';
- $productStories = $this->story->getProductStoryPairs($story->product, $branch, 0, 'all', 'id_desc', 0, '', $story->type);
- /* 追加字段的name、title属性,展开user数据。 */
- foreach($fields as $field => $attr)
- {
- if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users;
- if(isset($story->$field)) $fields[$field]['default'] = $story->$field;
- if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field;
- if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->story, $field);
- }
- /* 设置下拉菜单内容。 */
- if(isset($stories[$storyID])) unset($stories[$storyID]);
- $fields['product']['options'] = $products;
- $fields['branch']['options'] = $branchTagOption;
- $fields['module']['options'] = $moduleOptionMenu;
- $fields['plan']['options'] = $plans;
- $fields['reviewer']['options'] = $reviewers;
- $fields['parent']['options'] = array_filter($stories);
- $fields['grade']['options'] = $grades;
- $fields['duplicateStory']['options'] = $productStories;
- $fields['assignedTo']['options'] += array('closed' => 'Closed');
- $fields['stage']['options'] = $this->lang->{$story->type}->stageList;
- /* 设置默认值。 */
- if(empty($fields['reviewer']['default'])) $fields['reviewer']['default'] = implode(',', array_keys($reviewerList));
- if($story->type == 'story') unset($fields['stage']['options']['inroadmap'], $fields['stage']['options']['incharter']);
- $this->view->users = $users;
- $this->view->storyReviewers = array_keys($reviewerList);
- $this->view->gradeRule = $this->config->{$story->type}->gradeRule;
- return $fields;
- }
- /**
- * 获取批量创建需求的表单字段。
- * Get form fields for batch create.
- *
- * @param int $productID
- * @param string $branch
- * @param int $executionID
- * @access protected
- * @return array
- * @param string $storyType
- */
- protected function getFormFieldsForBatchCreate($productID, $branch, $executionID = 0, $storyType = '')
- {
- $product = $this->loadModel('product')->getByID($productID);
- $fields = $this->config->story->form->batchCreate;
- foreach(explode(',', trim($this->config->{$storyType}->create->requiredFields, ',')) as $field) $fields[$field]['required'] = true;
- if($executionID)
- {
- $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct(array($productID), $executionID, 'noclosed|withMain') : array();
- $branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array();
- $branch = empty($branches) ? '' : key($branches);
- if(isset($this->view->execution->type) && $this->view->execution->type == 'kanban')
- {
- $fields['region']['options'] = zget($this->view, 'regionPairs', array());
- $fields['lane']['options'] = zget($this->view, 'lanePairs', array());
- $fields['region']['default'] = zget($this->view, 'regionID', 0);
- $fields['lane']['default'] = zget($this->view, 'laneID', 0);
- }
- }
- else
- {
- $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array();
- }
- $branch = current(explode(',', (string)$branch));
- $modules = $this->tree->getOptionMenu($productID, 'story', 0, $branch === 'all' ? '0' : $branch);
- $plans = $this->loadModel('productplan')->getPairs($productID, ($branch === 'all' or empty($branch)) ? '' : $branch, 'unexpired|noclosed', true);
- $reviewers = $this->story->getProductReviewers($productID);
- $users = $this->user->getPairs('pdfirst|noclosed|nodeleted');
- $stories = $this->story->getParentStoryPairs($productID, '', $storyType);
- $grades = $this->story->getGradePairs($storyType);
- /* 追加字段的label属性。 */
- foreach($fields as $field => $attr)
- {
- if(!isset($attr['label']))
- {
- if(strpos(',region,lane,', ",$field,") !== false)
- {
- $this->app->loadLang('kanban');
- $fields[$field]['label'] = zget($this->lang->kanbancard, $field);
- continue;
- }
- $fields[$field]['label'] = zget($this->lang->story, $field);
- }
- }
- /* 设置下拉菜单内容。 */
- $fields['branch']['options'] = $branches;
- if($product->type == 'normal') unset($fields['branch']);
- $fields['grade']['default'] = key($grades);
- $fields['module']['options'] = $modules;
- $fields['grade']['options'] = $grades;
- $fields['reviewer']['options'] = $reviewers;
- $fields['assignedTo']['options'] = $users;
- $fields['mailto']['options'] = $users;
- $fields['parent']['options'] = array_filter($stories);
- if(!empty($fields['plan'])) $fields['plan']['options'] = $plans;
- if($this->story->checkForceReview($storyType)) $fields['reviewer']['required'] = true;
- if(empty($branches)) unset($fields['branch']);
- if(!empty($this->view->hiddenPlan)) unset($fields['plan']);
- if($this->config->edition != 'ipd' && $storyType == 'epic') $fields['parent']['hidden'] = true;
- $this->view->branchID = $branch;
- $this->view->gradeRule = $this->config->{$storyType}->gradeRule;
- return $fields;
- }
- /**
- * 获取变更需求的表单字段。
- * Get form fields for change story.
- *
- * @param int $storyID
- * @access protected
- * @return array
- */
- protected function getFormFieldsForChange($storyID)
- {
- $story = $this->view->story;
- $fields = $this->config->story->form->change;
- unset($fields['relievedTwins']);
- unset($fields['deleteFiles']);
- unset($fields['renameFiles']);
- unset($fields['docs']);
- unset($fields['oldDocs']);
- unset($fields['docVersions']);
- foreach(array_keys($fields) as $field)
- {
- if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field;
- if(!isset($fields[$field]['title'])) $fields[$field]['title'] = $field == 'reviewer' ? $this->lang->story->reviewers : zget($this->lang->story, $field);
- }
- $reviewerAndResultPairs = $this->story->getReviewerPairs($storyID, $story->version);
- $reviewer = array_keys($reviewerAndResultPairs);
- $fields['reviewer']['options'] = $this->story->getProductReviewers($story->product, $reviewer);
- $fields['reviewer']['default'] = $reviewer;
- $fields['title']['default'] = $story->title;
- $fields['color']['default'] = $story->color;
- $fields['spec']['default'] = $story->spec;
- $fields['verify']['default'] = $story->verify;
- $fields['status']['default'] = $story->status;
- $forceReview = $this->story->checkForceReview($story->type);
- if($forceReview) $fields['reviewer']['required'] = true;
- $this->view->forceReview = $forceReview;
- $this->view->needReview = ($this->app->user->account == $this->view->product->PO || $this->config->{$story->type}->needReview == 0 || !$forceReview) && empty($reviewer);
- $fields['comment'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => '', 'name' => 'comment', 'title' => $this->lang->comment);
- $requiredFields = $this->config->{$story->type}->change->requiredFields;
- if(strpos(",{$requiredFields},", ',comment,') !== false) $fields['comment']['required'] = true;
- if(strpos(",{$requiredFields},", ',spec,') !== false) $fields['spec']['required'] = true;
- if(strpos(",{$requiredFields},", ',verify,') !== false) $fields['verify']['required'] = true;
- return $fields;
- }
- /**
- * 获取评审需求的表单字段。
- * Get form fields for review story.
- *
- * @param int $storyID
- * @access protected
- * @return array
- */
- protected function getFormFieldsForReview($storyID)
- {
- $story = $this->view->story;
- $fields = $this->config->story->form->review;
- $users = $this->loadModel('user')->getPairs('nodeleted|noclosed', "$story->lastEditedBy,$story->openedBy");
- $resultList = $this->lang->{$story->type}->reviewResultList;
- if($story->status == 'reviewing')
- {
- if($story->version == 1) unset($resultList['revert']);
- if($story->version > 1) unset($resultList['reject']);
- }
- foreach($fields as $field => $attr)
- {
- if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users;
- if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field;
- if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->story, $field);
- }
- $fields['result']['options'] = $resultList;
- $fields['reviewedDate']['default'] = helper::now();
- $fields['assignedTo']['default'] = $story->assignedTo;
- $fields['pri']['default'] = $story->pri;
- $fields['estimate']['default'] = $story->estimate ? $story->estimate : 0;
- $fields['status']['default'] = $story->status;
- $fields['closedReason']['required'] = true;
- $fields['duplicateStory']['required'] = true;
- $fields['comment'] = array('type' => 'string', 'control' => 'editor', 'required' => false, 'default' => '', 'name' => 'comment', 'title' => $this->lang->comment, 'width' => 'full');
- if(strpos($this->config->{$story->type}->review->requiredFields, 'reviewedDate') !== false) $fields['reviewedDate']['required'] = true;
- if(strpos($this->config->{$story->type}->review->requiredFields, 'comment') !== false) $fields['comment']['required'] = true;
- $this->view->users = $users;
- return $fields;
- }
- /**
- * Set form options for batch edit.
- *
- * @param int $productID
- * @param int $executionID
- * @param array $stories
- * @access protected
- * @return void
- */
- protected function setFormOptionsForBatchEdit($productID, $executionID, $stories)
- {
- $this->loadModel('product');
- $this->loadModel('tree');
- if($productID and !$executionID)
- {
- $product = $this->product->getByID($productID);
- $options = $this->getFormOptionsForSingleProduct($productID, $executionID, $product);
- $branchProduct = $options['branchProduct'];
- $modules = $options['modules'];
- $branchTagOption = $options['branchTagOption'];
- $products = $options['products'];
- $plans = $options['plans'];
- }
- else
- {
- /* Get product id list by the stories. */
- $branchProduct = false;
- $branchTagOption = array();
- $plans = array();
- $modules = array();
- $productIdList = array();
- foreach($stories as $story) $productIdList[$story->product] = $story->product;
- $products = $this->product->getByIdList($productIdList);
- foreach($products as $storyProduct)
- {
- $options = $this->getFormOptionsForSingleProduct($storyProduct->id, $executionID, $storyProduct);
- if($options['branchProduct'] && !$branchProduct) $branchProduct = true;
- if($options['branchProduct']) $branchTagOption[$storyProduct->id] = $options['branchTagOption'][$storyProduct->id];
- $modules += $options['modules'];
- $plans += $options['plans'];
- if(empty($plans[$storyProduct->id])) $plans[$storyProduct->id][0] = $plans[$storyProduct->id];
- }
- }
- /* Append module when change product type. */
- $moduleList = array(0 => '/');
- $productStoryList = array();
- foreach($stories as $story)
- {
- $moduleList[$story->id] = array();
- if(isset($modules[$story->product][$story->branch])) $moduleList[$story->id] = $modules[$story->product][$story->branch];
- if(empty($moduleList[$story->id]) and isset($modules[$story->product])) $moduleList[$story->id] = zget($modules[$story->product], 0, array()) + $this->tree->getModulesName(array($story->module));
- if($story->status == 'closed')
- {
- $storyBranch = $story->branch > 0 ? $story->branch : '0';
- $branch = $products[$story->product]->type == 'branch' ? $storyBranch : 'all';
- if(!isset($productStoryList[$story->product][$story->branch])) $productStoryList[$story->product][$story->branch] = $this->story->getProductStoryPairs($story->product, $branch, 0, 'all', 'id_desc', 0, '', $story->type);
- }
- if(!empty($story->plan))
- {
- foreach(explode(',', $story->plan) as $planID)
- {
- if(empty($planID)) continue;
- if(!isset($plans[$story->product][$story->branch][$planID]))
- {
- $plan = $this->dao->select('id,title,begin,end')->from(TABLE_PRODUCTPLAN)->where('id')->eq($planID)->fetch();
- $plans[$story->product][$story->branch][$planID] = $plan->title . ' [' . $plan->begin . '~' . $plan->end . ']';
- }
- }
- }
- }
- $urStageOptions = array();
- foreach($this->lang->requirement->stageList as $stageKey => $stageLang) $urStageOptions[] = array('text' => $stageLang, 'value' => $stageKey);
- $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed');
- $this->view->branchTagOption = $branchTagOption;
- $this->view->moduleList = $moduleList;
- $this->view->productStoryList = $productStoryList;
- $this->view->plans = $plans;
- $this->view->branchProduct = $branchProduct;
- $this->view->urStageOptions = $urStageOptions;
- }
- /**
- * Get form options for single product.
- *
- * @param int $productID
- * @param int $executionID
- * @param object $product
- * @access private
- * @return array
- */
- private function getFormOptionsForSingleProduct($productID, $executionID, $product)
- {
- $this->loadModel('branch');
- $this->loadModel('productplan');
- $this->loadModel('tree');
- $branchProduct = $product->type == 'normal' ? false : true;
- $branches = array(0);
- $branchTagOption = array();
- if($branchProduct)
- {
- $branches = $this->branch->getList($productID, $executionID, 'all');
- foreach($branches as $branchInfo) $branchTagOption['branch' . $branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
- $branches = array_keys($branches);
- }
- $modulePairs = $this->tree->getOptionMenu($productID, 'story', 0, $branches);
- $modules = array($productID => $modulePairs);
- $branchTagOption = array($productID => $branchTagOption);
- $products = array($productID => $product);
- $plans = array($productID => $this->productplan->getBranchPlanPairs($productID, $branches, '', true));
- return array('branchProduct' => $branchProduct, 'modules' => $modules, 'branchTagOption' => $branchTagOption, 'products' => $products, 'plans' => $plans);
- }
- /**
- * Get stories by post checked id list.
- *
- * @access protected
- * @return array|false
- */
- protected function getStoriesByChecked()
- {
- $storyIdList = $this->post->storyIdList;
- if(empty($storyIdList)) return false;
- /* Get edited stories. */
- $storyIdList = array_unique($storyIdList);
- foreach($storyIdList as $index => $storyID)
- {
- /* 处理选中的子需求的ID,截取-后的子需求ID。*/
- /* Process selected child story ID. */
- if(strpos((string)$storyID, '-') !== false) $storyIdList[$index] = substr($storyID, strpos($storyID, '-') + 1);
- }
- $stories = $this->story->getByList($storyIdList);
- if(empty($stories)) return false;
- /* Filter twins. */
- $twins = '';
- $frozenStories = '';
- foreach($stories as $id => $story)
- {
- if(!empty($story->frozen))
- {
- $frozenStories .= "#$id ";
- unset($stories[$id]);
- continue;
- }
- if(empty($story->twins)) continue;
- $twins .= "#$id ";
- unset($stories[$id]);
- }
- if(!empty($twins)) $this->view->twinsTip = sprintf($this->lang->story->batchEditTip, $twins);
- if(!empty($frozenStories)) $this->view->frozenTips = sprintf($this->lang->story->frozenTips, $frozenStories, $this->lang->story->edit);
- return $stories;
- }
- /**
- * 设置模块字段的表单字段。
- * Set module form field.
- *
- * @param array $fields
- * @param int $moduleID
- * @access protected
- * @return array
- */
- protected function setModuleField($fields, $moduleID)
- {
- $productID = $this->view->productID;
- $branch = $this->view->branch;
- $optionMenu = $this->tree->getOptionMenu($productID, 'story', 0, $branch === 'all' ? '0' : (string)$branch, 'nodeleted');
- $moduleID = $moduleID ? $moduleID : (int)$this->cookie->lastStoryModule;
- $moduleID = $moduleID ? $moduleID : $fields['module']['default'];
- $moduleID = isset($optionMenu[$moduleID]) ? $moduleID : 0;
- $fields['module']['options'] = $optionMenu;
- $fields['module']['default'] = $moduleID;
- $fields['modules']['options'] = $optionMenu;
- $fields['modules']['default'] = $moduleID;
- $this->view->moduleID = $moduleID;
- return $fields;
- }
- /**
- * 根据配置,删除非必要的表单字段配置。
- * Remove form fields for create.
- *
- * @param array $fields
- * @param string $storyType
- * @access protected
- * @return array
- */
- protected function removeFormFieldsForCreate($fields, $storyType = 'story')
- {
- $objectID = $this->view->objectID;
- /* Hidden some fields of projects without products. */
- $hiddenProduct = $hiddenPlan = false;
- $teamUsers = array();
- if($this->app->tab === 'project' || $this->app->tab === 'execution')
- {
- $project = $this->dao->findById((int)$objectID)->from(TABLE_PROJECT)->fetch();
- if(!empty($project->project)) $project = $this->dao->findById((int)$project->project)->from(TABLE_PROJECT)->fetch();
- if(empty($project->hasProduct))
- {
- $teamUsers = $this->project->getTeamMemberPairs($project->id);
- $hiddenProduct = true;
- if($project->model !== 'scrum' or !$project->multiple) $hiddenPlan = true;
- }
- }
- if($storyType != 'story') unset($fields['branches'], $fields['modules'], $fields['plans']);
- if($hiddenPlan) unset($fields['plan']);
- if($hiddenProduct)
- {
- $fields['product']['control'] = 'hidden';
- $fields['reviewer']['options'] = $teamUsers;
- $fields['assignedTo']['options'] = $teamUsers;
- }
- return $fields;
- }
- /**
- * 隐藏不显示的字段。
- * Hide form fields for edi.
- *
- * @param array $fields
- * @param string $storyType
- * @access protected
- * @return array
- */
- protected function hiddenFormFieldsForEdit($fields, $storyType)
- {
- $product = $this->view->product;
- $hiddenProduct = $hiddenPlan = false;
- $teamUsers = array();
- if($product->shadow)
- {
- $this->loadModel('project');
- $project = $this->project->getByShadowProduct($product->id);
- $teamUsers = $this->project->getTeamMemberPairs($project->id);
- $hiddenProduct = true;
- if($project->model !== 'scrum') $hiddenPlan = true;
- if(!$project->multiple)
- {
- $hiddenPlan = true;
- unset($this->lang->story->stageList[''], $this->lang->story->stageList['wait'], $this->lang->story->stageList['planned']);
- }
- }
- if($hiddenPlan) $fields['plan']['className'] = 'hidden';
- if($hiddenProduct)
- {
- $fields['product']['className'] = 'hidden';
- $fields['reviewer']['options'] = $teamUsers;
- $fields['assignedTo']['options'] = $teamUsers;
- }
- return $fields;
- }
- /**
- * 根据配置,删除非必要的表单字段配置。
- * Remove form fields for batch create.
- *
- * @param array $fields
- * @param bool $hiddenPlan
- * @param string $executionType
- * @param int $executionID
- * @access protected
- * @return array
- */
- protected function removeFormFieldsForBatchCreate($fields, $hiddenPlan, $executionType, $executionID = 0)
- {
- if($hiddenPlan) unset($fields['plan']);
- if($executionType != 'kanban')
- {
- unset($fields['region']);
- unset($fields['lane']);
- }
- if($this->app->tab == 'project' || $this->app->tab == 'execution')
- {
- $fields['parent']['hidden'] = true;
- $project = $this->dao->findById((int)$executionID)->from(TABLE_PROJECT)->fetch();
- if(!empty($project->project)) $project = $this->dao->findById((int)$project->project)->from(TABLE_PROJECT)->fetch();
- if(empty($project->hasProduct))
- {
- $teamUsers = $this->project->getTeamMemberPairs($project->id);
- $fields['reviewer']['options'] = $teamUsers;
- $fields['assignedTo']['options'] = $teamUsers;
- }
- }
- return $fields;
- }
- /**
- * 获取指派给我的Block编号。
- * Get assign me block id.
- *
- * @access protected
- * @return int
- */
- protected function getAssignMeBlockID()
- {
- if(!isonlybody()) return 0;
- return (int)$this->dao->select('id')->from(TABLE_BLOCK)->where('module')->eq('assigntome')
- ->andWhere('module')->eq('my')
- ->andWhere('account')->eq($this->app->user->account)
- ->andWhere('vision')->eq($this->config->vision)
- ->orderBy('order_desc')
- ->limit(1)
- ->fetch('id');
- }
- /**
- * 构建创建需求数据。
- * Build story for create
- *
- * @param int $executionID
- * @param int $bugID
- * @param string $storyType
- * @access protected
- * @return object|false
- */
- protected function buildStoryForCreate($executionID, $bugID, $storyType = 'story')
- {
- $fields = $this->config->story->form->create;
- foreach(explode(',', trim($this->config->{$storyType}->create->requiredFields, ',')) as $field)
- {
- if($field == 'files')
- {
- if(empty($_FILES['files']['name'][0]))
- {
- dao::$errors['files'][] = sprintf($this->lang->error->notempty, $this->lang->files);
- }
- continue;
- }
- $fields[$field]['required'] = true;
- }
- if(!isset($_POST['plan'])) $this->config->{$storyType}->create->requiredFields = str_replace(',plan,', ',', ",{$this->config->story->create->requiredFields},");
- if(!empty($_POST['modules']) && !empty($fields['module']['required']))
- {
- /* Check empty module in the product with multi-branches. */
- $fields['module']['required'] = false;
- $this->config->{$storyType}->create->requiredFields = str_replace(',module,', ',', ",{$this->config->{$storyType}->create->requiredFields},");
- foreach($_POST['modules'] as $key => $moduleID)
- {
- if(!$key) continue;
- if(empty($moduleID)) dao::$errors["modules[{$key}]"][] = sprintf($this->lang->error->notempty, $this->lang->story->module);
- }
- }
- if(!empty($_POST['estimate']) && $_POST['estimate'] < 0) dao::$errors['estimate'] = sprintf($this->lang->story->errorRecordMinus, $this->lang->story->estimateAB);
- if(dao::isError()) return false;
- $storyData = form::data($fields)
- ->setIF($this->post->assignedTo, 'assignedDate', helper::now())
- ->setIF($this->post->plan > 0 && $storyType == 'story', 'stage', 'planned')
- ->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)
- ->setIF(!$this->post->estimate, 'estimate', 0)
- ->setIF($storyType, 'type', $storyType)
- ->get();
- if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']);
- if(!$this->post->needNotReview and empty($_POST['reviewer']))
- {
- dao::$errors['reviewer'] = sprintf($this->lang->error->notempty, $this->lang->story->reviewer);
- return false;
- }
- /* Need and force review, then set status to reviewing. */
- if($storyData->status != 'draft' and $this->story->checkForceReview($storyType) and !$this->post->needNotReview) $storyData->status = 'reviewing';
- return $this->loadModel('file')->processImgURL($storyData, $this->config->story->editor->create['id'], $this->post->uid);
- }
- /**
- * 构建编辑需求数据。
- * Build story for edit
- *
- * @param int $storyID
- * @access protected
- * @return object|false
- */
- protected function buildStoryForEdit($storyID)
- {
- $storyPlan = array();
- $oldStory = $this->story->getByID($storyID);
- if(!$oldStory) return false;
- if(!empty($_POST['lastEditedDate']) and $oldStory->lastEditedDate != $this->post->lastEditedDate) dao::$errors[] = $this->lang->error->editedByOther;
- if(strpos('draft,changing', $oldStory->status) !== false and $this->story->checkForceReview($oldStory->type) and empty($_POST['reviewer'])) dao::$errors[] = $this->lang->story->notice->reviewerNotEmpty;
- if(!empty($_POST['plan'])) $storyPlan = is_array($_POST['plan']) ? array_filter($_POST['plan']) : array($_POST['plan']);
- if(count($storyPlan) > 1 && $oldStory->type == 'story')
- {
- $oldStoryPlan = !empty($oldStory->plan) ? array_filter(explode(',', $oldStory->plan)) : array();
- $oldPlanDiff = array_diff($storyPlan, $oldStoryPlan);
- $storyPlanDiff = array_diff($oldStoryPlan, $storyPlan);
- if(!empty($oldPlanDiff) or !empty($storyPlanDiff)) dao::$errors['plan'] = $this->lang->story->notice->changePlan;
- }
- if(strpos(',draft,changing,', $oldStory->status) !== false)
- {
- if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']);
- if(!$this->post->needNotReview and empty($_POST['reviewer'])) dao::$errors['reviewer'] = $this->lang->story->errorEmptyReviewedBy;
- }
- if(!empty($_POST['estimate']) && $_POST['estimate'] < 0) dao::$errors['estimate'] = sprintf($this->lang->story->errorRecordMinus, $this->lang->story->estimateAB);
- if(dao::isError()) return false;
- $hasProduct = $this->dao->select('t2.hasProduct')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
- ->where('t1.product')->eq($oldStory->product)
- ->andWhere('t2.deleted')->eq(0)
- ->fetch('hasProduct');
- $_POST['product'] = (!empty($hasProduct) && !$hasProduct) ? $oldStory->product : $this->post->product;
- $now = helper::now();
- $fields = $this->config->story->form->edit;
- $storyData = form::data($fields, $storyID)
- ->add('id', $storyID)
- ->add('lastEditedBy', $this->app->user->account)
- ->add('lastEditedDate', $now)
- ->add('demand', $oldStory->demand)
- ->setDefault('deleteFiles', array())
- ->setDefault('reviewedBy', $oldStory->reviewedBy)
- ->setDefault('renameFiles', array())
- ->setDefault('product', $oldStory->product)
- ->setDefault('branch', $oldStory->branch)
- ->setDefault('stage', $oldStory->stage)
- ->setDefault('stagedBy', $oldStory->stagedBy)
- ->setIF($this->post->assignedTo != $oldStory->assignedTo, 'assignedDate', $now)
- ->setIF($this->post->closedBy && $oldStory->closedDate == '', 'closedDate', $now)
- ->setIF($this->post->closedReason && $oldStory->closedDate == '', 'closedDate', $now)
- ->setIF($this->post->closedBy || $this->post->closedReason != false, 'status', 'closed')
- ->setIF($this->post->closedReason && $this->post->closedBy == false, 'closedBy', $this->app->user->account)
- ->setIF($this->post->stage == 'released', 'releasedDate', $now)
- ->setIF(!$this->post->grade, 'grade', $oldStory->grade)
- ->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')
- ->setIF(!isset($_POST['title']), 'title', $oldStory->title)
- ->setIF(!isset($_POST['spec']), 'spec', $oldStory->spec)
- ->setIF(!isset($_POST['verify']), 'verify', $oldStory->verify)
- ->setIF(!isset($_POST['estimate']), 'estimate', $oldStory->estimate)
- ->get();
- if($this->post->linkStories) $storyData->linkStories = implode(',', array_unique($this->post->linkStories));
- if($this->post->linkRequirements) $storyData->linkRequirements = implode(',', array_unique($this->post->linkRequirements));
- if($oldStory->product != $storyData->product) $storyData->root = $storyID;
- return $this->loadModel('file')->processImgURL($storyData, $this->config->story->editor->edit['id'], $this->post->uid);
- }
- /**
- * 构建变更需求数据。
- * Build story for change
- *
- * @param int $storyID
- * @access protected
- * @return object|false
- */
- protected function buildStoryForChange($storyID)
- {
- $oldStory = $this->story->getByID($storyID);
- if(!empty($_POST['lastEditedDate']) and $oldStory->lastEditedDate != $this->post->lastEditedDate) dao::$errors[] = $this->lang->error->editedByOther;
- if(strpos($this->config->{$oldStory->type}->change->requiredFields, 'spec') !== false and !$this->post->spec) dao::$errors['spec'][] = sprintf($this->lang->error->notempty, $this->lang->story->spec);
- if(strpos($this->config->{$oldStory->type}->change->requiredFields, 'verify') !== false and !$this->post->spec) dao::$errors['verify'][] = sprintf($this->lang->error->notempty, $this->lang->story->verify);
- if(strpos($this->config->{$oldStory->type}->change->requiredFields, 'comment') !== false and !$this->post->comment) dao::$errors['comment'] = sprintf($this->lang->error->notempty, $this->lang->comment);
- if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']);
- if(!$this->post->needNotReview and empty($_POST['reviewer'])) dao::$errors['reviewer'] = $this->lang->story->errorEmptyReviewedBy;
- if(dao::isError()) return false;
- $now = helper::now();
- $fields = $this->config->story->form->change;
- $story = form::data($fields, $storyID)
- ->setDefault('lastEditedBy', $this->app->user->account)
- ->setDefault('deleteFiles', array())
- ->setDefault('lastEditedDate', $now)
- ->setDefault('version', $oldStory->version)
- ->get();
- $specChanged = false;
- $oldStoryReviewers = array_keys($this->story->getReviewerPairs($storyID, $oldStory->version));
- $_POST['reviewer'] = isset($_POST['reviewer']) ? $_POST['reviewer'] : array();
- $reviewerHasChanged = (array_diff($oldStoryReviewers, $_POST['reviewer']) || array_diff($_POST['reviewer'], $oldStoryReviewers));
- if($story->spec != $oldStory->spec or $story->verify != $oldStory->verify or $story->title != $oldStory->title or $this->loadModel('file')->getCount() or $reviewerHasChanged or !empty($story->deleteFiles)) $specChanged = true;
- $story->reviewerHasChanged = $reviewerHasChanged;
- if($specChanged)
- {
- $story->version = (int)$oldStory->version + 1;
- $story->reviewedBy = '';
- $story->changedBy = $this->app->user->account;
- $story->changedDate = $now;
- $story->closedBy = '';
- $story->closedReason = '';
- if($oldStory->reviewedBy) $story->reviewedDate = null;
- if($oldStory->closedBy) $story->closedDate = null;
- }
- else
- {
- $story->status = $oldStory->status;
- }
- if(!isset($_POST['relievedTwins'])) unset($story->relievedTwins);
- return $this->loadModel('file')->processImgURL($story, $this->config->story->editor->change['id'], $this->post->uid);
- }
- /**
- * 构建需求转化任务的数据。
- * Build data for batchToTask.
- *
- * @param int $executionID
- * @param int $projectID
- * @access protected
- * @return array|false
- */
- protected function buildDataForBatchToTask($executionID, $projectID = 0)
- {
- $this->loadModel('task');
- $now = helper::now();
- $fields = $this->config->story->form->batchToTask;
- $requiredFields = "," . $this->config->task->create->requiredFields . ",";
- foreach(explode(',', trim($requiredFields, ',')) as $field)
- {
- if(isset($fields[$field])) $fields[$field]['required'] = true;
- }
- $syncFields = zget($_POST, 'syncFields', '');
- $stories = array();
- if(!empty($syncFields)) $stories = empty($_POST['story']) ? array() : $this->story->getByList($_POST['story']);
- $this->lang->story->estimate = $this->lang->task->estimateAB;
- $this->lang->story->deadline = $this->lang->task->deadline;
- $this->lang->story->estStarted = $this->lang->task->estStarted;
- $tasks = form::batchData($fields)->get();
- $taskNames = array();
- foreach($tasks as $task)
- {
- $task->project = $projectID;
- $task->execution = $executionID;
- $task->left = $task->estimate;
- if($task->assignedTo) $task->assignedDate = $now;
- if($task->story)
- {
- $story = zget($stories, $task->story, null);
- if($story)
- {
- $task->storyVersion = $story->version;
- if(strpos(",{$syncFields},", ',spec,') !== false) $task->desc = $story->spec;
- if(strpos(",{$syncFields},", ',mailto,') !== false) $task->mailto = $story->mailto;
- }
- }
- if(in_array($task->name, $taskNames)) dao::$errors['message'][] = sprintf($this->lang->duplicate, $this->lang->task->common) . ' ' . $task->name;
- if(!helper::isZeroDate($task->deadline) and $task->deadline < $task->estStarted) dao::$errors['message'][] = $this->lang->task->error->deadlineSmall;
- if($task->estimate and !preg_match("/^[0-9]+(.[0-9]+)?$/", (string)$task->estimate)) dao::$errors['message'][] = $this->lang->task->error->estimateNumber;
- if(!empty($this->config->limitTaskDate)) $this->task->checkEstStartedAndDeadline($executionID, (string)$task->estStarted, (string)$task->deadline);
- $taskNames[] = $task->name;
- }
- if(dao::isError()) return false;
- return $tasks;
- }
- /**
- * 处理编辑需求数据。
- * Process data for edit.
- *
- * @param int $storyID
- * @param object $story
- * @access protected
- * @return void
- */
- protected function processDataForEdit($storyID, $story)
- {
- $oldStory = $this->story->fetchByID($storyID);
- if($oldStory->type == 'story' and !isset($story->linkStories)) $story->linkStories = '';
- if($oldStory->type == 'requirement' and !isset($story->linkRequirements)) $story->linkRequirements = '';
- if($oldStory->status == 'changing' and $story->status == 'draft') $story->status = 'changing';
- if(isset($_POST['plan']) and is_array($_POST['plan'])) $story->plan = trim(implode(',', $_POST['plan']), ',');
- if(isset($_POST['branch']) and $_POST['branch'] == 0) $story->branch = 0;
- if(isset($story->stage) and $oldStory->stage != $story->stage) $story->stagedBy = (strpos('tested|verified|rejected|pending|released|closed', $story->stage) !== false) ? $this->app->user->account : '';
- }
- /**
- * 构建评审需求数据。
- * Build story for review
- *
- * @param int $storyID
- * @access protected
- * @return object|false
- */
- protected function buildStoryForReview($storyID)
- {
- $oldStory = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch();
- $now = helper::now();
- $fields = $this->config->story->form->review;
- foreach(explode(',', trim($this->config->{$oldStory->type}->review->requiredFields, ',')) as $field)
- {
- if($field == 'comment' && !$this->post->comment)
- {
- dao::$errors['comment'][] = sprintf($this->lang->error->notempty, $this->lang->comment);
- return false;
- }
- if($field == 'reviewedDate' && !$this->post->reviewedDate)
- {
- dao::$errors['reviewedDate'] = sprintf($this->lang->error->notempty, $this->lang->story->reviewedDate);
- return false;
- }
- if(isset($fields[$field])) $fields[$field]['required'] = true;
- }
- if($this->post->result == false)
- {
- dao::$errors['result'][] = $this->lang->story->mustChooseResult;
- return false;
- }
- $result = $this->post->result;
- $closedReason = $this->post->closedReason;
- $storyData = form::data($fields, $storyID)
- ->setDefault('lastEditedBy', $this->app->user->account)
- ->setDefault('lastEditedDate', $now)
- ->removeIF($result != 'reject', 'closedReason,duplicateStory')
- ->removeIF($result == 'reject' && $closedReason != 'duplicate', 'duplicateStory')
- ->get();
- if($oldStory->assignedTo != $storyData->assignedTo) $storyData->assignedDate = $now;
- $storyData->reviewedBy = implode(',', array_unique(explode(',', $oldStory->reviewedBy . ',' . $this->app->user->account)));
- if($result == 'reject' && empty($closedReason)) dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->story->rejectedReason);
- if($result == 'reject' && $closedReason == 'duplicate' && empty($storyData->duplicateStory)) dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->story->duplicateStory);
- if(!empty($storyData->estimate) && $storyData->estimate < 0) dao::$errors['estimate'] = sprintf($this->lang->story->errorRecordMinus, $this->lang->story->estimate);
- if(dao::isError()) return false;
- return $this->loadModel('file')->processImgURL($storyData, $this->config->story->editor->review['id'], $this->post->uid);
- }
- /**
- * 构建批量创建需求数据。
- * Build stories for batch create.
- *
- * @param int $productID
- * @param string $storyType
- * @access protected
- * @return array
- */
- protected function buildStoriesForBatchCreate($productID, $storyType)
- {
- $forceReview = $this->story->checkForceReview($storyType);
- $fields = $this->config->story->form->batchCreate;
- $account = $this->app->user->account;
- $now = helper::now();
- $saveDraft = $this->post->status == 'draft';
- if($forceReview) $fields['reviewer']['required'] = true;
- $stories = form::batchData($fields)->get();
- foreach($stories as $i => $story)
- {
- if(!empty($story->title) && mb_strlen($story->title) > 255)
- {
- dao::$errors["title[$i]"] = sprintf($this->lang->story->error->length, 255);
- }
- if(!empty($story->estimate) && $story->estimate < 0) dao::$errors["estimate[$i]"] = sprintf($this->lang->story->errorRecordMinus, $this->lang->story->estimate);
- $story->type = $storyType;
- $story->status = (empty($story->reviewer) && !$forceReview) ? 'active' : 'reviewing';
- $story->status = $saveDraft ? 'draft' : $story->status;
- $story->product = $productID;
- $story->openedBy = $account;
- $story->vision = $this->config->vision;
- $story->openedDate = $now;
- $story->version = 1;
- if(in_array($this->app->tab, array('project', 'execution')))
- {
- $story->stage = 'projected';
- }
- !empty($story->assignedTo) && $story->assignedDate = $now;
- if($this->post->uploadImage && $this->post->uploadImage[$i]) $story->uploadImage = $this->post->uploadImage[$i];
- }
- return $stories;
- }
- /**
- * 构建批量编辑需求数据。
- * Build stories for batch edit page.
- *
- * @access protected
- * @return array
- */
- protected function buildStoriesForBatchEdit()
- {
- $fields = $this->config->story->form->batchEdit;
- $account = $this->app->user->account;
- $now = helper::now();
- $fields['duplicateStory'] = array('type' => 'int', 'required' => false, 'default' => 0);
- $stories = form::batchData($fields)->get();
- $oldStories = $this->story->getByList(array_keys($stories));
- if($this->config->edition == 'ipd') $roadmaps = $this->loadModel('roadmap')->getList();
- foreach($stories as $storyID => $story)
- {
- $oldStory = $oldStories[$storyID];
- $story->lastEditedBy = $this->app->user->account;
- $story->lastEditedDate = $now;
- $story->stage = empty($story->stage) ? $oldStory->stage : $story->stage;
- $story->status = empty($story->status) ? $oldStory->status : $story->status;
- $story->branch = $story->branch === '' ? $oldStory->branch : str_replace('branch', '', $story->branch);
- if(empty($story->roadmap)) $story->roadmap = $oldStory->roadmap;
- if($oldStory->assignedTo != $story->assignedTo) $story->assignedDate = $now;
- if($oldStory->parent < 0) $story->plan = '';
- if($story->stage != $oldStory->stage) $story->stagedBy = (strpos('|tested|verified|rejected|pending|released|closed|', "|{$story->stage}|") !== false) ? $account : '';
- if($story->closedBy && helper::isZeroDate($oldStory->closedDate)) $story->closedDate = $now;
- if($story->closedReason && helper::isZeroDate($oldStory->closedDate)) $story->closedDate = $now;
- if($story->closedBy || $story->closedReason) $story->status = 'closed';
- if($story->closedReason && empty($story->closedBy)) $story->closedBy = $account;
- if($story->closedBy && empty($story->closedReason)) dao::$errors['closedReason'] = sprintf($this->lang->error->notempty, $this->lang->story->closedReason);
- if($story->closedReason == 'done' && empty($story->stage)) dao::$errors['stage'] = sprintf($this->lang->error->notempty, $this->lang->story->stage);
- if($story->closedReason == 'duplicate' && empty($story->duplicateStory)) dao::$errors['duplicateStory'] = sprintf($this->lang->error->notempty, $this->lang->story->duplicateStory);
- if($this->config->vision == 'or' && $this->config->edition == 'ipd')
- {
- if($story->stage == 'wait' && !empty($story->roadmap) && isset($roadmaps[$story->roadmap])) $story->stage = $roadmaps[$story->roadmap]->status == 'launched' ? 'incharter' : 'inroadmap';
- if(empty($story->roadmap)) $story->stage = 'wait';
- }
- if(!empty($story->estimate) && $story->estimate < 0) dao::$errors["estimate[{$storyID}]"] = sprintf($this->lang->story->errorRecordMinus, $this->lang->story->estimate);
- }
- return $stories;
- }
- /**
- * 构建批量关闭需求数据,跳过有子需求的父需求以及已经关闭了的需求。
- * Build stories for batch close.
- *
- * @access protected
- * @return array
- */
- protected function buildStoriesForBatchClose()
- {
- $account = $this->app->user->account;
- $now = helper::now();
- $data = form::batchData()->get();
- $oldStories = $this->story->getByList(array_keys($data));
- $stories = array();
- foreach($data as $storyID => $story)
- {
- $oldStory = $oldStories[$storyID];
- if($oldStory->parent == -1) continue; /* Skip the story which has any child story. */
- if($oldStory->status == 'closed') continue; /* Skip the story which has been closed. */
- $story->lastEditedBy = $account;
- $story->lastEditedDate = $now;
- $story->closedBy = $account;
- $story->closedDate = $now;
- $story->assignedTo = 'closed';
- $story->assignedDate = $now;
- $story->status = 'closed';
- $story->stage = 'closed';
- if($story->closedReason != 'done') $story->plan = '';
- if($story->closedReason == 'duplicate' && empty($story->duplicateStory)) dao::$errors["duplicateStory[{$storyID}]"] = sprintf($this->lang->error->notempty, $this->lang->story->duplicateStory);
- $stories[$storyID] = $story;
- }
- return $stories;
- }
- /**
- * 如果是在弹窗中打开页面,获取跳转地址。
- * Get response when open in modal.
- *
- * @param string $message
- * @access protected
- * @return array|false
- */
- protected function getResponseInModal($message = '')
- {
- if(!isInModal()) return false;
- $execution = $this->execution->getByID((int)$this->session->execution);
- if($this->app->tab == 'execution' and $execution->type == 'kanban')
- {
- return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'callback' => "refreshKanban()", 'closeModal' => true));
- }
- else
- {
- return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true, 'closeModal' => true));
- }
- }
- /**
- * 获取编辑需求后的跳转地址。
- * Get location when after edit a story.
- *
- * @param int $storyID
- * @param string $storyType
- * @access protected
- * @return string
- */
- protected function getAfterEditLocation($storyID, $storyType)
- {
- $module = $storyType;
- $params = "storyID=$storyID&version=0¶m=0&storyType=$storyType";
- $method = 'view';
- if($this->app->tab == 'project')
- {
- $project = $this->loadModel('project')->getByID($this->session->project);
- if(empty($project->multiple))
- {
- $module = 'execution';
- $method = 'storyView';
- $params = "storyID=$storyID&project={$this->session->project}";
- }
- else
- {
- $module = 'projectstory';
- $params = "storyID=$storyID&project={$this->session->project}";
- }
- }
- return $this->createLink($module, $method, $params);
- }
- /**
- * 获取创建需求后的跳转地址。
- * Get location when after create story.
- *
- * @param int $productID
- * @param string $branch
- * @param int $objectID
- * @param int $storyID
- * @param string $storyType
- * @param string $extra feedback扩展使用
- * @access public
- * @return string
- */
- public function getAfterCreateLocation($productID, $branch, $objectID, $storyID, $storyType, $extra = '')
- {
- if($this->app->getViewType() == 'xhtml') return $this->createLink('story', 'view', "storyID=$storyID", 'html');
- if($objectID)
- {
- helper::setcookie('storyModuleParam', '0', 0);
- $object = $this->loadModel('project')->fetchByID($objectID);
- if(empty($_SESSION['storyList'])) return $this->createLink($this->app->tab == 'project' && $object->type == 'project' ? 'projectstory' : 'execution', 'story', "objectID=$objectID");
- return $this->session->storyList;
- }
- if($this->app->tab == 'product')
- {
- $storyProductID = $this->story->fetchByID($storyID)->product;
- return $this->createLink('product', 'browse', "productID=$storyProductID&branch=$branch&browseType=unclosed¶m=0&storyType=$storyType");
- }
- helper::setcookie('storyModule', '0', 0);
- $branchID = $this->post->branch ? $this->post->branch : $branch;
- if(empty($_SESSION['storyList'])) return $this->createLink('product', 'browse', "productID=$productID&branch=$branchID&browseType=¶m=0&storyType=$storyType&orderBy=id_desc");
- if(!empty($_POST['branches']) and count($_POST['branches']) > 1) return preg_replace('/branch=(\d+|[A-Za-z]+)/', 'branch=all', $this->session->storyList);
- return $this->session->storyList;
- }
- /**
- * 获取批量创建需求后的跳转地址。
- * Get after batch create location.
- *
- * @param int $productID
- * @param string $branch
- * @param int $executionID
- * @param int $storyID
- * @param string $storyType
- * @access protected
- * @return string
- */
- protected function getAfterBatchCreateLocation($productID, $branch, $executionID, $storyID, $storyType)
- {
- if($storyID)
- {
- $locateLink = $this->session->storyList ? $this->session->storyList : $this->createLink('projectstory', 'view', "storyID=$storyID&projectID={$this->app->project}");
- if($this->app->tab == 'product')
- {
- $story = $this->story->fetchById($storyID);
- $locateLink = $this->createLink($story->type, 'view', "storyID=$storyID&version=0¶m=0&storyType=$storyType");
- }
- return $locateLink;
- }
- if($executionID)
- {
- helper::setcookie('storyModuleParam', '0', 0);
- return $this->session->storyList;
- }
- if($this->app->tab == 'product')
- {
- return $this->createLink('product', 'browse', "productID=$productID&branch=$branch&browseType=unclosed&queryID=0&storyType=$storyType");
- }
- helper::setcookie('storyModule', '0', 0);
- if($this->session->storyList) return $this->session->storyList;
- return $this->createLink('product', 'browse', "productID=$productID&branch=$branch&browseType=unclosed&queryID=0&storyType=$storyType");
- }
- /**
- * 获取变更需求后的跳转地址。
- * Get after change story location.
- *
- * @param int $storyID
- * @param string $storyType
- * @access protected
- * @return string
- */
- protected function getAfterChangeLocation($storyID, $storyType = 'story')
- {
- if($this->app->tab == 'execution') return helper::createLink('execution', 'storyView', "storyID=$storyID");
- if($this->app->tab != 'project') return helper::createLink($storyType, 'view', "storyID=$storyID&version=0¶m=0&storyType=$storyType");
- if($this->app->tab == 'project')
- {
- $module = 'projectstory';
- $method = 'view';
- $params = "storyID=$storyID";
- if(!$this->session->multiple)
- {
- $module = 'story';
- $params .= "&version=0¶m={$this->session->project}&storyType=$storyType";
- }
- return helper::createLink($module, $method, $params);
- }
- }
- /**
- * 获取评审需求后的跳转地址。
- * Get after review location.
- *
- * @param int $storyID
- * @param string $storyType
- * @param string $from
- * @access protected
- * @return string
- */
- protected function getAfterReviewLocation($storyID, $storyType = 'story', $from = '')
- {
- if($from == 'project')
- {
- $project = $this->project->getByID($this->session->project);
- if($project && !$project->multiple) return helper::createLink('execution', 'storyView', "storyID=$storyID");
- return helper::createLink('projectstory', 'view', "storyID={$storyID}&projectID={$this->session->project}");
- }
- if($from != 'execution') return helper::createLink($storyType, 'view', "storyID={$storyID}&version=0¶m=0&storyType={$storyType}");
- $module = 'execution';
- $method = 'storyView';
- $params = "storyID=$storyID";
- return helper::createLink($module, $method, $params);
- }
- /**
- * 根据上传图片,批量创建需求时,获取初始化需求数据。
- * Get data from upload images.
- *
- * @param int $productID
- * @param int $moduleID
- * @param int $planID
- * @access protected
- * @return array
- */
- protected function getDataFromUploadImages($productID, $moduleID = 0, $planID = 0)
- {
- /* Clear title when switching products and set the session for the current product. */
- if($productID != $this->cookie->preProductID) unset($_SESSION['storyImagesFile']);
- helper::setcookie('preProductID', (string)$productID);
- $defaultStory = array('title' => '', 'spec' => '', 'module' => $moduleID, 'plan' => $planID, 'pri' => (string)$this->config->story->defaultPriority, 'estimate' => '', 'branch' => $this->view->branchID);
- $batchStories = array();
- $count = $this->config->story->batchCreate;
- for($batchIndex = 0; $batchIndex < $count; $batchIndex++) $batchStories[] = $defaultStory;
- if(empty($_SESSION['storyImagesFile'])) return $batchStories;
- $files = $this->session->storyImagesFile;
- $batchStories = array();
- foreach($files as $fileName => $file)
- {
- $defaultStory['title'] = $file['title'];
- $defaultStory['uploadImage'] = $fileName;
- $batchStories[] = $defaultStory;
- }
- return $batchStories;
- }
- /**
- * Get custom fields list.
- *
- * @param object $config
- * @param string $storyType
- * @param bool $hiddenPlan
- * @param object $product
- * @access protected
- * @return array
- */
- protected function getCustomFields(&$config, $storyType, $hiddenPlan, $product)
- {
- $customFields = array();
- /* Attach multi-branch or multi-platform field. */
- if($product->type != 'normal') $customFields[$product->type] = $this->lang->product->branchName[$product->type];
- foreach(explode(',', $config->story->list->customBatchCreateFields) as $field) $customFields[$field] = $this->lang->story->$field;
- if($hiddenPlan) unset($customFields['plan']);
- if($this->app->tab == 'project' || $this->app->tab == 'execution') unset($customFields['parent']);
- if($product->type != 'normal')
- {
- $config->{$storyType}->custom->batchCreateFields = sprintf($config->story->custom->batchCreateFields, $product->type);
- }
- else
- {
- $config->{$storyType}->custom->batchCreateFields = trim(sprintf($config->story->custom->batchCreateFields, ''), ',');
- }
- return $customFields;
- }
- /**
- * Get show fields list.
- *
- * @param string $fieldListStr
- * @param bool $hiddenPlan
- * @param object $product
- * @access protected
- * @return array
- * @param string $storyType
- */
- protected function getShowFields($fieldListStr, $storyType, $product)
- {
- $showFields = $fieldListStr;
- if($product->type == 'normal')
- {
- $showFields = str_replace(array(0 => ",branch,", 1 => ",platform,"), '', ",$showFields,");
- $showFields = trim($showFields, ',');
- }
- return $showFields;
- }
- /**
- * Build story post data for activating the story.
- *
- * @param int $storyID
- * @access protected
- * @return object
- */
- protected function buildStoryForActivate($storyID)
- {
- $postData = form::data($this->config->story->form->activate, $storyID)->get();
- $story = $this->loadModel('file')->processImgURL($postData, $this->config->story->editor->activate['id'], $this->post->uid);
- return $story;
- }
- /**
- * Build story post data for submitReview the story.
- *
- * @param int $storyID
- * @access protected
- * @return object|false
- */
- protected function buildStoryForSubmitReview($storyID)
- {
- if(isset($_POST['reviewer'])) $_POST['reviewer'] = array_filter($_POST['reviewer']);
- if(!$this->post->needNotReview and empty($_POST['reviewer']))
- {
- dao::$errors['reviewer'] = $this->lang->story->errorEmptyReviewedBy;
- return false;
- }
- return form::data($this->config->story->form->submitReview, $storyID)->get();
- }
- /**
- * Get linked objects. e.g. bugs,cases,linkedMRs,linkedCommits,twins,reviewers,relations.
- *
- * @param object $story
- * @access protected
- * @return void
- */
- protected function getLinkedObjects($story)
- {
- $linkedStories = isset($story->linkStoryTitles) ? array_keys($story->linkStoryTitles) : array();
- $this->view->bugs = $this->dao->select('id,title,status,pri,severity')->from(TABLE_BUG)->where('story')->eq($story->id)->andWhere('deleted')->eq(0)->fetchAll();
- $this->view->fromBug = $story->fromBug ? $this->dao->select('id,title')->from(TABLE_BUG)->where('id')->eq($story->fromBug)->fetch() : '';
- $this->view->cases = $this->dao->select('id,title,status,pri')->from(TABLE_CASE)->where('story')->eq($story->id)->andWhere('deleted')->eq(0)->fetchAll();
- $this->view->linkedMRs = $this->loadModel('mr')->getLinkedMRPairs($story->id, 'story');
- $this->view->linkedCommits = $this->loadModel('repo')->getCommitsByObject($story->id, 'story');
- $this->view->modulePath = $this->tree->getParents($story->module);
- $this->view->storyModule = empty($story->module) ? '' : $this->tree->getById($story->module);
- $this->view->storyProducts = $this->dao->select('id,product')->from(TABLE_STORY)->where('id')->in($linkedStories)->fetchPairs();
- $this->view->twins = !empty($story->twins) ? $this->story->getByList($story->twins) : array();
- $this->view->reviewers = $this->story->getReviewerPairs($story->id, $story->version);
- $this->view->relations = $this->story->getStoryRelation($story->id, $story->type);
- }
- /**
- * Set hidden fields for view. like: hiddenPlan.
- *
- * @param object $product
- * @access protected
- * @return void
- */
- protected function setHiddenFieldsForView($product)
- {
- $this->view->hiddenPlan = false;
- if(empty($product->shadow)) return;
- $projectInfo = $this->dao->select('t2.model, t2.multiple')->from(TABLE_PROJECTPRODUCT)->alias('t1')
- ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
- ->where('t1.product')->eq($product->id)
- ->andWhere('t2.type')->eq('project')
- ->fetch();
- if($projectInfo->model == 'waterfall') $this->view->hiddenPlan = true;
- if($projectInfo->model == 'kanban') $this->view->hiddenPlan = true;
- if(!$projectInfo->multiple) $this->view->hiddenPlan = true;
- }
- /**
- * Convert child ID.
- *
- * @param array $storyIdList
- * @access protected
- * @return array
- */
- protected function convertChildID($storyIdList)
- {
- $storyIdList = array_unique($storyIdList);
- $storyIdList = array_filter($storyIdList);
- foreach($storyIdList as $index => $storyID)
- {
- /* 处理选中的子需求的ID,截取-后的子需求ID。*/
- /* Process selected child story ID. */
- if(strpos((string)$storyID, '-') !== false) $storyIdList[$index] = substr($storyID, strpos($storyID, '-') + 1);
- }
- return $storyIdList;
- }
- /**
- * 处理过滤条件显示内容。
- * Process filter title.
- *
- * @param string $browseType
- * @param int $param
- * @access public
- * @return string
- */
- protected function processFilterTitle($browseType, $param)
- {
- $filter = '';
- $fieldsType = 'execution';
- $searchConfig = $this->session->executionStorysearchParams;
- $searchFields = $this->session->executionStoryForm;
- $featureBar = $this->lang->execution->featureBar['story'];
- if($this->app->tab == 'project' && $this->session->multiple)
- {
- $this->app->loadLang('projectstory');
- $this->app->loadConfig('product');
- $fieldsType = 'product';
- $searchConfig = $this->session->projectstorysearchParams;
- $searchFields = $this->session->projectstoryForm;
- $featureBar = array_merge($this->lang->projectstory->featureBar['story'], $this->lang->projectstory->moreSelects['story']['more']);
- }
- $fieldParams = array();
- if($searchConfig && !empty($searchConfig['params'])) $fieldParams = $searchConfig['params'];
- if($browseType != 'bysearch' && $browseType != 'bymodule' && $browseType != 'byproduct')
- {
- $statusName = zget($featureBar, $browseType, '');
- $filter = sprintf($this->lang->story->report->tpl->feature, $statusName);
- if(!$param) return $filter;
- return $filter . zget($this->lang->search->andor, 'and') . sprintf($this->lang->story->report->tpl->search, $this->config->$fieldsType->search['fields']['module'], '=', zget($fieldParams['module']['values'], $param));
- }
- if($browseType == 'byproduct' && $param)
- {
- $productName = $this->loadModel('product')->getById($param)->name;
- return sprintf($this->lang->story->report->tpl->feature, $productName);
- }
- if($browseType == 'bymodule' && $param)
- {
- if($filter) $filter .= ', ';
- return $filter . sprintf($this->lang->story->report->tpl->search, $this->config->$fieldsType->search['fields']['module'], '=', zget($fieldParams['module']['values'], $param));
- }
- $leftConditions = array();
- $rightConditions = array();
- $fieldNames = $searchConfig['fields'];
- if(!$searchFields) return '';
- $this->app->loadLang('search');
- $groupAndOr = 'and';
- $users = $this->loadModel('user')->getPairs('noletter');
- foreach($searchFields as $index => $field)
- {
- if($index == 6) $groupAndOr = $field['groupAndOr'];
- if(!isset($field['field'])) continue;
- if(isset($field['value']) && $field['value'] === '') continue;
- if(!empty($fieldParams[$field['field']]['values']))
- {
- if($fieldParams[$field['field']]['values'] == 'users') $fieldParams[$field['field']]['values'] = $users;
- $field['value'] = zget($fieldParams[$field['field']]['values'], $field['value']);
- }
- $fieldName = zget($fieldNames, $field['field']);
- $operator = zget($this->lang->search->operators, $field['operator']);
- if($index == 0 || $index == 3) $field['andOr'] = '';
- if($index < 3) $leftConditions[] = zget($this->lang->search->andor, $field['andOr']) . sprintf($this->lang->story->report->tpl->search, $fieldName, $operator, $field['value']);
- elseif($index < 6) $rightConditions[] = zget($this->lang->search->andor, $field['andOr']) . sprintf($this->lang->story->report->tpl->search, $fieldName, $operator, $field['value']);
- }
- if(empty($leftConditions) && empty($rightConditions)) return '';
- if(empty($leftConditions)) return implode('', $rightConditions);
- if(empty($rightConditions)) return implode('', $leftConditions);
- return sprintf($this->lang->story->report->tpl->multi, implode('', $leftConditions), zget($this->lang->search->andor, $groupAndOr), implode('', $rightConditions));
- }
- /**
- * 检查数据是否有变化。
- * Check if data has changed.
- *
- * @param int $storyID
- * @param object $storyData
- * @access public
- * @return bool
- */
- public function checkDataChanged($storyID, $storyData)
- {
- if($this->post->comment) return true;
- if(!empty($storyData->docs)) return true;
- if(!empty($_FILES['files']['name'][0]) || !empty($_POST['renameFiles']) || !empty($_POST['deleteFiles'])) return true;
- $oldStory = $this->story->getByID($storyID);
- if(!empty($oldStory->docs) && empty($_POST['oldDocs'])) return true;
- $changes = common::createChanges($oldStory, $storyData);
- foreach($changes as $index => $change)
- {
- if(in_array($change['field'], array('status', 'version', 'reviewedBy', 'changedBy', 'changedDate', 'reviewedDate', 'docs'))) unset($changes[$index]);
- }
- if(!empty($changes)) return true;
- $reviewers = $this->story->getReviewerPairs($storyID, $oldStory->version);
- $oldStory->reviewer = array_keys($reviewers);
- $diff = array_diff($oldStory->reviewer, $storyData->reviewer) || array_diff($storyData->reviewer, $oldStory->reviewer);
- if($diff) return true;
- $docVersions = !empty($_POST['docVersions']) ? $_POST['docVersions'] : array();
- foreach($docVersions as $docID => $version)
- {
- if(empty($oldStory->docVersions[$docID]) || $oldStory->docVersions[$docID] != $version) return true;
- }
- return false;
- }
- }
|