PageRenderTime 53ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/functions/treeMenu.inc.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 2261 lines | 1381 code | 319 blank | 561 comment | 218 complexity | a498daec9acd3fa114f8e86d6dcd8d56 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * TestLink Open Source Project - http://testlink.sourceforge.net/
  4. * This script is distributed under the GNU General Public License 2 or later.
  5. *
  6. * This file generates tree menu for test specification and test execution.
  7. *
  8. * @package TestLink
  9. * @author Martin Havlat
  10. * @copyright 2005-2009, TestLink community
  11. * @version CVS: $Id: treeMenu.inc.php,v 1.144 2010/08/21 13:38:25 asimon83 Exp $
  12. * @link http://www.teamst.org/index.php
  13. * @uses config.inc.php
  14. *
  15. * @internal Revisions:
  16. * 20100820 - asimon - refactoring for less redundant checks and better readibility of code
  17. * in generateExecTree()
  18. * 20100812 - franciscom - get_filtered_req_map() - BUGID 3671
  19. * 20100810 - asimon - added filtering by TC ID on prepareNode() and generateTestSpecTree()
  20. * 20100808 - asimon - generate_reqspec_tree() implemented to generate statically filtered
  21. * requirement specification tree, plus additional functions
  22. * get_filtered_req_map(), prepare_reqspec_treenode(),
  23. * render_reqspec_treenode()
  24. * 20100702 - asimon - fixed custom field filtering problem caused by
  25. * wrong array indexes and little logic errors
  26. * 20100701 - asimon - replaced is_null in renderTreeNode() by !isset
  27. * because of warnings in event log
  28. * 20100701 - asimon - added some additional isset() checks to avoid warnings
  29. * 20100628 - asimon - removal of constants from filter control class
  30. * 20160625 - asimon - refactoring for new filter features and BUGID 3516
  31. * 20100624 - asimon - CVS merge (experimental branch to HEAD)
  32. * 20100622 - asimon - refactoring of following functions for new filter classes:
  33. * generateExecTree, renderExecTreeNode(), prepareNode(),
  34. * generateTestSpecTree, renderTreeNode(), filter_by_*()
  35. * 20100611 - franciscom - renderExecTreeNode(), renderTreeNode() interface changes
  36. * generateExecTree() - interface changes and output changes
  37. * 20100602 - franciscom - extjs_renderExecTreeNodeOnOpen() - added 'tlNodeType'
  38. * 20100428 - asimon - BUGID 3301 and related:
  39. * added filtering by custom fields to generateTestSpecTree(),
  40. * added importance setting in prepareNode() because of
  41. * "undefined" error in event log,
  42. * added function filter_by_cfield_values()
  43. * which is used from generateTestSpecTree()
  44. * 20100417 - franciscom - BUGID 2498 - spec tree - filter by test case spec importance
  45. * 20100417 - franciscom - BUGID 3380 - execution tree - filter by test case execution type
  46. * 20100415 - franciscom - BUGID 2797 - filter by test case execution type
  47. * 20100202 - asimon - changes for filtering, BUGID 2455, BUGID 3026
  48. * added filter_by_* - functions, changed generateExecTree()
  49. * 20091212 - franciscom - prepareNode(), generateTestSpecTree() interface changes
  50. * added logic to do filtering on test spec for execution type
  51. *
  52. * 20090815 - franciscom - get_last_execution() call changes
  53. * 20090801 - franciscom - table prefix missed
  54. * 20090716 - franciscom - BUGID 2692
  55. * 20090328 - franciscom - BUGID 2299 - introduced on 20090308.
  56. * Added logic to remove Empty Top level test suites
  57. * (have neither test cases nor test suites inside) when applying
  58. * test case keyword filtering.
  59. * BUGID 2296
  60. * 20090308 - franciscom - generateTestSpecTree() - changes for EXTJS tree
  61. * 20090211 - franciscom - BUGID 2094
  62. * 20090202 - franciscom - minor changes to avoid BUGID 2009
  63. * 20090118 - franciscom - replaced multiple calls config_get('testcase_cfg')
  64. * added extjs_renderTestSpecTreeNodeOnOpen(), to allow filtering
  65. *
  66. */
  67. require_once(dirname(__FILE__)."/../../third_party/dBug/dBug.php");
  68. /**
  69. * strip potential newlines and other unwanted chars from strings
  70. * Mainly for stripping out newlines, carriage returns, and quotes that were
  71. * causing problems in javascript using jtree
  72. *
  73. * @param string $str
  74. * @return string string with the newlines removed
  75. */
  76. function filterString($str)
  77. {
  78. $str = str_replace(array("\n","\r"), array("",""), $str);
  79. $str = addslashes($str);
  80. $str = htmlspecialchars($str, ENT_QUOTES);
  81. return $str;
  82. }
  83. /**
  84. * generate data for tree menu of Test Specification
  85. *
  86. * @param boolean $ignore_inactive_testcases if all test case versions are inactive,
  87. * the test case will ignored.
  88. * @param array $exclude_branches map key=node_id
  89. *
  90. * @internal Revisions:
  91. * 20100810 - asimon - filtering by testcase ID
  92. * 20100428 - asimon - BUGID 3301, added filtering by custom fields
  93. * 20090328 - franciscom - BUGID 2299, that was generated during 20090308
  94. * trying to fix another not reported bug.
  95. * 20090308 - franciscom - changed arguments in str_ireplace() call
  96. * Due to bug in Test Spec tree when using Keywords filter
  97. * 20080501 - franciscom - keyword_id can be an array
  98. * 20071014 - franciscom - $bForPrinting
  99. * used to choose Javascript function
  100. * to call when clicking on a tree node
  101. * 20070922 - franciscom - interface changes added $tplan_id,
  102. * 20070217 - franciscom - added $exclude_branches
  103. * 20061105 - franciscom - added $ignore_inactive_testcases
  104. */
  105. function generateTestSpecTree(&$db,$tproject_id, $tproject_name,$linkto,$filters=null,$options=null)
  106. {
  107. $tables = tlObjectWithDB::getDBTables(array('tcversions','nodes_hierarchy'));
  108. $my = array();
  109. // 20100412 - franciscom
  110. // $my['options'] = array('forPrinting' => 0, 'hideTestCases' => 0,'getArguments' => '',
  111. // 'tc_action_enabled' => 1, 'ignore_inactive_testcases' => 0,
  112. // 'exclude_branches' => null, 'viewType' => 'testSpecTree');
  113. // $my['options'] = array('forPrinting' => 0, 'hideTestCases' => 0,
  114. // 'tc_action_enabled' => 1, 'ignore_inactive_testcases' => 0,
  115. // 'exclude_branches' => null, 'viewType' => 'testSpecTree');
  116. $my['options'] = array('forPrinting' => 0, 'hideTestCases' => 0,
  117. 'tc_action_enabled' => 1, 'ignore_inactive_testcases' => 0,
  118. 'viewType' => 'testSpecTree');
  119. // 20100412 - franciscom
  120. // testplan => only used if opetions['viewType'] == 'testSpecTreeForTestPlan'
  121. // 20100417 - franciscom - BUGID 2498
  122. $my['filters'] = array('keywords' => null, 'executionType' => null, 'importance' => null,
  123. 'testplan' => null, 'filter_tc_id' => null);
  124. $my['options'] = array_merge($my['options'], (array)$options);
  125. $my['filters'] = array_merge($my['filters'], (array)$filters);
  126. $treeMenu = new stdClass();
  127. $treeMenu->rootnode = null;
  128. $treeMenu->menustring = '';
  129. $resultsCfg = config_get('results');
  130. $showTestCaseID = config_get('treemenu_show_testcase_id');
  131. $glueChar = config_get('testcase_cfg')->glue_character;
  132. $menustring = null;
  133. $tproject_mgr = new testproject($db);
  134. $tree_manager = &$tproject_mgr->tree_manager;
  135. $tcase_node_type = $tree_manager->node_descr_id['testcase'];
  136. // BUGID 3301
  137. $tsuite_node_type = $tree_manager->node_descr_id['testsuite'];
  138. $hash_descr_id = $tree_manager->get_available_node_types();
  139. $hash_id_descr = array_flip($hash_descr_id);
  140. $status_descr_code=$resultsCfg['status_code'];
  141. $status_code_descr=$resultsCfg['code_status'];
  142. $decoding_hash=array('node_id_descr' => $hash_id_descr,
  143. 'status_descr_code' => $status_descr_code,
  144. 'status_code_descr' => $status_code_descr);
  145. $exclude_branches = isset($filters['filter_toplevel_testsuite'])
  146. && is_array($filters['filter_toplevel_testsuite']) ?
  147. $filters['filter_toplevel_testsuite'] : null;
  148. $tcase_prefix=$tproject_mgr->getTestCasePrefix($tproject_id) . $glueChar;
  149. $test_spec = $tproject_mgr->get_subtree($tproject_id,testproject::RECURSIVE_MODE,
  150. testproject::INCLUDE_TESTCASES, $exclude_branches);
  151. // Added root node for test specification -> testproject
  152. $test_spec['name'] = $tproject_name;
  153. $test_spec['id'] = $tproject_id;
  154. $test_spec['node_type_id'] = $hash_descr_id['testproject'];
  155. $map_node_tccount=array();
  156. $tplan_tcs=null;
  157. DEFINE('DONT_FILTER_BY_TESTER',0);
  158. DEFINE('DONT_FILTER_BY_EXEC_STATUS',null);
  159. if($test_spec)
  160. {
  161. $tck_map = null; // means no filter
  162. if(!is_null($my['filters']['filter_keywords']))
  163. {
  164. //$tck_map = $tproject_mgr->get_keywords_tcases($tproject_id,$my['filters']['keywords']->items,
  165. // $my['filters']['keywords']->type);
  166. $tck_map = $tproject_mgr->get_keywords_tcases($tproject_id,
  167. $my['filters']['filter_keywords'],
  168. $my['filters']['filter_keywords_filter_type']);
  169. if( is_null($tck_map) )
  170. {
  171. $tck_map=array(); // means filter everything
  172. }
  173. }
  174. // Important: prepareNode() will make changes to $test_spec like filtering by test case
  175. // keywords using $tck_map;
  176. $pnFilters = null;
  177. // $pnFilters['filter_testcase_name'] =
  178. // isset($my['filters']['filter_testcase_name']) ?
  179. // $my['filters']['filter_testcase_name'] : null;
  180. //
  181. // $pnFilters['filter_execution_type'] =
  182. // isset($my['filters']['filter_execution_type']) ?
  183. // $my['filters']['filter_execution_type'] : null;
  184. //
  185. // $pnFilters['filter_priority'] =
  186. // isset($my['filters']['filter_priority']) ?
  187. // $my['filters']['filter_priority'] : null;
  188. $keys2init = array('filter_testcase_name',
  189. 'filter_execution_type',
  190. 'filter_priority',
  191. 'filter_tc_id');
  192. foreach ($keys2init as $keyname) {
  193. $pnFilters[$keyname] = isset($my['filters'][$keyname]) ? $my['filters'][$keyname] : null;
  194. }
  195. $pnFilters['setting_testplan'] =
  196. $my['filters']['setting_testplan'];
  197. // BUGID 3301 - added filtering by custom field values
  198. if (isset($my['filters']['filter_custom_fields'])
  199. && isset($test_spec['childNodes'])) {
  200. $test_spec['childNodes'] = filter_by_cf_values($test_spec['childNodes'],
  201. $my['filters']['filter_custom_fields'],
  202. $db, $tsuite_node_type, $tcase_node_type);
  203. }
  204. // 20100412 - franciscom
  205. $pnOptions = array('hideTestCases' => $my['options']['hideTestCases'],
  206. 'viewType' => $my['options']['viewType'],
  207. 'ignoreInactiveTestCases' => $my['options']['ignore_inactive_testcases']);
  208. $testcase_counters = prepareNode($db,$test_spec,$decoding_hash,$map_node_tccount,$tck_map,
  209. $tplan_tcs,$pnFilters,$pnOptions);
  210. foreach($testcase_counters as $key => $value)
  211. {
  212. $test_spec[$key]=$testcase_counters[$key];
  213. }
  214. $menustring = renderTreeNode(1,$test_spec,$hash_id_descr,
  215. $my['options']['tc_action_enabled'],$linkto,$tcase_prefix,
  216. $my['options']['forPrinting'],$showTestCaseID);
  217. }
  218. $menustring ='';
  219. $treeMenu->rootnode = new stdClass();
  220. $treeMenu->rootnode->name = $test_spec['text'];
  221. $treeMenu->rootnode->id = $test_spec['id'];
  222. $treeMenu->rootnode->leaf = isset($test_spec['leaf']) ? $test_spec['leaf'] : false;
  223. $treeMenu->rootnode->text = $test_spec['text'];
  224. $treeMenu->rootnode->position = $test_spec['position'];
  225. $treeMenu->rootnode->href = $test_spec['href'];
  226. // Change key ('childNodes') to the one required by Ext JS tree.
  227. if(isset($test_spec['childNodes']))
  228. {
  229. $menustring = str_ireplace('childNodes', 'children', json_encode($test_spec['childNodes']));
  230. }
  231. // 20090328 - franciscom - BUGID 2299
  232. // More details about problem found on 20090308 and fixed IN WRONG WAY
  233. // TPROJECT
  234. // |______ TSA
  235. // |__ TC1
  236. // |__ TC2
  237. // |
  238. // |______ TSB
  239. // |______ TSC
  240. //
  241. // Define Keyword K1,K2
  242. //
  243. // NO TEST CASE HAS KEYWORD ASSIGNED
  244. // Filter by K1
  245. // Tree will show root that spins Forever
  246. // menustring before str_ireplace : [null,null]
  247. // menustring AFTER [null]
  248. //
  249. // Now fixed.
  250. //
  251. // Some minor fix to do
  252. // Il would be important exclude Top Level Test suites.
  253. //
  254. //
  255. // 20090308 - franciscom
  256. // Changed because found problem on:
  257. // Test Specification tree when applying Keyword filter using a keyword NOT PRESENT
  258. // in test cases => Tree root shows loading icon and spin never stops.
  259. //
  260. // Attention: do not know if in other situation this will generate a different bug
  261. //
  262. if(!is_null($menustring))
  263. {
  264. // Remove null elements (Ext JS tree do not like it ).
  265. // :null happens on -> "children":null,"text" that must become "children":[],"text"
  266. // $menustring = str_ireplace(array(':null',',null','null,'),array(':[]','',''), $menustring);
  267. $menustring = str_ireplace(array(':null',',null','null,','null'),array(':[]','','',''), $menustring);
  268. }
  269. $treeMenu->menustring = $menustring;
  270. return $treeMenu;
  271. }
  272. /**
  273. * Prepares a Node to be displayed in a navigation tree.
  274. * This function is used in the construction of:
  275. * - Test project specification -> we want ALL test cases defined in test project.
  276. * - Test execution -> we only want the test cases linked to a test plan.
  277. *
  278. * IMPORTANT:
  279. * when analising a container node (Test Suite) if it is empty and we have requested
  280. * some sort of filtering NODE WILL BE PRUNED.
  281. *
  282. *
  283. * status: one of the possible execution status of a test case.
  284. *
  285. *
  286. * tplan_tcases: map with testcase versions linked to test plan.
  287. * due to the multiples uses of this function, null has to meanings
  288. *
  289. * When we want to build a Test Project specification tree,
  290. * WE SET it to NULL, because we are not interested in a test plan.
  291. *
  292. * When we want to build a Test execution tree, we dont set it deliverately
  293. * to null, but null can be the result of NO tcversion linked.
  294. *
  295. *
  296. * 20081220 - franciscom - status can be an array with multple values, to do OR search.
  297. *
  298. * 20071014 - franciscom - added version info fro test cases in return data structure.
  299. *
  300. * 20061105 - franciscom
  301. * ignore_inactive_testcases: useful when building a Test Project Specification tree
  302. * to be used in the add/link test case to Test Plan.
  303. *
  304. *
  305. * 20061030 - franciscom
  306. * tck_map: Test Case Keyword map:
  307. * null => no filter
  308. * empty map => filter out ALL test case ALWAYS
  309. * initialized map => filter out test case ONLY if NOT present in map.
  310. *
  311. *
  312. * added argument:
  313. * $map_node_tccount
  314. * key => node_id
  315. * values => node test case count
  316. * node name (useful only for debug purpouses
  317. *
  318. * IMPORTANT: this new argument is not useful for tree rendering
  319. * but to avoid duplicating logic to get test case count
  320. *
  321. *
  322. * return: map with keys:
  323. * 'total_count'
  324. * 'passed'
  325. * 'failed'
  326. * 'blocked'
  327. * 'not run'
  328. *
  329. * @internal Revisions
  330. * 20100810 - asimon - filtering by testcase ID
  331. * 20100417 - franciscom - BUGID 2498 - filter by test case importance
  332. * 20100415 - franciscom - BUGID 2797 - filter by test case execution type
  333. */
  334. function prepareNode(&$db,&$node,&$decoding_info,&$map_node_tccount,$tck_map = null,
  335. $tplan_tcases = null,$filters=null, $options=null)
  336. {
  337. static $hash_id_descr;
  338. static $status_descr_code;
  339. static $status_code_descr;
  340. static $debugMsg;
  341. static $tables;
  342. static $my;
  343. static $enabledFiltersOn;
  344. static $activeVersionClause;
  345. static $filterOnTCVersionAttribute;
  346. static $filtersApplied;
  347. $tpNode = null;
  348. if (!$tables)
  349. {
  350. $debugMsg = 'Class: ' . __CLASS__ . ' - ' . 'Method: ' . __FUNCTION__ . ' - ';
  351. $tables = tlObjectWithDB::getDBTables(array('tcversions','nodes_hierarchy','testplan_tcversions'));
  352. }
  353. if (!$hash_id_descr)
  354. {
  355. $hash_id_descr = $decoding_info['node_id_descr'];
  356. }
  357. if (!$status_descr_code)
  358. {
  359. $status_descr_code = $decoding_info['status_descr_code'];
  360. }
  361. if (!$status_code_descr)
  362. {
  363. $status_code_descr = $decoding_info['status_code_descr'];
  364. }
  365. if (!$my)
  366. {
  367. $my = array();
  368. $my['options'] = array('hideTestCases' => 0, 'showTestCaseID' => 1, 'viewType' => 'testSpecTree',
  369. 'getExternalTestCaseID' => 1,'ignoreInactiveTestCases' => 0);
  370. // asimon - added importance here because of "undefined" error in event log
  371. $my['filters'] = array('status' => null, 'assignedTo' => null,
  372. 'importance' => null, 'executionType' => null,
  373. 'filter_tc_id' => null);
  374. $my['options'] = array_merge($my['options'], (array)$options);
  375. $my['filters'] = array_merge($my['filters'], (array)$filters);
  376. }
  377. if(!$enabledFiltersOn)
  378. {
  379. // 3301 - added filtering by testcase name
  380. // 20100702 - and custom fields
  381. // 20100810 - and testcase ID
  382. $enabledFiltersOn['testcase_id'] =
  383. isset($my['filters']['filter_tc_id']);
  384. $enabledFiltersOn['testcase_name'] =
  385. isset($my['filters']['filter_testcase_name']);
  386. $enabledFiltersOn['keywords'] = isset($tck_map);
  387. $enabledFiltersOn['executionType'] =
  388. isset($my['filters']['filter_execution_type']);
  389. $enabledFiltersOn['importance'] = isset($my['filters']['filter_priority']);
  390. $enabledFiltersOn['custom_fields'] = isset($my['filters']['filter_custom_fields']);
  391. $filterOnTCVersionAttribute = $enabledFiltersOn['executionType'] || $enabledFiltersOn['importance'];
  392. $filtersApplied = false;
  393. foreach($enabledFiltersOn as $filterValue)
  394. {
  395. $filtersApplied = $filtersApplied || $filterValue;
  396. }
  397. $activeVersionClause = $filterOnTCVersionAttribute ? " AND TCV.active=1 " : '';
  398. }
  399. $tcase_counters = array('testcase_count' => 0);
  400. foreach($status_descr_code as $status_descr => $status_code)
  401. {
  402. $tcase_counters[$status_descr]=0;
  403. }
  404. $node_type = isset($node['node_type_id']) ? $hash_id_descr[$node['node_type_id']] : null;
  405. $tcase_counters['testcase_count']=0;
  406. if($node_type == 'testcase')
  407. {
  408. $viewType = $my['options']['viewType'];
  409. if ($enabledFiltersOn['keywords'])
  410. {
  411. if (!isset($tck_map[$node['id']]))
  412. {
  413. $node = null;
  414. }
  415. }
  416. // added filter by testcase name
  417. if ($node && $enabledFiltersOn['testcase_name']) {
  418. $filter_name = $my['filters']['filter_testcase_name'];
  419. // IMPORTANT:
  420. // checking with === here, because function stripos could return 0 when string
  421. // is found at position 0, if clause would then evaluate wrong because
  422. // 0 would be casted to false and we only want to delete node if it really is false
  423. if (stripos($node['name'], $filter_name) === FALSE) {
  424. $node = null;
  425. }
  426. }
  427. // filter by testcase ID
  428. if ($node && $enabledFiltersOn['testcase_id']) {
  429. $filter_id = $my['filters']['filter_tc_id'];
  430. if ($node['id'] != $filter_id) {
  431. $node = null;
  432. }
  433. }
  434. if ($node && $viewType == 'executionTree')
  435. {
  436. $tpNode = isset($tplan_tcases[$node['id']]) ? $tplan_tcases[$node['id']] : null;
  437. // if (!$tpNode || (!is_null($my['filters']['assignedTo'])) &&
  438. // ((isset($my['filters']['assignedTo'][TL_USER_NOBODY]) && !is_null($tpNode['user_id'])) ||
  439. // (!isset($my['filters']['assignedTo'][TL_USER_NOBODY]) && (!isset($my['filters']['assignedTo'][TL_USER_SOMEBODY])) &&
  440. // !isset($my['filters']['assignedTo'][$tpNode['user_id']]))) ||
  441. // (!is_null($my['filters']['status']) && !isset($my['filters']['status'][$tpNode['exec_status']])) ||
  442. // (isset($my['filters']['assignedTo'][TL_USER_SOMEBODY]) && !is_numeric($tpNode['user_id']))
  443. // )
  444. // {
  445. // asimon - additional variables for better readability of following if condition.
  446. // For original statement/condition see above commented out lines.
  447. // This is longer, but MUCH better readable and easier to extend for new filter conditions.
  448. $users2filter = isset($my['filters']['filter_assigned_user']) ?
  449. $my['filters']['filter_assigned_user'] : null;
  450. $results2filter = isset($my['filters']['filter_result_result']) ?
  451. $my['filters']['filter_result_result'] : null;
  452. $wrong_result = !is_null($results2filter)
  453. && !isset($results2filter[$tpNode['exec_status']]);
  454. $somebody_wanted_but_nobody_there = !is_null($users2filter)
  455. && isset($users2filter[TL_USER_SOMEBODY])
  456. && !is_numeric($tpNode['user_id']);
  457. $unassigned_wanted_but_someone_assigned = !is_null($users2filter)
  458. && isset($users2filter[TL_USER_NOBODY])
  459. && !is_null($tpNode['user_id']);
  460. $wrong_user = !is_null($users2filter)
  461. && !isset($users2filter[TL_USER_NOBODY])
  462. && !isset($users2filter[TL_USER_SOMEBODY])
  463. && !isset($users2filter[$tpNode['user_id']]);
  464. $delete_node = $unassigned_wanted_but_someone_assigned
  465. || $wrong_user
  466. || $wrong_result
  467. || $somebody_wanted_but_nobody_there;
  468. if (!$tpNode || $delete_node) {
  469. $node = null;
  470. } else {
  471. $externalID='';
  472. $node['tcversion_id'] = $tpNode['tcversion_id'];
  473. $node['version'] = $tpNode['version'];
  474. if ($my['options']['getExternalTestCaseID'])
  475. {
  476. if (!isset($tpNode['external_id']))
  477. {
  478. $sql = " /* $debugMsg - line:" . __LINE__ . " */ " .
  479. " SELECT TCV.tc_external_id AS external_id " .
  480. " FROM {$tables['tcversions']} TCV " .
  481. " WHERE TCV.id=" . $node['tcversion_id'];
  482. $result = $db->exec_query($sql);
  483. $myrow = $db->fetch_array($result);
  484. $externalID = $myrow['external_id'];
  485. }
  486. else
  487. {
  488. $externalID = $tpNode['external_id'];
  489. }
  490. }
  491. $node['external_id'] = $externalID;
  492. unset($tplan_tcases[$node['id']]);
  493. }
  494. }
  495. if ($node && $my['options']['ignoreInactiveTestCases'])
  496. {
  497. // there are active tcversions for this node ???
  498. // I'm doing this instead of creating a test case manager object, because
  499. // I think is better for performance.
  500. //
  501. // =======================================================================================
  502. // 20070106 - franciscom
  503. // Postgres Problems
  504. // =======================================================================================
  505. // Problem 1 - SQL Syntax
  506. // While testing with postgres
  507. // SELECT count(TCV.id) NUM_ACTIVE_VERSIONS -> Error
  508. //
  509. // At least for what I remember using AS to create COLUMN ALIAS IS REQUIRED and Standard
  510. // while AS is NOT REQUIRED (and with some DBMS causes errors) when you want to give a
  511. // TABLE ALIAS
  512. //
  513. // Problem 2 - alias case
  514. // At least in my installation the aliases column name is returned lower case, then
  515. // PHP fails when:
  516. // if($myrow['NUM_ACTIVE_VERSIONS'] == 0)
  517. //
  518. //
  519. $sql=" /* $debugMsg - line:" . __LINE__ . " */ " .
  520. " SELECT count(TCV.id) AS num_active_versions " .
  521. " FROM {$tables['tcversions']} TCV, {$tables['nodes_hierarchy']} NH " .
  522. " WHERE NH.parent_id=" . $node['id'] .
  523. " AND NH.id = TCV.id AND TCV.active=1";
  524. $result = $db->exec_query($sql);
  525. $myrow = $db->fetch_array($result);
  526. if($myrow['num_active_versions'] == 0)
  527. {
  528. $node = null;
  529. }
  530. }
  531. // -------------------------------------------------------------------
  532. if ($node && ($viewType=='testSpecTree' || $viewType=='testSpecTreeForTestPlan') )
  533. {
  534. $sql = " /* $debugMsg - line:" . __LINE__ . " */ " .
  535. " SELECT COALESCE(MAX(TCV.id),0) AS targetid, TCV.tc_external_id AS external_id" .
  536. " FROM {$tables['tcversions']} TCV, {$tables['nodes_hierarchy']} NH " .
  537. " WHERE NH.id = TCV.id {$activeVersionClause} AND NH.parent_id={$node['id']} " .
  538. " GROUP BY TCV.tc_external_id ";
  539. $rs = $db->get_recordset($sql);
  540. if( is_null($rs) )
  541. {
  542. $node = null;
  543. }
  544. else
  545. {
  546. $node['external_id'] = $rs[0]['external_id'];
  547. $target_id = $rs[0]['targetid'];
  548. if( $filterOnTCVersionAttribute )
  549. {
  550. // BUGID 2797
  551. switch ($viewType)
  552. {
  553. case 'testSpecTreeForTestPlan':
  554. // Try to get info from linked tcversions
  555. // Platform is not needed
  556. $sql = " /* $debugMsg - line:" . __LINE__ . " */ " .
  557. " SELECT DISTINCT TPTCV.tcversion_id AS targetid " .
  558. " FROM {$tables['tcversions']} TCV " .
  559. " JOIN {$tables['nodes_hierarchy']} NH " .
  560. " ON NH.id = TCV.id {$activeVersionClause} " .
  561. " AND NH.parent_id={$node['id']} " .
  562. " JOIN {$tables['testplan_tcversions']} TPTCV " .
  563. " ON TPTCV.tcversion_id = TCV.id " .
  564. " AND TPTCV.testplan_id = " .
  565. " {$my['filters']['setting_testplan']}";
  566. $rs = $db->get_recordset($sql);
  567. $target_id = !is_null($rs) ? $rs[0]['targetid'] : $target_id;
  568. break;
  569. }
  570. $sql = " /* $debugMsg - line:" . __LINE__ . " */ " .
  571. " SELECT TCV.execution_type " .
  572. " FROM {$tables['tcversions']} TCV " .
  573. " WHERE TCV.id = {$target_id} ";
  574. if( $enabledFiltersOn['executionType'] )
  575. {
  576. $sql .= " AND TCV.execution_type = " .
  577. " {$my['filters']['filter_execution_type']} ";
  578. }
  579. if( $enabledFiltersOn['importance'] )
  580. {
  581. $sql .= " AND TCV.importance = " .
  582. " {$my['filters']['filter_priority']} ";
  583. }
  584. $rs = $db->fetchRowsIntoMap($sql,'execution_type');
  585. if(is_null($rs))
  586. {
  587. $node = null;
  588. }
  589. }
  590. }
  591. if( !is_null($node) )
  592. {
  593. // needed to avoid problems when using json_encode with EXTJS
  594. unset($node['childNodes']);
  595. $node['leaf']=true;
  596. }
  597. }
  598. // -------------------------------------------------------------------
  599. foreach($tcase_counters as $key => $value)
  600. {
  601. $tcase_counters[$key]=0;
  602. }
  603. if(isset($tpNode['exec_status']) )
  604. {
  605. $tc_status_code = $tpNode['exec_status'];
  606. $tc_status_descr = $status_code_descr[$tc_status_code];
  607. }
  608. else
  609. {
  610. $tc_status_descr = "not_run";
  611. $tc_status_code = $status_descr_code[$tc_status_descr];
  612. }
  613. $init_value = $node ? 1 : 0;
  614. $tcase_counters[$tc_status_descr]=$init_value;
  615. $tcase_counters['testcase_count']=$init_value;
  616. if ( $my['options']['hideTestCases'] )
  617. {
  618. $node = null;
  619. }
  620. } // if($node_type == 'testcase')
  621. if (isset($node['childNodes']) && is_array($node['childNodes']))
  622. {
  623. // node has to be a Test Suite ?
  624. $childNodes = &$node['childNodes'];
  625. $childNodesQty = count($childNodes);
  626. for($idx = 0;$idx < $childNodesQty ;$idx++)
  627. {
  628. $current = &$childNodes[$idx];
  629. // I use set an element to null to filter out leaf menu items
  630. if(is_null($current))
  631. {
  632. continue;
  633. }
  634. $counters_map = prepareNode($db,$current,$decoding_info,$map_node_tccount,
  635. $tck_map,$tplan_tcases,$my['filters'],$my['options']);
  636. foreach($counters_map as $key => $value)
  637. {
  638. $tcase_counters[$key] += $counters_map[$key];
  639. }
  640. }
  641. foreach($tcase_counters as $key => $value)
  642. {
  643. $node[$key] = $tcase_counters[$key];
  644. }
  645. if (isset($node['id']))
  646. {
  647. $map_node_tccount[$node['id']] = array( 'testcount' => $node['testcase_count'],
  648. 'name' => $node['name']);
  649. }
  650. // node must be dstroyed if empty had we have using filtering conditions
  651. if( ($filtersApplied || !is_null($tplan_tcases)) &&
  652. !$tcase_counters['testcase_count'] && ($node_type != 'testproject'))
  653. {
  654. $node = null;
  655. }
  656. }
  657. else if ($node_type == 'testsuite')
  658. {
  659. // does this means is an empty test suite ??? - franciscom 20080328
  660. $map_node_tccount[$node['id']] = array( 'testcount' => 0,'name' => $node['name']);
  661. // If is an EMPTY Test suite and we have added filtering conditions,
  662. // We will destroy it.
  663. if ($filtersApplied || !is_null($tplan_tcases) )
  664. {
  665. $node = null;
  666. }
  667. }
  668. return $tcase_counters;
  669. }
  670. /**
  671. * Create the string representation suitable to create a graphic visualization
  672. * of a node, for the type of menu selected.
  673. *
  674. * @internal Revisions
  675. * 20100611 - franciscom - removed useless $getArguments
  676. */
  677. function renderTreeNode($level,&$node,$hash_id_descr,
  678. $tc_action_enabled,$linkto,$testCasePrefix,
  679. $bForPrinting=0,$showTestCaseID)
  680. {
  681. $menustring='';
  682. $node_type = $hash_id_descr[$node['node_type_id']];
  683. extjs_renderTestSpecTreeNodeOnOpen($node,$node_type,$tc_action_enabled,$bForPrinting,
  684. $showTestCaseID,$testCasePrefix);
  685. if (isset($node['childNodes']) && $node['childNodes'])
  686. {
  687. // 20090118 - franciscom - need to work always original object
  688. // in order to change it's values using reference .
  689. // Can not assign anymore to intermediate variables.
  690. //
  691. $nChildren = sizeof($node['childNodes']);
  692. for($idx = 0;$idx < $nChildren;$idx++)
  693. {
  694. // asimon - replaced is_null by !isset because of warnings in event log
  695. if(!isset($node['childNodes'][$idx]))
  696. //if(is_null($node['childNodes'][$idx]))
  697. {
  698. continue;
  699. }
  700. $menustring .= renderTreeNode($level+1,$node['childNodes'][$idx],$hash_id_descr,
  701. $tc_action_enabled,$linkto,$testCasePrefix,
  702. $bForPrinting,$showTestCaseID);
  703. }
  704. }
  705. return $menustring;
  706. }
  707. /**
  708. * Creates data for tree menu used on :
  709. * - Execution of Test Cases
  710. * - Remove Test cases from test plan
  711. *
  712. * @internal Revisions:
  713. * 20100820 - asimon - refactoring for less redundant checks and better readibility
  714. * 20100719 - asimon - BUGID 3406 - user assignments per build:
  715. * filter assigned test cases by setting_build
  716. * 20080617 - franciscom - return type changed to use extjs tree component
  717. * 20080305 - franciscom - interface refactoring
  718. * 20080224 - franciscom - added include_unassigned
  719. * 20071002 - jbarchibald - BUGID 1051 - added cf element to parameter list
  720. * 20070204 - franciscom - changed $bForPrinting -> $bHideTCs
  721. */
  722. function generateExecTree(&$db,&$menuUrl,$tproject_id,$tproject_name,$tplan_id,
  723. $tplan_name,$filters,$additionalInfo)
  724. {
  725. $treeMenu = new stdClass();
  726. $treeMenu->rootnode = null;
  727. $treeMenu->menustring = '';
  728. $resultsCfg = config_get('results');
  729. $showTestCaseID = config_get('treemenu_show_testcase_id');
  730. $glueChar=config_get('testcase_cfg')->glue_character;
  731. $menustring = null;
  732. $any_exec_status = null;
  733. $tplan_tcases = null;
  734. $tck_map = null;
  735. $idx=0;
  736. $testCaseQty=0;
  737. $testCaseSet=null;
  738. $keyword_id = 0;
  739. $keywordsFilterType = 'Or';
  740. if (property_exists($filters, 'filter_keywords') && !is_null($filters->{'filter_keywords'})) {
  741. $keyword_id = $filters->{'filter_keywords'};
  742. $keywordsFilterType = $filters->{'filter_keywords_filter_type'};
  743. }
  744. // BUGID 3406 - user assignments per build
  745. // can't use $build_id here because we need the build ID from settings panel
  746. $build2filter_assignments = isset($filters->{'setting_build'}) ? $filters->{'setting_build'} : 0;
  747. $tc_id = isset($filters->filter_tc_id) ? $filters->filter_tc_id : null;
  748. $build_id = isset($filters->filter_result_build) ? $filters->filter_result_build : null;
  749. $bHideTCs = isset($filters->hide_testcases) ? $filters->hide_testcases : false;
  750. $assignedTo = isset($filters->filter_assigned_user) ? $filters->filter_assigned_user : null;
  751. $include_unassigned = isset($filters->filter_assigned_user_include_unassigned) ?
  752. $filters->filter_assigned_user_include_unassigned : false;
  753. $setting_platform = isset($filters->setting_platform) ? $filters->setting_platform : null;
  754. $execution_type = isset($filters->filter_execution_type) ? $filters->filter_execution_type : null;
  755. $status = isset($filters->filter_result_result) ? $filters->filter_result_result : null;
  756. $cf_hash = isset($filters->filter_custom_fields) ? $filters->filter_custom_fields : null;
  757. $show_testsuite_contents = isset($filters->show_testsuite_contents) ?
  758. $filters->show_testsuite_contents : true;
  759. $urgencyImportance = isset($filters->filter_priority) ?
  760. $filters->filter_priority : null;
  761. $useCounters=isset($additionalInfo->useCounters) ? $additionalInfo->useCounters : null;
  762. $useColors=isset($additionalInfo->useColours) ? $additionalInfo->useColours : null;
  763. $colorBySelectedBuild = isset($additionalInfo->testcases_colouring_by_selected_build) ?
  764. $additionalInfo->testcases_colouring_by_selected_build : null;
  765. $tplan_mgr = new testplan($db);
  766. $tproject_mgr = new testproject($db);
  767. $tcase_mgr = new testcase($db);
  768. $tree_manager = $tplan_mgr->tree_manager;
  769. $tcase_node_type = $tree_manager->node_descr_id['testcase'];
  770. $hash_descr_id = $tree_manager->get_available_node_types();
  771. $hash_id_descr = array_flip($hash_descr_id);
  772. $decoding_hash = array('node_id_descr' => $hash_id_descr,
  773. 'status_descr_code' => $resultsCfg['status_code'],
  774. 'status_code_descr' => $resultsCfg['code_status']);
  775. $tcase_prefix = $tproject_mgr->getTestCasePrefix($tproject_id) . $glueChar;
  776. $nt2exclude = array('testplan' => 'exclude_me',
  777. 'requirement_spec'=> 'exclude_me',
  778. 'requirement'=> 'exclude_me');
  779. $nt2exclude_children = array('testcase' => 'exclude_my_children',
  780. 'requirement_spec'=> 'exclude_my_children');
  781. $my['options']=array('recursive' => true,
  782. 'order_cfg' => array("type" =>'exec_order',"tplan_id" => $tplan_id));
  783. $my['filters'] = array('exclude_node_types' => $nt2exclude,
  784. 'exclude_children_of' => $nt2exclude_children);
  785. // BUGID 3301 - added for filtering by toplevel testsuite
  786. if (isset($filters->{'filter_toplevel_testsuite'}) && is_array($filters->{'filter_toplevel_testsuite'})) {
  787. $my['filters']['exclude_branches'] = $filters->{'filter_toplevel_testsuite'};
  788. }
  789. $test_spec = $tree_manager->get_subtree($tproject_id,$my['filters'],$my['options']);
  790. $test_spec['name'] = $tproject_name . " / " . $tplan_name; // To be discussed
  791. $test_spec['id'] = $tproject_id;
  792. $test_spec['node_type_id'] = $hash_descr_id['testproject'];
  793. $map_node_tccount = array();
  794. $tplan_tcases = null;
  795. $apply_other_filters=true;
  796. if($test_spec)
  797. {
  798. if(is_null($tc_id) || $tc_id >= 0)
  799. {
  800. $doFilterByKeyword = (!is_null($keyword_id) && $keyword_id > 0);
  801. if($doFilterByKeyword)
  802. {
  803. $tck_map = $tproject_mgr->get_keywords_tcases($tproject_id,$keyword_id,$keywordsFilterType);
  804. }
  805. // Multiple step algoritm to apply keyword filter on type=AND
  806. // get_linked_tcversions filters by keyword ALWAYS in OR mode.
  807. // BUGID 3406: user assignments per build
  808. $opt = array('include_unassigned' => $include_unassigned, 'steps_info' => false,
  809. 'user_assignments_per_build' => $build2filter_assignments);
  810. // 20100417 - BUGID 3380 - execution type
  811. $linkedFilters = array('tcase_id' => $tc_id, 'keyword_id' => $keyword_id,
  812. 'assigned_to' => $assignedTo,
  813. 'cf_hash' => $cf_hash,
  814. 'platform_id' => $setting_platform,
  815. 'urgencyImportance' => $urgencyImportance,
  816. 'exec_type' => $execution_type);
  817. $tplan_tcases = $tplan_mgr->get_linked_tcversions($tplan_id,$linkedFilters,$opt);
  818. if($tplan_tcases && $doFilterByKeyword && $keywordsFilterType == 'AND')
  819. {
  820. $filteredSet = $tcase_mgr->filterByKeyword(array_keys($tplan_tcases),$keyword_id,$keywordsFilterType);
  821. // CAUTION: if $filteredSet is null,
  822. // then get_linked_tcversions() thinks there are just no filters set,
  823. // but really there are no testcases which match the wanted keyword criteria,
  824. // so we have to set $tplan_tcases to null because there is no more filtering necessary
  825. if ($filteredSet != null) {
  826. $linkedFilters = array('tcase_id' => array_keys($filteredSet));
  827. $tplan_tcases = $tplan_mgr->get_linked_tcversions($tplan_id,$linkedFilters);
  828. } else {
  829. $tplan_tcases = null;
  830. }
  831. }
  832. }
  833. if (is_null($tplan_tcases))
  834. {
  835. $tplan_tcases = array();
  836. $apply_other_filters=false;
  837. }
  838. $filter_methods = config_get('execution_filter_methods');
  839. // 20100820 - asimon - refactoring for less redundant checks and better readibility
  840. $ffn = array($filter_methods['status_code']['any_build'] => 'filter_by_status_for_any_build',
  841. $filter_methods['status_code']['all_builds'] => 'filter_by_same_status_for_all_builds',
  842. $filter_methods['status_code']['specific_build'] => 'filter_by_status_for_build',
  843. $filter_methods['status_code']['current_build'] => 'filter_by_status_for_build',
  844. $filter_methods['status_code']['latest_execution'] => 'filter_by_status_for_last_execution');
  845. $requested_filter_method = isset($filters->filter_result_method) ? $filters->filter_result_method : null;
  846. $requested_filter_result = isset($filters->filter_result_result) ? $filters->filter_result_result : null;
  847. // if "any" was selected as filtering status, don't filter by status
  848. $requested_filter_result = (array)$requested_filter_result;
  849. if (in_array($resultsCfg['status_code']['all'], $requested_filter_result)) {
  850. $requested_filter_result = null;
  851. }
  852. if ($apply_other_filters && !is_null($requested_filter_method) && isset($ffn[$requested_filter_method])) {
  853. // special case 1: when filtering by "not run" status in any build,
  854. // we need another filter function
  855. if (in_array($resultsCfg['status_code']['not_run'], $requested_filter_result)) {
  856. $ffn[$filter_methods['status_code']['any_build']] = 'filter_not_run_for_any_build';
  857. }
  858. // special case 2: when filtering by "current build", we set the build to filter with
  859. // to the build chosen in settings instead of the one in filters
  860. if ($requested_filter_method == $filter_methods['status_code']['current_build']) {
  861. $filters->filter_result_build = $filters->setting_build;
  862. }
  863. // call the filter function and do the filtering
  864. $tplan_tcases = $ffn[$requested_filter_method]($tplan_mgr, $tplan_tcases, $tplan_id, $filters);
  865. if (is_null($tplan_tcases)) {
  866. $tplan_tcases = array();
  867. $apply_other_filters=false;
  868. }
  869. }
  870. // end 20100820 refactoring
  871. // BUGID 3450 - Change colors/counters in exec tree.
  872. // Means: replace exec status in filtered array $tplan_tcases
  873. // by the one of last execution of selected build.
  874. // Since this changes exec status, replacing is done after filtering by status.
  875. // It has to be done before call to prepareNode() though,
  876. // because that one sets the counters according to status.
  877. if ($useColors && $colorBySelectedBuild) {
  878. $tables = tlObject::getDBTables('executions');
  879. foreach ($tplan_tcases as $id => $info) {
  880. // get last execution result for selected build
  881. $sql = " SELECT status FROM {$tables['executions']} E " .
  882. " WHERE tcversion_id = {$info['tcversion_id']} " .
  883. " AND testplan_id = {$tplan_id} " .
  884. " AND platform_id = {$info['platform_id']} " .
  885. " AND build_id = {$filters->setting_build} " .
  886. " ORDER BY execution_ts DESC LIMIT 1 ";
  887. $result = null;
  888. $result = $db->fetchOneValue($sql);
  889. if (is_null($result)) {
  890. // if no value can be loaded it has to be set to not run
  891. $result = $resultsCfg['status_code']['not_run'];
  892. }
  893. if ($result != $info['exec_status']) {
  894. $tplan_tcases[$id]['exec_status'] = $result;
  895. }
  896. }
  897. }
  898. // 20080224 - franciscom -
  899. // After reviewing code, seems that assignedTo has no sense because tp_tcs
  900. // has been filtered.
  901. // Then to avoid changes to prepareNode() due to include_unassigned,
  902. // seems enough to set assignedTo to 0, if include_unassigned==true
  903. $assignedTo = $include_unassigned ? null : $assignedTo;
  904. $pnFilters = array('assignedTo' => $assignedTo);
  905. $keys2init = array('filter_testcase_name',
  906. 'filter_execution_type',
  907. 'filter_priority');
  908. foreach ($keys2init as $keyname) {
  909. $pnFilters[$keyname] = isset($filters->{$keyname}) ? $filters->{$keyname} : null;
  910. }
  911. // 20100412 - franciscom
  912. $pnOptions = array('hideTestCases' => $bHideTCs, 'viewType' => 'executionTree');
  913. $testcase_counters = prepareNode($db,$test_spec,$decoding_hash,$map_node_tccount,
  914. $tck_map,$tplan_tcases,$pnFilters,$pnOptions);
  915. foreach($testcase_counters as $key => $value)
  916. {
  917. $test_spec[$key] = $testcase_counters[$key];
  918. }
  919. // BUGID 3516
  920. // can now be left in form of array, will not be sent by URL anymore
  921. //$keys = implode(array_keys($tplan_tcases), ",");
  922. $keys = array_keys($tplan_tcases);
  923. $menustring = renderExecTreeNode(1,$test_spec,$tplan_tcases,
  924. $hash_id_descr,1,$menuUrl,$bHideTCs,$useCounters,$useColors,
  925. $showTestCaseID,$tcase_prefix,$show_testsuite_contents);
  926. } // if($test_spec)
  927. $treeMenu->rootnode=new stdClass();
  928. $treeMenu->rootnode->name=$test_spec['text'];
  929. $treeMenu->rootnode->id=$test_spec['id'];
  930. $treeMenu->rootnode->leaf=$test_spec['leaf'];
  931. $treeMenu->rootnode->text=$test_spec['text'];
  932. $treeMenu->rootnode->position=$test_spec['position'];
  933. $treeMenu->rootnode->href=$test_spec['href'];
  934. if( !is_null($menustring) )
  935. {
  936. // Change key ('childNodes') to the one required by Ext JS tree.
  937. $menustring = str_ireplace('childNodes', 'children', json_encode($test_spec['childNodes']));
  938. // Remove null elements (Ext JS tree do not like it ).
  939. // :null happens on -> "children":null,"text" that must become "children":[],"text"
  940. // $menustring = str_ireplace(array(':null',',null','null,'),array(':[]','',''), $menustring);
  941. $menustring = str_ireplace(array(':null',',null','null,','null'),array(':[]','','',''), $menustring);
  942. }
  943. $treeMenu->menustring = $menustring;
  944. return array($treeMenu, $keys);
  945. }
  946. /**
  947. *
  948. *
  949. * @param integer $level
  950. * @param array &$node reference to recursive map
  951. * @param array &$tcases_map reference to map that contains info about testcase exec status
  952. * when node is of testcase type.
  953. * @param boolean $bHideTCs 1 -> hide testcase
  954. *
  955. * @return datatype description
  956. *
  957. * @internal Revisions:
  958. * 20100611 - franciscom - removed useless $getArguments
  959. * 20071229 - franciscom -added $useCounters,$useColors
  960. */
  961. function renderExecTreeNode($level,&$node,&$tcase_node,$hash_id_descr,
  962. $tc_action_enabled,$linkto,$bHideTCs,$useCounters,$useColors,
  963. $showTestCaseID,$testCasePrefix,$showTestSuiteContents)
  964. {
  965. $node_type = $hash_id_descr[$node['node_type_id']];
  966. $menustring = '';
  967. extjs_renderExecTreeNodeOnOpen($node,$node_type,$tcase_node,$tc_action_enabled,$bHideTCs,
  968. $useCounters,$useColors,$showTestCaseID,$testCasePrefix,
  969. $showTestSuiteContents);
  970. if( isset($tcase_node[$node['id']]) )
  971. {
  972. unset($tcase_node[$node['id']]);
  973. }
  974. if (isset($node['childNodes']) && $node['childNodes'])
  975. {
  976. // 20080615 - franciscom - need to work always original object
  977. // in order to change it's values using reference .
  978. // Can not assign anymore to intermediate variables.
  979. $nodes_qty = sizeof($node['childNodes']);
  980. for($idx = 0;$idx <$nodes_qty ;$idx++)
  981. {
  982. if(is_null($node['childNodes'][$idx]))
  983. {
  984. continue;
  985. }
  986. $menustring .= renderExecTreeNode($level+1,$node['childNodes'][$idx],$tcase_node,
  987. $hash_id_descr,
  988. $tc_action_enabled,$linkto,$bHideTCs,
  989. $useCounters,$useColors,$showTestCaseID,
  990. $testCasePrefix,$showTestSuiteContents);
  991. }
  992. }
  993. return $menustring;
  994. }
  995. /**
  996. * @return array a map:
  997. * key => node_id
  998. * values => node test case count considering test cases presents
  999. * in the nodes of the subtree that starts on node_id
  1000. * Means test case can not be sons/daughters of node_id.
  1001. * node name (useful only for debug purpouses).
  1002. */
  1003. function get_testproject_nodes_testcount(&$db,$tproject_id, $tproject_name,
  1004. $keywordsFilter=null)
  1005. {
  1006. $tproject_mgr = new testproject($db);
  1007. $tree_manager = &$tproject_mgr->tree_manager;
  1008. $tcase_node_type = $tree_manager->node_descr_id['testcase'];
  1009. $hash_descr_id = $tree_manager->get_available_node_types();
  1010. $hash_id_descr = array_flip($hash_descr_id);
  1011. $resultsCfg = config_get('results');
  1012. $decoding_hash = array('node_id_descr' => $hash_id_descr,
  1013. 'status_descr_code' => $resultsCfg['status_code'],
  1014. 'status_code_descr' => $resultsCfg['code_status']);
  1015. $test_spec = $tproject_mgr->get_subtree($tproject_id,RECURSIVE_MODE);
  1016. $test_spec['name'] = $tproject_name;
  1017. $test_spec['id'] = $tproject_id;
  1018. $test_spec['node_type_id'] = 1;
  1019. $map_node_tccount = array();
  1020. $tplan_tcases = null;
  1021. if($test_spec)
  1022. {
  1023. $tck_map = null;
  1024. if( !is_null($keywordsFilter) )
  1025. {
  1026. $tck_map = $tproject_mgr->get_keywords_tcases($tproject_id,
  1027. $keywordsFilter->items,$keywordsFilter->type);
  1028. }
  1029. //@TODO: schlundus, can we speed up with NO_EXTERNAL?
  1030. $filters = null;
  1031. // 20100412 - franciscon
  1032. $options = array('hideTestCases' => 0, 'viewType' => 'testSpecTree');
  1033. $testcase_counters = prepareNode($db,$test_spec,$decoding_hash,$map_node_tccount,
  1034. $tck_map,$tplan_tcases,$filters,$options);
  1035. $test_spec['testcase_count'] = $testcase_counters['testcase_count'];
  1036. }
  1037. return $map_node_tccount;
  1038. }
  1039. /**
  1040. * @return array a map:
  1041. * key => node_id
  1042. * values => node test case count considering test cases presents
  1043. * in the nodes of the subtree that starts on node_id
  1044. * Means test case can not be sons/daughters of node_id.
  1045. *
  1046. * node name (useful only for debug purpouses).
  1047. */
  1048. function get_testplan_nodes_testcount(&$db,$tproject_id, $tproject_name,
  1049. $tplan_id,$tplan_name,$keywordsFilter=null)
  1050. {
  1051. $tplan_mgr = new testplan($db);
  1052. $tproject_mgr = new testproject($db);
  1053. $tree_manager = $tplan_mgr->tree_manager;
  1054. $tcase_node_type = $tree_manager->node_descr_id['testcase'];
  1055. $hash_descr_id = $tree_manager->get_available_node_types();
  1056. $hash_id_descr = array_flip($hash_descr_id);
  1057. $resultsCfg=config_get('results');
  1058. $decoding_hash=array('node_id_descr' => $hash_id_descr, 'status_descr_code' => $resultsCfg['status_code'],
  1059. 'status_code_descr' => $resultsCfg['code_status']);
  1060. $test_spec = $tproject_mgr->get_subtree($tproject_id,RECURSIVE_MODE);
  1061. $linkedFilters = array('keyword_id' => $keywordsFilter->items);
  1062. $tplan_tcases = $tplan_mgr->get_linked_tcversions($tplan_id,$linkedFilters);
  1063. if (is_null($tplan_tcases))
  1064. {
  1065. $tplan_tcases = array();
  1066. }
  1067. $test_spec['name'] = $tproject_name;
  1068. $test_spec['id'] = $tproject_id;
  1069. $test_spec['node_type_id'] = $hash_descr_id['testproject'];
  1070. $map_node_tccount=array();
  1071. if($test_spec)
  1072. {
  1073. $tck_map = null;
  1074. if(!is_null($keywordsFilter))
  1075. {
  1076. $tck_map = $tproject_mgr->get_keywords_tcases($tproject_id,
  1077. $keywordsFilter->items,$keywordsFilter->type);
  1078. }
  1079. //@TODO: schlundus, can we speed up with NO_EXTERNAL?
  1080. $filters = null;
  1081. $options = array('hideTestCases' => 0, 'viewType' => 'executionTree');
  1082. $testcase_counters = prepareNode($db,$test_spec,$decoding_hash,$map_node_tccount,
  1083. $tck_map,$tplan_tcases,$filters,$options);
  1084. $test_spec['testcase_count'] = $testcase_counters['testcase_count'];
  1085. }
  1086. return($map_node_tccount);
  1087. }
  1088. function create_counters_info(&$node,$useColors)
  1089. {
  1090. $resultsCfg=config_get('results');
  1091. // I will add not_run if not exists
  1092. $keys2display=array('not_run' => 'not_run');
  1093. foreach($resultsCfg['status_label_for_exec_ui'] as $key => $value)
  1094. {
  1095. if( $key != 'not_run')
  1096. {
  1097. $keys2display[$key]=$key;
  1098. }
  1099. }
  1100. $status_verbose=$resultsCfg['status_label'];
  1101. $add_html='';
  1102. foreach($keys2display as $key => $value)
  1103. {
  1104. if( isset($node[$key]) )
  1105. {
  1106. $css_class= $useColors ? (" class=\"light_{$key}\" ") : '';
  1107. $add_html .= "<span {$css_class} " . ' title="' . lang_get($status_verbose[$key]) . '">' .
  1108. $node[$key] . ",</span>";
  1109. }
  1110. }
  1111. $add_html = "(" . rtrim($add_html,",</span>") . "</span>)";
  1112. return $add_html;
  1113. }
  1114. /**
  1115. * VERY IMPORTANT: node must be passed BY REFERENCE
  1116. *
  1117. * @internal Revisions:
  1118. * 20080629 - franciscom - fixed bug missing argument for call to ST
  1119. */
  1120. function extjs_renderExecTreeNodeOnOpen(&$node,$node_type,$tcase_node,$tc_action_enabled,
  1121. $bForPrinting,$useCounters=1,$useColors=null,
  1122. $showTestCaseID=1,$testCasePrefix,$showTestSuiteContents=1)
  1123. {
  1124. static $resultsCfg;
  1125. static $status_descr_code;
  1126. static $status_code_descr;
  1127. static $status_verbose;
  1128. if(!$resultsCfg)
  1129. {
  1130. $resultsCfg=config_get('results');
  1131. $status_descr_code=$resultsCfg['status_code'];
  1132. $status_code_descr=$resultsCfg['code_status'];
  1133. $status_verbose=$resultsCfg['status_label'];
  1134. }
  1135. $name = filterString($node['name']);
  1136. $buildLinkTo = 1;
  1137. $pfn = "ST";
  1138. $testcase_count = isset($node['testcase_count']) ? $node['testcase_count'] : 0;
  1139. $create_counters=0;
  1140. $versionID = 0;
  1141. $node['leaf']=false;
  1142. $testcaseColouring=1;
  1143. $countersColouring=1;
  1144. if( !is_null($useColors) )
  1145. {
  1146. $testcaseColouring=$useColors->testcases;
  1147. $countersColouring=$useColors->counters;
  1148. }
  1149. // $doIt=true;
  1150. // custom Property that will be accessed by EXT-JS using node.attributes
  1151. $node['tlNodeType'] = $node_type;
  1152. switch($node_type)
  1153. {
  1154. case 'testproject':
  1155. $create_counters=1;
  1156. $pfn = $bForPrinting ? 'TPLAN_PTP' : 'SP';
  1157. $label = $name . " (" . $testcase_count . ")";
  1158. break;
  1159. case 'testsuite':
  1160. $create_counters=1;
  1161. $label = $name . " (" . $testcase_count . ")";
  1162. if( $bForPrinting )
  1163. {
  1164. $pfn = 'TPLAN_PTS';
  1165. }
  1166. else
  1167. {
  1168. $pfn = $showTestSuiteContents ? 'STS' : null;
  1169. }
  1170. break;
  1171. case 'testcase':
  1172. $node['leaf'] = true;
  1173. $buildLinkTo = $tc_action_enabled;
  1174. if (!$buildLinkTo)
  1175. {
  1176. $pfn = null;
  1177. }
  1178. //echo "DEBUG - Test Case rendering: \$node['id']:{$node['id']}<br>";
  1179. $status_code = $tcase_node[$node['id']]['exec_status'];
  1180. $status_descr = $status_code_descr[$status_code];
  1181. $status_text = lang_get($status_verbose[$status_descr]);
  1182. $css_class = $testcaseColouring ? (" class=\"light_{$status_descr}\" ") : '';
  1183. $label = "<span {$css_class} " . ' title="' . $status_text . '" alt="' . $status_text . '">';
  1184. if($showTestCaseID)
  1185. {
  1186. $label .= "<b>".htmlspecialchars($testCasePrefix.$node['external_id'])."</b>:";
  1187. }
  1188. $label .= "{$name}</span>";
  1189. $versionID = $node['tcversion_id'];
  1190. break;
  1191. }
  1192. if($create_counters)
  1193. {
  1194. $label = $name ." (" . $testcase_count . ")";
  1195. if($useCounters)
  1196. {
  1197. $add_html = create_counters_info($node,$countersColouring);
  1198. $label .= $add_html;
  1199. }
  1200. }
  1201. $node['text'] = $label;
  1202. $node['position'] = isset($node['node_order']) ? $node['node_order'] : 0;
  1203. $node['href'] = is_null($pfn)? '' : "javascript:{$pfn}({$node['id']},{$versionID})";
  1204. // Remove useless keys
  1205. foreach($status_descr_code as $key => $code)
  1206. {
  1207. if(isset($node[$key]))
  1208. {
  1209. unset($node[$key]);
  1210. }
  1211. }
  1212. $key2del = array('node_type_id','parent_id','node_order','node_table',
  1213. 'tcversion_id','external_id','version','testcase_count');
  1214. foreach($key2del as $key)
  1215. {
  1216. if(isset($node[$key]))
  1217. {
  1218. unset($node[$key]);
  1219. }
  1220. }
  1221. }
  1222. /**
  1223. * Filter out the testcases that don't have the given value
  1224. * in their custom field(s) from the tree.
  1225. * Recursive function.
  1226. *
  1227. * @author Andreas Simon
  1228. * @since 1.9
  1229. *
  1230. * @param array &$tcase_tree reference to test case set/tree to filter
  1231. * @param array &$cf_hash reference to selected custom field information
  1232. * @param resource &$db reference to DB handler object
  1233. * @param int $node_type_testsuite ID of node type for testsuites
  1234. * @param int $node_type_testcase ID of node type for testcase
  1235. *
  1236. * @return array $tcase_tree filtered tree structure
  1237. *
  1238. * @internal revisions:
  1239. *
  1240. * 20100702 - did some changes to logic in here and added a fix for array indexes
  1241. */
  1242. function filter_by_cf_values(&$tcase_tree, &$cf_hash, &$db, $node_type_testsuite, $node_type_testcase) {
  1243. static $tables = null;
  1244. static $debugMsg = null;
  1245. if (!$debugMsg) {
  1246. $tables = tlObject::getDBTables('cfield_design_values');
  1247. $debugMsg = 'Function: ' . __FUNCTION__;
  1248. }
  1249. $node_deleted = false;
  1250. // This code is in parts based on (NOT simply copy/pasted)
  1251. // some filter code used in testplan class.
  1252. // Implemented because we have a tree here,
  1253. // not simple one-dimensional array of testcases like in tplan class.
  1254. foreach ($tcase_tree as $key => $node) {
  1255. if ($node['node_type_id'] == $node_type_testsuite) {
  1256. $delete_suite = false;
  1257. if (isset($node['childNodes']) && is_array($node['childNodes'])) {
  1258. // node is a suite and has children, so recurse one level deeper
  1259. $tcase_tree[$key]['childNodes'] = filter_by_cf_values($tcase_tree[$key]['childNodes'],
  1260. $cf_hash, $db,
  1261. $node_type_testsuite,
  1262. $node_type_testcase);
  1263. // now remove testsuite node if it is empty after coming back from recursion
  1264. if (!count($tcase_tree[$key]['childNodes'])) {
  1265. $delete_suite = true;
  1266. }
  1267. } else {
  1268. // nothing in here, suite was already empty
  1269. $delete_suite = true;
  1270. }
  1271. if ($delete_suite) {
  1272. unset($tcase_tree[$key]);
  1273. $node_deleted = true;
  1274. }
  1275. } else if ($node['node_type_id'] == $node_type_testcase) {
  1276. // node is testcase, check if we need to delete it
  1277. $passed = false;
  1278. foreach ($cf_hash as $cf_id => $cf_value)
  1279. {
  1280. // there will never be more than one record that has a field_id / node_id combination
  1281. $sql = " /* $debugMsg */ SELECT value FROM {$tables['cfield_design_values']} " .
  1282. " WHERE field_id = $cf_id " .
  1283. " AND node_id = {$node['id']} ";
  1284. $result = $db->exec_query($sql);
  1285. $row = $db->fetch_array($result);
  1286. // push both to arrays so we can compare
  1287. $possibleValues = explode ('|', $row['value']);
  1288. $valuesSelected = explode ('|', $cf_value);
  1289. // we want to match any selected item from list and checkboxes.
  1290. if ( count($valuesSelected) ) {
  1291. foreach ($valuesSelected as $vs_id => $vs_value) {
  1292. $found = array_search($vs_value, $possibleValues);
  1293. if (is_int($found)) {
  1294. $passed = true;
  1295. } else {
  1296. $passed = false;
  1297. break;
  1298. }
  1299. }
  1300. }
  1301. // jumping out of foreach here creates an AND search
  1302. // removing this if would cause OR search --> the first found value counts
  1303. if (!$passed) {
  1304. break;
  1305. }
  1306. }
  1307. // now delete node if no match was found
  1308. if (!$passed) {
  1309. unset($tcase_tree[$key]);
  1310. $node_deleted = true;
  1311. }
  1312. }
  1313. }
  1314. // 20100702 - asimon
  1315. // if we deleted a note, the numeric indexes of this array do have missing numbers,
  1316. // which causes problems in later loop constructs in other functions that assume numeric keys
  1317. // in these arrays without missing numbers in between - crashes JS tree!
  1318. // -> so I have to fix the array indexes here starting from 0 without missing a key
  1319. if ($node_deleted) {
  1320. $tcase_tree = array_values($tcase_tree);
  1321. }
  1322. return $tcase_tree;
  1323. }
  1324. /**
  1325. * remove the testcases that don't have the given result in any build
  1326. *
  1327. * @param object &$tplan_mgr reference to test plan manager object
  1328. * @param array &$tcase_set reference to test case set to filter
  1329. * @param integer $tplan_id ID of test plan
  1330. * @param array $filters filters to apply to test case set
  1331. * @return array new tcase_set
  1332. */
  1333. function filter_by_status_for_any_build(&$tplan_mgr,&$tcase_set,$tplan_id,$filters) {
  1334. $key2remove=null;
  1335. $buildSet = $tplan_mgr->get_builds($tplan_id, testplan::ACTIVE_BUILDS);
  1336. $status = 'filter_result_result';
  1337. if( !is_null($buildSet) ) {
  1338. $tcase_build_set = $tplan_mgr->get_status_for_any_build($tplan_id,
  1339. array_keys($buildSet),$filters->{$status});
  1340. if( is_null($tcase_build_set) ) {
  1341. $tcase_set = array();
  1342. } else {
  1343. $key2remove=null;
  1344. foreach($tcase_set as $key_tcase_id => $value) {
  1345. if( !isset($tcase_build_set[$key_tcase_id]) ) {
  1346. $key2remove[]=$key_tcase_id;
  1347. }
  1348. }
  1349. }
  1350. if( !is_null($key2remove) ) {
  1351. foreach($key2remove as $key) {
  1352. unset($tcase_set[$key]);
  1353. }
  1354. }
  1355. }
  1356. return $tcase_set;
  1357. }
  1358. /**
  1359. * filter testcases out that do not have the same execution result in all builds
  1360. *
  1361. * @param object &$tplan_mgr reference to test plan manager object
  1362. * @param array &$tcase_set reference to test case set to filter
  1363. * @param integer $tplan_id ID of test plan
  1364. * @param array $filters filters to apply to test case set
  1365. *
  1366. * @return array new tcase_set
  1367. */
  1368. function filter_by_same_status_for_all_builds(&$tplan_mgr,&$tcase_set,$tplan_id,$filters) {
  1369. $key2remove=null;
  1370. $buildSet = $tplan_mgr->get_builds($tplan_id, testplan::ACTIVE_BUILDS);
  1371. $status = 'filter_result_result';
  1372. if( !is_null($buildSet) ) {
  1373. $tcase_build_set = $tplan_mgr->get_same_status_for_build_set($tplan_id,
  1374. array_keys($buildSet),$filters->{$status});
  1375. if( is_null($tcase_build_set) ) {
  1376. $tcase_set = array();
  1377. } else {
  1378. $key2remove=null;
  1379. foreach($tcase_set as $key_tcase_id => $value) {
  1380. if( !isset($tcase_build_set[$key_tcase_id]) ) {
  1381. $key2remove[]=$key_tcase_id;
  1382. }
  1383. }
  1384. }
  1385. if( !is_null($key2remove) ) {
  1386. foreach($key2remove as $key) {
  1387. unset($tcase_set[$key]);
  1388. }
  1389. }
  1390. }
  1391. return $tcase_set;
  1392. }
  1393. /**
  1394. * filter testcases out which do not have the chosen status in the given build
  1395. * used by filter options 'result on specific build' and 'result on current build'
  1396. *
  1397. * @param object &$tplan_mgr reference to test plan manager object
  1398. * @param array &$tcase_set reference to test case set to filter
  1399. * @param integer $tplan_id ID of test plan
  1400. * @param array $filters filters to apply to test case set
  1401. * @return array new tcase_set
  1402. */
  1403. function filter_by_status_for_build(&$tplan_mgr,&$tcase_set,$tplan_id,$filters) {
  1404. $key2remove=null;
  1405. $build_key = 'filter_result_build';
  1406. $result_key = 'filter_result_result';
  1407. $buildSet = array($filters->$build_key => $tplan_mgr->get_build_by_id($tplan_id,$filters->$build_key));
  1408. if( !is_null($buildSet) ) {
  1409. $tcase_build_set = $tplan_mgr->get_status_for_any_build($tplan_id,
  1410. array_keys($buildSet),$filters->$result_key);
  1411. if( is_null($tcase_build_set) ) {
  1412. $tcase_set = array();
  1413. } else {
  1414. $key2remove=null;
  1415. foreach($tcase_set as $key_tcase_id => $value) {
  1416. if( !isset($tcase_build_set[$key_tcase_id]) ) {
  1417. $key2remove[]=$key_tcase_id;
  1418. }
  1419. }
  1420. }
  1421. if( !is_null($key2remove) ) {
  1422. foreach($key2remove as $key) {
  1423. unset($tcase_set[$key]);
  1424. }
  1425. }
  1426. }
  1427. return $tcase_set;
  1428. }
  1429. /**
  1430. * filter testcases by the result of their latest execution
  1431. *
  1432. * @param object &$db reference to database handler
  1433. * @param object &$tplan_mgr reference to test plan manager object
  1434. * @param array &$tcase_set reference to test case set to filter
  1435. * @param integer $tplan_id ID of test plan
  1436. * @param array $filters filters to apply to test case set
  1437. * @return array new tcase_set
  1438. */
  1439. function filter_by_status_for_last_execution(&$db, &$tplan_mgr,&$tcase_set,$tplan_id,$filters) {
  1440. $tables = tlObject::getDBTables('executions');
  1441. $result_key = 'filter_result_result';
  1442. $in_status = implode("','", $filters->$result_key);
  1443. foreach($tcase_set as $tc_id => $tc_info) {
  1444. // get last execution result for each testcase,
  1445. // if it differs from the result in tcase_set the tcase will be deleted from set
  1446. $sql = " SELECT status FROM {$tables['executions']} E " .
  1447. " WHERE tcversion_id = {$tc_info['tcversion_id']} AND testplan_id = {$tplan_id} " .
  1448. " AND platform_id = {$tc_info['platform_id']} " .
  1449. " AND status = '{$tc_info['exec_status']}' " .
  1450. " AND status IN ('{$in_status}') " .
  1451. " ORDER BY execution_ts DESC limit 1 ";
  1452. $result = null;
  1453. $result = $db->fetchArrayRowsIntoMap($sql,'status');
  1454. if (is_null($result)) {
  1455. unset($tcase_set[$tc_id]);
  1456. }
  1457. }
  1458. return $tcase_set;
  1459. }
  1460. /**
  1461. * filter out those testcases, that do not have at least one build in 'not run' status
  1462. *
  1463. * @param object &$tplan_mgr reference to test plan manager object
  1464. * @param array &$tcase_set reference to test case set to filter
  1465. * @param integer $tplan_id ID of test plan
  1466. * @param array $filters filters to apply to test case set
  1467. * @return array new tcase_set
  1468. */
  1469. function filter_not_run_for_any_build(&$tplan_mgr,&$tcase_set,$tplan_id,$filters) {
  1470. $key2remove=null;
  1471. $buildSet = $tplan_mgr->get_builds($tplan_id);
  1472. if( !is_null($buildSet) ) {
  1473. $tcase_build_set = $tplan_mgr->get_not_run_for_any_build($tplan_id, array_keys($buildSet));
  1474. if( is_null($tcase_build_set) ) {
  1475. $tcase_set = array();
  1476. } else {
  1477. $key2remove=null;
  1478. foreach($tcase_set as $key_tcase_id => $value) {
  1479. if( !isset($tcase_build_set[$key_tcase_id]) ) {
  1480. $key2remove[]=$key_tcase_id;
  1481. }
  1482. }
  1483. }
  1484. if( !is_null($key2remove) ) {
  1485. foreach($key2remove as $key) {
  1486. unset($tcase_set[$key]);
  1487. }
  1488. }
  1489. }
  1490. return $tcase_set;
  1491. }
  1492. /** VERY IMPORTANT: node must be passed BY REFERENCE */
  1493. function extjs_renderTestSpecTreeNodeOnOpen(&$node,$node_type,$tc_action_enabled,
  1494. $bForPrinting,$showTestCaseID,$testCasePrefix)
  1495. {
  1496. $name = filterString($node['name']);
  1497. $buildLinkTo = 1;
  1498. $pfn = "ET";
  1499. $testcase_count = isset($node['testcase_count']) ? $node['testcase_count'] : 0;
  1500. switch($node_type)
  1501. {
  1502. case 'testproject':
  1503. $pfn = $bForPrinting ? 'TPROJECT_PTP' : 'EP';
  1504. $label = $name . " (" . $testcase_count . ")";
  1505. break;
  1506. case 'testsuite':
  1507. $pfn = $bForPrinting ? 'TPROJECT_PTS' : 'ETS';
  1508. $label = $name . " (" . $testcase_count . ")";
  1509. break;
  1510. case 'testcase':
  1511. $buildLinkTo = $tc_action_enabled;
  1512. if (!$buildLinkTo)
  1513. {
  1514. $pfn = "void";
  1515. }
  1516. $label = "";
  1517. if($showTestCaseID)
  1518. {
  1519. $label .= "<b>{$testCasePrefix}{$node['external_id']}</b>:";
  1520. }
  1521. $label .= $name;
  1522. break;
  1523. } // switch
  1524. $node['text']=$label;
  1525. $node['position']=isset($node['node_order']) ? $node['node_order'] : 0;
  1526. $node['href']=is_null($pfn)? '' : "javascript:{$pfn}({$node['id']})";
  1527. // Remove useless keys
  1528. $resultsCfg=config_get('results');
  1529. $status_descr_code=$resultsCfg['status_code'];
  1530. foreach($status_descr_code as $key => $code)
  1531. {
  1532. if(isset($node[$key]))
  1533. {
  1534. unset($node[$key]);
  1535. }
  1536. }
  1537. $key2del=array('node_type_id','parent_id','node_order','node_table',
  1538. 'tcversion_id','external_id','version','testcase_count');
  1539. foreach($key2del as $key)
  1540. {
  1541. if(isset($node[$key]))
  1542. {
  1543. unset($node[$key]);
  1544. }
  1545. }
  1546. }
  1547. /**
  1548. * generate array with Keywords for a filter
  1549. *
  1550. */
  1551. function buildKeywordsFilter($keywordsId,&$guiObj)
  1552. {
  1553. $keywordsFilter = null;
  1554. if(!is_null($keywordsId))
  1555. {
  1556. $items = array_flip((array)$keywordsId);
  1557. if(!isset($items[0]))
  1558. {
  1559. $keywordsFilter = new stdClass();
  1560. $keywordsFilter->items = $keywordsId;
  1561. $keywordsFilter->type = isset($guiObj->keywordsFilterTypes) ? $guiObj->keywordsFilterTypes->selected: 'OR';
  1562. }
  1563. }
  1564. return $keywordsFilter;
  1565. }
  1566. /**
  1567. * generate object with test case execution type for a filter
  1568. *
  1569. */
  1570. function buildExecTypeFilter($execTypeSet)
  1571. {
  1572. $itemsFilter = null;
  1573. if(!is_null($execTypeSet))
  1574. {
  1575. $items = array_flip((array)$execTypeSet);
  1576. if(!isset($items[0]))
  1577. {
  1578. $itemsFilter = new stdClass();
  1579. $itemsFilter->items = $execTypeSet;
  1580. }
  1581. }
  1582. return $itemsFilter;
  1583. }
  1584. /**
  1585. * generate object with test case importance for a filter
  1586. *
  1587. */
  1588. function buildImportanceFilter($importance)
  1589. {
  1590. $itemsFilter = null;
  1591. if(!is_null($importance))
  1592. {
  1593. $items = array_flip((array)$importance);
  1594. if(!isset($items[0]))
  1595. {
  1596. $itemsFilter = new stdClass();
  1597. $itemsFilter->items = $importance;
  1598. }
  1599. }
  1600. return $itemsFilter;
  1601. }
  1602. /**
  1603. * Generate the necessary data object for the filtered requirement specification tree.
  1604. *
  1605. * @author Andreas Simon
  1606. * @param Database $db reference to database handler object
  1607. * @param testproject $testproject_mgr reference to testproject manager object
  1608. * @param int $testproject_id ID of the project for which the tree shall be generated
  1609. * @param string $testproject_name Name of the test project
  1610. * @param array $filters Filter settings which shall be applied to the tree, possible values are:
  1611. * 'filter_doc_id',
  1612. * 'filter_title',
  1613. * 'filter_status',
  1614. * 'filter_type',
  1615. * 'filter_spec_type',
  1616. * 'filter_coverage',
  1617. * 'filter_relation',
  1618. * 'filter_tc_id',
  1619. * 'filter_custom_fields'
  1620. * @param array $options Further options which shall be applied on generating the tree
  1621. * @return stdClass $treeMenu object with which ExtJS can generate the graphical tree
  1622. */
  1623. function generate_reqspec_tree(&$db, &$testproject_mgr, $testproject_id, $testproject_name,
  1624. $filters = null, $options = null) {
  1625. $tables = tlObjectWithDB::getDBTables(array('requirements', 'req_versions',
  1626. 'req_specs', 'req_relations',
  1627. 'req_coverage', 'nodes_hierarchy'));
  1628. $tree_manager = &$testproject_mgr->tree_manager;
  1629. $glue_char = config_get('testcase_cfg')->glue_character;
  1630. $tcase_prefix=$testproject_mgr->getTestCasePrefix($testproject_id) . $glue_char;
  1631. $req_node_type = $tree_manager->node_descr_id['testcase'];
  1632. $req_spec_node_type = $tree_manager->node_descr_id['testsuite'];
  1633. $map_nodetype_id = $tree_manager->get_available_node_types();
  1634. $map_id_nodetype = array_flip($map_nodetype_id);
  1635. $my = array();
  1636. $my['options'] = array('for_printing' => 0,
  1637. 'exclude_branches' => null,
  1638. 'recursive' => true,
  1639. 'order_cfg' => array('type' => 'spec_order'));
  1640. $my['filters'] = array('exclude_node_types' => array('testplan'=>'exclude me',
  1641. 'testsuite'=>'exclude me',
  1642. 'testcase'=>'exclude me'),
  1643. 'exclude_children_of' => array('testcase'=>'exclude my children',
  1644. 'requirement'=>'exclude my children',
  1645. 'testsuite'=> 'exclude my children'),
  1646. 'filter_doc_id' => null,
  1647. 'filter_title' => null,
  1648. 'filter_status' => null,
  1649. 'filter_type' => null,
  1650. 'filter_spec_type' => null,
  1651. 'filter_coverage' => null,
  1652. 'filter_relation' => null,
  1653. 'filter_tc_id' => null,
  1654. 'filter_custom_fields' => null);
  1655. // merge with given parameters
  1656. $my['options'] = array_merge($my['options'], (array) $options);
  1657. $my['filters'] = array_merge($my['filters'], (array) $filters);
  1658. $req_spec = $tree_manager->get_subtree($testproject_id, $my['filters'], $my['options']);
  1659. $req_spec['name'] = $testproject_name;
  1660. $req_spec['id'] = $testproject_id;
  1661. $req_spec['node_type_id'] = $map_nodetype_id['testproject'];
  1662. $filtered_map = get_filtered_req_map($db, $testproject_id, $testproject_mgr,
  1663. $my['filters'], $my['options']);
  1664. $level = 1;
  1665. $req_spec = prepare_reqspec_treenode($level, $req_spec, $filtered_map, $map_id_nodetype,
  1666. $map_nodetype_id, $my['filters'], $my['options']);
  1667. $menustring = null;
  1668. $treeMenu = new stdClass();
  1669. $treeMenu->rootnode = new stdClass();
  1670. $treeMenu->rootnode->name = $req_spec['name'];
  1671. $treeMenu->rootnode->id = $req_spec['id'];
  1672. $treeMenu->rootnode->leaf = isset($req_spec['leaf']) ? $req_spec['leaf'] : false;
  1673. $treeMenu->rootnode->text = $req_spec['name'];
  1674. $treeMenu->rootnode->position = $req_spec['position'];
  1675. $treeMenu->rootnode->href = $req_spec['href'];
  1676. // replace key ('childNodes') to 'children'
  1677. if (isset($req_spec['childNodes']))
  1678. {
  1679. $menustring = str_ireplace('childNodes', 'children',
  1680. json_encode($req_spec['childNodes']));
  1681. }
  1682. if (!is_null($menustring))
  1683. {
  1684. // delete null elements for Ext JS
  1685. $menustring = str_ireplace(array(':null',',null','null,','null'),
  1686. array(':[]','','',''),
  1687. $menustring);
  1688. }
  1689. $treeMenu->menustring = $menustring;
  1690. return $treeMenu;
  1691. }
  1692. /**
  1693. * Generate a filtered map with all fitting requirements in it.
  1694. *
  1695. * @author Andreas Simon
  1696. * @param Database $db reference to database handler object
  1697. * @param int $testproject_id ID of the project for which the tree shall be generated
  1698. * @param testproject $testproject_mgr reference to testproject manager object
  1699. * @param array $filters Filter settings which shall be applied to the tree
  1700. * @param array $options Further options which shall be applied on generating the tree
  1701. * @return array $filtered_map map with all fitting requirements
  1702. */
  1703. function get_filtered_req_map(&$db, $testproject_id, &$testproject_mgr, $filters, $options) {
  1704. $filtered_map = null;
  1705. $tables = tlObjectWithDB::getDBTables(array('nodes_hierarchy', 'requirements', 'req_specs',
  1706. 'req_relations', 'req_versions', 'req_coverage',
  1707. 'tcversions', 'cfield_design_values'));
  1708. $sql = " SELECT R.id, R.req_doc_id, NH_R.name AS title, R.srs_id, " .
  1709. " RS.doc_id AS req_spec_doc_id, NH_RS.name AS req_spec_title, " .
  1710. " RV.version, RV.id AS version_id, NH_R.node_order, " .
  1711. " RV.expected_coverage, RV.status, RV.type, RV.active, RV.is_open " .
  1712. " FROM {$tables['requirements']} R " .
  1713. " JOIN {$tables['nodes_hierarchy']} NH_R ON NH_R.id = R.id " .
  1714. " JOIN {$tables['nodes_hierarchy']} NH_RV ON NH_RV.parent_id = NH_R.id " .
  1715. " JOIN {$tables['req_versions']} RV ON RV.id = NH_RV.id " .
  1716. " JOIN {$tables['req_specs']} RS ON RS.id = R.srs_id " .
  1717. " JOIN {$tables['nodes_hierarchy']} NH_RS ON NH_RS.id = RS.id ";
  1718. if (isset($filters['filter_relation'])) {
  1719. $sql .= " JOIN {$tables['req_relations']} RR " .
  1720. " ON (RR.destination_id = R.id OR RR.source_id = R.id) ";
  1721. }
  1722. if (isset($filters['filter_tc_id'])) {
  1723. $tc_cfg = config_get('testcase_cfg');
  1724. $tc_prefix = $testproject_mgr->getTestCasePrefix($testproject_id);
  1725. $tc_prefix .= $tc_cfg->glue_character;
  1726. $tc_ext_id = $db->prepare_int(str_replace($tc_prefix, '', $filters['filter_tc_id']));
  1727. $sql .= " JOIN {$tables['req_coverage']} RC ON RC.req_id = R.id " .
  1728. " JOIN {$tables['nodes_hierarchy']} NH_T ON NH_T.id = RC.testcase_id " .
  1729. " JOIN {$tables['nodes_hierarchy']} NH_TV on NH_TV.parent_id = NH_T.id " .
  1730. " JOIN {$tables['tcversions']} TV ON TV.id = NH_TV.id " .
  1731. " AND TV.tc_external_id = {$tc_ext_id} ";
  1732. }
  1733. if (isset($filters['filter_custom_fields'])) {
  1734. $suffix = 1;
  1735. foreach ($filters['filter_custom_fields'] as $cf_id => $cf_value) {
  1736. $sql .= " JOIN {$tables['cfield_design_values']} CF{$suffix} " .
  1737. " ON CF{$suffix}.node_id = R.id ";
  1738. " AND CF{$suffix}.field_id = {$cf_id} ";
  1739. // single value or array?
  1740. if (is_array($cf_value)) {
  1741. $sql .= " AND ( ";
  1742. $count = 1;
  1743. foreach ($cf_value as $value) {
  1744. if ($count > 1) {
  1745. $sql .= " OR ";
  1746. }
  1747. $sql .= " CF{$suffix}.value LIKE '%{$value}%' ";
  1748. $count++;
  1749. }
  1750. $sql .= " ) ";
  1751. } else {
  1752. $sql .= " AND CF{$suffix}.value LIKE '%{$cf_value}%' ";
  1753. }
  1754. $suffix ++;
  1755. }
  1756. }
  1757. $sql .= " WHERE RS.testproject_id = {$testproject_id} ";
  1758. if (isset($filters['filter_doc_id'])) {
  1759. $doc_id = $db->prepare_string($filters['filter_doc_id']);
  1760. $sql .= " AND R.req_doc_id LIKE '%{$doc_id}%' OR RS.doc_id LIKE '%{$doc_id}%' ";
  1761. }
  1762. if (isset($filters['filter_title'])) {
  1763. $title = $db->prepare_string($filters['filter_title']);
  1764. $sql .= " AND NH_R.name LIKE '%{$title}%' ";
  1765. }
  1766. if (isset($filters['filter_coverage'])) {
  1767. $coverage = $db->prepare_int($filters['filter_coverage']);
  1768. $sql .= " AND expected_coverage = {$coverage} ";
  1769. }
  1770. if (isset($filters['filter_status'])) {
  1771. $statuses = (array) $filters['filter_status'];
  1772. foreach ($statuses as $key => $status) {
  1773. $statuses[$key] = "'" . $db->prepare_string($status) . "'";
  1774. }
  1775. $statuses = implode(",", $statuses);
  1776. $sql .= " AND RV.status IN ({$statuses}) ";
  1777. }
  1778. if (isset($filters['filter_type'])) {
  1779. $types = (array) $filters['filter_type'];
  1780. // BUGID 3671
  1781. foreach ($types as $key => $type) {
  1782. $types[$key] = $db->prepare_string($type);
  1783. }
  1784. $types = implode("','", $types);
  1785. $sql .= " AND RV.type IN ('{$types}') ";
  1786. }
  1787. if (isset($filters['filter_spec_type'])) {
  1788. $spec_types = (array) $filters['filter_spec_type'];
  1789. // BUGID 3671
  1790. foreach ($spec_types as $key => $type) {
  1791. $spec_types[$key] = $db->prepare_string($type);
  1792. }
  1793. $spec_types = implode("','", $spec_types);
  1794. $sql .= " AND RS.type IN ('{$spec_types}') ";
  1795. }
  1796. if (isset($filters['filter_relation'])) {
  1797. $sql .= " AND ( ";
  1798. $count = 1;
  1799. foreach ($filters['filter_relation'] as $key => $rel_filter) {
  1800. $relation_info = explode('_', $rel_filter);
  1801. $relation_type = $db->prepare_int($relation_info[0]);
  1802. $relation_side = isset($relation_info[1]) ? $relation_info[1] : null;
  1803. $sql .= ($count == 1) ? " ( " : " OR ( ";
  1804. if ($relation_side == "destination") {
  1805. $sql .= " RR.destination_id = R.id ";
  1806. } else if ($relation_side == "source") {
  1807. $sql .= " RR.source_id = R.id ";
  1808. } else {
  1809. $sql .= " (RR.destination_id = R.id OR RR.source_id = R.id) ";
  1810. }
  1811. $sql .= " AND RR.relation_type = {$relation_type} ) ";
  1812. $count++;
  1813. }
  1814. $sql .= " ) ";
  1815. }
  1816. $sql .= " ORDER BY RV.version DESC ";
  1817. $filtered_map = $db->fetchRowsIntoMap($sql, 'id');
  1818. return $filtered_map;
  1819. }
  1820. /**
  1821. * Prepares nodes for the filtered requirement tree.
  1822. * Filters out those nodes which are not in the given map and counts the remaining subnodes.
  1823. * @author Andreas Simn
  1824. * @param int $level gets increased by one for each sublevel in recursion
  1825. * @param array $node the tree structure to traverse
  1826. * @param array $filtered_map a map of filtered requirements, req that are not in this map will be deleted
  1827. * @param array $map_id_nodetype array with node type IDs as keys, node type descriptions as values
  1828. * @param array $map_nodetype_id array with node type descriptions as keys, node type IDs as values
  1829. * @param array $filters
  1830. * @param array $options
  1831. * @return array tree structure after filtering out unneeded nodes
  1832. */
  1833. function prepare_reqspec_treenode($level, &$node, &$filtered_map, &$map_id_nodetype,
  1834. &$map_nodetype_id, &$filters, &$options) {
  1835. $child_req_count = 0;
  1836. if (isset($node['childNodes']) && is_array($node['childNodes'])) {
  1837. // node has childs, must be a specification (or testproject)
  1838. foreach ($node['childNodes'] as $key => $childnode) {
  1839. $current_childnode = &$node['childNodes'][$key];
  1840. $current_childnode = prepare_reqspec_treenode($level + 1, $current_childnode,
  1841. $filtered_map, $map_id_nodetype,
  1842. $map_nodetype_id,
  1843. $filters, $options);
  1844. // now count childnodes that have not been deleted and are requirements
  1845. if (!is_null($current_childnode)) {
  1846. switch ($current_childnode['node_type_id']) {
  1847. case $map_nodetype_id['requirement']:
  1848. $child_req_count ++;
  1849. break;
  1850. case $map_nodetype_id['requirement_spec']:
  1851. $child_req_count += $current_childnode['child_req_count'];
  1852. break;
  1853. }
  1854. }
  1855. }
  1856. }
  1857. $node_type = $map_id_nodetype[$node['node_type_id']];
  1858. $delete_node = false;
  1859. switch ($node_type) {
  1860. case 'testproject':
  1861. break;
  1862. case 'requirement_spec':
  1863. // add requirement count
  1864. $node['child_req_count'] = $child_req_count;
  1865. // delete empty specs
  1866. if (!$child_req_count) {
  1867. $delete_node = true;
  1868. }
  1869. break;
  1870. case 'requirement':
  1871. // delete node from tree if it is not in $filtered_map
  1872. if (is_null($filtered_map) || !array_key_exists($node['id'], $filtered_map)) {
  1873. $delete_node = true;
  1874. }
  1875. break;
  1876. }
  1877. if ($delete_node) {
  1878. unset($node);
  1879. $node = null;
  1880. } else {
  1881. $node = render_reqspec_treenode($node, $filtered_map, $map_id_nodetype);
  1882. }
  1883. return $node;
  1884. }
  1885. /**
  1886. * Prepares nodes in the filtered requirement tree for displaying with ExtJS.
  1887. * @author Andreas Simn
  1888. * @param array $node the object to prepare
  1889. * @param array $filtered_map a map of filtered requirements, req that are not in this map will be deleted
  1890. * @param array $map_id_nodetype array with node type IDs as keys, node type descriptions as values
  1891. * @return array tree object with all needed data for ExtJS tree
  1892. */
  1893. function render_reqspec_treenode(&$node, &$filtered_map, &$map_id_nodetype) {
  1894. static $js_functions;
  1895. static $forbidden_parents;
  1896. if (!$js_functions) {
  1897. $js_functions = array('testproject' => 'TPROJECT_REQ_SPEC_MGMT',
  1898. 'requirement_spec' =>'REQ_SPEC_MGMT',
  1899. 'requirement' => 'REQ_MGMT');
  1900. $req_cfg = config_get('req_cfg');
  1901. $forbidden_parents['testproject'] = 'none';
  1902. $forbidden_parents['requirement'] = 'testproject';
  1903. $forbidden_parents['requirement_spec'] = 'requirement_spec';
  1904. if($req_cfg->child_requirements_mgmt)
  1905. {
  1906. $forbidden_parents['requirement_spec'] = 'none';
  1907. }
  1908. }
  1909. $node_type = $map_id_nodetype[$node['node_type_id']];
  1910. $node_id = $node['id'];
  1911. $node['href'] = "javascript:{$js_functions[$node_type]}({$node_id});";
  1912. $node['text'] = htmlspecialchars($node['name']);
  1913. $node['leaf'] = false; // will be set to true later for requirement nodes
  1914. $node['position'] = isset($node['node_order']) ? $node['node_order'] : 0;
  1915. $node['cls'] = 'folder';
  1916. $node['testlink_node_type'] = $node_type;
  1917. $node['forbidden_parent'] = $forbidden_parents[$node_type];
  1918. switch ($node_type) {
  1919. case 'testproject':
  1920. break;
  1921. case 'requirement_spec':
  1922. // get doc id from filtered array, it's already stored in there
  1923. $doc_id = '';
  1924. foreach($node['childNodes'] as $child) {
  1925. if (!is_null($child)) {
  1926. $child_id = $child['id'];
  1927. $doc_id = htmlspecialchars($filtered_map[$child_id]['req_spec_doc_id']);
  1928. break; // only need to get one child for this
  1929. }
  1930. }
  1931. $count = $node['child_req_count'];
  1932. $node['text'] = "{$doc_id}:{$node['text']} ({$count})";
  1933. break;
  1934. case 'requirement':
  1935. $node['leaf'] = true;
  1936. $doc_id = htmlspecialchars($filtered_map[$node_id]['req_doc_id']);
  1937. $node['text'] = "{$doc_id}:{$node['text']}";
  1938. break;
  1939. }
  1940. return $node;
  1941. }
  1942. ?>