| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274 |
- <?php
- /**
- * The model file of bug module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
- * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Chunsheng Wang <chunsheng@cnezsoft.com>
- * @package bug
- * @version $Id: model.php 5079 2013-07-10 00:44:34Z chencongzhi520@gmail.com $
- * @link https://www.zentao.net
- */
- ?>
- <?php
- class bugModel extends model
- {
- /**
- * bug 的入库操作。
- * Insert bug into zt_bug.
- *
- * @param object $bug
- * @param string $from
- * @access public
- * @return int|false
- */
- public function create($bug, $from = '')
- {
- $this->dao->insert(TABLE_BUG)->data($bug, 'laneID,uploadImage,imageFile')
- ->autoCheck()
- ->checkIF(!empty($bug->notifyEmail), 'notifyEmail', 'email')
- ->batchCheck($this->config->bug->create->requiredFields, 'notempty')
- ->checkFlow()
- ->exec();
- if(dao::isError()) return false;
- $bugID = $this->dao->lastInsertID();
- $action = $from == 'sonarqube' ? 'fromSonarqube' : 'Opened';
- $this->loadModel('action')->create('bug', $bugID, $action);
- if(!empty($bug->assignedTo)) $this->action->create('bug', $bugID, 'Assigned', '', $bug->assignedTo);
- /* Add score for create. */
- $this->loadModel('file')->saveUpload('bug', $bugID);
- if(!empty($bug->case))
- {
- $this->loadModel('score')->create('bug', 'createFormCase', $bug->case);
- }
- else
- {
- $this->loadModel('score')->create('bug', 'create', $bugID);
- }
- return $bugID;
- }
- /**
- * Gitlab 问题转为 bug。
- * Create bug from gitlab issue.
- *
- * @param object $bug
- * @param int $executionID
- * @access public
- * @return int|bool
- */
- public function createBugFromGitlabIssue($bug, $executionID)
- {
- $bug->openedBy = $this->app->user->account;
- $bug->openedDate = helper::now();
- $bug->assignedDate = isset($bug->assignedTo) ? helper::now() : null;
- $bug->openedBuild = 'trunk';
- $bug->story = 0;
- $bug->task = 0;
- $bug->pri = 3;
- $bug->severity = 3;
- $bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
- $this->dao->insert(TABLE_BUG)->data($bug, $skip = 'gitlab,gitlabProject')->autoCheck()->batchCheck($this->config->bug->create->requiredFields, 'notempty')->exec();
- if(!dao::isError()) return $this->dao->lastInsertID();
- return false;
- }
- /**
- * 根据浏览类型获取 bug 列表。
- * Get bug list by browse type.
- *
- * @param string $browseType
- * @param array $productIdList
- * @param int $projectID
- * @param int[] $executionIdList
- * @param int|string $branch
- * @param int $moduleID
- * @param int $queryID
- * @param string $orderBy
- * @param object $pager
- * @access public
- * @return array
- */
- public function getList($browseType, $productIdList, $projectID, $executionIdList, $branch = 'all', $moduleID = 0, $queryID = 0, $orderBy = 'id_desc', $pager = null)
- {
- if($browseType == 'bymodule' && $this->session->bugBrowseType && $this->session->bugBrowseType != 'bysearch') $browseType = $this->session->bugBrowseType;
- if(!in_array($browseType, $this->config->bug->browseTypeList)) return array();
- /* 处理排序。*/
- /* Process sort field. */
- if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
- if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
- $modules = $moduleID ? $this->loadModel('tree')->getAllChildID($moduleID) : array();
- $bugList = $this->bugTao->getListByBrowseType($browseType, $productIdList, $projectID, $executionIdList, $branch, $modules, $queryID, $orderBy, $pager);
- return $this->bugTao->batchAppendDelayedDays($bugList);
- }
- /**
- * 获取计划关联的 bugs。
- * Get bug list of a plan.
- *
- * @param int $planID
- * @param string $status
- * @param string $orderBy
- * @param object $pager
- * @access public
- * @return array
- */
- public function getPlanBugs($planID, $status = 'all', $orderBy = 'id_desc', $pager = null)
- {
- if(common::isTutorialMode()) return array();
- if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
- $bugs = $this->dao->select("*, IF(`pri` = 0, {$this->config->maxPriValue}, `pri`) AS priOrder")->from(TABLE_BUG)
- ->where('plan')->eq($planID)
- ->beginIF(!$this->app->user->admin)->andWhere('execution')->in('0,' . $this->app->user->view->sprints)->fi()
- ->beginIF($status != 'all')->andWhere('status')->in($status)->fi()
- ->andWhere('deleted')->eq(0)
- ->orderBy($orderBy)->page($pager)
- ->fetchAll('id', false);
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug');
- return $bugs;
- }
- /**
- * 获取 bug。
- * Get info of a bug.
- *
- * @param int $bugID
- * @param bool $setImgSize
- * @access public
- * @return object|false
- */
- public function getByID($bugID, $setImgSize = false)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBug();
- $bug = $this->bugTao->fetchBugInfo($bugID);
- if(!$bug) return false;
- $bug = $this->loadModel('file')->replaceImgURL($bug, 'steps');
- if($setImgSize) $bug->steps = $this->file->setImgSize($bug->steps);
- if($bug->project) $bug->projectName = $this->bugTao->getNameFromTable($bug->project, TABLE_PROJECT, 'name');
- if($bug->duplicateBug) $bug->duplicateBugTitle = $this->bugTao->getNameFromTable($bug->duplicateBug, TABLE_BUG, 'title');
- if($bug->case) $bug->caseTitle = $this->bugTao->getNameFromTable($bug->case, TABLE_CASE, 'title');
- if($bug->toStory) $bug->toStoryTitle = $this->bugTao->getNameFromTable($bug->toStory, TABLE_STORY, 'title');
- if($bug->toTask) $bug->toTaskTitle = $this->bugTao->getNameFromTable($bug->toTask, TABLE_TASK, 'name');
- if($bug->relatedBug) $bug->relatedBugTitles = $this->bugTao->getBugPairsByList($bug->relatedBug);
- if(in_array($this->config->edition, array('max', 'ipd')))
- {
- $identifyList = $this->loadModel('review')->getPairs($bug->project, $bug->product, true);
- $bug->injectionTitle = is_numeric($bug->injection) ? zget($identifyList, $bug->injection, '') : zget($this->lang->bug->injectionList, $bug->injection, '');
- $bug->identifyTitle = is_numeric($bug->identify) ? zget($identifyList, $bug->identify, '') : zget($this->lang->bug->identifyList, $bug->identify, '');
- }
- $bug->linkMRTitles = $this->loadModel('mr')->getLinkedMRPairs($bugID, 'bug');
- $bug->toCases = $this->bugTao->getCasesFromBug($bugID);
- $bug->files = $this->file->getByObject('bug', $bugID);
- return $this->bugTao->appendDelayedDays($bug);
- }
- /**
- * 获取指定字段的 bug 列表。
- * Get bugs by ID list.
- *
- * @param int|array|string $bugIdList
- * @param string $fields
- * @param string $orderBy
- * @access public
- * @return array
- */
- public function getByIdList($bugIdList = 0, $fields = '*', $orderBy = '')
- {
- return $this->dao->select($fields)->from(TABLE_BUG)
- ->where('deleted')->eq('0')
- ->beginIF($bugIdList)->andWhere('id')->in($bugIdList)->fi()
- ->beginIF($orderBy)->orderBy($orderBy)->fi()
- ->fetchAll('id');
- }
- /**
- * 获取激活的未转为 bug 和任务的 bugs。
- * Get active bugs.
- *
- * @param array|int $products
- * @param int|string $branch
- * @param string $executions
- * @param array $excludeBugs
- * @param object $pager
- * @param string $orderBy
- * @access public
- * @return array
- */
- public function getActiveBugs($products, $branch, $executions, $excludeBugs, $pager = null, $orderBy = 'id desc')
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
- return $this->dao->select('*')->from(TABLE_BUG)
- ->where('status')->eq('active')
- ->andWhere('toStory')->eq(0)
- ->andWhere('toTask')->eq(0)
- ->beginIF(!empty($products))->andWhere('product')->in($products)->fi()
- ->beginIF($branch !== '' and $branch !== 'all')->andWhere('branch')->in("0,$branch")->fi()
- ->beginIF(!empty($executions))->andWhere('execution')->in($executions)->fi()
- ->beginIF($excludeBugs)->andWhere('id')->notIN($excludeBugs)->fi()
- ->andWhere('deleted')->eq(0)
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 获取激活和延期处理的 bug 列表。
- * Get active and postponed bugs.
- *
- * @param array $products
- * @param int $executionID
- * @param object $pager
- * @access public
- * @return array
- */
- public function getActiveAndPostponedBugs($products, $executionID, $pager = null)
- {
- return $this->dao->select('t1.*')->from(TABLE_BUG)->alias('t1')
- ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.product = t2.product')
- ->where("((t1.status = 'resolved' AND t1.resolution = 'postponed') OR (t1.status = 'active'))")
- ->andWhere('t1.toTask')->eq(0)
- ->andWhere('t1.toStory')->eq(0)
- ->beginIF(!empty($products))->andWhere('t1.product')->in($products)->fi()
- ->beginIF(empty($products))->andWhere('t1.execution')->eq($executionID)->fi()
- ->andWhere('t2.project')->eq($executionID)
- ->andWhere("(t2.branch = '0' OR t1.branch = '0' OR t2.branch = t1.branch)")
- ->andWhere('t1.deleted')->eq('0')
- ->orderBy('id desc')
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 获取模块的负责人。
- * Get the owner of module.
- *
- * @param int $moduleID
- * @param int $productID
- * @access public
- * @return array
- */
- public function getModuleOwner($moduleID, $productID)
- {
- $users = $this->loadModel('user')->getPairs('nodeleted');
- /* 获取所属产品的测试负责人。*/
- /* Return the QD of the product. */
- $account = $this->dao->findByID($productID)->from(TABLE_PRODUCT)->fetch('QD');
- $account = isset($users[$account]) ? $account : '';
- $realname = zget($users, $account, '');
- /* 如果没有模块 ID,直接返回测试负责人。*/
- if(!$moduleID) return array($account, $realname);
- /* 获取模块,如果模块为空,直接返回测试负责人。*/
- $module = $this->dao->findByID($moduleID)->from(TABLE_MODULE)->andWhere('root')->eq($productID)->fetch();
- if(empty($module)) return array($account, $realname);
- /* 如果模块有负责人返回模块负责人。*/
- if($module->owner && isset($users[$module->owner])) return array($module->owner, $users[$module->owner]);
- /* 获取除了模块ID以外的模块的路径,如果没有其他路径,返回测试负责人。*/
- $moduleIdList = explode(',', trim(str_replace(",$module->id,", ',', $module->path), ','));
- if(!$moduleIdList) return array($account, $realname);
- /* 从上级到下级,如果有模块有负责人,返回模块负责人。*/
- krsort($moduleIdList);
- $modules = $this->dao->select('id,owner')->from(TABLE_MODULE)->where('id')->in($moduleIdList)->andWhere('deleted')->eq('0')->fetchAll('id');
- foreach($modules as $module)
- {
- if($module->owner && isset($users[$module->owner])) return array($module->owner, $users[$module->owner]);
- }
- return array($account, $realname);
- }
- /**
- * 更新 bug 信息。
- * Update a bug.
- *
- * @param object $bug
- * @param string $action
- * @access public
- * @return array|false
- */
- public function update($bug, $action = 'Edited')
- {
- $oldBug = $this->fetchByID($bug->id);
- $oldBug->files = $this->loadModel('file')->getByObject('bug', $bug->id);
- $this->dao->update(TABLE_BUG)->data($bug, 'deleteFiles,renameFiles,files,comment')
- ->autoCheck()
- ->batchCheck($this->config->bug->edit->requiredFields, 'notempty')
- ->checkIF(!empty($bug->resolvedBy), 'resolution', 'notempty')
- ->checkIF(!empty($bug->closedBy), 'resolution', 'notempty')
- ->checkIF(!empty($bug->notifyEmail),'notifyEmail', 'email')
- ->checkIF(!empty($bug->resolution) && $bug->resolution == 'duplicate', 'duplicateBug', 'notempty')
- ->checkIF(!empty($bug->resolution) && $bug->resolution == 'fixed', 'resolvedBuild','notempty')
- ->checkFlow()
- ->where('id')->eq($bug->id)
- ->exec();
- if(dao::isError()) return false;
- /* 更新 bug 的附件。*/
- /* Update the files of bug. */
- $this->file->processFileDiffsForObject('bug', $oldBug, $bug);
- $changes = common::createChanges($oldBug, $bug);
- if($changes || !empty($bug->comment))
- {
- $actionID = $this->loadModel('action')->create('bug', $bug->id, $changes ? $action : 'Commented', zget($bug, 'comment', ''));
- if($changes) $this->action->logHistory($actionID, $changes);
- }
- if($this->config->edition != 'open' && $this->app->rawMethod != 'batchedit')
- {
- if($oldBug->story > 0 || $oldBug->task > 0 || $oldBug->case > 0)
- {
- $AID = $oldBug->story > 0 ? ($oldBug->task > 0 ? "{$oldBug->story},{$oldBug->task}" : $oldBug->story) : $oldBug->task;
- $AID .= $oldBug->case > 0 ? ",{$oldBug->case}" : '';
- $AType = $oldBug->story > 0 ? ($oldBug->task > 0 ? 'story,task' : 'story') : 'task';
- $AType .= $oldBug->case > 0 ? ",testcase" : '';
- $this->dao->delete()->from(TABLE_RELATION)
- ->where('relation')->eq('generated')
- ->andWhere('AID')->in($AID)
- ->andWhere('AType')->in($AType)
- ->andWhere('BID')->eq($oldBug->id)
- ->andWhere('BType')->eq('bug')
- ->exec();
- }
- if(zget($bug, 'story', 0) > 0 || zget($bug, 'task', 0) > 0 || zget($bug, 'case', 0) > 0)
- {
- $relation = new stdClass();
- $relation->relation = 'generated';
- $relation->BID = $bug->id;
- $relation->BType = 'bug';
- $relation->product = 0;
- if(!empty($bug->story))
- {
- $relation->AID = $bug->story;
- $relation->AType = 'story';
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- }
- if(!empty($bug->task))
- {
- $relation->AID = $bug->task;
- $relation->AType = 'task';
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- }
- if(!empty($bug->case))
- {
- $relation->AID = $bug->case;
- $relation->AType = 'testcase';
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- }
- }
- }
- if(dao::isError()) return false;
- return $changes;
- }
- /**
- * 将 bug 指派给一个用户。
- * Assign a bug to a user.
- *
- * @param object $bug
- * @param object $oldBug
- * @access public
- * @return bool
- */
- public function assign($bug, $oldBug)
- {
- $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->andWhere('status')->ne('closed')->exec();
- if(dao::isError()) return false;
- /* 记录指派动作。*/
- /* Record log. */
- $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Assigned', $bug->comment, $bug->assignedTo);
- $changes = common::createChanges($oldBug, $bug);
- if($changes) $this->action->logHistory($actionID, $changes);
- return !dao::isError();
- }
- /**
- * 确认 bug。
- * Confirm a bug.
- *
- * @param object $bug
- * @param array $kanbanData
- * @access public
- * @return bool
- */
- public function confirm($bug, $kanbanData = array())
- {
- $oldBug = $this->getByID($bug->id);
- $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
- if(dao::isError()) return false;
- /* 确认 bug 后的积分变动。*/
- /* Record score after confirming bug. */
- $this->loadModel('score')->create('bug', 'confirm', $oldBug);
- /* 如果 bug 有所属执行,更新看板数据。*/
- /* Update kanban if the bug has execution. */
- if($oldBug->execution)
- {
- $this->loadModel('kanban');
- if(!isset($kanbanData['toColID'])) $this->kanban->updateLane($oldBug->execution, 'bug', $oldBug->id);
- 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);
- }
- /* 记录历史记录。*/
- /* Record history. */
- $changes = common::createChanges($oldBug, $bug);
- $actionID = $this->loadModel('action')->create('bug', $oldBug->id, 'bugConfirmed', $bug->comment);
- if($changes) $this->action->logHistory($actionID, $changes);
- return !dao::isError();
- }
- /**
- * 解决一个bug。
- * Resolve a bug.
- *
- * @param object $bug
- * @param array $output
- * @access public
- * @return bool
- */
- public function resolve($bug, $output = array())
- {
- /* Get old bug. */
- $oldBug = $this->getById($bug->id);
- if(!empty($bug->duplicateBug))
- {
- $duplicateBug = $this->fetchByID($bug->duplicateBug);
- if(!$duplicateBug || $duplicateBug->deleted == '1')
- {
- dao::$errors['duplicateBug'][] = $this->lang->bug->error->duplicateBugNotExist;
- return false;
- }
- }
- /* Update bug. */
- $this->dao->update(TABLE_BUG)->data($bug, 'buildName,createBuild,buildExecution,comment')
- ->autoCheck()
- ->batchCheck($this->config->bug->resolve->requiredFields, 'notempty')
- ->checkIF($bug->resolution == 'duplicate', 'duplicateBug', 'notempty')
- ->checkIF($bug->resolution == 'fixed', 'resolvedBuild','notempty')
- ->checkFlow()
- ->where('id')->eq($bug->id)
- ->exec();
- if(dao::isError()) return false;
- /* Add score. */
- $this->loadModel('score')->create('bug', 'resolve', $oldBug);
- /* Move bug card in kanban. */
- if($oldBug->execution)
- {
- if(!isset($output['toColID'])) $this->loadModel('kanban')->updateLane($oldBug->execution, 'bug', $bug->id);
- if(isset($output['toColID'])) $this->loadModel('kanban')->moveCard((int)$bug->id, (int)$output['fromColID'], (int)$output['toColID'], (int)$output['fromLaneID'], (int)$output['toLaneID']);
- }
- /* Link bug to build and release. */
- $this->linkBugToBuild($bug->id, $bug->resolvedBuild);
- /* Save files and record log. */
- $files = $this->loadModel('file')->saveUpload('bug', $bug->id);
- $fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
- $changes = common::createChanges($oldBug, $bug);
- $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Resolved', $fileAction . (!empty($bug->comment) ? $this->post->comment : ''), $bug->resolution . (isset($bug->duplicateBug) ? ':' . $bug->duplicateBug : ''));
- if($changes) $this->action->logHistory($actionID, $changes);
- /* If the edition is not pms, update feedback. */
- if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
- return !dao::isError();
- }
- /**
- * 在解决bug的时候创建一个版本。
- * Create build when resolving a bug.
- *
- * @param object $bug
- * @param object $oldBug
- * @access public
- * @return bool
- */
- public function createBuild($bug, $oldBug)
- {
- /* Construct build data. */
- $buildData = new stdclass();
- $buildData->product = (int)$oldBug->product;
- $buildData->branch = (int)$oldBug->branch;
- $buildData->project = $bug->buildExecution ? $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($bug->buildExecution)->fetch('project') : 0;
- $buildData->execution = $bug->buildExecution;
- $buildData->name = $bug->buildName;
- $buildData->date = date('Y-m-d');
- $buildData->builder = $this->app->user->account;
- $buildData->createdBy = $this->app->user->account;
- $buildData->createdDate = helper::now();
- /* Create a build. */
- $this->lang->build->name = $this->lang->bug->placeholder->newBuildName;
- $this->dao->insert(TABLE_BUILD)->data($buildData)->autoCheck()
- ->check('name', 'unique', "product = {$buildData->product} AND branch = {$buildData->branch} AND deleted = '0'")
- ->batchCheck('name,execution', 'notempty')
- ->exec();
- if(dao::isError())
- {
- $error = dao::getError();
- if(isset($error['name']))
- {
- $error['buildName'] = $error['name'];
- unset($error['name']);
- }
- if(isset($error['execution']))
- {
- $executionLang = $this->lang->bug->execution;
- if($oldBug->execution)
- {
- $execution = $this->dao->findByID($oldBug->execution)->from(TABLE_EXECUTION)->fetch();
- if($execution and $execution->type == 'kanban') $executionLang = $this->lang->bug->kanban;
- }
- $error['buildExecution'] = sprintf($this->lang->error->notempty, $executionLang);
- unset($error['execution']);
- }
- dao::$errors = $error;
- return false;
- }
- /* Get build id, and record log. */
- $buildID = $this->dao->lastInsertID();
- $this->loadModel('action')->create('build', $buildID, 'opened');
- $bug->resolvedBuild = $buildID;
- return !dao::isError();
- }
- /**
- * 激活一个bug。
- * Activate a bug.
- *
- * @param object $bug
- * @param array $kanbanParams
- * @access public
- * @return bool
- */
- public function activate($bug, $kanbanParams = array())
- {
- $oldBug = parent::fetchByID($bug->id);
- $this->dao->update(TABLE_BUG)->data($bug, 'comment')->check('openedBuild', 'notempty')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
- if(dao::isError()) return false;
- /* Update build. */
- $solveBuild = $this->dao->select('id, bugs')->from(TABLE_BUILD)->where("FIND_IN_SET('{$bug->id}', bugs)")->limit(1)->fetch();
- if($solveBuild)
- {
- $buildBugs = trim(str_replace(",{$bug->id},", ',', ",$solveBuild->bugs,"), ',');
- $this->dao->update(TABLE_BUILD)->set('bugs')->eq($buildBugs)->where('id')->eq($solveBuild->id)->exec();
- }
- /* Update kanban. */
- if($oldBug->execution)
- {
- $this->loadModel('kanban');
- if(!isset($kanbanParams['toColID'])) $this->kanban->updateLane($oldBug->execution, 'bug', $bug->id);
- if(isset($kanbanParams['toColID'])) $this->kanban->moveCard($bug->id, $kanbanParams['fromColID'], $kanbanParams['toColID'], $kanbanParams['fromLaneID'], $kanbanParams['toLaneID']);
- }
- $changes = common::createChanges($oldBug, $bug);
- $files = $this->loadModel('file')->saveUpload('bug', $bug->id);
- if($changes || $files)
- {
- $fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
- $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Activated', $fileAction . $bug->comment);
- $this->action->logHistory($actionID, $changes);
- }
- if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
- return !dao::isError();
- }
- /**
- * 关闭一个bug。
- * Close a bug.
- *
- * @param object $bug
- * @param array $output
- * @access public
- * @return bool
- */
- public function close($bug, $output = array())
- {
- $oldBug = $this->getById($bug->id);
- $bug = $this->loadModel('file')->processImgURL($bug, $this->config->bug->editor->close['id'], $this->post->uid);
- $this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
- if(dao::isError()) return false;
- if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
- $changes = common::createChanges($oldBug, $bug);
- $actionID = $this->loadModel('action')->create('bug', $bug->id, 'Closed', $this->post->comment);
- if($changes) $this->action->logHistory($actionID, $changes);
- if($oldBug->execution)
- {
- $this->loadModel('kanban');
- if(!isset($output['toColID'])) $this->kanban->updateLane($oldBug->execution, 'bug', $bug->id);
- if(isset($output['toColID'])) $this->kanban->moveCard($bug->id, $output['fromColID'], $output['toColID'], $output['fromLaneID'], $output['toLaneID']);
- }
- /* 给原bug的抄送人发送完消息后,再处理它。 */
- /* After sending a message to the cc of the original bug, then process with it. */
- $this->dao->update(TABLE_BUG)->set('assignedTo')->eq('closed')->where('id')->eq($bug->id)->exec();
- return !dao::isError();
- }
- /**
- * 获取可以关联的 bug 列表。
- * Get bugs to link.
- *
- * @param int $bugID
- * @param bool $bySearch
- * @param int $queryID
- * @param string $excludeBugs
- * @param object $pager
- * @access public
- * @return array
- */
- public function getBugs2Link($bugID, $bySearch = false, $excludeBugs = '', $queryID = 0, $pager = null)
- {
- $bug = $this->getByID($bugID);
- $excludeBugs .= ",{$bug->id}";
- if($bySearch) return $this->getBySearch('bug', (array)$bug->product, 'all', 0, 0, $queryID, $excludeBugs, 'id desc', $pager);
- return $this->dao->select('*')->from(TABLE_BUG)
- ->where('deleted')->eq('0')
- ->andWhere('id')->notin($excludeBugs)
- ->andWhere('product')->eq($bug->product)
- ->beginIF($bug->project)->andWhere('project')->eq($bug->project)->fi()
- ->beginIF($bug->execution)->andWhere('execution')->eq($bug->execution)->fi()
- ->orderBy('id desc')
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * Get statistic.
- *
- * @param int $productID
- * @param string $endDate
- * @param int $days
- * @access public
- * @return void
- */
- public function getStatistic($productID = 0, $endDate = '', $days = 30)
- {
- $startDate = '';
- if(empty($endDate)) $endDate = date('Y-m-d');
- $dateArr = array();
- for($day = $days - 1; $day >= 0; $day--)
- {
- $time = strtotime(-$day . ' day', strtotime($endDate));
- $date = date('m/d', $time);
- $dateArr[$date] = new stdClass();
- $dateArr[$date]->num = 0;
- $dateArr[$date]->date = $date;
- if($day == $days -1) $startDate = date('Y-m-d', $time) . ' 00:00:00';
- }
- $dateFields = array('openedDate', 'resolvedDate', 'closedDate');
- $staticData = array();
- foreach($dateFields as $field)
- {
- $bugCount = $this->dao->select("COUNT(id) AS num, DATE_FORMAT($field, '%m/%d') AS date")->from(TABLE_BUG)
- ->where('product')->eq($productID)
- ->andWhere('deleted')->eq(0)
- ->andWhere($field)->between($startDate, $endDate . ' 23:50:59')
- ->groupBy('date')
- ->fetchAll('date');
- $staticData[$field] = array_merge($dateArr, $bugCount);
- }
- return $staticData;
- }
- /**
- * 构造搜索表单。
- * Build search form.
- *
- * @param int $productID
- * @param array $products
- * @param int $queryID
- * @param string $actionURL
- * @param string $branch
- * @access public
- * @return void
- */
- public function buildSearchForm($productID, $products, $queryID, $actionURL, $branch = '0')
- {
- if(commonModel::isTutorialMode()) return;
- if(empty($productID) && empty($products)) $products = $this->loadModel('product')->getPairs('', 0, '', 'all');
- $projectID = $this->lang->navGroup->bug == 'qa' ? 0 : $this->session->project;
- /* Get product params. */
- $productParams = ($productID && isset($products[$productID])) ? array($productID => $products[$productID]) : $products;
- $productParams = $productParams + array('all' => $this->lang->all);
- /* Get project params. */
- $projectParams = $this->loadModel('product')->getProjectPairsByProduct($productID, $branch);
- $projectParams = $projectParams + array('all' => $this->lang->bug->allProject);
- /* Get all modules. */
- $this->loadModel('tree');
- if($productID) $modules = $this->tree->getOptionMenu($productID, 'bug', 0, $branch);
- if(!$productID)
- {
- $modules = array();
- foreach($products as $id => $productName) $modules += $this->tree->getOptionMenu($id, 'bug');
- }
- $this->config->bug->search['actionURL'] = $actionURL;
- $this->config->bug->search['queryID'] = $queryID;
- $this->config->bug->search['params']['project']['values'] = $projectParams;
- $this->config->bug->search['params']['product']['values'] = $productParams;
- $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productID, $branch, '', true);
- $this->config->bug->search['params']['module']['values'] = $modules;
- $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($productID, $branch, (int)$projectID);
- $this->config->bug->search['params']['severity']['values'] = array(0 => '') + $this->lang->bug->severityList; //Fix bug #939.
- $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($productID), 'all', 'withbranch|releasetag');
- $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
- $product = $this->loadModel('product')->fetchByID(empty($productID) ? key($products) : $productID);
- if($product->type == 'normal')
- {
- unset($this->config->bug->search['fields']['branch']);
- unset($this->config->bug->search['params']['branch']);
- }
- else
- {
- $branches = $this->loadModel('branch')->getPairs($productID, 'noempty');
- $this->config->bug->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
- $this->config->bug->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches + array('all' => $this->lang->branch->all);
- }
- $this->loadModel('search')->setSearchParams($this->config->bug->search);
- }
- /**
- * 获取Bug的搜索表单配置。
- * Get bug search config.
- *
- * @param int $productID
- * @access public
- * @return array
- */
- public function buildSearchConfig($productID)
- {
- unset($this->config->bug->search['fields']['product']);
- $this->config->bug->search['params']['project']['values'] = $this->loadModel('product')->getProjectPairsByProduct($productID, 'all');
- $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs($productID);
- $this->config->bug->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($productID, 'bug');
- $this->config->bug->search['params']['execution']['values'] = $this->loadModel('product')->getExecutionPairsByProduct($productID);
- $this->config->bug->search['params']['severity']['values'] = array(0 => '') + $this->lang->bug->severityList; //Fix bug #939.
- $this->config->bug->search['params']['openedBuild']['values'] = $this->loadModel('build')->getBuildPairs(array($productID), 'all', 'withbranch|releasetag');
- $this->config->bug->search['params']['resolvedBuild']['values'] = $this->config->bug->search['params']['openedBuild']['values'];
- $_SESSION['searchParams']['module'] = 'bug';
- $searchConfig = $this->config->bug->search;
- $searchConfig['params'] = $this->loadModel('search')->setDefaultParams('bug', $searchConfig['fields'], $searchConfig['params']);
- return $searchConfig;
- }
- /**
- * 获取 bugs 的影响版本和解决版本。
- * Process the openedBuild and resolvedBuild fields for bugs.
- *
- * @param array $bugs
- * @access public
- * @return array
- */
- public function processBuildForBugs($bugs)
- {
- $productIdList = array();
- foreach($bugs as $bug) $productIdList[$bug->product] = $bug->product;
- $builds = $this->loadModel('build')->getBuildPairs(array_unique($productIdList), 'all', 'hasdeleted');
- /* Process the openedBuild and resolvedBuild fields. */
- foreach($bugs as $bug)
- {
- $openBuildIdList = explode(',', $bug->openedBuild);
- $openedBuild = '';
- foreach($openBuildIdList as $buildID) $openedBuild .= zget($builds, $buildID) . ',';
- $bug->openedBuild = rtrim($openedBuild, ',');
- $bug->resolvedBuild = zget($builds, $bug->resolvedBuild);
- }
- return $bugs;
- }
- /**
- * 测试获取当前用户的 bugs。
- * Get user bugs.
- *
- * @param string $account
- * @param string $type
- * @param string $orderBy
- * @param int $limit
- * @param object $pager
- * @param int $executionID
- * @param int $queryID
- * @access public
- * @return array
- */
- public function getUserBugs($account, $type = 'assignedTo', $orderBy = 'id_desc', $limit = 0, $pager = null, $executionID = 0, $queryID = 0)
- {
- if($type != 'bySearch' and !$this->loadModel('common')->checkField(TABLE_BUG, $type)) return array();
- $moduleName = $this->app->rawMethod == 'work' ? 'workBug' : 'contributeBug';
- $queryName = $moduleName . 'Query';
- $formName = $moduleName . 'Form';
- if($queryID)
- {
- $query = $this->loadModel('search')->getQuery($queryID);
- if($query)
- {
- $this->session->set($queryName, $query->sql);
- $this->session->set($formName, $query->form);
- }
- else
- {
- $this->session->set($queryName, ' 1 = 1');
- }
- }
- else
- {
- if($this->session->$queryName === false) $this->session->set($queryName, ' 1 = 1');
- }
- if($moduleName == 'contributeBug') $bugsAssignedByMe = $this->loadModel('my')->getAssignedByMe($account, null, $orderBy, 'bug');
- $query = $this->session->$queryName;
- if(strpos($query, "`project` = 'all'") !== false) $query = str_replace("`project` = 'all'", '1 = 1', $query);
- $query = preg_replace('/`(\w+)`/', 't1.`$1`', $query);
- $orderBy = str_replace('id_', 't1.id_', $orderBy);
- 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")
- ->from(TABLE_BUG)->alias('t1')
- ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product=t2.id')
- ->where('t1.deleted')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->beginIF($type == 'bySearch')->andWhere($query)->fi()
- ->beginIF($executionID)->andWhere('t1.execution')->eq($executionID)->fi()
- ->beginIF($type != 'closedBy' and $this->app->moduleName == 'block')->andWhere('t1.status')->ne('closed')->fi()
- ->beginIF($type != 'all' and $type != 'bySearch')->andWhere("t1.`$type`")->eq($account)->fi()
- ->beginIF($type == 'bySearch' and $moduleName == 'workBug')->andWhere("t1.assignedTo")->eq($account)->fi()
- ->beginIF($type == 'assignedTo' and $moduleName == 'workBug')->andWhere('t1.status')->ne('closed')->fi()
- ->beginIF($type == 'bySearch' and $moduleName == 'contributeBug')
- ->andWhere('t1.openedBy', 1)->eq($account)
- ->orWhere('t1.closedBy')->eq($account)
- ->orWhere('t1.resolvedBy')->eq($account)
- ->orWhere('t1.id')->in(!empty($bugsAssignedByMe) ? array_keys($bugsAssignedByMe) : array())
- ->markRight(1)
- ->fi()
- ->orderBy($orderBy)
- ->beginIF($limit > 0)->limit($limit)->fi()
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 测试获取用户的bugs的 id => title 数组。
- * Get bug pairs of a user.
- *
- * @param string $account
- * @param bool $appendProduct
- * @param int $limit
- * @param array $skipProductIdList
- * @param array $skipExecutionIdList
- * @param int|array $appendBugID
- * @access public
- * @return array
- */
- public function getUserBugPairs($account, $appendProduct = true, $limit = 0, $skipProductIdList = array(), $skipExecutionIdList = array(), $appendBugID = 0)
- {
- $deletedProjectIdList = $this->dao->select('id')->from(TABLE_PROJECT)->where('deleted')->eq(1)->fetchPairs();
- $bugs = array();
- $stmt = $this->dao->select('t1.id, t1.title, t2.name as product')
- ->from(TABLE_BUG)->alias('t1')
- ->leftJoin(TABLE_PRODUCT)->alias('t2')
- ->on('t1.product=t2.id')
- ->where('t1.assignedTo')->eq($account)
- ->andWhere('t1.status')->ne('closed')
- ->beginIF(!empty($deletedProjectIdList))->andWhere('t1.execution')->notin($deletedProjectIdList)->fi()
- ->beginIF(!empty($skipProductIdList))->andWhere('t1.product')->notin($skipProductIdList)->fi()
- ->beginIF(!empty($skipExecutionIdList))->andWhere('t1.execution')->notin($skipExecutionIdList)->fi()
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->beginIF(!empty($appendBugID))->orWhere('t1.id')->in($appendBugID)->fi()
- ->orderBy('id desc')
- ->beginIF($limit > 0)->limit($limit)->fi()
- ->query();
- while($bug = $stmt->fetch())
- {
- if($appendProduct) $bug->title = $bug->product . ' / ' . $bug->title;
- $bugs[$bug->id] = $bug->title;
- }
- return $bugs;
- }
- /**
- * 获取某个项目的 bugs。
- * Get bugs of a project.
- *
- * @param int $projectID
- * @param int $productID
- * @param int|string $branchID
- * @param int $build
- * @param string $type
- * @param int $param
- * @param string $orderBy
- * @param string $excludeBugs
- * @param object $pager
- * @access public
- * @return array
- */
- public function getProjectBugs($projectID, $productID = 0, $branchID = 0, $build = 0, $type = '', $param = 0, $orderBy = 'id_desc', $excludeBugs = '', $pager = null)
- {
- if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
- if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
- $type = strtolower($type);
- if($type == 'bysearch')
- {
- $bugs = $this->getBySearch('project', $productID, $branchID, $projectID, 0, $param, $excludeBugs, $orderBy, $pager);
- }
- else
- {
- $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')
- ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id')
- ->where('t1.deleted')->eq(0)
- ->beginIF(empty($build))->andWhere('t1.project')->eq($projectID)->fi()
- ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID)->fi()
- ->beginIF(!empty($productID) and $branchID != 'all')->andWhere('t1.branch')->eq($branchID)->fi()
- ->beginIF($type == 'unresolved')->andWhere('t1.status')->eq('active')->fi()
- ->beginIF($type == 'noclosed')->andWhere('t1.status')->ne('closed')->fi()
- ->beginIF($type == 'assignedtome')->andWhere('t1.assignedTo')->eq($this->app->user->account)->fi()
- ->beginIF($type == 'openedbyme')->andWhere('t1.openedBy')->eq($this->app->user->account)->fi()
- ->beginIF(!empty($param))->andWhere('t2.path')->like("%,$param,%")->andWhere('t2.deleted')->eq(0)->fi()
- ->beginIF($build)->andWhere("CONCAT(',', t1.openedBuild, ',') like '%,$build,%'")->fi()
- ->beginIF($excludeBugs)->andWhere('t1.id')->notIN($excludeBugs)->fi()
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id', false);
- }
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug', false);
- return $bugs;
- }
- /**
- * 获取执行的 bug。
- * Get bugs of a execution.
- *
- * @param int $executionID
- * @param int $productID
- * @param int|string $branchID
- * @param string|array $builds
- * @param string $type
- * @param int $param
- * @param string $orderBy
- * @param string $excludeBugs
- * @param object $pager
- * @access public
- * @return array
- */
- public function getExecutionBugs($executionID, $productID = 0, $branchID = 'all', $builds = '0', $type = '', $param = 0, $orderBy = 'id_desc', $excludeBugs = '', $pager = null)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
- if(strpos($orderBy, 'pri_') !== false) $orderBy = str_replace('pri_', 'priOrder_', $orderBy);
- if(strpos($orderBy, 'severity_') !== false) $orderBy = str_replace('severity_', 'severityOrder_', $orderBy);
- if(strpos($orderBy, 'status') !== false) $orderBy = str_replace('status', 'statusOrder', $orderBy);
- $type = strtolower($type);
- if($type == 'bysearch')
- {
- $bugs = $this->getBySearch('execution', $productID, $branchID, $projectID = 0, $executionID, $param, $excludeBugs, $orderBy, $pager);
- }
- else
- {
- $condition = '';
- if($builds)
- {
- $conditions = array();
- if(!is_array($builds)) $builds = array_filter(array_unique(explode(',', $builds)));
- foreach($builds as $build) $conditions[] = "FIND_IN_SET('$build', t1.openedBuild)";
- $condition = implode(' OR ', $conditions);
- $condition = "($condition)";
- }
- $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')
- ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module = t2.id')
- ->where('t1.deleted')->eq('0')
- ->beginIF(!empty($productID) && $branchID !== 'all')->andWhere('t1.branch')->eq($branchID)->fi()
- ->beginIF(empty($builds))->andWhere('t1.execution')->eq($executionID)->fi()
- ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID)->fi()
- ->beginIF($type == 'unresolved')->andWhere('t1.status')->eq('active')->fi()
- ->beginIF($type == 'noclosed')->andWhere('t1.status')->ne('closed')->fi()
- ->beginIF($condition)->andWhere("$condition")->fi()
- ->beginIF(!empty($param))
- ->andWhere('t2.path')->like("%,$param,%")
- ->andWhere('t2.deleted')->eq('0')
- ->fi()
- ->beginIF($excludeBugs)->andWhere('t1.id')->notIN($excludeBugs)->fi()
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id', false);
- }
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'bug', false);
- return $bugs;
- }
- /**
- * 获取产品未关联版本的bug。
- * Get product left bugs.
- *
- * @param array $buildIdList
- * @param int $productID
- * @param int|string $branch
- * @param string $linkedBugs
- * @param object $pager
- * @access public
- * @return array|null
- */
- public function getProductLeftBugs($buildIdList, $productID, $branch = '', $linkedBugs = '', $pager = null)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
- /* 获取版本关联的执行。 */
- /* Get executions of builds. */
- $executionIdList = $this->getLinkedExecutionByIdList($buildIdList);
- if(empty($executionIdList)) return array();
- /* 获取执行的最小开始时间和最大结束时间。 */
- /* Get min begin date and max end date. */
- $minBegin = '1970-01-01';
- $maxEnd = '1970-01-01';
- $executions = $this->dao->select('id,begin,end')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->fetchAll();
- foreach($executions as $execution)
- {
- if(empty($minBegin) || $minBegin > $execution->begin) $minBegin = $execution->begin;
- if(empty($maxEnd) || $maxEnd < $execution->end) $maxEnd = $execution->end;
- }
- /* 获取在最小开始日期之前 未完成的版本id。 */
- /* Get undone builds before min begin date. */
- $beforeBuilds = $this->dao->select('t1.id')->from(TABLE_BUILD)->alias('t1')
- ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution=t2.id')
- ->where('t1.product')->eq($productID)
- ->andWhere('t2.status')->ne('done')
- ->andWhere('t2.deleted')->eq(0)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t1.date')->lt($minBegin)
- ->fetchPairs('id', 'id');
- /* 返回在执行最小开始和最大结束时间内的未关联版本的bugs。 */
- /* Return bugs that unrelate builds in the execution timeframe. */
- return $this->dao->select('*')->from(TABLE_BUG)->where('deleted')->eq(0)
- ->andWhere('product')->eq($productID)
- ->andWhere('toStory')->eq(0)
- ->andWhere('openedDate')->ge($minBegin)
- ->andWhere('openedDate')->le($maxEnd)
- ->andWhere("(status = 'active' OR resolvedDate > '{$maxEnd}')")
- ->andWhere('openedBuild')->notin($beforeBuilds)
- ->beginIF($linkedBugs)->andWhere('id')->notIN($linkedBugs)->fi()
- ->beginIF($branch !== '')->andWhere('branch')->in("0,$branch")->fi()
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 获取产品的bug键对。
- * Get product bug pairs.
- *
- * @param int $productID
- * @param int|string $branch
- * @param string $search
- * @param int $limit
- * @param string $range single|all
- * @access public
- * @return array
- */
- public function getProductBugPairs($productID, $branch = '', $search = '', $limit = 0, $range = 'single')
- {
- /* 获取产品的bugs。 */
- /* Get product bugs. */
- $productID = (int)$productID;
- 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)
- ->where('deleted')->eq(0)
- ->beginIF($range == 'single' && $productID)->andWhere('product')->eq($productID)->fi()
- ->beginIF(!$this->app->user->admin)->andWhere('execution')->in('0,' . $this->app->user->view->sprints)->fi()
- ->beginIF($range == 'all' && !$this->app->user->admin)->andWhere('product')->in($this->app->user->view->products)->fi()
- ->beginIF($branch !== '')->andWhere('branch')->in($branch)->fi()
- ->beginIF(strlen(trim($search)))
- ->andWhere('title', true)->like('%' . $search . '%')
- ->orWhere('id')->like('%' . $search . '%')
- ->markRight(1)
- ->fi()
- ->andWhere('deleted')->eq(0)
- ->orderBy('`order`, id desc')
- ->beginIF($limit)->limit($limit)->fi()
- ->fetchPairs('id', 'title');
- }
- /**
- * 获取产品成员键对。
- * get Product member pairs.
- *
- * @param int $productID
- * @param string $branchID
- * @access public
- * @return array
- */
- public function getProductMemberPairs($productID, $branchID = '')
- {
- if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getTeamMembersPairs();
- /* 获取产品关联的项目。 */
- /* Get related projects of product. */
- $projects = $this->loadModel('product')->getProjectPairsByProduct($productID, $branchID);
- /* 获取项目的团队成员。 */
- /* Get team members of projects. */
- return $this->loadModel('user')->getTeamMemberPairs(array_keys($projects));
- }
- /**
- * 通过版本 id 和产品 id 获取 bugs。
- * Get bugs according to buildID and productID.
- *
- * @param array $buildIdList
- * @param int $productID
- * @param string $branch
- * @param string $linkedBugs
- * @param object $pager
- * @access public
- * @return array
- */
- public function getReleaseBugs($buildIdList, $productID, $branch = 0, $linkedBugs = '', $pager = null)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getBugs();
- $executionIdList = $this->getLinkedExecutionByIdList($buildIdList);
- if(empty($executionIdList)) return array();
- $executions = $this->dao->select('id,type,begin')->from(TABLE_EXECUTION)->where('id')->in($executionIdList)->fetchAll('id');
- if(count($executionIdList) > 1) $condition = 'execution NOT ' . helper::dbIN($executionIdList);
- else $condition = 'execution !' . helper::dbIN($executionIdList);
- $minBegin = '';
- foreach($executions as $execution)
- {
- if(empty($minBegin) or $minBegin > $execution->begin) $minBegin = $execution->begin;
- $condition .= " OR (`execution` = '{$execution->id}' AND openedDate < '{$execution->begin}')";
- }
- return $this->dao->select('*')->from(TABLE_BUG)
- ->where('resolution')->ne('postponed')
- ->beginIF(!empty($minBegin))->andWhere('resolvedDate')->ge($minBegin)->fi()
- ->andWhere('product')->eq($productID)
- ->beginIF($linkedBugs)->andWhere('id')->notIN($linkedBugs)->fi()
- ->beginIF($branch)->andWhere('branch')->in("0,$branch")->fi()
- ->andWhere("($condition)")
- ->andWhere('deleted')->eq(0)
- ->orderBy('openedDate ASC')
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 通过版本 id 列表获取版本关联的执行。
- * Get linked execution by build id list.
- *
- * @param array $buildIdList
- * @access public
- * @return array
- */
- public function getLinkedExecutionByIdList($buildIdList)
- {
- /* Get information of builds. */
- $builds = $this->dao->select('id,execution,builds')->from(TABLE_BUILD)->where('id')->in($buildIdList)->fetchAll('id');
- $executionIdList = array();
- $linkedBuildIdList = array();
- foreach($builds as $build)
- {
- /* 如果版本的builds字段不为空,将版本的builds追加到linkedBuildIdList数组。 */
- /* If build builds is not empty, append build builds to linkedBuildIdList. */
- if($build->builds) $linkedBuildIdList = array_merge($linkedBuildIdList, explode(',', $build->builds));
- /* 如果版本的执行字段不为空,将执行字段追加到executionIdList数组。 */
- /* If build execution is not empty, append build execution to executionIdList. */
- if(!empty($build->execution)) $executionIdList[$build->execution] = $build->execution;
- }
- /* 如果linkedBuildIdList不为空,将关联builds的版本追加到executionIdList数组。 */
- /* If linkedBuildIdList is not empty, append the execution of the linked builds to executionIdList. */
- if($linkedBuildIdList)
- {
- $linkedBuilds = $this->dao->select('id,execution')->from(TABLE_BUILD)->where('id')->in(array_unique($linkedBuildIdList))->fetchAll('id');
- foreach($linkedBuilds as $build)
- {
- if(empty($build->execution)) continue;
- $executionIdList[$build->execution] = $build->execution;
- }
- }
- return $executionIdList;
- }
- /**
- * 获取需求产生的bugs。
- * Get bugs of a story.
- *
- * @param int $storyID
- * @param int $executionID
- * @access public
- * @return array
- */
- public function getStoryBugs($storyID, $executionID = 0)
- {
- /* 获取需求产生的bugs。 */
- /* Get bugs of the story. */
- return $this->dao->select('id, title, pri, type, status, assignedTo, resolvedBy, resolution')
- ->from(TABLE_BUG)
- ->where('story')->eq($storyID)
- ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
- ->andWhere('deleted')->eq(0)
- ->fetchAll('id');
- }
- /**
- * 获取用例关联的bugs。
- * Get case bugs.
- *
- * @param int $runID
- * @param int $caseID
- * @param int $version
- * @param string $orderBy
- * @access public
- * @return array|null
- */
- public function getCaseBugs($runID, $caseID = 0, $version = 0, $orderBy = 'id_asc')
- {
- /* 获取用例关联的bugs。 */
- /* Get case bugs. */
- return $this->dao->select('*')->from(TABLE_BUG)
- ->where('1=1')
- ->beginIF($runID)->andWhere('`result`')->eq($runID)->fi()
- ->beginIF($runID == 0 and $caseID)->andWhere('`case`')->eq($caseID)->fi()
- ->beginIF($version)->andWhere('`caseVersion`')->eq($version)->fi()
- ->andWhere('deleted')->eq(0)
- ->orderBy($orderBy)
- ->fetchAll('id', false);
- }
- /**
- * 获取需求关联的 bug 数量。
- * Get counts of some stories' bugs.
- *
- * @param array $stories
- * @param int $executionID
- * @access public
- * @return array
- */
- public function getStoryBugCounts($stories, $executionID = 0)
- {
- if(empty($stories)) return array();
- $bugCounts = $this->dao->select('story, COUNT(1) AS bugs')
- ->from(TABLE_BUG)
- ->where('story')->in($stories)
- ->andWhere('deleted')->eq(0)
- ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
- ->groupBy('story')
- ->fetchPairs();
- foreach($stories as $storyID) if(!isset($bugCounts[$storyID])) $bugCounts[$storyID] = 0;
- return $bugCounts;
- }
- /**
- * 从测试结果中获取bug信息。
- * Get bug info from a result.
- *
- * @param int $resultID
- * @param int $caseID
- * @param string $caseID
- * @access public
- * @return array
- * @param string $stepIdList
- */
- public function getBugInfoFromResult($resultID, $caseID = 0, $stepIdList = '')
- {
- $result = common::isTutorialMode() ? $this->loadModel('tutorial')->getResult() : $this->dao->findById($resultID)->from(TABLE_TESTRESULT)->fetch();
- if(!$result) return array();
- $run = $this->loadModel('testtask')->getRunById($result->run);
- if(!$run) $run = new stdclass();
- if($caseID > 0) $run->case = $this->loadModel('testcase')->getById($caseID, $result->version);
- $stepResults = unserialize($result->stepResults);
- if(!empty($stepResults))
- {
- $bugStep = '';
- $bugResult = isset($stepResults[0]) ? zget($stepResults[0], 'real') : '';
- $bugExpect = '';
- $caseSteps = $run->case->steps;
- $caseSteps = array_combine(array_column($caseSteps, 'id'), $caseSteps);
- $steps = explode('_', trim($stepIdList, '_'));
- foreach($steps as $stepId)
- {
- if(!isset($caseSteps[$stepId])) continue;
- $step = $caseSteps[$stepId];
- $stepDesc = str_replace("\n", '</p><p>', $step->desc);
- $stepExpect = str_replace("\n", '</p><p>', $step->expect);
- $stepResult = (!isset($stepResults[$stepId]) or empty($stepResults[$stepId]['real'])) ? '' : $stepResults[$stepId]['real'];
- $bugStep .= '<p>' . $step->name . '. ' . $stepDesc . '</p>';
- $bugResult .= '<p>' . $step->name . '. ' . $stepResult . '</p>';
- $bugExpect .= '<p>' . $step->name . '. ' . $stepExpect . '</p>';
- }
- }
- $bugSteps = $run->case->precondition != '' ? "<p>[" . $this->lang->testcase->precondition . "]</p>" . "\n" . $run->case->precondition : '';
- $bugSteps .= '<p></p>';
- $bugSteps .= !empty($stepResults) && !empty($bugStep) ? str_replace(array('<br/>', '<p></p>'), '', $this->lang->bug->tplStep) . $bugStep : $this->lang->bug->tplStep;
- $bugSteps .= '<p></p>';
- $bugSteps .= !empty($stepResults) && !empty($bugResult) ? str_replace(array('<br/>', '<p></p>'), '', $this->lang->bug->tplResult) . $bugResult : $this->lang->bug->tplResult;
- $bugSteps .= '<p></p>';
- $bugSteps .= !empty($stepResults) && !empty($bugExpect) ? str_replace(array('<br/>', '<p></p>'), '', $this->lang->bug->tplExpect) . $bugExpect : $this->lang->bug->tplExpect;
- if(!empty($run->task)) $testtask = $this->loadModel('testtask')->getByID($run->task);
- $executionID = isset($testtask->execution) ? $testtask->execution : 0;
- $projectID = isset($testtask->project) ? $testtask->project : 0;
- if(!$executionID and $caseID > 0) $executionID = isset($run->case->execution) ? $run->case->execution : 0; // Fix feedback #1043.
- if(!$executionID and $this->app->tab == 'execution') $executionID = $this->session->execution;
- if(!$projectID and $caseID > 0) $projectID = isset($run->case->project) ? $run->case->project : 0;
- if(!$projectID and $this->app->tab == 'projectID') $projectID = $this->session->project;
- 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);
- }
- /**
- * 统计迭代 bug 数量。
- * Statistics by bug execution.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerExecution()
- {
- $datas = $this->dao->select('execution AS name, count(execution) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('execution')->orderBy('value_desc')->fetchAll('name');
- if(!$datas) return array();
- $executionPairs = $this->loadModel('execution')->getPairs();
- $maxLength = common::checkNotCN() ? 22 : 12;
- foreach($datas as $executionID => $data)
- {
- $data->name = $executionID == 0 ? $this->lang->report->undefined : zget($executionPairs, $executionID, (string)$executionID);
- $data->title = $data->name;
- if(mb_strlen($data->name, 'UTF-8') > $maxLength) $data->name = mb_substr($data->name, 0, $maxLength, 'UTF-8') . '...';
- }
- return $datas;
- }
- /**
- * 统计版本 Bug 数量。
- * Statistics by bug build.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerBuild()
- {
- $datas = $this->dao->select('openedBuild AS name, COUNT(openedBuild) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('openedBuild')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $buildIdList => $data)
- {
- if(is_int($buildIdList) || $buildIdList == 'trunk') continue;
- $openedBuildIdList = explode(',', $buildIdList);
- /* Bug 可以关联多个影响版本,一个版本被多个 bug 关联时,累加 bug 数量。*/
- /* Bugs can be associated with multiple builds. When a build is associated with multiple bugs, accumulated bugs.*/
- foreach($openedBuildIdList as $buildID)
- {
- if(!isset($datas[$buildID]))
- {
- $datas[$buildID] = new stdclass();
- $datas[$buildID]->name = $buildID;
- $datas[$buildID]->value = 0;
- }
- $datas[$buildID]->value += $data->value;
- }
- unset($datas[$buildIdList]);
- }
- /* 统计最后一次查询的所属产品列表。*/
- /* Get products in the last query. */
- $productIdList = $this->session->product ? array($this->session->product) : array();
- if($this->reportCondition() !== true)
- {
- preg_match('/`product` IN \((?P<productIdList>.+)\)/', $this->reportCondition(), $matches);
- if(!empty($matches) && isset($matches['productIdList']))
- {
- $productIdList = str_replace('\'', '', $matches['productIdList']);
- $productIdList = explode(',', $productIdList);
- }
- }
- $builds = $this->loadModel('build')->getBuildPairs($productIdList, 0, 'hasdeleted');
- $this->app->loadLang('report');
- foreach($datas as $buildID => $data) $data->name = zget($builds, $buildID, $this->lang->report->undefined);
- ksort($datas);
- return $datas;
- }
- /**
- * 统计模块 bug 数量。
- * Statistics by bug module.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerModule()
- {
- $datas = $this->dao->select('module AS name, COUNT(module) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('module')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- $modules = $this->loadModel('tree')->getModulesName(array_keys($datas), true, true);
- foreach($datas as $moduleID => $data) $data->name = zget($modules, $moduleID, '/');
- return $datas;
- }
- /**
- * 统计每天新增 bug 数量。
- * Statistics by the number of created bug daily.
- *
- * @access public
- * @return array
- */
- public function getDataOfOpenedBugsPerDay()
- {
- 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();
- }
- /**
- * 统计每天解决 bug 数量。
- * Statistics by the number of resolved bug.
- *
- * @access public
- * @return array
- */
- public function getDataOfResolvedBugsPerDay()
- {
- return $this->dao->select('DATE_FORMAT(resolvedDate, "%Y-%m-%d") AS name, COUNT(1) AS value')->from(TABLE_BUG)
- ->where($this->reportCondition())
- ->groupBy('name')
- ->having('name != 0000-00-00')
- ->orderBy('name')
- ->fetchAll();
- }
- /**
- * 统计每天关闭 bug 数量。
- * Statistics by the number of closed bug.
- *
- * @access public
- * @return array
- */
- public function getDataOfClosedBugsPerDay()
- {
- return $this->dao->select('DATE_FORMAT(closedDate, "%Y-%m-%d") AS name, COUNT(1) AS value')->from(TABLE_BUG)
- ->where($this->reportCondition())
- ->groupBy('name')
- ->having('name != 0000-00-00')
- ->orderBy('name')
- ->fetchAll();
- }
- /**
- * 统计每人提交的 bug 数量。
- * Statistics by bug creator.
- *
- * @access public
- * @return array
- */
- public function getDataOfOpenedBugsPerUser()
- {
- $datas = $this->dao->select('openedBy AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
- foreach($datas as $account => $data) $data->name = zget($this->users, $account);
- return $datas;
- }
- /**
- * 统计每人解决的 bug 数量。
- * Statistics by bug resolver.
- *
- * @access public
- * @return array
- */
- public function getDataOfResolvedBugsPerUser()
- {
- $datas = $this->dao->select('resolvedBy AS name, COUNT(1) AS value')
- ->from(TABLE_BUG)->where($this->reportCondition())
- ->andWhere('resolvedBy')->ne('')
- ->groupBy('name')
- ->orderBy('value DESC')
- ->fetchAll('name');
- if(!$datas) return array();
- if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
- foreach($datas as $account => $data) $data->name = zget($this->users, $account);
- return $datas;
- }
- /**
- * 统计每人关闭的 bug 数量。
- * Statistics by bug closer.
- *
- * @access public
- * @return array
- */
- public function getDataOfClosedBugsPerUser()
- {
- $datas = $this->dao->select('closedBy AS name, COUNT(1) AS value')
- ->from(TABLE_BUG)
- ->where($this->reportCondition())
- ->andWhere('closedBy')->ne('')
- ->groupBy('name')
- ->orderBy('value DESC')
- ->fetchAll('name');
- if(!$datas) return array();
- if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
- foreach($datas as $account => $data) $data->name = zget($this->users, $account);
- return $datas;
- }
- /**
- * 按照 bug 严重程度统计。
- * Statistics by bug severity
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerSeverity()
- {
- $datas = $this->dao->select('severity AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $severity => $data) $data->name = $this->lang->bug->report->bugsPerSeverity->graph->xAxisName . ':' . zget($this->lang->bug->severityList, $severity);
- return $datas;
- }
- /**
- * 按照解决方案统计。
- * Statistics by bug resolution.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerResolution()
- {
- $datas = $this->dao->select('resolution AS name, COUNT(1) AS value')
- ->from(TABLE_BUG)
- ->where($this->reportCondition())
- ->andWhere('resolution')->ne('')
- ->groupBy('name')
- ->orderBy('value DESC')
- ->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $resolution => $data) $data->name = zget($this->lang->bug->resolutionList, $resolution);
- return $datas;
- }
- /**
- * 按 bug 状态统计。
- * Statistics by bug status.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerStatus()
- {
- $datas = $this->dao->select('status AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $status => $data) $data->name = zget($this->lang->bug->statusList, $status);
- return $datas;
- }
- /**
- * 按照 bug 优先级统计。
- * Statistics by bug pri.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerPri()
- {
- $datas = $this->dao->select('pri AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $data) $data->name = $this->lang->bug->report->bugsPerPri->graph->xAxisName . ':' . zget($this->lang->bug->priList, $data->name);
- return $datas;
- }
- /**
- * 按照 bug 激活次数统计。
- * Statistics by bug activation times.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerActivatedCount()
- {
- $datas = $this->dao->select('activatedCount AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $data) $data->name = $this->lang->bug->report->bugsPerActivatedCount->graph->xAxisName . ':' . $data->name;
- return $datas;
- }
- /**
- * 按照 bug 类型统计。
- * Statistics by bug type.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerType()
- {
- $datas = $this->dao->select('type AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- foreach($datas as $type => $data) $data->name = zget($this->lang->bug->typeList, $type);
- return $datas;
- }
- /**
- * 按照 bug 指派给统计。
- * Statistics by bug assignment.
- *
- * @access public
- * @return array
- */
- public function getDataOfBugsPerAssignedTo()
- {
- $datas = $this->dao->select('assignedTo AS name, COUNT(1) AS value')->from(TABLE_BUG)->where($this->reportCondition())->groupBy('name')->orderBy('value DESC')->fetchAll('name');
- if(!$datas) return array();
- if(!isset($this->users)) $this->users = $this->loadModel('user')->getPairs('noletter');
- foreach($datas as $account => $data) $data->name = zget($this->users, $account);
- return $datas;
- }
- /**
- * 通过 sonarqube id 获取bug。
- * Get by Sonarqube id.
- *
- * @param int $sonarqubeID
- * @access public
- * @return array|false
- */
- public function getBySonarqubeID($sonarqubeID)
- {
- return $this->dao->select('issueKey')->from(TABLE_BUG)->where('issueKey')->like("$sonarqubeID:%")->fetchPairs();
- }
- /**
- * 获取bug查询语句。
- * Get bug query.
- *
- * @param string $bugQuery
- * @access public
- * @return string
- */
- public function getBugQuery($bugQuery)
- {
- /* 如果要查询所有产品,将当前用户可以看到的"`product` = 'all'" 换为自己可以看到所有的产品ID。 */
- /* If you need to query all products, replace "`product` = 'all'" with the products that the current user can see.. */
- $allProduct = "`product` = 'all'";
- if(strpos($bugQuery, $allProduct) !== false)
- {
- $products = $this->app->user->view->products;
- $bugQuery = str_replace($allProduct, '1', $bugQuery);
- $bugQuery = $bugQuery . ' AND `product` ' . helper::dbIN($products);
- }
- /* 如果要查询所有项目,将当前用户可以看到的"`project` = 'all'" 换为自己可以看到所有的项目ID。 */
- /* If you need to query all projects, replace "`project` = 'all'" with the projects that the current user can see.. */
- $allProject = "`project` = 'all'";
- if(strpos($bugQuery, $allProject) !== false)
- {
- $projects = $this->loadModel('project')->getPairs();
- if(is_array($projects)) $projectIdList = implode(',', array_keys($projects));
- $bugQuery = str_replace($allProject, '1', $bugQuery);
- $bugQuery = $bugQuery . ' AND `project` in (' . $projectIdList . ')';
- }
- /* 如果要依据解决日期搜索,解决日期应该不是'0000-00-00'。 */
- /* If you need to query by resolvedDate, resolvedDate shouldn't be '0000-00-00'. */
- if(strpos($bugQuery, ' `resolvedDate` ') !== false) $bugQuery = str_replace(' `resolvedDate` ', " `resolvedDate` != '0000-00-00 00:00:00' AND `resolvedDate` ", $bugQuery);
- /* 如果要依据关闭日期搜索,关闭日期应该不是'0000-00-00'。 */
- /* If you need to query by closedDate, closedDate shouldn't be '0000-00-00'. */
- if(strpos($bugQuery, ' `closedDate` ') !== false) $bugQuery = str_replace(' `closedDate` ', " `closedDate` != '0000-00-00 00:00:00' AND `closedDate` ", $bugQuery);
- /* 如果要依据需求搜索,需求ID应该不是0。 */
- /* If you need to query by story, story shouldn't be zero. */
- if(strpos($bugQuery, ' `story` ') !== false)
- {
- /* 如果要搜索条件是包含或者不包含,应该从需求的标题、关键字、描述、期望中搜索。 */
- /* If query condition is include or notinclude, you should query from the title, keywords, spec, and verify of the story. */
- preg_match_all("/`story`[ ]+(NOT *)?LIKE[ ]+'%([^%]*)%'/Ui", $bugQuery, $out);
- if(!empty($out[2]))
- {
- foreach($out[2] as $searchValue)
- {
- $story = $this->dao->select('t1.id')->from(TABLE_STORY)->alias('t1')
- ->leftJoin(TABLE_STORYSPEC)->alias('t2')->on('t1.id=t2.story')
- ->where('t1.title')->like("%$searchValue%")
- ->orWhere('t1.keywords')->like("%$searchValue%")
- ->orWhere('t2.spec')->like("%$searchValue%")
- ->orWhere('t2.verify')->like("%$searchValue%")
- ->fetchPairs('id');
- if(empty($story)) $story = array(0);
- $searchValue = preg_quote($searchValue, '/');
- $bugQuery = preg_replace("/`story`[ ]+(NOT[ ]*)?LIKE[ ]+'%$searchValue%'/Ui", '`story` $1 IN (' . implode(',', $story) .')', $bugQuery);
- }
- }
- $bugQuery .= ' AND `story` != 0';
- }
- return $bugQuery;
- }
- /**
- * 获取产品 bugs。
- * Get product bugs.
- *
- * @param array $productIdList
- * @param string $type
- * @param string $begin
- * @param string $end
- * @access public
- * @return array
- */
- public function getProductBugs($productIdList, $type = '', $begin = '', $end = '')
- {
- return $this->dao->select('*')->from(TABLE_BUG)
- ->where('product')->in($productIdList)
- ->beginIF($type == 'resolved')->andWhere('resolvedDate')->ge($begin)->andWhere('resolvedDate')->le("{$end} 23:59:59")->fi()
- ->beginIF($type == 'opened')->andWhere('openedDate')->ge($begin)->andWhere('openedDate')->le("{$end} 23:59:59")->fi()
- ->andWhere('deleted')->eq(0)
- ->fetchAll('id', false);
- }
- /**
- * 获取重新激活的 bugs。
- * Get activated bugs.
- *
- * @param array $productIdList
- * @param string $begin
- * @param string $end
- * @param array $buildIdList
- * @access public
- * @return array
- */
- public function getActivatedBugs($productIdList, $begin, $end, $buildIdList)
- {
- /* Get all bugs in the builds of products. */
- $buildBugs = array();
- $allBugs = $this->getProductBugs($productIdList);
- foreach($allBugs as $bug)
- {
- $intersect = array_intersect(explode(',', $bug->openedBuild), $buildIdList);
- if(!empty($intersect)) $buildBugs[$bug->id] = $bug;
- }
- /* Get bug reactivated actions during the testreport. */
- $actions = $this->dao->select('id,objectID')->from(TABLE_ACTION)
- ->where('objectType')->eq('bug')
- ->andWhere('action')->eq('activated')
- ->andWhere('date')->ge($begin)
- ->andWhere('date')->le($end . ' 23:59:59')
- ->andWhere('objectID')->in(array_keys($buildBugs))
- ->fetchPairs();
- $histories = $this->loadModel('action')->getHistory(array_keys($actions));
- foreach($actions as $actionID => $bugID) $bugActions[$bugID][$actionID] = zget($histories, $actionID, array());
- /* Get reactivated bugs. */
- $activatedBugs = array();
- foreach($buildBugs as $bug)
- {
- if(empty($bugActions[$bug->id])) continue;
- foreach($bugActions[$bug->id] as $actionID => $histories)
- {
- foreach($histories as $history)
- {
- if($history->field == 'openedBuild' && !in_array($history->new, $buildIdList)) continue;
- $activatedBugs[$bug->id] = $bug;
- }
- }
- }
- return $activatedBugs;
- }
- /**
- * Get related objects id lists.
- *
- * @param string $object
- * @param string $pairs
- * @access public
- * @return void
- */
- public function getRelatedObjects($object, $pairs = '')
- {
- /* Get bugs. */
- $bugs = $this->loadModel('transfer')->getQueryDatas('bug');
- /* Get related objects id lists. */
- $relatedObjectIdList = array();
- $relatedObjects = array();
- foreach($bugs as $bug)
- {
- if(is_numeric($bug->$object)) $relatedObjectIdList[$bug->$object] = $bug->$object;
- }
- if($object == 'openedBuild' or $object == 'resolvedBuild') $object = 'build';
- /* Get related objects title or names. */
- $table = $this->config->objectTables[$object];
- if($table) $relatedObjects = $this->dao->select($pairs)->from($table)->where('id')->in($relatedObjectIdList)->fetchPairs();
- if(in_array($object, array('build','resolvedBuild'))) $relatedObjects = array('trunk' => $this->lang->trunk) + $relatedObjects;
- return array('' => '', 0 => '') + $relatedObjects;
- }
- /**
- * 判断当前动作是否可以点击。
- * Adjust the action is clickable.
- *
- * @param string $bug
- * @param string $action
- * @param string $module
- * @access public
- * @return bool
- * @param object $object
- */
- public static function isClickable($object, $action, $module = 'bug')
- {
- global $config, $app;
- $action = strtolower($action);
- /* 如果bug状态是激活,没有确认过,这个bug可以被确认。 */
- /* If the status is active, and the confirmed is 0, the bug can be confirm. */
- if($module == 'bug' && $action == 'confirm') return $object->status == 'active' && $object->confirmed == 0;
- /* 如果bug不是关闭状态,这个bug可以被指派。 */
- /* If the status isn't closed, the bug can be assginTo. */
- if($module == 'bug' && $action == 'assignto') return $object->status != 'closed';
- /* 如果bug是激活状态,这个bug可以被解决。 */
- /* If the status is active, the bug can be resolve. */
- if($module == 'bug' && $action == 'resolve') return $object->status == 'active';
- /* 如果bug是解决状态,这个bug可以被关闭。 */
- /* If the status is resolved, the bug can be close. */
- if($module == 'bug' && $action == 'close') return $object->status == 'resolved';
- /* 如果bug不是激活状态,这个bug可以被激活。 */
- /* If the status isn't active, the bug can be activate. */
- if($module == 'bug' && $action == 'activate') return $object->status != 'active';
- /* 如果bug是激活状态,这个bug可以被转为需求。 */
- /* If the status is active, the bug can be toStory. */
- if($module == 'bug' && $action == 'tostory') return $object->status == 'active';
- /* 判断反馈转bug操作按钮的权限。 */
- /* check feedback toStory priv. */
- if($module == 'bug' && $action == 'create' && isset($object->solution)) return ($config->global->flow == 'full' || $config->global->flow == 'onlyTest') && strpos('closed|clarify|noreview', $object->status) === false;
- /* 判断确认撤销操作按钮的权限。 */
- /* Check confirmdemandretract priv. */
- if($module == 'bug' && $action == 'confirmdemandretract') return !empty($object->confirmeActionType) && $object->confirmeActionType == 'confirmedretract';
- /* 判断确认移除操作按钮的权限。 */
- /* Check confirmdemandunlink priv. */
- if($module == 'bug' && $action == 'confirmdemandunlink') return !empty($object->confirmeActionType) && $object->confirmeActionType == 'confirmedunlink';
- return true;
- }
- /**
- * Get report condition from session.
- *
- * @access public
- * @return void
- */
- public function reportCondition()
- {
- if(isset($_SESSION['bugQueryCondition']))
- {
- if(!$this->session->bugOnlyCondition) return 'id in (' . preg_replace('/SELECT .* FROM/', 'SELECT t1.id FROM', $this->session->bugQueryCondition) . ')';
- return $this->session->bugQueryCondition;
- }
- return '1=1';
- }
- /**
- * Link bug to build and release
- *
- * @param int $bugID
- * @param int|string $resolvedBuild
- * @access public
- * @return bool
- */
- public function linkBugToBuild($bugID, $resolvedBuild)
- {
- /* 如果版本为空,或者版本为主干,返回。 */
- /* If resolved build is empty or resolved build is trunk, return true. */
- if(empty($resolvedBuild) || $resolvedBuild == 'trunk') return true;
- /* 获取版本信息,并且将bugs关联到版本。 */
- /* Get build information, and relate the bugs to the build. */
- $build = $this->dao->select('id,product,bugs')->from(TABLE_BUILD)->where('id')->eq($resolvedBuild)->fetch();
- $buildBugs = $build->bugs . ',' . $bugID;
- $buildBugs = explode(',', trim($buildBugs, ','));
- $buildBugs = array_unique($buildBugs);
- $this->dao->update(TABLE_BUILD)->set('bugs')->eq(implode(',', $buildBugs))->where('id')->eq($resolvedBuild)->exec();
- /* 将bugs关联到版本关联的发布。 */
- /* Relate bugs to the build-related release. */
- $this->loadModel('release');
- $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();
- if($release)
- {
- $releaseBugs = $release->bugs . ',' . $bugID;
- $releaseBugs = explode(',', trim($releaseBugs, ','));
- $releaseBugs = array_unique($releaseBugs);
- $this->dao->update(TABLE_RELEASE)->set('bugs')->eq(implode(',', $releaseBugs))->where('id')->eq($release->id)->exec();
- $this->release->updateRelated($release->id, 'bug', $releaseBugs);
- }
- return true;
- }
- /**
- * 更新相关 bug。
- * Update the related bug.
- *
- * @param int $bugID
- * @param string $relatedBug
- * @param string $oldRelatedBug
- * @access public
- * @return bool
- */
- public function updateRelatedBug($bugID, $relatedBug, $oldRelatedBug)
- {
- /* 获取需要修改的相关 bug。 */
- /* Get related bugs that need to change. */
- $relatedBugs = explode(',', $relatedBug);
- $oldRelatedBugs = explode(',', $oldRelatedBug);
- $addedRelatedBugs = array_diff($relatedBugs, $oldRelatedBugs);
- $removedRelatedBugs = array_diff($oldRelatedBugs, $relatedBugs);
- $changedRelatedBugs = array_merge($addedRelatedBugs, $removedRelatedBugs);
- $changedRelatedBugs = $this->dao->select('id, relatedBug')->from(TABLE_BUG)->where('id')->in(array_filter($changedRelatedBugs))->fetchPairs();
- /* 更新相关 bug。 */
- /* Update the related bug. */
- foreach($changedRelatedBugs as $changedBugID => $relatedBugs)
- {
- if(in_array($changedBugID, $addedRelatedBugs))
- {
- $relatedBugs = explode(',', $relatedBugs);
- if(!empty($relatedBugs) && !in_array($bugID, $relatedBugs)) $relatedBugs[] = $bugID;
- }
- else
- {
- $relatedBugs = explode(',', $relatedBugs);
- unset($relatedBugs[array_search($bugID, $relatedBugs)]);
- }
- $currentRelatedBug = implode(',', array_filter($relatedBugs));
- $this->dao->update(TABLE_BUG)->set('relatedBug')->eq($currentRelatedBug)->where('id')->eq($changedBugID)->exec();
- }
- return !dao::isError();
- }
- /**
- * 获取指派用户和抄送给用户列表。
- * Get toList and ccList.
- *
- * @param object $bug
- * @access public
- * @return array|false
- */
- public function getToAndCcList($bug)
- {
- /* Set toList and ccList. */
- $toList = $bug->assignedTo ? $bug->assignedTo : '';
- $ccList = trim((string)$bug->mailto, ',');
- if(empty($toList))
- {
- if(empty($ccList)) return false;
- if(strpos($ccList, ',') === false)
- {
- $toList = $ccList;
- $ccList = '';
- }
- else
- {
- $commaPos = strpos($ccList, ',');
- $toList = substr($ccList, 0, $commaPos);
- $ccList = substr($ccList, $commaPos + 1);
- }
- }
- elseif($bug->status == 'closed')
- {
- $ccList .= ',' . $bug->resolvedBy;
- }
- return array($toList, $ccList);
- }
- /**
- * Bug 列表底部的统计信息。
- * The statistic summary in table footer.
- *
- * @param array $bugs
- * @access public
- * @return string
- */
- public function summary($bugs)
- {
- /* 获取为解决的bug数量。 */
- /* Get unresolved bug count. */
- $unresolved = 0;
- foreach($bugs as $bug)
- {
- if($bug->status != 'resolved' && $bug->status != 'closed') $unresolved ++;
- }
- /* 返回bug的统计信息。 */
- /* Return the statistics of the bugs. */
- return sprintf($this->lang->bug->notice->summary, count($bugs), $unresolved);
- }
- /**
- * 搜索 bug。
- * Search bugs.
- *
- * @param string $object
- * @param array|int $productIdList
- * @param int|string $branch
- * @param int $projectID
- * @param int $executionID
- * @param int $queryID
- * @param string $excludeBugs
- * @param string $orderBy
- * @param object $pager
- * @access public
- * @return array
- */
- public function getBySearch($object = 'bug', $productIdList = array(), $branch = 0, $projectID = 0, $executionID = 0, $queryID = 0, $excludeBugs = '', $orderBy = '', $pager = null)
- {
- $bugQuery = $this->processSearchQuery($object, $queryID, $productIdList, (string)$branch);
- 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)
- ->where($bugQuery)
- ->andWhere('deleted')->eq('0')
- ->beginIF($excludeBugs)->andWhere('id')->notIN($excludeBugs)->fi()
- ->beginIF($object == 'bug' && !$this->app->user->admin)
- ->andWhere('project')->in('0,' . $this->app->user->view->projects)
- ->andWhere('execution')->in('0,' . $this->app->user->view->sprints)
- ->fi()
- ->beginIF($projectID)
- ->andWhere('project', true)->eq($projectID)
- ->fi()
- ->beginIF($projectID && $object == 'bug')
- ->orWhere('project')->eq(0)
- ->andWhere('openedBuild')->eq('trunk')
- ->fi()
- ->beginIF($projectID)
- ->markRight(1)
- ->fi()
- ->beginIF($object == 'execution')
- ->andWhere('execution')->eq($executionID)
- ->fi()
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id', false);
- }
- /**
- * 为 datatable 获取模块。
- * Get modules for datatable.
- *
- * @param int $productID
- * @access public
- * @return void
- */
- public function getDatatableModules($productID)
- {
- $branches = $this->loadModel('branch')->getPairs($productID);
- $modules = $this->loadModel('tree')->getOptionMenu($productID, 'bug', 0);
- if(count($branches) <= 1) return $modules;
- foreach($branches as $branchID => $branchName) $modules += $this->tree->getOptionMenu($productID, 'bug', 0, (string)$branchID);
- return $modules;
- }
- /**
- * 更新Bug关联的代码提交记录。
- * Update the commit logs linked with the bugs.
- *
- * @param int $bugID
- * @param int $repoID
- * @param array $revisions
- * @access protected
- * @return bool
- */
- public function updateLinkedCommits($bugID, $repoID, $revisions)
- {
- if(!$bugID || !$repoID || empty($revisions)) return true;
- $bug = $this->dao->select('product, project')->from(TABLE_BUG)->where('id')->eq($bugID)->fetch();
- if(!$bug) return true;
- foreach($revisions as $revision)
- {
- $data = new stdclass();
- $data->project = $bug->project;
- $data->product = $bug->product;
- $data->AType = 'bug';
- $data->AID = $bugID;
- $data->BType = 'commit';
- $data->BID = $revision;
- $data->relation = 'completedin';
- $data->extra = $repoID;
- $this->dao->replace(TABLE_RELATION)->data($data)->autoCheck()->exec();
- $data->AType = 'commit';
- $data->AID = $revision;
- $data->BType = 'bug';
- $data->BID = $bugID;
- $data->relation = 'completedfrom';
- $this->dao->replace(TABLE_RELATION)->data($data)->autoCheck()->exec();
- }
- return !dao::isError();
- }
- /**
- * 获取Bug关联的提交数据。
- * Get the commit data for the associated bugs
- * @param int $repoID
- * @param array $revisions
- * @access protected
- * @return bool
- */
- public function getLinkedCommits($repoID, $revisions)
- {
- return $this->dao->select('t1.revision,t3.id AS id,t3.title AS title')
- ->from(TABLE_REPOHISTORY)->alias('t1')
- ->leftJoin(TABLE_RELATION)->alias('t2')->on("t2.relation='completedin' AND t2.BType='commit' AND t2.BID=t1.id")
- ->leftJoin(TABLE_BUG)->alias('t3')->on("t2.AType='bug' AND t2.AID=t3.id")
- ->where('t1.revision')->in($revisions)
- ->andWhere('t1.repo')->eq($repoID)
- ->andWhere('t3.id')->notNULL()
- ->fetchGroup('revision', 'id');
- }
- /**
- * 通过任务ID获取相关的Bug
- * @param int $taskID
- *
- * @access public
- * @return array|false
- */
- public function getLinkedBugsByTaskID($taskID)
- {
- return $this->dao->select('t1.id,t1.title')
- ->from(TABLE_BUG)->alias('t1')
- ->leftJoin(TABLE_TASK)->alias('t2')->on('t1.task=t2.id')
- ->where('t2.id')->eq($taskID)
- ->andWhere('t1.deleted')->eq(0)
- ->andWhere('t2.deleted')->eq(0)
- ->fetchAll('id');
- }
- }
|