control.php 161 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588
  1. <?php
  2. /**
  3. * The control file of execution module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  7. * @package execution
  8. * @version $Id: control.php 5094 2013-07-10 08:46:15Z chencongzhi520@gmail.com $
  9. * @link https://www.zentao.net
  10. */
  11. class execution extends control
  12. {
  13. /**
  14. * The global $executions.
  15. *
  16. * @var array
  17. * @access public
  18. */
  19. public $executions;
  20. /**
  21. * The global $objectType.
  22. *
  23. * @var string
  24. * @access public
  25. */
  26. public $objectType = 'execution';
  27. /**
  28. * Construct function, Set executions.
  29. *
  30. * @param string $moduleName
  31. * @param string $methodName
  32. * @access public
  33. * @return void
  34. */
  35. public function __construct($moduleName = '', $methodName = '')
  36. {
  37. parent::__construct($moduleName, $methodName);
  38. $this->loadModel('project');
  39. if($this->app->upgrading || !isset($this->app->user)) return false;
  40. $mode = $this->app->tab == 'execution' ? 'multiple' : '';
  41. if((defined('RUN_MODE') and RUN_MODE == 'api') or $this->viewType == 'json') $mode = '';
  42. $this->executions = $this->execution->getPairs(0, 'all', "nocode,noprefix,{$mode}");
  43. $skipCreateStep = array('computeburn', 'ajaxgetdropmenu', 'executionkanban', 'ajaxgetteammembers', 'all', 'ajaxgetcopyprojectexecutions');
  44. if(in_array($this->methodName, $skipCreateStep)) return false;
  45. if($this->executions || $this->methodName == 'index' || $this->methodName == 'create' || $this->app->getViewType() == 'mhtml') return false;
  46. if($this->app->tab == 'project' && in_array($this->app->rawMethod, array('linkstory', 'importplanstories', 'unlinkstory', 'export'))) return false;
  47. if(($this->methodName == 'story' || $this->methodName == 'task') && $this->app->tab == 'doc') return false;
  48. $this->locate($this->createLink('execution', 'create'));
  49. }
  50. /**
  51. * 执行任务列表页。
  52. * Browse task list for a execution.
  53. *
  54. * @param int $executionID
  55. * @access public
  56. * @return void
  57. */
  58. public function browse($executionID = 0)
  59. {
  60. $this->locate($this->createLink($this->moduleName, 'task', "executionID=$executionID"));
  61. }
  62. /**
  63. * 设置页面公共数据。
  64. * Common actions.
  65. *
  66. * @param int $executionID
  67. * @param string $extra
  68. * @access public
  69. * @return object|false
  70. */
  71. public function commonAction($executionID = 0, $extra = '')
  72. {
  73. /* Get executions and products info. */
  74. $executionID = $this->execution->checkAccess($executionID, $this->executions);
  75. $execution = $this->execution->getById($executionID);
  76. $products = $this->loadModel('product')->getProducts($executionID);
  77. $childExecutions = $this->execution->getChildExecutions($executionID);
  78. $teamMembers = $this->execution->getTeamMembers($executionID);
  79. $actions = $this->loadModel('action')->getList($this->objectType, $executionID);
  80. $project = $this->loadModel('project')->fetchByID($execution->project);
  81. /* Set menu. */
  82. $this->execution->setMenu($executionID, 0, $extra);
  83. /* Assign view data. */
  84. $this->view->projectID = $execution->project;
  85. $this->view->hidden = !empty($project->hasProduct) ? '' : 'hide';
  86. $this->view->executions = $this->executions;
  87. $this->view->execution = $execution;
  88. $this->view->executionID = $executionID;
  89. $this->view->childExecutions = $childExecutions;
  90. $this->view->products = $products;
  91. $this->view->teamMembers = $teamMembers;
  92. $this->view->actions = $actions;
  93. return $execution;
  94. }
  95. /**
  96. * 执行下任务列表页。
  97. * Tasks of a execution.
  98. *
  99. * @param int $executionID
  100. * @param string $status
  101. * @param int $param
  102. * @param string $orderBy
  103. * @param int $recTotal
  104. * @param int $recPerPage
  105. * @param int $pageID
  106. * @access public
  107. * @return void
  108. * @param string $from
  109. * @param int $blockID
  110. */
  111. public function task($executionID = 0, $status = 'unclosed', $param = 0, $orderBy = '', $recTotal = 0, $recPerPage = 100, $pageID = 1, $from = 'execution', $blockID = 0)
  112. {
  113. $this->app->loadLang('doc');
  114. if(($from == 'doc' || $from == 'ai') && empty($this->executions)) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->tips->noExecution));
  115. if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
  116. /* Save to session. */
  117. $this->session->set('taskList', $this->app->getURI(true), 'execution');
  118. /* Set browse type. */
  119. $browseType = strtolower($status);
  120. $execution = $this->commonAction($executionID, $status);
  121. $executionID = $execution->id;
  122. if($execution->type == 'kanban' && $this->config->vision != 'lite' && $this->app->getViewType() != 'json' && $from != 'doc') $this->locate($this->createLink('execution', 'kanban', "executionID=$executionID"));
  123. /* Save the recently five executions visited in the cookie. */
  124. $this->executionZen->setRecentExecutions($executionID);
  125. /* Append id for second sort and process the order by field. */
  126. if(!$orderBy) $orderBy = $this->cookie->executionTaskOrder ? $this->cookie->executionTaskOrder : 'status,id_desc';
  127. $sort = common::appendOrder($orderBy);
  128. $this->executionZen->setTaskPageStorage($executionID, $sort, $browseType, (int)$param);
  129. /* Set queryID, moduleID and productID. */
  130. $queryID = $moduleID = $productID = 0;
  131. if($browseType == 'bysearch') $queryID = (int)$param;
  132. if($browseType == 'bymodule') $moduleID = (int)$param;
  133. if($browseType == 'byproduct') $productID = (int)$param;
  134. if(!in_array($browseType, array('bysearch', 'bymodule', 'byproduct')))
  135. {
  136. $moduleID = $this->cookie->moduleBrowseParam ? $this->cookie->moduleBrowseParam : 0;
  137. $productID = $this->cookie->productBrowseParam ? $this->cookie->productBrowseParam : 0;
  138. }
  139. /* Load pager and get tasks. */
  140. $this->app->loadClass('pager', true);
  141. if($this->app->getViewType() == 'mhtml' || $this->app->getViewType() == 'xhtml') $recPerPage = 10;
  142. $pager = new pager($recTotal, $recPerPage, $pageID);
  143. $tasks = $this->execution->getTasks((int)$productID, $executionID, $this->executions, $browseType, $queryID, (int)$moduleID, $sort, $pager);
  144. /* team member pairs. */
  145. $memberPairs = array();
  146. foreach($this->view->teamMembers as $key => $member) $memberPairs[$key] = $member->realname;
  147. $memberPairs = $this->loadModel('user')->setCurrentUserFirst($memberPairs);
  148. if($this->config->edition != 'open') $taskRelatedObject = $this->loadModel('custom')->getRelatedObjectList(array_keys($tasks), 'task', 'byRelation', true);
  149. /* Append branches to task. */
  150. $this->loadModel('task');
  151. $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($this->view->products));
  152. foreach($tasks as $task)
  153. {
  154. $task->name = htmlspecialchars_decode($task->name);
  155. if($task->mode == 'multi' && strpos('done,closed,cancel', $task->status) === false)
  156. {
  157. $task->assignedTo = '';
  158. $taskTeam = $this->task->getTeamByTask($task->id);
  159. foreach($taskTeam as $teamMember)
  160. {
  161. if($this->app->user->account == $teamMember->account && $teamMember->status != 'done')
  162. {
  163. $task->assignedTo = $this->app->user->account;
  164. break;
  165. }
  166. }
  167. }
  168. if(isset($branchGroups[$task->product][$task->branch])) $task->branch = $branchGroups[$task->product][$task->branch];
  169. if($task->needConfirm) $task->status = $task->rawStatus = 'changed';
  170. if($this->config->edition != 'open') $task->relatedObject = zget($taskRelatedObject, $task->id, 0);
  171. }
  172. $showAllModule = empty($this->config->execution->task->allModule) ? '' : 'allModule';
  173. $showModule = !empty($this->config->execution->task->showModule) ? $this->config->execution->task->showModule : '';
  174. /* Build the search form. */
  175. $modules = $this->loadModel('tree')->getTaskOptionMenu($executionID, 0, $showModule);
  176. $actionURL = $this->createLink('execution', 'task', "executionID=$executionID&status=bySearch&param=myQueryID&orderBy=$orderBy&recTotal=0&recPerPage=100&pageID=1&from=$from&blockID=$blockID");
  177. $this->execution->buildTaskSearchForm($executionID, $this->executions, $queryID, $actionURL);
  178. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->task;
  179. $this->view->tasks = $tasks;
  180. $this->view->pager = $pager;
  181. $this->view->orderBy = $orderBy;
  182. $this->view->browseType = $browseType;
  183. $this->view->status = $status;
  184. $this->view->param = $param;
  185. $this->view->moduleID = $moduleID;
  186. $this->view->modules = $modules;
  187. $this->view->modulePairs = $showModule ? $this->tree->getModulePairs($executionID, 'task', $showModule) : array();
  188. $this->view->moduleTree = $this->tree->getTaskTreeMenu($executionID, (int)$productID, 0, array('treeModel', 'createTaskLink'), $showAllModule);
  189. $this->view->memberPairs = $memberPairs;
  190. $this->view->execution = $execution;
  191. $this->view->executionID = $executionID;
  192. $this->view->productID = $productID;
  193. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  194. $this->view->features = $this->execution->getExecutionFeatures($execution);
  195. $this->view->from = $from;
  196. $this->view->blockID = $blockID;
  197. $this->view->idList = '';
  198. if($from === 'doc')
  199. {
  200. $docBlock = $this->loadModel('doc')->getDocBlock($blockID);
  201. $this->view->docBlock = $docBlock;
  202. if($docBlock)
  203. {
  204. $content = json_decode($docBlock->content, true);
  205. if(isset($content['idList'])) $this->view->idList = $content['idList'];
  206. }
  207. }
  208. $this->display();
  209. }
  210. /**
  211. * 任务分组视图。
  212. * Browse tasks in group.
  213. *
  214. * @param int $executionID
  215. * @param string $groupBy story|status|pri|assignedTo|finishedBy|closedBy|type
  216. * @param string $filter
  217. * @access public
  218. * @return void
  219. */
  220. public function grouptask($executionID = 0, $groupBy = 'story', $filter = '')
  221. {
  222. $execution = $this->commonAction($executionID);
  223. $executionID = $execution->id;
  224. /* Execution of the ops type don't show group by story. */
  225. if(empty($groupBy))$groupBy = 'story';
  226. if($groupBy == 'story' && $execution->lifetime == 'ops')
  227. {
  228. $groupBy = 'status';
  229. unset($this->lang->execution->groups['story']);
  230. }
  231. $sort = common::appendOrder($groupBy);
  232. $tasks = $this->loadModel('task')->getExecutionTasks($executionID, 0, 'all', array(), $sort);
  233. $groupBy = str_replace('`', '', $groupBy);
  234. $groupTasks = array();
  235. $allCount = 0;
  236. foreach($tasks as $task)
  237. {
  238. if($task->mode == 'multi') $task->assignedToRealName = $this->lang->task->team;
  239. $groupTasks[] = $task;
  240. $allCount ++;
  241. if(isset($task->children))
  242. {
  243. foreach($task->children as $child)
  244. {
  245. $groupTasks[] = $child;
  246. $allCount ++;
  247. }
  248. unset($task->children);
  249. }
  250. }
  251. /* Get users and build task group data. */
  252. $users = $this->loadModel('user')->getPairs('noletter');
  253. $tasks = $groupTasks;
  254. list($groupTasks, $groupByList) = $this->executionZen->buildGroupTasks($groupBy, $groupTasks, array('' => '') + $users);
  255. /* Remove task by filter and group. */
  256. $filter = (empty($filter) && isset($this->lang->execution->groupFilter[$groupBy])) ? key($this->lang->execution->groupFilter[$groupBy]) : $filter;
  257. list($groupTasks, $allCount) = $this->executionZen->filterGroupTasks($groupTasks, $groupBy, $filter, $allCount, $tasks);
  258. if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
  259. /* Assign. */
  260. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->task;
  261. $this->view->members = $this->execution->getTeamMembers($executionID);
  262. $this->view->tasks = $groupTasks;
  263. $this->view->groupByList = $groupByList;
  264. $this->view->browseType = 'group';
  265. $this->view->groupBy = $groupBy;
  266. $this->view->orderBy = $groupBy;
  267. $this->view->executionID = $executionID;
  268. $this->view->users = $users;
  269. $this->view->features = $this->execution->getExecutionFeatures($execution);
  270. $this->view->filter = $filter;
  271. $this->view->allCount = $allCount;
  272. $this->view->execution = $execution;
  273. $this->view->isLimited = !$this->app->user->admin && strpos(",{$this->session->limitedExecutions},", ",{$executionID},") !== false;
  274. $this->display();
  275. }
  276. /**
  277. * Import tasks undoned from other executions.
  278. *
  279. * @param int $executionID
  280. * @param int $fromExecution
  281. * @param string $orderBy
  282. * @param int $recPerPage
  283. * @param int $pageID
  284. * @access public
  285. * @return void
  286. * @param int $toExecution
  287. */
  288. public function importTask($toExecution, $fromExecution = 0, $orderBy = 'id_desc', $recPerPage = 20, $pageID = 1)
  289. {
  290. if(!empty($_POST))
  291. {
  292. $taskIdList = form::data($this->config->execution->form->importTask)->get('taskIdList');
  293. $dateExceed = $this->execution->importTask($toExecution, $taskIdList);
  294. $result = array('load' => true);
  295. if($dateExceed) $result['message'] = sprintf($this->lang->task->error->dateExceed, implode(',', $dateExceed));
  296. return $this->sendSuccess($result);
  297. }
  298. $execution = $this->commonAction($toExecution);
  299. $toExecution = $execution->id;
  300. $project = $this->loadModel('project')->getByID($execution->project);
  301. $branches = $this->execution->getBranches($toExecution);
  302. $tasks = $this->execution->getTasks2Imported($toExecution, $branches, $orderBy);
  303. $executions = $this->execution->getToImport(array_keys($tasks), $execution->type, $project->model);
  304. unset($executions[$toExecution]);
  305. $tasks2Imported = array();
  306. if($fromExecution == 0)
  307. {
  308. foreach($executions as $executionID => $executionName) $tasks2Imported = array_merge($tasks2Imported, $tasks[$executionID]);
  309. }
  310. else
  311. {
  312. $tasks2Imported = zget($tasks, $fromExecution, array());
  313. }
  314. /* Pager. */
  315. $this->app->loadClass('pager', true);
  316. $pager = new pager(count($tasks2Imported), $recPerPage, $pageID);
  317. $tasks2ImportedList = array_chunk($tasks2Imported, $pager->recPerPage, true);
  318. if(!empty($tasks2ImportedList)) $tasks2ImportedList = isset($tasks2ImportedList[$pageID - 1]) ? $tasks2ImportedList[$pageID - 1] : current($tasks2ImportedList);
  319. $tasks2ImportedList = $this->loadModel('task')->processTasks($tasks2ImportedList);
  320. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->importTask;
  321. $this->view->pager = $pager;
  322. $this->view->orderBy = $orderBy;
  323. $this->view->executions = $executions;
  324. $this->view->fromExecution = $fromExecution;
  325. $this->view->tasks2Imported = $tasks2ImportedList;
  326. $this->view->memberPairs = $this->loadModel('user')->getPairs('noletter|pofirst');
  327. $this->display();
  328. }
  329. /**
  330. * 导入Bug。
  331. * Import from Bug.
  332. *
  333. * @param int $executionID
  334. * @param string $browseType
  335. * @param int $param
  336. * @param int $recTotal
  337. * @param int $recPerPage
  338. * @param int $pageID
  339. * @access public
  340. * @return void
  341. */
  342. public function importBug($executionID = 0, $browseType = 'all', $param = 0, $recTotal = 0, $recPerPage = 30, $pageID = 1)
  343. {
  344. $this->session->set('bugList', $this->app->getURI(true), 'execution');
  345. $this->execution->setMenu($executionID);
  346. $execution = $this->execution->getByID($executionID);
  347. if(!empty($_POST))
  348. {
  349. $postData = form::batchData($this->config->execution->form->importBug)->get();
  350. $tasks = $this->executionZen->buildTasksForImportBug($execution, $postData);
  351. if(dao::isError()) return $this->sendError(dao::getError());
  352. $this->execution->importBug($tasks);
  353. if(dao::isError()) return $this->sendError(dao::getError());
  354. return $this->sendSuccess(array('load' => true, 'closeModal' => true));
  355. }
  356. /* Get users, products, executions, project and projects.*/
  357. $users = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
  358. $products = $this->loadModel('product')->getProductPairsByProject($executionID);
  359. $executions = !empty($products) ? $this->execution->getPairsByProduct(array_keys($products)) : array($executionID => $execution->name);
  360. $project = $this->loadModel('project')->getByID($execution->project);
  361. !empty($products) ? $projects = $this->product->getProjectPairsByProductIDList(array_keys($products)) : $projects[$project->id] = $project->name;
  362. /* Set browseType, productID, moduleID and queryID. */
  363. $browseType = strtolower($browseType);
  364. $queryID = ($browseType == 'bysearch') ? (int)$param : 0;
  365. /* Load pager. */
  366. $this->app->loadClass('pager', true);
  367. $pager = new pager($recTotal, $recPerPage, $pageID);
  368. /* Get bugs.*/
  369. $bugs = $this->executionZen->getImportBugs($executionID, array_keys($products), $browseType, $queryID, $pager);
  370. /* Build the search form. */
  371. $this->executionZen->buildImportBugSearchForm($execution, $queryID, $products, $executions, $projects);
  372. if($this->app->tab == 'project') $this->view->projectID = $execution->project;
  373. /* Assign. */
  374. $this->view->title = $executions[$executionID] . $this->lang->hyphen . $this->lang->execution->importBug;
  375. $this->view->pager = $pager;
  376. $this->view->bugs = $bugs;
  377. $this->view->browseType = $browseType;
  378. $this->view->param = $param;
  379. $this->view->users = $users;
  380. $this->view->execution = $execution;
  381. $this->view->executionID = $executionID;
  382. $this->display();
  383. }
  384. /**
  385. * 需求列表。
  386. * Browse stories of a execution.
  387. *
  388. * @param int $executionID
  389. * @param string $storyType story|requirement
  390. * @param string $orderBy
  391. * @param string $type all|byModule|byProduct|byBranch|bySearch
  392. * @param int $param
  393. * @param int $recTotal
  394. * @param int $recPerPage
  395. * @param int $pageID
  396. * @access public
  397. * @return void
  398. * @param string $from
  399. * @param int $blockID
  400. */
  401. public function story($executionID = 0, $storyType = 'story', $orderBy = 'order_desc', $type = 'all', $param = 0, $recTotal = 0, $recPerPage = 50, $pageID = 1, $from = 'execution', $blockID = 0)
  402. {
  403. /* Load these models. */
  404. $this->loadModel('story');
  405. $this->loadModel('requirement');
  406. $this->loadModel('epic');
  407. $this->loadModel('product');
  408. $this->app->loadLang('doc');
  409. if(($from == 'doc' || $from == 'ai') && empty($this->executions)) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->tips->noExecution));
  410. /* Change for requirement story title. */
  411. $this->lang->story->linkStory = str_replace($this->lang->URCommon, $this->lang->SRCommon, $this->lang->story->linkStory);
  412. if($storyType == 'requirement')
  413. {
  414. $this->story->replaceURLang($storyType);
  415. $this->config->product->search['fields']['title'] = $this->lang->story->title;
  416. unset($this->config->product->search['fields']['plan']);
  417. unset($this->config->product->search['params']['plan']);
  418. unset($this->config->product->search['fields']['stage']);
  419. unset($this->config->product->search['params']['stage']);
  420. }
  421. /* Process the order by field. */
  422. if(!$orderBy) $orderBy = $this->cookie->executionStoryOrder ? $this->cookie->executionStoryOrder : 'pri';
  423. $param = (int)$param;
  424. $type = strtolower($type);
  425. $productID = $this->executionZen->setStorageForStory((string)$executionID, $type, (string)$param, $orderBy);
  426. /* Append id for second sort. */
  427. $sort = common::appendOrder($orderBy);
  428. if(strpos($sort, 'pri_') !== false) $sort = str_replace('pri_', 'priOrder_', $sort);
  429. $execution = $this->commonAction($executionID);
  430. $executionID = $execution->id;
  431. $project = $this->loadModel('project')->getByID($execution->project);
  432. /* Build the search form. */
  433. $products = $this->product->getProducts($executionID);
  434. $actionURL = $this->createLink('execution', 'story', "executionID=$executionID&storyType=$storyType&orderBy=$orderBy&type=bySearch&queryID=myQueryID&recTotal=0&recPerPage=&pageID=1&from=$from&blockID=$blockID");
  435. $this->executionZen->buildStorySearchForm($execution, $productID, $products, $type == 'bysearch' ? $param : 0, $actionURL);
  436. /* Load pager. */
  437. $this->app->loadClass('pager', true);
  438. if($this->app->getViewType() == 'xhtml') $recPerPage = 10;
  439. $pager = new pager($recTotal, $recPerPage, $pageID);
  440. $stories = $this->story->getExecutionStories($executionID, 0, $sort, $type, (string)$param, $project->storyType ?? 'story', '', $pager);
  441. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
  442. if(!empty($stories)) $stories = $this->story->mergeReviewer($stories);
  443. if($this->config->edition == 'ipd') $stories = $this->loadModel('story')->getAffectObject($stories, 'story');
  444. $this->executionZen->assignCountForStory($executionID, $stories, $storyType);
  445. $this->executionZen->assignRelationForStory($execution, $products, $productID, $type, $storyType, $param, $orderBy, $pager);
  446. $this->view->from = $from;
  447. $this->view->blockID = $blockID;
  448. $this->view->idList = '';
  449. if($from === 'doc')
  450. {
  451. $docBlock = $this->loadModel('doc')->getDocBlock($blockID);
  452. $this->view->docBlock = $docBlock;
  453. if($docBlock)
  454. {
  455. $content = json_decode($docBlock->content, true);
  456. if(isset($content['idList'])) $this->view->idList = $content['idList'];
  457. }
  458. $this->view->executions = $this->execution->getPairs();
  459. }
  460. $this->view->productID = $productID;
  461. $this->view->project = $project;
  462. $this->view->linkedProductCount = count($products);
  463. $this->view->hasFrozenStories = $this->project->hasFrozenObject($project->id, 'SRS');
  464. $this->display();
  465. }
  466. /**
  467. * 查看需求详情。
  468. * View a story.
  469. *
  470. * @param int $storyID
  471. * @param int $executionID
  472. * @access public
  473. * @return void
  474. */
  475. public function storyView($storyID, $executionID = 0)
  476. {
  477. $this->session->set('productList', $this->app->getURI(true), 'product');
  478. $story = $this->loadModel('story')->getByID($storyID);
  479. echo $this->fetch('story', 'view', "storyID=$storyID&version=$story->version&param=" . ($executionID ? $executionID : $this->session->execution) . "&storyType={$story->type}");
  480. }
  481. /**
  482. * Bug列表。
  483. * Browse bugs of a execution.
  484. *
  485. * @param int $executionID
  486. * @param int $productID
  487. * @param int $branchID
  488. * @param string $orderBy
  489. * @param string $build
  490. * @param string $type
  491. * @param int $param
  492. * @param int $recTotal
  493. * @param int $recPerPage
  494. * @param int $pageID
  495. * @access public
  496. * @return void
  497. * @param string $branch
  498. */
  499. public function bug($executionID = 0, $productID = 0, $branch = 'all', $orderBy = 'status,id_desc', $build = '', $type = 'all', $param = 0, $recTotal = 0, $recPerPage = 20, $pageID = 1)
  500. {
  501. /* Save session and load model. */
  502. $this->loadModel('bug');
  503. $this->session->set('bugList', $this->app->getURI(true), 'execution');
  504. $this->session->set('buildList', $this->app->getURI(true), 'execution');
  505. $type = strtolower($type);
  506. $execution = $this->commonAction($executionID);
  507. $project = $this->loadModel('project')->getByID($execution->project);
  508. $executionID = $execution->id;
  509. $products = $this->product->getProducts($execution->id);
  510. $param = in_array($type, array('bysearch', 'bymodule')) ? (int)$param : 0;
  511. if(count($products) === 1) $productID = current($products)->id;
  512. /* Set the product drop-down and search fields. */
  513. $productOption = array();
  514. $branchOption = array();
  515. $showBranch = $this->loadModel('branch')->showBranch($productID);
  516. if($execution->hasProduct)
  517. {
  518. list($productOption, $branchOption) = $this->executionZen->buildProductSwitcher($executionID, $productID, $products);
  519. unset($this->config->bug->search['fields']['product']);
  520. unset($this->config->bug->search['params']['product']);
  521. if($project->model != 'scrum')
  522. {
  523. unset($this->config->bug->search['fields']['plan']);
  524. unset($this->config->bug->search['params']['plan']);
  525. }
  526. }
  527. /* Load pager and get bugs, user. */
  528. $this->app->loadClass('pager', true);
  529. if($this->app->getViewType() == 'xhtml') $recPerPage = 10;
  530. $pager = new pager($recTotal, $recPerPage, $pageID);
  531. $sort = common::appendOrder($orderBy);
  532. $bugs = $this->bug->getExecutionBugs($executionID, $productID, $branch, $build, $type, $param, $sort, '', $pager);
  533. $bugs = $this->bug->batchAppendDelayedDays($bugs);
  534. /* Build the search form. */
  535. $actionURL = $this->createLink('execution', 'bug', "executionID=$executionID&productID=$productID&branch=$branch&orderBy=$orderBy&build=$build&type=bysearch&queryID=myQueryID");
  536. $this->execution->buildBugSearchForm($products, $param, $actionURL);
  537. $bugs = $this->bug->processBuildForBugs($bugs);
  538. $this->executionZen->assignBugVars($execution, $project, $productID, $branch, $products, $orderBy, $type, $param, $build, $bugs, $pager);
  539. $storyIdList = $taskIdList = array();
  540. foreach($bugs as $bug)
  541. {
  542. if($bug->story) $storyIdList[$bug->story] = $bug->story;
  543. if($bug->task) $taskIdList[$bug->task] = $bug->task;
  544. if($bug->toTask) $taskIdList[$bug->toTask] = $bug->toTask;
  545. }
  546. $this->view->showBranch = $showBranch;
  547. $this->view->productOption = $productOption;
  548. $this->view->branchOption = $branchOption;
  549. $this->view->plans = $this->loadModel('productplan')->getForProducts(array_keys($products));
  550. $this->view->tasks = $this->loadModel('task')->getPairsByIdList($taskIdList);
  551. $this->view->stories = $this->loadModel('story')->getPairsByList($storyIdList);
  552. $this->view->moduleID = $type == 'bymodule' ? $param : 0;
  553. $this->view->switcherParams = "executionID={$executionID}&productID={$productID}&currentMethod=bug";
  554. $this->view->switcherText = isset($products[$productID]) ? $products[$productID]->name : $this->lang->product->all;
  555. if(empty($project->hasProduct)) $this->config->excludeSwitcherList[] = 'execution-bug';
  556. $this->display();
  557. }
  558. /**
  559. * 用例列表。
  560. * Execution case list.
  561. *
  562. * @param int $executionID
  563. * @param int $productID
  564. * @param string $branchID
  565. * @param string $type
  566. * @param int $moduleID
  567. * @param string $orderBy
  568. * @param int $recTotal
  569. * @param int $recPerPage
  570. * @param int $pageID
  571. * @access public
  572. * @return void
  573. * @param int $param
  574. */
  575. public function testcase($executionID = 0, $productID = 0, $branchID = 'all', $type = 'all', $param = 0, $moduleID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  576. {
  577. $this->commonAction($executionID);
  578. $uri = $this->app->getURI(true);
  579. $this->session->set('caseList', $uri, 'execution');
  580. $this->session->set('bugList', $uri, 'execution');
  581. $products = $this->product->getProducts($executionID);
  582. if(count($products) == 1) $productID = key($products);
  583. $execution = $this->execution->getByID($executionID);
  584. $productOption = array();
  585. $branchOption = array();
  586. if($execution->hasProduct)
  587. {
  588. list($productOption, $branchOption) = $this->executionZen->buildProductSwitcher($executionID, $productID, $products);
  589. }
  590. /* Load pager. */
  591. $this->app->loadClass('pager', true);
  592. $pager = pager::init($recTotal, $recPerPage, $pageID);
  593. unset($this->config->testcase->dtable->fieldList['title']['nestedToggle']);
  594. if($productID && $products[$productID]->type == 'normal') unset($this->config->testcase->dtable->fieldList['branch']);
  595. /* Build the search form. */
  596. $actionURL = $this->createLink('execution', 'testcase', "executionID=$executionID&productID=$productID&branchID=$branchID&type=bysearch&queryID=myQueryID&moduleID=0&orderBy=$orderBy");
  597. $this->execution->buildCaseSearchForm($products, $param, $actionURL, $executionID);
  598. $this->executionZen->assignTestcaseVars($executionID, $productID, $branchID, $moduleID, $param, $orderBy, $type, $pager);
  599. $this->view->execution = $execution;
  600. $this->view->productOption = $productOption;
  601. $this->view->branchOption = $branchOption;
  602. $this->view->switcherParams = "executionID={$executionID}&productID={$productID}&currentMethod=testcase";
  603. $this->view->switcherText = isset($products[$productID]) ? $products[$productID]->name : $this->lang->product->all;
  604. $this->view->switcherObjectID = $productID;
  605. if(empty($execution->hasProduct)) $this->config->excludeSwitcherList[] = 'execution-testcase';
  606. $this->display();
  607. }
  608. /**
  609. * 测试报告列表。
  610. * List of test reports for the execution.
  611. *
  612. * @param int $executionID
  613. * @param string $objectType project|execution|product
  614. * @param string $extra
  615. * @param string $orderBy
  616. * @param int $recTotal
  617. * @param int $recPerPage
  618. * @param int $pageID
  619. * @access public
  620. * @return void
  621. */
  622. public function testreport($executionID = 0, $objectType = 'execution', $extra = '', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  623. {
  624. echo $this->fetch('testreport', 'browse', "objectID=$executionID&objectType=$objectType&extra=$extra&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID");
  625. }
  626. /**
  627. * 版本列表。
  628. * Browse builds of a execution.
  629. *
  630. * @param int $executionID
  631. * @param string $type all|product|bysearch
  632. * @param int $param
  633. * @param string $orderBy
  634. * @param int $recTotal
  635. * @param int $recPerPage
  636. * @param int $pageID
  637. * @access public
  638. * @return void
  639. */
  640. public function build($executionID = 0, $type = 'all', $param = 0, $orderBy = 't1.date_desc,t1.id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  641. {
  642. $execution = $this->commonAction($executionID);
  643. $executionID = (int)$execution->id;
  644. /* Get products' list. */
  645. $products = $this->product->getProducts($executionID, 'all', '', false);
  646. /* Build the search form. */
  647. $type = strtolower($type);
  648. $this->project->buildProjectBuildSearchForm($products, $type == 'bysearch' ? (int)$param : 0, $executionID, $param, 'execution');
  649. /* Load pager. */
  650. $this->app->loadClass('pager', true);
  651. $pager = pager::init($recTotal, $recPerPage, $pageID);
  652. /* Get builds. */
  653. $this->loadModel('build');
  654. if($type == 'bysearch')
  655. {
  656. $builds = $this->loadModel('build')->getExecutionBuildsBySearch($executionID, (int)$param, $pager);
  657. }
  658. else
  659. {
  660. $builds = $this->loadModel('build')->getExecutionBuilds($executionID, $type, (string)$param, $orderBy, $pager);
  661. }
  662. /* Set view data. */
  663. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->build;
  664. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  665. $this->view->builds = $this->executionZen->processBuildListData($builds, $executionID);
  666. $this->view->product = $type == 'product' ? $param : '';
  667. $this->view->products = $products;
  668. $this->view->system = $this->loadModel('system')->getPairs();
  669. $this->view->type = $type;
  670. $this->view->param = $param;
  671. $this->view->orderBy = $orderBy;
  672. $this->view->pager = $pager;
  673. $this->display();
  674. }
  675. /**
  676. * 测试单列表页面。
  677. * Browse test tasks of execution.
  678. *
  679. * @param int $executionID
  680. * @param int $productID
  681. * @param string $orderBy
  682. * @param int $recTotal
  683. * @param int $recPerPage
  684. * @param int $pageID
  685. * @access public
  686. * @return void
  687. */
  688. public function testtask($executionID = 0, $productID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  689. {
  690. $this->loadModel('testtask');
  691. $this->app->loadLang('testreport');
  692. /* Save session. */
  693. $this->session->set('testtaskList', $this->app->getURI(true), 'execution');
  694. $this->session->set('buildList', $this->app->getURI(true), 'execution');
  695. $execution = $this->commonAction($executionID);
  696. $executionID = $execution->id;
  697. $sort = str_replace('taskID', 'id', $orderBy);
  698. /* Load pager. */
  699. $this->app->loadClass('pager', true);
  700. $pager = pager::init($recTotal, $recPerPage, $pageID);
  701. $tasks = $this->testtask->getExecutionTasks($executionID, $productID, 'execution', 'product_asc,' . $sort, $pager);
  702. $this->executionZen->assignTesttaskVars($tasks);
  703. $this->view->title = $this->executions[$executionID] . $this->lang->hyphen . $this->lang->testtask->common;
  704. $this->view->execution = $execution;
  705. $this->view->project = $this->loadModel('project')->getByID($execution->project);
  706. $this->view->executionID = $executionID;
  707. $this->view->productID = $productID;
  708. $this->view->executionName = $this->executions[$executionID];
  709. $this->view->pager = $pager;
  710. $this->view->orderBy = $orderBy;
  711. $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
  712. $this->view->products = $this->loadModel('product')->getProducts($executionID, 'all', '', false);
  713. $this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
  714. $this->display();
  715. }
  716. /**
  717. * 燃尽图。
  718. * Browse burndown chart of a execution.
  719. *
  720. * @param int $executionID
  721. * @param string $type noweekend|withweekend
  722. * @param string $interval
  723. * @param string $burnBy left|estimate|storyPoint
  724. * @access public
  725. * @return void
  726. */
  727. public function burn($executionID = 0, $type = 'noweekend', $interval = '', $burnBy = 'left')
  728. {
  729. $execution = $this->commonAction($executionID);
  730. $executionID = $execution->id;
  731. /* Determine whether to display delay data. */
  732. if(((strpos('closed,suspended', $execution->status) === false && helper::today() > $execution->end)
  733. || ($execution->status == 'closed' && substr($execution->closedDate, 0, 10) > $execution->end)
  734. || ($execution->status == 'suspended' && $execution->suspendedDate > $execution->end))
  735. && strpos($type, 'delay') === false)
  736. $type .= ',withdelay';
  737. /* Get burn date list and interval. */
  738. $deadline = $execution->status == 'closed' ? substr($execution->closedDate, 0, 10) : $execution->suspendedDate;
  739. $deadline = strpos('closed,suspended', $execution->status) === false ? helper::today() : $deadline;
  740. $endDate = strpos($type, 'withdelay') !== false ? $deadline : $execution->end;
  741. list($dateList, $interval) = $this->execution->getDateList($execution->begin, $endDate, $type, $interval, 'Y-m-d', $execution->end);
  742. $executionEnd = strpos($type, 'withdelay') !== false ? $execution->end : '';
  743. $burnBy = $this->cookie->burnBy ? $this->cookie->burnBy : $burnBy;
  744. $chartData = $this->execution->buildBurnData($executionID, $dateList, $burnBy, $executionEnd);
  745. $allDateList = date::getDateList($execution->begin, $endDate, 'Y-m-d', $type, $this->config->execution->weekend);
  746. $dayList = array_fill(1, (int)floor(count($allDateList) / $this->config->execution->maxBurnDay) + 5, '');
  747. foreach($dayList as $key => $val) $dayList[$key] = $this->lang->execution->interval . ($key + 1) . $this->lang->day;
  748. unset($this->lang->TRActions);
  749. /* Shows the variables needed to burn page. */
  750. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->burn;
  751. $this->view->tabID = 'burn';
  752. $this->view->burnBy = $burnBy;
  753. $this->view->executionID = $executionID;
  754. $this->view->executionName = $execution->name;
  755. $this->view->type = $type;
  756. $this->view->interval = $interval;
  757. $this->view->chartData = $chartData;
  758. $this->view->dayList = array('full' => $this->lang->execution->interval . '1' . $this->lang->day) + $dayList;
  759. $this->display();
  760. }
  761. /**
  762. * 更新燃尽图数据。
  763. * Compute burndown data.
  764. *
  765. * @param string $reload
  766. * @access public
  767. * @return void
  768. */
  769. public function computeBurn($reload = 'no')
  770. {
  771. $burns = $this->execution->computeBurn();
  772. if($reload == 'yes') return $this->send(array('load' => true, 'result' => 'success'));
  773. return print(json_encode($burns));
  774. }
  775. /**
  776. * 累计流图。
  777. * Cumulative flow diagram.
  778. *
  779. * @param int $executionID
  780. * @param string $type story|bug|task
  781. * @param string $withWeekend
  782. * @param string $begin
  783. * @param string $end
  784. * @access public
  785. * @return void
  786. */
  787. public function cfd($executionID = 0, $type = 'story', $withWeekend = 'false', $begin = '', $end = '')
  788. {
  789. $execution = $this->commonAction($executionID);
  790. $executionID = $execution->id;
  791. $this->app->loadClass('date');
  792. $minDate = !helper::isZeroDate($execution->openedDate) ? date('Y-m-d', strtotime($execution->openedDate)) : date('Y-m-d', strtotime($execution->begin));
  793. $maxDate = !helper::isZeroDate($execution->closedDate) ? date('Y-m-d', strtotime($execution->closedDate)) : helper::today();
  794. if($execution->lifetime == 'ops' or in_array($execution->attribute, array('request', 'review'))) $type = 'task';
  795. if(!empty($_POST))
  796. {
  797. $begin = htmlspecialchars($this->post->begin, ENT_QUOTES);
  798. $end = htmlspecialchars($this->post->end, ENT_QUOTES);
  799. $this->executionZen->checkCFDDate($begin, $end, $minDate, $maxDate);
  800. if(dao::isError()) return $this->sendError(dao::getError());
  801. $this->execution->computeCFD($executionID);
  802. $this->execution->updateCFDData($executionID, $begin);
  803. return $this->send(array('result' => 'success', 'locate' => $this->createLink('execution', 'cfd', "executionID=$executionID&type=$type&withWeekend=$withWeekend&begin=" . helper::safe64Encode(urlencode($begin)) . "&end=" . helper::safe64Encode(urlencode($end)))));
  804. }
  805. if($begin && $end)
  806. {
  807. $begin = urldecode(helper::safe64Decode($begin));
  808. $end = urldecode(helper::safe64Decode($end));
  809. }
  810. else
  811. {
  812. list($begin, $end) = $this->execution->getBeginEnd4CFD($execution);
  813. }
  814. $dateList = date::getDateList($begin, $end, 'Y-m-d', $withWeekend == 'false'? 'noweekend' : '');
  815. $chartData = $this->execution->buildCFDData($executionID, $dateList, $type);
  816. if(isset($chartData['line'])) $chartData['line'] = array_reverse($chartData['line']);
  817. $this->view->title = $this->lang->execution->CFD;
  818. $this->view->type = $type;
  819. $this->view->execution = $execution;
  820. $this->view->withWeekend = $withWeekend;
  821. $this->view->executionName = $execution->name;
  822. $this->view->executionID = $executionID;
  823. $this->view->chartData = $chartData;
  824. $this->view->features = $this->execution->getExecutionFeatures($execution);
  825. $this->view->begin = $begin;
  826. $this->view->end = $end;
  827. $this->view->minDate = $minDate;
  828. $this->view->maxDate = $maxDate;
  829. $this->display();
  830. }
  831. /**
  832. * 计算累计流图数据。
  833. * Compute cfd data.
  834. *
  835. * @param string $reload
  836. * @param int $executionID
  837. * @access public
  838. * @return void
  839. */
  840. public function computeCFD($reload = 'no', $executionID = 0)
  841. {
  842. $this->execution->computeCFD($executionID);
  843. if($reload == 'yes') return $this->sendSuccess(array('load' => true));
  844. }
  845. /**
  846. * 修改燃尽图的首天工时
  847. * Fix burn for first date.
  848. *
  849. * @param int $executionID
  850. * @access public
  851. * @return void
  852. */
  853. public function fixFirst($executionID)
  854. {
  855. $execution = $this->execution->getByID($executionID);
  856. if($_POST)
  857. {
  858. if(!preg_match("/^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|[1-9]\d*$/", (string)$this->post->estimate) || $this->post->estimate <= 0)
  859. {
  860. dao::$errors['estimate'] = sprintf($this->lang->execution->errorFloat, $this->lang->execution->estimate);
  861. return $this->sendError(dao::getError());
  862. }
  863. $burn = $this->execution->getBurnByExecution($executionID, $execution->begin, 0);
  864. $left = empty($burn) ? 0 : $burn->left;
  865. $withLeft = $this->post->withLeft ? $this->post->withLeft : 0;
  866. if($withLeft) $left = $this->post->estimate;
  867. $burnData = form::data($this->config->execution->form->fixfirst)
  868. ->add('task', 0)
  869. ->add('execution', $executionID)
  870. ->add('date', $execution->begin)
  871. ->add('left', $left)
  872. ->add('consumed', empty($burn) ? 0 : $burn->consumed)
  873. ->get();
  874. if(!$burnData->left) $burnData->left = 0;
  875. if(is_numeric($burnData->estimate)) $this->execution->fixFirst($burnData);
  876. return $this->send(array('result' => 'success', 'load' => true, 'closeModal' => true));
  877. }
  878. $this->view->firstBurn = $this->execution->getBurnByExecution($executionID, $execution->begin);
  879. $this->view->execution = $execution;
  880. $this->display();
  881. }
  882. /**
  883. * 执行团队列表。
  884. * Browse team of a execution.
  885. *
  886. * @param int $executionID
  887. * @access public
  888. * @return void
  889. */
  890. public function team($executionID = 0)
  891. {
  892. $execution = $this->commonAction($executionID);
  893. $deptID = $this->app->user->admin ? 0 : $this->app->user->dept;
  894. $teamMembers = array();
  895. foreach($this->view->teamMembers as $member)
  896. {
  897. $member->days = $member->days . $this->lang->execution->day;
  898. $member->hours = helper::formatHours($member->hours) . $this->lang->execution->workHour;
  899. $member->total = helper::formatHours($member->totalHours) . $this->lang->execution->workHour;
  900. $member->actions = array();
  901. if(common::hasPriv('execution', 'unlinkMember', $member) && common::canModify('execution', $execution)) $member->actions = array('unlink');
  902. $teamMembers[] = $member;
  903. }
  904. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->team;
  905. $this->view->deptUsers = $this->loadModel('dept')->getDeptUserPairs($deptID, 'id');
  906. $this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
  907. $this->view->recTotal = count($this->view->teamMembers);
  908. $this->view->teamMembers = $teamMembers;
  909. $this->display();
  910. }
  911. /**
  912. * Create a execution.
  913. *
  914. * @param int $projectID
  915. * @param int $executionID
  916. * @param int $copyExecutionID
  917. * @param int $planID
  918. * @param string $confirm
  919. * @param int $productID
  920. * @param string $extra
  921. *
  922. * @access public
  923. * @return void
  924. */
  925. public function create($projectID = 0, $executionID = 0, $copyExecutionID = 0, $planID = 0, $confirm = 'no', $productID = 0, $extra = '')
  926. {
  927. $project = empty($projectID) ? null : $this->loadModel('project')->fetchByID($projectID);
  928. if(!empty($project->isTpl)) dao::$filterTpl = 'never';
  929. if($this->app->tab == 'doc') unset($this->lang->doc->menu->execution['subMenu']);
  930. if($this->app->tab == 'project') $this->project->setMenu($projectID);
  931. $extra = str_replace(array(',', ' '), array('&', ''), $extra);
  932. parse_str($extra, $output);
  933. $execution = $this->executionZen->initFieldsForCreate($projectID, $output);
  934. if($copyExecutionID) $this->executionZen->setFieldsByCopyExecution($execution, $copyExecutionID);
  935. $projectID = isset($execution->project) ? $execution->project : $projectID;
  936. if($executionID) return $this->executionZen->displayAfterCreated($projectID, $executionID, $planID, $confirm);
  937. $allProjects = $this->project->getPairsByModel('all', 'noclosed,multiple');
  938. $this->loadModel('project')->checkAccess($projectID, $allProjects);
  939. if(empty($projectID)) $projectID = key($allProjects) ? key($allProjects) : 0;
  940. /* 过滤掉阶段已打基线的项目。*/
  941. $executionList = $this->project->getExecutionList(array_keys($allProjects));
  942. foreach($executionList as $executionInfo)
  943. {
  944. if(!empty($executionInfo->frozen)) unset($allProjects[$executionInfo->project]);
  945. }
  946. $project = empty($projectID) ? null : $this->loadModel('project')->fetchByID($projectID);
  947. $project ? $this->executionZen->correctExecutionCommonLang($project, $execution->type) : $project = null;
  948. $products = $this->executionZen->getLinkedProducts($copyExecutionID, $planID, $project);
  949. $this->executionZen->setLinkedBranches($products, $copyExecutionID, $planID, $project);
  950. if(!empty($_POST))
  951. {
  952. /* Filter empty plans. */
  953. if(isset($_POST['attribute']) and $_POST['attribute'] == 'review') unset($_POST['plans']);
  954. if(!empty($_POST['plans']))
  955. {
  956. foreach($_POST['plans'] as $key => $planItem) $_POST['plans'][$key] = array_filter($_POST['plans'][$key]);
  957. $_POST['plans'] = array_filter($_POST['plans']);
  958. }
  959. $execution = $this->executionZen->buildExecutionForCreate();
  960. if(!$execution) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  961. $executionID = $this->execution->create($execution, isset($_POST['teamMembers']) ? $_POST['teamMembers'] : array());
  962. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  963. $this->loadModel('action')->create($this->objectType, $executionID, 'opened', '', $project->hasProduct ? implode(',', $_POST['products']) : '');
  964. if(!empty($projectID) and strpos(',kanban,agileplus,waterfallplus,ipd,', ",$project->model,") !== false and $execution->type == 'kanban')
  965. {
  966. $execution = $this->execution->fetchByID($executionID);
  967. $this->loadModel('kanban')->createRDKanban($execution);
  968. }
  969. $message = $this->executeHooks($executionID);
  970. if(empty($message)) $message = $this->lang->saveSuccess;
  971. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message, 'id' => $executionID));
  972. $location = $this->executionZen->getAfterCreateLocation($projectID, $executionID, $project->model);
  973. return $this->send(array('result' => 'success', 'message' => $message, 'load' => $location));
  974. }
  975. list($this->view->pmUsers, $this->view->poUsers, $this->view->qdUsers, $this->view->rdUsers) = $this->executionZen->setUserMoreLink();
  976. $this->executionZen->setCopyProjects($project);
  977. $isStage = isset($output['type']) && $output['type'] == 'stage';
  978. if(!$isStage && !empty($this->view->execution) && $this->view->execution->type == 'stage') $isStage = true;
  979. if(!empty($project) && in_array($project->model, $this->config->project->waterfallList))
  980. {
  981. if(in_array($project->model, $this->config->project->waterfallList) && !isset($output['type'])) $isStage = true;
  982. $parentStage = isset($output['parentStage']) ? $output['parentStage'] : 0;
  983. $type = isset($output['type']) ? $output['type'] : '';
  984. if($parentStage)
  985. {
  986. $parent = $this->execution->fetchById((int)$parentStage);
  987. if($parent->attribute != 'mix')
  988. {
  989. $this->app->loadLang('stage');
  990. foreach($this->lang->stage->typeList as $type => $label)
  991. {
  992. if($type != $parent->attribute) unset($this->lang->stage->typeList[$type]);
  993. }
  994. }
  995. }
  996. else
  997. {
  998. unset($this->lang->execution->typeList['kanban']);
  999. unset($this->lang->execution->typeList['sprint']);
  1000. }
  1001. $this->view->parentStage = $parentStage;
  1002. $this->view->parentStages = $this->loadModel('programplan')->getParentStageList($projectID, 0, 0, 'withparent|noclosed|' . ($isStage ? 'stage' : 'notstage'));
  1003. }
  1004. $this->view->title = $this->app->tab == 'execution' ? $this->lang->execution->createExec : $this->lang->execution->create;
  1005. $this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('execution', 'all') : '';
  1006. $this->view->allProducts = array_filter($this->executionZen->getAllProductsForCreate($project));
  1007. $this->view->allProjects = $allProjects;
  1008. $this->view->multiBranchProducts = $this->loadModel('product')->getMultiBranchPairs();
  1009. $this->view->products = $products;
  1010. $this->view->teams = $this->execution->getCanCopyObjects((int)$projectID);
  1011. $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed');
  1012. $this->view->copyExecutionID = $copyExecutionID;
  1013. $this->view->productID = $productID;
  1014. $this->view->projectID = $projectID;
  1015. $this->view->from = $this->app->tab;
  1016. $this->view->isStage = $isStage;
  1017. $this->view->isKanban = isset($output['type']) && $output['type'] == 'kanban';
  1018. $this->view->project = $project;
  1019. $this->view->planID = $planID;
  1020. $this->display();
  1021. }
  1022. /**
  1023. * 编辑一个执行。
  1024. * Edit a execution.
  1025. *
  1026. * @param int $executionID
  1027. * @param string $action
  1028. * @param string $extra
  1029. * @param string $newPlans
  1030. * @param string $confirm
  1031. * @access public
  1032. * @return void
  1033. */
  1034. public function edit($executionID, $action = 'edit', $extra = '', $newPlans = '', $confirm = 'no')
  1035. {
  1036. /* Update linked plans if confirmed and new plans added. */
  1037. $this->executionZen->updateLinkedPlans($executionID, $newPlans, $confirm);
  1038. $this->loadModel('product');
  1039. $this->loadModel('programplan');
  1040. $this->loadModel('productplan');
  1041. $this->app->loadLang('program');
  1042. $this->app->loadLang('stage');
  1043. $execution = $this->execution->getById($executionID);
  1044. $project = $this->project->getById($execution->project);
  1045. $branches = $this->project->getBranchesByProject($executionID);
  1046. $linkedProductIdList = empty($branches) ? '' : array_keys($branches);
  1047. /* Set menu. */
  1048. $this->execution->setMenu($executionID);
  1049. if(!empty($_POST))
  1050. {
  1051. $oldProducts = $this->product->getProducts($executionID, 'all', '', true, $linkedProductIdList);
  1052. $oldPlans = $this->dao->select('plan')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($executionID)->andWhere('plan')->ne(0)->fetchPairs('plan');
  1053. $oldExecution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch();
  1054. /* Get the data from the post. */
  1055. $formData = form::data(null, $executionID)
  1056. ->add('id', $executionID)
  1057. ->setDefault('lastEditedBy', $this->app->user->account)
  1058. ->setDefault('lastEditedDate', helper::now())
  1059. ->setIF($this->post->heightType == 'auto', 'displayCards', 0)
  1060. ->setIF(helper::isZeroDate($this->post->begin), 'begin', '')
  1061. ->setIF(helper::isZeroDate($this->post->end), 'end', '')
  1062. ->setIF($this->post->status == 'closed' && $oldExecution->status != 'closed', 'closedDate', helper::now())
  1063. ->setIF($this->post->status == 'closed' && $oldExecution->status != 'closed', 'realEnd', helper::today())
  1064. ->setIF($this->post->status == 'suspended' && $oldExecution->status != 'suspended', 'suspendedDate', helper::today())
  1065. ->setIF($oldExecution->type == 'stage', 'project', $oldExecution->project)
  1066. ->setDefault('project', $oldExecution->project)
  1067. ->setDefault('team', $this->post->name)
  1068. ->setDefault('branch', $this->post->branch)
  1069. ->setDefault('attribute', $oldExecution->attribute)
  1070. ->get();
  1071. $changes = $this->execution->update($executionID, $formData);
  1072. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1073. $this->execution->updateProducts($executionID, $formData);
  1074. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1075. $this->loadModel('action');
  1076. if($action == 'undelete')
  1077. {
  1078. $this->dao->update(TABLE_EXECUTION)->set('deleted')->eq('0')->where('id')->eq($executionID)->exec();
  1079. $this->dao->update(TABLE_ACTION)->set('extra')->eq(actionModel::BE_UNDELETED)->where('id')->eq($extra)->exec();
  1080. $this->action->create($this->objectType, $executionID, 'undeleted');
  1081. }
  1082. $oldProducts = array_keys($oldProducts);
  1083. $newProducts = $this->product->getProducts($executionID, 'all', '', true, $linkedProductIdList);
  1084. $newProducts = array_keys($newProducts);
  1085. $diffProducts = array_merge(array_diff($oldProducts, $newProducts), array_diff($newProducts, $oldProducts));
  1086. $products = $diffProducts ? implode(',', $newProducts) : '';
  1087. if($changes or $diffProducts)
  1088. {
  1089. $actionID = $this->action->create($this->objectType, $executionID, 'edited', '', $products);
  1090. $this->action->logHistory($actionID, $changes);
  1091. }
  1092. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->programplan->computeProgress($executionID, 'edit');
  1093. /* Redirect to confirm page if the execution can link plan stories. */
  1094. $this->executionZen->checkLinkPlan($executionID, $oldPlans);
  1095. $message = $this->executeHooks($executionID);
  1096. if($message) $this->lang->saveSuccess = $message;
  1097. if($formData->status == 'doing') $this->loadModel('common')->syncPPEStatus($executionID);
  1098. /* If link from no head then reload. */
  1099. if(isInModal())
  1100. {
  1101. $kanbanLoad = array('selector' => '#main>*');
  1102. return $this->sendSuccess(array('closeModal' => true, 'callback' => $execution->type == 'kanban' ? 'loadCurrentPage(' . json_encode($kanbanLoad) . ');' : 'loadCurrentPage()'));
  1103. }
  1104. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => inlink('view', "executionID=$executionID")));
  1105. }
  1106. $executions = $this->executions;
  1107. unset($executions[$executionID]); /* Remove current execution from the executions. */
  1108. /* Get linked objects of this execution. */
  1109. $linkedObjects = $this->executionZen->getLinkedObjects($execution);
  1110. list($pmUsers, $poUsers, $qdUsers, $rdUsers) = $this->executionZen->setUserMoreLink($execution);
  1111. if(!$project->hasProduct) $this->lang->execution->PO = $this->lang->common->story . $this->lang->execution->owner;
  1112. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')))
  1113. {
  1114. $parentStage = $this->project->getByID($execution->parent, 'stage');
  1115. $this->view->enableOptionalAttr = (empty($parentStage) or (!empty($parentStage) and $parentStage->attribute == 'mix'));
  1116. }
  1117. if($this->app->tab == 'project' and $project->model == 'waterfallplus')
  1118. {
  1119. $productID = $this->product->getProductIDByProject($executionID);
  1120. $parentStageList = $this->programplan->getParentStageList($execution->project, $executionID, $productID);
  1121. unset($parentStageList[0]);
  1122. }
  1123. if(empty($project->hasProduct))
  1124. {
  1125. $linkedProduct = current($linkedObjects->linkedProducts);
  1126. $linkedPlans = !empty($linkedProduct->plans) ? array_values($linkedProduct->plans) : array();
  1127. }
  1128. $this->view->title = $this->lang->execution->edit . $this->lang->hyphen . $execution->name;
  1129. $this->view->executions = $executions;
  1130. $this->view->execution = $execution;
  1131. $this->view->project = $project;
  1132. $this->view->poUsers = $poUsers;
  1133. $this->view->pmUsers = $pmUsers;
  1134. $this->view->qdUsers = $qdUsers;
  1135. $this->view->rdUsers = $rdUsers;
  1136. $this->view->users = $this->loadModel('user')->getPairs('nodeleted|noclosed');
  1137. $this->view->groups = $this->loadModel('group')->getPairs();
  1138. $this->view->branches = $branches;
  1139. $this->view->allProducts = $linkedObjects->allProducts;
  1140. $this->view->multiBranchProducts = $this->product->getMultiBranchPairs();
  1141. $this->view->linkedProducts = $linkedObjects->linkedProducts;
  1142. $this->view->linkedBranches = $linkedObjects->linkedBranches;
  1143. $this->view->linkedStoryIDList = $linkedObjects->linkedStoryIDList;
  1144. $this->view->unmodifiableProducts = $linkedObjects->unmodifiableProducts;
  1145. $this->view->unmodifiableBranches = $linkedObjects->unmodifiableBranches;
  1146. $this->view->productPlans = $linkedObjects->productPlans;
  1147. $this->view->productPlan = $linkedObjects->productPlan;
  1148. $this->view->currentPlan = empty($project->hasProduct) ? $linkedPlans : $linkedObjects->currentPlan;
  1149. $this->view->branchGroups = $this->execution->getBranchByProduct(array_keys($linkedObjects->linkedProducts), $execution->project, 'noclosed', $linkedObjects->linkedBranchList);
  1150. $this->view->teamMembers = $this->execution->getTeamMembers($executionID);
  1151. $this->view->allProjects = $this->project->getPairsByModel($project->model, 'noclosed|multiple', $project->id);
  1152. $this->view->parentStageList = isset($parentStageList) ? $parentStageList : array();
  1153. $this->view->isStage = isset($project->model) && in_array($project->model, array('waterfall', 'waterfallplus'));
  1154. $this->view->unclosedTasks = $this->loadModel('task')->getUnclosedTasksByExecution($executionID);
  1155. $this->display();
  1156. }
  1157. /**
  1158. * 批量编辑多个执行。
  1159. * Batch edit the executions.
  1160. *
  1161. * @param int $executionID
  1162. * @access public
  1163. * @return void
  1164. */
  1165. public function batchEdit($executionID = 0)
  1166. {
  1167. $this->app->loadLang('stage');
  1168. $this->app->loadLang('programplan');
  1169. if($this->app->tab == 'project')
  1170. {
  1171. $projectID = $this->session->project;
  1172. $project = $this->project->getById($projectID);
  1173. $this->project->setMenu($projectID);
  1174. $this->view->project = $project;
  1175. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->lang->execution->common = $this->lang->execution->stage;
  1176. if($project->model == 'ipd') $this->config->execution->list->customBatchEditFields = 'days,team,desc,PO,QD,PM,RD';
  1177. }
  1178. else
  1179. {
  1180. $this->config->excludeDropmenuList[] = 'execution-batchedit';
  1181. }
  1182. if($this->post->name)
  1183. {
  1184. $postData = fixer::input('post')->get();
  1185. $allChanges = $this->execution->batchUpdate($postData);
  1186. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1187. $this->executeHooks(key($this->post->id));
  1188. if(!empty($allChanges))
  1189. {
  1190. foreach($allChanges as $executionID => $changes)
  1191. {
  1192. if(empty($changes)) continue;
  1193. $actionID = $this->loadModel('action')->create($this->objectType, $executionID, 'Edited');
  1194. $this->action->logHistory($actionID, $changes);
  1195. }
  1196. }
  1197. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $this->session->executionList));
  1198. }
  1199. $projectModel = $this->app->tab == 'project' ? $project->model : 'all';
  1200. $allProjects = $this->project->getPairsByModel($projectModel, 'noclosed', isset($projectID) ? $projectID : 0);
  1201. if(!$this->post->executionIDList)
  1202. {
  1203. /* Use a fallback link to locate in case session has no related data. */
  1204. $locateLink = !empty($this->session->executionList) ? $this->session->executionList : $this->createLink('execution', 'all');
  1205. return $this->locate($locateLink);
  1206. }
  1207. $executionIDList = $this->post->executionIDList;
  1208. $executions = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->in($executionIDList)->fetchAll('id', false);
  1209. $relatedProjects = $this->dao->select('id,project')->from(TABLE_PROJECT)->where('id')->in($executionIDList)->fetchPairs(); /* 获取执行所属的项目列表。*/
  1210. $projects = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->in($relatedProjects)->fetchAll('id'); /* 获取执行所属的项目列表中每个项目的项目信息。*/
  1211. $frozenStages = '';
  1212. foreach($executions as $key => $execution)
  1213. {
  1214. if(!empty($execution->frozen))
  1215. {
  1216. $frozenStages .= "#{$execution->id} ";
  1217. unset($executions[$key]);
  1218. }
  1219. }
  1220. if(empty($executions) && !empty($frozenStages)) return $this->send(array('result' => 'fail', 'load' => array('alert' => sprintf($this->lang->execution->frozenTip, $frozenStages), 'load' => true)));
  1221. list($pmUsers, $poUsers, $qdUsers, $rdUsers) = $this->executionZen->setUserMoreLink($executions);
  1222. /* Set custom fields. */
  1223. foreach(explode(',', $this->config->execution->list->customBatchEditFields) as $field)
  1224. {
  1225. $fieldTitle = $this->app->tab == 'execution' ? str_replace($this->lang->executionCommon, $this->lang->execution->common, $this->lang->execution->$field) : $this->lang->execution->$field;
  1226. if($field == 'lifetime') $fieldTitle = $this->app->tab == 'execution' ? $this->lang->execution->execType : $this->lang->execution->type;
  1227. if($field == 'team') $fieldTitle = $this->lang->execution->teamName;
  1228. $customFields[$field] = $fieldTitle;
  1229. }
  1230. $parentIdList = array();
  1231. foreach($executions as $execution)
  1232. {
  1233. if($execution->parent == 0) continue;
  1234. $parentIdList[] = $execution->parent;
  1235. }
  1236. $this->view->customFields = $customFields;
  1237. $this->view->showFields = $this->config->execution->custom->batchEditFields;
  1238. $this->view->title = $this->lang->execution->batchEdit;
  1239. $this->view->executions = $executions;
  1240. $this->view->allProjects = $allProjects;
  1241. $this->view->projects = $projects;
  1242. $this->view->pmUsers = $pmUsers;
  1243. $this->view->poUsers = $poUsers;
  1244. $this->view->qdUsers = $qdUsers;
  1245. $this->view->rdUsers = $rdUsers;
  1246. $this->view->from = $this->app->tab;
  1247. $this->view->parents = $this->execution->getByIdList($parentIdList);
  1248. $this->view->frozenStages = $frozenStages;
  1249. $this->display();
  1250. }
  1251. /**
  1252. * 批量设置执行状态。
  1253. * Batch change status.
  1254. *
  1255. * @param string $status
  1256. * @param int $projectID
  1257. * @access public
  1258. * @return void
  1259. */
  1260. public function batchChangeStatus($status, $projectID = 0)
  1261. {
  1262. if($_SERVER['REQUEST_METHOD'] !== 'POST') return $this->sendError($this->lang->error->unsupportedReq);
  1263. $executionIdList = $this->post->executionIDList;
  1264. if(is_string($executionIdList)) $executionIdList = explode(',', $executionIdList);
  1265. $message = $this->execution->batchChangeStatus($executionIdList, $status);
  1266. if(empty($message['byChild']) && empty($message['byDeliverable'])) return $this->sendSuccess(array('load' => true));
  1267. $alertMsg = '';
  1268. if($status == 'wait')
  1269. {
  1270. $project = $this->loadModel('project')->getById($projectID);
  1271. /* In execution-all list or waterfall, waterfallplus project's execution list. */
  1272. if(empty($project) or (!empty($project) and strpos($project->model, 'waterfall') !== false))
  1273. {
  1274. $executionLang = (empty($project) or (!empty($project) and $project->model == 'waterfallplus')) ? $this->lang->execution->common : $this->lang->stage->common;
  1275. $alertMsg = sprintf($this->lang->execution->hasStartedTaskOrSubStage, $executionLang, $message['byChild']);
  1276. }
  1277. if(!empty($project) and strpos('agileplus,scrum', $project->model) !== false)
  1278. {
  1279. $executionLang = $project->model == 'scrum' ? $this->lang->executionCommon : $this->lang->execution->common;
  1280. $alertMsg = sprintf($this->lang->execution->hasStartedTask, $executionLang, $message['byChild']);
  1281. }
  1282. }
  1283. if($status == 'suspended') $alertMsg = sprintf($this->lang->execution->hasSuspendedOrClosedChildren, $message['byChild']);
  1284. if($status == 'closed')
  1285. {
  1286. if(!empty($message['byChild']))
  1287. {
  1288. $alertMsg .= sprintf($this->lang->execution->hasNotClosedChildren, $message['byChild']);
  1289. }
  1290. elseif(!empty($message['byDeliverable']))
  1291. {
  1292. $alertMsg .= sprintf($this->lang->execution->cannotCloseByDeliverable, $message['byDeliverable']);
  1293. }
  1294. }
  1295. return $this->send(array('load' => array('alert' => $alertMsg), 'result' => 'success'));
  1296. }
  1297. /**
  1298. * 开始一个执行。
  1299. * Start the execution.
  1300. *
  1301. * @param int $executionID
  1302. * @param string $from
  1303. * @access public
  1304. * @return void
  1305. */
  1306. public function start($executionID, $from = 'execution')
  1307. {
  1308. $execution = $this->commonAction($executionID);
  1309. $executionID = $execution->id;
  1310. if($execution->type == 'kanban') $this->lang->executionCommon = $this->lang->execution->kanban;
  1311. $this->extendRequireFields($executionID);
  1312. if(!empty($_POST))
  1313. {
  1314. $postData = fixer::input('post')
  1315. ->add('id', $executionID)
  1316. ->setDefault('status', 'doing')
  1317. ->setDefault('lastEditedBy', $this->app->user->account)
  1318. ->setDefault('lastEditedDate', helper::now())
  1319. ->stripTags($this->config->execution->editor->start['id'], $this->config->allowedTags)
  1320. ->get();
  1321. $this->execution->start($executionID, $postData);
  1322. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1323. $project = $this->loadModel('project')->getById($execution->project);
  1324. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->loadModel('programplan')->computeProgress($executionID, 'start');
  1325. $this->loadModel('common')->syncPPEStatus($executionID);
  1326. $this->executeHooks($executionID);
  1327. $response['closeModal'] = true;
  1328. $response['load'] = true;
  1329. if($from == 'kanban') $response['callback'] = "changeStatus('doing')";
  1330. return $this->sendSuccess($response);
  1331. }
  1332. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->execution->start;
  1333. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  1334. $this->view->actions = $this->loadModel('action')->getList($this->objectType, $executionID);
  1335. $this->display();
  1336. }
  1337. /**
  1338. * 延期一个迭代。
  1339. * Delay the execution.
  1340. *
  1341. * @param int $executionID
  1342. * @param string $from
  1343. * @access public
  1344. * @return void
  1345. */
  1346. public function putoff($executionID, $from = 'execution')
  1347. {
  1348. $execution = $this->commonAction($executionID);
  1349. $executionID = $execution->id;
  1350. $this->extendRequireFields($executionID);
  1351. if(!empty($_POST))
  1352. {
  1353. $postData = fixer::input('post')
  1354. ->add('id', $executionID)
  1355. ->stripTags($this->config->execution->editor->putoff['id'], $this->config->allowedTags)
  1356. ->setDefault('lastEditedBy', $this->app->user->account)
  1357. ->setDefault('lastEditedDate', helper::now())
  1358. ->get();
  1359. $this->execution->putoff($executionID, $postData);
  1360. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1361. $this->executeHooks($executionID);
  1362. $response['closeModal'] = true;
  1363. $response['load'] = true;
  1364. if($from == 'kanban') $response['callback'] = "changeStatus('doing')";
  1365. return $this->sendSuccess($response);
  1366. }
  1367. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->execution->putoff;
  1368. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  1369. $this->view->actions = $this->loadModel('action')->getList($this->objectType, $executionID);
  1370. $this->display();
  1371. }
  1372. /**
  1373. * 挂起一个执行。
  1374. * Suspend a execution.
  1375. *
  1376. * @param int $executionID
  1377. * @param string $from
  1378. * @access public
  1379. * @return void
  1380. */
  1381. public function suspend($executionID, $from = 'execution')
  1382. {
  1383. $execution = $this->commonAction($executionID);
  1384. $executionID = $execution->id;
  1385. if($execution->type == 'kanban') $this->lang->executionCommon = $this->lang->execution->kanban;
  1386. $this->extendRequireFields($executionID);
  1387. if(!empty($_POST))
  1388. {
  1389. $postData = fixer::input('post')
  1390. ->add('id', $executionID)
  1391. ->setDefault('status', 'suspended')
  1392. ->setDefault('lastEditedBy', $this->app->user->account)
  1393. ->setDefault('lastEditedDate', helper::now())
  1394. ->setDefault('suspendedDate', helper::today())
  1395. ->stripTags($this->config->execution->editor->suspend['id'], $this->config->allowedTags)
  1396. ->get();
  1397. $this->execution->computeBurn($executionID);
  1398. $this->execution->suspend($executionID, $postData);
  1399. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1400. $project = $this->loadModel('project')->getById($execution->project);
  1401. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->loadModel('programplan')->computeProgress($executionID, 'suspend');
  1402. $this->executeHooks($executionID);
  1403. $response['closeModal'] = true;
  1404. $response['load'] = true;
  1405. if($from == 'kanban') $response['callback'] = "changeStatus('suspended')";
  1406. return $this->sendSuccess($response);
  1407. }
  1408. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->execution->suspend;
  1409. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  1410. $this->view->actions = $this->loadModel('action')->getList($this->objectType, $executionID);
  1411. $this->display();
  1412. }
  1413. /**
  1414. * 激活一个执行。
  1415. * Activate a execution.
  1416. *
  1417. * @param int $executionID
  1418. * @param string $from
  1419. * @access public
  1420. * @return void
  1421. */
  1422. public function activate($executionID, $from = 'execution')
  1423. {
  1424. $execution = $this->commonAction($executionID);
  1425. $executionID = $execution->id;
  1426. if($execution->type == 'kanban') $this->lang->executionCommon = $this->lang->execution->kanban;
  1427. $this->extendRequireFields($executionID);
  1428. if(!empty($_POST))
  1429. {
  1430. $postData = fixer::input('post')
  1431. ->add('id', $executionID)
  1432. ->setDefault('realEnd', null)
  1433. ->setDefault('status', 'doing')
  1434. ->setDefault('lastEditedBy', $this->app->user->account)
  1435. ->setDefault('lastEditedDate', helper::now())
  1436. ->setDefault('closedBy', '')
  1437. ->setDefault('closedDate', null)
  1438. ->stripTags($this->config->execution->editor->activate['id'], $this->config->allowedTags)
  1439. ->get();
  1440. $this->execution->activate($executionID, $postData);
  1441. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1442. $project = $this->loadModel('project')->getById($execution->project);
  1443. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->loadModel('programplan')->computeProgress($executionID, 'activate');
  1444. $this->executeHooks($executionID);
  1445. $response['closeModal'] = true;
  1446. $response['load'] = true;
  1447. if($from == 'kanban') $response['callback'] = "changeStatus('doing')";
  1448. return $this->sendSuccess($response);
  1449. }
  1450. $newBegin = date('Y-m-d');
  1451. $dateDiff = helper::diffDate($newBegin, $execution->begin);
  1452. $newEnd = date('Y-m-d', strtotime($execution->end) + $dateDiff * 24 * 3600);
  1453. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->execution->activate;
  1454. $this->view->execution = $execution;
  1455. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  1456. $this->view->actions = $this->loadModel('action')->getList($this->objectType, $executionID);
  1457. $this->view->newBegin = $newBegin;
  1458. $this->view->newEnd = $newEnd;
  1459. $this->display();
  1460. }
  1461. /**
  1462. * 关闭迭代。
  1463. * Close execution.
  1464. *
  1465. * @param int $executionID
  1466. * @param string $from
  1467. * @access public
  1468. * @return void
  1469. */
  1470. public function close($executionID, $from = 'execution')
  1471. {
  1472. $execution = $this->commonAction($executionID);
  1473. $executionID = $execution->id;
  1474. if($execution->type == 'kanban') $this->lang->executionCommon = $this->lang->execution->kanban;
  1475. $this->extendRequireFields($executionID);
  1476. if(!empty($_POST))
  1477. {
  1478. $now = helper::now();
  1479. $postData = fixer::input('post')
  1480. ->add('id', $executionID)
  1481. ->setDefault('status', 'closed')
  1482. ->setDefault('closedBy', $this->app->user->account)
  1483. ->setDefault('closedDate', $now)
  1484. ->setDefault('lastEditedBy', $this->app->user->account)
  1485. ->setDefault('lastEditedDate', $now)
  1486. ->stripTags($this->config->execution->editor->close['id'], $this->config->allowedTags)
  1487. ->get();
  1488. $this->execution->computeBurn($executionID);
  1489. $this->execution->close($executionID, $postData);
  1490. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1491. $project = $this->loadModel('project')->getById($execution->project);
  1492. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd')))
  1493. {
  1494. $result = $this->loadModel('programplan')->computeProgress($executionID, 'close');
  1495. if(is_array($result)) return $this->send($result);
  1496. }
  1497. $this->executeHooks($executionID);
  1498. $response['closeModal'] = true;
  1499. $response['load'] = true;
  1500. if($from == 'kanban') $response['callback'] = "changeStatus('closed')";
  1501. return $this->sendSuccess($response);
  1502. }
  1503. $this->view->title = $this->view->execution->name . $this->lang->hyphen .$this->lang->execution->close;
  1504. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  1505. $this->view->from = $from;
  1506. $this->view->actions = $this->loadModel('action')->getList($this->objectType, $executionID);
  1507. $this->view->unclosedTasks = $this->loadModel('task')->getUnclosedTasksByExecution($executionID);
  1508. $this->display();
  1509. }
  1510. /**
  1511. * 执行详情。
  1512. * View a execution.
  1513. *
  1514. * @param int $executionID
  1515. * @access public
  1516. * @return void
  1517. */
  1518. public function view($executionID)
  1519. {
  1520. /* Check execution permission. */
  1521. $accessExecutionID = $this->execution->checkAccess((int)$executionID, $this->executions);
  1522. if($this->app->apiVersion == 'v2' && $accessExecutionID != $executionID) return $this->sendError($this->lang->execution->accessDenied);
  1523. $executionID = $accessExecutionID;
  1524. $execution = $this->execution->getByID($executionID, true);
  1525. $type = $this->config->vision == 'lite' ? 'kanban' : 'stage,sprint,kanban';
  1526. if($execution->type == 'stage')
  1527. {
  1528. $childExecutions = $this->execution->getChildExecutions($executionID);
  1529. if(!empty($childExecutions)) return $this->app->control->sendError($this->lang->execution->errorParentExecution, helper::createLink('execution', 'all'));
  1530. }
  1531. if(empty($execution) || strpos($type, $execution->type) === false) return $this->send(array('result' => 'success', 'load' => array('alert' => $this->lang->notFound, 'locate' => $this->config->vision == 'lite' ? $this->createLink('project', 'index') : $this->createLink('execution', 'all'))));
  1532. if($execution->type == 'kanban' and defined('RUN_MODE') and RUN_MODE == 'api') return print($this->fetch('execution', 'kanban', "executionID=$executionID"));
  1533. /* Load lang and set session. */
  1534. $this->app->loadLang('program');
  1535. $this->app->loadLang('bug');
  1536. $this->app->session->set('teamList', $this->app->getURI(true), 'execution');
  1537. $this->execution->setMenu($execution->id);
  1538. /* Execution not found to prevent searching for. */
  1539. if(!isset($this->executions[$execution->id])) $this->executions = $this->execution->getPairs($execution->project, 'all', 'nocode');
  1540. if(!$this->loadModel('common')->checkPrivByObject('execution', $executionID)) return $this->execution->accessDenied();
  1541. $execution->projectInfo = $this->loadModel('project')->getByID($execution->project);
  1542. $programList = $execution->projectInfo ? array_filter(explode(',', $execution->projectInfo->path)) : array();
  1543. array_pop($programList);
  1544. if(empty($execution->projectInfo->hasProduct)) $this->lang->execution->PO = $this->lang->common->story . $this->lang->execution->owner;
  1545. $this->executionZen->assignViewVars($executionID);
  1546. $this->view->title = $this->lang->execution->view;
  1547. $this->view->execution = $execution;
  1548. $this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
  1549. $this->view->type = $type;
  1550. $this->view->features = $this->execution->getExecutionFeatures($execution);
  1551. $this->view->project = $execution->projectInfo;
  1552. $this->view->programList = $this->loadModel('program')->getPairsByList($programList);
  1553. $this->display();
  1554. }
  1555. /**
  1556. * 研发看板。
  1557. * Research and development Kanban.
  1558. *
  1559. * @param int $executionID
  1560. * @param string $browseType all|story|bug|task
  1561. * @param string $orderBy
  1562. * @param string $groupBy default|pri|category|module|source|assignedTo|type|story|severity
  1563. * @access public
  1564. * @return void
  1565. */
  1566. public function kanban($executionID, $browseType = 'all', $orderBy = 'id_asc', $groupBy = 'default')
  1567. {
  1568. $this->app->loadLang('bug');
  1569. $this->app->loadLang('epic');
  1570. $this->app->loadLang('requirement');
  1571. $this->app->loadLang('kanban');
  1572. if($this->config->vision != 'lite') $this->lang->execution->menu = new stdclass();
  1573. $execution = $this->execution->fetchByID($executionID);
  1574. if(!$execution || $execution->deleted) return $this->sendError($this->lang->notFound, $this->createLink('execution', 'all'));
  1575. $execution = $this->commonAction($executionID);
  1576. if($execution->type != 'kanban') return $this->locate(inlink('view', "executionID={$execution->id}"));
  1577. /* Save the recently five executions visited in the cookie. */
  1578. $this->executionZen->setRecentExecutions($execution->id);
  1579. /* Set Session. */
  1580. if(empty($groupBy)) $groupBy = 'default';
  1581. $this->session->set('execGroupBy', $groupBy);
  1582. $this->session->set('storyList', $this->app->getURI(true), 'execution');
  1583. $this->session->set('rdSearchValue', '');
  1584. /* Get kanban data and set actions. */
  1585. $executionActions = array();
  1586. foreach($this->config->execution->statusActions as $action)
  1587. {
  1588. if($this->execution->isClickable($execution, $action)) $executionActions[] = $action;
  1589. }
  1590. /* Set lane type. */
  1591. $features = $this->execution->getExecutionFeatures($execution);
  1592. if(!$features['story'] and !$features['qa']) $browseType = 'task';
  1593. if(!$features['story']) unset($this->lang->kanban->group->task['story']);
  1594. $this->session->set('execLaneType', $browseType);
  1595. $taskToOpen = $this->cookie->taskToOpen ? $this->cookie->taskToOpen : 0;
  1596. helper::setcookie('taskToOpen', 0, 0);
  1597. $this->executionZen->assignKanbanVars($executionID);
  1598. $this->view->title = $this->lang->kanban->view;
  1599. $this->view->execution = $execution;
  1600. $this->view->executionList = $this->loadModel('project')->getExecutionList(array($execution->project));
  1601. $this->view->executionID = $executionID;
  1602. $this->view->kanbanList = $this->loadModel('kanban')->getRDKanban($executionID, $browseType, $orderBy, 0, $groupBy);
  1603. $this->view->browseType = $browseType;
  1604. $this->view->orderBy = $orderBy;
  1605. $this->view->groupBy = $groupBy;
  1606. $this->view->projectID = $execution->project;
  1607. $this->view->project = $this->loadModel('project')->getByID($execution->project);
  1608. $this->view->features = $features;
  1609. $this->view->executionActions = $executionActions;
  1610. $this->view->kanban = $this->lang->execution->kanban;
  1611. $this->view->taskToOpen = $taskToOpen;
  1612. $this->display();
  1613. }
  1614. /**
  1615. * 任务看板
  1616. * Task kanban.
  1617. *
  1618. * @param int $executionID
  1619. * @param string $browseType story|bug|task|all
  1620. * @param string $orderBy
  1621. * @param string $groupBy default|pri|category|module|source|assignedTo|type|story|severity
  1622. * @access public
  1623. * @return void
  1624. */
  1625. public function taskKanban($executionID, $browseType = 'all', $orderBy = 'order_asc', $groupBy = '')
  1626. {
  1627. if(!$this->loadModel('common')->checkPrivByObject('execution', $executionID)) return $this->execution->accessDenied();
  1628. /* Load model and language. */
  1629. $this->app->loadLang('epic');
  1630. $this->app->loadLang('requirement');
  1631. $this->app->loadLang('task');
  1632. $this->app->loadLang('bug');
  1633. $this->loadModel('kanban');
  1634. if(strpos($this->server->http_user_agent, 'MSIE 8.0') !== false) helper::header('X-UA-Compatible', 'IE=EmulateIE7'); // Compatibility IE8.
  1635. $this->execution->setMenu($executionID);
  1636. $execution = $this->execution->getByID($executionID);
  1637. $features = $this->execution->getExecutionFeatures($execution);
  1638. if($execution->lifetime == 'ops')
  1639. {
  1640. $browseType = $browseType == 'all' ? 'task' : $browseType;
  1641. unset($this->lang->kanban->type['all']);
  1642. unset($this->lang->kanban->type['story']);
  1643. unset($this->lang->kanban->type['bug']);
  1644. unset($this->lang->kanban->type['parentStory']);
  1645. }
  1646. elseif(!$features['story'])
  1647. {
  1648. $browseType = 'task';
  1649. unset($this->lang->kanban->group->task['story'], $this->lang->kanban->type['story'], $this->lang->kanban->type['parentStory']);
  1650. }
  1651. if(!$features['qa']) unset($this->lang->kanban->type['bug']);
  1652. /* Save to session. */
  1653. $uri = $this->app->getURI(true);
  1654. $groupBy = empty($groupBy) ? 'default' : $groupBy;
  1655. $this->session->set('taskList', $uri, 'execution');
  1656. $this->session->set('storyList', $uri, 'storyList');
  1657. $this->session->set('bugList', $uri, 'qa');
  1658. $this->session->set('taskSearchValue', '');
  1659. $this->session->set('execGroupBy', $groupBy);
  1660. $this->session->set('execLaneType', $browseType);
  1661. /* Get kanban data. */
  1662. $orderBy = $groupBy == 'story' && $browseType == 'task' && !isset($this->lang->kanban->orderList[$orderBy]) ? 'id_asc' : $orderBy;
  1663. $this->kanban->createLaneIfNotExist($execution);
  1664. list($kanbanGroup, $links) = $this->kanban->getExecutionKanban($executionID, $browseType, $groupBy, '', $orderBy);
  1665. /* Show lanes of the attribute: no story and bug in request, no bug in design. */
  1666. if(!isset($this->lang->execution->menu->story)) unset($kanbanGroup['story']);
  1667. if(!isset($this->lang->execution->menu->qa)) unset($kanbanGroup['bug']);
  1668. $this->executionZen->assignTaskKanbanVars($execution);
  1669. if($this->app->tab == 'project') $this->view->projectID = $execution->project;
  1670. $this->view->storyOrder = $orderBy;
  1671. $this->view->orderBy = 'id_asc';
  1672. $this->view->executionID = $executionID;
  1673. $this->view->browseType = $browseType;
  1674. $this->view->features = $features;
  1675. $this->view->kanbanGroup = $kanbanGroup;
  1676. $this->view->links = $links;
  1677. $this->view->groupBy = $groupBy;
  1678. $this->display();
  1679. }
  1680. /**
  1681. * 执行看板。
  1682. * Execution kanban.
  1683. *
  1684. * @access public
  1685. * @return void
  1686. */
  1687. public function executionKanban()
  1688. {
  1689. $projects = $this->loadModel('project')->getPairsByProgram(0, 'noclosed');
  1690. $executions = $this->execution->getStatData(0, 'all', 0, 0, false, 'withchild', 'id_desc');
  1691. foreach($executions as $execution)
  1692. {
  1693. $execution->name = htmlspecialchars_decode($execution->name);
  1694. $execution->team = htmlspecialchars_decode($execution->team);
  1695. }
  1696. list($projectCount, $statusCount, $myExecutions, $kanbanGroup) = $this->executionZen->buildExecutionKanbanData(array_keys($projects), $executions);
  1697. $kanbanGroup = empty($myExecutions) ? $kanbanGroup : array($myExecutions) + $kanbanGroup;
  1698. $kanbanList = array();
  1699. $lanes = array();
  1700. $items = array();
  1701. $columnCards = array();
  1702. $columns = array();
  1703. foreach($kanbanGroup as $laneKey => $laneData)
  1704. {
  1705. $laneTitle = zget($projects, $laneKey, $this->lang->execution->myExecutions);
  1706. $laneKey ++;
  1707. $lanes[] = array('name' => $laneKey, 'title' => $laneTitle);
  1708. foreach(array('wait', 'doing', 'suspended', 'closed') as $columnKey)
  1709. {
  1710. $cardList = !empty($laneData[$columnKey]) ? $laneData[$columnKey] : array();
  1711. foreach($cardList as $card)
  1712. {
  1713. $items[$laneKey][$columnKey][] = array('id' => $card->id, 'name' => $card->id, 'title' => $card->name, 'status' => $card->status, 'delay' => !empty($card->delay) ? $card->delay : 0, 'progress' => $card->progress);
  1714. if(!isset($columnCards[$columnKey])) $columnCards[$columnKey] = 0;
  1715. $columnCards[$columnKey] ++;
  1716. }
  1717. }
  1718. }
  1719. foreach(array('wait', 'doing', 'suspended', 'closed') as $columnKey)
  1720. {
  1721. $columns[] = array('name' => $columnKey, 'title' => $this->lang->execution->kanbanColType[$columnKey], 'cards' => !empty($columnCards[$columnKey]) ? $columnCards[$columnKey] : 0);
  1722. }
  1723. if(!empty($kanbanGroup))
  1724. {
  1725. $groupData['key'] = 'executionKanban';
  1726. $groupData['data']['lanes'] = $lanes;
  1727. $groupData['data']['cols'] = $columns;
  1728. $groupData['data']['items'] = $items;
  1729. $kanbanList[] = array('items' => array($groupData), 'key' => 'executionKanban', 'heading' => array('title' => $this->lang->execution->executionKanban));
  1730. }
  1731. $this->view->title = $this->lang->execution->executionKanban;
  1732. $this->view->kanbanList = $kanbanList;
  1733. $this->display();
  1734. }
  1735. /**
  1736. * 设置看板配置。
  1737. * Set Kanban.
  1738. *
  1739. * @param int $executionID
  1740. * @access public
  1741. * @return void
  1742. */
  1743. public function setKanban($executionID)
  1744. {
  1745. $execution = $this->execution->getByID($executionID);
  1746. if($_POST)
  1747. {
  1748. $executionData = form::data($this->config->execution->form->setkanban)
  1749. ->setIF($this->post->heightType == 'auto', 'displayCards', 0)
  1750. ->setDefault('minColWidth', $execution->minColWidth)
  1751. ->remove('heightType')
  1752. ->get();
  1753. if(!isset($_POST['heightType']) || $this->post->heightType != 'custom' || $this->loadModel('kanban')->checkDisplayCards($executionData->displayCards))
  1754. {
  1755. $this->execution->setKanban($executionID, $executionData);
  1756. }
  1757. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  1758. return $this->sendSuccess(array('load' => true, 'closeModal' => true));
  1759. }
  1760. $this->view->title = $this->lang->execution->setKanban;
  1761. $this->view->execution = $execution;
  1762. $this->view->laneCount = $this->loadModel('kanban')->getLaneCount($executionID, $execution->type);
  1763. $this->view->heightType = $execution->displayCards > 2 ? 'custom' : 'auto';
  1764. $this->view->displayCards = $execution->displayCards ? $execution->displayCards : '';
  1765. $this->display();
  1766. }
  1767. /**
  1768. * 用树状图的形式查看迭代/执行的视图。
  1769. * Show the view of the execution in a tree mode.
  1770. *
  1771. * @param int $executionID
  1772. * @param string $type
  1773. * @access public
  1774. * @return void
  1775. */
  1776. public function tree($executionID, $type = 'task')
  1777. {
  1778. $this->execution->setMenu($executionID);
  1779. /* Save this URI to session for locate it back. */
  1780. $uri = $this->app->getURI(true);
  1781. $this->app->session->set('taskList', $uri, 'execution');
  1782. $this->app->session->set('storyList', $uri, 'execution');
  1783. $this->app->session->set('executionList', $uri, 'execution');
  1784. $this->app->session->set('caseList', $uri, 'qa');
  1785. $this->app->session->set('bugList', $uri, 'qa');
  1786. $tree = $this->execution->getTree($executionID);
  1787. if($type === 'json') return print(helper::jsonEncode4Parse($tree, JSON_HEX_QUOT | JSON_HEX_APOS));
  1788. $execution = $this->execution->getById($executionID);
  1789. $project = $this->project->getById($execution->project);
  1790. if($execution->lifetime == 'ops') unset($this->lang->execution->treeLevel['story']);
  1791. if($this->app->tab == 'project') $this->view->projectID = $execution->project;
  1792. $gradeGroup = $this->loadModel('story')->getGradeGroup();
  1793. if(!isset($_SESSION['limitedExecutions'])) $this->execution->getLimitedExecution();
  1794. $this->view->title = $this->lang->execution->tree;
  1795. $this->view->execution = $execution;
  1796. $this->view->executionID = $executionID;
  1797. $this->view->level = $type;
  1798. $this->view->tree = $this->execution->buildTree($tree, (bool)$project->hasProduct, $gradeGroup);
  1799. $this->view->features = $this->execution->getExecutionFeatures($execution);
  1800. $this->view->isLimited = !$this->app->user->admin && strpos(",{$this->session->limitedExecutions},", ",{$executionID},") !== false;
  1801. $this->display();
  1802. }
  1803. /**
  1804. * 看板看板。
  1805. * Print kanban.
  1806. *
  1807. * @param int $executionID
  1808. * @param string $orderBy
  1809. * @access public
  1810. * @return void
  1811. */
  1812. public function printKanban($executionID, $orderBy = 'id_asc')
  1813. {
  1814. $this->view->title = $this->lang->execution->printKanban;
  1815. $this->execution->setMenu($executionID);
  1816. if($_POST)
  1817. {
  1818. /* Get stories information. */
  1819. $order = 1;
  1820. $stories = $this->loadModel('story')->getExecutionStories($executionID, 0, $orderBy);
  1821. $storySpecs = $this->story->getStorySpecs(array_keys($stories));
  1822. foreach($stories as $story) $story->order = $order ++;
  1823. /* Get printed kanban data. */
  1824. list($dataList, $users) = $this->executionZen->getPrintKanbanData($executionID, $stories);
  1825. $originalDataList = $dataList;
  1826. if($this->post->content == 'increment') $dateList = $this->executionZen->processPrintKanbanData($executionID, $dataList);
  1827. /* Close the page when there is no data. */
  1828. $hasData = false;
  1829. foreach($dataList as $data)
  1830. {
  1831. if(empty($data)) continue;
  1832. $hasData = true;
  1833. break;
  1834. }
  1835. if(!$hasData) return $this->sendSuccess(array('message' => $this->lang->execution->noPrintData, 'load' => true));
  1836. $this->execution->saveKanbanData($executionID, $originalDataList);
  1837. /* Get date list. */
  1838. if($this->post->content == 'all')
  1839. {
  1840. $executionInfo = $this->execution->getByID($executionID);
  1841. list($dateList) = $this->execution->getDateList($executionInfo->begin, $executionInfo->end, 'noweekend');
  1842. }
  1843. $this->view->hasBurn = $this->post->content == 'all';
  1844. $this->view->datas = $dataList;
  1845. $this->view->chartData = $this->post->content == 'all' ? $this->execution->buildBurnData($executionID, $dateList) : array();
  1846. $this->view->storySpecs = $storySpecs;
  1847. $this->view->realnames = $this->loadModel('user')->getRealNameAndEmails($users);
  1848. $this->view->executionID = $executionID;
  1849. return $this->display();
  1850. }
  1851. $this->view->executionID = $executionID;
  1852. $this->display();
  1853. }
  1854. /**
  1855. * 需求看板。
  1856. * Story kanban.
  1857. *
  1858. * @param int $executionID
  1859. * @access public
  1860. * @return void
  1861. */
  1862. public function storyKanban($executionID)
  1863. {
  1864. $this->app->loadLang('kanban');
  1865. $this->session->set('executionStoryBrowseType', 'unclosed');
  1866. $execution = $this->commonAction($executionID);
  1867. $executionID = $execution->id;
  1868. $stories = $this->loadModel('story')->getExecutionStories($executionID);
  1869. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'story', false);
  1870. $stories = array_filter($stories, function($story) {return in_array($story->stage, $this->config->execution->storyKanbanCols);});
  1871. foreach($stories as $story) unset($story->type);
  1872. /* Get execution's product. */
  1873. $productID = 0;
  1874. $productPairs = $this->loadModel('product')->getProductPairsByProject($executionID);
  1875. if($productPairs)
  1876. {
  1877. $productID = key($productPairs);
  1878. }
  1879. else
  1880. {
  1881. return $this->sendError($this->lang->execution->errorNoLinkedProducts, $this->createLink('execution', 'manageproducts', "executionID=$executionID"));
  1882. }
  1883. $this->app->session->set('executionStoryList', $this->app->getURI(true), 'execution');
  1884. $this->view->title = $this->lang->execution->storyKanban;
  1885. $this->view->kanbanData = $this->story->getKanbanGroupData($stories);
  1886. $this->view->realnames = $this->loadModel('user')->getPairs('noletter');
  1887. $this->view->executionID = $executionID;
  1888. $this->view->execution = $execution;
  1889. $this->view->productID = $productID;
  1890. $this->view->total = count($stories);
  1891. $this->view->product = $this->product->getByID($productID);
  1892. $this->view->canBeChanged = common::canModify('execution', $execution); // Determines whether an object is editable.
  1893. $this->display();
  1894. }
  1895. /**
  1896. * 删除一个执行。
  1897. * Delete a execution.
  1898. *
  1899. * @param int $executionID
  1900. * @param string $confirm yes|no
  1901. * @access public
  1902. * @return void
  1903. */
  1904. public function delete($executionID, $confirm = 'no')
  1905. {
  1906. if($confirm == 'no')
  1907. {
  1908. /* Get the number of unfinished tasks and unresolved bugs. */
  1909. $unfinishedTasks = $this->loadModel('task')->getUnfinishTasks($executionID);
  1910. $unresolvedBugs = $this->loadModel('bug')->getActiveBugs(0, '', (string)$executionID, array());
  1911. $unfinishedTaskCount = count($unfinishedTasks);
  1912. $unresolvedBugCount = count($unresolvedBugs);
  1913. /* Set prompt information. */
  1914. $tips = '';
  1915. if($unfinishedTaskCount) $tips = sprintf($this->lang->execution->unfinishedTask, $unfinishedTaskCount);
  1916. if($unresolvedBugCount) $tips .= sprintf($this->lang->execution->unresolvedBug, $unresolvedBugCount);
  1917. if($tips) $tips = $this->lang->execution->unfinishedExecution . $tips;
  1918. $execution = $this->execution->fetchByID($executionID);
  1919. if($execution->type == 'stage')
  1920. {
  1921. if($tips) $tips = str_replace($this->lang->executionCommon, $this->lang->project->stage, $tips);
  1922. $this->lang->execution->confirmDelete = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->execution->confirmDelete);
  1923. }
  1924. elseif($execution->type == 'kanban')
  1925. {
  1926. global $lang;
  1927. $lang->executionCommon = $lang->execution->kanban;
  1928. include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
  1929. }
  1930. $tips = $tips . sprintf($this->lang->execution->confirmDelete, $execution->name);
  1931. return $this->send(array('callback' => "confirmDeleteExecution({$executionID}, \"{$tips}\")"));
  1932. }
  1933. else
  1934. {
  1935. /* Delete an execution and update the information. */
  1936. $execution = $this->execution->fetchByID($executionID);
  1937. $this->execution->delete(TABLE_EXECUTION, $executionID);
  1938. $this->execution->updateUserView($executionID);
  1939. $project = $this->loadModel('project')->getByID($execution->project);
  1940. if(in_array($project->model, array('waterfall', 'waterfallplus', 'ipd'))) $this->loadModel('programplan')->computeProgress($executionID);
  1941. $this->session->set('execution', '');
  1942. $message = $this->executeHooks($executionID);
  1943. if($message) $this->lang->saveSuccess = $message;
  1944. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess));
  1945. $locate = true;
  1946. if($execution->type == 'kanban')
  1947. {
  1948. $locate = $this->config->vision == 'lite' ? $this->createLink('project', 'execution', "status=all&projectID=$execution->project") : $this->createLink('execution', 'all');
  1949. }
  1950. if($this->app->tab == 'project' && $this->session->executionList) $locate = $this->session->executionList;
  1951. return $this->send(array('result' => 'success', 'load' => $locate));
  1952. }
  1953. }
  1954. /**
  1955. * 维护关联产品。
  1956. * Manage products.
  1957. *
  1958. * @param int $executionID
  1959. * @access public
  1960. * @return void
  1961. */
  1962. public function manageProducts($executionID)
  1963. {
  1964. /* Use first execution if executionID does not exist. */
  1965. if(!isset($this->executions[$executionID])) $executionID = key($this->executions);
  1966. $execution = $this->execution->getByID($executionID);
  1967. $project = $this->project->getByID($execution->project);
  1968. if(!$project->hasProduct) return $this->sendError($this->lang->project->cannotManageProducts, true);
  1969. if($project->model == 'waterfall' || $project->model == 'waterfallplus') return $this->sendError(sprintf($this->lang->execution->cannotManageProducts, zget($this->lang->project->modelList, $project->model)), true);
  1970. /* Set menu. */
  1971. $this->execution->setMenu($execution->id);
  1972. if($_SERVER['REQUEST_METHOD'] == 'POST')
  1973. {
  1974. $oldProducts = $this->loadModel('product')->getProducts($executionID);
  1975. $oldProducts = array_keys($oldProducts);
  1976. $postData = form::data()->get();
  1977. $this->execution->updateProducts($executionID, $postData);
  1978. if(dao::isError()) return $this->sendError(dao::getError());
  1979. $newProducts = $this->product->getProducts($executionID);
  1980. $newProducts = array_keys($newProducts);
  1981. $diffProducts = array_merge(array_diff($oldProducts, $newProducts), array_diff($newProducts, $oldProducts));
  1982. if($diffProducts) $this->loadModel('action')->create($this->objectType, $executionID, 'Managed', '', !empty($_POST['products']) ? implode(',', $_POST['products']) : '');
  1983. return $this->sendSuccess(array('load' => true, 'closeModal' => true));
  1984. }
  1985. $this->executionZen->assignManageProductsVars($execution);
  1986. }
  1987. /**
  1988. * 维护执行的团队成员。
  1989. * Manage members of the execution.
  1990. *
  1991. * @param int $executionID
  1992. * @param int $team2Import the team to import.
  1993. * @param int $dept
  1994. * @access public
  1995. * @return void
  1996. */
  1997. public function manageMembers($executionID = 0, $team2Import = 0, $dept = 0)
  1998. {
  1999. /* Set menu. */
  2000. $this->execution->setMenu($executionID);
  2001. $execution = $this->execution->getByID($executionID);
  2002. if(!empty($_POST))
  2003. {
  2004. $memberDataList = $this->executionZen->buildMembersForManageMembers($execution);
  2005. if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
  2006. $this->execution->manageMembers($execution, $memberDataList);
  2007. if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
  2008. $this->execution->getLimitedExecution();
  2009. if(isInModal()) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "renderTaskAssignedTo($executionID);"));
  2010. $this->execution->getLimitedExecution();
  2011. return $this->sendSuccess(array('load' => $this->createLink('execution', 'team', "executionID={$executionID}")));
  2012. }
  2013. $this->loadModel('dept');
  2014. $deptUsers = empty($dept) ? array() : $this->dept->getDeptUserPairs($dept);
  2015. $currentMembers = $this->execution->getTeamMembers($executionID);
  2016. $members2Import = $this->execution->getMembers2Import($team2Import, array_keys($currentMembers));
  2017. /* Append users for get users. */
  2018. $appendUsers = array();
  2019. foreach($currentMembers as $account => $member) $appendUsers[$account] = $account;
  2020. foreach($members2Import as $member) $appendUsers[$member->account] = $member->account;
  2021. foreach($deptUsers as $deptAccount => $userName) $appendUsers[$deptAccount] = $deptAccount;
  2022. if(!empty($this->config->user->moreLink)) $this->config->moreLinks["accounts[]"] = $this->config->user->moreLink;
  2023. if($execution->type == 'kanban') $this->lang->execution->copyTeamTitle = str_replace($this->lang->execution->common, $this->lang->execution->kanban, $this->lang->execution->copyTeamTitle);
  2024. $users = $this->loadModel('user')->getPairs('noclosed|nodeleted|devfirst', $appendUsers);
  2025. $userItems = array();
  2026. foreach($users as $account => $realName) $userItems[$account] = array('value' => $account, 'text' => $realName, 'keys' => $account, 'disabled' => false);
  2027. $this->view->title = $this->lang->execution->manageMembers . $this->lang->hyphen . $execution->name;
  2028. $this->view->execution = $execution;
  2029. $this->view->users = $users;
  2030. $this->view->userItems = $userItems;
  2031. $this->view->roles = $this->user->getUserRoles(array_keys($this->view->users));
  2032. $this->view->dept = $dept;
  2033. $this->view->depts = $this->dept->getOptionMenu();
  2034. $this->view->teams2Import = $this->loadModel('personnel')->getCopiedObjects($executionID, 'sprint', true);
  2035. $this->view->team2Import = $team2Import;
  2036. $this->view->teamMembers = $this->executionZen->buildMembers($currentMembers, $members2Import, $deptUsers, $execution->days);
  2037. $this->display();
  2038. }
  2039. /**
  2040. * 移除团队成员。
  2041. * Unlink a memeber.
  2042. *
  2043. * @param int $executionID
  2044. * @param int $userID
  2045. * @access public
  2046. * @return void
  2047. */
  2048. public function unlinkMember($executionID, $userID)
  2049. {
  2050. $user = $this->loadModel('user')->getById($userID, 'id');
  2051. $this->execution->unlinkMember($executionID, $user->account);
  2052. if(dao::isError()) return $this->sendError(dao::getError());
  2053. $this->execution->getLimitedExecution();
  2054. $changes = array(array('field' => 'removeDiff', 'old' => '', 'new' => '', 'diff' => $user->realname));
  2055. $actionID = $this->loadModel('action')->create('execution', $executionID, 'managedTeam');
  2056. $this->action->logHistory($actionID, $changes);
  2057. return $this->sendSuccess(array('message' => '', 'load' => helper::createLink('execution', 'team', "executionID={$executionID}")));
  2058. }
  2059. /**
  2060. * 在迭代中关联需求。
  2061. * Link stories in the execution.
  2062. *
  2063. * @param int $objectID
  2064. * @param string $browseType
  2065. * @param int $param
  2066. * @param string $orderBy
  2067. * @param int $recPerPage
  2068. * @param int $pageID
  2069. * @param string $extra
  2070. * @param string $storyType
  2071. * @access public
  2072. * @return void
  2073. */
  2074. public function linkStory($objectID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recPerPage = 50, $pageID = 1, $extra = '', $storyType = 'story')
  2075. {
  2076. $this->loadModel('story');
  2077. $this->loadModel('product');
  2078. $this->loadModel('tree');
  2079. $this->loadModel('branch');
  2080. /* Save old objectID to use later. */
  2081. $originObjectID = $objectID;
  2082. /* Use the projectID of Kanban as objectID if vision is lite. */
  2083. if($this->config->vision == 'lite')
  2084. {
  2085. $kanban = $this->project->getByID($objectID, 'kanban');
  2086. if($kanban) $objectID = $kanban->project;
  2087. }
  2088. $products = $this->product->getProducts($objectID);
  2089. if(empty($products)) return $this->sendError($this->lang->execution->errorNoLinkedProducts, $this->createLink('execution', 'manageproducts', "executionID=$objectID"));
  2090. /* 通过objectID获取符合项目、执行、阶段和看板类型的对象。*/
  2091. $object = $this->project->getByID($objectID, 'project,sprint,stage,kanban');
  2092. $browseLink = $this->createLink('execution', 'story', "executionID=$objectID");
  2093. if($this->app->tab == 'project' and $object->multiple) $browseLink = $this->createLink('projectstory', 'story', "objectID=$objectID");
  2094. if($object->type == 'kanban' && !$object->hasProduct) $this->lang->productCommon = $this->lang->project->common;
  2095. $this->session->set('storyList', $this->app->getURI(true), $this->app->tab);
  2096. if($object->type == 'project') $this->project->setMenu($object->id);
  2097. if(in_array($object->type, array('sprint', 'stage', 'kanban'))) $this->execution->setMenu($object->id);
  2098. if(!empty($_POST))
  2099. {
  2100. if($object->type != 'project' and $object->project != 0) $this->execution->linkStory($object->project, $this->post->stories ? $this->post->stories : array(), '', array(), $storyType);
  2101. $this->execution->linkStory($objectID, $this->post->stories ? $this->post->stories : array(), $extra, array(), $storyType);
  2102. if(isInModal()) return $this->sendSuccess(array('closeModal' => true, 'load' => true, 'callback' => "refreshKanban()"));
  2103. return $this->sendSuccess(array('load' => $browseLink));
  2104. }
  2105. /* Set modules and branches. */
  2106. $modules = array();
  2107. $branchIDList = array(BRANCH_MAIN);
  2108. $branches = $this->project->getBranchesByProject($objectID);
  2109. $productType = 'normal';
  2110. if(commonModel::isTutorialMode())
  2111. {
  2112. $modules = $this->loadModel('tutorial')->getModulePairs();
  2113. }
  2114. else
  2115. {
  2116. foreach($products as $product)
  2117. {
  2118. $productModules = $this->tree->getOptionMenu($product->id, 'story', 0, array_keys($branches[$product->id]));
  2119. foreach($productModules as $branchModules)
  2120. {
  2121. foreach($branchModules as $moduleID => $moduleName) $modules[$moduleID] = ((count($products) >= 2 and $moduleID != 0) ? $product->name : '') . $moduleName;
  2122. }
  2123. if($product->type != 'normal')
  2124. {
  2125. $productType = $product->type;
  2126. if(isset($branches[$product->id]))
  2127. {
  2128. foreach($branches[$product->id] as $branchID => $branch) $branchIDList[$branchID] = $branchID;
  2129. }
  2130. }
  2131. }
  2132. }
  2133. /* Build the search form. */
  2134. $actionURL = $this->createLink($this->app->rawModule, 'linkStory', "objectID=$objectID&browseType=bySearch&queryID=myQueryID&orderBy=$orderBy&recPerPage=$recPerPage&pageID=$pageID&extra=$extra&storyType=$storyType");
  2135. $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products));
  2136. $queryID = ($browseType == 'bySearch') ? (int)$param : 0;
  2137. $this->execution->buildStorySearchForm($products, $branchGroups, $modules, $queryID, $actionURL, 'linkStory', $object);
  2138. $project = (strpos('sprint,stage,kanban', $object->type) !== false) ? $this->loadModel('project')->getByID($object->project) : $object;
  2139. $storyType = (($object->type == 'stage' && in_array($object->attribute, array('mix', 'request', 'design'))) || $object->type == 'project' || !$object->multiple) ? ($project->storyType ?? 'story') : 'story';
  2140. if($browseType == 'bySearch') $allStories = $this->story->getBySearch('all', '', $queryID, $orderBy, $objectID, $storyType);
  2141. if($browseType != 'bySearch') $allStories = $this->story->getProductStories(implode(',', array_keys($products)), $branchIDList, '0', 'active,launched', $storyType, $orderBy, true, '', null);
  2142. $linkedStories = $this->story->getExecutionStoryPairs($objectID, 0, 'all', 0, 'full', 'all', $storyType);
  2143. foreach($allStories as $id => $story)
  2144. {
  2145. if(isset($linkedStories[$story->id])) unset($allStories[$id]);
  2146. if(!isset($modules[$story->module]))
  2147. {
  2148. $storyModule = $this->tree->getModulesName((array)$story->module);
  2149. $productName = count($products) > 1 ? $products[$story->product]->name : '';
  2150. $modules[$story->module] = $productName . zget($storyModule, $story->module, '');
  2151. }
  2152. $story->estimate = $story->estimate . $this->config->hourUnit;
  2153. }
  2154. $gradeGroup = array();
  2155. $gradeList = $this->story->getGradeList('');
  2156. foreach($gradeList as $grade) $gradeGroup[$grade->type][$grade->grade] = $grade->name;
  2157. /* Set the pager. */
  2158. $this->app->loadClass('pager', true);
  2159. $pager = new pager(count($allStories), $recPerPage, $pageID);
  2160. $allStories = array_chunk($allStories, $pager->recPerPage);
  2161. $productPairs = array();
  2162. foreach($products as $id => $product) $productPairs[$id] = $product->name;
  2163. $this->view->title = $object->name . $this->lang->hyphen . $this->lang->execution->linkStory;
  2164. $this->view->objectID = $originObjectID;
  2165. $this->view->param = $param;
  2166. $this->view->extra = $extra;
  2167. $this->view->object = $object;
  2168. $this->view->orderBy = $orderBy;
  2169. $this->view->gradeGroup = $gradeGroup;
  2170. $this->view->showGrade = $this->config->edition == 'ipd';
  2171. $this->view->productPairs = $productPairs;
  2172. $this->view->allStories = empty($allStories) ? $allStories : $allStories[$pageID - 1];
  2173. $this->view->pager = $pager;
  2174. $this->view->browseType = $browseType;
  2175. $this->view->productType = $productType;
  2176. $this->view->modules = $modules;
  2177. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  2178. $this->view->branchGroups = $branchGroups;
  2179. $this->view->browseLink = $browseLink;
  2180. $this->view->project = $project;
  2181. $this->view->executionID = $object->id;
  2182. $this->view->projectID = $object->id;
  2183. $this->view->storyType = $storyType;
  2184. if($this->config->edition == 'ipd') $this->view->roadmaps = $this->loadModel('roadmap')->getPairs(array_keys($productPairs));
  2185. $this->display();
  2186. }
  2187. /**
  2188. * 取消关联需求(移除需求)。
  2189. * Unlink a story.
  2190. *
  2191. * @param int $executionID
  2192. * @param int $storyID
  2193. * @param string $confirm yes|no
  2194. * @param string $from taskkanban
  2195. * @param int $laneID
  2196. * @param int $columnID
  2197. * @access public
  2198. * @return void
  2199. */
  2200. public function unlinkStory($executionID, $storyID, $confirm = 'no', $from = '', $laneID = 0, $columnID = 0)
  2201. {
  2202. if($confirm == 'no')
  2203. {
  2204. $confirmURL = $this->createLink('execution', 'unlinkstory', "executionID=$executionID&storyID=$storyID&confirm=yes&from=$from&laneID=$laneID&columnID=$columnID");
  2205. $tip = $this->app->rawModule == 'projectstory' ? $this->lang->execution->confirmUnlinkExecutionStory : $this->lang->execution->confirmUnlinkStory;
  2206. $story = $this->loadModel('story')->getByID($storyID);
  2207. if($story->type == 'requirement') $tip = str_replace($this->lang->SRCommon, $this->lang->URCommon, $tip);
  2208. return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$tip}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '$confirmURL'});});"));
  2209. }
  2210. $execution = $this->execution->getByID($executionID);
  2211. $this->execution->unlinkStory($executionID, $storyID, $laneID, $columnID);
  2212. if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
  2213. if($execution->type == 'kanban')
  2214. {
  2215. /* Fix bug #29171. */
  2216. $executions = $this->dao->select('*')->from(TABLE_EXECUTION)->where('parent')->eq($execution->parent)->fetchAll('id');
  2217. $executionStories = $this->dao->select('project,story')->from(TABLE_PROJECTSTORY)->where('story')->eq($storyID)->andWhere('project')->in(array_keys($executions))->fetchAll();
  2218. if(empty($executionStories)) $this->execution->unlinkStory($execution->parent, $storyID, $laneID, $columnID);
  2219. }
  2220. if($this->app->tab == 'execution' and $execution->type == 'kanban' or $from == 'taskkanban') return $this->send(array('result' => 'success', 'closeModal' => true, 'callback' => "refreshKanban()"));
  2221. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true));
  2222. }
  2223. /**
  2224. * 批量取消关联需求(移除需求)。
  2225. * Batch unlink story.
  2226. *
  2227. * @param int $executionID
  2228. * @access public
  2229. * @return void
  2230. */
  2231. public function batchUnlinkStory($executionID)
  2232. {
  2233. if(isset($_POST['storyIdList']))
  2234. {
  2235. $storyIdList = $this->post->storyIdList;
  2236. $_POST = array();
  2237. $this->loadModel('gitlab');
  2238. foreach($storyIdList as $storyID)
  2239. {
  2240. /* Delete related issue in gitlab. */
  2241. $relation = $this->gitlab->getRelationByObject('story', (int)$storyID);
  2242. if(!empty($relation)) $this->gitlab->deleteIssue('story', (int)$storyID, $relation->issueID);
  2243. $this->execution->unlinkStory($executionID, (int)$storyID);
  2244. }
  2245. }
  2246. if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchOther');
  2247. $execution = $this->execution->getByID($executionID);
  2248. return $this->send(array('result' => 'success', 'load' => array('alert' => $this->lang->execution->confirmBatchUnlinkStory, 'locate' => $this->createLink('execution', 'story', "executionID=$executionID") . ($execution->multiple ? '' : '#app=project'))));
  2249. }
  2250. /**
  2251. * 获取执行动态。
  2252. * Get the dynamic of execution.
  2253. *
  2254. * @param int $executionID
  2255. * @param string $type
  2256. * @param string $param
  2257. * @param int $recTotal
  2258. * @param string $date
  2259. * @param string $direction next|pre
  2260. * @access public
  2261. * @return void
  2262. */
  2263. public function dynamic($executionID = 0, $type = 'today', $param = '', $recTotal = 0, $date = '', $direction = 'next')
  2264. {
  2265. if(empty($type)) $type = 'today';
  2266. /* Save session. */
  2267. $uri = $this->app->getURI(true);
  2268. $this->session->set('productList', $uri, 'product');
  2269. $this->session->set('productPlanList', $uri, 'product');
  2270. $this->session->set('releaseList', $uri, 'product');
  2271. $this->session->set('storyList', $uri, 'product');
  2272. $this->session->set('executionList', $uri, 'execution');
  2273. $this->session->set('taskList', $uri, 'execution');
  2274. $this->session->set('buildList', $uri, 'execution');
  2275. $this->session->set('bugList', $uri, 'qa');
  2276. $this->session->set('caseList', $uri, 'qa');
  2277. $this->session->set('testtaskList', $uri, 'qa');
  2278. $this->session->set('reportList', $uri, 'qa');
  2279. /* use first execution if executionID does not exist. */
  2280. $executionID = $this->execution->checkAccess($executionID, $this->executions);
  2281. $this->execution->setMenu($executionID);
  2282. $execution = $this->execution->getByID($executionID);
  2283. /* Set the user and type. */
  2284. $account = 'all';
  2285. if($type == 'account')
  2286. {
  2287. $user = $this->loadModel('user')->getById((int)$param, 'id'); /* 通过 id 字段获取用户。另一种是通过 account 获取。*/
  2288. if($user) $account = $user->account;
  2289. }
  2290. /* 获取这个时间段的操作日志列表。*/
  2291. $period = $type == 'account' ? 'all' : $type;
  2292. $orderBy = $direction == 'next' ? 'date_desc' : 'date_asc';
  2293. $date = empty($date) ? '' : date('Y-m-d', (int)$date);
  2294. $actions = $this->loadModel('action')->getDynamicByExecution($executionID, $account, $period, $orderBy, 50, $date, $direction);
  2295. $dateGroups = $this->action->buildDateGroup($actions, $direction);
  2296. if(empty($recTotal) && $dateGroups) $recTotal = $this->action->getDynamicCount($period);
  2297. $this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->dynamic;
  2298. $this->view->userIdPairs = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted|useid');
  2299. $this->view->accountPairs = $this->loadModel('user')->getPairs('noletter|nodeleted');
  2300. $this->view->executionID = $executionID;
  2301. $this->view->type = $type;
  2302. $this->view->orderBy = $orderBy;
  2303. $this->view->account = $account;
  2304. $this->view->param = $param;
  2305. $this->view->dateGroups = $dateGroups; /* 将日志按照日期分组,以日期为索引,将日志列表作为值。*/
  2306. $this->view->direction = $direction;
  2307. $this->view->recTotal = $recTotal;
  2308. $this->display();
  2309. }
  2310. /**
  2311. * 通过执行ID获取产品。
  2312. * AJAX: get products of a execution in html select.
  2313. *
  2314. * @param int $executionID
  2315. * @access public
  2316. * @return void
  2317. */
  2318. public function ajaxGetProducts($executionID)
  2319. {
  2320. $products = $this->loadModel('product')->getProducts($executionID, 'all', '', false);
  2321. return print(html::select('product', $products, '', 'class="form-control"'));
  2322. }
  2323. /**
  2324. * 通过执行ID/项目ID和指派给获取团队成员。
  2325. * AJAX: get team members of the execution.
  2326. *
  2327. * @param int $objectID
  2328. * @param string $assignedTo
  2329. * @access public
  2330. * @return void
  2331. */
  2332. public function ajaxGetMembers($objectID, $assignedTo = '')
  2333. {
  2334. $object = $this->execution->fetchByID($objectID);
  2335. $users = $this->loadModel('user')->getTeamMemberPairs($objectID, $object->type == 'project' ? 'project' : 'execution');
  2336. if($this->app->getViewType() === 'json') return print(json_encode($users));
  2337. $items = array();
  2338. foreach($users as $account => $realName) $items[] = array('value' => $account, 'text' => $realName, 'keys' => $account);
  2339. return print(json_encode($items));
  2340. }
  2341. /**
  2342. * 通过项目ID或执行ID获取团队成员。
  2343. * AJAX: get team members by projectID/executionID.
  2344. *
  2345. * @param int $objectID
  2346. * @access public
  2347. * @return string
  2348. */
  2349. public function ajaxGetTeamMembers($objectID)
  2350. {
  2351. $objectType = $this->dao->select('`type`')->from(TABLE_PROJECT)->where('id')->eq($objectID)->fetch('type');
  2352. if($objectType != 'project') $objectType = 'execution';
  2353. $members = $this->loadModel('user')->getTeamMemberPairs($objectID, $objectType);
  2354. $selectedMembers = array();
  2355. foreach($members as $account => $realName) $selectedMembers[] = $account;
  2356. return print(json_encode($selectedMembers));
  2357. }
  2358. /**
  2359. * 在用户创建完执行后,给用户发送一个后续操作的提示(比如设置团队、关联软件需求等)方便引导用户使用。
  2360. * Show tips to guide the user to do something after the execution created successfully.
  2361. *
  2362. * @param int $executionID
  2363. * @access public
  2364. * @return void
  2365. */
  2366. public function tips($executionID)
  2367. {
  2368. $execution = $this->execution->getById($executionID);
  2369. $this->view->executionID = $executionID;
  2370. $this->view->execution = $execution;
  2371. $this->view->projectID = $execution->project;
  2372. $this->display();
  2373. }
  2374. /**
  2375. * 获取执行下拉列表用于切换不同的执行。
  2376. * Drop menu page.
  2377. *
  2378. * @param int $executionID 已经打开的页面对应的执行ID
  2379. * @param string $module 链接里要访问的模块
  2380. * @param string $method 链接里要访问的方法
  2381. * @param string $extra 给链接传入的额外的参数
  2382. * @access public
  2383. * @return void
  2384. */
  2385. public function ajaxGetDropMenu($executionID, $module = 'execution', $method = 'task', $extra = '')
  2386. {
  2387. $execution = $this->execution->fetchByID($executionID);
  2388. if(!empty($execution->isTpl)) dao::$filterTpl = 'never';
  2389. $this->view->link = $this->executionZen->getLink($module, $method, $extra);
  2390. $this->view->module = $module;
  2391. $this->view->method = $method;
  2392. $this->view->executionID = $executionID;
  2393. $this->view->extra = $extra;
  2394. $projects = $this->loadModel('program')->getProjectList(0, 'all', 0, 'order_asc', '', true); /* 获取所有项目的列表。*/
  2395. $executionGroups = $this->dao->select('*')->from(TABLE_EXECUTION) /* 按照项目分组,获取有权限访问的执行列表。*/
  2396. ->where('deleted')->eq('0')
  2397. ->andWhere('multiple')->eq('1')
  2398. ->andWhere('type')->in(!empty($execution->isTpl) ? 'sprint,stage' : 'sprint,stage,kanban')
  2399. ->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
  2400. ->beginIF(!empty($execution->isTpl))->andWhere('isTpl')->eq('1')->fi()
  2401. ->andWhere('project')->in(array_keys($projects))
  2402. ->orderBy('order_asc')
  2403. ->fetchGroup('project', 'id');
  2404. $teams = $this->dao->select('root,account')->from(TABLE_TEAM) /* 按照执行ID分组,获取有权限访问的执行的团队信息。*/
  2405. ->where('root')->in($this->app->user->view->sprints)
  2406. ->andWhere('type')->eq('execution')
  2407. ->fetchGroup('root', 'account');
  2408. $projectPairs = array(); /* 项目ID为索引,项目名称为值的数组 [projectID => projectName]。 */
  2409. $orderedExecutions = array();
  2410. $parentExecutions = array();
  2411. foreach($projects as $project)
  2412. {
  2413. $executions = zget($executionGroups, $project->id, array());
  2414. if(isset($project->model) and $project->model == 'waterfall') ksort($executions);
  2415. $topExecutions = array();
  2416. foreach($executions as $execution)
  2417. {
  2418. if($execution->grade == 1) $topExecutions[$execution->id] = $execution->id;
  2419. if($execution->grade > 1 && $execution->parent) $parentExecutions[$execution->parent][$execution->id] = $execution;
  2420. }
  2421. /* 获取排序后的执行列表,并给每个执行设置团队信息。*/
  2422. $executions = $this->execution->resetExecutionSorts($executions, $topExecutions, $parentExecutions);
  2423. foreach($executions as $execution)
  2424. {
  2425. $execution->teams = zget($teams, $execution->id, array());
  2426. $orderedExecutions[$execution->id] = $execution;
  2427. }
  2428. $projectPairs[$project->id] = $project->name;
  2429. }
  2430. $executionNameList = $this->execution->getFullNameList($orderedExecutions);
  2431. $projectExecutions = array(); /* 项目对应的执行列表 [projectID => execution]。*/
  2432. foreach($orderedExecutions as $execution)
  2433. {
  2434. if(isset($parentExecutions[$execution->id])) continue;
  2435. $execution->name = $executionNameList[$execution->id];
  2436. $projectExecutions[$execution->project][] = $execution;
  2437. }
  2438. $this->view->projects = $projectPairs; /* 项目ID为索引,项目名称为值的数组 [projectID => projectName]。 */
  2439. $this->view->projectExecutions = $projectExecutions; /* 项目对应的执行列表 [projectID => execution]。*/
  2440. $this->view->executionID = $executionID;
  2441. $this->view->execution = $execution;
  2442. $this->display();
  2443. }
  2444. /**
  2445. * 将执行列表内的执行进行排序。
  2446. * Update the order of the executions.
  2447. *
  2448. * @access public
  2449. * @return void
  2450. */
  2451. public function updateOrder()
  2452. {
  2453. if($_SERVER['REQUEST_METHOD'] !== 'POST') return $this->sendError($this->lang->error->unsupportedReq);
  2454. $idList = explode(',', trim($this->post->executions, ','));
  2455. $orderBy = $this->post->orderBy;
  2456. if(strpos($orderBy, 'order') === false) return false;
  2457. $executions = $this->dao->select('id,`order`')->from(TABLE_EXECUTION)->where('id')->in($idList)->orderBy($orderBy)->fetchPairs('order', 'id');
  2458. foreach($executions as $order => $id)
  2459. {
  2460. $newID = array_shift($idList);
  2461. if($id == $newID) continue;
  2462. $this->dao->update(TABLE_EXECUTION)->set('`order`')->eq($order)->where('id')->eq($newID)->exec();
  2463. if(dao::isError()) return $this->sendError(dao::getError());
  2464. }
  2465. return $this->sendSuccess();
  2466. }
  2467. /**
  2468. * 需求排序。
  2469. * Sort stories.
  2470. *
  2471. * @param int $executionID
  2472. * @access public
  2473. * @return void
  2474. */
  2475. public function storySort($executionID)
  2476. {
  2477. $storyIdList = json_decode($this->post->storyIdList, true);
  2478. $storyIdList = array_flip($storyIdList);
  2479. $orderBy = $this->post->orderBy;
  2480. krsort($storyIdList);
  2481. $order = $this->dao->select('`order`')->from(TABLE_PROJECTSTORY)->where('story')->in($storyIdList)->andWhere('project')->eq($executionID)->orderBy('order_asc')->fetch('order');
  2482. foreach($storyIdList as $storyID)
  2483. {
  2484. $this->dao->update(TABLE_PROJECTSTORY)->set('`order`')->eq($order)->where('story')->eq($storyID)->andWhere('project')->eq($executionID)->exec();
  2485. $order++;
  2486. }
  2487. return $this->sendSuccess();
  2488. }
  2489. /**
  2490. * 在执行需求列表内进行需求估算。
  2491. * Story estimate.
  2492. *
  2493. * @param int $executionID
  2494. * @param int $storyID
  2495. * @param int $round The round of estimate for stroy 估算的轮次
  2496. * @access public
  2497. * @return void
  2498. */
  2499. public function storyEstimate($executionID, $storyID, $round = 0)
  2500. {
  2501. $this->loadModel('story');
  2502. if($_POST)
  2503. {
  2504. $this->story->saveEstimateInfo($storyID);
  2505. if(dao::isError()) return $this->sendError(dao::getError());
  2506. $this->loadModel('action')->create('story', $storyID, 'estimated', '', $executionID);
  2507. $link = $this->createLink('execution', 'storyEstimate', "executionID={$executionID}&storyID={$storyID}&round={$round}");
  2508. return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'callback' => "loadModal(\"$link\", '#storyEstimateModal');"));
  2509. }
  2510. $estimateInfo = $this->story->getEstimateInfo($storyID, $round);
  2511. $team = $this->execution->getTeamMembers($executionID);
  2512. /* If user has been removed from team and has estimate record, join him into team.*/
  2513. if(!empty($estimateInfo->estimate))
  2514. {
  2515. foreach($estimateInfo->estimate as $account => $estimate)
  2516. {
  2517. if(!in_array($account, array_keys($team)))
  2518. {
  2519. $team[$account] = new stdclass();
  2520. $team[$account]->account = $account;
  2521. }
  2522. }
  2523. }
  2524. $this->view->estimateInfo = $estimateInfo;
  2525. $this->view->round = !empty($estimateInfo->round) ? $estimateInfo->round : 0;
  2526. $this->view->rounds = $this->story->getEstimateRounds($storyID);
  2527. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  2528. $this->view->team = $team;
  2529. $this->view->executionID = $executionID;
  2530. $this->view->storyID = $storyID;
  2531. $this->view->story = $this->story->getById($storyID);
  2532. $this->display();
  2533. }
  2534. /**
  2535. * 查看执行列表。
  2536. * All execution.
  2537. *
  2538. * @param string $status
  2539. * @param string $orderBy
  2540. * @param int $productID
  2541. * @param string $param
  2542. * @param int $recTotal
  2543. * @param int $recPerPage
  2544. * @param int $pageID
  2545. * @access public
  2546. * @return void
  2547. */
  2548. public function all($status = 'undone', $orderBy = 'order_asc', $productID = 0, $param = '', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  2549. {
  2550. $this->app->loadLang('my');
  2551. $this->app->loadLang('stage');
  2552. $this->app->loadLang('programplan');
  2553. $this->loadModel('product');
  2554. $this->loadModel('project');
  2555. $this->loadModel('datatable');
  2556. $from = $this->app->tab;
  2557. if($from == 'execution') $this->session->set('executionList', $this->app->getURI(true), 'execution');
  2558. if($this->app->viewType == 'mhtml')
  2559. {
  2560. $executionID = $this->execution->checkAccess(0, $this->executions);
  2561. $this->execution->setMenu($executionID);
  2562. }
  2563. /* Load pager and get tasks. */
  2564. $this->app->loadClass('pager', true);
  2565. $pager = new pager($recTotal, $recPerPage, $pageID);
  2566. $queryID = ($status == 'bySearch') ? (int)$param : 0;
  2567. $actionURL = $this->createLink('execution', 'all', "status=bySearch&orderBy=$orderBy&productID=$productID&param=myQueryID");
  2568. $this->execution->buildSearchForm($queryID, $actionURL);
  2569. $sort = $orderBy;
  2570. if(strpos($sort, 'rawID_') !== false) $sort = str_replace('rawID_', 'id_', $sort);
  2571. if(strpos($sort, 'nameCol_') !== false) $sort = str_replace('nameCol_', 'name_', $sort);
  2572. $executionStats = $this->execution->getStatData(0, $status, $productID, 0, false, $queryID, $sort, $pager);
  2573. $this->view->title = $this->lang->execution->allExecutions;
  2574. $this->view->executionStats = $executionStats;
  2575. $this->view->allExecutionsNum = $this->execution->getExecutionCounts(0, 'all');
  2576. $this->view->productList = $this->loadModel('product')->getProductPairsByProject(0);
  2577. $this->view->productID = $productID;
  2578. $this->view->pager = $pager;
  2579. $this->view->orderBy = $orderBy;
  2580. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  2581. $this->view->projects = array('') + $this->project->getPairsByProgram();
  2582. $this->view->status = $status;
  2583. $this->view->from = $from;
  2584. $this->view->param = $param;
  2585. $this->view->showBatchEdit = $this->cookie->showExecutionBatchEdit;
  2586. $this->view->avatarList = $this->user->getAvatarPairs('');
  2587. $this->display();
  2588. }
  2589. /**
  2590. * 显示白名单信息。
  2591. * Get white list personnel.
  2592. *
  2593. * @param int $executionID
  2594. * @param string $module
  2595. * @param string $objectType
  2596. * @param string $orderby
  2597. * @param int $recTotal
  2598. * @param int $recPerPage
  2599. * @param int $pageID
  2600. * @access public
  2601. * @return void
  2602. * @param string $orderBy
  2603. */
  2604. public function whitelist($executionID = 0, $module='execution', $objectType = 'sprint', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
  2605. {
  2606. /* Set the menu. If the executionID = 0, use the indexMenu instead. */
  2607. $execution = $this->commonAction($executionID);
  2608. if(!empty($execution->acl) and $execution->acl != 'private') return $this->sendError($this->lang->whitelistNotNeed, $this->createLink('execution', 'task', "executionID=$executionID"));
  2609. echo $this->fetch('personnel', 'whitelist', "objectID=$executionID&module=$module&browseType=$objectType&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID");
  2610. }
  2611. /**
  2612. * 添加用户到白名单。
  2613. * Adding users to the white list.
  2614. *
  2615. * @param int $executionID
  2616. * @param int $deptID
  2617. * @param int $copyID
  2618. * @access public
  2619. * @return void
  2620. */
  2621. public function addWhitelist($executionID = 0, $deptID = 0, $copyID = 0)
  2622. {
  2623. /* use first execution if executionID does not exist. */
  2624. if(!isset($this->executions[$executionID])) $executionID = key($this->executions);
  2625. /* Set the menu. If the executionID = 0, use the indexMenu instead. */
  2626. $this->execution->setMenu($executionID);
  2627. $execution = $this->execution->getByID($executionID);
  2628. if(!empty($execution->acl) and $execution->acl != 'private') return $this->sendError($this->lang->whitelistNotNeed, $this->createLink('execution', 'task', "executionID=$executionID"));
  2629. echo $this->fetch('personnel', 'addWhitelist', "objectID=$executionID&dept=$deptID&copyID=$copyID&objectType=sprint&module=execution");
  2630. }
  2631. /*
  2632. * 从白名单中移除用户。
  2633. * Removing users from the white list.
  2634. *
  2635. * @param int $id
  2636. * @param string $confirm
  2637. * @access public
  2638. * @return void
  2639. */
  2640. /**
  2641. * @param int $id
  2642. * @param string $confirm
  2643. */
  2644. public function unbindWhitelist($id = 0, $confirm = 'no')
  2645. {
  2646. echo $this->fetch('personnel', 'unbindWhitelist', "id=$id&confirm=$confirm");
  2647. }
  2648. /**
  2649. * 执行列表导出数据。
  2650. * Export execution.
  2651. *
  2652. * @param string $status
  2653. * @param int $productID
  2654. * @param string $orderBy
  2655. * @param string $from
  2656. * @access public
  2657. * @return void
  2658. */
  2659. public function export($status, $productID, $orderBy, $from)
  2660. {
  2661. $lowerStatus = strtolower($status);
  2662. if($_POST)
  2663. {
  2664. $executionLang = $this->lang->execution;
  2665. $executionConfig = $this->config->execution;
  2666. $projectExecutionDtable = $this->config->project->execution->dtable->fieldList;
  2667. $projectID = $from == 'project' ? $this->session->project : 0;
  2668. if($projectID) $this->project->setMenu($projectID);
  2669. $project = $this->project->getByID($projectID);
  2670. /* Create field lists. */
  2671. $fields = $this->post->exportFields ? $this->post->exportFields : explode(',', $executionConfig->list->exportFields);
  2672. foreach($fields as $key => $fieldName)
  2673. {
  2674. if($fieldName == 'type' and $this->app->tab == 'project' and ($project->model == 'agileplus' or $project->model == 'waterfallplus')) $fields['method'] = $executionLang->method;
  2675. $fieldName = trim($fieldName);
  2676. $fields[$fieldName] = $from == 'project' && !empty($projectExecutionDtable[$fieldName]) ? $projectExecutionDtable[$fieldName]['title'] : zget($executionLang, $fieldName);
  2677. if($fieldName == 'type') $fields['type'] = $this->lang->typeAB;
  2678. unset($fields[$key]);
  2679. }
  2680. if(isset($fields['id'])) $fields['id'] = $this->lang->idAB;
  2681. unset($fields[$from == 'project' ? 'projectName' : 'productName']);
  2682. $users = $this->loadModel('user')->getPairs('noletter');
  2683. $showTask = ($this->app->tab == 'project' && (bool)$this->cookie->showTask);
  2684. if($showTask && $lowerStatus == 'bysearch')
  2685. {
  2686. $executionIdList = $this->loadModel('programplan')->getStageList($projectID, $productID, 'all', 'order');
  2687. $tasks = $this->programplan->getGanttTasks($projectID, array_keys($executionIdList), $lowerStatus, 0, null);
  2688. $executionTasks = array();
  2689. $executionIdList = array();
  2690. foreach($tasks as $task)
  2691. {
  2692. $executionIdList[$task->execution] = $task->execution;
  2693. if(!isset($executionTasks[$task->execution])) $executionTasks[$task->execution] = array();
  2694. $executionTasks[$task->execution][$task->id] = $task;
  2695. }
  2696. $executions = $this->execution->getByIdList($executionIdList);
  2697. $executions = $this->execution->batchProcessExecution($executions, $projectID, $productID, true, '', $executionTasks);
  2698. $executionStats = array_values($executions);
  2699. }
  2700. else
  2701. {
  2702. $executionStats = $this->execution->getStatData($projectID, $lowerStatus == 'byproduct' ? 'all' : $status, $productID, 0, (bool)$showTask, 'withchild', $orderBy);
  2703. }
  2704. $executionStats = $this->flattenObjectArray($executionStats);
  2705. $rows = array();
  2706. $checkedItem = $this->cookie->checkedItem;
  2707. foreach($executionStats as $i => $execution)
  2708. {
  2709. if($this->post->exportType == 'selected' && strpos(",$checkedItem,", ",pid{$execution->id},") === false) continue;
  2710. $execution->PM = zget($users, $execution->PM);
  2711. $execution->status = !empty($execution->delay) ? $executionLang->delayed : $this->processStatus('execution', $execution);
  2712. $execution->progress .= '%';
  2713. $execution->name = isset($execution->title) ? $execution->title : $execution->name;
  2714. $execution->type = zget($this->lang->execution->typeList, $execution->type, $this->lang->executionCommon);
  2715. if(isset($executionStats[$execution->parent])) $execution->name = $executionStats[$execution->parent]->name . '/' . $execution->name;
  2716. if($this->app->tab == 'project' and ($project->model == 'agileplus' or $project->model == 'waterfallplus')) $execution->method = zget($executionLang->typeList, $execution->type);
  2717. $rows[] = $execution;
  2718. if(empty($execution->tasks)) continue;
  2719. foreach($execution->tasks as $task)
  2720. {
  2721. if($task->isParent)
  2722. {
  2723. $task->name = '[' . $this->lang->task->parentAB . '] ' . $task->name;
  2724. }
  2725. elseif($task->parent > 0)
  2726. {
  2727. $task->name = '[' . $this->lang->task->childrenAB . '] ' . $task->name;
  2728. }
  2729. $task->type = $this->lang->task->common;
  2730. $task->status = zget($this->lang->task->statusList, $task->status);
  2731. $task->totalEstimate = $task->estimate;
  2732. $task->totalConsumed = $task->consumed;
  2733. $task->totalLeft = $task->left;
  2734. $task->progress = (($task->consumed + $task->left) == 0 ? 0 : round($task->consumed / ($task->consumed + $task->left), 4) * 100) . '%';
  2735. $task->begin = !helper::isZeroDate($task->estStarted) ? $task->estStarted : '';
  2736. $task->end = !helper::isZeroDate($task->deadline) ? $task->deadline : '';
  2737. $task->realBegan = !helper::isZeroDate($task->realStarted) ? $task->realStarted : '';
  2738. $task->realEnd = !helper::isZeroDate($task->finishedDate) ? $task->finishedDate : '';
  2739. $task->PM = zget($users, $task->assignedTo);
  2740. $rows[] = $task;
  2741. }
  2742. }
  2743. if($this->config->edition != 'open') list($fields, $executionStats) = $this->loadModel('workflowfield')->appendDataFromFlow($fields, $executionStats);
  2744. $this->post->set('fields', $fields);
  2745. $this->post->set('rows', $rows);
  2746. $this->post->set('kind', $this->lang->execution->common);
  2747. $this->fetch('file', 'export2' . $this->post->fileType, $_POST);
  2748. }
  2749. $executionConcept = $this->lang->execution->common;
  2750. if($this->app->tab == 'project')
  2751. {
  2752. $project = $this->project->getByID($this->session->project);
  2753. if(!empty($project->model) && $project->model == 'waterfall') $executionConcept = $this->lang->project->stage;
  2754. if(!empty($project->model) && $project->model == 'scrum') $executionConcept = $this->lang->executionCommon;
  2755. $fileName = isset($project->name) ? $project->name . ' - ' . $executionConcept : $executionConcept;
  2756. }
  2757. if($lowerStatus != 'bysearch') $fileName = (in_array($status, array('all', 'undone', 'delayed')) ? zget($this->lang->execution, $status, '') : zget($this->lang->execution->statusList, $status, '')) . $executionConcept;
  2758. $this->view->fileName = !empty($fileName) ? $fileName : $executionConcept;
  2759. $this->display();
  2760. }
  2761. /**
  2762. * 在迭代中查看迭代的文档。
  2763. * Doc for compatible.
  2764. *
  2765. * @param int $executionID
  2766. * @access public
  2767. * @return void
  2768. * @param int $libID
  2769. * @param int $moduleID
  2770. * @param string $browseType
  2771. * @param string $orderBy
  2772. * @param int $param
  2773. * @param int $recTotal
  2774. * @param int $recPerPage
  2775. * @param int $pageID
  2776. * @param string $mode
  2777. * @param int $docID
  2778. * @param string $search
  2779. */
  2780. public function doc($executionID = 0, $libID = 0, $moduleID = 0, $browseType = 'all', $orderBy = 'order_asc', $param = 0, $recTotal = 0, $recPerPage = 20, $pageID = 1, $mode = 'list', $docID = 0, $search = '')
  2781. {
  2782. $this->commonAction($executionID);
  2783. echo $this->fetch('doc', 'app', "type=execution&spaceID=$executionID&libID=$libID&moduleID=$moduleID&docID=$docID&mode=$mode&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&filterType=$browseType&search=$search&noSpace=true");
  2784. }
  2785. /**
  2786. * 将计划中的需求关联到此项目或迭代/执行或看板中。
  2787. * Import the stories to execution/project/kanban by product plan.
  2788. *
  2789. * @param int $executionID
  2790. * @param int $planID
  2791. * @param int $productID
  2792. * @param string $extra
  2793. * @access public
  2794. * @return void
  2795. */
  2796. public function importPlanStories($executionID, $planID, $productID = 0, $extra = '')
  2797. {
  2798. $execution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch();
  2799. $planStories = array();
  2800. $planStory = $this->loadModel('story')->getPlanStories($planID);
  2801. $draftCount = 0;
  2802. if(!empty($planStory))
  2803. {
  2804. $projectProducts = $this->loadModel('project')->getBranchesByProject($executionID);
  2805. foreach($planStory as $id => $story)
  2806. {
  2807. $projectBranches = zget($projectProducts, $story->product, array());
  2808. if($story->status != 'active' or (!empty($story->branch) and !empty($projectBranches) and !isset($projectBranches[$story->branch])))
  2809. {
  2810. $draftCount++;
  2811. unset($planStory[$id]);
  2812. }
  2813. }
  2814. $planStories = array_keys($planStory);
  2815. if($execution->project != 0) $this->execution->linkStory($execution->project, $planStories); /* Link story to the project of the execution. */
  2816. $this->execution->linkStory($executionID, $planStories, $extra); /* Link story to the execution. */
  2817. }
  2818. $moduleName = 'execution';
  2819. if($execution->type == 'project') $moduleName = 'projectstory';
  2820. if($execution->type == 'kanban')
  2821. {
  2822. global $lang;
  2823. $lang->executionCommon = $lang->execution->kanban;
  2824. include $this->app->getModulePath('', 'execution') . 'lang/' . $this->app->getClientLang() . '.php';
  2825. }
  2826. /* Check if the product is multiple branch. */
  2827. $multiBranchProduct = $this->executionZen->hasMultipleBranch($productID, $executionID);
  2828. if($draftCount != 0)
  2829. {
  2830. $importPlanStoryTips = $multiBranchProduct ? $this->lang->execution->haveBranchDraft : $this->lang->execution->haveDraft;
  2831. $haveDraft = sprintf($importPlanStoryTips, $draftCount);
  2832. if(!$execution->multiple || $moduleName == 'projectstory') $haveDraft = str_replace($this->lang->executionCommon, $this->lang->projectCommon, $haveDraft);
  2833. return $this->send(array('closeModal' => true, 'load' => true, 'result' => 'fail', 'message' => $haveDraft));
  2834. }
  2835. return $this->sendSuccess(array('closeModal' => true, 'load' => true));
  2836. }
  2837. /**
  2838. * 在执行/迭代的树状图中查看某个需求的详情。
  2839. * View a story in the tree view of the execution.
  2840. *
  2841. * @param int $storyID
  2842. * @param int $version The version of story.
  2843. *
  2844. * @access public
  2845. * @return void
  2846. */
  2847. public function treeStory($storyID, $version = 0)
  2848. {
  2849. $this->loadModel('requirement');
  2850. $this->loadModel('epic');
  2851. $story = $this->loadModel('story')->getById($storyID, $version, true);
  2852. $product = $this->dao->findById($story->product)->from(TABLE_PRODUCT)->fields('name, id, `type`, shadow')->fetch();
  2853. $this->view->story = $story;
  2854. $this->view->product = $product;
  2855. $this->view->branches = $product->type == 'normal' ? array() : $this->loadModel('branch')->getPairs($product->id);
  2856. $this->view->plan = $this->dao->findById($story->plan)->from(TABLE_PRODUCTPLAN)->fields('title')->fetch('title');
  2857. $this->view->bugs = $this->dao->select('id,title,status')->from(TABLE_BUG)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll();
  2858. $this->view->cases = $this->dao->select('id,title')->from(TABLE_CASE)->where('story')->eq($storyID)->andWhere('deleted')->eq(0)->fetchAll();
  2859. $this->view->modulePath = $this->loadModel('tree')->getParents($story->module);
  2860. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  2861. $this->view->executions = $this->loadModel('execution')->getPairs(0, 'all', 'nocode');
  2862. $this->view->actions = $this->loadModel('action')->getList('story', $storyID);
  2863. $this->view->version = $version == 0 ? $story->version : $version;
  2864. $this->view->executionID = $this->session->execution;
  2865. $this->display();
  2866. }
  2867. /**
  2868. * 在执行/迭代的树状图中查看某个任务的详情。
  2869. * View a task in the tree view of the execution.
  2870. *
  2871. * @param int $taskID
  2872. *
  2873. * @access public
  2874. * @return void
  2875. */
  2876. public function treeTask($taskID)
  2877. {
  2878. $this->loadModel('task');
  2879. $task = $this->task->getById($taskID, true);
  2880. if($task->fromBug != 0)
  2881. {
  2882. $bug = $this->loadModel('bug')->getById($task->fromBug);
  2883. $task->bugSteps = '';
  2884. if($bug)
  2885. {
  2886. $task->bugSteps = $this->loadModel('file')->setImgSize($bug->steps);
  2887. foreach($bug->files as $file) $task->files[] = $file;
  2888. }
  2889. $this->view->fromBug = $bug;
  2890. }
  2891. else
  2892. {
  2893. $story = $this->loadModel('story')->getById($task->story);
  2894. $task->storySpec = empty($story) ? '' : $this->loadModel('file')->setImgSize($story->spec);
  2895. $task->storyVerify = empty($story) ? '' : $this->loadModel('file')->setImgSize($story->verify);
  2896. $task->storyFiles = $this->loadModel('file')->getByObject('story', $task->story);
  2897. }
  2898. if($task->team) $this->lang->task->assign = $this->lang->task->transfer;
  2899. /* Update action. */
  2900. if($task->assignedTo == $this->app->user->account) $this->loadModel('action')->read('task', $taskID);
  2901. $this->view->task = $task;
  2902. $this->view->execution = $this->execution->getById($task->execution);
  2903. $this->view->actions = $this->loadModel('action')->getList('task', $taskID);
  2904. $this->view->users = $this->loadModel('user')->getPairs('noletter');
  2905. $this->display();
  2906. }
  2907. /**
  2908. * 获取泳道分组下拉。
  2909. * Get the lane group drop-down by ajax.
  2910. *
  2911. * @param string $type all|syory|task|bug
  2912. * @param string $group
  2913. * @access public
  2914. * @return void
  2915. */
  2916. public function ajaxGetGroup($type, $group = 'default')
  2917. {
  2918. $this->app->loadLang('kanban');
  2919. return print(html::select("group", $this->lang->kanban->group->$type, $group, 'class="form-control chosen" data-max_drop_width="215"'));
  2920. }
  2921. /**
  2922. * 更新看板数据。
  2923. * Update kanban by ajax.
  2924. *
  2925. * @param int $executionID
  2926. * @param int $enterTime
  2927. * @param string $browseType all|story|task|bug
  2928. * @param string $groupBy default|pri|category|module|source|assignedTo|type|story|severity
  2929. * @param string $from taskkanban|execution
  2930. * @param string $searchValue
  2931. * @param string $orderBy
  2932. * @access public
  2933. * @return void
  2934. */
  2935. public function ajaxUpdateKanban($executionID = 0, $enterTime = 0, $browseType = '', $groupBy = '', $from = 'taskkanban', $searchValue = '', $orderBy = 'id_asc')
  2936. {
  2937. $this->loadModel('kanban');
  2938. if($groupBy == 'story' and $browseType == 'task' and !isset($this->lang->kanban->orderList[$orderBy])) $orderBy = 'pri_asc';
  2939. if($from == 'execution') $this->session->set('taskSearchValue', $searchValue);
  2940. if($from == 'RD') $this->session->set('rdSearchValue', $searchValue);
  2941. $lastEditedTime = $this->execution->getLaneMaxEditedTime($executionID);
  2942. $enterTime = date('Y-m-d H:i:s', $enterTime);
  2943. if(in_array(true, array(empty($lastEditedTime), strtotime($lastEditedTime) < 0, $lastEditedTime > $enterTime, $groupBy != 'default', !empty($searchValue))))
  2944. {
  2945. if($from == 'taskkanban')
  2946. {
  2947. list($kanbanGroup, $links) = $this->kanban->getExecutionKanban($executionID, $browseType, $groupBy, $searchValue, $orderBy);
  2948. }
  2949. else
  2950. {
  2951. $kanbanGroup = $this->kanban->getRDKanban($executionID, $browseType, $orderBy, 0, $groupBy, $searchValue);
  2952. }
  2953. return print(json_encode($kanbanGroup));
  2954. }
  2955. }
  2956. /**
  2957. * 创建执行时,根据项目ID获取可以复制的执行。
  2958. * When the create an execution, get execution list that can be copied by the project ID.
  2959. *
  2960. * @param int $projectID
  2961. * @param int $copyExecutionID
  2962. * @access public
  2963. * @return void
  2964. */
  2965. public function ajaxGetCopyProjectExecutions($projectID = 0, $copyExecutionID = 0)
  2966. {
  2967. $project = $this->loadModel('project')->fetchByID($projectID);
  2968. if($project) $this->executionZen->correctExecutionCommonLang($project, zget($this->config->execution->modelList, $project->model, 'scrum'));
  2969. $this->view->executions = $this->execution->getList($projectID, 'all', 'all', 0, 0, 0, null, false);
  2970. $this->view->copyExecutionID = $copyExecutionID;
  2971. $this->display();
  2972. }
  2973. /**
  2974. * 获取专业研发看板的数据。
  2975. * Get execution kanban data by ajax.
  2976. *
  2977. * @param int $executionID
  2978. * @access public
  2979. * @return void
  2980. */
  2981. public function ajaxGetExecutionKanban($executionID)
  2982. {
  2983. $execution = $this->execution->getByID($executionID);
  2984. if($this->app->tab == 'execution' and $execution->type == 'kanban')
  2985. {
  2986. $execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
  2987. $execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
  2988. $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
  2989. $kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
  2990. echo json_encode($kanbanData);
  2991. }
  2992. }
  2993. /**
  2994. * 展示燃尽图。
  2995. * Display burn chart.
  2996. *
  2997. * @param int $executionID
  2998. * @access public
  2999. * @return void
  3000. */
  3001. public function ajaxGetBurn($executionID)
  3002. {
  3003. $execution = $this->execution->getByID($executionID, true);
  3004. $type = 'noweekend';
  3005. if(((strpos('closed,suspended', $execution->status) === false && helper::today() > $execution->end)
  3006. || ($execution->status == 'closed' && substr($execution->closedDate ?? '', 0, 10) > $execution->end)
  3007. || ($execution->status == 'suspended' && $execution->suspendedDate > $execution->end))
  3008. && strpos($type, 'delay') === false)
  3009. {
  3010. $type .= ',withdelay';
  3011. }
  3012. $deadline = $execution->status == 'closed' ? substr($execution->closedDate ?? '', 0, 10) : $execution->suspendedDate;
  3013. $deadline = strpos('closed,suspended', $execution->status) === false ? helper::today() : $deadline;
  3014. $endDate = strpos($type, 'withdelay') !== false ? $deadline : $execution->end;
  3015. list($dateList, $interval) = $this->execution->getDateList($execution->begin, $endDate, $type, 0, 'Y-m-d', $execution->end);
  3016. $executionEnd = strpos($type, 'withdelay') !== false ? $execution->end : '';
  3017. $this->view->chartData = $this->execution->buildBurnData($executionID, $dateList, 'left', $executionEnd);
  3018. $this->view->execution = $execution;
  3019. $this->display();
  3020. }
  3021. /**
  3022. * 展示累计流图。
  3023. * Display cumulative flow diagram.
  3024. *
  3025. * @param int $executionID
  3026. * @access public
  3027. * @return void
  3028. */
  3029. public function ajaxGetCFD($executionID)
  3030. {
  3031. $execution = $this->execution->getById($executionID, true);
  3032. $this->app->loadClass('date');
  3033. list($begin, $end) = $this->execution->getBeginEnd4CFD($execution);
  3034. $dateList = date::getDateList($begin, $end, 'Y-m-d', 'noweekend');
  3035. $chartData = $this->execution->buildCFDData($executionID, $dateList, 'task');
  3036. if(isset($chartData['line'])) $chartData['line'] = array_reverse($chartData['line']);
  3037. $this->view->begin = helper::safe64Encode(urlencode($begin));
  3038. $this->view->end = helper::safe64Encode(urlencode($end));
  3039. $this->view->execution = $execution;
  3040. $this->view->chartData = $chartData;
  3041. $this->display();
  3042. }
  3043. /**
  3044. * Ajax get product drop menu.
  3045. *
  3046. * @param int $executionID
  3047. * @param int $productID
  3048. * @access public
  3049. * @return void
  3050. * @param string $currentMethod
  3051. */
  3052. public function ajaxSwitcherMenu($executionID, $productID, $currentMethod = '')
  3053. {
  3054. $link = helper::createLink('execution', $currentMethod, "executionID=$executionID&productID={id}");
  3055. if($currentMethod == 'zerocase') $link = helper::createLink('testcase', 'zerocase', "productID={id}&branch=0&orderBy=id_desc&objectID=$executionID");
  3056. $this->view->link = $link;
  3057. $this->view->productID = $productID;
  3058. $this->view->products = $this->loadModel('product')->getProducts($executionID);
  3059. $this->view->executionID = $executionID;
  3060. $this->view->currentMethod = $currentMethod;
  3061. $this->display();
  3062. }
  3063. /**
  3064. * Ajax get execution drop menu.
  3065. * 通过Ajax获取执行下拉菜单。
  3066. *
  3067. * @param int $projectID
  3068. * @param int $executionID
  3069. * @param string $method
  3070. * @access public
  3071. * @return void
  3072. */
  3073. public function ajaxGetExecutionSwitcherMenu($projectID, $executionID, $method = '')
  3074. {
  3075. $this->view->link = helper::createLink('execution', $method, 'executionID={id}');
  3076. $this->view->executionID = $executionID;
  3077. $this->view->executions = $this->execution->getList($projectID, 'kanban');
  3078. $this->display();
  3079. }
  3080. /**
  3081. * Flatten Object Array.
  3082. *
  3083. * @param array $array
  3084. * @access public
  3085. * @return void
  3086. */
  3087. public function flattenObjectArray($array = array())
  3088. {
  3089. $this->loadModel('task');
  3090. $result = array();
  3091. foreach($array as $object)
  3092. {
  3093. $result[$object->id] = $object;
  3094. $tasks = zget($object, 'tasks', array());
  3095. if(empty($tasks)) continue;
  3096. $object->tasks = $this->task->mergeChildIntoParent($tasks);
  3097. }
  3098. return $result;
  3099. }
  3100. /**
  3101. * AJAX: 获取有未关闭任务的执行。
  3102. * AJAX: get executions that have unclosed tasks.
  3103. *
  3104. * @param string $executionIdList
  3105. * @access public
  3106. * @return string
  3107. */
  3108. public function ajaxGetNonClosableExecutions($executionIdList)
  3109. {
  3110. $executionIdList = str_replace('pid', '', $executionIdList);
  3111. $tasks = $this->loadModel('task')->getUnclosedTasksByExecution(explode(',', $executionIdList));
  3112. $tasks = array_filter($tasks);
  3113. $executions = !empty($tasks) ? $this->execution->getPairsByList(array_keys($tasks)) : array();
  3114. return print(json_encode(array_values($executions)));
  3115. }
  3116. }