model.php 113 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016
  1. <?php
  2. /**
  3. * The model file of case 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 case
  9. * @version $Id: model.php 5108 2013-07-12 01:59:04Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class testcaseModel extends model
  15. {
  16. /**
  17. * 设置菜单。
  18. * Set menu.
  19. *
  20. * @param int $productID
  21. * @param int|string $branch
  22. * @access public
  23. * @return void
  24. */
  25. public function setMenu($productID, $branch = 0)
  26. {
  27. $this->loadModel('qa')->setMenu($productID, $branch);
  28. }
  29. /**
  30. * 创建一个用例。
  31. * Create a case.
  32. *
  33. * @param object $case
  34. * @access public
  35. * @return bool|int
  36. */
  37. public function create($case)
  38. {
  39. /* 插入测试用例。 */
  40. /* Insert testcase. */
  41. $this->testcaseTao->doCreate($case);
  42. if(dao::isError()) return false;
  43. $caseID = $this->dao->lastInsertID();
  44. /* 记录动态。*/
  45. /* Record log. */
  46. $this->loadModel('action');
  47. $this->action->create('case', $caseID, 'Opened');
  48. if($case->status == 'wait') $this->action->create('case', $caseID, 'submitReview');
  49. /* 存储上传的文件。 */
  50. /* Save upload files. */
  51. $this->config->dangers = '';
  52. $this->loadModel('file')->saveUpload('testcase', $caseID, 'autoscript', 'script', 'scriptName');
  53. $files = $this->loadModel('file')->saveUpload('testcase', $caseID);
  54. $this->testcaseTao->doCreateSpec($caseID, $case, $files);
  55. $this->loadModel('score')->create('testcase', 'create', $caseID);
  56. /* 插入用例步骤。 */
  57. /* Insert testcase steps. */
  58. $this->testcaseTao->insertSteps($caseID, $case->steps, $case->expects, $case->stepType);
  59. if(isset($case->auto) && $case->auto != 'auto')
  60. {
  61. ob_start();
  62. setcookie('onlyAutoCase', '0');
  63. }
  64. if(dao::isError()) return false;
  65. return $caseID;
  66. }
  67. /**
  68. * 获取模块的用例。
  69. * Get cases of modules.
  70. *
  71. * @param int|array $productID
  72. * @param int|string $branch
  73. * @param int|array $moduleIdList
  74. * @param string $browseType
  75. * @param string $auto no|unit
  76. * @param string $caseType
  77. * @param string $orderBy
  78. * @param object $pager
  79. * @param string $from
  80. * @access public
  81. * @return array
  82. */
  83. public function getModuleCases($productID, $branch = 0, $moduleIdList = 0, $browseType = '', $auto = 'no', $caseType = '', $orderBy = 'id_desc', $pager = null, $from = 'testcase')
  84. {
  85. $isProjectTab = $this->app->tab == 'project' && $from != 'doc';
  86. $isExecutionTab = $this->app->tab == 'execution' && $from != 'doc';
  87. $stmt = $this->dao->select('t1.*, t2.title as storyTitle, t2.deleted as storyDeleted')->from(TABLE_CASE)->alias('t1')
  88. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id');
  89. if($isProjectTab) $stmt = $stmt->leftJoin(TABLE_PROJECTCASE)->alias('t3')->on('t1.id=t3.case');
  90. if($isExecutionTab) $stmt = $stmt->leftJoin(TABLE_PROJECTCASE)->alias('t3')->on('t1.id=t3.case');
  91. return $stmt ->where('t1.product')->in($productID)
  92. ->beginIF($isProjectTab)->andWhere('t3.project')->eq($this->session->project)->fi()
  93. ->beginIF($isExecutionTab)->andWhere('t3.project')->eq($this->session->execution)->fi()
  94. ->beginIF($branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
  95. ->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi()
  96. ->beginIF($browseType == 'wait')->andWhere('t1.status')->eq($browseType)->fi()
  97. ->beginIF($browseType == 'needconfirm')
  98. ->andWhere('t2.version > t1.storyVersion')
  99. ->andWhere('t2.status')->eq('active')
  100. ->fi()
  101. ->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
  102. ->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
  103. ->beginIF($caseType)->andWhere('t1.type')->eq($caseType)->fi()
  104. ->andWhere('t1.deleted')->eq('0')
  105. ->orderBy($orderBy)
  106. ->page($pager)
  107. ->fetchAll('id', false);
  108. }
  109. /**
  110. * 获取执行的用例。
  111. * Get execution cases.
  112. *
  113. * @param string $browseType all|wait|needconfirm
  114. * @param int $executionID
  115. * @param int $productID
  116. * @param int $branchID
  117. * @param int $moduleID
  118. * @param int $paramID
  119. * @param string $orderBy
  120. * @param object $pager
  121. * @access public
  122. * @return array
  123. */
  124. public function getExecutionCases($browseType = 'all', $executionID = 0, $productID = 0, $branchID = 0, $moduleID = 0, $paramID = 0, $orderBy = 'id_desc', $pager = null)
  125. {
  126. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCases();
  127. if(strpos($orderBy, 'id_') !== false) $orderBy = str_replace('id_', 't2.id_', $orderBy);
  128. if(strpos($orderBy, 'order_') !== false) $orderBy = str_replace('order_', 't2.order_', $orderBy);
  129. if(strpos($orderBy, 'version_') !== false) $orderBy = str_replace('version_', 't2.version_', $orderBy);
  130. if($browseType == 'needconfirm')
  131. {
  132. return $this->dao->select('distinct t1.*, t2.*')->from(TABLE_PROJECTCASE)->alias('t1')
  133. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  134. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
  135. ->leftJoin(TABLE_MODULE)->alias('t4')->on('t2.module = t4.id')
  136. ->where('t1.project')->eq($executionID)
  137. ->andWhere('t2.deleted')->eq('0')
  138. ->andWhere('t3.version > t2.storyVersion')
  139. ->andWhere("t3.status")->eq('active')
  140. ->beginIF($productID)->andWhere('t1.product')->eq($productID)->fi()
  141. ->beginIF($productID && $branchID !== 'all')->andWhere('t2.branch')->eq($branchID)->fi()
  142. ->beginIF($moduleID)->andWhere('t4.path')->like("%,$moduleID,%")->fi()
  143. ->orderBy($orderBy)
  144. ->page($pager)
  145. ->fetchAll('id', false);
  146. }
  147. if($browseType == 'bysearch') return $this->testcaseTao->getExecutionCasesBySearch($executionID, $productID, $branchID, $paramID, $orderBy, $pager);
  148. return $this->dao->select('distinct t1.*, t2.*')->from(TABLE_PROJECTCASE)->alias('t1')
  149. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  150. ->leftJoin(TABLE_MODULE)->alias('t3')->on('t2.module = t3.id')
  151. ->where('t1.project')->eq($executionID)
  152. ->andWhere('t2.deleted')->eq('0')
  153. ->beginIF($browseType != 'all' && $browseType != 'byModule')->andWhere('t2.status')->eq($browseType)->fi()
  154. ->beginIF($productID)->andWhere('t1.product')->eq($productID)->fi()
  155. ->beginIF($productID && $branchID !== 'all')->andWhere('t2.branch')->eq($branchID)->fi()
  156. ->beginIF($moduleID)->andWhere('t3.path')->like("%,$moduleID,%")->fi()
  157. ->orderBy($orderBy)
  158. ->page($pager)
  159. ->fetchAll('id', false);
  160. }
  161. /**
  162. * Get case info by ID.
  163. *
  164. * @param int $caseID
  165. * @param int $version
  166. * @access public
  167. * @return object|bool
  168. */
  169. public function getById($caseID, $version = 0)
  170. {
  171. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCase();
  172. $case = $this->dao->findById($caseID)->from(TABLE_CASE)->fetch();
  173. if(!$case) return false;
  174. $case = $this->loadModel('file')->replaceImgURL($case, 'steps');
  175. $case = $this->processDateField($case);
  176. /* Get project and execution. */
  177. if($this->app->tab == 'project')
  178. {
  179. $case->project = $this->session->project;
  180. }
  181. elseif($this->app->tab == 'execution')
  182. {
  183. $case->execution = $this->session->execution;
  184. $case->project = $this->dao->select('project')->from(TABLE_PROJECT)->where('id')->eq($case->execution)->fetch('project');
  185. }
  186. else
  187. {
  188. $objects = $this->dao->select('t1.project as objectID, t2.type')->from(TABLE_PROJECTCASE)->alias('t1')
  189. ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project=t2.id')
  190. ->where('t1.case')->eq($caseID)
  191. ->fetchPairs();
  192. foreach($objects as $objectID => $objectType)
  193. {
  194. if($objectType == 'project') $case->project = $objectID;
  195. if(in_array($objectType, array('sprint', 'stage', 'kanban'))) $case->execution = $objectID;
  196. }
  197. }
  198. /* Set related variables. */
  199. $toBugs = $this->dao->select('id, title, severity, openedDate, status')->from(TABLE_BUG)->where('`case`')->eq($caseID)->fetchAll();
  200. $case->toBugs = array();
  201. $case->fromBugData = array();
  202. $case->linkCaseTitles = array();
  203. foreach($toBugs as $toBug) $case->toBugs[$toBug->id] = $toBug;
  204. if($case->story)
  205. {
  206. $story = $this->dao->findById($case->story)->from(TABLE_STORY)->fields('title, status, version')->fetch();
  207. $case->storyTitle = $story->title;
  208. $case->storyStatus = $story->status;
  209. $case->latestStoryVersion = $story->version;
  210. }
  211. if($case->fromBug) $case->fromBugData = $this->dao->findById($case->fromBug)->from(TABLE_BUG)->fields('id, title, severity, openedDate, status')->fetch();
  212. if($case->linkCase || $case->fromCaseID) $case->linkCaseTitles = $this->dao->select('id,title')->from(TABLE_CASE)->where('id')->in($case->linkCase)->orWhere('id')->eq($case->fromCaseID)->fetchPairs();
  213. $case->currentVersion = $version ? $version : $case->version;
  214. $case->files = $this->loadModel('file')->getByObject('testcase', $caseID);
  215. $case->steps = $this->testcaseTao->getSteps($caseID, $case->currentVersion);
  216. $spec = $this->dao->select('title,precondition,files')->from(TABLE_CASESPEC)->where('case')->eq($caseID)->andWhere('version')->eq($version)->fetch();
  217. if($spec)
  218. {
  219. $case->title = $spec->title;
  220. $case->precondition = $spec->precondition;
  221. $case->files = $this->file->getByIdList($spec->files);
  222. }
  223. return $case;
  224. }
  225. /**
  226. * 测试通过 id 列表和查询语句获取用例。
  227. * Get cases by id list and query string.
  228. *
  229. * @param array $caseIdList
  230. * @param string $query
  231. * @access public
  232. * @return array
  233. */
  234. public function getByList($caseIdList, $query = '')
  235. {
  236. if(!$caseIdList) return array();
  237. return $this->dao->select('*,precondition')->from(TABLE_CASE)
  238. ->where('deleted')->eq(0)
  239. ->andWhere('id')->in($caseIdList)
  240. ->beginIF($query)->andWhere($query)->fi()
  241. ->fetchAll('id', false);
  242. }
  243. /**
  244. * 获取用例列表。
  245. * Get test cases.
  246. *
  247. * @param int|array $productID
  248. * @param int|string $branch
  249. * @param string $browseType
  250. * @param int $queryID
  251. * @param int $moduleID
  252. * @param string $caseType
  253. * @param string $auto no|unit
  254. * @param string $orderBy
  255. * @param object $pager
  256. * @param string $from
  257. * @access public
  258. * @return array
  259. */
  260. public function getTestCases($productID, $branch, $browseType, $queryID, $moduleID, $caseType = '', $auto = 'no', $orderBy = 'id_desc', $pager = null, $from = 'testcase')
  261. {
  262. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCases();
  263. $modules = $moduleID ? $this->loadModel('tree')->getAllChildId($moduleID) : array();
  264. $browseType = ($browseType == 'bymodule' and $this->session->caseBrowseType and $this->session->caseBrowseType != 'bysearch') ? $this->session->caseBrowseType : $browseType;
  265. $auto = $this->cookie->onlyAutoCase ? 'auto' : $auto;
  266. if($browseType == 'bymodule' || $browseType == 'all' || $browseType == 'wait')
  267. {
  268. if($this->app->tab == 'project' && $from != 'doc') return $this->testcaseTao->getModuleProjectCases($productID, $branch, $modules, $browseType, $auto, $caseType, $orderBy, $pager);
  269. return $this->getModuleCases($productID, $branch, $modules, $browseType, $auto, $caseType, $orderBy, $pager, $from);
  270. }
  271. if($browseType == 'needconfirm') return $this->testcaseTao->getNeedConfirmList($productID, $branch, $modules, $auto, $caseType, $orderBy, $pager);
  272. if($browseType == 'bysuite') return $this->testcaseTao->getBySuite($productID, $branch, (int)$queryID, $modules, $auto, $orderBy, $pager);
  273. if($browseType == 'bysearch') return $this->getBySearch($productID, $branch, (int)$queryID, $auto, $orderBy, $pager);
  274. return array();
  275. }
  276. /**
  277. * 搜索用例。
  278. * Get cases by search.
  279. *
  280. * @param int $productID
  281. * @param int|string $branch
  282. * @param int $queryID
  283. * @param string $auto no|unit
  284. * @param string $orderBy
  285. * @param object $pager
  286. * @access public
  287. * @return array
  288. */
  289. public function getBySearch($productID, $branch = 0, $queryID = 0, $auto = 'no', $orderBy = 'id_desc', $pager = null)
  290. {
  291. $queryID = (int)$queryID;
  292. if($queryID)
  293. {
  294. $query = $this->loadModel('search')->getQuery($queryID);
  295. if($query)
  296. {
  297. $this->session->set('testcaseQuery', $query->sql);
  298. $this->session->set('testcaseForm', $query->form);
  299. }
  300. }
  301. if($this->session->testcaseQuery == false) $this->session->set('testcaseQuery', ' 1 = 1');
  302. $caseQuery = '(' . $this->session->testcaseQuery;
  303. // 将caseQuery中的字段替换成t1.字段
  304. $caseQuery = preg_replace('/`(.*?)`/', 't1.`$1`', $caseQuery);
  305. /* 处理用例查询中的产品条件。*/
  306. /* Process product condition in case query. */
  307. $queryProductID = $productID;
  308. if(strpos($this->session->testcaseQuery, "`product` = 'all'") !== false)
  309. {
  310. $caseQuery = str_replace("t1.`product` = 'all'", '1', $caseQuery);
  311. $caseQuery .= ' AND t1.`product` ' . helper::dbIN($this->app->user->view->products);
  312. $queryProductID = 'all';
  313. }
  314. if($this->app->tab == 'project') $caseQuery = str_replace('t1.`product`', 't2.`product`', $caseQuery);
  315. /* 处理用例查询中的产品分支条件。*/
  316. /* Process branch condition in case query. */
  317. if($branch !== 'all' && strpos($caseQuery, '`branch` =') === false) $caseQuery .= " AND t1.`branch` in ('$branch')";
  318. if(strpos($caseQuery, "`branch` = 'all'") !== false) $caseQuery = str_replace("t1.`branch` = 'all'", '1 = 1', $caseQuery);
  319. $caseQuery .= ')';
  320. /* Search criteria under compatible project. */
  321. $sql = $this->dao->select('DISTINCT t1.*,t3.title as storyTitle')->from(TABLE_CASE)->alias('t1');
  322. if($this->app->tab == 'project') $sql->leftJoin(TABLE_PROJECTCASE)->alias('t2')->on('t1.id = t2.case');
  323. $sql->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id');
  324. return $sql->where($caseQuery)
  325. ->beginIF($this->app->tab == 'project' && $this->config->systemMode == 'ALM')->andWhere('t2.project')->eq($this->session->project)->fi()
  326. ->beginIF($this->app->tab == 'project' && !empty($productID) && $queryProductID != 'all')->andWhere('t2.product')->eq($productID)->fi()
  327. ->beginIF($this->app->tab != 'project' && !empty($productID) && $queryProductID != 'all')->andWhere('t1.product')->eq($productID)->fi()
  328. ->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
  329. ->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
  330. ->andWhere('t1.deleted')->eq('0')
  331. ->orderBy($orderBy)
  332. ->page($pager, 't1.id')
  333. ->fetchAll('id', false);
  334. }
  335. /**
  336. * 根据指派给获取用例。
  337. * Get cases by assignedTo.
  338. *
  339. * @param string $account
  340. * @param string $auto no|unit|skip
  341. * @param string $orderBy
  342. * @param object $pager
  343. * @access public
  344. * @return array
  345. */
  346. public function getByAssignedTo($account, $auto = 'no', $orderBy = 'id_desc', $pager = null)
  347. {
  348. return $this->dao->select('t1.id AS run, t1.task, t1.case, t1.version, t1.assignedTo, t1.lastRunner, t1.lastRunDate, t1.lastRunResult, t1.status AS lastRunStatus, t2.id AS id, t2.project, t2.pri, t2.title, t2.type, t2.openedBy, t2.color, t2.product, t2.lib, t2.branch, t2.module, t2.status, t2.auto, t2.story, t2.storyVersion, t3.name AS taskName')->from(TABLE_TESTRUN)->alias('t1')
  349. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  350. ->leftJoin(TABLE_TESTTASK)->alias('t3')->on('t1.task = t3.id')
  351. ->where('t1.assignedTo')->eq($account)
  352. ->andWhere('t3.deleted')->eq('0')
  353. ->andWhere('t2.deleted')->eq('0')
  354. ->andWhere('t3.status')->ne('done')
  355. ->beginIF(strpos($auto, 'skip') === false && $auto != 'unit')->andWhere('t2.auto')->ne('unit')->fi()
  356. ->beginIF($auto == 'unit')->andWhere('t2.auto')->eq('unit')->fi()
  357. ->orderBy($orderBy)
  358. ->page($pager)
  359. ->fetchAll(strpos($auto, 'run') !== false ? 'run' : 'id');
  360. }
  361. /**
  362. * 根据创建者获取用例。
  363. * Get cases by openedBy
  364. *
  365. * @param string $account
  366. * @param string $auto no|unit|skip
  367. * @param string $orderBy
  368. * @param object $pager
  369. * @access public
  370. * @return array
  371. */
  372. public function getByOpenedBy($account, $auto = 'no', $orderBy = 'id_desc', $pager = null)
  373. {
  374. return $this->dao->findByOpenedBy($account)->from(TABLE_CASE)
  375. ->andWhere('deleted')->eq('0')
  376. ->beginIF($auto != 'skip')->andWhere('product')->ne(0)->fi()
  377. ->beginIF($auto != 'skip' && $auto != 'unit')->andWhere('auto')->ne('unit')->fi()
  378. ->beginIF($auto == 'unit')->andWhere('auto')->eq('unit')->fi()
  379. ->orderBy($orderBy)
  380. ->page($pager)
  381. ->fetchAll('id', false);
  382. }
  383. /**
  384. * 根据状态获取用例。
  385. * Get cases by status.
  386. *
  387. * @param int $productID
  388. * @param int|string $branch
  389. * @param string $type all|needconfirm
  390. * @param string $status all|normal|blocked|investigate
  391. * @param int $moduleID
  392. * @param string $auto no|unit|skip
  393. * @param string $orderBy
  394. * @param object $pager
  395. * @access public
  396. * @return array
  397. */
  398. public function getByStatus($productID = 0, $branch = 0, $type = 'all', $status = 'all', $moduleID = 0, $auto = 'no', $orderBy = 'id_desc', $pager = null)
  399. {
  400. $modules = $moduleID ? $this->loadModel('tree')->getAllChildId($moduleID) : '0';
  401. $cases = $this->dao->select('t1.*, t2.title AS storyTitle')->from(TABLE_CASE)->alias('t1')
  402. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
  403. ->where('1=1')
  404. ->beginIF($productID)->andWhere('t1.product')->eq((int) $productID)->fi()
  405. ->beginIF(!$productID)->andWhere('t1.product')->in($this->app->user->view->products)->fi()
  406. ->beginIF($branch)->andWhere('t1.branch')->eq($branch)->fi()
  407. ->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
  408. ->beginIF($type == 'needconfirm')
  409. ->andWhere('t2.status')->eq('active')
  410. ->andWhere('t2.version > t1.storyVersion')
  411. ->fi()
  412. ->beginIF($status != 'all')->andWhere('t1.status')->eq($status)->fi()
  413. ->beginIF($auto == 'unit')->andWhere('t1.auto')->eq('unit')->fi()
  414. ->beginIF($auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
  415. ->andWhere('t1.deleted')->eq('0')
  416. ->orderBy($orderBy)
  417. ->page($pager)
  418. ->fetchAll('id', false);
  419. return $this->appendData($cases);
  420. }
  421. /**
  422. * 根据产品获取用例。
  423. * Get cases by product id.
  424. *
  425. * @param int $productID
  426. * @access public
  427. * @return array
  428. */
  429. public function getByProduct($productID)
  430. {
  431. return $this->dao->select('*')->from(TABLE_CASE)->where('deleted')->eq('0')->andWhere('product')->eq($productID)->fetchAll('id', false);
  432. }
  433. /**
  434. * 通过产品 id 和分支获取用例键对。
  435. * Get case pairs by product id and branch.
  436. *
  437. * @param int $productID
  438. * @param int|array $branch
  439. * @param string $search
  440. * @param int $limit
  441. * @access public
  442. * @return array
  443. */
  444. public function getPairsByProduct($productID, $branch = 0, $search = '', $limit = 0)
  445. {
  446. return $this->dao->select("id, concat_ws(':', id, title) as title")->from(TABLE_CASE)
  447. ->where('deleted')->eq(0)
  448. ->andWhere('product')->eq($productID)
  449. ->beginIF($branch)->andWhere('branch')->in($branch)->fi()
  450. ->beginIF(strlen(trim($search)))->andWhere('title')->like('%' . $search . '%')->fi()
  451. ->orderBy('id_desc')
  452. ->beginIF($limit)->limit($limit)->fi()
  453. ->fetchPairs();
  454. }
  455. /**
  456. * 获取关联需求的用例。
  457. * Get cases of a story.
  458. *
  459. * @param int $storyID
  460. * @access public
  461. * @return array
  462. */
  463. public function getStoryCases($storyID)
  464. {
  465. return $this->dao->select('id, project, title, pri, type, status, lastRunner, lastRunDate, lastRunResult')
  466. ->from(TABLE_CASE)
  467. ->where('story')->eq((int)$storyID)
  468. ->andWhere('deleted')->eq(0)
  469. ->fetchAll('id');
  470. }
  471. /**
  472. * 获取需求列表关联的用例数量数组。
  473. * Get counts of some stories' cases.
  474. *
  475. * @param array $stories
  476. * @access public
  477. * @return array
  478. */
  479. public function getStoryCaseCounts($stories)
  480. {
  481. if(empty($stories)) return array();
  482. $caseCounts = $this->dao->select('story, COUNT(1) AS cases')
  483. ->from(TABLE_CASE)
  484. ->where('story')->in($stories)
  485. ->andWhere('deleted')->eq(0)
  486. ->groupBy('story')
  487. ->fetchPairs();
  488. foreach($stories as $storyID)
  489. {
  490. if(!isset($caseCounts[$storyID])) $caseCounts[$storyID] = 0;
  491. }
  492. return $caseCounts;
  493. }
  494. /**
  495. * 获取导出的用例。
  496. * Get cases to export.
  497. *
  498. * @param string $exportType
  499. * @param int $taskID
  500. * @param string $orderBy
  501. * @param int $limit
  502. * @access public
  503. * @return array
  504. */
  505. public function getCasesToExport($exportType, $taskID, $orderBy, $limit)
  506. {
  507. if(strpos($orderBy, 'case') !== false)
  508. {
  509. list($field, $sort) = explode('_', $orderBy);
  510. $orderBy = '`' . $field . '`_' . $sort;
  511. }
  512. $queryCondition = preg_replace("/AND\s+t[0-9]\.scene\s+=\s+'0'/i", '', $this->session->testcaseQueryCondition);
  513. if($this->session->testcaseOnlyCondition)
  514. {
  515. $caseIdList = array();
  516. if($taskID) $caseIdList = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->fetchPairs();
  517. return $this->dao->select('*')->from(TABLE_CASE)->where($queryCondition)
  518. ->beginIF($taskID)->andWhere('id')->in($caseIdList)->fi()
  519. ->beginIF($exportType == 'selected')->andWhere('id')->in($this->cookie->checkedItem)->fi()
  520. ->orderBy($orderBy)
  521. ->beginIF($limit)->limit($limit)->fi()
  522. ->fetchAll('id', false);
  523. }
  524. $cases = array();
  525. $orderBy = " ORDER BY " . str_replace(array('|', '^A', '_'), ' ', $orderBy);
  526. $stmt = $this->dao->query($queryCondition . $orderBy . ($limit ? ' LIMIT ' . $limit : ''));
  527. while($row = $stmt->fetch())
  528. {
  529. $caseID = isset($row->case) ? $row->case : $row->id;
  530. if($exportType == 'selected' && $taskID && strpos(",{$this->cookie->checkedItem},", ",$row->id,") === false) continue;
  531. if($exportType == 'selected' && !$taskID && strpos(",{$this->cookie->checkedItem},", ",$caseID,") === false) continue;
  532. $row->id = $caseID;
  533. $cases[$caseID] = $row;
  534. }
  535. return $cases;
  536. }
  537. /**
  538. * 获取导出的用例的结果。
  539. * Get case results for export.
  540. *
  541. * @param array $caseIdList
  542. * @param int $taskID
  543. * @access public
  544. * @return array
  545. */
  546. public function getCaseResultsForExport($caseIdList, $taskID = 0)
  547. {
  548. $stmt = $this->dao->select('t1.*')->from(TABLE_TESTRESULT)->alias('t1')
  549. ->leftJoin(TABLE_TESTRUN)->alias('t2')->on('t1.run = t2.id')
  550. ->where('t1.`case`')->in($caseIdList)
  551. ->beginIF($taskID)->andWhere('t2.task')->eq($taskID)->fi()
  552. ->beginIF($this->app->tab == 'devops')->andWhere('t1.deploy')->eq($this->session->deployID)->fi()
  553. ->orderBy('id_desc')
  554. ->query();
  555. $results = array();
  556. while($result = $stmt->fetch())
  557. {
  558. if(!isset($results[$result->case])) $results[$result->case] = unserialize($result->stepResults);
  559. }
  560. return $results;
  561. }
  562. /**
  563. * 更新用例。
  564. * Update a case.
  565. *
  566. * @param object $case
  567. * @param object $oldCase
  568. * @param array $testtasks
  569. * @access public
  570. * @return bool|array
  571. */
  572. public function update($case, $oldCase, $testtasks = array())
  573. {
  574. $this->testcaseTao->doUpdate($case);
  575. if(dao::isError()) return false;
  576. $this->testcaseTao->updateCase2Project($oldCase, $case);
  577. if($oldCase->version != $case->version || !empty($case->stepChanged)) $this->testcaseTao->updateStep($case, $oldCase);
  578. if(isset($oldCase->toBugs) && isset($case->linkBug)) $this->testcaseTao->linkBugs($oldCase->id, array_keys($oldCase->toBugs), $case);
  579. if($case->branch && !empty($testtasks)) $this->testcaseTao->unlinkCaseFromTesttask($oldCase->id, $case->branch, $testtasks);
  580. $this->loadModel('file')->processFileDiffsForObject('testcase', $oldCase, $case);
  581. if($oldCase->version != $case->version) $this->testcaseTao->doCreateSpec($oldCase->id, $case, $case->files);
  582. /* Join the steps to diff. */
  583. if(!empty($case->stepChanged) && $case->steps)
  584. {
  585. $oldCase->steps = $this->joinStep($oldCase->steps);
  586. $case->steps = $this->joinStep($this->getByID($oldCase->id, $case->version)->steps);
  587. }
  588. else
  589. {
  590. unset($oldCase->steps);
  591. unset($case->steps);
  592. }
  593. return common::createChanges($oldCase, $case);
  594. }
  595. /**
  596. * 评审用例。
  597. * Review case.
  598. *
  599. * @param object $case
  600. * @param object $oldCase
  601. * @access public
  602. * @return bool
  603. */
  604. public function review($case, $oldCase)
  605. {
  606. $this->dao->update(TABLE_CASE)->data($case, 'result,comment')->autoCheck()->checkFlow()->where('id')->eq($oldCase->id)->exec();
  607. if(dao::isError()) return false;
  608. $changes = common::createChanges($oldCase, $case);
  609. $actionID = $this->loadModel('action')->create('case', $oldCase->id, 'Reviewed', $this->post->comment, ucfirst($case->result));
  610. $this->action->logHistory($actionID, $changes);
  611. return true;
  612. }
  613. /**
  614. * Batch review cases.
  615. *
  616. * @param array $caseIdList
  617. * @param string $result
  618. * @access public
  619. * @return bool
  620. */
  621. public function batchReview($caseIdList, $result)
  622. {
  623. $caseIdList = array_filter($caseIdList);
  624. if(!$caseIdList) return false;
  625. $oldCases = $this->getByList($caseIdList, "status = 'wait'");
  626. if(!$oldCases) return false;
  627. $now = helper::now();
  628. $case = new stdClass();
  629. $case->reviewedBy = $this->app->user->account;
  630. $case->reviewedDate = substr($now, 0, 10);
  631. $case->lastEditedBy = $this->app->user->account;
  632. $case->lastEditedDate = $now;
  633. if($result == 'pass') $case->status = 'normal';
  634. $this->dao->update(TABLE_CASE)->data($case)->autoCheck()->where('id')->in(array_keys($oldCases))->exec();
  635. if(dao::isError()) return false;
  636. $this->loadModel('action');
  637. foreach($oldCases as $oldCase)
  638. {
  639. $changes = common::createChanges($oldCase, $case);
  640. if($changes)
  641. {
  642. $actionID = $this->action->create('case', $oldCase->id, 'Reviewed', '', ucfirst($result));
  643. $this->action->logHistory($actionID, $changes);
  644. }
  645. }
  646. return !dao::isError();
  647. }
  648. /**
  649. * 获取可关联的用例。
  650. * Get cases to link.
  651. *
  652. * @param int $caseID
  653. * @param string $browseType
  654. * @param int $queryID
  655. * @access public
  656. * @return array
  657. */
  658. public function getCases2Link($caseID, $browseType = 'bySearch', $queryID = 0)
  659. {
  660. if($browseType != 'bySearch') return array();
  661. $case = $this->getByID($caseID);
  662. $cases2Link = $this->getBySearch($case->product, $case->branch, (int)$queryID, $auto = 'no', $orderBy = 'id');
  663. foreach($cases2Link as $key => $case2Link)
  664. {
  665. if($case2Link->id == $caseID) unset($cases2Link[$key]);
  666. if(in_array($case2Link->id, explode(',', $case->linkCase))) unset($cases2Link[$key]);
  667. }
  668. return $cases2Link;
  669. }
  670. /**
  671. * 获取可关联的 bug。
  672. * Get bugs to link.
  673. *
  674. * @param int $caseID
  675. * @param string $browseType
  676. * @param int $queryID
  677. * @param string $orderBy
  678. * @access public
  679. * @return array
  680. */
  681. public function getBugs2Link($caseID, $browseType = 'bySearch', $queryID = 0, $orderBy = 'id_asc')
  682. {
  683. if($browseType != 'bySearch') return array();
  684. $case = $this->getByID($caseID);
  685. $bugs2Link = $this->loadModel('bug')->getBySearch('bug', $case->product, (string)$case->branch, 0, 0, (int)$queryID, '', $orderBy);
  686. foreach($bugs2Link as $key => $bug2Link)
  687. {
  688. if($bug2Link->case != 0) unset($bugs2Link[$key]);
  689. }
  690. return $bugs2Link;
  691. }
  692. /**
  693. * 批量删除用例和场景。
  694. * Batch delete cases and scenes.
  695. *
  696. * @param array $caseIdList
  697. * @param array $sceneIdList
  698. * @access public
  699. * @return bool
  700. */
  701. public function batchDelete($caseIdList, $sceneIdList)
  702. {
  703. /* 过滤用例和场景。 */
  704. /* Filter cases and scenes. */
  705. $caseIdList = array_filter($caseIdList);
  706. $sceneIdList = array_filter($sceneIdList);
  707. if(!$caseIdList && !$sceneIdList) return false;
  708. $this->loadModel('action');
  709. /* 删除用例。 */
  710. /* Delete cases. */
  711. if($caseIdList)
  712. {
  713. $this->dao->update(TABLE_CASE)->set('deleted')->eq('1')->where('id')->in($caseIdList)->exec();
  714. foreach($caseIdList as $caseID) $this->action->create('case', (int)$caseID, 'deleted', '', actionModel::CAN_UNDELETED);
  715. }
  716. /* 删除场景。 */
  717. /* Delete scenes. */
  718. if($sceneIdList)
  719. {
  720. $this->dao->update(TABLE_SCENE)->set('deleted')->eq('1')->where('id')->in($sceneIdList)->exec();
  721. foreach($sceneIdList as $sceneID) $this->action->create('scene', $sceneID, 'deleted', '', actionModel::CAN_UNDELETED);
  722. }
  723. return !dao::isError();
  724. }
  725. /**
  726. * Batch change branch of cases and scenes.
  727. *
  728. * @param array $caseIdList
  729. * @param array $sceneIdList
  730. * @param int $branchID
  731. * @access public
  732. * @return bool
  733. */
  734. public function batchChangeBranch($caseIdList, $sceneIdList, $branchID)
  735. {
  736. if($branchID < 0 || $branchID > 16777215) return false; // The branch column's data type is mediumint unsigned and its range is 0-16777215.
  737. $caseIdList = array_filter($caseIdList);
  738. $sceneIdList = array_filter($sceneIdList);
  739. if(!$caseIdList && !$sceneIdList) return false;
  740. if($caseIdList) $this->batchChangeCaseBranch($caseIdList, $branchID);
  741. if($sceneIdList) $this->batchChangeSceneBranch($sceneIdList, $branchID);
  742. return !dao::isError();
  743. }
  744. /**
  745. * Batch change branch of cases.
  746. *
  747. * @param array $caseIdList
  748. * @param int $branchID
  749. * @access public
  750. * @return bool
  751. */
  752. public function batchChangeCaseBranch($caseIdList, $branchID)
  753. {
  754. if(!$caseIdList) return false;
  755. if($branchID < 0 || $branchID > 16777215) return false; // The branch column's data type is mediumint unsigned and its range is 0-16777215.
  756. $oldCases = $this->getByList($caseIdList, "branch != '{$branchID}'");
  757. if(!$oldCases) return false;
  758. $case = new stdclass();
  759. $case->branch = $branchID;
  760. $case->lastEditedBy = $this->app->user->account;
  761. $case->lastEditedDate = helper::now();
  762. $this->dao->update(TABLE_CASE)->data($case)->where('id')->in(array_keys($oldCases))->exec();
  763. if(dao::isError()) return false;
  764. $this->loadModel('action');
  765. foreach($oldCases as $oldCase)
  766. {
  767. $changes = common::createChanges($oldCase, $case);
  768. if($changes)
  769. {
  770. $actionID = $this->action->create('case', $oldCase->id, 'edited');
  771. $this->action->logHistory($actionID, $changes);
  772. }
  773. }
  774. return !dao::isError();
  775. }
  776. /**
  777. * Batch change branch of scenes.
  778. *
  779. * @param array $sceneIdList
  780. * @param int $branchID
  781. * @access public
  782. * @return bool
  783. */
  784. public function batchChangeSceneBranch($sceneIdList, $branchID)
  785. {
  786. if(!$sceneIdList) return false;
  787. if($branchID < 0 || $branchID > 16777215) return false; // The branch column's data type is mediumint unsigned and its range is 0-16777215.
  788. $oldScenes = $this->getScenesByList($sceneIdList, "branch != '{$branchID}'");
  789. if(!$oldScenes) return false;
  790. $scene = new stdclass();
  791. $scene->branch = $branchID;
  792. $scene->lastEditedBy = $this->app->user->account;
  793. $scene->lastEditedDate = helper::now();
  794. $this->dao->update(TABLE_SCENE)->data($scene)->where('id')->in(array_keys($oldScenes))->exec();
  795. if(dao::isError()) return false;
  796. $this->loadModel('action');
  797. foreach($oldScenes as $oldScene)
  798. {
  799. $changes = common::createChanges($oldScene, $scene);
  800. if($changes)
  801. {
  802. $actionID = $this->action->create('scene', $oldScene->id, 'edited');
  803. $this->action->logHistory($actionID, $changes);
  804. }
  805. }
  806. return !dao::isError();
  807. }
  808. /**
  809. * Batch change module of cases and scenes.
  810. *
  811. * @param array $caseIdList
  812. * @param array $sceneIdList
  813. * @param int $moduleID
  814. * @access public
  815. * @return bool
  816. */
  817. public function batchChangeModule($caseIdList, $sceneIdList, $moduleID)
  818. {
  819. if($moduleID < 0 || $moduleID > 16777215) return false; // The module column's data type is mediumint unsigned and its range is 0-16777215.
  820. $caseIdList = array_filter($caseIdList);
  821. $sceneIdList = array_filter($sceneIdList);
  822. if(!$caseIdList && !$sceneIdList) return false;
  823. if($caseIdList) $this->batchChangeCaseModule($caseIdList, $moduleID);
  824. if($sceneIdList) $this->batchChangeSceneModule($sceneIdList, $moduleID);
  825. return !dao::isError();
  826. }
  827. /**
  828. * Batch change module of cases.
  829. *
  830. * @param array $caseIdList
  831. * @param int $moduleID
  832. * @access public
  833. * @return bool
  834. */
  835. public function batchChangeCaseModule($caseIdList, $moduleID)
  836. {
  837. if(!$caseIdList) return false;
  838. if($moduleID < 0 || $moduleID > 16777215) return false; // The module column's data type is mediumint unsigned and its range is 0-16777215.
  839. $oldCases = $this->getByList($caseIdList, "module != '{$moduleID}'");
  840. if(!$oldCases) return false;
  841. $case = new stdclass();
  842. $case->module = $moduleID;
  843. $case->lastEditedBy = $this->app->user->account;
  844. $case->lastEditedDate = helper::now();
  845. $this->dao->update(TABLE_CASE)->data($case)->where('id')->in(array_keys($oldCases))->exec();
  846. if(dao::isError()) return false;
  847. $this->loadModel('action');
  848. foreach($oldCases as $oldCase)
  849. {
  850. $changes = common::createChanges($oldCase, $case);
  851. if($changes)
  852. {
  853. $actionID = $this->action->create('case', $oldCase->id, 'edited');
  854. $this->action->logHistory($actionID, $changes);
  855. }
  856. }
  857. return !dao::isError();
  858. }
  859. /**
  860. * Batch change module of scenes.
  861. *
  862. * @param array $sceneIdList
  863. * @param int $moduleID
  864. * @access public
  865. * @return bool
  866. */
  867. public function batchChangeSceneModule($sceneIdList, $moduleID)
  868. {
  869. if(!$sceneIdList) return false;
  870. if($moduleID < 0 || $moduleID > 16777215) return false; // The module column's data type is mediumint unsigned and its range is 0-16777215.
  871. $oldScenes = $this->getScenesByList($sceneIdList, "module != '{$moduleID}'");
  872. if(!$oldScenes) return false;
  873. $scene = new stdclass();
  874. $scene->module = $moduleID;
  875. $scene->lastEditedBy = $this->app->user->account;
  876. $scene->lastEditedDate = helper::now();
  877. $this->dao->update(TABLE_SCENE)->data($scene)->where('id')->in(array_keys($oldScenes))->exec();
  878. if(dao::isError()) return false;
  879. $this->loadModel('action');
  880. foreach($oldScenes as $oldScene)
  881. {
  882. $changes = common::createChanges($oldScene, $scene);
  883. if($changes)
  884. {
  885. $actionID = $this->action->create('scene', $oldScene->id, 'edited');
  886. $this->action->logHistory($actionID, $changes);
  887. }
  888. }
  889. return !dao::isError();
  890. }
  891. /**
  892. * Batch change type of cases.
  893. *
  894. * @param array $caseIdList
  895. * @param string $type
  896. * @access public
  897. * @return bool
  898. */
  899. public function batchChangeType($caseIdList, $type)
  900. {
  901. if(!$type) return false;
  902. $caseIdList = array_filter($caseIdList);
  903. if(!$caseIdList) return false;
  904. $oldCases = $this->getByList($caseIdList, "type != '{$type}'");
  905. if(!$oldCases) return false;
  906. $case = new stdClass();
  907. $case->type = $type;
  908. $case->lastEditedBy = $this->app->user->account;
  909. $case->lastEditedDate = helper::now();
  910. $this->dao->update(TABLE_CASE)->data($case)->autoCheck()->where('id')->in(array_keys($oldCases))->exec();
  911. if(dao::isError()) return false;
  912. $this->loadModel('action');
  913. foreach($oldCases as $oldCase)
  914. {
  915. $changes = common::createChanges($oldCase, $case);
  916. if($changes)
  917. {
  918. $actionID = $this->action->create('case', $oldCase->id, 'Edited', '', ucfirst($type));
  919. $this->action->logHistory($actionID, $changes);
  920. }
  921. }
  922. return !dao::isError();
  923. }
  924. /**
  925. * Batch confirm story change of cases.
  926. *
  927. * @param array $caseIdList
  928. * @access public
  929. * @return bool
  930. */
  931. public function batchConfirmStoryChange($caseIdList)
  932. {
  933. $caseIdList = array_filter($caseIdList);
  934. if(!$caseIdList) return false;
  935. $cases = $this->getByList($caseIdList);
  936. if(!$cases) return false;
  937. $storyIdList = array_unique(array_filter(array_map(function($case){return $case->story;}, $cases)));
  938. if(!$storyIdList) return false;
  939. $stories = $this->dao->select('id, version')->from(TABLE_STORY)->where('id')->in($storyIdList)->fetchPairs();
  940. $this->loadModel('action');
  941. foreach($cases as $case)
  942. {
  943. $storyVersion = zget($stories, $case->story, 0);
  944. if(!$storyVersion) continue;
  945. $this->dao->update(TABLE_CASE)->set('storyVersion')->eq($storyVersion)->where('id')->eq($case->id)->exec();
  946. $this->action->create('case', $case->id, 'confirmed', '', $storyVersion);
  947. }
  948. return !dao::isError();
  949. }
  950. /**
  951. * 将步骤转为字符串,并且区分它们。
  952. * Join steps to a string, thus can diff them.
  953. *
  954. * @param array $steps
  955. * @access public
  956. * @return string
  957. */
  958. public function joinStep($steps)
  959. {
  960. $return = '';
  961. if(empty($steps)) return $return;
  962. foreach($steps as $step) $return .= $step->desc . ' EXPECT:' . $step->expect . "\n";
  963. return $return;
  964. }
  965. /**
  966. * 从 bug 的步骤创建用例步骤。
  967. * Create case steps from a bug's step.
  968. *
  969. * @param string $steps
  970. * @access public
  971. * @return array
  972. */
  973. public function createStepsFromBug($steps)
  974. {
  975. /* 初始化步骤相关变量,获取步骤的描述、结果和期望,以及他们在字符串中的位置。 */
  976. /* Initializes the step variable, and get the desc, result and expect of the step and its position. */
  977. $steps = strip_tags($steps);
  978. $caseSteps = array((object)array('name' => '1', 'desc' => $steps, 'step' => $steps, 'expect' => '')); // the default steps before parse.
  979. $lblStep = strip_tags($this->lang->bug->tplStep);
  980. $lblResult = strip_tags($this->lang->bug->tplResult);
  981. $lblExpect = strip_tags($this->lang->bug->tplExpect);
  982. $lblStepPos = strpos($steps, $lblStep);
  983. $lblResultPos = strpos($steps, $lblResult);
  984. $lblExpectPos = strpos($steps, $lblExpect);
  985. /* 如果 bug 的步骤没有描述、结果或者期望,返回默认步骤。 */
  986. /* If steps don't have desc, result or expect, return default steps. */
  987. if($lblStepPos === false || $lblResultPos === false || $lblExpectPos === false) return $caseSteps;
  988. /* 计算描述和期望。 */
  989. /* Process desc and expect. */
  990. $caseSteps = substr($steps, $lblStepPos + strlen($lblStep), $lblResultPos - strlen($lblStep) - $lblStepPos);
  991. $caseExpect = substr($steps, $lblExpectPos + strlen($lblExpect));
  992. $caseSteps = trim($caseSteps);
  993. $caseExpect = trim($caseExpect);
  994. /* 计算步骤。 */
  995. /* Process steps. */
  996. $caseSteps = explode("\n", trim($caseSteps));
  997. $stepCount = count($caseSteps);
  998. foreach($caseSteps as $key => $caseStep)
  999. {
  1000. $expect = $key + 1 == $stepCount ? $caseExpect : '';
  1001. $caseSteps[$key] = (object)array('name' => '1', 'desc' => trim($caseStep), 'step' => trim($caseStep), 'expect' => $expect, 'type' => 'item');
  1002. }
  1003. return $caseSteps;
  1004. }
  1005. /**
  1006. * 判断当前动作是否可以点击。
  1007. * Adjust the action is clickable.
  1008. *
  1009. * @param object $case
  1010. * @param string $action
  1011. * @access public
  1012. * @return bool
  1013. */
  1014. public static function isClickable($case, $action)
  1015. {
  1016. $canBeChanged = common::canBeChanged('case', $case);
  1017. if(!$canBeChanged) return false;
  1018. global $config;
  1019. $action = strtolower($action);
  1020. if($action == 'runcase') return (empty($case->lib) || !empty($case->product)) && $case->auto == 'no' && $case->status != 'wait';
  1021. if($action == 'runresult') return !$case->lib || !empty($case->product);
  1022. if($action == 'importtolib') return !$case->lib || !empty($case->product);
  1023. if($action == 'ztfrun') return $case->auto == 'auto';
  1024. if($action == 'confirmchange') return isset($case->caseStatus) && isset($case->caseVersion) && $case->caseStatus != 'wait' && $case->version < $case->caseVersion;
  1025. if($action == 'confirmstorychange') return !empty($case->needconfirm) || (isset($case->browseType) && $case->browseType == 'needconfirm');
  1026. if($action == 'createbug') return isset($case->caseFails) && $case->caseFails > 0;
  1027. if($action == 'create') return !$case->lib || !empty($case->product);
  1028. if($action == 'review') return ($config->testcase->needReview || !empty($config->testcase->forceReview)) && (isset($case->caseStatus) ? $case->caseStatus == 'wait' : $case->status == 'wait');
  1029. if($action == 'showscript') return $case->auto == 'auto';
  1030. if($action == 'createcase') return !isset($case->lib) || ($case->lib && empty($case->product));
  1031. /* 判断确认撤销操作按钮的权限。 */
  1032. /* Check confirmdemandretract priv. */
  1033. if($action == 'confirmdemandretract') return !empty($case->confirmeActionType) && $case->confirmeActionType == 'confirmedretract';
  1034. /* 判断确认移除操作按钮的权限。 */
  1035. /* Check confirmdemandunlink priv. */
  1036. if($action == 'confirmdemandunlink') return !empty($case->confirmeActionType) && $case->confirmeActionType == 'confirmedunlink';
  1037. return true;
  1038. }
  1039. /**
  1040. * 获取导入的字段。
  1041. * Get fields for import.
  1042. *
  1043. * @param int $productID
  1044. * @access public
  1045. * @return array
  1046. */
  1047. public function getImportFields($productID = 0)
  1048. {
  1049. /* If type of the product isn't normal, set language item of branch. */
  1050. /* 如果产品是多分支产品,设置分支语言项。 */
  1051. $product = $this->loadModel('product')->getById($productID);
  1052. if($product && $product->type != 'normal') $this->lang->testcase->branch = $this->lang->product->branchName[$product->type];
  1053. $caseLang = $this->lang->testcase;
  1054. $caseConfig = $this->config->testcase;
  1055. $fields = explode(',', $caseConfig->exportFields);
  1056. foreach($fields as $key => $fieldName)
  1057. {
  1058. /* 设置字段的语言项。 */
  1059. /* Set language item of the fieldName. */
  1060. $fieldName = trim($fieldName);
  1061. $fields[$fieldName] = isset($caseLang->{$fieldName}) ? $caseLang->{$fieldName} : $fieldName;
  1062. unset($fields[$key]);
  1063. }
  1064. return $fields;
  1065. }
  1066. /**
  1067. * 导入测试用例到用例库。
  1068. * Import cases to lib.
  1069. *
  1070. * @param array $cases
  1071. * @param array $steps
  1072. * @param array $files
  1073. * @access public
  1074. * @return bool
  1075. */
  1076. public function importToLib($cases, $steps, $files)
  1077. {
  1078. $this->loadModel('action');
  1079. $caseIdList = array_column($cases, 'id');
  1080. $oldCaseList = $this->getByList($caseIdList);
  1081. foreach($cases as $case)
  1082. {
  1083. /* 如果用例没有 ID,插入用例。 */
  1084. /* If case id is not exist, insert it. */
  1085. if(!isset($case->id))
  1086. {
  1087. $this->testcaseTao->doCreate($case);
  1088. if(!dao::isError())
  1089. {
  1090. $caseID = $this->dao->lastInsertID();
  1091. $this->action->create('case', $caseID, 'tolib', '', $case->fromCaseID);
  1092. }
  1093. }
  1094. /* 如果用例有 ID,更新用例。 */
  1095. /* If case id is exist, update it. */
  1096. else
  1097. {
  1098. $caseID = $case->id;
  1099. $oldCase = isset($oldCaseList[$caseID]) ? $oldCaseList[$caseID] : null;
  1100. $this->testcaseTao->doUpdate($case);
  1101. $this->action->create('case', $caseID, 'updatetolib', '', $case->fromCaseID);
  1102. $this->dao->delete()->from(TABLE_CASESTEP)->where('`case`')->eq($caseID)->exec();
  1103. $removeFiles = $this->dao->select('id,pathname')->from(TABLE_FILE)->where('`objectID`')->eq($caseID)->andWhere('objectType')->eq('testcase')->fetchAll('id');
  1104. $this->dao->delete()->from(TABLE_FILE)->where('`objectID`')->eq($caseID)->andWhere('objectType')->eq('testcase')->exec();
  1105. foreach($removeFiles as $fileID => $file)
  1106. {
  1107. if(empty($file->pathname)) continue;
  1108. $filePath = pathinfo($file->pathname, PATHINFO_BASENAME);
  1109. $datePath = substr($file->pathname, 0, 6);
  1110. $filePath = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . "{$datePath}/" . $filePath;
  1111. if(file_exists($filePath)) unlink($filePath);
  1112. }
  1113. if(!empty($oldCase) && $oldCase->lib && empty($oldCase->product))
  1114. {
  1115. $fromcaseVersion = $this->dao->select('fromCaseVersion')->from(TABLE_CASE)->where('fromCaseID')->eq($case->id)->fetch('fromCaseVersion');
  1116. $fromcaseVersion = (int)$fromcaseVersion + 1;
  1117. $this->dao->update(TABLE_CASE)->set('`fromCaseVersion`')->eq($fromcaseVersion)->where('`fromCaseID`')->eq($case->id)->exec();
  1118. }
  1119. }
  1120. if(isset($caseID))
  1121. {
  1122. $this->testcaseTao->importSteps($caseID, zget($steps, $case->fromCaseID, array()));
  1123. $this->testcaseTao->importFiles($caseID, zget($files, $case->fromCaseID, array()));
  1124. }
  1125. }
  1126. return !dao::isError();
  1127. }
  1128. /**
  1129. * 导入用例关联的模块。
  1130. * Import case related modules.
  1131. *
  1132. * @param int $libID
  1133. * @param int $oldModuleID
  1134. * @param int $maxOrder
  1135. * @access public
  1136. * @return int
  1137. */
  1138. public function importCaseRelatedModules($libID, $oldModuleID = 0, $maxOrder = 0)
  1139. {
  1140. /* If module has been imported, return imported module id. */
  1141. $moduleID = $this->checkModuleImported($libID, $oldModuleID);
  1142. if($moduleID) return $moduleID;
  1143. /* Build old module, and insert it. */
  1144. $oldModule = $this->dao->select('name, parent, grade, `order`, short')->from(TABLE_MODULE)->where('id')->eq($oldModuleID)->fetch();
  1145. $oldModule->root = $libID;
  1146. $oldModule->from = $oldModuleID;
  1147. $oldModule->type = 'caselib';
  1148. if(!empty($maxOrder)) $oldModule->order = $maxOrder + $oldModule->order;
  1149. $this->dao->insert(TABLE_MODULE)->data($oldModule)->autoCheck()->exec();
  1150. if(!dao::isError())
  1151. {
  1152. /* Get new module id. */
  1153. $newModuleID = $this->dao->lastInsertID();
  1154. /* Set path and parent. */
  1155. if($oldModule->parent)
  1156. {
  1157. /* If old module has parent module, import parent module. */
  1158. $parentModuleID = $this->importCaseRelatedModules($libID, $oldModule->parent, !empty($maxOrder) ? $maxOrder : 0);
  1159. $parentModule = $this->dao->select('id, path')->from(TABLE_MODULE)->where('id')->eq($parentModuleID)->fetch();
  1160. $parent = $parentModule->id;
  1161. $path = $parentModule->path . "$newModuleID,";
  1162. }
  1163. else
  1164. {
  1165. $path = ",$newModuleID,";
  1166. $parent = 0;
  1167. }
  1168. /* Update path and parent. */
  1169. $this->dao->update(TABLE_MODULE)->set('parent')->eq($parent)->set('path')->eq($path)->where('id')->eq($newModuleID)->exec();
  1170. /* Return new module id. */
  1171. return $newModuleID;
  1172. }
  1173. return 0;
  1174. }
  1175. /**
  1176. * 检查模块是否可以导入。
  1177. * Adjust module is can import.
  1178. *
  1179. * @param int $libID
  1180. * @param int $oldModule
  1181. * @access public
  1182. * @return int
  1183. */
  1184. public function checkModuleImported($libID, $oldModule = 0)
  1185. {
  1186. /* Get module if from is oldModule and root is libID. */
  1187. $module = $this->dao->select('id')->from(TABLE_MODULE)
  1188. ->where('root')->eq($libID)
  1189. ->andWhere('`from`')->eq($oldModule)
  1190. ->andWhere('type')->eq('caselib')
  1191. ->andWhere('deleted')->eq(0)
  1192. ->fetch();
  1193. /* If module isn't exist, return 0. */
  1194. if(!$module) return 0;
  1195. /* Return module id. */
  1196. return $module->id;
  1197. }
  1198. /**
  1199. * 追加 bug 和用例执行结果信息
  1200. * Append bugs and results.
  1201. *
  1202. * @param array $cases
  1203. * @param string $type
  1204. * @param array $caseIdList
  1205. * @access public
  1206. * @return void
  1207. */
  1208. public function appendData($cases, $type = 'case', $caseIdList = array())
  1209. {
  1210. if(empty($caseIdList)) $caseIdList = array_keys($cases);
  1211. /* 查询用例的 bugs 和结果。 */
  1212. /* Get bugs and results. */
  1213. $queryField = $type == 'case' ? '`case`' : '`result`';
  1214. $caseBugs = $this->dao->select('COUNT(1) AS count, `case`')->from(TABLE_BUG)->where($queryField)->in($caseIdList)->andWhere('deleted')->eq(0)->groupBy('`case`')->fetchPairs('case', 'count');
  1215. $results = $this->dao->select('COUNT(1) AS count, `case`')->from(TABLE_TESTRESULT)->where('`case`')->in($caseIdList)->groupBy('`case`')->fetchPairs('case', 'count');
  1216. /* 查询用例的失败结果。 */
  1217. /* Get result fails of the the testcases. */
  1218. if($type != 'case') $queryField = '`run`';
  1219. $caseFails = $this->dao->select('COUNT(1) AS count, `case`')->from(TABLE_TESTRESULT)
  1220. ->where('caseResult')->eq('fail')
  1221. ->andWhere($queryField)->in($caseIdList)
  1222. ->groupBy('`case`')
  1223. ->fetchPairs('case','count');
  1224. /* 查询用例的步骤。 */
  1225. /* Get the the testcase steps. */
  1226. $queryTable = $type == 'case' ? TABLE_CASE : TABLE_TESTRUN;
  1227. $queryOn = $type == 'case' ? 't1.`case`=t2.`id`' : 't1.`case`=t2.`case`';
  1228. $queryField = $type == 'case' ? 't1.`case`' : 't2.`id`';
  1229. $steps = $this->dao->select('COUNT(DISTINCT t1.id) AS count, t1.`case`')->from(TABLE_CASESTEP)->alias('t1')
  1230. ->leftJoin($queryTable)->alias('t2')->on($queryOn)
  1231. ->where($queryField)->in($caseIdList)
  1232. ->andWhere('t1.type')->ne('group')
  1233. ->andWhere('t1.version=t2.version')
  1234. ->groupBy('t1.`case`')
  1235. ->fetchPairs('case', 'count');
  1236. /* 设置测试用例的 bugs 执行结果和步骤。 */
  1237. /* Set related bugs, results and steps of the testcases. */
  1238. foreach($cases as $case)
  1239. {
  1240. $caseID = $type == 'case' ? $case->id : $case->case;
  1241. $case->bugs = zget($caseBugs, $caseID, 0);
  1242. $case->results = zget($results, $caseID, 0);
  1243. $case->caseFails = zget($caseFails, $caseID, 0);
  1244. $case->stepNumber = zget($steps, $caseID, 0);
  1245. $case = $this->processDateField($case);
  1246. }
  1247. return $cases;
  1248. }
  1249. /**
  1250. * 检查是否不需要评审。
  1251. * Check whether force not review.
  1252. *
  1253. * @access public
  1254. * @return bool
  1255. */
  1256. public function forceNotReview()
  1257. {
  1258. if(!$this->config->testcase->needReview)
  1259. {
  1260. if(!isset($this->config->testcase->forceReview)) return true;
  1261. if(strpos(",{$this->config->testcase->forceReview},", ",{$this->app->user->account},") === false) return true;
  1262. }
  1263. elseif(isset($this->config->testcase->forceNotReview) && strpos(",{$this->config->testcase->forceNotReview},", ",{$this->app->user->account},") !== false)
  1264. {
  1265. return true;
  1266. }
  1267. return false;
  1268. }
  1269. /**
  1270. * 获取用例总结。
  1271. * Summary cases.
  1272. *
  1273. * @param array $cases
  1274. * @access public
  1275. * @return string
  1276. */
  1277. public function summary($cases)
  1278. {
  1279. $executed = 0;
  1280. foreach($cases as $case)
  1281. {
  1282. if($case->lastRunResult != '') $executed ++;
  1283. }
  1284. return sprintf($this->lang->testcase->summary, count($cases), $executed);
  1285. }
  1286. /**
  1287. * 同步用例到项目中。
  1288. * Sync case to project.
  1289. *
  1290. * @param object $case
  1291. * @param int $caseID
  1292. * @access public
  1293. * @return bool
  1294. */
  1295. public function syncCase2Project($case, $caseID)
  1296. {
  1297. $projects = array();
  1298. if(!empty($case->story))
  1299. {
  1300. $projects = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($case->story)->fetchPairs();
  1301. }
  1302. elseif($this->app->tab == 'project' && empty($case->story) && empty($case->project))
  1303. {
  1304. $projects = array($this->session->project);
  1305. }
  1306. elseif($this->app->tab == 'execution' && empty($case->story) && empty($case->execution))
  1307. {
  1308. $projects = array($this->session->execution);
  1309. }
  1310. if(!empty($case->project)) $projects[] = $case->project;
  1311. if(!empty($case->execution)) $projects[] = $case->execution;
  1312. if(empty($projects)) return false;
  1313. $this->loadModel('action');
  1314. $projects = array_filter(array_unique($projects));
  1315. $objectInfo = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->in($projects)->fetchAll('id', false);
  1316. foreach($projects as $projectID)
  1317. {
  1318. if(!isset($objectInfo[$projectID])) continue;
  1319. $lastOrder = (int)$this->dao->select('*')->from(TABLE_PROJECTCASE)->where('project')->eq($projectID)->orderBy('order_desc')->limit(1)->fetch('order');
  1320. $lastOrder++;
  1321. $data = new stdclass();
  1322. $data->project = $projectID;
  1323. $data->product = $case->product;
  1324. $data->case = $caseID;
  1325. $data->version = 1;
  1326. $data->order = $lastOrder;
  1327. $this->dao->insert(TABLE_PROJECTCASE)->data($data)->exec();
  1328. $object = $objectInfo[$projectID];
  1329. $objectType = $object->type;
  1330. if($objectType == 'project') $this->action->create('case', $caseID, 'linked2project', '', $projectID);
  1331. if(in_array($objectType, array('sprint', 'stage')) && $object->multiple) $this->action->create('case', $caseID, 'linked2execution', '', $projectID);
  1332. }
  1333. return !dao::isError();
  1334. }
  1335. /**
  1336. * processDatas
  1337. *
  1338. * @param array $datas
  1339. * @access public
  1340. * @return void
  1341. */
  1342. public function processDatas($datas)
  1343. {
  1344. if(isset($datas->datas)) $datas = $datas->datas;
  1345. $stepData = array();
  1346. $stepVars = 0;
  1347. foreach($datas as $row => $cellValue)
  1348. {
  1349. foreach($cellValue as $field => $value)
  1350. {
  1351. if($field != 'stepDesc' and $field != 'stepExpect') continue;
  1352. $value = (string)$value;
  1353. $steps = $value;
  1354. if(strpos($value, "\n"))
  1355. {
  1356. $steps = explode("\n", $value);
  1357. }
  1358. elseif(strpos($value, "\r"))
  1359. {
  1360. $steps = explode("\r", $value);
  1361. }
  1362. if(is_string($steps)) $steps = explode("\n", $steps);
  1363. $stepKey = str_replace('step', '', strtolower($field));
  1364. $caseStep = array();
  1365. foreach($steps as $step)
  1366. {
  1367. $trimmedStep = trim($step);
  1368. if(empty($trimmedStep)) continue;
  1369. preg_match('/^((([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $trimmedStep, $out);
  1370. if(!$out) preg_match('/^(([0-9]+)[.]([0-9]+))[.、](.*)$/Uu', $trimmedStep, $out);
  1371. if(!$out) preg_match('/^([0-9]+)[.、](.*)$/Uu', $trimmedStep, $out);
  1372. if($out && !empty(trim($out[1])))
  1373. {
  1374. $count = count($out);
  1375. $num = $out[1];
  1376. $parent = $count > 4 ? $out[2] : '0';
  1377. $grand = $count > 6 ? $out[3] : '0';
  1378. $step = trim($out[2]);
  1379. if($count > 4) $step = $count > 6 ? trim($out[6]) : trim($out[4]);
  1380. $caseStep[$num]['content'] = $step;
  1381. $caseStep[$num]['number'] = $num;
  1382. $caseStep[$num]['type'] = $count > 4 ? 'item' : 'step';
  1383. if(!empty($parent)) $caseStep[$parent]['type'] = 'group';
  1384. if(!empty($grand)) $caseStep[$grand]['type'] = 'group';
  1385. }
  1386. elseif(isset($num))
  1387. {
  1388. $caseStep[$num]['content'] = isset($caseStep[$num]['content']) ? "{$caseStep[$num]['content']}\n{$step}" : "\n{$step}";
  1389. }
  1390. elseif($field == 'stepDesc')
  1391. {
  1392. $num = 1;
  1393. $caseStep[$num]['content'] = $step;
  1394. $caseStep[$num]['type'] = 'step';
  1395. $caseStep[$num]['number'] = $num;
  1396. }
  1397. elseif($field == 'stepExpect' && isset($stepData[$row]['desc']))
  1398. {
  1399. end($stepData[$row]['desc']);
  1400. $num = key($stepData[$row]['desc']);
  1401. $caseStep[$num]['content'] = $step;
  1402. $caseStep[$num]['number'] = $num;
  1403. }
  1404. }
  1405. unset($num);
  1406. unset($sign);
  1407. if($stepKey == 'expect' && !empty($stepData[$row]['desc']))
  1408. {
  1409. foreach($stepData[$row]['desc'] as $stepDescValue)
  1410. {
  1411. if(empty($stepDescValue['number'])) continue;
  1412. $caseNumber = $stepDescValue['number'];
  1413. if($stepDescValue && !isset($caseStep[$caseNumber]) || empty($caseStep[$caseNumber]['content'])) $caseStep[$caseNumber] = '';
  1414. }
  1415. }
  1416. $stepVars += count($caseStep, COUNT_RECURSIVE) - count($caseStep);
  1417. $stepData[$row][$stepKey] = $caseStep;
  1418. }
  1419. }
  1420. return $stepData;
  1421. }
  1422. /**
  1423. * Process date field when date is empty.
  1424. *
  1425. * @param object $case
  1426. * @access public
  1427. * @return object
  1428. */
  1429. public function processDateField($case)
  1430. {
  1431. foreach($case as $key => $value)
  1432. {
  1433. if(strpos($key, 'Date') !== false && helper::isZeroDate($value)) $case->$key = '';
  1434. }
  1435. return $case;
  1436. }
  1437. /**
  1438. * 处理批量表单内的用例步骤或预期。
  1439. * Process steps or expects in batch form.
  1440. *
  1441. * @param string $steps
  1442. * @access protected
  1443. * @return array
  1444. */
  1445. public function processStepsOrExpects($steps)
  1446. {
  1447. $caseSteps = array();
  1448. $stepTypes = array();
  1449. $steps = explode("\n", trim($steps));
  1450. $preParent = $preGrand = $preNum = '0';
  1451. foreach($steps as $step)
  1452. {
  1453. $step = trim($step);
  1454. if(empty($step)) continue;
  1455. $appendToPre = false;
  1456. preg_match('/^((([0-9]+)[.]([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
  1457. if($out) $appendToPre = true; // 如果层级大于3级,追加到目前的步骤中
  1458. preg_match('/^((([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
  1459. if(!$out) preg_match('/^(([0-9]+)[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
  1460. if(!$out) preg_match('/^([0-9]+)[.、](.*)$/Uu', $step, $out);
  1461. $grand = $parent = $num = '0';
  1462. if(!$appendToPre && $out && isset($caseSteps[$out[1]]))
  1463. {
  1464. $appendToPre = true; // 如果已经设置过,追加到目前的步骤中
  1465. }
  1466. elseif(!$appendToPre && $out)
  1467. {
  1468. $count = count($out);
  1469. if($count > 6)
  1470. {
  1471. $grand = $out[3];
  1472. $parent = $out[4];
  1473. $num = $out[5];
  1474. }
  1475. elseif($count > 4)
  1476. {
  1477. $grand = $out[2];
  1478. $parent = $out[3];
  1479. }
  1480. else
  1481. {
  1482. $grand = $out[1];
  1483. }
  1484. $appendToPre = !(($grand == $preGrand && $parent == $preParent && $num == $preNum + 1)
  1485. || ($grand == $preGrand && $parent == $preParent + 1 && $num == '0')
  1486. || ($grand == $preGrand + 1 && $parent == '0' && $num == '0'));
  1487. }
  1488. if(!$appendToPre && $out)
  1489. {
  1490. $preGrand = $grand;
  1491. $preParent = $parent;
  1492. $preNum = $num;
  1493. $parent = $count > 4 ? $out[2] : '0';
  1494. $step = trim($out[2]);
  1495. $code = $out[1];
  1496. if($count > 4) $step = $count > 6 ? trim($out[6]) : trim($out[4]);
  1497. $caseSteps[$code] = $step;
  1498. $stepTypes[$code] = $count > 4 ? 'item' : 'step';
  1499. if($count > 4 && !empty($parent)) $stepTypes[$parent] = 'group';
  1500. if($count > 6 && !empty($grand)) $stepTypes[$grand] = 'group';
  1501. }
  1502. elseif($appendToPre && isset($code))
  1503. {
  1504. $caseSteps[$code] = isset($caseSteps[$code]) ? "{$caseSteps[$code]}\n{$step}" : "\n{$step}";
  1505. }
  1506. elseif(!$out && isset($code))
  1507. {
  1508. $caseSteps[$code] = isset($caseSteps[$code]) ? "{$caseSteps[$code]}\n{$step}" : "\n{$step}";
  1509. }
  1510. }
  1511. if(empty($caseSteps) && !empty($steps))
  1512. {
  1513. $caseSteps[] = implode("\n", $steps);
  1514. $stepTypes[] = 'step';
  1515. }
  1516. return array($caseSteps, $stepTypes);
  1517. }
  1518. /**
  1519. * 判断步骤是否变更。
  1520. * Judge if steps changed.
  1521. *
  1522. * @param object $case
  1523. * @param array $oldStep
  1524. * @access protected
  1525. * @return bool
  1526. */
  1527. protected function processStepsChanged($case, $oldStep)
  1528. {
  1529. $newSteps = array_filter($case->steps);
  1530. $stepChanged = (count($oldStep) != count($newSteps));
  1531. if(!$stepChanged)
  1532. {
  1533. $desc = array_values($case->steps);
  1534. $expect = array_values($case->expects);
  1535. $stepType = array_values($case->stepType);
  1536. foreach($oldStep as $index => $step)
  1537. {
  1538. if($stepChanged) break;
  1539. if(!isset($desc[$index]) || !isset($expect[$index]) || $step->desc != $desc[$index] || $step->expect != $expect[$index] || $step->type != $stepType[$index]) $stepChanged = true;
  1540. }
  1541. }
  1542. return $stepChanged;
  1543. }
  1544. /**
  1545. * 为 datatable 获取模块。
  1546. * Get modules for datatable.
  1547. *
  1548. * @param int $productID
  1549. * @access public
  1550. * @return void
  1551. */
  1552. public function getDatatableModules($productID)
  1553. {
  1554. $branches = $this->loadModel('branch')->getPairs($productID);
  1555. $modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0);
  1556. if(count($branches) <= 1) return $modules;
  1557. foreach($branches as $branchID => $branchName) $modules += $this->tree->getOptionMenu($productID, 'case', 0, (string)$branchID);
  1558. return $modules;
  1559. }
  1560. /**
  1561. * Batch change scene.
  1562. *
  1563. * @param array $caseIdList
  1564. * @param int $sceneID
  1565. * @access public
  1566. * @return bool
  1567. */
  1568. public function batchChangeScene($caseIdList, $sceneID)
  1569. {
  1570. if($sceneID < 0 || $sceneID > 16777215) return false; // The scene column's data type is mediumint unsigned and its range is 0-16777215.
  1571. $caseIdList = array_filter($caseIdList);
  1572. if(!$caseIdList) return false;
  1573. $oldCases = $this->getByList($caseIdList, "scene != '{$sceneID}'");
  1574. if(!$oldCases) return false;
  1575. $case = new stdclass();
  1576. $case->scene = $sceneID;
  1577. $case->lastEditedBy = $this->app->user->account;
  1578. $case->lastEditedDate = helper::now();
  1579. $this->dao->update(TABLE_CASE)->data($case)->where('id')->in(array_keys($oldCases))->exec();
  1580. if(dao::isError()) return false;
  1581. $this->loadModel('action');
  1582. foreach($oldCases as $oldCase)
  1583. {
  1584. $changes = common::createChanges($oldCase, $case);
  1585. if($changes)
  1586. {
  1587. $actionID = $this->action->create('case', $oldCase->id, 'edited');
  1588. $this->action->logHistory($actionID, $changes);
  1589. }
  1590. }
  1591. return !dao::isError();
  1592. }
  1593. /**
  1594. * 为构建场景菜单获取场景。
  1595. * Get scenes for menu.
  1596. *
  1597. * @param int $productID
  1598. * @param int $moduleID
  1599. * @param int $startScene
  1600. * @param string $branch
  1601. * @param int $currentScene
  1602. * @access public
  1603. * @return array
  1604. */
  1605. public function getScenesForMenu($productID, $moduleID, $startScene = 0, $branch = 'all', $currentScene = 0)
  1606. {
  1607. /* Set the start scene. */
  1608. $startScenePath = '';
  1609. if($startScene > 0)
  1610. {
  1611. $startScene = $this->getSceneByID($startScene);
  1612. if($startScene) $startScenePath = $startScene->path . '%';
  1613. }
  1614. $currentScenePath = '';
  1615. if($currentScene > 0)
  1616. {
  1617. $currentScene = $this->getSceneByID($currentScene);
  1618. if($currentScene) $currentScenePath = $currentScene->path . '%';
  1619. }
  1620. /* Return scenes. */
  1621. return $this->dao->select('*')->from(TABLE_SCENE)
  1622. ->where('deleted')->eq(0)
  1623. ->beginIF($productID)->andWhere('product')->eq($productID)->fi()
  1624. ->beginIF($moduleID > 0)->andWhere('module')->eq($moduleID)->fi()
  1625. ->beginIF($startScenePath)->andWhere('path')->like($startScenePath)->fi()
  1626. ->beginIF($currentScenePath)->andWhere('path')->notlike($currentScenePath)->fi()
  1627. ->beginIF($branch !== 'all' && $branch !== '')->andWhere('branch')->eq((int)$branch)->fi()
  1628. ->orderBy('grade desc, sort')
  1629. ->fetchAll('id', false);
  1630. }
  1631. /**
  1632. * 构建树数组。
  1633. * Build tree array.
  1634. *
  1635. * @param array $treeMenu
  1636. * @param array $scenes
  1637. * @param object $scene
  1638. * @param string $sceneName
  1639. * @access public
  1640. * @return void
  1641. */
  1642. public function buildTreeArray(&$treeMenu, $scenes, $scene, $sceneName = '/')
  1643. {
  1644. $parentScenes = explode(',', $scene->path);
  1645. foreach($parentScenes as $parentSceneID)
  1646. {
  1647. if(empty($parentSceneID)) continue;
  1648. if(empty($scenes[$parentSceneID])) continue;
  1649. $sceneName .= $scenes[$parentSceneID]->title . '/';
  1650. }
  1651. $sceneName = rtrim($sceneName, '/');
  1652. $sceneName .= "|$scene->id\n";
  1653. if(isset($treeMenu[$scene->id]) and !empty($treeMenu[$scene->id]))
  1654. {
  1655. if(isset($treeMenu[$scene->parent]))
  1656. {
  1657. $treeMenu[$scene->parent] .= $sceneName;
  1658. }
  1659. else
  1660. {
  1661. $treeMenu[$scene->parent] = $sceneName;
  1662. }
  1663. $treeMenu[$scene->parent] .= $treeMenu[$scene->id];
  1664. }
  1665. else
  1666. {
  1667. if(isset($treeMenu[$scene->parent]) and !empty($treeMenu[$scene->parent]))
  1668. {
  1669. $treeMenu[$scene->parent] .= $sceneName;
  1670. }
  1671. else
  1672. {
  1673. $treeMenu[$scene->parent] = $sceneName;
  1674. }
  1675. }
  1676. }
  1677. /**
  1678. * 根据 ID 获取一个场景。
  1679. * Get a scene by id.
  1680. *
  1681. * @param int $sceneID
  1682. * @access public
  1683. * @return object
  1684. */
  1685. public function getSceneByID($sceneID)
  1686. {
  1687. return $this->dao->select('*')->from(TABLE_SCENE)->where('id')->eq($sceneID)->fetch();
  1688. }
  1689. /**
  1690. * 创建一个场景。
  1691. * Create a scene.
  1692. *
  1693. * @access public
  1694. * @return bool
  1695. * @param object $scene
  1696. */
  1697. public function createScene($scene)
  1698. {
  1699. $product = zget($scene, 'product', 0);
  1700. $this->dao->insert(TABLE_SCENE)->data($scene)
  1701. ->autoCheck()
  1702. ->batchCheck($this->config->testcase->createscene->requiredFields, 'notempty')
  1703. ->checkIF($product, 'title', 'unique', "product = '{$product}'")
  1704. ->checkFlow()
  1705. ->exec();
  1706. if(dao::isError()) return false;
  1707. $sceneID = $this->dao->lastInsertID();
  1708. $scene->sort = $sceneID;
  1709. $scene->path = ',' . $sceneID . ',';
  1710. $scene->grade = 1;
  1711. if(!empty($scene->parent))
  1712. {
  1713. $parent = $this->getSceneByID($scene->parent);
  1714. if($parent)
  1715. {
  1716. $scene->path = $parent->path . $sceneID . ',';
  1717. $scene->grade = ++$parent->grade;
  1718. $scene->product = $parent->product;
  1719. $scene->branch = $parent->branch;
  1720. $scene->module = $parent->module;
  1721. }
  1722. else
  1723. {
  1724. $scene->parent = 0;
  1725. }
  1726. }
  1727. $this->dao->update(TABLE_SCENE)->data($scene)->where('id')->eq($sceneID)->exec();
  1728. if(dao::isError()) return false;
  1729. $this->loadModel('action')->create('scene', $sceneID, 'Opened');
  1730. return !dao::isError();
  1731. }
  1732. /**
  1733. * 获取所有的子场景 id。
  1734. * Get all children id.
  1735. *
  1736. * @param int $sceneID
  1737. * @access public
  1738. * @return array
  1739. */
  1740. public function getAllChildId($sceneID)
  1741. {
  1742. if($sceneID == 0) return array();
  1743. $scene = $this->dao->findById($sceneID)->from(TABLE_SCENE)->fetch();
  1744. if(empty($scene)) return array();
  1745. return $this->dao->select('id')->from(TABLE_SCENE)
  1746. ->where('path')->like($scene->path . '%')
  1747. ->andWhere('deleted')->eq(0)
  1748. ->fetchPairs();
  1749. }
  1750. /**
  1751. * 通过 ID 列表和查询语句获取场景。
  1752. * Get scenes by id list and query string.
  1753. *
  1754. * @param array $sceneIdList
  1755. * @param string $query
  1756. * @access public
  1757. * @return array
  1758. */
  1759. public function getScenesByList($sceneIdList, $query = '')
  1760. {
  1761. if(!$sceneIdList) return array();
  1762. return $this->dao->select('*')->from(TABLE_SCENE)
  1763. ->where('deleted')->eq('0')
  1764. ->andWhere('id')->in($sceneIdList)
  1765. ->beginIF($query)->andWhere($query)->fi()
  1766. ->fetchAll('id', false);
  1767. }
  1768. /**
  1769. * 获取包含子场景的场景列表。
  1770. * Get scene list include sub scenes.
  1771. *
  1772. * @param int|array $productID
  1773. * @param string $branch
  1774. * @param int $moduleID
  1775. * @param string $orderBy
  1776. * @param object $pager
  1777. * @access public
  1778. * @return array
  1779. */
  1780. public function getSceneGroups($productID, $branch = '', $moduleID = 0, $orderBy = 'id_desc', $pager = null)
  1781. {
  1782. $modules = [];
  1783. if($moduleID)
  1784. {
  1785. $modules = $this->loadModel('tree')->getAllChildId($moduleID);
  1786. if(!$modules) return [];
  1787. }
  1788. $topScenes = $this->dao->select('*')->from(TABLE_SCENE)
  1789. ->where('deleted')->eq('0')
  1790. ->andWhere('grade')->eq(1)
  1791. ->andWhere('product')->in($productID)
  1792. ->beginIF($branch !== 'all')->andWhere('branch')->eq($branch)->fi()
  1793. ->beginIF($modules)->andWhere('module')->in($modules)->fi()
  1794. ->orderBy($orderBy)
  1795. ->page($pager)
  1796. ->fetchAll('id', false);
  1797. if(!$topScenes) return array();
  1798. $subScenes = $this->dao->select('*')->from(TABLE_SCENE)
  1799. ->where('deleted')->eq('0')
  1800. ->andWhere('grade')->gt(1)
  1801. ->andWhere('product')->in($productID)
  1802. ->beginIF($branch !== 'all')->andWhere('branch')->eq($branch)->fi()
  1803. ->beginIF($modules)->andWhere('module')->in($modules)->fi()
  1804. ->orderBy($orderBy)
  1805. ->fetchAll('id', false);
  1806. foreach($subScenes as $id => $scene)
  1807. {
  1808. $path = trim($scene->path, ',');
  1809. $root = substr($path, 0, strpos($path, ','));
  1810. if(!isset($topScenes[$root])) unset($subScenes[$id]);
  1811. }
  1812. return $subScenes + $topScenes;
  1813. }
  1814. /**
  1815. * 获取用场景 ID 分组的用例。
  1816. * Get cases by scene id.
  1817. *
  1818. * @param int|array $productID
  1819. * @param string $branch
  1820. * @param array $modules
  1821. * @param string $caseType
  1822. * @param string $orderBy
  1823. * @access public
  1824. * @return array
  1825. */
  1826. public function getSceneGroupCases($productID, $branch, $modules, $caseType, $orderBy)
  1827. {
  1828. $browseType = $this->session->caseBrowseType && $this->session->caseBrowseType != 'bysearch' ? $this->session->caseBrowseType : 'all';
  1829. $stmt = $this->dao->select('t1.*,t3.title as storyTitle')->from(TABLE_CASE)->alias('t1');
  1830. if($this->app->tab == 'project') $stmt = $stmt->leftJoin(TABLE_PROJECTCASE)->alias('t2')->on('t1.id=t2.case');
  1831. $stmt->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id');
  1832. $caseList = $stmt->where('t1.deleted')->eq('0')
  1833. ->andWhere('t1.scene')->ne(0)
  1834. ->andWhere('t1.product')->in($productID)
  1835. ->beginIF($this->app->tab == 'project')->andWhere('t2.project')->eq($this->session->project)->fi()
  1836. ->beginIF($branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
  1837. ->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
  1838. ->beginIF($browseType == 'wait')->andWhere('t1.status')->eq($browseType)->fi()
  1839. ->beginIF($this->cookie->onlyAutoCase)->andWhere('t1.auto')->eq('auto')->fi()
  1840. ->beginIF(!$this->cookie->onlyAutoCase)->andWhere('t1.auto')->ne('unit')->fi()
  1841. ->beginIF($caseType)->andWhere('t1.type')->eq($caseType)->fi()
  1842. ->orderBy($orderBy)
  1843. ->fetchAll('id', false);
  1844. $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false);
  1845. $caseList = $this->loadModel('story')->checkNeedConfirm($caseList);
  1846. $caseList = $this->appendData($caseList);
  1847. $cases = array();
  1848. foreach($caseList as $case) $cases[$case->scene][$case->id] = $case;
  1849. return $cases;
  1850. }
  1851. /**
  1852. * 获取场景菜单。
  1853. * Get scene menu.
  1854. *
  1855. * @param int $productID
  1856. * @param int $moduleID
  1857. * @param string $branch
  1858. * @param int $startScene
  1859. * @param int $currentScene
  1860. * @param bool $emptyMenu
  1861. * @access public
  1862. * @return array
  1863. */
  1864. public function getSceneMenu($productID, $moduleID = 0, $branch = 'all', $startScene = 0, $currentScene = 0, $emptyMenu = false)
  1865. {
  1866. $branches = array($branch => '');
  1867. if($branch != 'all')
  1868. {
  1869. $product = $this->loadModel('product')->getById($productID);
  1870. if($product && $product->type != 'normal')
  1871. {
  1872. $branchPairs = $this->loadModel('branch')->getPairs($productID, 'all');
  1873. $branches = array($branch => $branchPairs[$branch]);
  1874. }
  1875. elseif($product && $product->type == 'normal')
  1876. {
  1877. $branches = array('0' => '');
  1878. }
  1879. }
  1880. $treeMenu = array();
  1881. foreach($branches as $branchID => $branch)
  1882. {
  1883. $scenes = $this->getScenesForMenu($productID, $moduleID, $startScene, (string)$branchID, $currentScene);
  1884. foreach($scenes as $scene)
  1885. {
  1886. $branchName = !empty($product) && $product->type != 'normal' && $scene->branch === BRANCH_MAIN ? $this->lang->branch->main : $branch;
  1887. $this->buildTreeArray($treeMenu, $scenes, $scene, (empty($branchName)) ? '/' : "/$branchName/");
  1888. }
  1889. }
  1890. ksort($treeMenu);
  1891. $topMenu = @array_shift($treeMenu);
  1892. $topMenu = explode("\n", trim((string)$topMenu));
  1893. $lastMenu[] = '/';
  1894. foreach($topMenu as $menu)
  1895. {
  1896. if(!strpos($menu, '|')) continue;
  1897. list($label, $sceneID) = explode('|', $menu);
  1898. $lastMenu[$sceneID] = $label;
  1899. }
  1900. /* Attach empty option. */
  1901. if($emptyMenu) $lastMenu['null'] = $this->lang->null;
  1902. return $lastMenu;
  1903. }
  1904. /**
  1905. * 获取场景的名称。
  1906. * Get scene name.
  1907. *
  1908. * @param array $sceneIDList
  1909. * @param bool $fullPath
  1910. * @access public
  1911. * @return array
  1912. */
  1913. public function getScenesName($sceneIDList, $fullPath = true)
  1914. {
  1915. if(!$fullPath) return $this->dao->select('id, title')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in($sceneIDList)->fetchPairs();
  1916. $scenes = $this->dao->select('id, title, path')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in($sceneIDList)->fetchAll('path');
  1917. $allScenes = $this->dao->select('id, title')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in(implode(',', array_keys($scenes)))->fetchPairs();
  1918. $scenePairs = array();
  1919. foreach($scenes as $scene)
  1920. {
  1921. $title = '';
  1922. $path = explode(',', trim($scene->path, ','));
  1923. foreach($path as $sceneID) $title .= '/' . $allScenes[$sceneID];
  1924. $scenePairs[$scene->id] = $title;
  1925. }
  1926. return $scenePairs;
  1927. }
  1928. /**
  1929. * 编辑一个场景和它的子场景。
  1930. * Update a scene and its children.
  1931. *
  1932. * @param object $scene
  1933. * @access public
  1934. * @return bool
  1935. * @param object $oldScene
  1936. */
  1937. public function updateScene($scene, $oldScene)
  1938. {
  1939. $this->dao->update(TABLE_SCENE)->data($scene)
  1940. ->autoCheck()
  1941. ->batchCheck($this->config->testcase->editscene->requiredFields, 'notempty')
  1942. ->check('title', 'unique', "product = '{$scene->product}' AND id != '{$oldScene->id}'")
  1943. ->where('id')->eq($oldScene->id)
  1944. ->checkFlow()
  1945. ->exec();
  1946. if(dao::isError()) return false;
  1947. if(isset($scene->parent) && $scene->parent != $oldScene->parent)
  1948. {
  1949. if($scene->parent)
  1950. {
  1951. $parent = $this->getSceneByID($scene->parent);
  1952. $scene->path = $parent->path . $oldScene->id . ',';
  1953. $scene->grade = ++$parent->grade;
  1954. $scene->product = $parent->product;
  1955. $scene->branch = $parent->branch;
  1956. $scene->module = $parent->module;
  1957. }
  1958. else
  1959. {
  1960. $scene->path = ',' . $oldScene->id . ',';
  1961. $scene->grade = 1;
  1962. }
  1963. $this->dao->update(TABLE_SCENE)->data($scene)->where('id')->eq($oldScene->id)->exec();
  1964. $this->dao->update(TABLE_SCENE)->set("path = REPLACE(path, '{$oldScene->path}', '{$scene->path}')")
  1965. ->where('id')->ne($oldScene->id)
  1966. ->andWhere('path')->like("{$oldScene->path}%")
  1967. ->exec();
  1968. }
  1969. $changes = common::createChanges($oldScene, $scene);
  1970. if($changes)
  1971. {
  1972. $actionID = $this->loadModel('action')->create('scene', $oldScene->id, 'edited');
  1973. $this->action->logHistory($actionID, $changes);
  1974. }
  1975. return true;
  1976. }
  1977. /**
  1978. * 获取 xmind 文件内容。
  1979. * Get xmind file content.
  1980. *
  1981. * @param string $fileName
  1982. * @access public
  1983. * @return string
  1984. */
  1985. public function getXmindImport($fileName)
  1986. {
  1987. $xmlNode = simplexml_load_file($fileName);
  1988. $testData = $this->xmlToArray($xmlNode);
  1989. return json_encode($testData);
  1990. }
  1991. /**
  1992. * 保存 xmind 文件内容。
  1993. * Save xmind file content to database.
  1994. *
  1995. * @param array $scenes
  1996. * @param array $testcases
  1997. * @access public
  1998. * @return array
  1999. */
  2000. public function saveXmindImport($scenes, $testcases)
  2001. {
  2002. try
  2003. {
  2004. $this->dao->begin();
  2005. $sceneList = array_combine(array_map(function($scene){return $scene['tmpId'];}, $scenes), array_map(function($scene){return (array)$scene;}, $scenes));
  2006. $sceneIDList = array();
  2007. foreach($sceneList as $key => &$scene)
  2008. {
  2009. $result = $this->testcaseTao->saveScene($scene, $sceneList);
  2010. if($result['result'] == 'fail') throw new Exception($result['message']);
  2011. $scene['id'] = $result['sceneID'];
  2012. $sceneIDList[$key]['id'] = $result['sceneID'];
  2013. }
  2014. foreach($testcases as $testcase)
  2015. {
  2016. $result = $this->saveTestcase($testcase, $sceneIDList);
  2017. if($result['result'] == 'fail') throw new Exception($result['message']);
  2018. }
  2019. $this->dao->commit();
  2020. return array('result' => 'success', 'message' => $this->lang->saveSuccess);
  2021. }
  2022. catch (Exception $e)
  2023. {
  2024. $this->dao->rollBack();
  2025. return array('result' => 'fail', 'message' => $e->getMessage());
  2026. }
  2027. }
  2028. /**
  2029. * 导入 xmind 时保存其中的测试用例。
  2030. * Save the test cases in xmind when importing it.
  2031. *
  2032. * @param object $testcase
  2033. * @param array $sceneIdList
  2034. * @access public
  2035. * @return array
  2036. */
  2037. public function saveTestcase($testcase, $sceneIdList)
  2038. {
  2039. $scene = 0;
  2040. $nodeID = $testcase->tmpPId;
  2041. if(isset($sceneIdList[$nodeID]['id'])) $scene = $sceneIdList[$nodeID]['id'];
  2042. unset($testcase->tmpPId);
  2043. $testcase->scene = $scene;
  2044. if(empty($testcase->id))
  2045. {
  2046. $this->create($testcase);
  2047. }
  2048. else
  2049. {
  2050. $oldCase = $this->getById($testcase->id);
  2051. $changes = $this->update($testcase, $oldCase);
  2052. if($changes)
  2053. {
  2054. $actionID = $this->loadModel('action')->create('case', $testcase->id, 'edited');
  2055. $this->action->logHistory($actionID, $changes);
  2056. }
  2057. }
  2058. if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError(true));
  2059. return array('result' => 'success', 'message' => 1, 'testcaseID' => zget($testcase, 'id', 0));
  2060. }
  2061. /**
  2062. * 把导入 xmind 时 POST 提交的 stepList 属性转换为创建用例和编辑用例表单提交的 steps、expects 和 stepType 属性。
  2063. * Convert the stepList attribute submitted by POST when importing xmind into the steps, expects, and stepType attributes submitted by the form for creating and editing use cases.
  2064. *
  2065. * @param object $case
  2066. * @param object $testcase
  2067. * @access protected
  2068. * @return object
  2069. */
  2070. protected function processCaseSteps($case, $testcase)
  2071. {
  2072. $case->steps = array();
  2073. $case->expects = array();
  2074. $case->stepType = array();
  2075. if(isset($testcase->stepList))
  2076. {
  2077. $index = 1;
  2078. $postSteps = $testcase->stepList;
  2079. $stepList = array_combine(array_map(function($step){return $step['tmpId'];}, $postSteps), array_map(function($step){return (object)$step;}, $postSteps));
  2080. foreach($stepList as $step)
  2081. {
  2082. if(isset($stepList[$step->tmpPId]))
  2083. {
  2084. $parentStep = $stepList[$step->tmpPId];
  2085. $parentStep->children = isset($parentStep->children) ? $parentStep->children : array();
  2086. $step->index = $parentStep->index . '.' . (count($parentStep->children) + 1);
  2087. $parentStep->children[] = $step;
  2088. }
  2089. else
  2090. {
  2091. $step->index = $index;
  2092. $step->children = array();
  2093. $index++;
  2094. }
  2095. $case->steps[$step->index] = zget($step, 'desc', '');
  2096. $case->expects[$step->index] = zget($step, 'expect', '');
  2097. $case->stepType[$step->index] = zget($step, 'type', 'item');
  2098. }
  2099. }
  2100. return $case;
  2101. }
  2102. /**
  2103. * 导出 xmind 格式的用例时获取用例列表。
  2104. * Get case list for export xmind.
  2105. *
  2106. * @param int $productID
  2107. * @param int $moduleID
  2108. * @param string $branch
  2109. * @access public
  2110. * @return array
  2111. */
  2112. public function getCaseListForXmindExport($productID, $moduleID, $branch = '')
  2113. {
  2114. $fields = 't1.id AS testcaseID, t1.title AS `name`, t1.pri, t2.id AS productID, t2.`name` AS productName, t3.id AS moduleID, t3.`name` AS moduleName, t4.id AS sceneID, t4.title AS sceneName, t1.precondition';
  2115. return $this->dao->select($fields)->from(TABLE_CASE)->alias('t1')
  2116. ->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
  2117. ->leftJoin(TABLE_MODULE)->alias('t3')->on('t1.module = t3.id')
  2118. ->leftJoin(TABLE_SCENE)->alias('t4')->on('t1.scene = t4.id')
  2119. ->where('t1.deleted')->eq('0')
  2120. ->andWhere('t1.product')->eq($productID)
  2121. ->beginIF($moduleID)->andWhere('t1.module')->eq($moduleID)->fi()
  2122. ->beginIF($branch !== '' && $branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
  2123. ->fetchAll();
  2124. }
  2125. /**
  2126. * 通过产品和模块获取步骤信息。
  2127. * Get step by product and module.
  2128. *
  2129. * @param int $productID
  2130. * @param int $moduleID
  2131. * @access public
  2132. * @return array
  2133. */
  2134. public function getStepByProductAndModule($productID, $moduleID)
  2135. {
  2136. $fields = "t1.id as testcaseID,"
  2137. . "t2.id as stepID,"
  2138. . "t2.type,"
  2139. . "t2.parent as parentID,"
  2140. . "t2.`desc`,"
  2141. . "t2.expect";
  2142. $stepList = $this->dao->select($fields)->from(TABLE_CASE)->alias('t1')
  2143. ->leftJoin(TABLE_CASESTEP)->alias('t2')->on('t1.id = t2.`case` and t1.version = t2.version')
  2144. ->where('t1.deleted')->eq(0)
  2145. ->andWhere('t1.product')->eq($productID)
  2146. ->andWhere('t2.id')->gt('0')
  2147. ->beginIF($moduleID > 0)->andWhere('t1.module')->eq($moduleID)->fi()
  2148. ->fetchAll();
  2149. return $stepList;
  2150. }
  2151. /**
  2152. * 分组获取用例的步骤信息。
  2153. * Get steps grouped by case id.
  2154. *
  2155. * @param array $caseID
  2156. * @param string $status
  2157. * @access public
  2158. * @return array
  2159. * @param mixed[] $caseIdList
  2160. */
  2161. public function getStepGroupByIdList($caseIdList, $status = '')
  2162. {
  2163. if(!$caseIdList) return array();
  2164. return $this->dao->select('t1.*')->from(TABLE_CASESTEP)->alias('t1')
  2165. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case=t2.id')
  2166. ->where('t2.id')->in($caseIdList)
  2167. ->andWhere('t1.version=t2.version')
  2168. ->beginIF($status != 'all')->andWhere('t2.status')->ne('wait')->fi()
  2169. ->fetchGroup('case', 'id');
  2170. }
  2171. /**
  2172. * 通过产品和模块获取场景。
  2173. * Get scene by product and module.
  2174. *
  2175. * @param int $productID
  2176. * @param int $moduleID
  2177. * @access public
  2178. * @return array
  2179. */
  2180. public function getSceneByProductAndModule($productID, $moduleID)
  2181. {
  2182. /* Get scenes by product and module. */
  2183. $sceneList = $this->dao->select('id as sceneID, title as sceneName, path, parent as parentID, product as productID, module as moduleID')
  2184. ->from(TABLE_SCENE)
  2185. ->where('deleted')->eq(0)
  2186. ->andWhere('product')->eq($productID)
  2187. ->beginIF($moduleID > 0)->andWhere('module')->eq($moduleID)->fi()
  2188. ->fetchAll();
  2189. $sceneMaps = array();
  2190. $topScenes = array();
  2191. foreach($sceneList as $one)
  2192. {
  2193. /* If scene parent is zero, append it to topScenes. */
  2194. if($one->parentID == 0) $topScenes[] = $one;
  2195. /* Append it to sceneMaps. */
  2196. $sceneMaps[$one->sceneID] = $one;
  2197. }
  2198. return array('sceneMaps' => $sceneMaps,'topScenes' => $topScenes);
  2199. }
  2200. /**
  2201. * 存储 mind 配置。
  2202. * Save mind config.
  2203. *
  2204. * @param array $configList
  2205. * @access public
  2206. * @return array
  2207. * @param string $type
  2208. */
  2209. public function saveMindConfig($type, $configList)
  2210. {
  2211. $this->dao->begin();
  2212. $this->dao->delete()->from(TABLE_CONFIG)
  2213. ->where('owner')->eq($this->app->user->account)
  2214. ->andWhere('module')->eq('testcase')
  2215. ->andWhere('section')->eq($type)
  2216. ->exec();
  2217. foreach($configList as $one)
  2218. {
  2219. $config = new stdclass();
  2220. $config->module = 'testcase';
  2221. $config->section = $type;
  2222. $config->key = $one['key'];
  2223. $config->value = $one['value'];
  2224. $config->owner = $this->app->user->account;
  2225. $this->dao->insert(TABLE_CONFIG)->data($config)->autoCheck()->exec();
  2226. if($this->dao->isError())
  2227. {
  2228. $this->dao->rollBack();
  2229. return array('result' => 'fail', 'message' => $this->dao->getError(true));
  2230. }
  2231. }
  2232. $this->dao->commit();
  2233. return array("result" => "success", "message" => 1);
  2234. }
  2235. /**
  2236. * 获取 xmind 配置。
  2237. * Get xmind config.
  2238. *
  2239. * @access public
  2240. * @return array
  2241. * @param string $type
  2242. */
  2243. public function getMindConfig($type)
  2244. {
  2245. $configItems = $this->dao->select("`key`,value")->from(TABLE_CONFIG)
  2246. ->where('owner')->eq($this->app->user->account)
  2247. ->andWhere('module')->eq('testcase')
  2248. ->andWhere('section')->eq($type)
  2249. ->fetchAll();
  2250. $config = array();
  2251. foreach($configItems as $item) $config[$item->key] = $item->value;
  2252. if(!isset($config['module'])) $config['module'] = 'M';
  2253. if(!isset($config['scene'])) $config['scene'] = 'S';
  2254. if(!isset($config['case'])) $config['case'] = 'C';
  2255. if(!isset($config['precondition'])) $config['precondition'] = 'pre';
  2256. if(!isset($config['pri'])) $config['pri'] = 'P';
  2257. if(!isset($config['group'])) $config['group'] = 'G';
  2258. return $config;
  2259. }
  2260. /**
  2261. * 将 xml 内容转为数组。
  2262. * Convert xml to array.
  2263. *
  2264. * @param object $xml
  2265. * @param array $options
  2266. * @access private
  2267. * @return array
  2268. */
  2269. private function xmlToArray($xml, $options = array())
  2270. {
  2271. $defaults = array(
  2272. 'namespaceRecursive' => false, // Get XML doc namespaces recursively
  2273. 'removeNamespace' => true, // Remove namespace from resulting keys
  2274. 'namespaceSeparator' => ':', // Change separator to something other than a colon
  2275. 'attributePrefix' => '', // Distinguish between attributes and nodes with the same name
  2276. 'alwaysArray' => array(), // Array of XML tag names which should always become arrays
  2277. 'autoArray' => true, // Create arrays for tags which appear more than once
  2278. 'textContent' => 'text', // Key used for the text content of elements
  2279. 'autoText' => true, // Skip textContent key if node has no attributes or child nodes
  2280. 'keySearch' => false, // (Optional) search and replace on tag and attribute names
  2281. 'keyReplace' => false, // (Optional) replace values for above search values
  2282. );
  2283. $options = array_merge($defaults, $options);
  2284. $namespaces = $xml->getDocNamespaces($options['namespaceRecursive']);
  2285. $namespaces[''] = null; // Add empty base namespace
  2286. /* Get attributes from all namespaces. */
  2287. $attributesArray = array();
  2288. foreach($namespaces as $prefix => $namespace)
  2289. {
  2290. if($options['removeNamespace']) $prefix = '';
  2291. foreach($xml->attributes($namespace) as $attributeName => $attribute)
  2292. {
  2293. // (Optional) replace characters in attribute name
  2294. if($options['keySearch']) $attributeName = str_replace($options['keySearch'], $options['keyReplace'], $attributeName);
  2295. $attributeKey = $options['attributePrefix'] . ($prefix ? $prefix . $options['namespaceSeparator'] : '') . $attributeName;
  2296. $attributesArray[$attributeKey] = (string) $attribute;
  2297. }
  2298. }
  2299. // Get child nodes from all namespaces
  2300. $tagsArray = $this->getXmlTagsArray($xml, $namespaces, $options);
  2301. // Get text content of node
  2302. $textContentArray = array();
  2303. $plainText = trim((string) $xml);
  2304. if($plainText !== '') $textContentArray[$options['textContent']] = $plainText;
  2305. // Stick it all together
  2306. $propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || $plainText === '' ? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText;
  2307. // Return node as array
  2308. return array($xml->getName() => $propertiesArray);
  2309. }
  2310. /**
  2311. * 获取 xml 标签数组。
  2312. * Get xml tags array.
  2313. *
  2314. * @param object $xml
  2315. * @param array $namespaces
  2316. * @param array $options
  2317. * @access public
  2318. * @return array
  2319. */
  2320. public function getXmlTagsArray($xml, $namespaces, $options)
  2321. {
  2322. $tagsArray = array();
  2323. foreach($namespaces as $prefix => $namespace)
  2324. {
  2325. if($options['removeNamespace']) $prefix = '';
  2326. foreach($xml->children($namespace) as $childXml)
  2327. {
  2328. // Recurse into child nodes
  2329. $childArray = $this->xmlToArray($childXml, $options);
  2330. $childTagName = key($childArray);
  2331. $childProperties = current($childArray);
  2332. // Replace characters in tag name
  2333. if($options['keySearch']) $childTagName = str_replace($options['keySearch'], $options['keyReplace'], $childTagName);
  2334. // Add namespace prefix, if any
  2335. if($prefix) $childTagName = $prefix . $options['namespaceSeparator'] . $childTagName;
  2336. if(!isset($tagsArray[$childTagName]))
  2337. {
  2338. // Only entry with this key
  2339. // Test if tags of this type should always be arrays, no matter the element count
  2340. $tagsArray[$childTagName] = in_array($childTagName, $options['alwaysArray'], true) || !$options['autoArray'] ? array($childProperties) : $childProperties;
  2341. }
  2342. elseif(is_array($tagsArray[$childTagName]) && array_keys($tagsArray[$childTagName]) === range(0, count($tagsArray[$childTagName]) - 1))
  2343. {
  2344. // Key already exists and is integer indexed array
  2345. $tagsArray[$childTagName][] = $childProperties;
  2346. }
  2347. else
  2348. {
  2349. // Key exists so convert to integer indexed array with previous value in position 0
  2350. $tagsArray[$childTagName] = array($tagsArray[$childTagName], $childProperties);
  2351. }
  2352. }
  2353. }
  2354. return $tagsArray;
  2355. }
  2356. /**
  2357. * 追加用例执行失败次数。
  2358. * Append case fails.
  2359. *
  2360. * @param object $case
  2361. * @param string $from
  2362. * @param int $taskID
  2363. * @access public
  2364. * @return object
  2365. */
  2366. public function appendCaseFails($case, $from, $taskID)
  2367. {
  2368. $caseFails = $this->dao->select('COUNT(1) AS count')->from(TABLE_TESTRESULT)
  2369. ->where('caseResult')->eq('fail')
  2370. ->andwhere('`case`')->eq($case->id)
  2371. ->beginIF($from == 'testtask')->andwhere('`run`')->eq($taskID)->fi()
  2372. ->fetch('count');
  2373. $case->caseFails = $caseFails;
  2374. return $case;
  2375. }
  2376. /**
  2377. * 添加步骤。
  2378. * Append steps.
  2379. *
  2380. * @param array $steps
  2381. * @param int $count
  2382. * @access public
  2383. * @return array
  2384. */
  2385. public function appendSteps($steps, $count = 0)
  2386. {
  2387. if($count == 0) $count = $this->config->testcase->defaultSteps;
  2388. if(count($steps) < $count)
  2389. {
  2390. $step = new stdclass();
  2391. $step->step = '';
  2392. $step->desc = '';
  2393. $step->expect = '';
  2394. $step->type = 'step';
  2395. for($i = count($steps) + 1; $i <= $count; $i ++)
  2396. {
  2397. $data = clone $step;
  2398. $data->name = (string)$i;
  2399. $steps[] = $data;
  2400. }
  2401. }
  2402. return $steps;
  2403. }
  2404. /**
  2405. * 获取可以导入的测试用例。
  2406. * Get can import cases.
  2407. *
  2408. * @param int $productID
  2409. * @param int $libID
  2410. * @param string $orderBy
  2411. * @param object $pager
  2412. * @param int $queryID
  2413. * @access public
  2414. * @return array
  2415. * @param string $browseType
  2416. */
  2417. public function getCanImportCases($productID, $libID, $orderBy = 'id_desc', $pager = null, $browseType = '', $queryID = 0)
  2418. {
  2419. $query = '';
  2420. if($browseType == 'bysearch')
  2421. {
  2422. $queryID = (int)$queryID;
  2423. if($queryID)
  2424. {
  2425. $this->session->set('testsuiteQuery', ' 1 = 1');
  2426. $query = $this->loadModel('search')->getQuery($queryID);
  2427. if($query)
  2428. {
  2429. $this->session->set('testsuiteQuery', $query->sql);
  2430. $this->session->set('testsuiteForm', $query->form);
  2431. }
  2432. }
  2433. else
  2434. {
  2435. if($this->session->testsuiteQuery === false) $this->session->set('testsuiteQuery', ' 1 = 1');
  2436. }
  2437. $query = $this->session->testsuiteQuery;
  2438. $allLib = "`lib` = 'all'";
  2439. $query = strpos($query, $allLib) !== false ? str_replace($allLib, '1 = 1', $query) : "{$query} AND `lib` = '{$libID}'";
  2440. }
  2441. $this->loadModel('branch');
  2442. $this->loadModel('tree');
  2443. $product = $this->loadModel('product')->getById($productID);
  2444. $branches = $product->type != 'normal' ? array(BRANCH_MAIN => $this->lang->branch->main) + $this->branch->getPairs($productID, 'active') : array(0);
  2445. $branches = array_keys($branches);
  2446. $branchModules = array();
  2447. foreach($branches as $branch) $branchModules[$branch] = $this->tree->getOptionMenu($productID, 'case', 0, (string)$branch);
  2448. $caseModuleCount = $this->dao->select('fromCaseID,count(module) AS moduleCount')
  2449. ->from(TABLE_CASE)->alias('t1')
  2450. ->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id and t1.product = t2.root')
  2451. ->where('t1.product')->eq($productID)
  2452. ->andWhere('t1.lib')->eq($libID)
  2453. ->andWhere('t1.fromCaseID')->ne('0')
  2454. ->andWhere('t1.deleted')->eq('0')
  2455. ->andWhere('((t2.type')->in('story,case')->andWhere('t2.deleted')->eq('0')->markRight(1)
  2456. ->orWhere('t1.module')->eq('0')->markRight(1)
  2457. ->groupBy('t1.fromCaseID')
  2458. ->fetchPairs();
  2459. $branchModuleCount = $this->dao->select('branch,count(id) + 1 as moduleCount')
  2460. ->from(TABLE_MODULE)
  2461. ->where('root')->eq($productID)
  2462. ->andWhere('type')->in('story,case')
  2463. ->andWhere('deleted')->eq('0')
  2464. ->groupBy('branch')
  2465. ->fetchPairs();
  2466. foreach($branches as $branch)
  2467. {
  2468. if(!isset($branchModuleCount[$branch])) $branchModuleCount[$branch] = 0;
  2469. if(!empty($branch) && isset($branchModuleCount[0])) $branchModuleCount[$branch] += $branchModuleCount[0];
  2470. }
  2471. $maxCount = array_sum($branchModuleCount);
  2472. $canImport = $canNotImport = array();
  2473. $libCases = $this->loadModel('caselib')->getLibCases($libID, 'all');
  2474. foreach($libCases as $caseID => $case)
  2475. {
  2476. $caseModuleCount = zget($caseModuleCount, $caseID, 0);
  2477. if(!empty($caseModuleCount) && $caseModuleCount >= $maxCount)
  2478. {
  2479. $canNotImport[$caseID] = $caseID;
  2480. }
  2481. else
  2482. {
  2483. $canImport[$caseID] = $caseID;
  2484. }
  2485. }
  2486. return $this->dao->select('*')->from(TABLE_CASE)
  2487. ->where('deleted')->eq('0')
  2488. ->beginIF($browseType != 'bysearch')->andWhere('lib')->eq($libID)->fi()
  2489. ->beginIF($browseType == 'bysearch')->andWhere($query)->fi()
  2490. ->beginIF(count($canImport) <= count($canNotImport))->andWhere('id')->in($canImport)->fi()
  2491. ->beginIF(count($canImport) > count($canNotImport) && !empty($canNotImport))->andWhere('id')->notIn($canNotImport)->fi()
  2492. ->andWhere('product')->eq(0)
  2493. ->orderBy($orderBy)
  2494. ->page($pager)
  2495. ->fetchAll('id', false);
  2496. }
  2497. /**
  2498. * 获取已经导入的用例模块。
  2499. * Get imported case modules.
  2500. *
  2501. * @param int $productID
  2502. * @param int $libID
  2503. * @param int|string $branch
  2504. * @param string $returnType
  2505. * @access public
  2506. * @return array
  2507. * @param mixed[] $libCases
  2508. */
  2509. public function getCanImportedModules($productID, $libID, $branch, $returnType = 'pairs', $libCases = array())
  2510. {
  2511. $importedModules = $this->dao->select('fromCaseID,module')->from(TABLE_CASE)
  2512. ->where('product')->eq($productID)
  2513. ->beginIF($branch != 'all')->andWhere('branch')->eq($branch)->fi()
  2514. ->andWhere('fromCaseID')->ne('0')
  2515. ->andWhere('deleted')->eq('0')
  2516. ->fetchGroup('fromCaseID', 'module');
  2517. foreach($importedModules as $fromCaseID => $modules) $importedModules[$fromCaseID] = array_combine(array_keys($modules), array_keys($modules));
  2518. if(empty($libCases)) $libCases = $this->loadModel('caselib')->getLibCases($libID, 'all');
  2519. $modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, (string)$branch);
  2520. if($returnType == 'items')
  2521. {
  2522. $moduleItems = array();
  2523. foreach($modules as $moduleID => $moduleName) $moduleItems[$moduleID] = array('text' => $moduleName, 'value' => $moduleID);
  2524. }
  2525. $canImportModules = array();
  2526. foreach($libCases as $caseID => $case)
  2527. {
  2528. $caseModules = !empty($importedModules[$caseID]) ? $importedModules[$caseID] : array();
  2529. $canImportModules[$caseID] = $returnType == 'pairs' ? array_diff_key($modules, $caseModules) : array_diff_key($moduleItems, $caseModules);
  2530. if(!empty($canImportModules[$caseID]))
  2531. {
  2532. if($returnType == 'pairs')
  2533. {
  2534. $canImportModules[$caseID]['ditto'] = $this->lang->testcase->ditto;
  2535. }
  2536. else
  2537. {
  2538. $canImportModules[$caseID]['ditto'] = array('text' => $this->lang->testcase->ditto, 'value' => 'ditto');
  2539. $canImportModules[$caseID] = array_values($canImportModules[$caseID]);
  2540. }
  2541. }
  2542. if(empty($canImportModules[$caseID])) unset($canImportModules[$caseID]);
  2543. }
  2544. return $canImportModules;
  2545. }
  2546. /**
  2547. * Build search form.
  2548. *
  2549. * @param int $productID
  2550. * @param array $products
  2551. * @param int $queryID
  2552. * @param string $actionURL
  2553. * @param int $projectID
  2554. * @param int $moduleID
  2555. * @param string $branch
  2556. * @access public
  2557. * @return void
  2558. */
  2559. public function buildSearchForm($productID, $products, $queryID, $actionURL, $projectID = 0, $moduleID = 0, $branch = 'all')
  2560. {
  2561. /* 获取产品列表。Get productList. */
  2562. if($this->app->tab == 'project' && !$productID)
  2563. {
  2564. if($projectID)
  2565. {
  2566. $products = $this->loadModel('product')->getProducts($projectID);
  2567. $productList = array(0 => '');
  2568. foreach($products as $product) $productList[$product->id] = $product->name;
  2569. $productList['all'] = $this->lang->product->allProductsOfProject;
  2570. }
  2571. else
  2572. {
  2573. $productList = $products;
  2574. }
  2575. $this->config->testcase->search['params']['story']['values'] = $this->loadModel('story')->getExecutionStoryPairs($projectID, 0, 'all', $moduleID, 'full', 'active');
  2576. }
  2577. else
  2578. {
  2579. $productList = array(0 => '');
  2580. $productList['all'] = $this->lang->all;
  2581. if(isset($products[$productID])) $productList[$productID] = $products[$productID];
  2582. if(empty($productID) && empty($products))
  2583. {
  2584. $products = $this->loadModel('product')->getPairs('', 0, '', 'all');
  2585. $productList += $products;
  2586. }
  2587. $this->config->testcase->search['params']['story']['values'] = $this->loadModel('story')->getProductStoryPairs($productID, $branch, array(), 'active,reviewing', 'id_desc', 0, '', 'story', false);
  2588. }
  2589. /* 获取模块列表。*/
  2590. /* Get moduleList. */
  2591. if($productID)
  2592. {
  2593. $modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, $branch);
  2594. }
  2595. else
  2596. {
  2597. $modules = array();
  2598. foreach($products as $id => $name) $modules += $this->loadModel('tree')->getOptionMenu($id, 'case', 0);
  2599. }
  2600. $this->config->testcase->search['params']['product']['values'] = $productList;
  2601. $this->config->testcase->search['params']['module']['values'] = $modules;
  2602. $this->config->testcase->search['params']['scene']['values'] = $this->getSceneMenu($productID, $moduleID, $branch, 0, 0, true);
  2603. $this->config->testcase->search['params']['lib']['values'] = $this->loadModel('caselib')->getLibraries();
  2604. $product = $this->loadModel('product')->getByID($productID ? $productID : (int)key($products));
  2605. if((isset($product->type) && $product->type == 'normal') || $this->app->tab == 'project')
  2606. {
  2607. unset($this->config->testcase->search['fields']['branch']);
  2608. unset($this->config->testcase->search['params']['branch']);
  2609. }
  2610. else
  2611. {
  2612. $branches = $this->loadModel('branch')->getPairs($productID, '', $projectID);
  2613. $this->config->testcase->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
  2614. $this->config->testcase->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches + array('all' => $this->lang->branch->all);
  2615. }
  2616. if(!$this->config->testcase->needReview) unset($this->config->testcase->search['params']['status']['values']['wait']);
  2617. $this->config->testcase->search['actionURL'] = $actionURL;
  2618. $this->config->testcase->search['queryID'] = $queryID;
  2619. $this->config->testcase->search['module'] = 'testcase';
  2620. $this->loadModel('search')->setSearchParams($this->config->testcase->search);
  2621. }
  2622. /**
  2623. * 构建搜索配置。
  2624. * Build search config.
  2625. *
  2626. * @param int $productID
  2627. * @param string $branch
  2628. * @access public
  2629. * @return array
  2630. */
  2631. public function buildSearchConfig($productID, $branch = 'all')
  2632. {
  2633. $this->config->testcase->search['params']['story']['values'] = $this->loadModel('story')->getProductStoryPairs($productID, $branch, array(), 'active,reviewing', 'id_desc', 0, '', 'story', false);
  2634. /* 获取模块列表。*/
  2635. /* Get moduleList. */
  2636. $modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, $branch);
  2637. $this->config->testcase->search['params']['module']['values'] = $modules;
  2638. $this->config->testcase->search['params']['scene']['values'] = $this->getSceneMenu($productID, 0, $branch, 0, 0, true);
  2639. $this->config->testcase->search['params']['lib']['values'] = $this->loadModel('caselib')->getLibraries();
  2640. unset($this->config->testcase->search['fields']['product']);
  2641. unset($this->config->testcase->search['params']['product']);
  2642. $product = $this->loadModel('product')->getByID($productID ? $productID : 0);
  2643. if($productID)
  2644. {
  2645. if(isset($product->type) && $product->type == 'normal')
  2646. {
  2647. unset($this->config->testcase->search['fields']['branch']);
  2648. unset($this->config->testcase->search['params']['branch']);
  2649. }
  2650. else
  2651. {
  2652. $branches = $this->loadModel('branch')->getPairs($productID, '', 0);
  2653. $this->config->testcase->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
  2654. $this->config->testcase->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches + array('all' => $this->lang->branch->all);
  2655. }
  2656. }
  2657. if(!$this->config->testcase->needReview) unset($this->config->testcase->search['params']['status']['values']['wait']);
  2658. $_SESSION['searchParams']['module'] = 'testcase';
  2659. $searchConfig = $this->loadModel('search')->processBuildinFields('testcase', $this->config->testcase->search);
  2660. $searchConfig['params'] = $this->search->setDefaultParams('testcase', $searchConfig['fields'], $searchConfig['params']);
  2661. return $searchConfig;
  2662. }
  2663. /**
  2664. * 过滤自动测试用例的ID列表。
  2665. * Ignore auto testcase id list.
  2666. *
  2667. * @param array $caseIdList
  2668. * @access public
  2669. * @return array
  2670. */
  2671. public function ignoreAutoCaseIdList($caseIdList)
  2672. {
  2673. if(empty($caseIdList)) return array();
  2674. return $this->dao->select('id')->from(TABLE_CASE)->where('id')->in($caseIdList)->andWhere('auto')->ne('auto')->fetchPairs('id', 'id');
  2675. }
  2676. }