zen.php 108 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541
  1. <?php
  2. class bugZen extends bug
  3. {
  4. /**
  5. * 检查用户是否拥有所属执行的权限。
  6. * Check bug execution priv.
  7. *
  8. * @param object $bug
  9. * @access protected
  10. * @return bool|int
  11. */
  12. protected function checkBugExecutionPriv($bug)
  13. {
  14. if($bug->execution && !$this->loadModel('execution')->checkPriv($bug->execution))
  15. {
  16. if(isInModal() || !$this->server->http_referer)
  17. {
  18. echo js::alert($this->lang->bug->notice->executionAccessDenied);
  19. $loginLink = $this->createLink('user', 'login');
  20. if($this->server->http_referer && strpos($this->server->http_referer, $loginLink) !== false) return print(js::locate($this->createLink('bug', 'index', '')));
  21. if($this->app->tab == 'my') return print(js::reload('parent'));
  22. return print(js::locate('back'));
  23. }
  24. else
  25. {
  26. $locate = array('load' => true);
  27. $loginLink = $this->createLink('user', 'login');
  28. if($this->server->http_referer && (strpos($this->server->http_referer, $loginLink) !== false || strpos($this->server->http_referer, 'index'))) $locate = $this->createLink('bug', 'browse');
  29. return $this->send(array('result' => 'fail', 'load' => array('alert' => $this->lang->bug->notice->executionAccessDenied, 'locate' => $locate)));
  30. }
  31. }
  32. return true;
  33. }
  34. /**
  35. * 检查 bug 编辑时的必填项。
  36. * Check required fields when edit bug.
  37. *
  38. * @param object $bug
  39. * @access protected
  40. * @return bool
  41. */
  42. protected function checkRquiredForEdit($bug)
  43. {
  44. $requiredFields = explode(',', $this->config->bug->edit->requiredFields);
  45. $editErrors = array();
  46. /* Check required fields. */
  47. foreach($requiredFields as $requiredField)
  48. {
  49. if(isset($this->config->bug->form->edit[$requiredField])
  50. && (!isset($bug->{$requiredField}) || (isset($bug->{$requiredField}) && strlen(trim((string)$bug->{$requiredField})) == 0)))
  51. {
  52. $fieldName = isset($this->config->bug->form->edit[$requiredField]) && $this->config->bug->form->edit[$requiredField]['type'] != 'array' ? $requiredField : "{$requiredField}[]";
  53. $editErrors[$fieldName] = sprintf($this->lang->error->notempty, zget($this->lang->bug, $requiredField));
  54. }
  55. }
  56. if(!empty($bug->resolvedBy) && empty($bug->resolution)) $editErrors['resolution'] = sprintf($this->lang->error->notempty, $this->lang->bug->resolution);
  57. if($bug->resolution == 'duplicate' && empty($bug->duplicateBug)) $editErrors['duplicateBug'] = sprintf($this->lang->error->notempty, $this->lang->bug->duplicateBug);
  58. if(!empty($editErrors)) dao::$errors = $editErrors;
  59. return true;
  60. }
  61. /**
  62. * 检查解决bug时表单数据的完整性。
  63. * Check the integrity of form data when resolving bug.
  64. *
  65. * @param object $bug
  66. * @access protected
  67. * @return bool
  68. * @param int $oldExecution
  69. */
  70. protected function checkRequiredForResolve($bug, $oldExecution)
  71. {
  72. /* Set lang for error. */
  73. $this->lang->bug->comment = $this->lang->comment;
  74. /* When creating a new build, the execution of the build cannot be empty. */
  75. if($bug->createBuild == 'on' && empty($bug->buildExecution))
  76. {
  77. $executionLang = $this->lang->bug->execution;
  78. if($oldExecution)
  79. {
  80. $execution = $this->dao->findByID($oldExecution)->from(TABLE_EXECUTION)->fetch();
  81. if($execution and $execution->type == 'kanban') $executionLang = $this->lang->bug->kanban;
  82. }
  83. dao::$errors['buildExecution'][] = sprintf($this->lang->error->notempty, $executionLang);
  84. }
  85. /* When creating a new build, the build name cannot be empty. */
  86. if($bug->createBuild == 'on' && empty($bug->buildName)) dao::$errors['buildName'][] = sprintf($this->lang->error->notempty, $this->lang->bug->placeholder->newBuildName);
  87. /* Check required fields of resolving bug. */
  88. foreach(explode(',', $this->config->bug->resolve->requiredFields) as $requiredField)
  89. {
  90. if($requiredField == 'resolvedBuild') continue;
  91. if(!isset($bug->{$requiredField}) or strlen(trim($bug->{$requiredField})) == 0)
  92. {
  93. $fieldName = $requiredField;
  94. if(isset($this->lang->bug->$requiredField)) $fieldName = $this->lang->bug->$requiredField;
  95. dao::$errors[$requiredField][] = sprintf($this->lang->error->notempty, $fieldName);
  96. }
  97. }
  98. /* If the resolution of bug is duplicate, duplicate bug id cannot be empty. */
  99. if($bug->resolution == 'duplicate' && empty($bug->duplicateBug)) dao::$errors['duplicateBug'][] = sprintf($this->lang->error->notempty, $this->lang->bug->duplicateBug);
  100. /* When creating a new build, the build name cannot be empty. */
  101. if($bug->createBuild != 'on' && $bug->resolution == 'fixed' && empty($bug->resolvedBuild)) dao::$errors['resolvedBuild'][] = sprintf($this->lang->error->notempty, $this->lang->bug->resolvedBuild);
  102. return !dao::isError();
  103. }
  104. /**
  105. * 检查批量创建bug时表单数据的完整性。
  106. * Check the batch created bugs.
  107. *
  108. * @param array $bugs
  109. * @access protected
  110. * @return array
  111. */
  112. protected function checkBugsForBatchCreate($bugs)
  113. {
  114. /* Check required fields. */
  115. foreach($bugs as $index => $bug)
  116. {
  117. foreach(explode(',', $this->config->bug->create->requiredFields) as $field)
  118. {
  119. $field = trim($field);
  120. if($field and empty($bug->$field) and $field != 'title') dao::$errors["{$field}[{$index}]"] = sprintf($this->lang->error->notempty, $this->lang->bug->$field);
  121. }
  122. }
  123. return $bugs;
  124. }
  125. /**
  126. * 为批量编辑 bugs 检查数据。
  127. * Check bugs for batch update.
  128. *
  129. * @param array $bugs
  130. * @access protected
  131. * @return bool
  132. */
  133. protected function checkBugsForBatchUpdate($bugs)
  134. {
  135. $requiredFields = explode(',', $this->config->bug->edit->requiredFields);
  136. foreach($bugs as $bug)
  137. {
  138. /* Check required fields. */
  139. foreach($requiredFields as $requiredField)
  140. {
  141. if(isset($this->config->bug->form->batchEdit[$requiredField])
  142. && (!isset($bug->{$requiredField}) || (isset($bug->{$requiredField}) && strlen(trim((string)$bug->{$requiredField})) == 0)))
  143. {
  144. $fieldName = isset($this->config->bug->form->batchEdit[$requiredField]) && $this->config->bug->form->batchEdit[$requiredField]['type'] != 'array' ? "{$requiredField}[{$bug->id}]" : "{$requiredField}[{$bug->id}][]";
  145. dao::$errors[$fieldName] = sprintf($this->lang->error->notempty, zget($this->lang->bug, $requiredField));
  146. }
  147. }
  148. if(!empty($bug->resolvedBy) && empty($bug->resolution)) dao::$errors["resolution[{$bug->id}]"] = sprintf($this->lang->error->notempty, $this->lang->bug->resolution);
  149. if(!empty($bug->resolution) && $bug->resolution == 'duplicate' && empty($bug->duplicateBug)) dao::$errors["duplicateBug[{$bug->id}]"] = sprintf($this->lang->error->notempty, $this->lang->bug->duplicateBug);
  150. }
  151. return !dao::isError();
  152. }
  153. /**
  154. * 获取列表页面的 branch 参数。
  155. * Get browse branch param.
  156. *
  157. * @param string $branch
  158. * @param string $productType
  159. * @access protected
  160. * @return string
  161. */
  162. protected function getBrowseBranch($branch, $productType)
  163. {
  164. if($productType == 'normal') return 'all';
  165. if($branch === '') $branch = $this->cookie->preBranch;
  166. if($branch === '' || $branch === false) $branch = '0';
  167. $this->session->set('branch', $branch, 'qa');
  168. helper::setcookie('preBranch', $branch);
  169. return $branch;
  170. }
  171. /**
  172. * 获取列表页面的 bug 列表。
  173. * Get browse bugs.
  174. *
  175. * @param int $productID
  176. * @param string $branch
  177. * @param string $browseType
  178. * @param array $executions
  179. * @param int $moduleID
  180. * @param int $queryID
  181. * @param string $orderBy
  182. * @param object $pager
  183. * @access protected
  184. * @return array
  185. */
  186. protected function getBrowseBugs($productID, $branch, $browseType, $executions, $moduleID, $queryID, $orderBy, $pager)
  187. {
  188. $bugs = $this->bug->getList($browseType, (array)$productID, $this->projectID, $executions, $branch, $moduleID, $queryID, $orderBy, $pager);
  189. /* 把查询条件保存到 session。*/
  190. /* Process the sql, get the conditon partion, save it to session. */
  191. $this->loadModel('common')->saveQueryCondition($this->bug->dao->get(), 'bug', $browseType == 'needconfirm' ? false : true);
  192. /* 检查 bug 是否有过变更。*/
  193. /* Process bug for check story changed. */
  194. $bugs = $this->loadModel('story')->checkNeedConfirm($bugs);
  195. /* 检查是否需要确认撤销/移除。*/
  196. /* Build confirmeObject. */
  197. if($this->config->edition == 'ipd') $bugs = $this->loadModel('story')->getAffectObject($bugs, 'bug');
  198. /* 处理 bug 的版本信息。*/
  199. /* Process the openedBuild and resolvedBuild fields. */
  200. return $this->bug->processBuildForBugs($bugs);
  201. }
  202. /**
  203. * 获取分支。
  204. * Get branch options.
  205. *
  206. * @param int $productID
  207. * @access private
  208. * @return array
  209. */
  210. private function getBranchOptions($productID)
  211. {
  212. $branches = $this->loadModel('branch')->getList($productID, 0, 'all');
  213. $branchTagOption = array();
  214. foreach($branches as $branchInfo)
  215. {
  216. $branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
  217. }
  218. return $branchTagOption;
  219. }
  220. /**
  221. * 通过$_POST的值和解析出来的$output,获得看板的laneID和columnID。
  222. * Get kanban laneID and columnID from $_POST and $output from extra().
  223. *
  224. * @param array $output
  225. * @access private
  226. * @return array
  227. */
  228. private function getKanbanVariable($output)
  229. {
  230. $laneID = isset($output['laneID']) ? $output['laneID'] : 0;
  231. if(!empty($_POST['lane'])) $laneID = $this->post->lane;
  232. $columnID = $this->loadModel('kanban')->getColumnIDByLaneID((int)$laneID, 'unconfirmed');
  233. if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
  234. return array((int)$laneID, (int)$columnID);
  235. }
  236. /**
  237. * 获取bug创建页面的产品成员。
  238. * Get the product members for bug create page.
  239. *
  240. * @param object $bug
  241. * @access private
  242. * @return array
  243. */
  244. private function getProductMembersForCreate($bug)
  245. {
  246. $this->loadModel('user');
  247. if(!empty($bug->allUsers))
  248. {
  249. $productMembers = $this->user->getPairs('devfirst|noclosed');
  250. }
  251. else if($bug->executionID)
  252. {
  253. $productMembers = $this->user->getTeamMemberPairs((int)$bug->executionID, 'execution');
  254. }
  255. else if($bug->projectID)
  256. {
  257. $productMembers = $this->user->getTeamMemberPairs((int)$bug->projectID, 'project');
  258. }
  259. else
  260. {
  261. $productMembers = $this->bug->getProductMemberPairs((int)$bug->productID, (string)$bug->branch);
  262. }
  263. $productMembers = array_filter($productMembers);
  264. if(empty($productMembers)) $productMembers = $this->view->users;
  265. return $productMembers;
  266. }
  267. /**
  268. * 基于当前bug获取指派给。
  269. * Get assigned pairs by bug.
  270. *
  271. * @param object $bug
  272. * @access protected
  273. * @return string[]
  274. */
  275. protected function getAssignedToPairs($bug)
  276. {
  277. /* If the execution of the bug is not empty, get the team members for the execution. */
  278. if($bug->execution)
  279. {
  280. $users = $this->loadModel('user')->getTeamMemberPairs($bug->execution, 'execution');
  281. }
  282. /* If the project of the bug is not empty, get the team members for the project. */
  283. elseif($bug->project)
  284. {
  285. $users = $this->loadModel('project')->getTeamMemberPairs($bug->project);
  286. }
  287. /* If the execution and project of the bug are both empty, get the team member of the bug's product. */
  288. else
  289. {
  290. $users = $this->bug->getProductMemberPairs($bug->product, (string)$bug->branch);
  291. $users = array_filter($users);
  292. /* If the team member of the product is empty, get all user. */
  293. if(empty($users)) $users = $this->loadModel('user')->getPairs('devfirst|noclosed');
  294. }
  295. /* If the assigned person doesn't exist in the user list and the assigned person is not closed, append it. */
  296. if($bug->assignedTo && !isset($users[$bug->assignedTo]) && $bug->assignedTo != 'closed')
  297. {
  298. $assignedTo = $this->user->getByID($bug->assignedTo);
  299. $users[$bug->assignedTo] = $assignedTo->realname;
  300. }
  301. return $users;
  302. }
  303. /**
  304. * 获取导出文件名。
  305. * Get export file name.
  306. *
  307. * @param int $executionID
  308. * @param string $browseType
  309. * @param object|bool $product
  310. * @access protected
  311. * @return string
  312. */
  313. protected function getExportFileName($executionID, $browseType, $product)
  314. {
  315. $fileName = $this->lang->bug->common;
  316. if($executionID)
  317. {
  318. $executionName = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch('name');
  319. $fileName = $executionName . $this->lang->dash . $fileName;
  320. }
  321. else
  322. {
  323. $productName = !empty($product->name) ? $product->name : '';
  324. $browseType = isset($this->lang->bug->featureBar['browse'][$browseType]) ? $this->lang->bug->featureBar['browse'][$browseType] : zget($this->lang->bug->moreSelects, $browseType, '');
  325. $fileName = $productName . $this->lang->dash . $browseType . $fileName;
  326. }
  327. return $fileName;
  328. }
  329. /**
  330. * 获取导出字段。
  331. * Get export fields.
  332. *
  333. * @param int $executionID
  334. * @param object|bool $product
  335. * @access protected
  336. * @return string
  337. */
  338. protected function getExportFields($executionID, $product)
  339. {
  340. $exportFields = str_replace(' ', '', $this->config->bug->exportFields);
  341. $isShadow = false;
  342. if(isset($product->type) and $product->type == 'normal') $exportFields = str_replace(',branch,', ',', ",{$exportFields},");;
  343. if(!$product)
  344. {
  345. $products = $this->loadModel('product')->getProducts($executionID);
  346. $hasBranch = false;
  347. foreach($products as $product)
  348. {
  349. if($product->type != 'normal') $hasBranch = true;
  350. if(!empty($product->shadow)) $isShadow = true;
  351. }
  352. if(!$hasBranch) $exportFields = str_replace(',branch,', ',', ",{$exportFields},");
  353. }
  354. else
  355. {
  356. $isShadow = $product->shadow;
  357. }
  358. if($isShadow) $exportFields = str_replace(',plan,', ',', ",{$exportFields},");
  359. if($this->app->tab == 'project' or $this->app->tab == 'execution')
  360. {
  361. $execution = $this->loadModel('execution')->getByID($executionID);
  362. if(empty($execution->multiple)) $exportFields = str_replace(',execution,', ',', ",{$exportFields},");
  363. if(empty($execution->hasProduct) || !empty($product->shadow)) $exportFields = str_replace(array(',product,', ',branch,'), ',', ",{$exportFields},");
  364. }
  365. return $exportFields;
  366. }
  367. /**
  368. * 获取批量解决bug的数据。
  369. * Get batch resolve bug data.
  370. *
  371. * @param object[] $oldBugs
  372. * @access protected
  373. * @return array
  374. */
  375. protected function getBatchResolveVars($oldBugs)
  376. {
  377. $bug = reset($oldBugs);
  378. $productID = $bug->product;
  379. $product = $this->loadModel('product')->getByID($productID);
  380. $stmt = $this->dao->query($this->loadModel('tree')->buildMenuQuery($productID, 'bug'));
  381. $modules = array();
  382. while($module = $stmt->fetch()) $modules[$module->id] = $module;
  383. return array($modules, $product->QD);
  384. }
  385. /**
  386. * 设置浏览页面的 cookie。
  387. * Set cookie in browse view.
  388. *
  389. * @param object $product
  390. * @param string $branch
  391. * @param string $browseType
  392. * @param int $param
  393. * @param string $orderBy
  394. * @access protected
  395. * @return bool
  396. */
  397. protected function setBrowseCookie($product, $branch, $browseType, $param, $orderBy)
  398. {
  399. /* 如果产品或者分支变了,清空 bug 模块的 cookie。*/
  400. /* Clear cookie of bug module if the product or the branch is changed. */
  401. $productChanged = $this->cookie->preProductID != $product->id;
  402. $branchChanged = $product->type != 'normal' && $this->cookie->preBranch != $branch;
  403. if($productChanged || $branchChanged || $browseType == 'bysearch') helper::setcookie('bugModule', '0', 0);
  404. /* 如果浏览类型为按模块浏览或者浏览类型为空,设置 bug 模块的 cookie 为当前模块,清空 bug 分支的 cookie。*/
  405. /* Set cookie of bug module and clear cookie of bug branch if browse type is by module or is empty. */
  406. if($browseType == 'bymodule' || $browseType == '')
  407. {
  408. helper::setcookie('bugModule', (string)$param, 0);
  409. helper::setcookie('bugBranch', '0', 0);
  410. }
  411. /* 设置测试应用的 bug 排序 cookie。*/
  412. /* Set the cookie of bug order in qa. */
  413. helper::setcookie('qaBugOrder', $orderBy, 0);
  414. return true;
  415. }
  416. /**
  417. * 设置浏览界面的 session。
  418. * Set session in browse view.
  419. *
  420. * @param string $browseType
  421. * @access protected
  422. * @return bool
  423. */
  424. protected function setBrowseSession($browseType)
  425. {
  426. /* 设置浏览方式的 session,记录刚刚是搜索还是按模块浏览。*/
  427. /* Set session of browse type. */
  428. if($browseType != 'bymodule') $this->session->set('bugBrowseType', $browseType);
  429. if(($browseType == 'bymodule') && $this->session->bugBrowseType == 'bysearch') $this->session->set('bugBrowseType', 'unclosed');
  430. $this->session->set('bugList', $this->app->getURI(true) . "#app={$this->app->tab}", 'qa');
  431. return true;
  432. }
  433. /**
  434. * 为创建 bug 设置导航数据。
  435. * Set menu for create bug page.
  436. *
  437. * @param int $productID
  438. * @param string $branch
  439. * @param array $output
  440. * @access protected
  441. * @return bool
  442. */
  443. protected function setCreateMenu($productID, $branch, $output)
  444. {
  445. if(empty($this->products)) $this->locate($this->createLink('product', 'create'));
  446. /* Unset discarded types. */
  447. foreach($this->config->bug->discardedTypes as $type) unset($this->lang->bug->typeList[$type]);
  448. if($this->app->tab == 'execution')
  449. {
  450. if(isset($output['executionID']))
  451. {
  452. $this->loadModel('execution')->setMenu((int)$output['executionID']);
  453. $this->view->executionID = $output['executionID'];
  454. }
  455. $execution = $this->dao->findById($this->session->execution)->from(TABLE_EXECUTION)->fetch();
  456. if(!empty($execution) && $execution->type == 'kanban') $this->assignKanbanVars($execution, $output);
  457. }
  458. elseif($this->app->tab == 'project')
  459. {
  460. if(isset($output['projectID']))
  461. {
  462. $this->loadModel('project')->setMenu((int)$output['projectID']);
  463. $this->view->projectID = $output['projectID'];
  464. }
  465. }
  466. else
  467. {
  468. $this->qa->setMenu($productID, $branch);
  469. }
  470. $this->view->users = $this->user->getPairs('devfirst|noclosed|nodeleted');
  471. $this->app->loadLang('release');
  472. return true;
  473. }
  474. /**
  475. * 设置编辑页面的导航。
  476. * Set edit menu.
  477. *
  478. * @param object $bug
  479. * @access protected
  480. * @return bool
  481. */
  482. protected function setEditMenu($bug)
  483. {
  484. if($this->app->tab == 'project') $this->loadModel('project')->setMenu($bug->project);
  485. if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($bug->execution);
  486. if($this->app->tab == 'qa') $this->loadModel('qa')->setMenu($bug->product, $bug->branch);
  487. if($this->app->tab == 'devops')
  488. {
  489. session_write_close();
  490. $repoPairs = $this->loadModel('repo')->getRepoPairs('project', $bug->project);
  491. $this->repo->setMenu($repoPairs);
  492. $this->lang->navGroup->bug = 'devops';
  493. }
  494. return true;
  495. }
  496. /**
  497. * 如果不是弹窗,调用该方法为查看bug设置导航。
  498. * If it's not a iframe, call this method to set menu for view bug page.
  499. *
  500. * @param object $bug
  501. * @return bool
  502. */
  503. protected function setViewMenu($bug)
  504. {
  505. if($this->app->tab == 'project') $this->loadModel('project')->setMenu($bug->project);
  506. if($this->app->tab == 'execution') $this->loadModel('execution')->setMenu($bug->execution);
  507. if($this->app->tab == 'qa') $this->qa->setMenu($bug->product, $bug->branch);
  508. if($this->app->tab == 'devops')
  509. {
  510. $repos = $this->loadModel('repo')->getRepoPairs('project', $bug->project);
  511. $this->repo->setMenu($repos);
  512. $this->lang->navGroup->bug = 'devops';
  513. }
  514. if($this->app->tab == 'product')
  515. {
  516. $this->loadModel('product')->setMenu($bug->product);
  517. $this->lang->product->menu->plan['subModule'] .= ',bug';
  518. }
  519. return true;
  520. }
  521. /**
  522. * 处理列表页面的参数。
  523. * Processing browse params.
  524. *
  525. * @param string $browseType
  526. * @param int $param
  527. * @param string $orderBy
  528. * @param int $recTotal
  529. * @param int $recPerPage
  530. * @param int $pageID
  531. * @access protected
  532. * @return array
  533. */
  534. protected function prepareBrowseParams($browseType, $param, $orderBy, $recTotal, $recPerPage, $pageID)
  535. {
  536. /* 设置模块 ID。*/
  537. /* Set module id. */
  538. $moduleID = 0;
  539. if($this->cookie->bugModule) $moduleID = (int)$this->cookie->bugModule;
  540. if($browseType == 'bymodule') $moduleID = $param;
  541. /* 设置搜索查询 ID。*/
  542. /* Set query id. */
  543. $queryID = $browseType == 'bysearch' ? $param : 0;
  544. /* 设置 id 为第二排序规则。*/
  545. /* Append id for second sort rule. */
  546. $realOrderBy = common::appendOrder($orderBy);
  547. /* 加载分页器。*/
  548. /* Load pager. */
  549. $viewType = $this->app->getViewType();
  550. if($viewType == 'mhtml' || $viewType == 'xhtml') $recPerPage = 10;
  551. $this->app->loadClass('pager', true);
  552. $pager = new pager($recTotal, $recPerPage, $pageID);
  553. return array($moduleID, $queryID, $realOrderBy, $pager);
  554. }
  555. /**
  556. * 处理创建 bug 请求数据。
  557. * Processing request data for creating bug.
  558. *
  559. * @param form $formData
  560. * @access protected
  561. * @return object
  562. */
  563. protected function prepareCreateExtras($formData)
  564. {
  565. $bug = $formData->setIF($this->lang->navGroup->bug != 'qa', 'project', $this->session->project)
  566. ->setIF($formData->data->assignedTo != '', 'assignedDate', helper::now())
  567. ->setIF($formData->data->story !== false, 'storyVersion', $this->loadModel('story')->getVersion((int)$formData->data->story))
  568. ->setIF($this->post->project, 'project', $this->post->project)
  569. ->setIF($this->post->execution, 'execution', $this->post->execution)
  570. ->get();
  571. if($this->post->fromCase && $this->post->fromCase != $formData->data->case)
  572. {
  573. $case = $this->loadModel('testcase')->fetchByID((int)$this->post->fromCase);
  574. $bug->caseVersion = $case->version;
  575. $bug->result = 0;
  576. }
  577. return $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->create['id'], $this->post->uid);
  578. }
  579. /**
  580. * 处理更新请求数据。
  581. * Processing request data.
  582. *
  583. * @param form $formData
  584. * @param object $oldBug
  585. * @access protected
  586. * @return object|false
  587. */
  588. protected function prepareEditExtras($formData, $oldBug)
  589. {
  590. if(!empty($_POST['lastEditedDate']) and $oldBug->lastEditedDate != $this->post->lastEditedDate)
  591. {
  592. dao::$errors[] = $this->lang->error->editedByOther;
  593. return false;
  594. }
  595. $now = helper::now();
  596. $bug = $formData->add('id', $oldBug->id)
  597. ->setDefault('product', $oldBug->product)
  598. ->setDefault('deleteFiles', array())
  599. ->setDefault('lastEditedBy', $this->app->user->account)
  600. ->setDefault('resolvedDate', $oldBug->resolvedDate)
  601. ->add('lastEditedDate', $now)
  602. ->join('openedBuild,mailto,relatedBug,os,browser', ',')
  603. ->setIF($formData->data->assignedTo != $oldBug->assignedTo, 'assignedDate', $now)
  604. ->setIF($formData->data->resolvedBy != '' && $formData->data->resolvedDate != '', 'resolvedDate', formatTime($formData->data->resolvedDate, 'Y-m-d H:i:s'))
  605. ->setIF($formData->data->resolvedBy != '' && $formData->data->resolvedDate == '', 'resolvedDate', $now)
  606. ->setIF($formData->data->resolution != '' && $formData->data->resolvedDate == '', 'resolvedDate', $now)
  607. ->setIF($formData->data->resolution != '' && $formData->data->resolvedBy == '', 'resolvedBy', $this->app->user->account)
  608. ->setIF($formData->data->closedDate != '' && $formData->data->closedBy != '', 'closedDate', formatTime($formData->data->closedDate, 'Y-m-d H:i:s'))
  609. ->setIF($formData->data->closedDate != '' && $formData->data->closedBy == '', 'closedBy', $this->app->user->account)
  610. ->setIF($formData->data->closedBy != '' && $formData->data->closedDate == '', 'closedDate', $now)
  611. ->setIF($formData->data->closedBy != '' || $formData->data->closedDate != '', 'assignedTo', 'closed')
  612. ->setIF($formData->data->closedBy != '' || $formData->data->closedDate != '', 'assignedDate', $now)
  613. ->setIF($formData->data->resolution != '' || $formData->data->resolvedDate != '', 'status', 'resolved')
  614. ->setIF($formData->data->closedBy != '' || $formData->data->closedDate != '', 'status', 'closed')
  615. ->setIF(($formData->data->resolution != '' || $formData->data->resolvedDate != '') && $formData->data->assignedTo == '', 'assignedTo', $oldBug->openedBy)
  616. ->setIF(($formData->data->resolution != '' || $formData->data->resolvedDate != '') && $formData->data->assignedTo == '', 'assignedDate', $now)
  617. ->setIF($formData->data->resolution == '' && $formData->data->resolvedDate == '', 'status', 'active')
  618. ->setIF($formData->data->resolution != '' && $formData->data->resolution != 'duplicate', 'duplicateBug', 0)
  619. ->setIF($formData->data->assignedTo == '' && $oldBug->status == 'closed', 'assignedTo', 'closed')
  620. ->setIF($formData->data->resolution != '', 'confirmed', 1)
  621. ->setIF($formData->data->story && $formData->data->story != $oldBug->story, 'storyVersion', $this->loadModel('story')->getVersion((int)$formData->data->story))
  622. ->stripTags($this->config->bug->editor->edit['id'], $this->config->allowedTags)
  623. ->get();
  624. if($oldBug->resolvedBy == $bug->resolvedBy && !$this->post->resolvedDate) unset($bug->resolvedDate);
  625. $bug = $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->edit['id'], $bug->uid);
  626. return $bug;
  627. }
  628. /**
  629. * 设置列表页面的搜索表单。
  630. * Build browse search form.
  631. *
  632. * @param int $productID
  633. * @param string $branch
  634. * @param int $queryID
  635. * @param string $from
  636. * @access protected
  637. * @return void
  638. * @param string $actionURL
  639. */
  640. protected function buildBrowseSearchForm($productID, $branch, $queryID, $actionURL)
  641. {
  642. $this->config->bug->search['onMenuBar'] = 'yes';
  643. $searchProducts = $this->product->getPairs('', 0, '', 'all');
  644. $this->bug->buildSearchForm($productID, $searchProducts, $queryID, $actionURL, $branch);
  645. }
  646. /**
  647. * 获取浏览页面所需的变量, 并输出到前台。
  648. * Get the data required by the browse page and output.
  649. *
  650. * @param array $bugs
  651. * @param object $product
  652. * @param string $branch
  653. * @param string $browseType
  654. * @param int $moduleID
  655. * @param array $executions
  656. * @param int $param
  657. * @param string $orderBy
  658. * @param object $pager
  659. * @access protected
  660. * @return void
  661. */
  662. protected function buildBrowseView($bugs, $product, $branch, $browseType, $moduleID, $executions, $param, $orderBy, $pager)
  663. {
  664. $this->loadModel('datatable');
  665. $this->loadModel('custom');
  666. /* 获取分支列表。*/
  667. /* Get branch options. */
  668. $branchTagOption = array();
  669. if($product->type != 'normal') $branchTagOption = $this->getBranchOptions((int)$product->id);
  670. /* 获取需求和任务的 id 列表。*/
  671. /* Get story and task id list. */
  672. $storyIdList = $taskIdList = array();
  673. if($this->config->edition != 'open') $bugRelatedObjectList = $this->custom->getRelatedObjectList(array_keys($bugs), 'bug', 'byRelation', true);
  674. foreach($bugs as $bug)
  675. {
  676. if($bug->story) $storyIdList[$bug->story] = $bug->story;
  677. if($bug->task) $taskIdList[$bug->task] = $bug->task;
  678. if($bug->toTask) $taskIdList[$bug->toTask] = $bug->toTask;
  679. if($this->config->edition != 'open') $bug->relatedObject = zget($bugRelatedObjectList, $bug->id, 0);
  680. }
  681. $showModule = !empty($this->config->bug->browse->showModule) ? $this->config->bug->browse->showModule : '';
  682. /* Set view. */
  683. $this->view->title = $product->name . $this->lang->hyphen . $this->lang->bug->common;
  684. $this->view->product = $product;
  685. $this->view->branch = $branch;
  686. $this->view->browseType = $browseType;
  687. $this->view->currentModuleID = $moduleID;
  688. $this->view->param = $param;
  689. $this->view->orderBy = $orderBy;
  690. $this->view->pager = $pager;
  691. $this->view->modulePairs = $showModule ? $this->tree->getModulePairs($product->id, 'bug', $showModule) : array();
  692. $this->view->moduleTree = $this->tree->getTreeMenu((int)$product->id, 'bug', 0, array('treeModel', 'createBugLink'), array(), $branch);
  693. $this->view->branchTagOption = $branchTagOption;
  694. $this->view->projectPairs = $this->loadModel('project')->getPairsByProgram();
  695. $this->view->executions = $executions;
  696. $this->view->plans = $this->loadModel('productplan')->getPairs((int)$product->id);
  697. $this->view->tasks = $this->loadModel('task')->getPairsByIdList($taskIdList);
  698. $this->view->stories = $this->loadModel('story')->getPairsByList($storyIdList);
  699. $this->view->builds = $this->loadModel('build')->getBuildPairs(array($product->id), $branch);
  700. $this->view->bugs = $bugs;
  701. $this->view->users = $this->user->getPairs('noletter');
  702. $this->view->memberPairs = $this->user->getPairs('noletter|noclosed');
  703. }
  704. /**
  705. * 获取bug创建页面的产品列表,并绑定到bug上。
  706. * Get the products for the bug create page and bind them to bug.
  707. *
  708. * @param object $bug
  709. * @access private
  710. * @return object
  711. */
  712. private function getProductsForCreate($bug)
  713. {
  714. $productID = (int)$bug->productID;
  715. $projectID = (int)$bug->projectID;
  716. $executionID = (int)$bug->executionID;
  717. $products = $this->config->CRProduct ? $this->products : $this->product->getPairs('noclosed', 0, '', 'all');
  718. $productID = isset($products[$productID]) ? $productID : key($products);
  719. if($this->app->tab == 'project' && $projectID)
  720. {
  721. $products = array();
  722. $productList = $this->config->CRProduct ? $this->product->getOrderedProducts('all', 40, $projectID) : $this->product->getOrderedProducts('normal', 40, $projectID);
  723. foreach($productList as $product) $products[$product->id] = $product->name;
  724. $this->project->setMenu($projectID);
  725. }
  726. if($this->app->tab == 'execution' && $executionID)
  727. {
  728. $products = array();
  729. $linkedProducts = $this->product->getProducts($executionID);
  730. foreach($linkedProducts as $product) $products[$product->id] = $product->name;
  731. $execution = $this->loadModel('execution')->getByID($executionID);
  732. $projectID = $execution->project;
  733. $this->loadModel('execution')->setMenu($executionID);
  734. }
  735. return $this->updateBug($bug, array('products' => $products, 'productID' => $productID, 'projectID' => $projectID));
  736. }
  737. /**
  738. * 获取bug创建页面的分支列表,并绑定到bug上。
  739. * Get the branches for the bug create page and bind them to bug.
  740. *
  741. * @param object $bug
  742. * @access private
  743. * @return object
  744. */
  745. private function getBranchesForCreate($bug)
  746. {
  747. $productID = (int)$bug->productID;
  748. $branch = (string)$bug->branch;
  749. $product = $this->product->getByID($productID);
  750. if($this->app->tab == 'execution' || $this->app->tab == 'project')
  751. {
  752. $objectID = $this->app->tab == 'project' ? $bug->projectID : $bug->executionID;
  753. $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct(array($productID), (int)$objectID, 'noclosed|withMain') : array();
  754. $branches = isset($productBranches[$productID]) ? $productBranches[$productID] : array('');
  755. $branch = empty($branch) ? key($branches) : $branch;
  756. }
  757. else
  758. {
  759. $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($productID, 'active') : array('');
  760. $branch = isset($branches[$branch]) && $branch != 0 ? $branch : '';
  761. }
  762. return $this->updateBug($bug, array('branches' => $branches, 'branch' => $branch));
  763. }
  764. /**
  765. * 获取bug创建页面的模块列表,并绑定到bug上。
  766. * Get the modules for the bug create page and bind them to bug.
  767. *
  768. * @param object $bug
  769. * @access private
  770. * @return object
  771. */
  772. protected function getModulesForCreate($bug)
  773. {
  774. $productID = (int)$bug->productID;
  775. $branch = (string)$bug->branch;
  776. $moduleID = (int)$bug->moduleID;
  777. $modules = $this->tree->getOptionMenu($productID, 'bug', 0, ($branch === 'all' || !isset($bug->branches[$branch])) ? 'all' : $branch);
  778. $moduleID = isset($modules[$moduleID]) ? $moduleID : '';
  779. /* Get module owner. */
  780. if(!empty($moduleID))
  781. {
  782. list($account, $realname) = $this->bug->getModuleOwner($moduleID, $productID);
  783. if($account) $this->updateBug($bug, array('assignedTo' => $account));
  784. }
  785. return $this->updateBug($bug, array('modules' => $modules, 'moduleID' => $moduleID));
  786. }
  787. /**
  788. * 获取bug创建页面的项目列表,并绑定到bug上。
  789. * Get the projects for the bug create page and bind them to bug.
  790. *
  791. * @param object $bug
  792. * @access private
  793. * @return object
  794. */
  795. private function getProjectsForCreate($bug)
  796. {
  797. $projectID = (int)$bug->projectID;
  798. $productID = (int)$bug->productID;
  799. $branch = (string)$bug->branch;
  800. $executionID = (int)$bug->executionID;
  801. $product = $this->product->getByID($productID);
  802. $projects = $this->product->getProjectPairsByProduct($productID, $branch);
  803. $projectID = isset($projects[$projectID]) ? $projectID : '';
  804. if($this->app->tab == 'execution' && $executionID && !$projectID) $projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
  805. if($product->shadow && !$projectID) $projectID = key($projects);
  806. $project = array();
  807. if($projectID)
  808. {
  809. $project = $this->loadModel('project')->getByID($projectID);
  810. if(!empty($project->model) && $project->model == 'waterfall') $this->lang->bug->execution = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->bug->execution);
  811. if(!$project->multiple) $executionID = $this->loadModel('execution')->getNoMultipleID($projectID);
  812. $bug->projectModel = $project->model;
  813. }
  814. return $this->updateBug($bug, array('projects' => $projects, 'projectID' => $projectID, 'project' => $project, 'executionID' => $executionID));
  815. }
  816. /**
  817. * 获得bug创建页面的执行列表,并绑定到bug上。
  818. * Get the executions for the bug create page and bind them to bug.
  819. *
  820. * @param object $bug
  821. * @access private
  822. * @return object
  823. */
  824. private function getExecutionsForCreate($bug)
  825. {
  826. $productID = (int)$bug->productID;
  827. $branch = (string)$bug->branch;
  828. $projectID = (int)$bug->projectID;
  829. $executionID = (int)$bug->executionID;
  830. $executions = $this->product->getExecutionPairsByProduct($productID, $branch ? "0,$branch" : '0', $projectID, !$projectID ? 'multiple|stagefilter|noclosed' : 'stagefilter|noclosed');
  831. $executionID = isset($executions[$executionID]) ? $executionID : '';
  832. $execution = null;
  833. if($executionID) $execution = $this->loadModel('execution')->getByID($executionID);
  834. if($execution && !$execution->multiple)
  835. {
  836. $this->config->bug->list->customCreateFields = str_replace('execution,', '', $this->config->bug->list->customCreateFields);
  837. $this->config->bug->custom->createFields = str_replace('execution,', '', $this->config->bug->custom->createFields);
  838. }
  839. return $this->updateBug($bug, array('executions' => $executions, 'execution' => $execution, 'executionID' => $executionID));
  840. }
  841. /**
  842. * 获取bug创建页面的影响版本,并绑定到bug上。
  843. * Get the builds for the bug create page and bind them to bug.
  844. *
  845. * @param object $bug
  846. * @access private
  847. * @return object
  848. */
  849. private function getBuildsForCreate($bug)
  850. {
  851. $productID = (int)$bug->productID;
  852. $branch = (string)$bug->branch;
  853. $projectID = (int)$bug->projectID;
  854. $executionID = (int)$bug->executionID;
  855. $this->loadModel('build');
  856. if(!empty($bug->allBuilds))
  857. {
  858. $builds = $this->build->getBuildPairs(array($productID), empty($branch) ? 'all' : $branch, 'noempty,noterminate,nodone,withbranch,noreleased,noreplace', 0, '');
  859. }
  860. elseif($executionID)
  861. {
  862. $builds = $this->build->getBuildPairs(array($productID), $branch, 'noempty,noterminate,nodone,noreleased,nowaitreleased,nofail', $executionID, 'execution');
  863. }
  864. elseif($projectID)
  865. {
  866. $builds = $this->build->getBuildPairs(array($productID), $branch, 'noempty,noterminate,nodone,noreleased,nowaitreleased,nofail', $projectID, 'project');
  867. }
  868. else
  869. {
  870. $builds = $this->build->getBuildPairs(array($productID), empty($branch) ? 'all' : $branch, 'noempty,noterminate,nodone,withbranch,noreleased,nowaitreleased,nofail');
  871. }
  872. $builds = $this->build->addReleaseLabelForBuilds($productID, $builds);
  873. return $this->updateBug($bug, array('builds' => $builds));
  874. }
  875. /**
  876. * 获取bug创建页面的相关需求,并绑定到bug上。
  877. * Get the stories for the bug create page and bind them to bug.
  878. *
  879. * @param object $bug
  880. * @access private
  881. * @return object
  882. */
  883. private function getStoriesForCreate($bug)
  884. {
  885. $productID = (int)$bug->productID;
  886. $branch = (string)$bug->branch;
  887. $moduleID = (int)$bug->moduleID;
  888. $projectID = (int)$bug->projectID;
  889. $executionID = (int)$bug->executionID;
  890. if($executionID || $projectID)
  891. {
  892. $stories = $this->story->getExecutionStoryPairs($executionID ? $executionID : $projectID, $productID, $branch, $moduleID, 'full', 'all', 'story', false);
  893. }
  894. else
  895. {
  896. $moduleIdList = $moduleID;
  897. if($moduleIdList)
  898. {
  899. $moduleIdList = $this->loadModel('tree')->getStoryModule($moduleIdList);
  900. $moduleIdList = $this->tree->getAllChildID($moduleIdList);
  901. }
  902. $stories = $this->story->getProductStoryPairs($productID, $branch, $moduleIdList, 'active', 'id_desc', 0, '', 'story', false);
  903. }
  904. if(!in_array($this->app->tab, array('execution', 'project')) and empty($stories)) $stories = $this->story->getProductStoryPairs($productID, $branch, 0, 'active', 'id_desc', 0, '', 'story', false);
  905. $stories = $this->story->addGradeLabel($stories);
  906. return $this->updateBug($bug, array('stories' => $stories));
  907. }
  908. /**
  909. * 获取bug创建页面的相关任务,并绑定到bug上。
  910. * Get the tasks for the bug create page and bind them to bug.
  911. *
  912. * @param object $bug
  913. * @access private
  914. * @return object
  915. */
  916. private function getTasksForCreate($bug)
  917. {
  918. $executionID = (int)$bug->executionID;
  919. $tasks = null;
  920. if($executionID) $tasks = $this->task->getExecutionTaskPairs($executionID);
  921. return $this->updateBug($bug, array('tasks' => $tasks));
  922. }
  923. /**
  924. *
  925. * 构建创建bug页面数据。
  926. * Build form fields for create bug.
  927. *
  928. * @param object $bug
  929. * @param array $param
  930. * @param string $from
  931. * @access protected
  932. * @return void
  933. */
  934. public function buildCreateForm($bug, $param, $from)
  935. {
  936. extract($param);
  937. $originAssignedTo = $bug->assignedTo;
  938. $bug = $this->getProductsForCreate($bug);
  939. $bug = $this->getBranchesForCreate($bug);
  940. $bug = $this->getModulesForCreate($bug);
  941. $bug = $this->getProjectsForCreate($bug);
  942. $bug = $this->getExecutionsForCreate($bug);
  943. $bug = $this->getBuildsForCreate($bug);
  944. $bug = $this->getStoriesForCreate($bug);
  945. $bug = $this->gettasksForCreate($bug);
  946. $productMembers = $this->getProductMembersForCreate($bug);
  947. if(!in_array($bug->assignedTo, array_keys($productMembers))) $bug->assignedTo = $originAssignedTo;
  948. $resultFiles = array();
  949. if(!empty($resultID) && !empty($stepIdList))
  950. {
  951. $resultFiles = $this->loadModel('file')->getByObject('stepResult', (int)$resultID, str_replace('_', ',', $stepIdList));
  952. foreach($resultFiles as $resultFile)
  953. {
  954. $resultFile->name = $resultFile->title;
  955. $resultFile->url = $this->createLink('file', 'download', "fileID={$resultFile->id}");
  956. }
  957. }
  958. $this->view->title = isset($this->products[$bug->productID]) ? $this->products[$bug->productID] . $this->lang->hyphen . $this->lang->bug->create : $this->lang->bug->create;
  959. $this->view->productMembers = $productMembers;
  960. $this->view->gobackLink = $from == 'global' ? $this->createLink('bug', 'browse', "productID=$bug->productID") : '';
  961. $this->view->productName = isset($this->products[$bug->productID]) ? $this->products[$bug->productID] : '';
  962. $this->view->projectExecutionPairs = $this->loadModel('project')->getProjectExecutionPairs();
  963. $this->view->products = $bug->products;
  964. $this->view->product = $this->product->getByID((int)$bug->productID);
  965. $this->view->productID = $this->session->product;
  966. $this->view->projects = commonModel::isTutorialMode() ? $this->loadModel('tutorial')->getProjectPairs() : $bug->projects;
  967. $this->view->project = $bug->project;
  968. $this->view->projectID = $bug->projectID;
  969. $this->view->executions = commonModel::isTutorialMode() ? $this->loadModel('tutorial')->getExecutionPairs() : $bug->executions;
  970. $this->view->execution = $bug->execution;
  971. $this->view->executionID = !empty($executionID) ? $executionID : $bug->executionID;
  972. $this->view->branches = $bug->branches;
  973. $this->view->builds = $bug->builds;
  974. $this->view->moduleOptionMenu = $bug->modules;
  975. $this->view->bug = $bug;
  976. $this->view->allBuilds = !empty($bug->allBuilds) ? $bug->allBuilds : '';
  977. $this->view->allUsers = !empty($bug->allUsers) ? $bug->allUsers : '';
  978. $this->view->releasedBuilds = $this->loadModel('release')->getReleasedBuilds((int)$bug->productID, (string)$bug->branch);
  979. $this->view->resultFiles = $resultFiles;
  980. $this->view->contactList = $this->loadModel('user')->getContactLists();
  981. $this->view->branchID = $bug->branch != 'all' ? $bug->branch : '0';
  982. $this->view->cases = $this->loadModel('testcase')->getPairsByProduct((int)$bug->product, array(0, $this->view->branchID));
  983. $this->view->copyBugID = isset($bugID) ? $bugID : 0;
  984. $this->view->plans = $this->loadModel('productplan')->getPairs($bug->productID, $bug->branch, 'noclosed', true);
  985. }
  986. /**
  987. * 获取页面所需的变量, 并输出到前台。
  988. * Get the data required by the view page and output.
  989. *
  990. * @param object $bug
  991. * @access protected
  992. * @return void
  993. */
  994. protected function buildEditForm($bug)
  995. {
  996. /* 删掉当前 bug 类型不属于的并且已经弃用的类型。*/
  997. /* Unset discarded types. */
  998. foreach($this->config->bug->discardedTypes as $type)
  999. {
  1000. if($bug->type != $type) unset($this->lang->bug->typeList[$type]);
  1001. }
  1002. $product = $this->product->getByID($bug->product);
  1003. $execution = $this->loadModel('execution')->getByID($bug->execution);
  1004. /* Get module option menu. */
  1005. $moduleOptionMenu = $this->tree->getOptionMenu($bug->product, 'bug', 0, (string)$bug->branch);
  1006. if(!isset($moduleOptionMenu[$bug->module])) $moduleOptionMenu += $this->tree->getModulesName((array)$bug->module);
  1007. /* Get bugs of current product. */
  1008. $branch = '';
  1009. if($product->type == 'branch') $branch = $bug->branch > 0 ? "{$bug->branch},0" : '0';
  1010. /* Get execution pairs. */
  1011. $unAllowedStage = array('request', 'design', 'review');
  1012. $executions = $this->product->getExecutionPairsByProduct($bug->product, (string)$bug->branch, (int)$bug->project, '', $unAllowedStage);
  1013. if(!empty($bug->execution) && empty($executions[$bug->execution]) && !in_array($execution->attribute, $unAllowedStage)) $executions[$execution->id] = $execution->name . "({$this->lang->bug->deleted})";
  1014. /* Get project pairs. */
  1015. $projectPairs = $product->shadow ? $this->loadModel('project')->getPairs(false, 'noproduct,noclosed,haspriv') : array();
  1016. $projects = $this->product->getProjectPairsByProduct($bug->product, (string)$bug->branch, array_keys($projectPairs));
  1017. if(!empty($bug->project) && empty($projects[$bug->project]))
  1018. {
  1019. $project = $this->loadModel('project')->getByID($bug->project);
  1020. $projects[$project->id] = $project->name . "({$this->lang->bug->deleted})";
  1021. }
  1022. /* 获取分支列表。*/
  1023. /* Get branch options. */
  1024. $branchTagOption = array();
  1025. if($product->type != 'normal') $branchTagOption = $this->getBranchOptions($product->id);
  1026. if(in_array($this->config->edition, array('max', 'ipd')))
  1027. {
  1028. $this->view->injectionList = $this->lang->bug->injectionList;
  1029. $this->view->identifyList = $this->lang->bug->identifyList;
  1030. }
  1031. $this->assignVarsForEdit($bug, $product);
  1032. $duplicateBugs = $this->bug->getProductBugPairs(0, $bug->branch, '', 0, 'all');
  1033. if(!empty($bug->duplicateBug))
  1034. {
  1035. $duplicateBug = $this->bug->fetchByID($bug->duplicateBug);
  1036. $duplicateBugs[$bug->duplicateBug] = $this->lang->productCommon . '#' . $duplicateBug->product . '@'. $duplicateBug->id . ':' . $duplicateBug->title;
  1037. }
  1038. unset($duplicateBugs[$bug->id]);
  1039. $this->view->title = $this->lang->bug->edit . "BUG #$bug->id $bug->title - " . $this->products[$bug->product];
  1040. $this->view->bug = $bug;
  1041. $this->view->duplicateBugs = $duplicateBugs;
  1042. $this->view->product = $product;
  1043. $this->view->moduleOptionMenu = $moduleOptionMenu;
  1044. $this->view->projectID = $bug->project;
  1045. $this->view->projects = $projects;
  1046. $this->view->executions = $executions;
  1047. $this->view->branchTagOption = $branchTagOption;
  1048. $this->view->projectExecutionPairs = $this->loadModel('project')->getProjectExecutionPairs();
  1049. }
  1050. /**
  1051. * 为编辑 bug 指派版本数据。
  1052. * Assign variables for editing bug.
  1053. *
  1054. * @param object $bug
  1055. * @param object $product
  1056. * @access protected
  1057. * @return void
  1058. */
  1059. protected function assignVarsForEdit($bug, $product)
  1060. {
  1061. /* Add product related to the bug when it is not in the products. */
  1062. $product = $this->loadModel('product')->fetchByID($bug->product);
  1063. if(!isset($this->products[$bug->product]))
  1064. {
  1065. $this->products[$bug->product] = $product->name;
  1066. $this->view->products = $this->products;
  1067. }
  1068. if(empty($product->shadow))
  1069. {
  1070. $products = $this->view->products;
  1071. $productList = $this->loadModel('product')->getByIdList(array_keys($products));
  1072. foreach($products as $id => $name)
  1073. {
  1074. if($id != $bug->product && (!empty($productList[$id]->shadow) || $productList[$id]->status == 'closed')) unset($products[$id]);
  1075. }
  1076. $this->view->products = $products;
  1077. }
  1078. if($bug->execution)
  1079. {
  1080. $openedBuilds = $this->loadModel('build')->getBuildPairs(array($bug->product), $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased,nofail', $bug->execution, 'execution');
  1081. $assignedToList = $this->user->getTeamMemberPairs($bug->execution, 'execution');
  1082. }
  1083. elseif($bug->project)
  1084. {
  1085. $openedBuilds = $this->loadModel('build')->getBuildPairs(array($bug->product), $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased,nofail', $bug->project, 'project');
  1086. $assignedToList = $this->loadModel('project')->getTeamMemberPairs($bug->project);
  1087. }
  1088. else
  1089. {
  1090. $openedBuilds = $this->loadModel('build')->getBuildPairs(array($bug->product), empty($bug->branch) ? 'all' : $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased,nofail');
  1091. $assignedToList = $this->bug->getProductMemberPairs($bug->product, (string)$bug->branch);
  1092. $assignedToList = array_filter($assignedToList);
  1093. if(empty($assignedToList)) $assignedToList = $this->user->getPairs('devfirst|noclosed');
  1094. }
  1095. $bugOpenedBuilds = explode(',', $bug->openedBuild);
  1096. if(!empty($bugOpenedBuilds))
  1097. {
  1098. foreach($bugOpenedBuilds as $bugOpenedBuild)
  1099. {
  1100. if(!isset($openedBuilds[$bugOpenedBuild]))
  1101. {
  1102. $build = $this->build->getByID((int)$bugOpenedBuild);
  1103. if($build) $openedBuilds[$bugOpenedBuild] = $build->name;
  1104. }
  1105. }
  1106. }
  1107. $openedBuilds = $this->build->addReleaseLabelForBuilds($bug->product, $openedBuilds);
  1108. if($bug->assignedTo && !isset($assignedToList[$bug->assignedTo]) && $bug->assignedTo != 'closed')
  1109. {
  1110. $assignedTo = $this->user->getById($bug->assignedTo);
  1111. $assignedToList[$bug->assignedTo] = isset($assignedTo->realname) ? $assignedTo->realname : $bug->assignedTo;
  1112. }
  1113. if($bug->status == 'closed') $assignedToList['closed'] = 'Closed';
  1114. $cases = $this->loadModel('testcase')->getPairsByProduct($bug->product, array(0, $bug->branch));
  1115. $this->config->moreLinks['case'] = inlink('ajaxGetProductCases', "productID={$bug->product}");
  1116. if($bug->execution)
  1117. {
  1118. $stories = $this->story->getExecutionStoryPairs($bug->execution, 0, 'all', '', 'full', 'all', 'story', false);
  1119. }
  1120. else
  1121. {
  1122. $moduleIdList = $bug->module;
  1123. if($moduleIdList)
  1124. {
  1125. $moduleIdList = $this->loadModel('tree')->getStoryModule($moduleIdList);
  1126. $moduleIdList = $this->tree->getAllChildID($moduleIdList);
  1127. }
  1128. $stories = $this->story->getProductStoryPairs($bug->product, $bug->branch, $moduleIdList, 'active', 'id_desc', 0, '', 'story', false);
  1129. }
  1130. if(!in_array($this->app->tab, array('execution', 'project')) and empty($stories)) $stories = $this->story->getProductStoryPairs($bug->product, $bug->branch, 0, 'active', 'id_desc', 0, '', 'story', false);
  1131. if(!isset($stories[$bug->story])) $stories[$bug->story] = $bug->story . ':' . $bug->storyTitle;
  1132. $resolvedBuildPairs = $this->build->getBuildPairs(array($bug->product), $bug->branch, 'noempty');
  1133. $this->view->resolvedBuildPairs = $resolvedBuildPairs;
  1134. $this->view->resolvedBuilds = $this->build->addReleaseLabelForBuilds($bug->product, $resolvedBuildPairs);
  1135. $this->view->openedBuilds = $openedBuilds;
  1136. $this->view->plans = $this->loadModel('productplan')->getPairs($bug->product, $bug->branch, '', true);
  1137. $this->view->stories = $this->story->addGradeLabel($stories);
  1138. $this->view->tasks = $this->task->getExecutionTaskPairs($bug->execution);
  1139. $this->view->testtasks = $this->loadModel('testtask')->getPairs($bug->product, $bug->execution, $bug->testtask);
  1140. $this->view->cases = $cases;
  1141. $this->view->users = $this->user->getPairs('noclosed', "$bug->assignedTo,$bug->resolvedBy,$bug->closedBy,$bug->openedBy");
  1142. $this->view->actions = $this->loadModel('action')->getList('bug', $bug->id);
  1143. $this->view->contactList = $this->loadModel('user')->getContactLists();
  1144. $this->view->assignedToList = $assignedToList;
  1145. $this->view->execution = $this->loadModel('execution')->getByID($bug->execution);
  1146. }
  1147. /**
  1148. * 为解决bug构造bug数据。
  1149. * Build bug for resolving a bug.
  1150. *
  1151. * @param object $oldBug
  1152. * @access protected
  1153. * @return object
  1154. */
  1155. protected function buildBugForResolve($oldBug)
  1156. {
  1157. $bug = form::data($this->config->bug->form->resolve, $oldBug->id)
  1158. ->setDefault('resolvedDate', helper::now())
  1159. ->add('id', $oldBug->id)
  1160. ->add('execution', $oldBug->execution)
  1161. ->add('status', 'resolved')
  1162. ->add('confirmed', 1)
  1163. ->removeIF($this->post->resolution != 'duplicate', 'duplicateBug')
  1164. ->get();
  1165. /* If the resolved build is not the trunk, get test plan id. */
  1166. if(isset($bug->resolvedBuild) && $bug->resolvedBuild != 'trunk')
  1167. {
  1168. $testtaskID = (int)$this->dao->select('id')->from(TABLE_TESTTASK)->where('build')->eq($bug->resolvedBuild)->orderBy('id_desc')->limit(1)->fetch('id');
  1169. if($testtaskID and empty($oldBug->testtask)) $bug->testtask = $testtaskID;
  1170. }
  1171. return $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->resolve['id'], $this->post->uid);
  1172. }
  1173. /**
  1174. * 为批量创建 bug 构造数据。
  1175. * Build bugs for the batch creation.
  1176. *
  1177. * @param int $productID
  1178. * @param string $branch
  1179. * @param array $bugImagesFile
  1180. * @access protected
  1181. * @return array
  1182. */
  1183. protected function buildBugsForBatchCreate($productID, $branch, $bugImagesFile = array())
  1184. {
  1185. $bugs = form::batchData($this->config->bug->form->batchCreate)->get();
  1186. /* Get pairs(moduleID => moduleOwner) for bug. */
  1187. $stmt = $this->app->dbQuery($this->loadModel('tree')->buildMenuQuery($productID, 'bug', 0, $branch));
  1188. $moduleOwners = array();
  1189. while($module = $stmt->fetch()) $moduleOwners[$module->id] = $module->owner;
  1190. /* Construct data. */
  1191. foreach($bugs as $index => $bug)
  1192. {
  1193. $bug->openedBy = $this->app->user->account;
  1194. $bug->openedDate = helper::now();
  1195. $bug->product = $productID;
  1196. $bug->steps = nl2br($bug->steps);
  1197. /* Assign the bug to the person in charge of the module. */
  1198. if(!empty($moduleOwners[$bug->module]))
  1199. {
  1200. $bug->assignedTo = $moduleOwners[$bug->module];
  1201. $bug->assignedDate = helper::now();
  1202. }
  1203. $uploadImage = !empty($this->post->uploadImage[$index]) ? $this->post->uploadImage[$index] : '';
  1204. $imageFile = $this->processImageForBatchCreate($bug, $uploadImage, $bugImagesFile);
  1205. $bug->uploadImage = $uploadImage;
  1206. $bug->imageFile = $imageFile;
  1207. }
  1208. return $bugs;
  1209. }
  1210. /**
  1211. * 展示批量创建bug的相关变量。
  1212. * Show the variables associated with the batch creation bugs.
  1213. *
  1214. * @param int $executionID
  1215. * @param object $product
  1216. * @param string $branch
  1217. * @param array $output
  1218. * @param array $bugImagesFile
  1219. * @access protected
  1220. * @return void
  1221. */
  1222. protected function assignBatchCreateVars($executionID, $product, $branch, $output, $bugImagesFile)
  1223. {
  1224. if($executionID)
  1225. {
  1226. /* Get builds, stories and branches of this execution. */
  1227. $builds = $this->loadModel('build')->getBuildPairs(array($product->id), $branch, 'noempty,noreleased', $executionID, 'execution');
  1228. $stories = $this->story->getExecutionStoryPairs($executionID);
  1229. $productBranches = $product->type != 'normal' ? $this->loadModel('execution')->getBranchByProduct(array($product->id), $executionID) : array();
  1230. $branches = isset($productBranches[$product->id]) ? $productBranches[$product->id] : array();
  1231. $branch = key($branches) ? key($branches) : 'all';
  1232. /* Get the variables associated with kanban. */
  1233. $execution = $this->loadModel('execution')->getById($executionID);
  1234. if($execution->type == 'kanban') $this->assignKanbanVars($execution, $output);
  1235. }
  1236. else
  1237. {
  1238. /* Get builds, stories and branches of the product. */
  1239. $builds = $this->loadModel('build')->getBuildPairs(array($product->id), $branch, 'noempty,noreleased');
  1240. $stories = $this->story->getProductStoryPairs($product->id, $branch);
  1241. $branches = $product->type != 'normal' ? $this->loadModel('branch')->getPairs($product->id, 'active') : array();
  1242. }
  1243. /* Get project information. */
  1244. $projectID = isset($execution) ? $execution->project : 0;
  1245. $project = $this->loadModel('project')->getByID($projectID);
  1246. if(!$project) $project = new stdclass();
  1247. $this->assignVarsForBatchCreate($product, $project, $bugImagesFile);
  1248. $this->view->projects = $this->product->getProjectPairsByProduct($product->id, $branch ? "0,{$branch}" : '0');
  1249. $this->view->project = $project;
  1250. $this->view->projectID = $projectID;
  1251. $this->view->executions = $this->product->getExecutionPairsByProduct($product->id, $branch ? "0,{$branch}" : '0', (int)$projectID, 'multiple,stagefilter');
  1252. $this->view->executionID = $executionID;
  1253. $this->view->stories = $stories;
  1254. $this->view->builds = $builds;
  1255. $this->view->branch = $branch;
  1256. $this->view->branches = $branches;
  1257. $this->view->moduleOptionMenu = $this->tree->getOptionMenu($product->id, 'bug', 0, $branch === 'all' ? 'all' : (string)$branch);
  1258. $this->view->plans = $this->loadModel('productplan')->getPairs($product->id, $branch, 'noclosed', true);
  1259. }
  1260. /**
  1261. * 展示看板的相关变量。
  1262. * Show the variables associated with the kanban.
  1263. *
  1264. * @param object $execution
  1265. * @param array $output
  1266. * @access protected
  1267. * @return void
  1268. */
  1269. protected function assignKanbanVars($execution, $output)
  1270. {
  1271. $regionPairs = $this->loadModel('kanban')->getRegionPairs($execution->id, 0, 'execution');
  1272. $regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
  1273. if(!empty($output['groupID']))
  1274. {
  1275. $lanePairs = $this->kanban->getLanePairsByGroup((int)$output['groupID']);
  1276. $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs);
  1277. }
  1278. $lanePairs = $this->kanban->getLanePairsByRegion((int)$regionID, 'bug');
  1279. if(empty($laneID)) $laneID = !empty($output['laneID']) ? $output['laneID'] : key($lanePairs);
  1280. $this->view->executionType = $execution->type;
  1281. $this->view->regionID = $regionID;
  1282. $this->view->laneID = $laneID;
  1283. $this->view->regionPairs = $regionPairs;
  1284. $this->view->lanePairs = $lanePairs;
  1285. }
  1286. /**
  1287. * 展示字段相关变量。
  1288. * Show the variables associated with the batch created fields.
  1289. *
  1290. * @param object $product
  1291. * @param object $project
  1292. * @param array $bugImagesFile
  1293. * @access protected
  1294. * @return void
  1295. */
  1296. protected function assignVarsForBatchCreate($product, $project, $bugImagesFile)
  1297. {
  1298. /* Set custom fields. */
  1299. foreach(explode(',', $this->config->bug->list->customBatchCreateFields) as $field)
  1300. {
  1301. $customFields[$field] = $this->lang->bug->$field;
  1302. }
  1303. if($product->type != 'normal') $customFields['branch'] = $this->lang->product->branchName[$product->type];
  1304. if(isset($project->model) && $project->model == 'kanban') $customFields['execution'] = $this->lang->bug->kanban;
  1305. /* Set display fields. */
  1306. $showFields = $this->config->bug->custom->batchCreateFields;
  1307. $showFields = sprintf($showFields, $product->type != 'normal' ? 'branch' : '');
  1308. $showFields = trim($showFields, ',');
  1309. /* Get titles from uploaded images. */
  1310. if(!empty($bugImagesFile))
  1311. {
  1312. foreach($bugImagesFile as $fileName => $file)
  1313. {
  1314. $title = $file['title'];
  1315. $titles[$title] = $fileName;
  1316. }
  1317. $this->view->titles = $titles;
  1318. }
  1319. $this->view->customFields = $customFields;
  1320. $this->view->showFields = $showFields;
  1321. }
  1322. /**
  1323. * 设置关联相关 bug 页面的搜索表单。
  1324. * Build search form for link related bugs page.
  1325. *
  1326. * @param object $bug
  1327. * @param string $excludeBugs
  1328. * @param int $queryID
  1329. * @access protected
  1330. * @return void
  1331. */
  1332. protected function buildSearchFormForLinkBugs($bug, $excludeBugs, $queryID)
  1333. {
  1334. /* 无产品项目搜索时隐藏产品、执行和所属计划字段。*/
  1335. /* Hide plan, execution and product in no product project. */
  1336. if($bug->project && $this->app->tab != 'qa')
  1337. {
  1338. $project = $this->loadModel('project')->getByID($bug->project);
  1339. if(!$project->hasProduct)
  1340. {
  1341. unset($this->config->bug->search['fields']['product']);
  1342. /* 单迭代项目搜索时隐藏执行和所属计划字段。*/
  1343. /* Hide execution and plan in single project. */
  1344. if(!$project->multiple)
  1345. {
  1346. unset($this->config->bug->search['fields']['execution']);
  1347. unset($this->config->bug->search['fields']['plan']);
  1348. }
  1349. }
  1350. }
  1351. $actionURL = $this->createLink('bug', 'linkBugs', "bugID={$bug->id}&bySearch=true&excludeBugs={$excludeBugs}&queryID=myQueryID", '', true);
  1352. $this->bug->buildSearchForm($bug->product, $this->products, $queryID, $actionURL);
  1353. }
  1354. /**
  1355. * 为批量编辑 bugs 构造数据。
  1356. * Build bugs for the batch edit.
  1357. *
  1358. * @param array $oldBugs
  1359. * @access protected
  1360. * @return array
  1361. */
  1362. protected function buildBugsForBatchEdit($oldBugs = array())
  1363. {
  1364. $this->loadModel('execution');
  1365. if(empty($oldBugs)) return array();
  1366. /* Get bugs. */
  1367. $bugs = form::batchData($this->config->bug->form->batchEdit)->get();
  1368. $now = helper::now();
  1369. $noSprintProjects = $this->loadModel('project')->getPairs(false, 'nosprint');
  1370. /* Process bugs. */
  1371. foreach($bugs as $index => $bug)
  1372. {
  1373. $oldBug = $oldBugs[$bug->id];
  1374. if(is_array($bug->os)) $bug->os = implode(',', $bug->os);
  1375. if(is_array($bug->browser)) $bug->browser = implode(',', $bug->browser);
  1376. /* If bug is closed, the assignee will not be changed. */
  1377. if($oldBug->status == 'closed') $bug->assignedTo = $oldBug->assignedTo;
  1378. /* If resolution of the bug is not duplicate, duplicateBug is zero. */
  1379. if($bug->resolution != 'duplicate') $bug->duplicateBug = 0;
  1380. /* If assignee is changes, set the assigned date. */
  1381. if($bug->assignedTo != $oldBug->assignedTo) $bug->assignedDate = $now;
  1382. /* If resolution is not empty, set the confirmed. */
  1383. if($bug->resolution != '') $bug->confirmed = 1;
  1384. /* If the bug is resolved, set resolved date and bug status. */
  1385. if(($bug->resolvedBy != '' || $bug->resolution != '') && strpos(',resolved,closed,', ",{$oldBug->status},") === false)
  1386. {
  1387. $bug->resolvedDate = $now;
  1388. $bug->status = 'resolved';
  1389. }
  1390. /* If the bug without resolver is resolved, set resolver. */
  1391. if($bug->resolution != '' && $bug->resolvedBy == '') $bug->resolvedBy = $this->app->user->account;
  1392. /* If the bug without assignee is resolved, set assignee and assigned date. */
  1393. if($bug->resolution != '' && $bug->assignedTo == '')
  1394. {
  1395. $bug->assignedTo = $oldBug->openedBy;
  1396. $bug->assignedDate = $now;
  1397. }
  1398. if(!empty($bug->project) && !empty($noSprintProjects[$bug->project])) $bug->execution = $this->execution->getNoMultipleID($bug->project);
  1399. }
  1400. return $bugs;
  1401. }
  1402. /**
  1403. * 为批量编辑 bug 分配变量。
  1404. * Assign variables for batch edit.
  1405. *
  1406. * @param int $productID
  1407. * @param string $branch
  1408. * @access protected
  1409. * @return void
  1410. */
  1411. protected function assignBatchEditVars($productID, $branch)
  1412. {
  1413. /* Initialize vars.*/
  1414. $bugIdList = array_unique($this->post->bugIdList);
  1415. $bugs = $this->bug->getByIdList($bugIdList, '*', 'id_desc');
  1416. /* Set menu and get product id list. */
  1417. if($this->app->tab == 'product') $this->product->setMenu($productID);
  1418. if($productID)
  1419. {
  1420. $this->qa->setMenu($productID, $branch);
  1421. $productIdList = array($productID => $productID);
  1422. }
  1423. else
  1424. {
  1425. $this->app->loadLang('my');
  1426. $this->lang->task->menu = $this->lang->my->menu->work;
  1427. $this->lang->my->menu->work['subModule'] = 'bug';
  1428. $productIdList = array_column($bugs, 'product', 'product');
  1429. }
  1430. /* Get products. */
  1431. $products = $this->product->getByIdList($productIdList);
  1432. /* Get custom Fields. */
  1433. foreach(explode(',', $this->config->bug->list->customBatchEditFields) as $field) $customFields[$field] = $this->lang->bug->$field;
  1434. $this->view->title = ($productID ? (zget($products, $productID, '', $products[$productID]->name . $this->lang->hyphen) . "BUG") : '') . $this->lang->bug->batchEdit;
  1435. $this->view->customFields = $customFields;
  1436. /* Judge whether the editedBugs is too large and set session. */
  1437. $countInputVars = count($bugs) * (count(explode(',', $this->config->bug->custom->batchEditFields)) + 2);
  1438. $showSuhosinInfo = common::judgeSuhosinSetting($countInputVars);
  1439. if($showSuhosinInfo)
  1440. {
  1441. $this->view->suhosinInfo = extension_loaded('suhosin') ? sprintf($this->lang->suhosinInfo, $countInputVars) : sprintf($this->lang->maxVarsInfo, $countInputVars);
  1442. $this->display();
  1443. }
  1444. /* Assign product related variables. */
  1445. $branchTagOption = $this->assignProductRelatedVars($bugs, $products);
  1446. $this->view->productID = $productID;
  1447. $this->view->branch = $branch;
  1448. /* Assign project related variables. */
  1449. $this->assignProjectRelatedVars($bugs, $products);
  1450. /* Assign users. */
  1451. $this->assignUsersForBatchEdit($bugs, $productIdList, $branchTagOption);
  1452. }
  1453. /**
  1454. * 分配产品相关的变量。
  1455. * Assign product related variables.
  1456. *
  1457. * @param array $bugs
  1458. * @param array $products
  1459. * @access private
  1460. * @return array
  1461. */
  1462. private function assignProductRelatedVars($bugs, $products)
  1463. {
  1464. /* Get modules, bugs and plans of the products. */
  1465. $branchProduct = false;
  1466. $modules = array();
  1467. $branchTagOption = array();
  1468. $productPlanList = array();
  1469. foreach($products as $product)
  1470. {
  1471. if(!isset($productPlanList[$product->id])) $productPlanList[$product->id] = array();
  1472. $branches = '0';
  1473. if($product->type != 'normal')
  1474. {
  1475. $branchPairs = $this->loadModel('branch')->getPairs($product->id, 'withClosed');
  1476. $branches = array_keys($branchPairs);
  1477. $branchProduct = true;
  1478. foreach($branchPairs as $branchID => $branchName)
  1479. {
  1480. $branchTagOption[$product->id][$branchID] = "/{$product->name}/{$branchName}";
  1481. $productPlanList[$product->id][$branchID] = $this->loadModel('productplan')->getPairs($product->id, $branchID, '', true);
  1482. }
  1483. }
  1484. else
  1485. {
  1486. $productPlanList[$product->id][0] = $this->loadModel('productplan')->getPairs($product->id, 0, '', true);
  1487. }
  1488. $modulePairs = $this->tree->getOptionMenu($product->id, 'bug', 0, $branches);
  1489. $modules[$product->id] = $product->type != 'normal' ? $modulePairs : array(0 => $modulePairs);
  1490. }
  1491. $productPlanOptions = array();
  1492. foreach($productPlanList as $productID => $productPlans)
  1493. {
  1494. $productPlanOptions[$productID] = array();
  1495. foreach($productPlans as $branchID => $branchPlans)
  1496. {
  1497. $productPlanOptions[$productID][$branchID] = array();
  1498. foreach($branchPlans as $bugID => $bugTitle)
  1499. {
  1500. $productPlanOptions[$productID][$branchID][] = array('text' => $bugTitle, 'value' => $bugID);
  1501. }
  1502. }
  1503. }
  1504. /* Get module of the bugs, and set bug plans. */
  1505. foreach($bugs as $bug)
  1506. {
  1507. if(!isset($modules[$bug->product][0])) $modules[$bug->product][0] = array();
  1508. if(!isset($modules[$bug->product][$bug->branch]) && isset($modules[$bug->product])) $modules[$bug->product][$bug->branch] = $modules[$bug->product][0] + $this->tree->getModulesName($bug->module);
  1509. $bug->plans = isset($productPlanOptions[$bug->product]) && isset($productPlanOptions[$bug->product][$bug->branch]) ? $productPlanOptions[$bug->product][$bug->branch] : array();
  1510. }
  1511. $bugModules = array();
  1512. foreach($modules as $productID => $productModules)
  1513. {
  1514. $bugModules[$productID] = array();
  1515. foreach($productModules as $branchID => $branchModules)
  1516. {
  1517. $bugModules[$productID][$branchID] = array();
  1518. foreach($branchModules as $moduleID => $module)
  1519. {
  1520. $bugModules[$productID][$branchID][] = array('text' => $module, 'value' => $moduleID);
  1521. }
  1522. }
  1523. }
  1524. $branchOptions = array();
  1525. foreach($branchTagOption as $productID => $productBranches)
  1526. {
  1527. $branchOptions[$productID] = array();
  1528. foreach($productBranches as $branchID => $branchName)
  1529. {
  1530. $branchOptions[$productID][] = array('text' => $branchName, 'value' => $branchID);
  1531. }
  1532. }
  1533. $this->view->bugs = $bugs;
  1534. $this->view->branchProduct = $branchProduct;
  1535. $this->view->modules = $bugModules;
  1536. $this->view->branchTagOption = $branchOptions;
  1537. $this->view->products = $products;
  1538. return $branchTagOption;
  1539. }
  1540. /**
  1541. * 分配项目相关的变量。
  1542. *
  1543. * @param int $bugs
  1544. * @param int $products
  1545. * @access private
  1546. * @return void
  1547. */
  1548. private function assignProjectRelatedVars($bugs, $products)
  1549. {
  1550. $this->loadModel('product');
  1551. $this->loadModel('build');
  1552. $this->loadModel('execution');
  1553. $productProjectItems = array();
  1554. $productExecutionItems = array();
  1555. $productProjects = array();
  1556. $productExecutions = array();
  1557. $productOpenedBuilds = array();
  1558. $projectOpenedBuilds = array();
  1559. $executionOpenedBuilds = array();
  1560. $deletedProjects = array();
  1561. $deletedExecutions = array();
  1562. $noProductProjects = $this->loadModel('project')->getPairs(false, 'noproduct,noclosed,haspriv');
  1563. $noSprintProjects = $this->project->getPairs(false, 'nosprint');
  1564. foreach($bugs as $bug)
  1565. {
  1566. if(!isset($productProjects[$bug->product]))
  1567. {
  1568. $isShadowProduct = !empty($products[$bug->product]->shadow);
  1569. $productProjectItems[$bug->product] = $this->product->getProjectPairsByProduct($bug->product, (string)$bug->branch, $isShadowProduct ? array_keys($noProductProjects) : array());
  1570. foreach($productProjectItems[$bug->product] as $projectID => $projectName) $productProjects[$bug->product][] = array('text' => $projectName, 'value' => $projectID, 'keys' => $projectName);
  1571. }
  1572. if($bug->project > 0 && !isset($productProjectItems[$bug->product][$bug->project]))
  1573. {
  1574. $project = $this->project->fetchByID($bug->project);
  1575. $deletedProjects[$bug->project] = array('value' => $project->id, 'text' => $project->name . ' (' . $this->lang->bug->deleted . ')', 'keys' => $project->name);
  1576. }
  1577. if(!isset($productExecutions[$bug->product][$bug->project]))
  1578. {
  1579. $unAllowedStage = array('request', 'design', 'review');
  1580. $productExecutionItems[$bug->product][$bug->project] = $this->product->getExecutionPairsByProduct($bug->product, (string)$bug->branch, (int)$bug->project, '', $unAllowedStage);
  1581. foreach($productExecutionItems[$bug->product][$bug->project] as $executionID => $executionName) $productExecutions[$bug->product][$bug->project][] = array('text' => $executionName, 'value' => $executionID, 'keys' => $executionName);
  1582. }
  1583. if($bug->execution > 0 && !isset($productExecutionItems[$bug->product][$bug->project][$bug->execution]))
  1584. {
  1585. $execution = $this->execution->fetchByID($bug->execution);
  1586. $deletedExecutions[$bug->execution] = array('value' => $execution->id, 'text' => $execution->name . ' (' . $this->lang->bug->deleted . ')', 'keys' => $execution->name);
  1587. }
  1588. if($bug->execution)
  1589. {
  1590. if(isset($executionOpenedBuilds[$bug->execution])) continue;
  1591. $executionOpenedBuildItems = $this->build->getBuildPairs(array($bug->product), $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased,nofail', $bug->execution, 'execution');
  1592. foreach($executionOpenedBuildItems as $buildID => $buildName) $executionOpenedBuilds[$bug->execution][] = array('text' => $buildName, 'value' => $buildID, 'keys' => $buildName);
  1593. }
  1594. elseif($bug->project)
  1595. {
  1596. if(isset($projectOpenedBuilds[$bug->project])) continue;
  1597. $projectOpenedBuildItems = $this->build->getBuildPairs(array($bug->product), $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased,nofail', $bug->project, 'project');
  1598. foreach($projectOpenedBuildItems as $buildID => $buildName) $projectOpenedBuilds[$bug->project][] = array('text' => $buildName, 'value' => $buildID, 'keys' => $buildName);
  1599. }
  1600. else
  1601. {
  1602. if(isset($productOpenedBuilds[$bug->product])) continue;
  1603. $productOpenedBuildItems = $this->build->getBuildPairs(array($bug->product), empty($bug->branch) ? 'all' : $bug->branch, 'noempty,noterminate,nodone,withbranch,noreleased,nofail');
  1604. foreach($productOpenedBuildItems as $buildID => $buildName) $productOpenedBuilds[$bug->product][] = array('text' => $buildName, 'value' => $buildID, 'keys' => $buildName);
  1605. }
  1606. }
  1607. $this->view->noProductProjects = $noProductProjects;
  1608. $this->view->noSprintProjects = $noSprintProjects;
  1609. $this->view->deletedProjects = $deletedProjects;
  1610. $this->view->deletedExecutions = $deletedExecutions;
  1611. $this->view->projectExecutions = $this->project->getProjectExecutionPairs();
  1612. $this->view->productProjects = $productProjects;
  1613. $this->view->productExecutions = $productExecutions;
  1614. $this->view->productOpenedBuilds = $productOpenedBuilds;
  1615. $this->view->projectOpenedBuilds = $projectOpenedBuilds;
  1616. $this->view->executionOpenedBuilds = $executionOpenedBuilds;
  1617. }
  1618. /**
  1619. * 为批量编辑 bugs 分配人员。
  1620. * Assign users for batch edit.
  1621. *
  1622. * @param array $bugs
  1623. * @param array $productIdList
  1624. * @param array $branchTagOption
  1625. * @access private
  1626. * @return void
  1627. */
  1628. private function assignUsersForBatchEdit($bugs, $productIdList, $branchTagOption)
  1629. {
  1630. /* If current tab is execution or project, get project, execution, product team members of bugs.*/
  1631. if($this->app->tab == 'execution' || $this->app->tab == 'project')
  1632. {
  1633. $projectIdList = array_column($bugs, 'project', 'project');
  1634. $project = $this->loadModel('project')->getByID(key($projectIdList));
  1635. if(!empty($project) && empty($project->multiple))
  1636. {
  1637. $this->config->bug->custom->batchEditFields = str_replace('productplan', '', $this->config->bug->custom->batchEditFields);
  1638. $this->config->bug->list->customBatchEditFields = str_replace(',productplan,', ',', $this->config->bug->list->customBatchEditFields);
  1639. }
  1640. $productMembers = array();
  1641. foreach($productIdList as $id)
  1642. {
  1643. $branches = zget($branchTagOption, $id, array());
  1644. $branchList = array_keys($branches);
  1645. foreach($branchList as $branchID)
  1646. {
  1647. $members = $this->bug->getProductMemberPairs($id, (string)$branchID);
  1648. $productMembers[$id][$branchID] = array_filter($members);
  1649. }
  1650. }
  1651. /* Get members of projects. */
  1652. $projectMembers = array();
  1653. $projectMemberGroup = $this->project->getTeamMemberGroup($projectIdList);
  1654. foreach($projectIdList as $projectID)
  1655. {
  1656. $projectTeam = zget($projectMemberGroup, $projectID, array());
  1657. foreach($projectTeam as $user) $projectMembers[$projectID][$user->account] = $user->realname;
  1658. }
  1659. /* Get members of executions. */
  1660. $executionMembers = array();
  1661. $executionIdList = array_column($bugs, 'execution', 'execution');
  1662. $executionMemberGroup = $this->loadModel('execution')->getMembersByIdList($executionIdList);
  1663. foreach($executionIdList as $executionID)
  1664. {
  1665. $executionTeam = zget($executionMemberGroup, $executionID, array());
  1666. foreach($executionTeam as $user) $executionMembers[$executionID][$user->account] = $user->realname;
  1667. }
  1668. $this->view->productMembers = $productMembers;
  1669. $this->view->projectMembers = $projectMembers;
  1670. $this->view->executionMembers = $executionMembers;
  1671. }
  1672. $this->view->users = $this->user->getPairs('devfirst|noclosed|nodeleted');
  1673. }
  1674. /**
  1675. * 批量创建bug前处理上传图片。
  1676. * Before batch creating bugs, process the uploaded images.
  1677. *
  1678. * @param object $bug
  1679. * @param string $uploadImage
  1680. * @param array $bugImagesFiles
  1681. * @access protected
  1682. * @return array
  1683. */
  1684. protected function processImageForBatchCreate($bug, $uploadImage, $bugImagesFiles)
  1685. {
  1686. /* When the bug is created by uploading an image, add the image to the step of the bug. */
  1687. if(!empty($uploadImage))
  1688. {
  1689. $this->loadModel('file');
  1690. $file = $bugImagesFiles[$uploadImage];
  1691. $realPath = $file['realpath'];
  1692. if(rename($realPath, $this->file->savePath . $this->file->getSaveName($file['pathname'])))
  1693. {
  1694. if(in_array($file['extension'], $this->config->file->imageExtensions))
  1695. {
  1696. $file['addedBy'] = $this->app->user->account;
  1697. $file['addedDate'] = helper::now();
  1698. $this->loadModel('file')->saveFile($file, 'realpath');
  1699. $fileID = $this->dao->lastInsertID();
  1700. $bug->steps .= '<br><img src="{' . $fileID . '.' . $file['extension'] . '}" alt="" />';
  1701. }
  1702. }
  1703. else
  1704. {
  1705. unset($file);
  1706. }
  1707. }
  1708. return !empty($file) ? $file : array();
  1709. }
  1710. /**
  1711. * 创建bug后更新执行看板。
  1712. * Update execution kanban after create a bug.
  1713. *
  1714. * @param object $bug
  1715. * @param int $laneID
  1716. * @param int $columnID
  1717. * @param string $from
  1718. * @access private
  1719. * @return void
  1720. */
  1721. private function updateKanbanAfterCreate($bug, $laneID, $columnID, $from)
  1722. {
  1723. $bugID = $bug->id;
  1724. $executionID = $bug->execution;
  1725. if($executionID)
  1726. {
  1727. $this->loadModel('kanban');
  1728. if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell((int)$executionID, $laneID, $columnID, 'bug', (string)$bugID);
  1729. if(empty($laneID) or empty($columnID)) $this->kanban->updateLane((int)$executionID, 'bug');
  1730. }
  1731. /* Callback the callable method to process the related data for object that is transfered to bug. */
  1732. if($from && isset($this->config->bug->fromObjects[$from]) && is_callable(array($this, $this->config->bug->fromObjects[$from]['callback']))) call_user_func(array($this, $this->config->bug->fromObjects[$from]['callback']), $bugID);
  1733. }
  1734. /**
  1735. * 为create方法添加动态。
  1736. * Add action for create function.
  1737. *
  1738. * @param int $bug
  1739. * @param int $todoID
  1740. * @access private
  1741. * @return bool
  1742. * @param int $bugID
  1743. */
  1744. private function updateTodoAfterCreate($bugID, $todoID)
  1745. {
  1746. $this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec();
  1747. $this->action->create('todo', $todoID, 'finished', '', "BUG:$bugID");
  1748. if($this->config->edition != 'open')
  1749. {
  1750. $todo = $this->dao->select('type, objectID')->from(TABLE_TODO)->where('id')->eq($todoID)->fetch();
  1751. if($todo->type == 'feedback' && $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->objectID, 'done', '', $todoID);
  1752. }
  1753. return !dao::isError();
  1754. }
  1755. /**
  1756. * 更新bug模板。
  1757. * Update bug templete.
  1758. *
  1759. * @param object $bug
  1760. * @param array $fields
  1761. * @access private
  1762. * @return object
  1763. */
  1764. public function updateBug($bug, $fields)
  1765. {
  1766. foreach($fields as $field => $value) $bug->$field = $value;
  1767. return $bug;
  1768. }
  1769. /**
  1770. * 创建完 bug 后的相关处理。
  1771. * Relevant processing after create bug.
  1772. *
  1773. * @param object $bug
  1774. * @param array $params
  1775. * @param string $from
  1776. * @access protected
  1777. * @return bool
  1778. */
  1779. protected function afterCreate($bug, $params, $from = '')
  1780. {
  1781. /* 将 bug 的模块保存到 cookie。*/
  1782. /* Set module of bug to cookie. */
  1783. helper::setcookie('lastBugModule', (string)$bug->module);
  1784. if(!empty($_POST['fileList']))
  1785. {
  1786. $fileList = $this->post->fileList;
  1787. if($fileList) $fileList = json_decode($fileList, true);
  1788. $this->loadModel('file')->saveDefaultFiles($fileList, 'bug', $bug->id);
  1789. }
  1790. list($laneID, $columnID) = $this->getKanbanVariable($params);
  1791. $this->updateKanbanAfterCreate($bug, $laneID, $columnID, $from);
  1792. $todoID = isset($params['todoID']) ? $params['todoID'] : 0;
  1793. if($todoID) $this->updateTodoAfterCreate($bug->id, (int)$todoID);
  1794. return !dao::isError();
  1795. }
  1796. /**
  1797. * 更新完 bug 后的相关处理。
  1798. * Relevant processing after updating bug.
  1799. *
  1800. * @param object $bug
  1801. * @param object $oldBug
  1802. * @access protected
  1803. * @return bool
  1804. */
  1805. protected function afterUpdate($bug, $oldBug)
  1806. {
  1807. /* 解除旧的版本关联关系,关联新的版本。*/
  1808. /* Unlink old resolved build and link new resolved build. */
  1809. if(!empty($bug->resolvedBuild) && $bug->resolvedBuild != $oldBug->resolvedBuild)
  1810. {
  1811. if(!empty($oldBug->resolvedBuild)) $this->loadModel('build')->unlinkBug((int)$oldBug->resolvedBuild, $bug->id);
  1812. $this->bug->linkBugToBuild($bug->id, $bug->resolvedBuild);
  1813. }
  1814. /* 记录解除旧的计划关联关系和关联新的计划的历史。*/
  1815. /* Create actions for linking new plan and unlinking old plan. */
  1816. if($bug->plan != $oldBug->plan)
  1817. {
  1818. $this->loadModel('action');
  1819. if(!empty($oldBug->plan)) $this->action->create('productplan', $oldBug->plan, 'unlinkbug', '', $bug->id);
  1820. if(!empty($bug->plan)) $this->action->create('productplan', $bug->plan, 'linkbug', '', $bug->id);
  1821. }
  1822. if($this->config->edition == 'open') $this->bug->updateRelatedBug($bug->id, $bug->relatedBug, $oldBug->relatedBug);
  1823. /* 给 bug 解决者积分奖励。*/
  1824. /* Add score to the user who resolved the bug. */
  1825. if(!empty($bug->resolvedBy)) $this->loadModel('score')->create('bug', 'resolve', $bug);
  1826. /* 更新 bug 所属看板的泳道。*/
  1827. /* Update the lane of the bug kanban. */
  1828. if($bug->execution and $bug->status != $oldBug->status) $this->loadModel('kanban')->updateLane($bug->execution, 'bug');
  1829. /* 更新反馈的状态。*/
  1830. /* Update the status of feedback. */
  1831. if(($this->config->edition != 'open') && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
  1832. return !dao::isError();
  1833. }
  1834. /**
  1835. * 批量创建bug后的其他处理。
  1836. * Processing after batch creation of bug.
  1837. *
  1838. * @param object $bug
  1839. * @param array $output
  1840. * @access protected
  1841. * @return bool
  1842. */
  1843. protected function afterBatchCreate($bug, $output)
  1844. {
  1845. /* If bug has the execution, update kanban data. */
  1846. if($bug->execution)
  1847. {
  1848. /* Get lane id, remove laneID from bug. */
  1849. $laneID = !empty($bug->laneID) ? $bug->laneID : zget($output, 'laneID', 0);
  1850. unset($bug->laneID);
  1851. $columnID = $this->loadModel('kanban')->getColumnIDByLaneID((int)$laneID, 'unconfirmed');
  1852. if(empty($columnID)) $columnID = zget($output, 'columnID', 0);
  1853. if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($bug->execution, $laneID, $columnID, 'bug', (string)$bug->id);
  1854. if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($bug->execution, 'bug');
  1855. }
  1856. /* When the bug is created by uploading the image, add the image to the file of the bug. */
  1857. if(!empty($bug->uploadImage) and !empty($bug->imageFile))
  1858. {
  1859. $bug->imageFile['objectType'] = 'bug';
  1860. $bug->imageFile['objectID'] = $bug->id;
  1861. $bug->imageFile['addedBy'] = $this->app->user->account;
  1862. $bug->imageFile['addedDate'] = helper::now();
  1863. $this->dao->insert(TABLE_FILE)->data($bug->imageFile, 'realpath')->exec();
  1864. unset($bug->imageFile);
  1865. unset($bug->uploadImage);
  1866. }
  1867. return !dao::isError();
  1868. }
  1869. /**
  1870. * 返回不同的结果。
  1871. * Respond after updating bug.
  1872. *
  1873. * @param int $bugID
  1874. * @param array $changes
  1875. * @param int $regionID
  1876. * @param string $message
  1877. * @param bool $isInKanban true|false
  1878. * @access protected
  1879. * @return bool|int
  1880. */
  1881. protected function responseAfterOperate($bugID, $changes = array(), $message = '', $isInKanban = false)
  1882. {
  1883. if(!$message) $message = $this->lang->saveSuccess;
  1884. if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'message' => $message, 'data' => $bugID));
  1885. /* 如果 bug 转任务并且 bug 的状态发生变化,提示是否更新任务状态。*/
  1886. /* This bug has been converted to a task, update the status of the related task or not. */
  1887. $bug = $this->bug->getByID($bugID);
  1888. if($bug->toTask and !empty($changes))
  1889. {
  1890. $task = $this->loadModel('task')->fetchByID($bug->toTask);
  1891. if(in_array($task->status, array('wait', 'doing')))
  1892. {
  1893. foreach($changes as $change)
  1894. {
  1895. if($change['field'] == 'status')
  1896. {
  1897. $confirmedURL = $this->createLink('task', 'view', "taskID=$bug->toTask");
  1898. return $this->send(array('result' => 'success', 'load' => true, 'callback' => "zui.Modal.confirm('" . sprintf($this->lang->bug->notice->remindTask, $bug->toTask) . "').then((res) => {if(res) openUrl('{$confirmedURL}', {load: 'modal', size: 'lg'})});", 'closeModal' => true));
  1899. }
  1900. }
  1901. }
  1902. }
  1903. /* 在弹窗里编辑 bug 时的返回。*/
  1904. /* Respond after updating in modal. */
  1905. if(isInModal()) return $this->responseInModal($message, $isInKanban);
  1906. return $this->send(array('result' => 'success', 'message' => $message, 'closeModal' => true, 'load' => $this->createLink('bug', 'view', "bugID=$bugID")));
  1907. }
  1908. /**
  1909. * 在弹窗中操作后的返回。
  1910. * Respond after operating in modal.
  1911. *
  1912. * @param string $message
  1913. * @param bool $isInKanban true|false
  1914. * @access protected
  1915. * @return void
  1916. */
  1917. protected function responseInModal($message = '', $isInKanban = false)
  1918. {
  1919. /* 在执行应用下,编辑看板中的 bug 数据时,更新看板数据。*/
  1920. /* Update kanban data after updating bug in kanban. */
  1921. if(!$message) $message = $this->lang->saveSuccess;
  1922. if($this->app->tab == 'execution' && $isInKanban) return $this->send(array('result' => 'success', 'closeModal' => true, 'callback' => "refreshKanban()"));
  1923. return $this->send(array('result' => 'success', 'message' => $message, 'closeModal' => true, 'load' => true));
  1924. }
  1925. /**
  1926. * 创建 bug 后的返回结果。
  1927. * respond after deleting.
  1928. *
  1929. * @param object $bug
  1930. * @param array $params
  1931. * @param string $message
  1932. * @access protected
  1933. * @return bool|int
  1934. */
  1935. public function responseAfterCreate($bug, $params, $message = '')
  1936. {
  1937. $executionID = $bug->execution ? $bug->execution : (int)zget($params, 'executionID', $this->session->execution);
  1938. /* Return bug id when call the API. */
  1939. if(!$message) $message = $this->lang->saveSuccess;
  1940. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message, 'id' => $bug->id));
  1941. if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'success', 'data' => $bug->id));
  1942. if(isInModal()) return $this->send($this->responseInModal());
  1943. if($this->app->tab == 'execution')
  1944. {
  1945. $location = $this->createLink('execution', 'bug', "executionID=$executionID");
  1946. }
  1947. elseif($this->app->tab == 'project')
  1948. {
  1949. $location = $this->createLink('project', 'bug', "projectID=" . zget($params, 'projectID', $this->session->project));
  1950. }
  1951. else
  1952. {
  1953. helper::setcookie('bugModule', '0', 0);
  1954. $location = $this->createLink('bug', 'browse', "productID={$bug->product}&branch=$bug->branch&browseType=byModule&param={$bug->module}&orderBy=id_desc");
  1955. }
  1956. if($this->app->getViewType() == 'xhtml') $location = $this->createLink('bug', 'view', "bugID={$bug->id}", 'html');
  1957. return $this->send(array('result' => 'success', 'message' => $message, 'load' => $location));
  1958. }
  1959. /**
  1960. * 删除 bug 后不同的返回结果。
  1961. * respond after deleting.
  1962. *
  1963. * @param object $bug
  1964. * @param string $from
  1965. * @param string $message
  1966. * @access protected
  1967. * @return array
  1968. */
  1969. protected function responseAfterDelete($bug, $from, $message = '')
  1970. {
  1971. if(!$message) $message = $this->lang->saveSuccess;
  1972. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message));
  1973. /* 如果 bug 转任务,删除 bug 时确认是否更新任务状态。*/
  1974. /* If the bug has been transfered to a task, confirm to update task when delete the bug. */
  1975. if($bug->toTask)
  1976. {
  1977. $task = $this->task->getByID($bug->toTask);
  1978. if(!$task->deleted)
  1979. {
  1980. $confirmedURL = $this->createLink('task', 'view', "taskID={$bug->toTask}");
  1981. $canceledURL = $this->createLink('bug', 'view', "bugID={$bug->id}");
  1982. return $this->send(array('result' => 'success', 'load' => array('confirm' => sprintf($this->lang->bug->notice->remindTask, $bug->toTask), 'confirmed' => $confirmedURL, 'canceled' => $canceledURL)));
  1983. }
  1984. }
  1985. /* 在弹窗中删除 bug 时的返回。*/
  1986. /* Respond when delete bug in modal.。*/
  1987. if(isInModal()) return $this->send(array('result' => 'success', 'load' => true));
  1988. /* 在任务看板中删除 bug 时的返回。*/
  1989. /* Respond when delete in task kanban. */
  1990. if($from == 'taskkanban') return $this->send(array('result' => 'success', 'closeModal' => true, 'callback' => "refreshKanban()"));
  1991. return $this->send(array('result' => 'success', 'message' => $message, 'load' => $this->session->bugList ? $this->session->bugList : inlink('browse', "productID={$bug->product}"), 'closeModal' => true));
  1992. }
  1993. /**
  1994. * 批量创建bug后返回响应。
  1995. * Response after batch create.
  1996. *
  1997. * @param int $productID
  1998. * @param string $branch
  1999. * @param int $executionID
  2000. * @param array $bugIdList
  2001. * @param string $message
  2002. * @access protected
  2003. * @return bool
  2004. */
  2005. protected function responseAfterBatchCreate($productID, $branch, $executionID, $bugIdList, $message = '')
  2006. {
  2007. helper::setcookie('bugModule', '0', 0);
  2008. /* Remove upload image file and session. */
  2009. if(!empty($_POST['uploadImage']) && !empty($_SESSION['bugImagesFile']))
  2010. {
  2011. $classFile = $this->app->loadClass('zfile');
  2012. $file = current($_SESSION['bugImagesFile']);
  2013. $realPath = dirname($file['realpath']);
  2014. if(is_dir($realPath)) $classFile->removeDir($realPath);
  2015. unset($_SESSION['bugImagesFile']);
  2016. }
  2017. if(!$message) $message = $this->lang->saveSuccess;
  2018. /* Return bug id list when call the API. */
  2019. if($this->viewType == 'json') return $this->send(array('result' => 'success', 'message' => $message, 'idList' => $bugIdList));
  2020. /* Respond after updating in modal. */
  2021. if(isInModal() && $executionID) return $this->responseInModal();
  2022. /* If link from no head then reload. */
  2023. if(isInModal()) return $this->send(array('result' => 'success', 'message' => $message, 'closeModal' => true));
  2024. return $this->send(array('result' => 'success', 'message' => $message, 'load' => $this->createLink('bug', 'browse', "productID={$productID}&branch={$branch}&browseType=unclosed&param=0&orderBy=id_desc")));
  2025. }
  2026. /**
  2027. * 批量编辑 bug 后的一些操作。
  2028. * Operate after batch edit bugs.
  2029. *
  2030. * @param object $bug
  2031. * @param object $oldBug
  2032. * @access protected
  2033. * @return void
  2034. */
  2035. protected function operateAfterBatchEdit($bug, $oldBug)
  2036. {
  2037. /* 解决 bug 奖励积分。*/
  2038. /* Record score when bug is resolved. */
  2039. if(isset($bug->status) && $bug->status == 'resolved' && $oldBug->status == 'active') $this->loadModel('score')->create('bug', 'resolve', $bug, $bug->resolvedBy);
  2040. /* 更新相关反馈的状态。*/
  2041. /* Update status of related feedback. */
  2042. if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
  2043. }
  2044. /**
  2045. * 获取待处理的任务和计划列表。
  2046. * Get toTaskIdList, unlinkPlans and link2Plans to be processed.
  2047. *
  2048. * @param object $bug
  2049. * @param object $oldBug
  2050. * @access protected
  2051. * @return array
  2052. */
  2053. protected function getToBeProcessedData($bug, $oldBug)
  2054. {
  2055. static $toTaskIdList = array();
  2056. static $unlinkPlans = array();
  2057. static $link2Plans = array();
  2058. /* 获取转任务的任务列表。*/
  2059. /* Get task list that is transfered by bug. */
  2060. if($oldBug->toTask != 0 && isset($bug->status) && $bug->status != $oldBug->status) $toTaskIdList[$oldBug->toTask] = $oldBug->toTask;
  2061. /* Bug 的所属计划变更后,获取变更的计划列表。*/
  2062. /* Get plan list that has been changed. */
  2063. if($bug->plan != $oldBug->plan)
  2064. {
  2065. if(!empty($oldBug->plan)) $unlinkPlans[$oldBug->plan] = empty($unlinkPlans[$oldBug->plan]) ? $bug->id : "{$unlinkPlans[$oldBug->plan]},{$oldBug->id}";
  2066. if(!empty($bug->plan)) $link2Plans[$bug->plan] = empty($link2Plans[$bug->plan]) ? $bug->id : "{$link2Plans[$bug->plan]},{$oldBug->id}";
  2067. }
  2068. return array($toTaskIdList, $unlinkPlans, $link2Plans);
  2069. }
  2070. /**
  2071. * 获得 batchEdit 方法的 response。
  2072. * Get response for batchEdit.
  2073. *
  2074. * @param array $output
  2075. * @param string $message
  2076. * @access protected
  2077. * @return bool
  2078. * @param mixed[] $toTaskIdList
  2079. */
  2080. protected function responseAfterBatchEdit($toTaskIdList, $message = '')
  2081. {
  2082. if(!empty($toTaskIdList))
  2083. {
  2084. $taskID = key($toTaskIdList);
  2085. $confirmedURL = $this->createLink('task', 'view', 'taskID=' . $taskID);
  2086. $canceledURL = $this->server->HTTP_REFERER;
  2087. return $this->send(array('result' => 'success', 'message' => $message, 'load' => array('confirm' => sprintf($this->lang->bug->notice->remindTask, $taskID), 'confirmed' => $confirmedURL, 'canceled' => $canceledURL)));
  2088. }
  2089. return $this->send(array('result' => 'success', 'message' => $message, 'load' => $this->session->bugList));
  2090. }
  2091. /**
  2092. * 初始化一个默认的bug模板。
  2093. * Init a default bug templete.
  2094. *
  2095. * @param array $fields
  2096. * @access protected
  2097. * @return object
  2098. */
  2099. protected function initBug($fields)
  2100. {
  2101. $bug = new stdclass();
  2102. $bug->projectID = 0;
  2103. $bug->moduleID = 0;
  2104. $bug->executionID = 0;
  2105. $bug->productID = 0;
  2106. $bug->taskID = 0;
  2107. $bug->storyID = 0;
  2108. $bug->buildID = 0;
  2109. $bug->caseID = 0;
  2110. $bug->runID = 0;
  2111. $bug->testtask = 0;
  2112. $bug->version = 0;
  2113. $bug->title = '';
  2114. $bug->steps = $this->lang->bug->tplStep . $this->lang->bug->tplResult . $this->lang->bug->tplExpect;
  2115. $bug->os = '';
  2116. $bug->browser = '';
  2117. $bug->assignedTo = '';
  2118. $bug->deadline = '';
  2119. $bug->mailto = '';
  2120. $bug->keywords = '';
  2121. $bug->severity = 3;
  2122. $bug->type = 'codeerror';
  2123. $bug->pri = 3;
  2124. $bug->color = '';
  2125. $bug->feedbackBy = '';
  2126. $bug->notifyEmail = '';
  2127. $bug->project = '';
  2128. $bug->branch = '';
  2129. $bug->execution = '';
  2130. $bug->projectModel = '';
  2131. $bug->projects = array();
  2132. $bug->executions = array();
  2133. $bug->products = array();
  2134. $bug->stories = array();
  2135. $bug->builds = array();
  2136. $bug->branches = array();
  2137. if(!empty($fields)) $bug = $this->updateBug($bug, $fields);
  2138. return $bug;
  2139. }
  2140. /**
  2141. * 解析extras,如果bug来源于某个对象 (bug, case, testtask, todo) ,使用对象的一些属性对bug赋值。
  2142. * Extract extras, if bug come from an object(bug, case, testtask, todo), get some value from object.
  2143. *
  2144. * @param object $bug
  2145. * @param array $output
  2146. * @access protected
  2147. * @return object
  2148. */
  2149. protected function extractObjectFromExtras($bug, $output)
  2150. {
  2151. extract($output);
  2152. if(isset($resultID)) $resultID = (int)$resultID;
  2153. if(isset($caseID)) $caseID = (int)$caseID;
  2154. /* 获取用例的标题、步骤、所属需求、所属模块、版本、所属执行。 */
  2155. /* Get title, steps, storyID, moduleID, version, executionID from case. */
  2156. if(isset($runID) && $runID && isset($resultID) && $resultID) $fields = $this->bug->getBugInfoFromResult($resultID, isset($caseID) ? $caseID : 0, isset($stepIdList) ? $stepIdList : '');// If set runID and resultID, get the result info by resultID as template.
  2157. if(isset($runID) && !$runID && isset($caseID) && $caseID) $fields = $this->bug->getBugInfoFromResult($resultID, $caseID, isset($stepIdList) ? $stepIdList : '');// If not set runID but set caseID, get the result info by resultID and case info.
  2158. if(isset($fields)) $bug = $this->updateBug($bug, $fields);
  2159. /* 获得bug的所属项目、所属模块、所属执行、关联产品、关联任务、关联需求、关联版本、关联用例、标题、步骤、严重程度、类型、指派给、截止日期、操作系统、浏览器、抄送给、关键词、颜色、所属测试单、反馈人、通知邮箱、优先级。 */
  2160. /* Get projectID, moduleID, executionID, productID, taskID, storyID, buildID, caseID, title, steps, severity, type, assignedTo, deadline, os, browser, mailto, keywords, color, testtask, feedbackBy, notifyEmail, pri from case. */
  2161. if(isset($bugID) && $bugID)
  2162. {
  2163. $bugInfo = $this->bug->getById((int)$bugID);
  2164. $isSameProduct = $this->session->product == $bugInfo->product;
  2165. $fields = array('projectID' => $bugInfo->project, 'moduleID' => $bugInfo->module, 'executionID' => $bugInfo->execution, 'taskID' => $bugInfo->task, 'storyID' => $isSameProduct ? $bugInfo->story : 0, 'buildID' => $bugInfo->openedBuild,
  2166. 'caseID' => $bugInfo->case, 'title' => $bugInfo->title, 'steps' => $bugInfo->steps, 'severity' => $bugInfo->severity, 'type' => $bugInfo->type, 'assignedTo' => $bugInfo->assignedTo, 'deadline' => (helper::isZeroDate($bugInfo->deadline) ? '' : $bugInfo->deadline),
  2167. 'os' => $bugInfo->os, 'browser' => $bugInfo->browser, 'mailto' => $bugInfo->mailto, 'keywords' => $bugInfo->keywords, 'color' => $bugInfo->color, 'testtask' => $bugInfo->testtask, 'feedbackBy' => $bugInfo->feedbackBy, 'notifyEmail' => $bugInfo->notifyEmail,
  2168. 'pri' => ($bugInfo->pri == 0 ? 3 : $bugInfo->pri),
  2169. 'plan' => $bugInfo->plan,
  2170. 'injection' => $bugInfo->injection,
  2171. 'identify' => $bugInfo->identify
  2172. );
  2173. $bug = $this->updateBug($bug, $fields);
  2174. $bug->files = $bugInfo->files;
  2175. foreach($bug->files as $file)
  2176. {
  2177. $file->name = $file->title;
  2178. $file->url = $this->createLink('file', 'download', "fileID={$file->id}");
  2179. }
  2180. if($this->config->edition != 'open')
  2181. {
  2182. $fields = array();
  2183. $extendFields = $this->loadModel('flow')->getExtendFields('bug', 'create');
  2184. foreach($extendFields as $field) $fields[$field->field] = $bugInfo->{$field->field};
  2185. $bug = $this->updateBug($bug, $fields);
  2186. }
  2187. }
  2188. /* 获取测试单的版本。 */
  2189. /* Get buildID from testtask. */
  2190. if(isset($testtask) and $testtask)
  2191. {
  2192. $testtask = $this->loadModel('testtask')->getByID((int)$testtask);
  2193. $bug = $this->updateBug($bug, array('buildID' => $testtask->build));
  2194. }
  2195. /* 获得代办的标题、步骤和优先级。 */
  2196. /* Get title, steps, pri from todo. */
  2197. if(isset($todoID) and $todoID)
  2198. {
  2199. $todo = $this->loadModel('todo')->getById((int)$todoID);
  2200. $bug = $this->updateBug($bug, array('title' => $todo->name, 'steps' => $todo->desc, 'pri' => $todo->pri));
  2201. }
  2202. return $bug;
  2203. }
  2204. /**
  2205. * 将报表的默认设置合并到当前报表。
  2206. * Merge the default chart settings and the settings of current chart.
  2207. *
  2208. * @param string $chartCode
  2209. * @param string $chartType
  2210. * @access protected
  2211. * @return object
  2212. */
  2213. protected function mergeChartOption($chartCode, $chartType = 'default')
  2214. {
  2215. $chartOption = $this->lang->bug->report->$chartCode;
  2216. $commonOption = $this->lang->bug->report->options;
  2217. $chartOption->graph->caption = $this->lang->bug->report->charts[$chartCode];
  2218. if(!empty($chartType) && $chartType != 'default') $chartOption->type = $chartType;
  2219. if(!isset($chartOption->type)) $chartOption->type = $commonOption->type;
  2220. if(!isset($chartOption->width)) $chartOption->width = $commonOption->width;
  2221. if(!isset($chartOption->height)) $chartOption->height = $commonOption->height;
  2222. foreach($commonOption->graph as $key => $value) if(!isset($chartOption->graph->$key)) $chartOption->graph->$key = $value;
  2223. return $chartOption;
  2224. }
  2225. /**
  2226. * 处理代码问题页面的操作。
  2227. * Process the code issue actions.
  2228. *
  2229. * @param int $repoID
  2230. * @access protected
  2231. * @return void
  2232. */
  2233. protected function processRepoIssueActions($repoID)
  2234. {
  2235. $this->view->repoID = $repoID;
  2236. $this->config->bug->actions->view['mainActions'] = array('confirm', 'assignTo', 'resolve', 'close', 'activate');
  2237. $this->config->bug->actions->view['suffixActions'] = array('delete');
  2238. }
  2239. }