model.php 93 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274
  1. <?php
  2. /**
  3. * The model file of bug module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package bug
  9. * @version $Id: model.php 5079 2013-07-10 00:44:34Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class bugModel extends model
  15. {
  16. /**
  17. * bug 的入库操作。
  18. * Insert bug into zt_bug.
  19. *
  20. * @param object $bug
  21. * @param string $from
  22. * @access public
  23. * @return int|false
  24. */
  25. public function create($bug, $from = '')
  26. {
  27. $this->dao->insert(TABLE_BUG)->data($bug, 'laneID,uploadImage,imageFile')
  28. ->autoCheck()
  29. ->checkIF(!empty($bug->notifyEmail), 'notifyEmail', 'email')
  30. ->batchCheck($this->config->bug->create->requiredFields, 'notempty')
  31. ->checkFlow()
  32. ->exec();
  33. if(dao::isError()) return false;
  34. $bugID = $this->dao->lastInsertID();
  35. $action = $from == 'sonarqube' ? 'fromSonarqube' : 'Opened';
  36. $this->loadModel('action')->create('bug', $bugID, $action);
  37. if(!empty($bug->assignedTo)) $this->action->create('bug', $bugID, 'Assigned', '', $bug->assignedTo);
  38. /* Add score for create. */
  39. $this->loadModel('file')->saveUpload('bug', $bugID);
  40. if(!empty($bug->case))
  41. {
  42. $this->loadModel('score')->create('bug', 'createFormCase', $bug->case);
  43. }
  44. else
  45. {
  46. $this->loadModel('score')->create('bug', 'create', $bugID);
  47. }
  48. return $bugID;
  49. }
  50. /**
  51. * Gitlab 问题转为 bug。
  52. * Create bug from gitlab issue.
  53. *
  54. * @param object $bug
  55. * @param int $executionID
  56. * @access public
  57. * @return int|bool
  58. */
  59. public function createBugFromGitlabIssue($bug, $executionID)
  60. {
  61. $bug->openedBy = $this->app->user->account;
  62. $bug->openedDate = helper::now();
  63. $bug->assignedDate = isset($bug->assignedTo) ? helper::now() : null;
  64. $bug->openedBuild = 'trunk';
  65. $bug->story = 0;
  66. $bug->task = 0;
  67. $bug->pri = 3;
  68. $bug->severity = 3;
  69. $bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
  70. $this->dao->insert(TABLE_BUG)->data($bug, $skip = 'gitlab,gitlabProject')->autoCheck()->batchCheck($this->config->bug->create->requiredFields, 'notempty')->exec();
  71. if(!dao::isError()) return $this->dao->lastInsertID();
  72. return false;
  73. }
  74. /**
  75. * 根据浏览类型获取 bug 列表。
  76. * Get bug list by browse type.
  77. *
  78. * @param string $browseType
  79. * @param array $productIdList
  80. * @param int $projectID
  81. * @param int[] $executionIdList
  82. * @param int|string $branch
  83. * @param int $moduleID
  84. * @param int $queryID
  85. * @param string $orderBy
  86. * @param object $pager
  87. * @access public
  88. * @return array
  89. */
  90. public function getList($browseType, $productIdList, $projectID, $executionIdList, $branch = 'all', $moduleID = 0, $queryID = 0, $orderBy = 'id_desc', $pager = null)
  91. {
  92. if($browseType == 'bymodule' && $this->session->bugBrowseType && $this->session->bugBrowseType != 'bysearch') $browseType = $this->session->bugBrowseType;
  93. if(!in_array($browseType, $this->config->bug->browseTypeList)) return array();
  94. /* 处理排序。*/
  95. /* Process sort field. */
  96. if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
  97. if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
  98. $modules = $moduleID ? $this->loadModel('tree')->getAllChildID($moduleID) : array();
  99. $bugList = $this->bugTao->getListByBrowseType($browseType, $productIdList, $projectID, $executionIdList, $branch, $modules, $queryID, $orderBy, $pager);
  100. return $this->bugTao->batchAppendDelayedDays($bugList);
  101. }
  102. /**
  103. * 获取计划关联的 bugs。
  104. * Get bug list of a plan.
  105. *
  106. * @param int $planID
  107. * @param string $status
  108. * @param string $orderBy
  109. * @param object $pager
  110. * @access public
  111. * @return array
  112. */
  113. public function getPlanBugs($planID, $status = 'all', $orderBy = 'id_desc', $pager = null)
  114. {
  115. if(common::isTutorialMode()) return array();
  116. if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
  117. $bugs = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) AS priOrder")->from(TABLE_BUG)
  118. ->where('plan')->eq($planID)
  119. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in('0,' . $this->app->user->view->sprints)->fi()
  120. ->beginIF($status != 'all')->andWhere('status')->in($status)->fi()
  121. ->andWhere('deleted')->eq(0)
  122. ->orderBy($orderBy)->page($pager)
  123. ->fetchAll('id', false);
  124. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug');
  125. return $bugs;
  126. }
  127. /**
  128. * 获取 bug。
  129. * Get info of a bug.
  130. *
  131. * @param int $bugID
  132. * @param bool $setImgSize
  133. * @access public
  134. * @return object|false
  135. */
  136. public function getByID($bugID, $setImgSize = false)
  137. {
  138. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBug();
  139. $bug = $this->bugTao->fetchBugInfo($bugID);
  140. if(!$bug) return false;
  141. $bug = $this->loadModel('file')->replaceImgURL($bug, 'steps');
  142. if($setImgSize) $bug->steps = $this->file->setImgSize($bug->steps);
  143. if($bug->project) $bug->projectName = $this->bugTao->getNameFromTable($bug->project, TABLE_PROJECT, 'name');
  144. if($bug->duplicateBug) $bug->duplicateBugTitle = $this->bugTao->getNameFromTable($bug->duplicateBug, TABLE_BUG, 'title');
  145. if($bug->case) $bug->caseTitle = $this->bugTao->getNameFromTable($bug->case, TABLE_CASE, 'title');
  146. if($bug->toStory) $bug->toStoryTitle = $this->bugTao->getNameFromTable($bug->toStory, TABLE_STORY, 'title');
  147. if($bug->toTask) $bug->toTaskTitle = $this->bugTao->getNameFromTable($bug->toTask, TABLE_TASK, 'name');
  148. if($bug->relatedBug) $bug->relatedBugTitles = $this->bugTao->getBugPairsByList($bug->relatedBug);
  149. if(in_array($this->config->edition, array('max', 'ipd')))
  150. {
  151. $identifyList = $this->loadModel('review')->getPairs($bug->project, $bug->product, true);
  152. $bug->injectionTitle = is_numeric($bug->injection) ? zget($identifyList, $bug->injection, '') : zget($this->lang->bug->injectionList, $bug->injection, '');
  153. $bug->identifyTitle = is_numeric($bug->identify) ? zget($identifyList, $bug->identify, '') : zget($this->lang->bug->identifyList, $bug->identify, '');
  154. }
  155. $bug->linkMRTitles = $this->loadModel('mr')->getLinkedMRPairs($bugID, 'bug');
  156. $bug->toCases = $this->bugTao->getCasesFromBug($bugID);
  157. $bug->files = $this->file->getByObject('bug', $bugID);
  158. return $this->bugTao->appendDelayedDays($bug);
  159. }
  160. /**
  161. * 获取指定字段的 bug 列表。
  162. * Get bugs by ID list.
  163. *
  164. * @param int|array|string $bugIdList
  165. * @param string $fields
  166. * @param string $orderBy
  167. * @access public
  168. * @return array
  169. */
  170. public function getByIdList($bugIdList = 0, $fields = '*', $orderBy = '')
  171. {
  172. return $this->dao->select($fields)->from(TABLE_BUG)
  173. ->where('deleted')->eq('0')
  174. ->beginIF($bugIdList)->andWhere('id')->in($bugIdList)->fi()
  175. ->beginIF($orderBy)->orderBy($orderBy)->fi()
  176. ->fetchAll('id');
  177. }
  178. /**
  179. * 获取激活的未转为 bug 和任务的 bugs。
  180. * Get active bugs.
  181. *
  182. * @param array|int $products
  183. * @param int|string $branch
  184. * @param string $executions
  185. * @param array $excludeBugs
  186. * @param object $pager
  187. * @param string $orderBy
  188. * @access public
  189. * @return array
  190. */
  191. public function getActiveBugs($products, $branch, $executions, $excludeBugs, $pager = null, $orderBy = 'id desc')
  192. {
  193. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
  194. return $this->dao->select('*')->from(TABLE_BUG)
  195. ->where('status')->eq('active')
  196. ->andWhere('toStory')->eq(0)
  197. ->andWhere('toTask')->eq(0)
  198. ->beginIF(!empty($products))->andWhere('product')->in($products)->fi()
  199. ->beginIF($branch !== '' and $branch !== 'all')->andWhere('branch')->in("0,$branch")->fi()
  200. ->beginIF(!empty($executions))->andWhere('execution')->in($executions)->fi()
  201. ->beginIF($excludeBugs)->andWhere('id')->notIN($excludeBugs)->fi()
  202. ->andWhere('deleted')->eq(0)
  203. ->orderBy($orderBy)
  204. ->page($pager)
  205. ->fetchAll('id', false);
  206. }
  207. /**
  208. * 获取激活和延期处理的 bug 列表。
  209. * Get active and postponed bugs.
  210. *
  211. * @param array $products
  212. * @param int $executionID
  213. * @param object $pager
  214. * @access public
  215. * @return array
  216. */
  217. public function getActiveAndPostponedBugs($products, $executionID, $pager = null)
  218. {
  219. return $this->dao->select('t1.*')->from(TABLE_BUG)->alias('t1')
  220. ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.product = t2.product')
  221. ->where("((t1.status = 'resolved' AND t1.resolution = 'postponed') OR (t1.status = 'active'))")
  222. ->andWhere('t1.toTask')->eq(0)
  223. ->andWhere('t1.toStory')->eq(0)
  224. ->beginIF(!empty($products))->andWhere('t1.product')->in($products)->fi()
  225. ->beginIF(empty($products))->andWhere('t1.execution')->eq($executionID)->fi()
  226. ->andWhere('t2.project')->eq($executionID)
  227. ->andWhere("(t2.branch = '0' OR t1.branch = '0' OR t2.branch = t1.branch)")
  228. ->andWhere('t1.deleted')->eq('0')
  229. ->orderBy('id desc')
  230. ->page($pager)
  231. ->fetchAll('id', false);
  232. }
  233. /**
  234. * 获取模块的负责人。
  235. * Get the owner of module.
  236. *
  237. * @param int $moduleID
  238. * @param int $productID
  239. * @access public
  240. * @return array
  241. */
  242. public function getModuleOwner($moduleID, $productID)
  243. {
  244. $users = $this->loadModel('user')->getPairs('nodeleted');
  245. /* 获取所属产品的测试负责人。*/
  246. /* Return the QD of the product. */
  247. $account = $this->dao->findByID($productID)->from(TABLE_PRODUCT)->fetch('QD');
  248. $account = isset($users[$account]) ? $account : '';
  249. $realname = zget($users, $account, '');
  250. /* 如果没有模块 ID,直接返回测试负责人。*/
  251. if(!$moduleID) return array($account, $realname);
  252. /* 获取模块,如果模块为空,直接返回测试负责人。*/
  253. $module = $this->dao->findByID($moduleID)->from(TABLE_MODULE)->andWhere('root')->eq($productID)->fetch();
  254. if(empty($module)) return array($account, $realname);
  255. /* 如果模块有负责人返回模块负责人。*/
  256. if($module->owner && isset($users[$module->owner])) return array($module->owner, $users[$module->owner]);
  257. /* 获取除了模块ID以外的模块的路径,如果没有其他路径,返回测试负责人。*/
  258. $moduleIdList = explode(',', trim(str_replace(",$module->id,", ',', $module->path), ','));
  259. if(!$moduleIdList) return array($account, $realname);
  260. /* 从上级到下级,如果有模块有负责人,返回模块负责人。*/
  261. krsort($moduleIdList);
  262. $modules = $this->dao->select('id,owner')->from(TABLE_MODULE)->where('id')->in($moduleIdList)->andWhere('deleted')->eq('0')->fetchAll('id');
  263. foreach($modules as $module)
  264. {
  265. if($module->owner && isset($users[$module->owner])) return array($module->owner, $users[$module->owner]);
  266. }
  267. return array($account, $realname);
  268. }
  269. /**
  270. * 更新 bug 信息。
  271. * Update a bug.
  272. *
  273. * @param object $bug
  274. * @param string $action
  275. * @access public
  276. * @return array|false
  277. */
  278. public function update($bug, $action = 'Edited')
  279. {
  280. $oldBug = $this->fetchByID($bug->id);
  281. $oldBug->files = $this->loadModel('file')->getByObject('bug', $bug->id);
  282. $this->dao->update(TABLE_BUG)->data($bug, 'deleteFiles,renameFiles,files,comment')
  283. ->autoCheck()
  284. ->batchCheck($this->config->bug->edit->requiredFields, 'notempty')
  285. ->checkIF(!empty($bug->resolvedBy), 'resolution', 'notempty')
  286. ->checkIF(!empty($bug->closedBy), 'resolution', 'notempty')
  287. ->checkIF(!empty($bug->notifyEmail),'notifyEmail', 'email')
  288. ->checkIF(!empty($bug->resolution) && $bug->resolution == 'duplicate', 'duplicateBug', 'notempty')
  289. ->checkIF(!empty($bug->resolution) && $bug->resolution == 'fixed', 'resolvedBuild','notempty')
  290. ->checkFlow()
  291. ->where('id')->eq($bug->id)
  292. ->exec();
  293. if(dao::isError()) return false;
  294. /* 更新 bug 的附件。*/
  295. /* Update the files of bug. */
  296. $this->file->processFileDiffsForObject('bug', $oldBug, $bug);
  297. $changes = common::createChanges($oldBug, $bug);
  298. if($changes || !empty($bug->comment))
  299. {
  300. $actionID = $this->loadModel('action')->create('bug', $bug->id, $changes ? $action : 'Commented', zget($bug, 'comment', ''));
  301. if($changes) $this->action->logHistory($actionID, $changes);
  302. }
  303. if($this->config->edition != 'open' && $this->app->rawMethod != 'batchedit')
  304. {
  305. if($oldBug->story > 0 || $oldBug->task > 0 || $oldBug->case > 0)
  306. {
  307. $AID = $oldBug->story > 0 ? ($oldBug->task > 0 ? "{$oldBug->story},{$oldBug->task}" : $oldBug->story) : $oldBug->task;
  308. $AID .= $oldBug->case > 0 ? ",{$oldBug->case}" : '';
  309. $AType = $oldBug->story > 0 ? ($oldBug->task > 0 ? 'story,task' : 'story') : 'task';
  310. $AType .= $oldBug->case > 0 ? ",testcase" : '';
  311. $this->dao->delete()->from(TABLE_RELATION)
  312. ->where('relation')->eq('generated')
  313. ->andWhere('AID')->in($AID)
  314. ->andWhere('AType')->in($AType)
  315. ->andWhere('BID')->eq($oldBug->id)
  316. ->andWhere('BType')->eq('bug')
  317. ->exec();
  318. }
  319. if(zget($bug, 'story', 0) > 0 || zget($bug, 'task', 0) > 0 || zget($bug, 'case', 0) > 0)
  320. {
  321. $relation = new stdClass();
  322. $relation->relation = 'generated';
  323. $relation->BID = $bug->id;
  324. $relation->BType = 'bug';
  325. $relation->product = 0;
  326. if(!empty($bug->story))
  327. {
  328. $relation->AID = $bug->story;
  329. $relation->AType = 'story';
  330. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  331. }
  332. if(!empty($bug->task))
  333. {
  334. $relation->AID = $bug->task;
  335. $relation->AType = 'task';
  336. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  337. }
  338. if(!empty($bug->case))
  339. {
  340. $relation->AID = $bug->case;
  341. $relation->AType = 'testcase';
  342. $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
  343. }
  344. }
  345. }
  346. if(dao::isError()) return false;
  347. return $changes;
  348. }
  349. /**
  350. * 将 bug 指派给一个用户。
  351. * Assign a bug to a user.
  352. *
  353. * @param object $bug
  354. * @param object $oldBug
  355. * @access public
  356. * @return bool
  357. */
  358. public function assign($bug, $oldBug)
  359. {
  360. $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->andWhere('status')->ne('closed')->exec();
  361. if(dao::isError()) return false;
  362. /* 记录指派动作。*/
  363. /* Record log. */
  364. $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Assigned', $bug->comment, $bug->assignedTo);
  365. $changes = common::createChanges($oldBug, $bug);
  366. if($changes) $this->action->logHistory($actionID, $changes);
  367. return !dao::isError();
  368. }
  369. /**
  370. * 确认 bug。
  371. * Confirm a bug.
  372. *
  373. * @param object $bug
  374. * @param array $kanbanData
  375. * @access public
  376. * @return bool
  377. */
  378. public function confirm($bug, $kanbanData = array())
  379. {
  380. $oldBug = $this->getByID($bug->id);
  381. $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
  382. if(dao::isError()) return false;
  383. /* 确认 bug 后的积分变动。*/
  384. /* Record score after confirming bug. */
  385. $this->loadModel('score')->create('bug', 'confirm', $oldBug);
  386. /* 如果 bug 有所属执行,更新看板数据。*/
  387. /* Update kanban if the bug has execution. */
  388. if($oldBug->execution)
  389. {
  390. $this->loadModel('kanban');
  391. if(!isset($kanbanData['toColID'])) $this->kanban->updateLane($oldBug->execution, 'bug', $oldBug->id);
  392. if(isset($kanbanData['toColID'])) $this->kanban->moveCard((int)$oldBug->id, (int)$kanbanData['fromColID'], (int)$kanbanData['toColID'], (int)$kanbanData['fromLaneID'], (int)$kanbanData['toLaneID'], (int)$oldBug->execution);
  393. }
  394. /* 记录历史记录。*/
  395. /* Record history. */
  396. $changes = common::createChanges($oldBug, $bug);
  397. $actionID = $this->loadModel('action')->create('bug', $oldBug->id, 'bugConfirmed', $bug->comment);
  398. if($changes) $this->action->logHistory($actionID, $changes);
  399. return !dao::isError();
  400. }
  401. /**
  402. * 解决一个bug。
  403. * Resolve a bug.
  404. *
  405. * @param object $bug
  406. * @param array $output
  407. * @access public
  408. * @return bool
  409. */
  410. public function resolve($bug, $output = array())
  411. {
  412. /* Get old bug. */
  413. $oldBug = $this->getById($bug->id);
  414. if(!empty($bug->duplicateBug))
  415. {
  416. $duplicateBug = $this->fetchByID($bug->duplicateBug);
  417. if(!$duplicateBug || $duplicateBug->deleted == '1')
  418. {
  419. dao::$errors['duplicateBug'][] = $this->lang->bug->error->duplicateBugNotExist;
  420. return false;
  421. }
  422. }
  423. /* Update bug. */
  424. $this->dao->update(TABLE_BUG)->data($bug, 'buildName,createBuild,buildExecution,comment')
  425. ->autoCheck()
  426. ->batchCheck($this->config->bug->resolve->requiredFields, 'notempty')
  427. ->checkIF($bug->resolution == 'duplicate', 'duplicateBug', 'notempty')
  428. ->checkIF($bug->resolution == 'fixed', 'resolvedBuild','notempty')
  429. ->checkFlow()
  430. ->where('id')->eq($bug->id)
  431. ->exec();
  432. if(dao::isError()) return false;
  433. /* Add score. */
  434. $this->loadModel('score')->create('bug', 'resolve', $oldBug);
  435. /* Move bug card in kanban. */
  436. if($oldBug->execution)
  437. {
  438. if(!isset($output['toColID'])) $this->loadModel('kanban')->updateLane($oldBug->execution, 'bug', $bug->id);
  439. if(isset($output['toColID'])) $this->loadModel('kanban')->moveCard((int)$bug->id, (int)$output['fromColID'], (int)$output['toColID'], (int)$output['fromLaneID'], (int)$output['toLaneID']);
  440. }
  441. /* Link bug to build and release. */
  442. $this->linkBugToBuild($bug->id, $bug->resolvedBuild);
  443. /* Save files and record log. */
  444. $files = $this->loadModel('file')->saveUpload('bug', $bug->id);
  445. $fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
  446. $changes = common::createChanges($oldBug, $bug);
  447. $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Resolved', $fileAction . (!empty($bug->comment) ? $this->post->comment : ''), $bug->resolution . (isset($bug->duplicateBug) ? ':' . $bug->duplicateBug : ''));
  448. if($changes) $this->action->logHistory($actionID, $changes);
  449. /* If the edition is not pms, update feedback. */
  450. if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
  451. return !dao::isError();
  452. }
  453. /**
  454. * 在解决bug的时候创建一个版本。
  455. * Create build when resolving a bug.
  456. *
  457. * @param object $bug
  458. * @param object $oldBug
  459. * @access public
  460. * @return bool
  461. */
  462. public function createBuild($bug, $oldBug)
  463. {
  464. /* Construct build data. */
  465. $buildData = new stdclass();
  466. $buildData->product = (int)$oldBug->product;
  467. $buildData->branch = (int)$oldBug->branch;
  468. $buildData->project = $bug->buildExecution ? $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($bug->buildExecution)->fetch('project') : 0;
  469. $buildData->execution = $bug->buildExecution;
  470. $buildData->name = $bug->buildName;
  471. $buildData->date = date('Y-m-d');
  472. $buildData->builder = $this->app->user->account;
  473. $buildData->createdBy = $this->app->user->account;
  474. $buildData->createdDate = helper::now();
  475. /* Create a build. */
  476. $this->lang->build->name = $this->lang->bug->placeholder->newBuildName;
  477. $this->dao->insert(TABLE_BUILD)->data($buildData)->autoCheck()
  478. ->check('name', 'unique', "product = {$buildData->product} AND branch = {$buildData->branch} AND deleted = '0'")
  479. ->batchCheck('name,execution', 'notempty')
  480. ->exec();
  481. if(dao::isError())
  482. {
  483. $error = dao::getError();
  484. if(isset($error['name']))
  485. {
  486. $error['buildName'] = $error['name'];
  487. unset($error['name']);
  488. }
  489. if(isset($error['execution']))
  490. {
  491. $executionLang = $this->lang->bug->execution;
  492. if($oldBug->execution)
  493. {
  494. $execution = $this->dao->findByID($oldBug->execution)->from(TABLE_EXECUTION)->fetch();
  495. if($execution and $execution->type == 'kanban') $executionLang = $this->lang->bug->kanban;
  496. }
  497. $error['buildExecution'] = sprintf($this->lang->error->notempty, $executionLang);
  498. unset($error['execution']);
  499. }
  500. dao::$errors = $error;
  501. return false;
  502. }
  503. /* Get build id, and record log. */
  504. $buildID = $this->dao->lastInsertID();
  505. $this->loadModel('action')->create('build', $buildID, 'opened');
  506. $bug->resolvedBuild = $buildID;
  507. return !dao::isError();
  508. }
  509. /**
  510. * 激活一个bug。
  511. * Activate a bug.
  512. *
  513. * @param object $bug
  514. * @param array $kanbanParams
  515. * @access public
  516. * @return bool
  517. */
  518. public function activate($bug, $kanbanParams = array())
  519. {
  520. $oldBug = parent::fetchByID($bug->id);
  521. $this->dao->update(TABLE_BUG)->data($bug, 'comment')->check('openedBuild', 'notempty')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
  522. if(dao::isError()) return false;
  523. /* Update build. */
  524. $solveBuild = $this->dao->select('id, bugs')->from(TABLE_BUILD)->where("FIND_IN_SET('{$bug->id}', bugs)")->limit(1)->fetch();
  525. if($solveBuild)
  526. {
  527. $buildBugs = trim(str_replace(",{$bug->id},", ',', ",$solveBuild->bugs,"), ',');
  528. $this->dao->update(TABLE_BUILD)->set('bugs')->eq($buildBugs)->where('id')->eq($solveBuild->id)->exec();
  529. }
  530. /* Update kanban. */
  531. if($oldBug->execution)
  532. {
  533. $this->loadModel('kanban');
  534. if(!isset($kanbanParams['toColID'])) $this->kanban->updateLane($oldBug->execution, 'bug', $bug->id);
  535. if(isset($kanbanParams['toColID'])) $this->kanban->moveCard($bug->id, $kanbanParams['fromColID'], $kanbanParams['toColID'], $kanbanParams['fromLaneID'], $kanbanParams['toLaneID']);
  536. }
  537. $changes = common::createChanges($oldBug, $bug);
  538. $files = $this->loadModel('file')->saveUpload('bug', $bug->id);
  539. if($changes || $files)
  540. {
  541. $fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
  542. $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Activated', $fileAction . $bug->comment);
  543. $this->action->logHistory($actionID, $changes);
  544. }
  545. if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
  546. return !dao::isError();
  547. }
  548. /**
  549. * 关闭一个bug。
  550. * Close a bug.
  551. *
  552. * @param object $bug
  553. * @param array $output
  554. * @access public
  555. * @return bool
  556. */
  557. public function close($bug, $output = array())
  558. {
  559. $oldBug = $this->getById($bug->id);
  560. $bug = $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->close['id'], $this->post->uid);
  561. $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
  562. if(dao::isError()) return false;
  563. if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
  564. $changes = common::createChanges($oldBug, $bug);
  565. $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Closed', $this->post->comment);
  566. if($changes) $this->action->logHistory($actionID, $changes);
  567. if($oldBug->execution)
  568. {
  569. $this->loadModel('kanban');
  570. if(!isset($output['toColID'])) $this->kanban->updateLane($oldBug->execution, 'bug', $bug->id);
  571. if(isset($output['toColID'])) $this->kanban->moveCard($bug->id, $output['fromColID'], $output['toColID'], $output['fromLaneID'], $output['toLaneID']);
  572. }
  573. /* 给原bug的抄送人发送完消息后,再处理它。 */
  574. /* After sending a message to the cc of the original bug, then process with it. */
  575. $this->dao->update(TABLE_BUG)->set('assignedTo')->eq('closed')->where('id')->eq($bug->id)->exec();
  576. return !dao::isError();
  577. }
  578. /**
  579. * 获取可以关联的 bug 列表。
  580. * Get bugs to link.
  581. *
  582. * @param int $bugID
  583. * @param bool $bySearch
  584. * @param int $queryID
  585. * @param string $excludeBugs
  586. * @param object $pager
  587. * @access public
  588. * @return array
  589. */
  590. public function getBugs2Link($bugID, $bySearch = false, $excludeBugs = '', $queryID = 0, $pager = null)
  591. {
  592. $bug = $this->getByID($bugID);
  593. $excludeBugs .= ",{$bug->id}";
  594. if($bySearch) return $this->getBySearch('bug', (array)$bug->product, 'all', 0, 0, $queryID, $excludeBugs, 'id desc', $pager);
  595. return $this->dao->select('*')->from(TABLE_BUG)
  596. ->where('deleted')->eq('0')
  597. ->andWhere('id')->notin($excludeBugs)
  598. ->andWhere('product')->eq($bug->product)
  599. ->beginIF($bug->project)->andWhere('project')->eq($bug->project)->fi()
  600. ->beginIF($bug->execution)->andWhere('execution')->eq($bug->execution)->fi()
  601. ->orderBy('id desc')
  602. ->page($pager)
  603. ->fetchAll('id', false);
  604. }
  605. /**
  606. * Get statistic.
  607. *
  608. * @param int $productID
  609. * @param string $endDate
  610. * @param int $days
  611. * @access public
  612. * @return void
  613. */
  614. public function getStatistic($productID = 0, $endDate = '', $days = 30)
  615. {
  616. $startDate = '';
  617. if(empty($endDate)) $endDate = date('Y-m-d');
  618. $dateArr = array();
  619. for($day = $days - 1; $day >= 0; $day--)
  620. {
  621. $time = strtotime(-$day . ' day', strtotime($endDate));
  622. $date = date('m/d', $time);
  623. $dateArr[$date] = new stdClass();
  624. $dateArr[$date]->num = 0;
  625. $dateArr[$date]->date = $date;
  626. if($day == $days -1) $startDate = date('Y-m-d', $time) . ' 00:00:00';
  627. }
  628. $dateFields = array('openedDate', 'resolvedDate', 'closedDate');
  629. $staticData = array();
  630. foreach($dateFields as $field)
  631. {
  632. $bugCount = $this->dao->select("COUNT(id) AS num, DATE_FORMAT($field, '%m/%d') AS date")->from(TABLE_BUG)
  633. ->where('product')->eq($productID)
  634. ->andWhere('deleted')->eq(0)
  635. ->andWhere($field)->between($startDate, $endDate . ' 23:50:59')
  636. ->groupBy('date')
  637. ->fetchAll('date');
  638. $staticData[$field] = array_merge($dateArr, $bugCount);
  639. }
  640. return $staticData;
  641. }
  642. /**
  643. * 构造搜索表单。
  644. * Build search form.
  645. *
  646. * @param int $productID
  647. * @param array $products
  648. * @param int $queryID
  649. * @param string $actionURL
  650. * @param string $branch
  651. * @access public
  652. * @return void
  653. */
  654. public function buildSearchForm($productID, $products, $queryID, $actionURL, $branch = '0')
  655. {
  656. if(commonModel::isTutorialMode()) return;
  657. if(empty($productID) && empty($products)) $products = $this->loadModel('product')->getPairs('', 0, '', 'all');
  658. $projectID = $this->lang->navGroup->bug == 'qa' ? 0 : $this->session->project;
  659. /* Get product params. */
  660. $productParams = ($productID && isset($products[$productID])) ? array($productID => $products[$productID]) : $products;
  661. $productParams = $productParams + array('all' => $this->lang->all);
  662. /* Get project params. */
  663. $projectParams = $this->loadModel('product')->getProjectPairsByProduct($productID, $branch);
  664. $projectParams = $projectParams + array('all' => $this->lang->bug->allProject);
  665. /* Get all modules. */
  666. $this->loadModel('tree');
  667. if($productID) $modules = $this->tree->getOptionMenu($productID, 'bug', 0, $branch);
  668. if(!$productID)
  669. {
  670. $modules = array();
  671. foreach($products as $id => $productName) $modules += $this->tree->getOptionMenu($id, 'bug');
  672. }
  673. $this->config->bug->search['actionURL'] = $actionURL;
  674. $this->config->bug->search['queryID'] = $queryID;
  675. $this->config->bug->search['params']['project']['values'] = $projectParams;
  676. $this->config->bug->search['params']['product']['values'] = $productParams;
  677. $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productID, $branch, '', true);
  678. $this->config->bug->search['params']['module']['values'] = $modules;
  679. $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($productID, $branch, (int)$projectID);
  680. $this->config->bug->search['params']['severity']['values'] = array(0 => '') + $this->lang->bug->severityList; //Fix bug #939.
  681. $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($productID), 'all', 'withbranch|releasetag');
  682. $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
  683. $product = $this->loadModel('product')->fetchByID(empty($productID) ? key($products) : $productID);
  684. if($product->type == 'normal')
  685. {
  686. unset($this->config->bug->search['fields']['branch']);
  687. unset($this->config->bug->search['params']['branch']);
  688. }
  689. else
  690. {
  691. $branches = $this->loadModel('branch')->getPairs($productID, 'noempty');
  692. $this->config->bug->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
  693. $this->config->bug->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches + array('all' => $this->lang->branch->all);
  694. }
  695. $this->loadModel('search')->setSearchParams($this->config->bug->search);
  696. }
  697. /**
  698. * 获取Bug的搜索表单配置。
  699. * Get bug search config.
  700. *
  701. * @param int $productID
  702. * @access public
  703. * @return array
  704. */
  705. public function buildSearchConfig($productID)
  706. {
  707. unset($this->config->bug->search['fields']['product']);
  708. $this->config->bug->search['params']['project']['values'] = $this->loadModel('product')->getProjectPairsByProduct($productID, 'all');
  709. $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productID);
  710. $this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($productID, 'bug');
  711. $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($productID);
  712. $this->config->bug->search['params']['severity']['values'] = array(0 => '') + $this->lang->bug->severityList; //Fix bug #939.
  713. $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($productID), 'all', 'withbranch|releasetag');
  714. $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
  715. $_SESSION['searchParams']['module'] = 'bug';
  716. $searchConfig = $this->config->bug->search;
  717. $searchConfig['params'] = $this->loadModel('search')->setDefaultParams('bug', $searchConfig['fields'], $searchConfig['params']);
  718. return $searchConfig;
  719. }
  720. /**
  721. * 获取 bugs 的影响版本和解决版本。
  722. * Process the openedBuild and resolvedBuild fields for bugs.
  723. *
  724. * @param array $bugs
  725. * @access public
  726. * @return array
  727. */
  728. public function processBuildForBugs($bugs)
  729. {
  730. $productIdList = array();
  731. foreach($bugs as $bug) $productIdList[$bug->product] = $bug->product;
  732. $builds = $this->loadModel('build')->getBuildPairs(array_unique($productIdList), 'all', 'hasdeleted');
  733. /* Process the openedBuild and resolvedBuild fields. */
  734. foreach($bugs as $bug)
  735. {
  736. $openBuildIdList = explode(',', $bug->openedBuild);
  737. $openedBuild = '';
  738. foreach($openBuildIdList as $buildID) $openedBuild .= zget($builds, $buildID) . ',';
  739. $bug->openedBuild = rtrim($openedBuild, ',');
  740. $bug->resolvedBuild = zget($builds, $bug->resolvedBuild);
  741. }
  742. return $bugs;
  743. }
  744. /**
  745. * 测试获取当前用户的 bugs。
  746. * Get user bugs.
  747. *
  748. * @param string $account
  749. * @param string $type
  750. * @param string $orderBy
  751. * @param int $limit
  752. * @param object $pager
  753. * @param int $executionID
  754. * @param int $queryID
  755. * @access public
  756. * @return array
  757. */
  758. public function getUserBugs($account, $type = 'assignedTo', $orderBy = 'id_desc', $limit = 0, $pager = null, $executionID = 0, $queryID = 0)
  759. {
  760. if($type != 'bySearch' and !$this->loadModel('common')->checkField(TABLE_BUG, $type)) return array();
  761. $moduleName = $this->app->rawMethod == 'work' ? 'workBug' : 'contributeBug';
  762. $queryName = $moduleName . 'Query';
  763. $formName = $moduleName . 'Form';
  764. if($queryID)
  765. {
  766. $query = $this->loadModel('search')->getQuery($queryID);
  767. if($query)
  768. {
  769. $this->session->set($queryName, $query->sql);
  770. $this->session->set($formName, $query->form);
  771. }
  772. else
  773. {
  774. $this->session->set($queryName, ' 1 = 1');
  775. }
  776. }
  777. else
  778. {
  779. if($this->session->$queryName === false) $this->session->set($queryName, ' 1 = 1');
  780. }
  781. if($moduleName == 'contributeBug') $bugsAssignedByMe = $this->loadModel('my')->getAssignedByMe($account, null, $orderBy, 'bug');
  782. $query = $this->session->$queryName;
  783. if(strpos($query, "`project` = 'all'") !== false) $query = str_replace("`project` = 'all'", '1 = 1', $query);
  784. $query = preg_replace('/`(\w+)`/', 't1.`$1`', $query);
  785. $orderBy = str_replace('id_', 't1.id_', $orderBy);
  786. return $this->dao->select("t1.*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) AS priOrder, IF(`severity` = 0, {$this->config->maxPriValue}, `severity`) AS severityOrder,t2.name AS productName, t2.shadow")
  787. ->from(TABLE_BUG)->alias('t1')
  788. ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product=t2.id')
  789. ->where('t1.deleted')->eq(0)
  790. ->andWhere('t2.deleted')->eq(0)
  791. ->beginIF($type == 'bySearch')->andWhere($query)->fi()
  792. ->beginIF($executionID)->andWhere('t1.execution')->eq($executionID)->fi()
  793. ->beginIF($type != 'closedBy' and $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi()
  794. ->beginIF($type != 'all' and $type != 'bySearch')->andWhere("t1.`$type`")->eq($account)->fi()
  795. ->beginIF($type == 'bySearch' and $moduleName == 'workBug')->andWhere("t1.assignedTo")->eq($account)->fi()
  796. ->beginIF($type == 'assignedTo' and $moduleName == 'workBug')->andWhere('t1.status')->ne('closed')->fi()
  797. ->beginIF($type == 'bySearch' and $moduleName == 'contributeBug')
  798. ->andWhere('t1.openedBy', 1)->eq($account)
  799. ->orWhere('t1.closedBy')->eq($account)
  800. ->orWhere('t1.resolvedBy')->eq($account)
  801. ->orWhere('t1.id')->in(!empty($bugsAssignedByMe) ? array_keys($bugsAssignedByMe) : array())
  802. ->markRight(1)
  803. ->fi()
  804. ->orderBy($orderBy)
  805. ->beginIF($limit > 0)->limit($limit)->fi()
  806. ->page($pager)
  807. ->fetchAll('id', false);
  808. }
  809. /**
  810. * 测试获取用户的bugs的 id => title 数组。
  811. * Get bug pairs of a user.
  812. *
  813. * @param string $account
  814. * @param bool $appendProduct
  815. * @param int $limit
  816. * @param array $skipProductIdList
  817. * @param array $skipExecutionIdList
  818. * @param int|array $appendBugID
  819. * @access public
  820. * @return array
  821. */
  822. public function getUserBugPairs($account, $appendProduct = true, $limit = 0, $skipProductIdList = array(), $skipExecutionIdList = array(), $appendBugID = 0)
  823. {
  824. $deletedProjectIdList = $this->dao->select('id')->from(TABLE_PROJECT)->where('deleted')->eq(1)->fetchPairs();
  825. $bugs = array();
  826. $stmt = $this->dao->select('t1.id, t1.title, t2.name as product')
  827. ->from(TABLE_BUG)->alias('t1')
  828. ->leftJoin(TABLE_PRODUCT)->alias('t2')
  829. ->on('t1.product=t2.id')
  830. ->where('t1.assignedTo')->eq($account)
  831. ->andWhere('t1.status')->ne('closed')
  832. ->beginIF(!empty($deletedProjectIdList))->andWhere('t1.execution')->notin($deletedProjectIdList)->fi()
  833. ->beginIF(!empty($skipProductIdList))->andWhere('t1.product')->notin($skipProductIdList)->fi()
  834. ->beginIF(!empty($skipExecutionIdList))->andWhere('t1.execution')->notin($skipExecutionIdList)->fi()
  835. ->andWhere('t1.deleted')->eq(0)
  836. ->andWhere('t2.deleted')->eq(0)
  837. ->beginIF(!empty($appendBugID))->orWhere('t1.id')->in($appendBugID)->fi()
  838. ->orderBy('id desc')
  839. ->beginIF($limit > 0)->limit($limit)->fi()
  840. ->query();
  841. while($bug = $stmt->fetch())
  842. {
  843. if($appendProduct) $bug->title = $bug->product . ' / ' . $bug->title;
  844. $bugs[$bug->id] = $bug->title;
  845. }
  846. return $bugs;
  847. }
  848. /**
  849. * 获取某个项目的 bugs。
  850. * Get bugs of a project.
  851. *
  852. * @param int $projectID
  853. * @param int $productID
  854. * @param int|string $branchID
  855. * @param int $build
  856. * @param string $type
  857. * @param int $param
  858. * @param string $orderBy
  859. * @param string $excludeBugs
  860. * @param object $pager
  861. * @access public
  862. * @return array
  863. */
  864. public function getProjectBugs($projectID, $productID = 0, $branchID = 0, $build = 0, $type = '', $param = 0, $orderBy = 'id_desc', $excludeBugs = '', $pager = null)
  865. {
  866. if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
  867. if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
  868. $type = strtolower($type);
  869. if($type == 'bysearch')
  870. {
  871. $bugs = $this->getBySearch('project', $productID, $branchID, $projectID, 0, $param, $excludeBugs, $orderBy, $pager);
  872. }
  873. else
  874. {
  875. $bugs = $this->dao->select("t1.*, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) AS priOrder, IF(t1.`severity` = 0, {$this->config->maxPriValue}, t1.`severity`) AS severityOrder")->from(TABLE_BUG)->alias('t1')
  876. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id')
  877. ->where('t1.deleted')->eq(0)
  878. ->beginIF(empty($build))->andWhere('t1.project')->eq($projectID)->fi()
  879. ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID)->fi()
  880. ->beginIF(!empty($productID) and $branchID != 'all')->andWhere('t1.branch')->eq($branchID)->fi()
  881. ->beginIF($type == 'unresolved')->andWhere('t1.status')->eq('active')->fi()
  882. ->beginIF($type == 'noclosed')->andWhere('t1.status')->ne('closed')->fi()
  883. ->beginIF($type == 'assignedtome')->andWhere('t1.assignedTo')->eq($this->app->user->account)->fi()
  884. ->beginIF($type == 'openedbyme')->andWhere('t1.openedBy')->eq($this->app->user->account)->fi()
  885. ->beginIF(!empty($param))->andWhere('t2.path')->like("%,$param,%")->andWhere('t2.deleted')->eq(0)->fi()
  886. ->beginIF($build)->andWhere("CONCAT(',', t1.openedBuild, ',') like '%,$build,%'")->fi()
  887. ->beginIF($excludeBugs)->andWhere('t1.id')->notIN($excludeBugs)->fi()
  888. ->orderBy($orderBy)
  889. ->page($pager)
  890. ->fetchAll('id', false);
  891. }
  892. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug', false);
  893. return $bugs;
  894. }
  895. /**
  896. * 获取执行的 bug。
  897. * Get bugs of a execution.
  898. *
  899. * @param int $executionID
  900. * @param int $productID
  901. * @param int|string $branchID
  902. * @param string|array $builds
  903. * @param string $type
  904. * @param int $param
  905. * @param string $orderBy
  906. * @param string $excludeBugs
  907. * @param object $pager
  908. * @access public
  909. * @return array
  910. */
  911. public function getExecutionBugs($executionID, $productID = 0, $branchID = 'all', $builds = '0', $type = '', $param = 0, $orderBy = 'id_desc', $excludeBugs = '', $pager = null)
  912. {
  913. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
  914. if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
  915. if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
  916. if(strpos($orderBy, 'status') !== false) $orderBy = str_replace('status', 'statusOrder', $orderBy);
  917. $type = strtolower($type);
  918. if($type == 'bysearch')
  919. {
  920. $bugs = $this->getBySearch('execution', $productID, $branchID, $projectID = 0, $executionID, $param, $excludeBugs, $orderBy, $pager);
  921. }
  922. else
  923. {
  924. $condition = '';
  925. if($builds)
  926. {
  927. $conditions = array();
  928. if(!is_array($builds)) $builds = array_filter(array_unique(explode(',', $builds)));
  929. foreach($builds as $build) $conditions[] = "FIND_IN_SET('$build', t1.openedBuild)";
  930. $condition = implode(' OR ', $conditions);
  931. $condition = "($condition)";
  932. }
  933. $bugs = $this->dao->select("t1.*, IF(t1.`pri` = 0, {$this->config->maxPriValue}, t1.`pri`) AS priOrder, IF(t1.`severity` = 0, {$this->config->maxPriValue}, t1.`severity`) AS severityOrder, INSTR('active,resolved,closed,', t1.status) as statusOrder")->from(TABLE_BUG)->alias('t1')
  934. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module = t2.id')
  935. ->where('t1.deleted')->eq('0')
  936. ->beginIF(!empty($productID) && $branchID !== 'all')->andWhere('t1.branch')->eq($branchID)->fi()
  937. ->beginIF(empty($builds))->andWhere('t1.execution')->eq($executionID)->fi()
  938. ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID)->fi()
  939. ->beginIF($type == 'unresolved')->andWhere('t1.status')->eq('active')->fi()
  940. ->beginIF($type == 'noclosed')->andWhere('t1.status')->ne('closed')->fi()
  941. ->beginIF($condition)->andWhere("$condition")->fi()
  942. ->beginIF(!empty($param))
  943. ->andWhere('t2.path')->like("%,$param,%")
  944. ->andWhere('t2.deleted')->eq('0')
  945. ->fi()
  946. ->beginIF($excludeBugs)->andWhere('t1.id')->notIN($excludeBugs)->fi()
  947. ->orderBy($orderBy)
  948. ->page($pager)
  949. ->fetchAll('id', false);
  950. }
  951. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug', false);
  952. return $bugs;
  953. }
  954. /**
  955. * 获取产品未关联版本的bug。
  956. * Get product left bugs.
  957. *
  958. * @param array $buildIdList
  959. * @param int $productID
  960. * @param int|string $branch
  961. * @param string $linkedBugs
  962. * @param object $pager
  963. * @access public
  964. * @return array|null
  965. */
  966. public function getProductLeftBugs($buildIdList, $productID, $branch = '', $linkedBugs = '', $pager = null)
  967. {
  968. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
  969. /* 获取版本关联的执行。 */
  970. /* Get executions of builds. */
  971. $executionIdList = $this->getLinkedExecutionByIdList($buildIdList);
  972. if(empty($executionIdList)) return array();
  973. /* 获取执行的最小开始时间和最大结束时间。 */
  974. /* Get min begin date and max end date. */
  975. $minBegin = '1970-01-01';
  976. $maxEnd = '1970-01-01';
  977. $executions = $this->dao->select('id,begin,end')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->fetchAll();
  978. foreach($executions as $execution)
  979. {
  980. if(empty($minBegin) || $minBegin > $execution->begin) $minBegin = $execution->begin;
  981. if(empty($maxEnd) || $maxEnd < $execution->end) $maxEnd = $execution->end;
  982. }
  983. /* 获取在最小开始日期之前 未完成的版本id。 */
  984. /* Get undone builds before min begin date. */
  985. $beforeBuilds = $this->dao->select('t1.id')->from(TABLE_BUILD)->alias('t1')
  986. ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution=t2.id')
  987. ->where('t1.product')->eq($productID)
  988. ->andWhere('t2.status')->ne('done')
  989. ->andWhere('t2.deleted')->eq(0)
  990. ->andWhere('t1.deleted')->eq(0)
  991. ->andWhere('t1.date')->lt($minBegin)
  992. ->fetchPairs('id', 'id');
  993. /* 返回在执行最小开始和最大结束时间内的未关联版本的bugs。 */
  994. /* Return bugs that unrelate builds in the execution timeframe. */
  995. return $this->dao->select('*')->from(TABLE_BUG)->where('deleted')->eq(0)
  996. ->andWhere('product')->eq($productID)
  997. ->andWhere('toStory')->eq(0)
  998. ->andWhere('openedDate')->ge($minBegin)
  999. ->andWhere('openedDate')->le($maxEnd)
  1000. ->andWhere("(status = 'active' OR resolvedDate > '{$maxEnd}')")
  1001. ->andWhere('openedBuild')->notin($beforeBuilds)
  1002. ->beginIF($linkedBugs)->andWhere('id')->notIN($linkedBugs)->fi()
  1003. ->beginIF($branch !== '')->andWhere('branch')->in("0,$branch")->fi()
  1004. ->page($pager)
  1005. ->fetchAll('id', false);
  1006. }
  1007. /**
  1008. * 获取产品的bug键对。
  1009. * Get product bug pairs.
  1010. *
  1011. * @param int $productID
  1012. * @param int|string $branch
  1013. * @param string $search
  1014. * @param int $limit
  1015. * @param string $range single|all
  1016. * @access public
  1017. * @return array
  1018. */
  1019. public function getProductBugPairs($productID, $branch = '', $search = '', $limit = 0, $range = 'single')
  1020. {
  1021. /* 获取产品的bugs。 */
  1022. /* Get product bugs. */
  1023. $productID = (int)$productID;
  1024. return $this->dao->select("id, CONCAT(IF(product = $productID, '', CONCAT('{$this->lang->product->common}#', product, '@')), id, ':', title) AS title, IF(product = $productID, 0, product) AS `order`")->from(TABLE_BUG)
  1025. ->where('deleted')->eq(0)
  1026. ->beginIF($range == 'single' && $productID)->andWhere('product')->eq($productID)->fi()
  1027. ->beginIF(!$this->app->user->admin)->andWhere('execution')->in('0,' . $this->app->user->view->sprints)->fi()
  1028. ->beginIF($range == 'all' && !$this->app->user->admin)->andWhere('product')->in($this->app->user->view->products)->fi()
  1029. ->beginIF($branch !== '')->andWhere('branch')->in($branch)->fi()
  1030. ->beginIF(strlen(trim($search)))
  1031. ->andWhere('title', true)->like('%' . $search . '%')
  1032. ->orWhere('id')->like('%' . $search . '%')
  1033. ->markRight(1)
  1034. ->fi()
  1035. ->andWhere('deleted')->eq(0)
  1036. ->orderBy('`order`, id desc')
  1037. ->beginIF($limit)->limit($limit)->fi()
  1038. ->fetchPairs('id', 'title');
  1039. }
  1040. /**
  1041. * 获取产品成员键对。
  1042. * get Product member pairs.
  1043. *
  1044. * @param int $productID
  1045. * @param string $branchID
  1046. * @access public
  1047. * @return array
  1048. */
  1049. public function getProductMemberPairs($productID, $branchID = '')
  1050. {
  1051. if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getTeamMembersPairs();
  1052. /* 获取产品关联的项目。 */
  1053. /* Get related projects of product. */
  1054. $projects = $this->loadModel('product')->getProjectPairsByProduct($productID, $branchID);
  1055. /* 获取项目的团队成员。 */
  1056. /* Get team members of projects. */
  1057. return $this->loadModel('user')->getTeamMemberPairs(array_keys($projects));
  1058. }
  1059. /**
  1060. * 通过版本 id 和产品 id 获取 bugs。
  1061. * Get bugs according to buildID and productID.
  1062. *
  1063. * @param array $buildIdList
  1064. * @param int $productID
  1065. * @param string $branch
  1066. * @param string $linkedBugs
  1067. * @param object $pager
  1068. * @access public
  1069. * @return array
  1070. */
  1071. public function getReleaseBugs($buildIdList, $productID, $branch = 0, $linkedBugs = '', $pager = null)
  1072. {
  1073. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
  1074. $executionIdList = $this->getLinkedExecutionByIdList($buildIdList);
  1075. if(empty($executionIdList)) return array();
  1076. $executions = $this->dao->select('id,type,begin')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->fetchAll('id');
  1077. if(count($executionIdList) > 1) $condition = 'execution NOT ' . helper::dbIN($executionIdList);
  1078. else $condition = 'execution !' . helper::dbIN($executionIdList);
  1079. $minBegin = '';
  1080. foreach($executions as $execution)
  1081. {
  1082. if(empty($minBegin) or $minBegin > $execution->begin) $minBegin = $execution->begin;
  1083. $condition .= " OR (`execution` = '{$execution->id}' AND openedDate < '{$execution->begin}')";
  1084. }
  1085. return $this->dao->select('*')->from(TABLE_BUG)
  1086. ->where('resolution')->ne('postponed')
  1087. ->beginIF(!empty($minBegin))->andWhere('resolvedDate')->ge($minBegin)->fi()
  1088. ->andWhere('product')->eq($productID)
  1089. ->beginIF($linkedBugs)->andWhere('id')->notIN($linkedBugs)->fi()
  1090. ->beginIF($branch)->andWhere('branch')->in("0,$branch")->fi()
  1091. ->andWhere("($condition)")
  1092. ->andWhere('deleted')->eq(0)
  1093. ->orderBy('openedDate ASC')
  1094. ->page($pager)
  1095. ->fetchAll('id', false);
  1096. }
  1097. /**
  1098. * 通过版本 id 列表获取版本关联的执行。
  1099. * Get linked execution by build id list.
  1100. *
  1101. * @param array $buildIdList
  1102. * @access public
  1103. * @return array
  1104. */
  1105. public function getLinkedExecutionByIdList($buildIdList)
  1106. {
  1107. /* Get information of builds. */
  1108. $builds = $this->dao->select('id,execution,builds')->from(TABLE_BUILD)->where('id')->in($buildIdList)->fetchAll('id');
  1109. $executionIdList = array();
  1110. $linkedBuildIdList = array();
  1111. foreach($builds as $build)
  1112. {
  1113. /* 如果版本的builds字段不为空,将版本的builds追加到linkedBuildIdList数组。 */
  1114. /* If build builds is not empty, append build builds to linkedBuildIdList. */
  1115. if($build->builds) $linkedBuildIdList = array_merge($linkedBuildIdList, explode(',', $build->builds));
  1116. /* 如果版本的执行字段不为空,将执行字段追加到executionIdList数组。 */
  1117. /* If build execution is not empty, append build execution to executionIdList. */
  1118. if(!empty($build->execution)) $executionIdList[$build->execution] = $build->execution;
  1119. }
  1120. /* 如果linkedBuildIdList不为空,将关联builds的版本追加到executionIdList数组。 */
  1121. /* If linkedBuildIdList is not empty, append the execution of the linked builds to executionIdList. */
  1122. if($linkedBuildIdList)
  1123. {
  1124. $linkedBuilds = $this->dao->select('id,execution')->from(TABLE_BUILD)->where('id')->in(array_unique($linkedBuildIdList))->fetchAll('id');
  1125. foreach($linkedBuilds as $build)
  1126. {
  1127. if(empty($build->execution)) continue;
  1128. $executionIdList[$build->execution] = $build->execution;
  1129. }
  1130. }
  1131. return $executionIdList;
  1132. }
  1133. /**
  1134. * 获取需求产生的bugs。
  1135. * Get bugs of a story.
  1136. *
  1137. * @param int $storyID
  1138. * @param int $executionID
  1139. * @access public
  1140. * @return array
  1141. */
  1142. public function getStoryBugs($storyID, $executionID = 0)
  1143. {
  1144. /* 获取需求产生的bugs。 */
  1145. /* Get bugs of the story. */
  1146. return $this->dao->select('id, title, pri, type, status, assignedTo, resolvedBy, resolution')
  1147. ->from(TABLE_BUG)
  1148. ->where('story')->eq($storyID)
  1149. ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
  1150. ->andWhere('deleted')->eq(0)
  1151. ->fetchAll('id');
  1152. }
  1153. /**
  1154. * 获取用例关联的bugs。
  1155. * Get case bugs.
  1156. *
  1157. * @param int $runID
  1158. * @param int $caseID
  1159. * @param int $version
  1160. * @param string $orderBy
  1161. * @access public
  1162. * @return array|null
  1163. */
  1164. public function getCaseBugs($runID, $caseID = 0, $version = 0, $orderBy = 'id_asc')
  1165. {
  1166. /* 获取用例关联的bugs。 */
  1167. /* Get case bugs. */
  1168. return $this->dao->select('*')->from(TABLE_BUG)
  1169. ->where('1=1')
  1170. ->beginIF($runID)->andWhere('`result`')->eq($runID)->fi()
  1171. ->beginIF($runID == 0 and $caseID)->andWhere('`case`')->eq($caseID)->fi()
  1172. ->beginIF($version)->andWhere('`caseVersion`')->eq($version)->fi()
  1173. ->andWhere('deleted')->eq(0)
  1174. ->orderBy($orderBy)
  1175. ->fetchAll('id', false);
  1176. }
  1177. /**
  1178. * 获取需求关联的 bug 数量。
  1179. * Get counts of some stories' bugs.
  1180. *
  1181. * @param array $stories
  1182. * @param int $executionID
  1183. * @access public
  1184. * @return array
  1185. */
  1186. public function getStoryBugCounts($stories, $executionID = 0)
  1187. {
  1188. if(empty($stories)) return array();
  1189. $bugCounts = $this->dao->select('story, COUNT(1) AS bugs')
  1190. ->from(TABLE_BUG)
  1191. ->where('story')->in($stories)
  1192. ->andWhere('deleted')->eq(0)
  1193. ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
  1194. ->groupBy('story')
  1195. ->fetchPairs();
  1196. foreach($stories as $storyID) if(!isset($bugCounts[$storyID])) $bugCounts[$storyID] = 0;
  1197. return $bugCounts;
  1198. }
  1199. /**
  1200. * 从测试结果中获取bug信息。
  1201. * Get bug info from a result.
  1202. *
  1203. * @param int $resultID
  1204. * @param int $caseID
  1205. * @param string $caseID
  1206. * @access public
  1207. * @return array
  1208. * @param string $stepIdList
  1209. */
  1210. public function getBugInfoFromResult($resultID, $caseID = 0, $stepIdList = '')
  1211. {
  1212. $result = common::isTutorialMode() ? $this->loadModel('tutorial')->getResult() : $this->dao->findById($resultID)->from(TABLE_TESTRESULT)->fetch();
  1213. if(!$result) return array();
  1214. $run = $this->loadModel('testtask')->getRunById($result->run);
  1215. if(!$run) $run = new stdclass();
  1216. if($caseID > 0) $run->case = $this->loadModel('testcase')->getById($caseID, $result->version);
  1217. $stepResults = unserialize($result->stepResults);
  1218. if(!empty($stepResults))
  1219. {
  1220. $bugStep = '';
  1221. $bugResult = isset($stepResults[0]) ? zget($stepResults[0], 'real') : '';
  1222. $bugExpect = '';
  1223. $caseSteps = $run->case->steps;
  1224. $caseSteps = array_combine(array_column($caseSteps, 'id'), $caseSteps);
  1225. $steps = explode('_', trim($stepIdList, '_'));
  1226. foreach($steps as $stepId)
  1227. {
  1228. if(!isset($caseSteps[$stepId])) continue;
  1229. $step = $caseSteps[$stepId];
  1230. $stepDesc = str_replace("\n", '</p><p>', $step->desc);
  1231. $stepExpect = str_replace("\n", '</p><p>', $step->expect);
  1232. $stepResult = (!isset($stepResults[$stepId]) or empty($stepResults[$stepId]['real'])) ? '' : $stepResults[$stepId]['real'];
  1233. $bugStep .= '<p>' . $step->name . '. ' . $stepDesc . '</p>';
  1234. $bugResult .= '<p>' . $step->name . '. ' . $stepResult . '</p>';
  1235. $bugExpect .= '<p>' . $step->name . '. ' . $stepExpect . '</p>';
  1236. }
  1237. }
  1238. $bugSteps = $run->case->precondition != '' ? "<p>[" . $this->lang->testcase->precondition . "]</p>" . "\n" . $run->case->precondition : '';
  1239. $bugSteps .= '<p></p>';
  1240. $bugSteps .= !empty($stepResults) && !empty($bugStep) ? str_replace(array('<br/>', '<p></p>'), '', $this->lang->bug->tplStep) . $bugStep : $this->lang->bug->tplStep;
  1241. $bugSteps .= '<p></p>';
  1242. $bugSteps .= !empty($stepResults) && !empty($bugResult) ? str_replace(array('<br/>', '<p></p>'), '', $this->lang->bug->tplResult) . $bugResult : $this->lang->bug->tplResult;
  1243. $bugSteps .= '<p></p>';
  1244. $bugSteps .= !empty($stepResults) && !empty($bugExpect) ? str_replace(array('<br/>', '<p></p>'), '', $this->lang->bug->tplExpect) . $bugExpect : $this->lang->bug->tplExpect;
  1245. if(!empty($run->task)) $testtask = $this->loadModel('testtask')->getByID($run->task);
  1246. $executionID = isset($testtask->execution) ? $testtask->execution : 0;
  1247. $projectID = isset($testtask->project) ? $testtask->project : 0;
  1248. if(!$executionID and $caseID > 0) $executionID = isset($run->case->execution) ? $run->case->execution : 0; // Fix feedback #1043.
  1249. if(!$executionID and $this->app->tab == 'execution') $executionID = $this->session->execution;
  1250. if(!$projectID and $caseID > 0) $projectID = isset($run->case->project) ? $run->case->project : 0;
  1251. if(!$projectID and $this->app->tab == 'projectID') $projectID = $this->session->project;
  1252. return array('title' => $run->case->title, 'caseID' => $caseID, 'steps' => $bugSteps, 'storyID' => $run->case->story, 'moduleID' => $run->case->module, 'version' => $run->case->version, 'executionID' => $executionID, 'projectID' => $projectID);
  1253. }
  1254. /**
  1255. * 统计迭代 bug 数量。
  1256. * Statistics by bug execution.
  1257. *
  1258. * @access public
  1259. * @return array
  1260. */
  1261. public function getDataOfBugsPerExecution()
  1262. {
  1263. $datas = $this->dao->select('execution AS name, count(execution) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('execution')->orderBy('value_desc')->fetchAll('name');
  1264. if(!$datas) return array();
  1265. $executionPairs = $this->loadModel('execution')->getPairs();
  1266. $maxLength = common::checkNotCN() ? 22 : 12;
  1267. foreach($datas as $executionID => $data)
  1268. {
  1269. $data->name = $executionID == 0 ? $this->lang->report->undefined : zget($executionPairs, $executionID, (string)$executionID);
  1270. $data->title = $data->name;
  1271. if(mb_strlen($data->name, 'UTF-8') > $maxLength) $data->name = mb_substr($data->name, 0, $maxLength, 'UTF-8') . '...';
  1272. }
  1273. return $datas;
  1274. }
  1275. /**
  1276. * 统计版本 Bug 数量。
  1277. * Statistics by bug build.
  1278. *
  1279. * @access public
  1280. * @return array
  1281. */
  1282. public function getDataOfBugsPerBuild()
  1283. {
  1284. $datas = $this->dao->select('openedBuild AS name, COUNT(openedBuild) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('openedBuild')->orderBy('value DESC')->fetchAll('name');
  1285. if(!$datas) return array();
  1286. foreach($datas as $buildIdList => $data)
  1287. {
  1288. if(is_int($buildIdList) || $buildIdList == 'trunk') continue;
  1289. $openedBuildIdList = explode(',', $buildIdList);
  1290. /* Bug 可以关联多个影响版本,一个版本被多个 bug 关联时,累加 bug 数量。*/
  1291. /* Bugs can be associated with multiple builds. When a build is associated with multiple bugs, accumulated bugs.*/
  1292. foreach($openedBuildIdList as $buildID)
  1293. {
  1294. if(!isset($datas[$buildID]))
  1295. {
  1296. $datas[$buildID] = new stdclass();
  1297. $datas[$buildID]->name = $buildID;
  1298. $datas[$buildID]->value = 0;
  1299. }
  1300. $datas[$buildID]->value += $data->value;
  1301. }
  1302. unset($datas[$buildIdList]);
  1303. }
  1304. /* 统计最后一次查询的所属产品列表。*/
  1305. /* Get products in the last query. */
  1306. $productIdList = $this->session->product ? array($this->session->product) : array();
  1307. if($this->reportCondition() !== true)
  1308. {
  1309. preg_match('/`product` IN \((?P<productIdList>.+)\)/', $this->reportCondition(), $matches);
  1310. if(!empty($matches) && isset($matches['productIdList']))
  1311. {
  1312. $productIdList = str_replace('\'', '', $matches['productIdList']);
  1313. $productIdList = explode(',', $productIdList);
  1314. }
  1315. }
  1316. $builds = $this->loadModel('build')->getBuildPairs($productIdList, 0, 'hasdeleted');
  1317. $this->app->loadLang('report');
  1318. foreach($datas as $buildID => $data) $data->name = zget($builds, $buildID, $this->lang->report->undefined);
  1319. ksort($datas);
  1320. return $datas;
  1321. }
  1322. /**
  1323. * 统计模块 bug 数量。
  1324. * Statistics by bug module.
  1325. *
  1326. * @access public
  1327. * @return array
  1328. */
  1329. public function getDataOfBugsPerModule()
  1330. {
  1331. $datas = $this->dao->select('module AS name, COUNT(module) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('module')->orderBy('value DESC')->fetchAll('name');
  1332. if(!$datas) return array();
  1333. $modules = $this->loadModel('tree')->getModulesName(array_keys($datas), true, true);
  1334. foreach($datas as $moduleID => $data) $data->name = zget($modules, $moduleID, '/');
  1335. return $datas;
  1336. }
  1337. /**
  1338. * 统计每天新增 bug 数量。
  1339. * Statistics by the number of created bug daily.
  1340. *
  1341. * @access public
  1342. * @return array
  1343. */
  1344. public function getDataOfOpenedBugsPerDay()
  1345. {
  1346. return $this->dao->select('DATE_FORMAT(openedDate, "%Y-%m-%d") AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('name')->fetchAll();
  1347. }
  1348. /**
  1349. * 统计每天解决 bug 数量。
  1350. * Statistics by the number of resolved bug.
  1351. *
  1352. * @access public
  1353. * @return array
  1354. */
  1355. public function getDataOfResolvedBugsPerDay()
  1356. {
  1357. return $this->dao->select('DATE_FORMAT(resolvedDate, "%Y-%m-%d") AS name, COUNT(1) AS value')->from(TABLE_BUG)
  1358. ->where($this->reportCondition())
  1359. ->groupBy('name')
  1360. ->having('name != 0000-00-00')
  1361. ->orderBy('name')
  1362. ->fetchAll();
  1363. }
  1364. /**
  1365. * 统计每天关闭 bug 数量。
  1366. * Statistics by the number of closed bug.
  1367. *
  1368. * @access public
  1369. * @return array
  1370. */
  1371. public function getDataOfClosedBugsPerDay()
  1372. {
  1373. return $this->dao->select('DATE_FORMAT(closedDate, "%Y-%m-%d") AS name, COUNT(1) AS value')->from(TABLE_BUG)
  1374. ->where($this->reportCondition())
  1375. ->groupBy('name')
  1376. ->having('name != 0000-00-00')
  1377. ->orderBy('name')
  1378. ->fetchAll();
  1379. }
  1380. /**
  1381. * 统计每人提交的 bug 数量。
  1382. * Statistics by bug creator.
  1383. *
  1384. * @access public
  1385. * @return array
  1386. */
  1387. public function getDataOfOpenedBugsPerUser()
  1388. {
  1389. $datas = $this->dao->select('openedBy AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1390. if(!$datas) return array();
  1391. if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
  1392. foreach($datas as $account => $data) $data->name = zget($this->users, $account);
  1393. return $datas;
  1394. }
  1395. /**
  1396. * 统计每人解决的 bug 数量。
  1397. * Statistics by bug resolver.
  1398. *
  1399. * @access public
  1400. * @return array
  1401. */
  1402. public function getDataOfResolvedBugsPerUser()
  1403. {
  1404. $datas = $this->dao->select('resolvedBy AS name, COUNT(1) AS value')
  1405. ->from(TABLE_BUG)->where($this->reportCondition())
  1406. ->andWhere('resolvedBy')->ne('')
  1407. ->groupBy('name')
  1408. ->orderBy('value DESC')
  1409. ->fetchAll('name');
  1410. if(!$datas) return array();
  1411. if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
  1412. foreach($datas as $account => $data) $data->name = zget($this->users, $account);
  1413. return $datas;
  1414. }
  1415. /**
  1416. * 统计每人关闭的 bug 数量。
  1417. * Statistics by bug closer.
  1418. *
  1419. * @access public
  1420. * @return array
  1421. */
  1422. public function getDataOfClosedBugsPerUser()
  1423. {
  1424. $datas = $this->dao->select('closedBy AS name, COUNT(1) AS value')
  1425. ->from(TABLE_BUG)
  1426. ->where($this->reportCondition())
  1427. ->andWhere('closedBy')->ne('')
  1428. ->groupBy('name')
  1429. ->orderBy('value DESC')
  1430. ->fetchAll('name');
  1431. if(!$datas) return array();
  1432. if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
  1433. foreach($datas as $account => $data) $data->name = zget($this->users, $account);
  1434. return $datas;
  1435. }
  1436. /**
  1437. * 按照 bug 严重程度统计。
  1438. * Statistics by bug severity
  1439. *
  1440. * @access public
  1441. * @return array
  1442. */
  1443. public function getDataOfBugsPerSeverity()
  1444. {
  1445. $datas = $this->dao->select('severity AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1446. if(!$datas) return array();
  1447. foreach($datas as $severity => $data) $data->name = $this->lang->bug->report->bugsPerSeverity->graph->xAxisName . ':' . zget($this->lang->bug->severityList, $severity);
  1448. return $datas;
  1449. }
  1450. /**
  1451. * 按照解决方案统计。
  1452. * Statistics by bug resolution.
  1453. *
  1454. * @access public
  1455. * @return array
  1456. */
  1457. public function getDataOfBugsPerResolution()
  1458. {
  1459. $datas = $this->dao->select('resolution AS name, COUNT(1) AS value')
  1460. ->from(TABLE_BUG)
  1461. ->where($this->reportCondition())
  1462. ->andWhere('resolution')->ne('')
  1463. ->groupBy('name')
  1464. ->orderBy('value DESC')
  1465. ->fetchAll('name');
  1466. if(!$datas) return array();
  1467. foreach($datas as $resolution => $data) $data->name = zget($this->lang->bug->resolutionList, $resolution);
  1468. return $datas;
  1469. }
  1470. /**
  1471. * 按 bug 状态统计。
  1472. * Statistics by bug status.
  1473. *
  1474. * @access public
  1475. * @return array
  1476. */
  1477. public function getDataOfBugsPerStatus()
  1478. {
  1479. $datas = $this->dao->select('status AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1480. if(!$datas) return array();
  1481. foreach($datas as $status => $data) $data->name = zget($this->lang->bug->statusList, $status);
  1482. return $datas;
  1483. }
  1484. /**
  1485. * 按照 bug 优先级统计。
  1486. * Statistics by bug pri.
  1487. *
  1488. * @access public
  1489. * @return array
  1490. */
  1491. public function getDataOfBugsPerPri()
  1492. {
  1493. $datas = $this->dao->select('pri AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1494. if(!$datas) return array();
  1495. foreach($datas as $data) $data->name = $this->lang->bug->report->bugsPerPri->graph->xAxisName . ':' . zget($this->lang->bug->priList, $data->name);
  1496. return $datas;
  1497. }
  1498. /**
  1499. * 按照 bug 激活次数统计。
  1500. * Statistics by bug activation times.
  1501. *
  1502. * @access public
  1503. * @return array
  1504. */
  1505. public function getDataOfBugsPerActivatedCount()
  1506. {
  1507. $datas = $this->dao->select('activatedCount AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1508. if(!$datas) return array();
  1509. foreach($datas as $data) $data->name = $this->lang->bug->report->bugsPerActivatedCount->graph->xAxisName . ':' . $data->name;
  1510. return $datas;
  1511. }
  1512. /**
  1513. * 按照 bug 类型统计。
  1514. * Statistics by bug type.
  1515. *
  1516. * @access public
  1517. * @return array
  1518. */
  1519. public function getDataOfBugsPerType()
  1520. {
  1521. $datas = $this->dao->select('type AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1522. if(!$datas) return array();
  1523. foreach($datas as $type => $data) $data->name = zget($this->lang->bug->typeList, $type);
  1524. return $datas;
  1525. }
  1526. /**
  1527. * 按照 bug 指派给统计。
  1528. * Statistics by bug assignment.
  1529. *
  1530. * @access public
  1531. * @return array
  1532. */
  1533. public function getDataOfBugsPerAssignedTo()
  1534. {
  1535. $datas = $this->dao->select('assignedTo AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
  1536. if(!$datas) return array();
  1537. if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
  1538. foreach($datas as $account => $data) $data->name = zget($this->users, $account);
  1539. return $datas;
  1540. }
  1541. /**
  1542. * 通过 sonarqube id 获取bug。
  1543. * Get by Sonarqube id.
  1544. *
  1545. * @param int $sonarqubeID
  1546. * @access public
  1547. * @return array|false
  1548. */
  1549. public function getBySonarqubeID($sonarqubeID)
  1550. {
  1551. return $this->dao->select('issueKey')->from(TABLE_BUG)->where('issueKey')->like("$sonarqubeID:%")->fetchPairs();
  1552. }
  1553. /**
  1554. * 获取bug查询语句。
  1555. * Get bug query.
  1556. *
  1557. * @param string $bugQuery
  1558. * @access public
  1559. * @return string
  1560. */
  1561. public function getBugQuery($bugQuery)
  1562. {
  1563. /* 如果要查询所有产品,将当前用户可以看到的"`product` = 'all'" 换为自己可以看到所有的产品ID。 */
  1564. /* If you need to query all products, replace "`product` = 'all'" with the products that the current user can see.. */
  1565. $allProduct = "`product` = 'all'";
  1566. if(strpos($bugQuery, $allProduct) !== false)
  1567. {
  1568. $products = $this->app->user->view->products;
  1569. $bugQuery = str_replace($allProduct, '1', $bugQuery);
  1570. $bugQuery = $bugQuery . ' AND `product` ' . helper::dbIN($products);
  1571. }
  1572. /* 如果要查询所有项目,将当前用户可以看到的"`project` = 'all'" 换为自己可以看到所有的项目ID。 */
  1573. /* If you need to query all projects, replace "`project` = 'all'" with the projects that the current user can see.. */
  1574. $allProject = "`project` = 'all'";
  1575. if(strpos($bugQuery, $allProject) !== false)
  1576. {
  1577. $projects = $this->loadModel('project')->getPairs();
  1578. if(is_array($projects)) $projectIdList = implode(',', array_keys($projects));
  1579. $bugQuery = str_replace($allProject, '1', $bugQuery);
  1580. $bugQuery = $bugQuery . ' AND `project` in (' . $projectIdList . ')';
  1581. }
  1582. /* 如果要依据解决日期搜索,解决日期应该不是'0000-00-00'。 */
  1583. /* If you need to query by resolvedDate, resolvedDate shouldn't be '0000-00-00'. */
  1584. if(strpos($bugQuery, ' `resolvedDate` ') !== false) $bugQuery = str_replace(' `resolvedDate` ', " `resolvedDate` != '0000-00-00 00:00:00' AND `resolvedDate` ", $bugQuery);
  1585. /* 如果要依据关闭日期搜索,关闭日期应该不是'0000-00-00'。 */
  1586. /* If you need to query by closedDate, closedDate shouldn't be '0000-00-00'. */
  1587. if(strpos($bugQuery, ' `closedDate` ') !== false) $bugQuery = str_replace(' `closedDate` ', " `closedDate` != '0000-00-00 00:00:00' AND `closedDate` ", $bugQuery);
  1588. /* 如果要依据需求搜索,需求ID应该不是0。 */
  1589. /* If you need to query by story, story shouldn't be zero. */
  1590. if(strpos($bugQuery, ' `story` ') !== false)
  1591. {
  1592. /* 如果要搜索条件是包含或者不包含,应该从需求的标题、关键字、描述、期望中搜索。 */
  1593. /* If query condition is include or notinclude, you should query from the title, keywords, spec, and verify of the story. */
  1594. preg_match_all("/`story`[ ]+(NOT *)?LIKE[ ]+'%([^%]*)%'/Ui", $bugQuery, $out);
  1595. if(!empty($out[2]))
  1596. {
  1597. foreach($out[2] as $searchValue)
  1598. {
  1599. $story = $this->dao->select('t1.id')->from(TABLE_STORY)->alias('t1')
  1600. ->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story')
  1601. ->where('t1.title')->like("%$searchValue%")
  1602. ->orWhere('t1.keywords')->like("%$searchValue%")
  1603. ->orWhere('t2.spec')->like("%$searchValue%")
  1604. ->orWhere('t2.verify')->like("%$searchValue%")
  1605. ->fetchPairs('id');
  1606. if(empty($story)) $story = array(0);
  1607. $searchValue = preg_quote($searchValue, '/');
  1608. $bugQuery = preg_replace("/`story`[ ]+(NOT[ ]*)?LIKE[ ]+'%$searchValue%'/Ui", '`story` $1 IN (' . implode(',', $story) .')', $bugQuery);
  1609. }
  1610. }
  1611. $bugQuery .= ' AND `story` != 0';
  1612. }
  1613. return $bugQuery;
  1614. }
  1615. /**
  1616. * 获取产品 bugs。
  1617. * Get product bugs.
  1618. *
  1619. * @param array $productIdList
  1620. * @param string $type
  1621. * @param string $begin
  1622. * @param string $end
  1623. * @access public
  1624. * @return array
  1625. */
  1626. public function getProductBugs($productIdList, $type = '', $begin = '', $end = '')
  1627. {
  1628. return $this->dao->select('*')->from(TABLE_BUG)
  1629. ->where('product')->in($productIdList)
  1630. ->beginIF($type == 'resolved')->andWhere('resolvedDate')->ge($begin)->andWhere('resolvedDate')->le("{$end} 23:59:59")->fi()
  1631. ->beginIF($type == 'opened')->andWhere('openedDate')->ge($begin)->andWhere('openedDate')->le("{$end} 23:59:59")->fi()
  1632. ->andWhere('deleted')->eq(0)
  1633. ->fetchAll('id', false);
  1634. }
  1635. /**
  1636. * 获取重新激活的 bugs。
  1637. * Get activated bugs.
  1638. *
  1639. * @param array $productIdList
  1640. * @param string $begin
  1641. * @param string $end
  1642. * @param array $buildIdList
  1643. * @access public
  1644. * @return array
  1645. */
  1646. public function getActivatedBugs($productIdList, $begin, $end, $buildIdList)
  1647. {
  1648. /* Get all bugs in the builds of products. */
  1649. $buildBugs = array();
  1650. $allBugs = $this->getProductBugs($productIdList);
  1651. foreach($allBugs as $bug)
  1652. {
  1653. $intersect = array_intersect(explode(',', $bug->openedBuild), $buildIdList);
  1654. if(!empty($intersect)) $buildBugs[$bug->id] = $bug;
  1655. }
  1656. /* Get bug reactivated actions during the testreport. */
  1657. $actions = $this->dao->select('id,objectID')->from(TABLE_ACTION)
  1658. ->where('objectType')->eq('bug')
  1659. ->andWhere('action')->eq('activated')
  1660. ->andWhere('date')->ge($begin)
  1661. ->andWhere('date')->le($end . ' 23:59:59')
  1662. ->andWhere('objectID')->in(array_keys($buildBugs))
  1663. ->fetchPairs();
  1664. $histories = $this->loadModel('action')->getHistory(array_keys($actions));
  1665. foreach($actions as $actionID => $bugID) $bugActions[$bugID][$actionID] = zget($histories, $actionID, array());
  1666. /* Get reactivated bugs. */
  1667. $activatedBugs = array();
  1668. foreach($buildBugs as $bug)
  1669. {
  1670. if(empty($bugActions[$bug->id])) continue;
  1671. foreach($bugActions[$bug->id] as $actionID => $histories)
  1672. {
  1673. foreach($histories as $history)
  1674. {
  1675. if($history->field == 'openedBuild' && !in_array($history->new, $buildIdList)) continue;
  1676. $activatedBugs[$bug->id] = $bug;
  1677. }
  1678. }
  1679. }
  1680. return $activatedBugs;
  1681. }
  1682. /**
  1683. * Get related objects id lists.
  1684. *
  1685. * @param string $object
  1686. * @param string $pairs
  1687. * @access public
  1688. * @return void
  1689. */
  1690. public function getRelatedObjects($object, $pairs = '')
  1691. {
  1692. /* Get bugs. */
  1693. $bugs = $this->loadModel('transfer')->getQueryDatas('bug');
  1694. /* Get related objects id lists. */
  1695. $relatedObjectIdList = array();
  1696. $relatedObjects = array();
  1697. foreach($bugs as $bug)
  1698. {
  1699. if(is_numeric($bug->$object)) $relatedObjectIdList[$bug->$object] = $bug->$object;
  1700. }
  1701. if($object == 'openedBuild' or $object == 'resolvedBuild') $object = 'build';
  1702. /* Get related objects title or names. */
  1703. $table = $this->config->objectTables[$object];
  1704. if($table) $relatedObjects = $this->dao->select($pairs)->from($table)->where('id')->in($relatedObjectIdList)->fetchPairs();
  1705. if(in_array($object, array('build','resolvedBuild'))) $relatedObjects = array('trunk' => $this->lang->trunk) + $relatedObjects;
  1706. return array('' => '', 0 => '') + $relatedObjects;
  1707. }
  1708. /**
  1709. * 判断当前动作是否可以点击。
  1710. * Adjust the action is clickable.
  1711. *
  1712. * @param string $bug
  1713. * @param string $action
  1714. * @param string $module
  1715. * @access public
  1716. * @return bool
  1717. * @param object $object
  1718. */
  1719. public static function isClickable($object, $action, $module = 'bug')
  1720. {
  1721. global $config, $app;
  1722. $action = strtolower($action);
  1723. /* 如果bug状态是激活,没有确认过,这个bug可以被确认。 */
  1724. /* If the status is active, and the confirmed is 0, the bug can be confirm. */
  1725. if($module == 'bug' && $action == 'confirm') return $object->status == 'active' && $object->confirmed == 0;
  1726. /* 如果bug不是关闭状态,这个bug可以被指派。 */
  1727. /* If the status isn't closed, the bug can be assginTo. */
  1728. if($module == 'bug' && $action == 'assignto') return $object->status != 'closed';
  1729. /* 如果bug是激活状态,这个bug可以被解决。 */
  1730. /* If the status is active, the bug can be resolve. */
  1731. if($module == 'bug' && $action == 'resolve') return $object->status == 'active';
  1732. /* 如果bug是解决状态,这个bug可以被关闭。 */
  1733. /* If the status is resolved, the bug can be close. */
  1734. if($module == 'bug' && $action == 'close') return $object->status == 'resolved';
  1735. /* 如果bug不是激活状态,这个bug可以被激活。 */
  1736. /* If the status isn't active, the bug can be activate. */
  1737. if($module == 'bug' && $action == 'activate') return $object->status != 'active';
  1738. /* 如果bug是激活状态,这个bug可以被转为需求。 */
  1739. /* If the status is active, the bug can be toStory. */
  1740. if($module == 'bug' && $action == 'tostory') return $object->status == 'active';
  1741. /* 判断反馈转bug操作按钮的权限。 */
  1742. /* check feedback toStory priv. */
  1743. if($module == 'bug' && $action == 'create' && isset($object->solution)) return ($config->global->flow == 'full' || $config->global->flow == 'onlyTest') && strpos('closed|clarify|noreview', $object->status) === false;
  1744. /* 判断确认撤销操作按钮的权限。 */
  1745. /* Check confirmdemandretract priv. */
  1746. if($module == 'bug' && $action == 'confirmdemandretract') return !empty($object->confirmeActionType) && $object->confirmeActionType == 'confirmedretract';
  1747. /* 判断确认移除操作按钮的权限。 */
  1748. /* Check confirmdemandunlink priv. */
  1749. if($module == 'bug' && $action == 'confirmdemandunlink') return !empty($object->confirmeActionType) && $object->confirmeActionType == 'confirmedunlink';
  1750. return true;
  1751. }
  1752. /**
  1753. * Get report condition from session.
  1754. *
  1755. * @access public
  1756. * @return void
  1757. */
  1758. public function reportCondition()
  1759. {
  1760. if(isset($_SESSION['bugQueryCondition']))
  1761. {
  1762. if(!$this->session->bugOnlyCondition) return 'id in (' . preg_replace('/SELECT .* FROM/', 'SELECT t1.id FROM', $this->session->bugQueryCondition) . ')';
  1763. return $this->session->bugQueryCondition;
  1764. }
  1765. return '1=1';
  1766. }
  1767. /**
  1768. * Link bug to build and release
  1769. *
  1770. * @param int $bugID
  1771. * @param int|string $resolvedBuild
  1772. * @access public
  1773. * @return bool
  1774. */
  1775. public function linkBugToBuild($bugID, $resolvedBuild)
  1776. {
  1777. /* 如果版本为空,或者版本为主干,返回。 */
  1778. /* If resolved build is empty or resolved build is trunk, return true. */
  1779. if(empty($resolvedBuild) || $resolvedBuild == 'trunk') return true;
  1780. /* 获取版本信息,并且将bugs关联到版本。 */
  1781. /* Get build information, and relate the bugs to the build. */
  1782. $build = $this->dao->select('id,product,bugs')->from(TABLE_BUILD)->where('id')->eq($resolvedBuild)->fetch();
  1783. $buildBugs = $build->bugs . ',' . $bugID;
  1784. $buildBugs = explode(',', trim($buildBugs, ','));
  1785. $buildBugs = array_unique($buildBugs);
  1786. $this->dao->update(TABLE_BUILD)->set('bugs')->eq(implode(',', $buildBugs))->where('id')->eq($resolvedBuild)->exec();
  1787. /* 将bugs关联到版本关联的发布。 */
  1788. /* Relate bugs to the build-related release. */
  1789. $this->loadModel('release');
  1790. $release = $this->dao->select('id,bugs')->from(TABLE_RELEASE)->where('product')->eq($build->product)->andWhere("(FIND_IN_SET('$resolvedBuild', build) or shadow = $resolvedBuild)")->andWhere('deleted')->eq('0')->fetch();
  1791. if($release)
  1792. {
  1793. $releaseBugs = $release->bugs . ',' . $bugID;
  1794. $releaseBugs = explode(',', trim($releaseBugs, ','));
  1795. $releaseBugs = array_unique($releaseBugs);
  1796. $this->dao->update(TABLE_RELEASE)->set('bugs')->eq(implode(',', $releaseBugs))->where('id')->eq($release->id)->exec();
  1797. $this->release->updateRelated($release->id, 'bug', $releaseBugs);
  1798. }
  1799. return true;
  1800. }
  1801. /**
  1802. * 更新相关 bug。
  1803. * Update the related bug.
  1804. *
  1805. * @param int $bugID
  1806. * @param string $relatedBug
  1807. * @param string $oldRelatedBug
  1808. * @access public
  1809. * @return bool
  1810. */
  1811. public function updateRelatedBug($bugID, $relatedBug, $oldRelatedBug)
  1812. {
  1813. /* 获取需要修改的相关 bug。 */
  1814. /* Get related bugs that need to change. */
  1815. $relatedBugs = explode(',', $relatedBug);
  1816. $oldRelatedBugs = explode(',', $oldRelatedBug);
  1817. $addedRelatedBugs = array_diff($relatedBugs, $oldRelatedBugs);
  1818. $removedRelatedBugs = array_diff($oldRelatedBugs, $relatedBugs);
  1819. $changedRelatedBugs = array_merge($addedRelatedBugs, $removedRelatedBugs);
  1820. $changedRelatedBugs = $this->dao->select('id, relatedBug')->from(TABLE_BUG)->where('id')->in(array_filter($changedRelatedBugs))->fetchPairs();
  1821. /* 更新相关 bug。 */
  1822. /* Update the related bug. */
  1823. foreach($changedRelatedBugs as $changedBugID => $relatedBugs)
  1824. {
  1825. if(in_array($changedBugID, $addedRelatedBugs))
  1826. {
  1827. $relatedBugs = explode(',', $relatedBugs);
  1828. if(!empty($relatedBugs) && !in_array($bugID, $relatedBugs)) $relatedBugs[] = $bugID;
  1829. }
  1830. else
  1831. {
  1832. $relatedBugs = explode(',', $relatedBugs);
  1833. unset($relatedBugs[array_search($bugID, $relatedBugs)]);
  1834. }
  1835. $currentRelatedBug = implode(',', array_filter($relatedBugs));
  1836. $this->dao->update(TABLE_BUG)->set('relatedBug')->eq($currentRelatedBug)->where('id')->eq($changedBugID)->exec();
  1837. }
  1838. return !dao::isError();
  1839. }
  1840. /**
  1841. * 获取指派用户和抄送给用户列表。
  1842. * Get toList and ccList.
  1843. *
  1844. * @param object $bug
  1845. * @access public
  1846. * @return array|false
  1847. */
  1848. public function getToAndCcList($bug)
  1849. {
  1850. /* Set toList and ccList. */
  1851. $toList = $bug->assignedTo ? $bug->assignedTo : '';
  1852. $ccList = trim((string)$bug->mailto, ',');
  1853. if(empty($toList))
  1854. {
  1855. if(empty($ccList)) return false;
  1856. if(strpos($ccList, ',') === false)
  1857. {
  1858. $toList = $ccList;
  1859. $ccList = '';
  1860. }
  1861. else
  1862. {
  1863. $commaPos = strpos($ccList, ',');
  1864. $toList = substr($ccList, 0, $commaPos);
  1865. $ccList = substr($ccList, $commaPos + 1);
  1866. }
  1867. }
  1868. elseif($bug->status == 'closed')
  1869. {
  1870. $ccList .= ',' . $bug->resolvedBy;
  1871. }
  1872. return array($toList, $ccList);
  1873. }
  1874. /**
  1875. * Bug 列表底部的统计信息。
  1876. * The statistic summary in table footer.
  1877. *
  1878. * @param array $bugs
  1879. * @access public
  1880. * @return string
  1881. */
  1882. public function summary($bugs)
  1883. {
  1884. /* 获取为解决的bug数量。 */
  1885. /* Get unresolved bug count. */
  1886. $unresolved = 0;
  1887. foreach($bugs as $bug)
  1888. {
  1889. if($bug->status != 'resolved' && $bug->status != 'closed') $unresolved ++;
  1890. }
  1891. /* 返回bug的统计信息。 */
  1892. /* Return the statistics of the bugs. */
  1893. return sprintf($this->lang->bug->notice->summary, count($bugs), $unresolved);
  1894. }
  1895. /**
  1896. * 搜索 bug。
  1897. * Search bugs.
  1898. *
  1899. * @param string $object
  1900. * @param array|int $productIdList
  1901. * @param int|string $branch
  1902. * @param int $projectID
  1903. * @param int $executionID
  1904. * @param int $queryID
  1905. * @param string $excludeBugs
  1906. * @param string $orderBy
  1907. * @param object $pager
  1908. * @access public
  1909. * @return array
  1910. */
  1911. public function getBySearch($object = 'bug', $productIdList = array(), $branch = 0, $projectID = 0, $executionID = 0, $queryID = 0, $excludeBugs = '', $orderBy = '', $pager = null)
  1912. {
  1913. $bugQuery = $this->processSearchQuery($object, $queryID, $productIdList, (string)$branch);
  1914. return $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) AS priOrder, IF(`severity` = 0, {$this->config->maxPriValue}, `severity`) AS severityOrder, INSTR('active,resolved,closed,', status) as statusOrder")->from(TABLE_BUG)
  1915. ->where($bugQuery)
  1916. ->andWhere('deleted')->eq('0')
  1917. ->beginIF($excludeBugs)->andWhere('id')->notIN($excludeBugs)->fi()
  1918. ->beginIF($object == 'bug' && !$this->app->user->admin)
  1919. ->andWhere('project')->in('0,' . $this->app->user->view->projects)
  1920. ->andWhere('execution')->in('0,' . $this->app->user->view->sprints)
  1921. ->fi()
  1922. ->beginIF($projectID)
  1923. ->andWhere('project', true)->eq($projectID)
  1924. ->fi()
  1925. ->beginIF($projectID && $object == 'bug')
  1926. ->orWhere('project')->eq(0)
  1927. ->andWhere('openedBuild')->eq('trunk')
  1928. ->fi()
  1929. ->beginIF($projectID)
  1930. ->markRight(1)
  1931. ->fi()
  1932. ->beginIF($object == 'execution')
  1933. ->andWhere('execution')->eq($executionID)
  1934. ->fi()
  1935. ->orderBy($orderBy)
  1936. ->page($pager)
  1937. ->fetchAll('id', false);
  1938. }
  1939. /**
  1940. * 为 datatable 获取模块。
  1941. * Get modules for datatable.
  1942. *
  1943. * @param int $productID
  1944. * @access public
  1945. * @return void
  1946. */
  1947. public function getDatatableModules($productID)
  1948. {
  1949. $branches = $this->loadModel('branch')->getPairs($productID);
  1950. $modules = $this->loadModel('tree')->getOptionMenu($productID, 'bug', 0);
  1951. if(count($branches) <= 1) return $modules;
  1952. foreach($branches as $branchID => $branchName) $modules += $this->tree->getOptionMenu($productID, 'bug', 0, (string)$branchID);
  1953. return $modules;
  1954. }
  1955. /**
  1956. * 更新Bug关联的代码提交记录。
  1957. * Update the commit logs linked with the bugs.
  1958. *
  1959. * @param int $bugID
  1960. * @param int $repoID
  1961. * @param array $revisions
  1962. * @access protected
  1963. * @return bool
  1964. */
  1965. public function updateLinkedCommits($bugID, $repoID, $revisions)
  1966. {
  1967. if(!$bugID || !$repoID || empty($revisions)) return true;
  1968. $bug = $this->dao->select('product, project')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch();
  1969. if(!$bug) return true;
  1970. foreach($revisions as $revision)
  1971. {
  1972. $data = new stdclass();
  1973. $data->project = $bug->project;
  1974. $data->product = $bug->product;
  1975. $data->AType = 'bug';
  1976. $data->AID = $bugID;
  1977. $data->BType = 'commit';
  1978. $data->BID = $revision;
  1979. $data->relation = 'completedin';
  1980. $data->extra = $repoID;
  1981. $this->dao->replace(TABLE_RELATION)->data($data)->autoCheck()->exec();
  1982. $data->AType = 'commit';
  1983. $data->AID = $revision;
  1984. $data->BType = 'bug';
  1985. $data->BID = $bugID;
  1986. $data->relation = 'completedfrom';
  1987. $this->dao->replace(TABLE_RELATION)->data($data)->autoCheck()->exec();
  1988. }
  1989. return !dao::isError();
  1990. }
  1991. /**
  1992. * 获取Bug关联的提交数据。
  1993. * Get the commit data for the associated bugs
  1994. * @param int $repoID
  1995. * @param array $revisions
  1996. * @access protected
  1997. * @return bool
  1998. */
  1999. public function getLinkedCommits($repoID, $revisions)
  2000. {
  2001. return $this->dao->select('t1.revision,t3.id AS id,t3.title AS title')
  2002. ->from(TABLE_REPOHISTORY)->alias('t1')
  2003. ->leftJoin(TABLE_RELATION)->alias('t2')->on("t2.relation='completedin' AND t2.BType='commit' AND t2.BID=t1.id")
  2004. ->leftJoin(TABLE_BUG)->alias('t3')->on("t2.AType='bug' AND t2.AID=t3.id")
  2005. ->where('t1.revision')->in($revisions)
  2006. ->andWhere('t1.repo')->eq($repoID)
  2007. ->andWhere('t3.id')->notNULL()
  2008. ->fetchGroup('revision', 'id');
  2009. }
  2010. /**
  2011. * 通过任务ID获取相关的Bug
  2012. * @param int $taskID
  2013. *
  2014. * @access public
  2015. * @return array|false
  2016. */
  2017. public function getLinkedBugsByTaskID($taskID)
  2018. {
  2019. return $this->dao->select('t1.id,t1.title')
  2020. ->from(TABLE_BUG)->alias('t1')
  2021. ->leftJoin(TABLE_TASK)->alias('t2')->on('t1.task=t2.id')
  2022. ->where('t2.id')->eq($taskID)
  2023. ->andWhere('t1.deleted')->eq(0)
  2024. ->andWhere('t2.deleted')->eq(0)
  2025. ->fetchAll('id');
  2026. }
  2027. }