PageRenderTime 94ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/functions/testcase.class.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 4679 lines | 2534 code | 517 blank | 1628 comment | 280 complexity | 85cec307c4494274c6a143574811ca1f 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. * @package TestLink
  7. * @author Francisco Mancardi (francisco.mancardi@gmail.com)
  8. * @copyright 2005-2009, TestLink community
  9. * @version CVS: $Id: testcase.class.php,v 1.295 2010/08/31 20:07:11 franciscom Exp $
  10. * @link http://www.teamst.org/index.php
  11. *
  12. * @internal Revisions:
  13. *
  14. * 20100831 - franciscom - BUGID 3729 - get_by_name()
  15. * 20100825 - franciscom - BUGID 3702 - _blind_delete() issue
  16. * 20100821 - franciscom - BUGID 3695 - Test Case Steps - Export/Import - missing attribute execution type
  17. * create_step() - fixed issue when execution_type was NULL.
  18. * new method - update_tcversion_steps() needed for BUGID 3634
  19. *
  20. * 20100814 - franciscom - getInternalID() - removed unused code and minor code rearrangement
  21. * changes in returned value when internal ID can not be found.
  22. * 20100813 - asimon - deactivated last slash on full path in get_assigned_to_user()
  23. * to remove it from test suite name in "tc assigned to user" tables
  24. * 20100802 - asimon - BUGID 3647 - filtering by build id in get_assigned_to_user()
  25. * 20100731 - asimon - more modifications to get_assigned_to_user()
  26. * 20100722 - asimon - BUGID 3406 - modified statement to get build name in get_assigned_to_user()
  27. * 20100714 - Julian - BUGID 3575 - get_assigned_to_user() added priority in output set
  28. * 20100712 - asimon - inserted missing semicolon after break in get_assigned_to_user()
  29. * 20100711 - franciscom - BUGID 3575 - get_assigned_to_user() added $filters as optional arg
  30. * 20100708 - franciscom - BUGID 3575 - get_assigned_to_user() add plaftorm in output set
  31. * 20100706 - franciscom - BUGID 3573 - _blind_delete() with alias has problems with MySQL
  32. * 20100521 - franciscom - BUGID 3481 - copy_tcversion() - preconditions are not copied
  33. * 20100516 - franciscom - BUGID 3465: Delete Test Project - User Execution Assignment is not deleted
  34. * 20100514 - franciscom - get_by_id() interface changes and improvements
  35. * 20100503 - franciscom - create_tcase_only() - BUGID 3374
  36. * 20100502 - franciscom - show() fixed error due to non existent variable $info
  37. * 20100417 - franciscom - new method - filter_tcversions()
  38. * get_last_active_version() - changes on output data
  39. * 20100411 - franciscom - BUGID 3387 - changes in show()
  40. * 20100411 - franciscom - new methods: get_last_active_version(),filter_tcversions_by_exec_type()
  41. * 20100409 - franciscom - BUGID 3367: Error after trying to copy a test case that the name is in the size limit.
  42. * 20100330 - eloff - BUGID 3329 - fixes test plan usage with platforms
  43. * 20100323 - asimon - fixed BUGID 3316 in show()
  44. * 20100317 - franciscom - new method get_by_external()
  45. * 20100315 - amitkhullar - Added options for Requirements and CFields for Export.
  46. * 20100309 - franciscom - get_by_id() - improvements on control to apply when LATEST_VERSION is requested.
  47. * 20100309 - franciscom - get_exec_status() - interface changes
  48. * get_linked_versions() - interface changes
  49. * BUGID 0003253
  50. *
  51. * 20100301 - franciscom - changes on show() to solve
  52. * BUGID 3181: From test case specification, after adding the test case
  53. * to test a plan with platforms, platforms are not displayed
  54. *
  55. * 20100210 - franciscom - keywords XML export refactored
  56. * 20100204 - franciscom - copyKeywordsTo(),copyReqAssignmentTo() - interface changes
  57. * 20100201 - franciscom - getExternalID(), refactored to improve performance when used on loops
  58. * 20100124 - franciscom - BUGID 3090 - problems when trying to delete a test case that has 0 steps.
  59. * 20100111 - franciscom - get_version_exec_assignment() - refactoring due to platforms feature.
  60. * get_linked_versions() - refactoring due to platforms feature.
  61. * 20100107 - franciscom - Multiple Test Case Steps Feature
  62. * Affected methods: delete(), _blind_delete()
  63. *
  64. * 20100106 - franciscom - Multiple Test Case Steps Feature
  65. * Affected methods: get_by_id(), create(), update()
  66. * get_last_version_info(), get_linked_versions(), copy_to()
  67. * copy_tcversion(),exportTestCaseDataToXML()
  68. *
  69. * 20100105 - franciscom - fixed missing copy of preconditions on copy_tcversion()
  70. * exportTestCaseDataToXML() - added execution_type, importance
  71. *
  72. * 20100104 - franciscom - create_new_version() - interface changes
  73. * new method get_basic_info()
  74. * fixed bug in show() regarding $gui->can_do->add2tplan
  75. * get_last_version_info() - interface changes
  76. * 20100103 - franciscom - getPrefix() - interface changes & refactoring
  77. * new methods - buildDirectWebLink(), getExternalID()
  78. * 20091229 - eloff - BUGID 3021 - getInternalID() - fixed error when tc prefix contains glue character
  79. * 20091220 - franciscom - copy_attachments() refactoring
  80. * 20091217 - franciscom - getDuplicatesByName() - new argument added
  81. * 20091215 - franciscom - getPrefix() - changed in return type, to avoid in some situations
  82. * a double call.
  83. * 20091207 - franciscom - get_last_execution() - internal bug
  84. * 20091127 - franciscom - getByPathName() new method
  85. * 20091118 - franciscom - get_last_execution() - still working ond fixing bug when using self::ALL_VERSIONS
  86. * 20091113 - franciscom - get_last_execution() - fixed bug when using self::ALL_VERSIONS
  87. * 20091003 - franciscom - show() changes in template get logic
  88. * 20090927 - franciscom - new methods: getPathLayered(),getPathTopSuite()
  89. * 20090922 - franciscom - get_last_execution() - used COALESCE() to return code
  90. * also code for NOT RUN status.
  91. * 20090831 - franciscom - added management of new field: preconditions
  92. * create(),update(),exportTestCaseDataToXML()
  93. *
  94. * 20090815 - franciscom - get_executions() - added platform related info
  95. * interface changes.
  96. * get_last_execution() - added platform related info
  97. *
  98. * 20090720 - franciscom - found bug in get_linked_cfields_at_execution()
  99. * when calling cfield_mgr class method
  100. *
  101. * 20090718 - franciscom - new method buildCFLocationMap();
  102. * 20090716 - franciscom - get_last_execution() - BUGID 2692 - interface changes.
  103. * 20090713 - franciscom - solved bug on get_executions() (bad SQL statement).
  104. * 20090530 - franciscom - html_table_of_custom_field_inputs() changes in interface
  105. * 20090526 - franciscom - html_table_of_custom_field_values() - added scope 'testplan_design'
  106. * 20090521 - franciscom - get_by_id() added version_number argument
  107. * 20090419 - franciscom - BUGID 2364 - show() changes on edit enabled logic
  108. * 20090414 - franciscom - BUGID 2378
  109. * 20090401 - franciscom - BUGID 2316 - changes to copy_to()
  110. * 20090308 - franciscom - BUGID 2204 - create() fixed return of new version number
  111. * 20090220 - franciscom - BUGID 2129
  112. * 20090106 - franciscom - BUGID - exportTestCaseDataToXML() - added export of custom fields values
  113. * 20081103 - franciscom - new method setKeywords() - added by schlundus
  114. * removed useless code from getTestProjectFromTestCase()
  115. * 20081015 - franciscom - delete() - improve controls to avoid bug if no children
  116. * 20080812 - franciscom - BUGID 1650 (REQ)
  117. * html_table_of_custom_field_inputs() interface changes
  118. * to manage custom fields with scope='testplan_design'
  119. *
  120. * 20080602 - franciscom - get_linked_versions() - internal changes due to BUG1504
  121. * get_exec_status() - interface and internal changes due to BUG1504
  122. *
  123. * 20080126 - franciscom - BUGID 1313
  124. */
  125. /** related functionality */
  126. require_once( dirname(__FILE__) . '/requirement_mgr.class.php' );
  127. require_once( dirname(__FILE__) . '/assignment_mgr.class.php' );
  128. require_once( dirname(__FILE__) . '/attachments.inc.php' );
  129. require_once( dirname(__FILE__) . '/users.inc.php' );
  130. /** list of supported format for Test case import/export */
  131. $g_tcFormatStrings = array ("XML" => lang_get('the_format_tc_xml_import'));
  132. /**
  133. * class for Test case CRUD
  134. * @package TestLink
  135. */
  136. class testcase extends tlObjectWithAttachments
  137. {
  138. const AUTOMATIC_ID=0;
  139. const DEFAULT_ORDER=0;
  140. const ALL_VERSIONS=0;
  141. const LATEST_VERSION=-1;
  142. const AUDIT_OFF=0;
  143. const AUDIT_ON=1;
  144. const CHECK_DUPLICATE_NAME=1;
  145. const DONT_CHECK_DUPLICATE_NAME=0;
  146. const ENABLED=1;
  147. const ALL_TESTPLANS=null;
  148. const ANY_BUILD=null;
  149. const GET_NO_EXEC=1;
  150. const ANY_PLATFORM=null;
  151. /** @var database handler */
  152. var $db;
  153. var $tree_manager;
  154. var $tproject_mgr;
  155. var $node_types_descr_id;
  156. var $node_types_id_descr;
  157. var $my_node_type;
  158. var $assignment_mgr;
  159. var $assignment_types;
  160. var $assignment_status;
  161. var $cfield_mgr;
  162. var $import_file_types = array("XML" => "XML", "XLS" => "XLS" );
  163. var $export_file_types = array("XML" => "XML");
  164. var $execution_types = array();
  165. /**
  166. * testplan class constructor
  167. *
  168. * @param resource &$db reference to database handler
  169. */
  170. function __construct(&$db)
  171. {
  172. $this->db = &$db;
  173. $this->tproject_mgr = new testproject($this->db);
  174. $this->tree_manager = &$this->tproject_mgr->tree_manager;
  175. $this->node_types_descr_id=$this->tree_manager->get_available_node_types();
  176. $this->node_types_id_descr=array_flip($this->node_types_descr_id);
  177. $this->my_node_type=$this->node_types_descr_id['testcase'];
  178. $this->assignment_mgr=New assignment_mgr($this->db);
  179. $this->assignment_types=$this->assignment_mgr->get_available_types();
  180. $this->assignment_status=$this->assignment_mgr->get_available_status();
  181. $this->cfield_mgr = new cfield_mgr($this->db);
  182. $this->execution_types = array(TESTCASE_EXECUTION_TYPE_MANUAL => lang_get('manual'),
  183. TESTCASE_EXECUTION_TYPE_AUTO => lang_get('automated'));
  184. // ATTENTION:
  185. // second argument is used to set $this->attachmentTableName,property that this calls
  186. // get from his parent
  187. parent::__construct($this->db,"nodes_hierarchy");
  188. }
  189. /*
  190. function: get_export_file_types
  191. getter
  192. args: -
  193. returns: map
  194. key: export file type code
  195. value: export file type verbose description
  196. */
  197. function get_export_file_types()
  198. {
  199. return $this->export_file_types;
  200. }
  201. /*
  202. function: get_impor_file_types
  203. getter
  204. args: -
  205. returns: map
  206. key: import file type code
  207. value: import file type verbose description
  208. */
  209. function get_import_file_types()
  210. {
  211. return $this->import_file_types;
  212. }
  213. /*
  214. function: get_execution_types
  215. getter
  216. args: -
  217. returns: map
  218. key: execution type code
  219. value: execution type verbose description
  220. */
  221. function get_execution_types()
  222. {
  223. return $this->execution_types;
  224. }
  225. /**
  226. * create a test case
  227. */
  228. function create($parent_id,$name,$summary,$preconditions,$steps,$author_id,
  229. $keywords_id='',$tc_order=self::DEFAULT_ORDER,$id=self::AUTOMATIC_ID,
  230. $execution_type=TESTCASE_EXECUTION_TYPE_MANUAL,
  231. $importance=2,$options=null)
  232. {
  233. $status_ok = 1;
  234. $my['options'] = array( 'check_duplicate_name' => self::DONT_CHECK_DUPLICATE_NAME,
  235. 'action_on_duplicate_name' => 'generate_new');
  236. $my['options'] = array_merge($my['options'], (array)$options);
  237. $ret = $this->create_tcase_only($parent_id,$name,$tc_order,$id,$my['options']);
  238. if($ret["status_ok"])
  239. {
  240. if(trim($keywords_id) != "")
  241. {
  242. $a_keywords = explode(",",$keywords_id);
  243. $this->addKeywords($ret['id'],$a_keywords);
  244. }
  245. $version_number = 1;
  246. if(isset($ret['version_number']) && $ret['version_number'] < 0)
  247. {
  248. // We are in the special situation we are only creating a new version,
  249. // useful when importing test cases. Need to get last version number.
  250. // I do not use create_new_version() because it does a copy ot last version
  251. // and do not allow to set new values in different fields while doing this operation.
  252. $last_version_info = $this->get_last_version_info($ret['id'],array('output' => 'minimun'));
  253. $version_number = $last_version_info['version']+1;
  254. $ret['msg'] = sprintf($ret['msg'],$version_number);
  255. // BUGID 2204
  256. $ret['version_number']=$version_number;
  257. }
  258. // Multiple Test Case Steps Feature
  259. $op = $this->create_tcversion($ret['id'],$ret['external_id'],$version_number,$summary,
  260. $preconditions,$steps,$author_id,$execution_type,$importance);
  261. $ret['msg'] = $op['status_ok'] ? $ret['msg'] : $op['msg'];
  262. }
  263. return $ret;
  264. }
  265. /*
  266. 20061008 - franciscom
  267. added [$check_duplicate_name]
  268. [$action_on_duplicate_name]
  269. 20060725 - franciscom - interface changes
  270. [$order]
  271. [$id]
  272. 0 -> the id will be assigned by dbms
  273. x -> this will be the id
  274. Warning: no check is done before insert => can got error.
  275. return:
  276. $ret['id']
  277. $ret['external_id']
  278. $ret['status_ok']
  279. $ret['msg'] = 'ok';
  280. $ret['new_name']
  281. rev:
  282. 20100503 - franciscom - BUGID 3374
  283. 20100409 - franciscom - improved check on name len.
  284. BUGID 3367: Error after trying to copy a test case that
  285. the name is in the size limit.
  286. 20090120 - franciscom - added new action_on_duplicate_name
  287. */
  288. function create_tcase_only($parent_id,$name,$order=self::DEFAULT_ORDER,$id=self::AUTOMATIC_ID,
  289. $options=null)
  290. {
  291. $dummy = config_get('field_size');
  292. $name_max_len = $dummy->testcase_name;
  293. $name = trim($name);
  294. $originalNameLen = tlStringLen($name);
  295. $getOptions = array();
  296. $ret = array('id' => -1,'external_id' => 0, 'status_ok' => 1,'msg' => 'ok',
  297. 'new_name' => '', 'version_number' => 1, 'has_duplicate' => false);
  298. $my['options'] = array( 'check_duplicate_name' => self::DONT_CHECK_DUPLICATE_NAME,
  299. 'action_on_duplicate_name' => 'generate_new');
  300. $my['options'] = array_merge($my['options'], (array)$options);
  301. $doCreate=true;
  302. if ($my['options']['check_duplicate_name'])
  303. {
  304. $algo_cfg = config_get('testcase_cfg')->duplicated_name_algorithm;
  305. $getOptions['check_criteria'] = ($algo_cfg->type == 'counterSuffix') ? 'like' : '=';
  306. $getOptions['access_key'] = ($algo_cfg->type == 'counterSuffix') ? 'name' : 'id';
  307. $itemSet = $this->getDuplicatesByName($name,$parent_id,$getOptions);
  308. if( !is_null($itemSet) && ($siblingQty=count($itemSet)) > 0 )
  309. {
  310. $ret['has_duplicate'] = true;
  311. switch($my['options']['action_on_duplicate_name'])
  312. {
  313. case 'block':
  314. $doCreate=false;
  315. $ret['status_ok'] = 0;
  316. $ret['msg'] = sprintf(lang_get('testcase_name_already_exists'),$name);
  317. break;
  318. case 'generate_new':
  319. $doCreate=true;
  320. switch($algo_cfg->type)
  321. {
  322. case 'stringPrefix':
  323. $name = $algo_cfg->text . " " . $name ;
  324. $final_len = strlen($name);
  325. if( $final_len > $name_max_len)
  326. {
  327. $name = substr($name,0,$name_max_len);
  328. }
  329. break;
  330. case 'counterSuffix':
  331. $mask = !is_null($algo_cfg->text) ? $algo_cfg->text : '#%s';
  332. $nameSet = array_flip(array_keys($itemSet));
  333. $target = $name . ($suffix = sprintf($mask,++$siblingQty));
  334. // BUGID 3367
  335. $final_len = strlen($target);
  336. if( $final_len > $name_max_len)
  337. {
  338. $target = substr($target,strlen($suffix),$name_max_len);
  339. }
  340. // Need to recheck if new generated name does not crash with existent name
  341. // why? Suppose you have created:
  342. // TC [1]
  343. // TC [2]
  344. // TC [3]
  345. // Then you delete TC [2].
  346. // When I got siblings il will got 2 siblings, if I create new progressive using next,
  347. // it will be 3 => I will get duplicated name.
  348. while( isset($nameSet[$target]) )
  349. {
  350. $target = $name . ($suffix = sprintf($mask,++$siblingQty));
  351. // BUGID 3367
  352. $final_len = strlen($target);
  353. if( $final_len > $name_max_len)
  354. {
  355. $target = substr($target,strlen($suffix),$name_max_len);
  356. }
  357. }
  358. $name = $target;
  359. break;
  360. }
  361. $ret['status_ok'] = 1;
  362. $ret['new_name'] = $name;
  363. $ret['msg'] = sprintf(lang_get('created_with_title'),$name);
  364. break;
  365. case 'create_new_version':
  366. $doCreate=false;
  367. // If we found more that one with same name and same parent,
  368. // will take the first one.
  369. // BUGID 3374
  370. $xx = current($itemSet);
  371. $ret['id'] = $xx['id'];
  372. $ret['external_id']=$xx['tc_external_id'];
  373. $ret['status_ok'] = 1;
  374. $ret['new_name'] = $name;
  375. $ret['version_number'] = -1;
  376. $ret['msg'] = lang_get('create_new_version');
  377. break;
  378. default:
  379. break;
  380. }
  381. }
  382. }
  383. if( $ret['status_ok'] && $doCreate)
  384. {
  385. $safeLenName = tlSubStr($name, 0, $name_max_len);
  386. // Get tproject id
  387. $path2root=$this->tree_manager->get_path($parent_id);
  388. $tproject_id=$path2root[0]['parent_id'];
  389. $tcaseNumber=$this->tproject_mgr->generateTestCaseNumber($tproject_id);
  390. $tcase_id = $this->tree_manager->new_node($parent_id,$this->my_node_type,$safeLenName,$order,$id);
  391. $ret['id'] = $tcase_id;
  392. $ret['external_id'] = $tcaseNumber;
  393. if( !$ret['has_duplicate'] )
  394. {
  395. $ret['new_name'] = $safeLenName;
  396. $ret['msg'] = sprintf(lang_get('testcase_name_length_exceeded'),$originalNameLen,$name_max_len);
  397. }
  398. }
  399. return $ret;
  400. }
  401. /*
  402. function: create_tcversion
  403. args:
  404. returns:
  405. rev:
  406. 20100821 - franciscom - BUGID 3696 - test case step execution type ignored
  407. 20100106 - franciscom - Multiple Test Case Steps Feature
  408. 20080113 - franciscom - interface changes added tc_ext_id
  409. */
  410. function create_tcversion($id,$tc_ext_id,$version,$summary,$preconditions,$steps,
  411. $author_id,$execution_type=TESTCASE_EXECUTION_TYPE_MANUAL,$importance=2)
  412. {
  413. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  414. $tcase_version_id = $this->tree_manager->new_node($id,$this->node_types_descr_id['testcase_version']);
  415. $sql = "/* $debugMsg */ INSERT INTO {$this->tables['tcversions']} " .
  416. " (id,tc_external_id,version,summary,preconditions," .
  417. "author_id,creation_ts,execution_type,importance) " .
  418. " VALUES({$tcase_version_id},{$tc_ext_id},{$version},'" .
  419. $this->db->prepare_string($summary) . "','" . $this->db->prepare_string($preconditions) . "'," .
  420. $this->db->prepare_int($author_id) . "," . $this->db->db_now() .
  421. ", {$execution_type},{$importance} )";
  422. $result = $this->db->exec_query($sql);
  423. $ret['msg']='ok';
  424. $ret['id']=$tcase_version_id;
  425. $ret['status_ok']=1;
  426. if ($result && ( !is_null($steps) && is_array($steps) ) )
  427. {
  428. $steps2create = count($steps);
  429. $op['status_ok'] = 1;
  430. for($jdx=0 ; ($jdx < $steps2create && $op['status_ok']); $jdx++)
  431. {
  432. $op = $this->create_step($tcase_version_id,$steps[$jdx]['step_number'],$steps[$jdx]['actions'],
  433. $steps[$jdx]['expected_results'],$steps[$jdx]['execution_type']);
  434. }
  435. }
  436. if (!$result)
  437. {
  438. $ret['msg'] = $this->db->error_msg();
  439. $ret['status_ok']=0;
  440. $ret['id']=-1;
  441. }
  442. return $ret;
  443. }
  444. /*
  445. function: getDuplicatesByname
  446. args: $name
  447. $parent_id
  448. returns: hash
  449. */
  450. function getDuplicatesByName($name, $parent_id, $options=null)
  451. {
  452. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  453. $my['options'] = array( 'check_criteria' => '=', 'access_key' => 'id');
  454. $my['options'] = array_merge($my['options'], (array)$options);
  455. $target = $this->db->prepare_string($name);
  456. switch($my['options']['check_criteria'])
  457. {
  458. case '=':
  459. default:
  460. $check_criteria = " AND NHA.name = '{$target}' ";
  461. break;
  462. case 'like':
  463. $check_criteria = " AND NHA.name LIKE '{$target}%' ";
  464. break;
  465. }
  466. $sql = " SELECT DISTINCT NHA.id,NHA.name,TCV.tc_external_id" .
  467. " FROM {$this->tables['nodes_hierarchy']} NHA, " .
  468. " {$this->tables['nodes_hierarchy']} NHB, {$this->tables['tcversions']} TCV " .
  469. " WHERE NHA.node_type_id = {$this->my_node_type} " .
  470. " AND NHB.parent_id=NHA.id " .
  471. " AND TCV.id=NHB.id " .
  472. " AND NHB.node_type_id = {$this->node_types_descr_id['testcase_version']} " .
  473. " AND NHA.parent_id={$parent_id} {$check_criteria}";
  474. $rs = $this->db->fetchRowsIntoMap($sql,$my['options']['access_key']);
  475. if( is_null($rs) || count($rs) == 0 )
  476. {
  477. $rs=null;
  478. }
  479. return $rs;
  480. }
  481. /*
  482. function: get_by_name
  483. args: $name
  484. [$tsuite_name]: name of parent test suite
  485. [$tproject_name]
  486. returns: hash
  487. @internal revisions
  488. 20100831 - franciscom - BUGID 3729
  489. */
  490. function get_by_name($name, $tsuite_name = '', $tproject_name = '')
  491. {
  492. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  493. $recordset = null;
  494. $filters_on = array('tsuite_name' => false, 'tproject_name' => false);
  495. // BUGID 3729 - limit all names
  496. $field_size = config_get('field_size');
  497. $tsuite_name = tlSubStr(trim($tsuite_name),0, $field_size->testsuite_name);
  498. $tproject_name = tlSubStr(trim($tproject_name),0,$field_size->testproject_name);
  499. $name = tlSubStr(trim($name), 0, $field_size->testcase_name);
  500. $sql = "/* $debugMsg */ " .
  501. " SELECT DISTINCT NH_TCASE.id,NH_TCASE.name,NH_TCASE_PARENT.id AS parent_id," .
  502. " NH_TCASE_PARENT.name AS tsuite_name, TCV.tc_external_id " .
  503. " FROM {$this->tables['nodes_hierarchy']} NH_TCASE, " .
  504. " {$this->tables['nodes_hierarchy']} NH_TCASE_PARENT, " .
  505. " {$this->tables['nodes_hierarchy']} NH_TCVERSIONS," .
  506. " {$this->tables['tcversions']} TCV " .
  507. " WHERE NH_TCASE.node_type_id = {$this->my_node_type} " .
  508. " AND NH_TCASE.name = '{$this->db->prepare_string($name)}' " .
  509. " AND TCV.id=NH_TCVERSIONS.id " .
  510. " AND NH_TCVERSIONS.parent_id=NH_TCASE.id " .
  511. " AND NH_TCASE_PARENT.id=NH_TCASE.parent_id ";
  512. if($tsuite_name != "")
  513. {
  514. $sql .= " AND NH_TCASE_PARENT.name = '{$this->db->prepare_string($tsuite_name)}' " .
  515. " AND NH_TCASE_PARENT.node_type_id = {$this->node_types_descr_id['testsuite']} ";
  516. }
  517. $recordset = $this->db->get_recordset($sql);
  518. if(count($recordset) && $tproject_name != "")
  519. {
  520. list($tproject_info)=$this->tproject_mgr->get_by_name($tproject_name);
  521. foreach($recordset as $idx => $tcase_info)
  522. {
  523. if( $this->get_testproject($tcase_info['id']) != $tproject_info['id'] )
  524. {
  525. unset($recordset[$idx]);
  526. }
  527. }
  528. }
  529. return $recordset;
  530. }
  531. /*
  532. get array of info for every test case
  533. without any kind of filter.
  534. Every array element contains an assoc array with testcase info
  535. */
  536. function get_all()
  537. {
  538. $sql = " SELECT nodes_hierarchy.name, nodes_hierarchy.id
  539. FROM {$this->tables['nodes_hierarchy']} nodes_hierarchy
  540. WHERE nodes_hierarchy.node_type_id={$my_node_type}";
  541. $recordset = $this->db->get_recordset($sql);
  542. return $recordset;
  543. }
  544. /**
  545. * Show Test Case logic
  546. *
  547. * @param object $smarty reference to smarty object (controls viewer).
  548. * @param integer $id Test case unique identifier
  549. * @param integer $version_id (optional) you can work on ONE test case version,
  550. * or on ALL; default: ALL
  551. *
  552. * @internal
  553. [viewer_args]: map with keys
  554. action
  555. msg_result
  556. refresh_tree: controls if tree view is refreshed after every operation.
  557. default: yes
  558. user_feedback
  559. disable_edit: used to overwrite user rights
  560. default: 0 -> no
  561. returns:
  562. rev :
  563. 20090215 - franciscom - added info about links to test plans
  564. 20081114 - franciscom -
  565. added arguments and options that are useful when this method is
  566. used to display test case search results.
  567. path_info: map: key: testcase id
  568. value: array with path to test case, where:
  569. element 0 -> test project name
  570. other elements test suites name
  571. new options on viewer_args: hilite_testcase_name,show_match_count
  572. 20070930 - franciscom - REQ - BUGID 1078
  573. added disable_edit argument
  574. */
  575. function show(&$smarty,$guiObj,$template_dir,$id,$version_id = self::ALL_VERSIONS,
  576. $viewer_args = null,$path_info=null,$mode=null)
  577. {
  578. $status_ok = 1;
  579. $gui = is_null($guiObj) ? new stdClass() : $guiObj;
  580. $gui->parentTestSuiteName='';
  581. $gui->path_info=$path_info;
  582. $gui->tprojectName='';
  583. $gui->linked_versions=null;
  584. $gui->tc_current_version = array();
  585. $gui->bodyOnLoad="";
  586. $gui->bodyOnUnload="";
  587. $gui->submitCode="";
  588. $gui->dialogName = '';
  589. $gui->platforms = null;
  590. $gui->tableColspan = 5; // sorry magic related to table to display steps
  591. $gui->opt_requirements = false;
  592. $gui_cfg = config_get('gui');
  593. $the_tpl = config_get('tpl');
  594. $my_template = isset($the_tpl['tcView']) ? $the_tpl['tcView'] : 'tcView.tpl';
  595. $tcase_cfg = config_get('testcase_cfg');
  596. $req_mgr = new requirement_mgr($this->db);
  597. $tc_other_versions = array();
  598. $status_quo_map = array();
  599. $keywords_map = array();
  600. $arrReqs = array();
  601. $userid_array = array();
  602. // 20090718 - franciscom
  603. $cf_smarty = null;
  604. $formatOptions=null;
  605. $cfx=0;
  606. $filters=$this->buildCFLocationMap();
  607. if( !is_null($mode) && $mode=='editOnExec' )
  608. {
  609. // refers to two javascript functions present in testlink_library.js
  610. // and logic used to refresh both frames when user call this
  611. // method to edit a test case while executing it.
  612. $gui->dialogName='tcview_dialog';
  613. $gui->bodyOnLoad="dialog_onLoad($gui->dialogName)";
  614. $gui->bodyOnUnload="dialog_onUnload($gui->dialogName)";
  615. $gui->submitCode="return dialog_onSubmit($gui->dialogName)";
  616. }
  617. $viewer_defaults=array('title' => lang_get('title_test_case'),'show_title' => 'no',
  618. 'action' => '', 'msg_result' => '','user_feedback' => '',
  619. 'refreshTree' => 1, 'disable_edit' => 0,
  620. 'display_testproject' => 0,'display_parent_testsuite' => 0,
  621. 'hilite_testcase_name' => 0,'show_match_count' => 0);
  622. if( !is_null($viewer_args) && is_array($viewer_args) )
  623. {
  624. foreach($viewer_defaults as $key => $value)
  625. {
  626. if(isset($viewer_args[$key]) )
  627. {
  628. $viewer_defaults[$key]=$viewer_args[$key];
  629. }
  630. }
  631. }
  632. $gui->show_title=$viewer_defaults['show_title'];
  633. $gui->display_testcase_path=!is_null($path_info);
  634. $gui->hilite_testcase_name=$viewer_defaults['hilite_testcase_name'];
  635. $gui->pageTitle=$viewer_defaults['title'];
  636. $gui->show_match_count=$viewer_defaults['show_match_count'];
  637. if($gui->show_match_count && $gui->display_testcase_path )
  638. {
  639. $gui->match_count=count($path_info);
  640. }
  641. // fine grain control of operations
  642. // if( $viewer_defaults['disable_edit'] == 1 || has_rights($this->db,"mgt_modify_tc") == 'no' )
  643. // BUGID 3387
  644. if( $viewer_defaults['disable_edit'] == 1 || has_rights($this->db,"mgt_modify_tc") == false)
  645. {
  646. $mode = 'editDisabled';
  647. }
  648. $gui->show_mode = $mode;
  649. $gui->can_do = $this->getShowViewerActions($mode);
  650. if(is_array($id))
  651. {
  652. $a_id = $id;
  653. }
  654. else
  655. {
  656. $status_ok = $id > 0 ? 1 : 0;
  657. $a_id = array($id);
  658. }
  659. if($status_ok)
  660. {
  661. $path2root = $this->tree_manager->get_path($a_id[0]);
  662. $tproject_id = $path2root[0]['parent_id'];
  663. $info = $this->tproject_mgr->get_by_id($tproject_id);
  664. $gui->opt_requirements = $info['opt']->requirementsEnabled;
  665. $platformMgr = new tlPlatform($this->db,$tproject_id);
  666. $gui->platforms = $platformMgr->getAllAsMap();
  667. // BUGID 2378
  668. $testplans = $this->tproject_mgr->get_all_testplans($tproject_id,array('plan_status' =>1) );
  669. $gui->has_testplans = !is_null($testplans) && count($testplans) > 0 ? 1 : 0;
  670. if( $viewer_defaults['display_testproject'] )
  671. {
  672. $gui->tprojectName=$info['name'];
  673. }
  674. if( $viewer_defaults['display_parent_testsuite'] )
  675. {
  676. $parent_idx = count($path2root)-2;
  677. $gui->parentTestSuiteName = $path2root[$parent_idx]['name'];
  678. }
  679. $tcasePrefix = $this->tproject_mgr->getTestCasePrefix($tproject_id);
  680. if(trim($tcasePrefix) != "")
  681. {
  682. // Add To Testplan button will be disabled if the testcase doesn't belong to the current selected testproject
  683. // $gui->can_do->add2tplan = 'no';
  684. if ($_SESSION['testprojectPrefix'] == $tcasePrefix)
  685. {
  686. $gui->can_do->add2tplan = $gui->can_do->add2tplan == 'yes' ? has_rights($this->db,"testplan_planning") : 'no';
  687. }
  688. else
  689. {
  690. $gui->can_do->add2tplan = 'no';
  691. }
  692. $tcasePrefix .= $tcase_cfg->glue_character;
  693. }
  694. }
  695. if($status_ok && sizeof($a_id))
  696. {
  697. $allTCKeywords = $this->getKeywords($a_id,null,'testcase_id',' ORDER BY keyword ASC ');
  698. $allReqs = $req_mgr->get_all_for_tcase($a_id);
  699. foreach($a_id as $key => $tc_id)
  700. {
  701. $tc_array = $this->get_by_id($tc_id,$version_id);
  702. if (!$tc_array)
  703. {
  704. continue;
  705. }
  706. $tc_array[0]['tc_external_id'] = $tcasePrefix . $tc_array[0]['tc_external_id'];
  707. // get the status quo of execution and links of tc versions
  708. $status_quo_map[] = $this->get_versions_status_quo($tc_id);
  709. $gui->linked_versions[] = $this->get_linked_versions($tc_id);
  710. $keywords_map[] = isset($allTCKeywords[$tc_id]) ? $allTCKeywords[$tc_id] : null;
  711. $tc_current = $tc_array[0];
  712. $gui->tc_current_version[] = array($tc_current);
  713. //Get UserID and Updater ID for current Version
  714. $userid_array[$tc_current['author_id']] = null;
  715. $userid_array[$tc_current['updater_id']] = null;
  716. if(count($tc_array) > 1)
  717. {
  718. $tc_other_versions[] = array_slice($tc_array,1);
  719. }
  720. else
  721. {
  722. $tc_other_versions[] = null;
  723. }
  724. //Get author and updater id for each version
  725. if ($tc_other_versions[0])
  726. {
  727. foreach($tc_other_versions[0] as $key => $version)
  728. {
  729. $userid_array[$version['author_id']] = null;
  730. $userid_array[$version['updater_id']] = null;
  731. }
  732. }
  733. $tcReqs = isset($allReqs[$tc_id]) ? $allReqs[$tc_id] : null;
  734. $arrReqs[] = $tcReqs;
  735. foreach($filters as $locationKey => $locationFilter)
  736. {
  737. $cf_smarty[$cfx][$locationKey] =
  738. $this->html_table_of_custom_field_values($tc_id,'design',$locationFilter,
  739. null,null,$tproject_id);
  740. }
  741. $cfx++;
  742. } // foreach($a_id as $key => $tc_id)
  743. } // if (sizeof($a_id))
  744. // Removing duplicate and NULL id's
  745. unset($userid_array['']);
  746. $passeduserarray = array_keys($userid_array);
  747. $gui->cf = $cf_smarty;
  748. $gui->refreshTree = $viewer_defaults['refreshTree'];
  749. $gui->sqlResult = $viewer_defaults['msg_result'];
  750. $gui->action = $viewer_defaults['action'];
  751. $gui->user_feedback = $viewer_defaults['user_feedback'];
  752. $gui->execution_types = $this->execution_types;
  753. $gui->tcase_cfg = $tcase_cfg;
  754. $gui->users = tlUser::getByIDs($this->db,$passeduserarray,'id');
  755. $gui->status_quo = $status_quo_map;
  756. $gui->testcase_other_versions = $tc_other_versions;
  757. $gui->arrReqs = $arrReqs;
  758. $gui->view_req_rights = has_rights($this->db,"mgt_view_req");
  759. $gui->keywords_map = $keywords_map;
  760. $smarty->assign('gui',$gui);
  761. $smarty->display($template_dir . $my_template);
  762. }
  763. /**
  764. * update test case specification
  765. *
  766. * @param integer $id Test case unique identifier (node_hierarchy table)
  767. * @param integer $tcversion_id Test Case Version unique ID (node_hierarchy table)
  768. * @param string $name name/title
  769. * @param string $summary
  770. * @param string $preconditions
  771. * @param array $steps steps + expected results
  772. * @param integer $user_id who is doing the update
  773. * @param string $keywords_id optional list of keyword id to be linked to test case
  774. * this list will override previous keyword links (delete + insert).
  775. *
  776. * @param integer $tc_order optional order inside parent test suite
  777. * @param integer $execution_type optional
  778. * @param integer $importance optional
  779. *
  780. *
  781. *
  782. */
  783. function update($id,$tcversion_id,$name,$summary,$preconditions,$steps,
  784. $user_id,$keywords_id='',$tc_order=self::DEFAULT_ORDER,
  785. $execution_type=TESTCASE_EXECUTION_TYPE_MANUAL,$importance=2)
  786. {
  787. $ret['status_ok'] = 1;
  788. $ret['msg'] = '';
  789. tLog("TC UPDATE ID=($id): exec_type=$execution_type importance=$importance");
  790. // Check if new name will be create a duplicate testcase under same parent
  791. $checkDuplicates = config_get('check_names_for_duplicates');
  792. if ($checkDuplicates)
  793. {
  794. $check = $this->tree_manager->nodeNameExists($name,$this->my_node_type,$id);
  795. $ret['status_ok'] = !$check['status'];
  796. $ret['msg'] = $check['msg'];
  797. }
  798. if($ret['status_ok'])
  799. {
  800. $sql=array();
  801. $sql[] = " UPDATE {$this->tables['nodes_hierarchy']} SET name='" .
  802. $this->db->prepare_string($name) . "' WHERE id= {$id}";
  803. // test case version
  804. $sql[] = " UPDATE {$this->tables['tcversions']} tcversions " .
  805. " SET summary='" . $this->db->prepare_string($summary) . "'," .
  806. " updater_id=" . $this->db->prepare_int($user_id) . ", " .
  807. " modification_ts = " . $this->db->db_now() . "," .
  808. " execution_type=" . $this->db->prepare_int($execution_type) . ", " .
  809. " importance=" . $this->db->prepare_int($importance) . "," .
  810. " preconditions='" . $this->db->prepare_string($preconditions) . "' " .
  811. " WHERE tcversions.id = " . $this->db->prepare_int($tcversion_id);
  812. foreach($sql as $stm)
  813. {
  814. $result = $this->db->exec_query($stm);
  815. if( !$result )
  816. {
  817. $ret['status_ok'] = 0;
  818. $ret['msg'] = $this->db->error_msg;
  819. break;
  820. }
  821. }
  822. // BUGID 3634 - missing update.
  823. if( $ret['status_ok'] && !is_null($steps) )
  824. {
  825. $this->update_tcversion_steps($tcversion_id,$steps);
  826. }
  827. if( $ret['status_ok'] )
  828. {
  829. $this->updateKeywordAssignment($id,$keywords_id);
  830. }
  831. }
  832. return $ret;
  833. }
  834. /*
  835. function: updateKeywordAssignment
  836. args:
  837. returns:
  838. */
  839. private function updateKeywordAssignment($id,$keywords_id)
  840. {
  841. // To avoid false loggings, check is delete is needed
  842. $items = array();
  843. $items['stored'] = $this->get_keywords_map($id);
  844. if (is_null($items['stored']))
  845. $items['stored'] = array();
  846. $items['requested'] = array();
  847. if(trim($keywords_id) != "")
  848. {
  849. $a_keywords = explode(",",trim($keywords_id));
  850. $sql = " SELECT id,keyword " .
  851. " FROM {$this->tables['keywords']} " .
  852. " WHERE id IN (" . implode(',',$a_keywords) . ")";
  853. $items['requested'] = $this->db->fetchColumnsIntoMap($sql,'id','keyword');
  854. }
  855. $items['common'] = array_intersect_assoc($items['stored'],$items['requested']);
  856. $items['new'] = array_diff_assoc($items['requested'],$items['common']);
  857. $items['todelete'] = array_diff_assoc($items['stored'],$items['common']);
  858. if(!is_null($items['todelete']) && count($items['todelete']))
  859. {
  860. $this->deleteKeywords($id,array_keys($items['todelete']),self::AUDIT_ON);
  861. }
  862. if(!is_null($items['new']) && count($items['new']))
  863. {
  864. $this->addKeywords($id,array_keys($items['new']),self::AUDIT_ON);
  865. }
  866. }
  867. /*
  868. function: logKeywordChanges
  869. args:
  870. returns:
  871. */
  872. function logKeywordChanges($old,$new)
  873. {
  874. // try to understand the really new
  875. }
  876. /*
  877. function: check_link_and_exec_status
  878. Fore every version of testcase (id), do following checks:
  879. 1. testcase is linked to one of more test plans ?
  880. 2. if anwser is yes then,check if has been executed => has records on executions table
  881. args : id: testcase id
  882. returns: string with following values:
  883. no_links: testcase is not linked to any testplan
  884. linked_but_not_executed: testcase is linked at least to a testplan
  885. but has not been executed.
  886. linked_and_executed: testcase is linked at least to a testplan and
  887. has been executed => has records on executions table.
  888. */
  889. function check_link_and_exec_status($id)
  890. {
  891. $status = 'no_links';
  892. // get linked versions
  893. // ATTENTION TO PLATFORMS
  894. $linked_tcversions = $this->get_linked_versions($id);
  895. $has_links_to_testplans = is_null($linked_tcversions) ? 0 : 1;
  896. if($has_links_to_testplans)
  897. {
  898. // check if executed
  899. $linked_not_exec = $this->get_linked_versions($id,"NOT_EXECUTED");
  900. $status='linked_and_executed';
  901. if(count($linked_tcversions) == count($linked_not_exec))
  902. {
  903. $status = 'linked_but_not_executed';
  904. }
  905. }
  906. return $status;
  907. }
  908. /*
  909. rev:
  910. 20100107 - franciscom - Multiple Test Case Step Feature
  911. 20081015 - franciscom - added check to avoid bug due to no children
  912. */
  913. function delete($id,$version_id = self::ALL_VERSIONS)
  914. {
  915. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  916. $children=null;
  917. $do_it=true;
  918. // I'm trying to speedup the next deletes
  919. $sql="/* $debugMsg */ " .
  920. " SELECT NH_TCV.id AS tcversion_id, NH_TCSTEPS.id AS step_id " .
  921. " FROM {$this->tables['nodes_hierarchy']} NH_TCV " .
  922. " LEFT OUTER JOIN {$this->tables['nodes_hierarchy']} NH_TCSTEPS " .
  923. " ON NH_TCSTEPS.parent_id = NH_TCV.id ";
  924. if($version_id == self::ALL_VERSIONS)
  925. {
  926. if( is_array($id) )
  927. {
  928. $sql .= " WHERE NH_TCV.parent_id IN (" .implode(',',$id) . ") ";
  929. }
  930. else
  931. {
  932. $sql .= " WHERE NH_TCV.parent_id={$id} ";
  933. }
  934. }
  935. else
  936. {
  937. $sql .= " WHERE NH_TCV.parent_id={$id} AND NH_TCV.id = {$version_id}";
  938. }
  939. $children_rs=$this->db->get_recordset($sql);
  940. $do_it = !is_null($children_rs);
  941. if($do_it)
  942. {
  943. foreach($children_rs as $value)
  944. {
  945. $children['tcversion'][]=$value['tcversion_id'];
  946. $children['step'][]=$value['step_id'];
  947. }
  948. $this->_execution_delete($id,$version_id,$children);
  949. $this->_blind_delete($id,$version_id,$children);
  950. }
  951. return 1;
  952. }
  953. /*
  954. function: get_linked_versions
  955. For a test case get information about versions linked to testplans.
  956. Filters can be applied on:
  957. execution status
  958. active status
  959. args : id: testcase id
  960. [exec_status]: default: ALL, range: ALL,EXECUTED,NOT_EXECUTED
  961. [active_status]: default: ALL, range: ALL,ACTIVE,INACTIVE
  962. [tplan_id]
  963. returns: map.
  964. key: version id
  965. value: map with following structure:
  966. key: testplan id
  967. value: map with following structure:
  968. testcase_id
  969. tcversion_id
  970. id -> tcversion_id (node id)
  971. version
  972. summary
  973. importance
  974. author_id
  975. creation_ts
  976. updater_id
  977. modification_ts
  978. active
  979. is_open
  980. testplan_id
  981. tplan_name
  982. */
  983. function get_linked_versions($id,$exec_status="ALL",$active_status='ALL',$tplan_id=null,$platform_id=null)
  984. {
  985. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  986. $active_filter='';
  987. $active_status=strtoupper($active_status);
  988. if($active_status !='ALL')
  989. {
  990. $active_filter=' AND tcversions.active=' . $active_status=='ACTIVE' ? 1 : 0;
  991. }
  992. switch ($exec_status)
  993. {
  994. case "ALL":
  995. $sql = "/* $debugMsg */ " .
  996. " SELECT NH.parent_id AS testcase_id, NH.id AS tcversion_id, " .
  997. " tcversions.*, TTC.testplan_id, TTC.tcversion_id, TTC.platform_id," .
  998. " NHB.name AS tplan_name " .
  999. " FROM {$this->tables['nodes_hierarchy']} NH," .
  1000. " {$this->tables['tcversions']} tcversions," .
  1001. " {$this->tables['testplan_tcversions']} TTC, " .
  1002. " {$this->tables['nodes_hierarchy']} NHB " .
  1003. " WHERE TTC.tcversion_id = tcversions.id {$active_filter} " .
  1004. " AND tcversions.id = NH.id " .
  1005. " AND NHB.id = TTC.testplan_id " .
  1006. " AND NH.parent_id = {$id}";
  1007. if(!is_null($tplan_id))
  1008. {
  1009. $sql .= " AND TTC.testplan_id = {$tplan_id} ";
  1010. }
  1011. // 20100308 - franciscom
  1012. if(!is_null($platform_id))
  1013. {
  1014. $sql .= " AND TTC.platform_id = {$platform_id} ";
  1015. }
  1016. $recordset = $this->db->fetchMapRowsIntoMap($sql,'tcversion_id','testplan_id',database::CUMULATIVE);
  1017. // 20100330 - eloff - BUGID 3329
  1018. if( !is_null($recordset) )
  1019. {
  1020. // changes third access key from sequential index to platform_id
  1021. foreach ($recordset as $accessKey => $testplan)
  1022. {
  1023. foreach ($testplan as $tplanKey => $testcases)
  1024. {
  1025. // Use a temporary array to avoid key collisions
  1026. $newArray = array();
  1027. foreach ($testcases as $elemKey => $element)
  1028. {
  1029. $platform_id = $element['platform_id'];
  1030. $newArray[$platform_id] = $element;
  1031. }
  1032. $recordset[$accessKey][$tplanKey] = $newArray;
  1033. }
  1034. }
  1035. }
  1036. break;
  1037. case "EXECUTED":
  1038. $recordset=$this->get_exec_status($id,$exec_status,$active_status,$tplan_id,$platform_id);
  1039. break;
  1040. case "NOT_EXECUTED":
  1041. $recordset=$this->get_exec_status($id,$exec_status,$active_status,$tplan_id,$platform_id);
  1042. break;
  1043. }
  1044. // Multiple Test Case Steps
  1045. if( !is_null($recordset) )
  1046. {
  1047. $version2loop = array_keys($recordset);
  1048. foreach( $version2loop as $accessKey)
  1049. {
  1050. $step_set = $this->get_steps($accessKey);
  1051. $tplan2loop = array_keys($recordset[$accessKey]);
  1052. foreach( $tplan2loop as $tplanKey)
  1053. {
  1054. $elem2loop = array_keys($recordset[$accessKey][$tplanKey]);
  1055. foreach( $elem2loop as $elemKey)
  1056. {
  1057. $recordset[$accessKey][$tplanKey][$elemKey]['steps'] = $step_set;
  1058. }
  1059. }
  1060. }
  1061. }
  1062. return $recordset;
  1063. }
  1064. /*
  1065. Delete the following info:
  1066. req_coverage
  1067. risk_assignment
  1068. custom fields
  1069. keywords
  1070. links to test plans
  1071. tcversions
  1072. nodes from hierarchy
  1073. rev:
  1074. 20100825 - BUGID 3702
  1075. 20070602 - franciscom - delete attachments
  1076. */
  1077. function _blind_delete($id,$version_id=self::ALL_VERSIONS,$children=null)
  1078. {
  1079. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1080. $sql = array();
  1081. $destroyTC = false;
  1082. $item_id = $version_id;
  1083. $tcversion_list = $version_id;
  1084. if( $version_id == self::ALL_VERSIONS)
  1085. {
  1086. $destroyTC = true;
  1087. $item_id = $id;
  1088. $tcversion_list=implode(',',$children['tcversion']);
  1089. }
  1090. // BUGID 3465: Delete Test Project - User Execution Assignment is not deleted
  1091. // BUGID 3573: MySQL does not like ALIAS
  1092. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['user_assignments']} " .
  1093. " WHERE feature_id in (" .
  1094. " SELECT id FROM {$this->tables['testplan_tcversions']} " .
  1095. " WHERE tcversion_id IN ({$tcversion_list}))";
  1096. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['testplan_tcversions']} " .
  1097. " WHERE tcversion_id IN ({$tcversion_list})";
  1098. // Multiple Test Case Steps Feature
  1099. // BUGID 3702
  1100. if( !is_null($children['step']) )
  1101. {
  1102. // remove null elements
  1103. foreach($children['step'] as $key => $value)
  1104. {
  1105. if(is_null($value))
  1106. {
  1107. unset($children['step'][$key]);
  1108. }
  1109. }
  1110. if( count($children['step']) > 0)
  1111. {
  1112. $step_list=trim(implode(',',$children['step']));
  1113. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['tcsteps']} " .
  1114. " WHERE id IN ({$step_list})";
  1115. }
  1116. }
  1117. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['tcversions']} " .
  1118. " WHERE id IN ({$tcversion_list})";
  1119. foreach ($sql as $the_stm)
  1120. {
  1121. $result = $this->db->exec_query($the_stm);
  1122. }
  1123. if($destroyTC)
  1124. {
  1125. // Remove data that is related to Test Case => must be deleted when there is no more trace
  1126. // of test case => when all version are deleted
  1127. $sql = null;
  1128. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['testcase_keywords']} WHERE testcase_id = {$id}";
  1129. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['req_coverage']} WHERE testcase_id = {$id}";
  1130. foreach ($sql as $the_stm)
  1131. {
  1132. $result = $this->db->exec_query($the_stm);
  1133. }
  1134. $this->deleteAttachments($id);
  1135. $this->cfield_mgr->remove_all_design_values_from_node($id);
  1136. }
  1137. // Attention:
  1138. // After addition of test case steps feature, a test case version can be root of
  1139. // a subtree that contains the steps.
  1140. $this->tree_manager->delete_subtree($item_id);
  1141. }
  1142. /*
  1143. Delete the following info:
  1144. bugs
  1145. executions
  1146. cfield_execution_values
  1147. */
  1148. function _execution_delete($id,$version_id=self::ALL_VERSIONS,$children=null)
  1149. {
  1150. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1151. $sql = array();
  1152. if( $version_id == self::ALL_VERSIONS )
  1153. {
  1154. $tcversion_list=implode(',',$children['tcversion']);
  1155. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['execution_bugs']} " .
  1156. " WHERE execution_id IN (SELECT id FROM {$this->tables['executions']} " .
  1157. " WHERE tcversion_id IN ({$tcversion_list}))";
  1158. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['cfield_execution_values']} " .
  1159. " WHERE tcversion_id IN ({$tcversion_list})";
  1160. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['executions']} " .
  1161. " WHERE tcversion_id IN ({$tcversion_list})";
  1162. }
  1163. else
  1164. {
  1165. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['execution_bugs']} " .
  1166. " WHERE execution_id IN (SELECT id FROM {$this->tables['executions']} " .
  1167. " WHERE tcversion_id = {$version_id})";
  1168. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['cfield_execution_values']} " .
  1169. " WHERE tcversion_id = {$version_id}";
  1170. $sql[]="/* $debugMsg */ DELETE FROM {$this->tables['executions']} " .
  1171. " WHERE tcversion_id = {$version_id}";
  1172. }
  1173. foreach ($sql as $the_stm)
  1174. {
  1175. $result = $this->db->exec_query($the_stm);
  1176. }
  1177. }
  1178. /*
  1179. function: formatTestCaseIdentity
  1180. args: id: testcase id
  1181. external_id
  1182. returns: testproject id
  1183. */
  1184. function formatTestCaseIdentity($id,$external_id)
  1185. {
  1186. $path2root=$this->tree_manager->get_path($tc_id);
  1187. $tproject_id=$path2root[0]['parent_id'];
  1188. $tcasePrefix=$this->tproject_mgr->getTestCasePrefix($tproject_id);
  1189. }
  1190. /*
  1191. function: getPrefix
  1192. args: id: testcase id
  1193. [$tproject_id]
  1194. returns: array(prefix,testproject id)
  1195. */
  1196. function getPrefix($id, $tproject_id=null)
  1197. {
  1198. $root = $tproject_id;
  1199. if( is_null($root) )
  1200. {
  1201. $path2root=$this->tree_manager->get_path($id);
  1202. $root=$path2root[0]['parent_id'];
  1203. }
  1204. $tcasePrefix=$this->tproject_mgr->getTestCasePrefix($root);
  1205. return array($tcasePrefix,$root);
  1206. }
  1207. /*
  1208. function: get_testproject
  1209. Given a testcase id get node id of testproject to which testcase belongs.
  1210. args :id: testcase id
  1211. returns: testproject id
  1212. */
  1213. function get_testproject($id)
  1214. {
  1215. $a_path = $this->tree_manager->get_path($id);
  1216. return ($a_path[0]['parent_id']);
  1217. }
  1218. /*
  1219. 20061008 - franciscom - added
  1220. [$check_duplicate_name]
  1221. [$action_on_duplicate_name]
  1222. changed return type
  1223. */
  1224. function copy_to($id,$parent_id,$user_id,$options=null,$mappings=null)
  1225. {
  1226. $newTCObj = array('id' => -1, 'status_ok' => 0, 'msg' => 'ok', 'mappings' => null);
  1227. $my['options'] = array( 'check_duplicate_name' => self::DONT_CHECK_DUPLICATE_NAME,
  1228. 'action_on_duplicate_name' => 'generate_new', 'copy_also' => null);
  1229. // needed when Test Case is copied to a DIFFERENT Test Project,
  1230. // added during Test Project COPY Feature implementation
  1231. $my['mappings']['keywords'] = null;
  1232. $my['mappings']['requirements'] = null;
  1233. $my['mappings'] = array_merge($my['mappings'], (array)$mappings);
  1234. $my['options'] = array_merge($my['options'], (array)$options);
  1235. if( is_null($my['options']['copy_also']) )
  1236. {
  1237. $my['options']['copy_also'] = array('keyword_assignments' => true,'requirement_assignments' => true);
  1238. }
  1239. $tcase_info = $this->get_by_id($id);
  1240. if ($tcase_info)
  1241. {
  1242. $newTCObj = $this->create_tcase_only($parent_id,$tcase_info[0]['name'],
  1243. $tcase_info[0]['node_order'],self::AUTOMATIC_ID,
  1244. $my['options']);
  1245. if($newTCObj['status_ok'])
  1246. {
  1247. $ret['status_ok']=1;
  1248. $newTCObj['mappings'][$id] = $newTCObj['id'];
  1249. foreach($tcase_info as $tcversion)
  1250. {
  1251. // 20100221 - franciscom -
  1252. // IMPORTANT NOTICE:
  1253. // In order to implement COPY to another test project, WE CAN NOT ASK
  1254. // to method create_tcversion() to create inside itself THE STEPS.
  1255. // Passing NULL as steps we instruct create_tcversion() TO DO NOT CREATE STEPS
  1256. //
  1257. $op = $this->create_tcversion($newTCObj['id'],$newTCObj['external_id'],$tcversion['version'],
  1258. $tcversion['summary'],$tcversion['preconditions'],null,
  1259. $tcversion['author_id'],$tcversion['execution_type'],$tcversion['importance']);
  1260. if( $op['status_ok'] )
  1261. {
  1262. // 20100204 - franciscom
  1263. $newTCObj['mappings'][$tcversion['id']] = $op['id'];
  1264. // Need to get all steps
  1265. $stepsSet = $this->get_steps($tcversion['id']);
  1266. $to_tcversion_id = $op['id'];
  1267. if( !is_null($stepsSet) )
  1268. {
  1269. foreach($stepsSet as $key => $step)
  1270. {
  1271. $op = $this->create_step($to_tcversion_id,$step['step_number'],$step['actions'],
  1272. $step['expected_results'],$step['execution_type']);
  1273. }
  1274. }
  1275. }
  1276. }
  1277. // Conditional copies
  1278. if( isset($my['options']['copy_also']['keyword_assignments']) &&
  1279. $my['options']['copy_also']['keyword_assignments'])
  1280. {
  1281. $this->copyKeywordsTo($id,$newTCObj['id'],$my['mappings']['keywords']);
  1282. }
  1283. if (isset($my['options']['copy_also']['requirement_assignments']) &&
  1284. $my['options']['copy_also']['requirement_assignments'])
  1285. {
  1286. $this->copyReqAssignmentTo($id,$newTCObj['id'],$my['mappings']['requirements']);
  1287. }
  1288. $this->copy_cfields_design_values($id,$newTCObj['id']);
  1289. $this->copy_attachments($id,$newTCObj['id']);
  1290. }
  1291. }
  1292. return($newTCObj);
  1293. }
  1294. /*
  1295. function: create_new_version()
  1296. create a new test case version,
  1297. doing a copy of source test case version
  1298. args : $id: testcase id
  1299. $user_id: who is doing this operation.
  1300. [$source_version_id]: default null -> source is LATEST TCVERSION
  1301. returns:
  1302. map: id: node id of created tcversion
  1303. version: version number (i.e. 5)
  1304. msg
  1305. rev : 20070701 - franciscom - added version key on return map.
  1306. */
  1307. function create_new_version($id,$user_id,$source_version_id=null)
  1308. {
  1309. $tcversion_id = $this->tree_manager->new_node($id,$this->node_types_descr_id['testcase_version']);
  1310. // get last version for this test case (need to get new version number)
  1311. $last_version_info = $this->get_last_version_info($id, array('output' => 'minimun'));
  1312. $from = $source_version_id;
  1313. if( is_null($source_version_id) || $source_version_id <= 0)
  1314. {
  1315. $from = $last_version_info['id'];
  1316. }
  1317. $this->copy_tcversion($from,$tcversion_id,$last_version_info['version']+1,$user_id);
  1318. $ret['id'] = $tcversion_id;
  1319. $ret['version'] = $last_version_info['version']+1;
  1320. $ret['msg'] = 'ok';
  1321. return $ret;
  1322. }
  1323. /*
  1324. function: get_last_version_info
  1325. Get information about last version (greater number) of a testcase.
  1326. args : id: testcase id
  1327. [options]
  1328. returns: map with keys that depends of options['output']:
  1329. id -> tcversion_id
  1330. version
  1331. summary
  1332. importance
  1333. author_id
  1334. creation_ts
  1335. updater_id
  1336. modification_ts
  1337. active
  1338. is_open
  1339. */
  1340. function get_last_version_info($id,$options=null)
  1341. {
  1342. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1343. $my['options'] = array( 'get_steps' => false, 'output' => 'full');
  1344. $my['options'] = array_merge($my['options'], (array)$options);
  1345. $tcInfo = null;
  1346. switch($my['options']['output'])
  1347. {
  1348. case 'minimun':
  1349. default:
  1350. $fields2get = " TCV.id, TCV.version, TCV.tc_external_id ";
  1351. break;
  1352. case 'full':
  1353. default:
  1354. $fields2get = " TCV.* ";
  1355. break;
  1356. }
  1357. $sql = "/* $debugMsg */ SELECT MAX(version) AS version " .
  1358. " FROM {$this->tables['tcversions']} TCV," .
  1359. " {$this->tables['nodes_hierarchy']} NH WHERE ".
  1360. " NH.id = TCV.id ".
  1361. " AND NH.parent_id = {$id} ";
  1362. $max_version = $this->db->fetchFirstRowSingleColumn($sql,'version');
  1363. $tcInfo = null;
  1364. if ($max_version)
  1365. {
  1366. $sql = "SELECT {$fields2get} FROM {$this->tables['tcversions']} TCV," .
  1367. " {$this->tables['nodes_hierarchy']} NH ".
  1368. " WHERE TCV.version = {$max_version} AND NH.id = TCV.id".
  1369. " AND NH.parent_id = {$id}";
  1370. $tcInfo = $this->db->fetchFirstRow($sql);
  1371. }
  1372. // Multiple Test Case Steps Feature
  1373. if( !is_null($tcInfo) && $my['options']['get_steps'] )
  1374. {
  1375. $step_set = $this->get_steps($tcInfo['id']);
  1376. $tcInfo['steps'] = $step_set;
  1377. }
  1378. return $tcInfo;
  1379. }
  1380. /*
  1381. function: copy_tcversion
  1382. args:
  1383. returns:
  1384. rev:
  1385. 20100521 - franciscom - BUGID 3481 - preconditions are not copied
  1386. 20080119 - franciscom - tc_external_id management
  1387. */
  1388. function copy_tcversion($from_tcversion_id,$to_tcversion_id,$as_version_number,$user_id)
  1389. {
  1390. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1391. $now = $this->db->db_now();
  1392. $sql="/* $debugMsg */ " .
  1393. " INSERT INTO {$this->tables['tcversions']} " .
  1394. " (id,version,tc_external_id,author_id,creation_ts,summary, " .
  1395. " importance,execution_type,preconditions) " .
  1396. " SELECT {$to_tcversion_id} AS id, {$as_version_number} AS version, " .
  1397. " tc_external_id, " .
  1398. " {$user_id} AS author_id, {$now} AS creation_ts," .
  1399. " summary,importance,execution_type, preconditions" .
  1400. " FROM {$this->tables['tcversions']} " .
  1401. " WHERE id={$from_tcversion_id} ";
  1402. $result = $this->db->exec_query($sql);
  1403. // Need to get all steps
  1404. $stepsSet = $this->get_steps($from_tcversion_id);
  1405. if( !is_null($stepsSet) && count($stepsSet) > 0)
  1406. {
  1407. foreach($stepsSet as $key => $step)
  1408. {
  1409. $op = $this->create_step($to_tcversion_id,$step['step_number'],$step['actions'],
  1410. $step['expected_results'],$step['execution_type']);
  1411. }
  1412. }
  1413. }
  1414. /*
  1415. function: get_by_id_bulk
  1416. IMPORTANT CONSIDERATION:
  1417. how may elements can be used in an SQL IN CLAUSE?
  1418. Think there is a limit ( on MSSQL 1000 ?)
  1419. args :
  1420. returns:
  1421. */
  1422. function get_by_id_bulk($id,$version_id=self::ALL_VERSIONS, $get_active=0, $get_open=0)
  1423. {
  1424. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1425. $where_clause="";
  1426. $where_clause_names="";
  1427. $tcid_list ="";
  1428. $tcversion_id_filter="";
  1429. $sql = "";
  1430. $the_names = null;
  1431. if( is_array($id) )
  1432. {
  1433. $tcid_list = implode(",",$id);
  1434. $where_clause = " WHERE nodes_hierarchy.parent_id IN ($tcid_list) ";
  1435. $where_clause_names = " WHERE nodes_hierarchy.id IN ($tcid_list) ";
  1436. }
  1437. else
  1438. {
  1439. $where_clause = " WHERE nodes_hierarchy.parent_id = {$id} ";
  1440. $where_clause_names = " WHERE nodes_hierarchy.id = {$id} ";
  1441. }
  1442. if( $version_id != self::ALL_VERSIONS )
  1443. {
  1444. $tcversion_id_filter=" AND tcversions.id IN (" . implode(",",(array)$version_id) . ") ";
  1445. }
  1446. $sql = " /* $debugMsg */ SELECT nodes_hierarchy.parent_id AS testcase_id, ".
  1447. " tcversions.*, users.first AS author_first_name, users.last AS author_last_name, " .
  1448. " '' AS updater_first_name, '' AS updater_last_name " .
  1449. " FROM {$this->tables['nodes_hierarchy']} nodes_hierarchy " .
  1450. " JOIN {$this->tables['tcversions']} tcversions ON nodes_hierarchy.id = tcversions.id " .
  1451. " LEFT OUTER JOIN {$this->tables['users']} users ON tcversions.author_id = users.id " .
  1452. " {$where_clause} {$tcversion_id_filter} ORDER BY tcversions.version DESC";
  1453. $recordset = $this->db->get_recordset($sql);
  1454. if($recordset)
  1455. {
  1456. // get the names
  1457. $sql = " /* $debugMsg */ " .
  1458. " SELECT nodes_hierarchy.id AS testcase_id, nodes_hierarchy.name " .
  1459. " FROM {$this->tables['nodes_hierarchy']} nodes_hierarchy {$where_clause_names} ";
  1460. $the_names = $this->db->get_recordset($sql);
  1461. if($the_names)
  1462. {
  1463. foreach ($recordset as $the_key => $row )
  1464. {
  1465. reset($the_names);
  1466. foreach($the_names as $row_n)
  1467. {
  1468. if( $row['testcase_id'] == $row_n['testcase_id'])
  1469. {
  1470. $recordset[$the_key]['name']= $row_n['name'];
  1471. break;
  1472. }
  1473. }
  1474. }
  1475. }
  1476. $sql = " /* $debugMsg */ " .
  1477. " SELECT updater_id, users.first AS updater_first_name, users.last AS updater_last_name " .
  1478. " FROM {$this->tables['nodes_hierarchy']} nodes_hierarchy " .
  1479. " JOIN {$this->tables['tcversions']} tcversions ON nodes_hierarchy.id = tcversions.id " .
  1480. " LEFT OUTER JOIN {$this->tables['users']} users ON tcversions.updater_id = users.id " .
  1481. " {$where_clause} and tcversions.updater_id IS NOT NULL ";
  1482. $updaters = $this->db->get_recordset($sql);
  1483. if($updaters)
  1484. {
  1485. reset($recordset);
  1486. foreach ($recordset as $the_key => $row )
  1487. {
  1488. if ( !is_null($row['updater_id']) )
  1489. {
  1490. foreach ($updaters as $row_upd)
  1491. {
  1492. if ( $row['updater_id'] == $row_upd['updater_id'] )
  1493. {
  1494. $recordset[$the_key]['updater_last_name'] = $row_upd['updater_last_name'];
  1495. $recordset[$the_key]['updater_first_name'] = $row_upd['updater_first_name'];
  1496. break;
  1497. }
  1498. }
  1499. }
  1500. }
  1501. }
  1502. }
  1503. return($recordset ? $recordset : null);
  1504. }
  1505. /*
  1506. function: get_by_id
  1507. args : id: can be a single testcase id or an array od testcase id.
  1508. [version_id]: default self::ALL_VERSIONS => all versions
  1509. can be an array.
  1510. Useful to retrieve only a subset of versions.
  1511. null => means use version_number argument
  1512. [filters]:
  1513. [active_status]: default 'ALL', range: 'ALL','ACTIVE','INACTIVE'
  1514. has effect for the following version_id values:
  1515. self::ALL_VERSIONS,TC_LAST_VERSION, version_id is NOT an array
  1516. [open_status]: default 'ALL'
  1517. currently not used.
  1518. [version_number]: default 1, version number displayed at User Interface
  1519. [options]:
  1520. [output]: default 'full'
  1521. domain 'full','essential'
  1522. returns: array
  1523. */
  1524. function get_by_id($id,$version_id = self::ALL_VERSIONS, $filters = null, $options=null)
  1525. {
  1526. $my['filters'] = array( 'active_status' => 'ALL', 'open_status' => 'ALL', 'version_number' => 1);
  1527. $my['filters'] = array_merge($my['filters'], (array)$filters);
  1528. $my['options'] = array( 'output' => 'full', 'access_key' => 'tcversion_id');
  1529. $my['options'] = array_merge($my['options'], (array)$options);
  1530. $tcid_list = null;
  1531. $where_clause = '';
  1532. $active_filter = '';
  1533. if(is_array($id))
  1534. {
  1535. $tcid_list = implode(",",$id);
  1536. $where_clause = " WHERE NHTCV.parent_id IN ({$tcid_list}) ";
  1537. }
  1538. else
  1539. {
  1540. $where_clause = " WHERE NHTCV.parent_id = {$id} ";
  1541. }
  1542. if( ($version_id_is_array=is_array($version_id)) )
  1543. {
  1544. $versionid_list = implode(",",$version_id);
  1545. $where_clause .= " AND TCV.id IN ({$versionid_list}) ";
  1546. }
  1547. else
  1548. {
  1549. // 20090521 - franciscom - search by human version number
  1550. if( is_null($version_id) )
  1551. {
  1552. $where_clause .= " AND TCV.version = {$my['filters']['version_number']} ";
  1553. }
  1554. else
  1555. {
  1556. if($version_id != self::ALL_VERSIONS && $version_id != self::LATEST_VERSION)
  1557. {
  1558. $where_clause .= " AND TCV.id = {$version_id} ";
  1559. }
  1560. }
  1561. $active_status = strtoupper($my['filters']['active_status']);
  1562. if($active_status != 'ALL')
  1563. {
  1564. $active_filter =' AND TCV.active=' . ($active_status=='ACTIVE' ? 1 : 0) . ' ';
  1565. }
  1566. }
  1567. switch($my['options']['output'])
  1568. {
  1569. case 'full':
  1570. $sql = "SELECT UA.login AS updater_login,UB.login AS author_login,
  1571. NHTC.name,NHTC.node_order,NHTCV.parent_id AS testcase_id, TCV.*,
  1572. UB.first AS author_first_name,UB.last AS author_last_name,
  1573. UA.first AS updater_first_name,UA.last AS updater_last_name
  1574. FROM {$this->tables['nodes_hierarchy']} NHTCV
  1575. JOIN {$this->tables['nodes_hierarchy']} NHTC ON NHTCV.parent_id = NHTC.id
  1576. JOIN {$this->tables['tcversions']} TCV ON NHTCV.id = TCV.id
  1577. LEFT OUTER JOIN {$this->tables['users']} UB ON TCV.author_id = UB.id
  1578. LEFT OUTER JOIN {$this->tables['users']} UA ON TCV.updater_id = UA.id
  1579. $where_clause $active_filter
  1580. ORDER BY TCV.version DESC";
  1581. break;
  1582. case 'essential':
  1583. $sql = " SELECT NHTC.name,NHTC.node_order,NHTCV.parent_id AS testcase_id, " .
  1584. " TCV.version, TCV.id, TCV.tc_external_id " .
  1585. " FROM {$this->tables['nodes_hierarchy']} NHTCV " .
  1586. " JOIN {$this->tables['nodes_hierarchy']} NHTC ON NHTCV.parent_id = NHTC.id " .
  1587. " JOIN {$this->tables['tcversions']} TCV ON NHTCV.id = TCV.id " .
  1588. " {$where_clause} {$active_filter} " .
  1589. " ORDER BY TCV.version DESC";
  1590. break;
  1591. }
  1592. // Control improvements
  1593. if( !$version_id_is_array && $version_id == self::LATEST_VERSION)
  1594. {
  1595. // 20090413 - franciscom -
  1596. // But, how performance wise can be do this, instead of using MAX(version)
  1597. // and a group by?
  1598. //
  1599. // 20100309 - franciscom -
  1600. // if $id was a list then this will return something USELESS
  1601. //
  1602. if( is_null($tcid_list) )
  1603. {
  1604. $recordset = array($this->db->fetchFirstRow($sql));
  1605. }
  1606. else
  1607. {
  1608. // Write to event viewer ???
  1609. // throw exception ??
  1610. }
  1611. }
  1612. else
  1613. {
  1614. $recordset = $this->db->get_recordset($sql);
  1615. }
  1616. // Multiple Test Case Steps
  1617. if( !is_null($recordset) && $my['options']['output'] == 'full')
  1618. {
  1619. $key2loop = array_keys($recordset);
  1620. foreach( $key2loop as $accessKey)
  1621. {
  1622. $step_set = $this->get_steps($recordset[$accessKey]['id']);
  1623. $recordset[$accessKey]['steps'] = $step_set;
  1624. }
  1625. }
  1626. return ($recordset ? $recordset : null);
  1627. }
  1628. /*
  1629. function: get_versions_status_quo
  1630. Get linked and executed status quo.
  1631. IMPORTANT:
  1632. NO INFO SPECIFIC TO TESTPLAN ITEMS where testacase can be linked to
  1633. is returned.
  1634. args : id: test case id
  1635. [tcversion_id]: default: null -> get info about all versions.
  1636. can be a single value or an array.
  1637. [testplan_id]: default: null -> all testplans where testcase is linked,
  1638. are analised to generate results.
  1639. when not null, filter for testplan_id, to analise for
  1640. generating results.
  1641. returns: map.
  1642. key: tcversion_id.
  1643. value: map with the following keys:
  1644. tcversion_id, linked , executed
  1645. linked field: will take the following values
  1646. if $testplan_id == null
  1647. NULL if the tc version is not linked to ANY TEST PLAN
  1648. tcversion_id if linked
  1649. if $testplan_id != null
  1650. NULL if the tc version is not linked to $testplan_id
  1651. executed field: will take the following values
  1652. if $testplan_id == null
  1653. NULL if the tc version has not been executed in ANY TEST PLAN
  1654. tcversion_id if has executions.
  1655. if $testplan_id != null
  1656. NULL if the tc version has not been executed in $testplan_id
  1657. rev :
  1658. */
  1659. function get_versions_status_quo($id, $tcversion_id=null, $testplan_id=null)
  1660. {
  1661. $testplan_filter='';
  1662. $tcversion_filter='';
  1663. if(!is_null($tcversion_id))
  1664. {
  1665. if(is_array($tcversion_id))
  1666. {
  1667. $tcversion_filter=" AND NH.id IN (" . implode(",",$tcversion_id) . ") ";
  1668. }
  1669. else
  1670. {
  1671. $tcversion_filter=" AND NH.id={$tcversion_id} ";
  1672. }
  1673. }
  1674. $testplan_filter='';
  1675. if(!is_null($testplan_id))
  1676. {
  1677. $testplan_filter=" AND E.testplan_id = {$testplan_id} ";
  1678. }
  1679. $execution_join=" LEFT OUTER JOIN {$this->tables['executions']} E " .
  1680. " ON (E.tcversion_id = NH.id {$testplan_filter})";
  1681. $sqlx= " SELECT TCV.id,TCV.version " .
  1682. " FROM {$this->tables['nodes_hierarchy']} NHA " .
  1683. " JOIN {$this->tables['nodes_hierarchy']} NHB ON NHA.parent_id = NHB.id " .
  1684. " JOIN {$this->tables['tcversions']} TCV ON NHA.id = TCV.id " .
  1685. " WHERE NHA.parent_id = {$id}";
  1686. $version_id = $this->db->fetchRowsIntoMap($sqlx,'version');
  1687. $sql="SELECT DISTINCT NH.id AS tcversion_id,T.tcversion_id AS linked, " .
  1688. " E.tcversion_id AS executed,E.tcversion_number,TCV.version " .
  1689. " FROM {$this->tables['nodes_hierarchy']} NH " .
  1690. " JOIN {$this->tables['tcversions']} TCV ON (TCV.id = NH.id ) " .
  1691. " LEFT OUTER JOIN {$this->tables['testplan_tcversions']} T ON T.tcversion_id = NH.id " .
  1692. " {$execution_join} WHERE NH.parent_id = {$id} {$tcversion_filter} ORDER BY executed DESC";
  1693. $rs = $this->db->get_recordset($sql);
  1694. $recordset=array();
  1695. $template=array('tcversion_id' => '','linked' => '','executed' => '');
  1696. foreach($rs as $elem)
  1697. {
  1698. $recordset[$elem['tcversion_id']]=$template;
  1699. $recordset[$elem['tcversion_id']]['tcversion_id']=$elem['tcversion_id'];
  1700. $recordset[$elem['tcversion_id']]['linked']=$elem['linked'];
  1701. $recordset[$elem['tcversion_id']]['version']=$elem['version'];
  1702. }
  1703. foreach($rs as $elem)
  1704. {
  1705. $tcvid=null;
  1706. if( $elem['tcversion_number'] != $elem['version'])
  1707. {
  1708. if( !is_null($elem['tcversion_number']) )
  1709. {
  1710. $tcvid=$version_id[$elem['tcversion_number']]['id'];
  1711. }
  1712. }
  1713. else
  1714. {
  1715. $tcvid=$elem['tcversion_id'];
  1716. }
  1717. if( !is_null($tcvid) )
  1718. {
  1719. $recordset[$tcvid]['executed']=$tcvid;
  1720. $recordset[$tcvid]['version']=$elem['tcversion_number'];
  1721. }
  1722. }
  1723. return($recordset);
  1724. }
  1725. /*
  1726. function: get_exec_status
  1727. Get information about executed and linked status in
  1728. every testplan, a testcase is linked to.
  1729. args : id : testcase id
  1730. [exec_status]: default: ALL, range: ALL,EXECUTED,NOT_EXECUTED
  1731. [active_status]: default: ALL, range: ALL,ACTIVE,INACTIVE
  1732. returns: map
  1733. key: tcversion_id
  1734. value: map:
  1735. key: testplan_id
  1736. value: map with following keys:
  1737. tcase_id
  1738. tcversion_id
  1739. version
  1740. testplan_id
  1741. tplan_name
  1742. linked if linked to testplan -> tcversion_id
  1743. executed if executed in testplan -> tcversion_id
  1744. exec_on_tplan if executed in testplan -> testplan_id
  1745. rev:
  1746. 20080531 - franciscom
  1747. Because we allow people to update test case version linked to test plan,
  1748. and to do this we update tcversion_id on executions to new version
  1749. maintaining the really executed version in tcversion_number (version number displayed
  1750. on User Interface) field we need to change algorithm.
  1751. */
  1752. function get_exec_status($id,$exec_status="ALL",$active_status='ALL',$tplan_id=null,$platform_id=null)
  1753. {
  1754. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1755. $active_status = strtoupper($active_status);
  1756. // Get info about tcversions of this test case
  1757. $sqlx = "/* $debugMsg */ " .
  1758. " SELECT TCV.id,TCV.version,TCV.active" .
  1759. " FROM {$this->tables['nodes_hierarchy']} NHA " .
  1760. " JOIN {$this->tables['nodes_hierarchy']} NHB ON NHA.parent_id = NHB.id " .
  1761. " JOIN {$this->tables['tcversions']} TCV ON NHA.id = TCV.id ";
  1762. $where_clause = " WHERE NHA.parent_id = {$id}";
  1763. if(!is_null($tplan_id))
  1764. {
  1765. $sqlx .= " JOIN {$this->tables['testplan_tcversions']} TTCV ON TTCV.tcversion_id = TCV.id ";
  1766. $where_clause .= " AND TTCV.tplan_id = {$tplan_id} ";
  1767. }
  1768. $sqlx .= $where_clause;
  1769. $version_id = $this->db->fetchRowsIntoMap($sqlx,'version');
  1770. $sql = "/* $debugMsg */ " .
  1771. " SELECT DISTINCT NH.parent_id AS tcase_id, NH.id AS tcversion_id, " .
  1772. " T.tcversion_id AS linked, T.platform_id, TCV.active, E.tcversion_id AS executed, " .
  1773. " E.testplan_id AS exec_on_tplan, E.tcversion_number, " .
  1774. " T.testplan_id, NHB.name AS tplan_name, TCV.version " .
  1775. " FROM {$this->tables['nodes_hierarchy']} NH " .
  1776. " JOIN {$this->tables['testplan_tcversions']} T ON T.tcversion_id = NH.id " .
  1777. " JOIN {$this->tables['tcversions']} TCV ON T.tcversion_id = TCV.id " .
  1778. " JOIN {$this->tables['nodes_hierarchy']} NHB ON T.testplan_id = NHB.id " .
  1779. " LEFT OUTER JOIN {$this->tables['executions']} E " .
  1780. " ON (E.tcversion_id = NH.id AND E.testplan_id=T.testplan_id AND E.platform_id=T.platform_id ) " .
  1781. " WHERE NH.parent_id = {$id} ";
  1782. if(!is_null($tplan_id))
  1783. {
  1784. $sql .= " AND T.tplan_id = {$tplan_id} ";
  1785. }
  1786. if(!is_null($platform_id))
  1787. {
  1788. $sql .= " AND T.platform_id = {$platform_id} ";
  1789. }
  1790. $sql .= " ORDER BY version,tplan_name";
  1791. $rs = $this->db->get_recordset($sql);
  1792. // set right tcversion_id, based on tcversion_number,version comparison
  1793. $item_not_executed = null;
  1794. $item_executed = null;
  1795. $link_info = null;
  1796. $in_set = null;
  1797. if (sizeof($rs))
  1798. {
  1799. foreach($rs as $idx => $elem)
  1800. {
  1801. if( $elem['tcversion_number'] != $elem['version'])
  1802. {
  1803. // Save to generate record for linked but not executed if needed
  1804. // (see below fix not executed section)
  1805. // 20100111 - franciscom - PLATFORM REFACTORING
  1806. // access key => (version,test plan, platform)
  1807. $link_info[$elem['tcversion_id']][$elem['testplan_id']][$elem['platform_id']]=$elem;
  1808. // We are working with a test case version, that was used in a previous life of this test plan
  1809. // information about his tcversion_id is not anymore present in tables:
  1810. //
  1811. // testplan_tcversions
  1812. // executions
  1813. // cfield_execution_values.
  1814. //
  1815. // if has been executed, but after this operation User has choosen to upgrade tcversion
  1816. // linked to testplan to a different (may be a newest) test case version.
  1817. //
  1818. // We can get this information using table tcversions using tcase id and version number
  1819. // (value displayed at User Interface) as search key.
  1820. //
  1821. // Important:
  1822. // executions.tcversion_number: maintain info about RIGHT TEST case version executed
  1823. // executions.tcversion_id : test case version linked to test plan.
  1824. //
  1825. //
  1826. if( is_null($elem['tcversion_number']) )
  1827. {
  1828. // Not Executed
  1829. $rs[$idx]['executed']=null;
  1830. $rs[$idx]['tcversion_id']=$elem['tcversion_id'];
  1831. $rs[$idx]['version']=$elem['version'];
  1832. $rs[$idx]['linked']=$elem['tcversion_id'];
  1833. $item_not_executed[]=$idx;
  1834. }
  1835. else
  1836. {
  1837. // Get right tcversion_id
  1838. $rs[$idx]['executed']=$version_id[$elem['tcversion_number']]['id'];
  1839. $rs[$idx]['tcversion_id']=$rs[$idx]['executed'];
  1840. $rs[$idx]['version']=$elem['tcversion_number'];
  1841. $rs[$idx]['linked']=$rs[$idx]['executed'];
  1842. $item_executed[]=$idx;
  1843. }
  1844. $version=$rs[$idx]['version'];
  1845. $rs[$idx]['active']=$version_id[$version]['active'];
  1846. }
  1847. else
  1848. {
  1849. $item_executed[]=$idx;
  1850. }
  1851. // needed for logic to avoid miss not executed (see below fix not executed)
  1852. // $in_set[$rs[$idx]['tcversion_id']][$rs[$idx]['testplan_id']]=$rs[$idx]['tcversion_id'];
  1853. $in_set[$rs[$idx]['tcversion_id']][$rs[$idx]['testplan_id']][$rs[$idx]['platform_id']]=$rs[$idx]['tcversion_id'];
  1854. }
  1855. }
  1856. else
  1857. {
  1858. $rs = array();
  1859. }
  1860. // fix not executed
  1861. //
  1862. // need to add record for linked but not executed, that due to new
  1863. // logic to upate testplan-tcversions link can be absent
  1864. if(!is_null($link_info))
  1865. {
  1866. foreach($link_info as $tcversion_id => $elem)
  1867. {
  1868. foreach($elem as $testplan_id => $platform_link)
  1869. {
  1870. foreach($platform_link as $platform_id => $value)
  1871. {
  1872. if( !isset($in_set[$tcversion_id][$testplan_id][$platform_id]) )
  1873. {
  1874. // missing record
  1875. $value['executed']=null;
  1876. $value['exec_on_tplan']=null;
  1877. $value['tcversion_number']=null;
  1878. $rs[]=$value;
  1879. // Must Update list of not executed
  1880. $kix=count($rs);
  1881. $item_not_executed[]=$kix > 0 ? $kix-1 : $kix;
  1882. }
  1883. }
  1884. }
  1885. }
  1886. }
  1887. // Convert to result map.
  1888. switch ($exec_status)
  1889. {
  1890. case 'NOT_EXECUTED':
  1891. $target=$item_not_executed;
  1892. break;
  1893. case 'EXECUTED':
  1894. $target=$item_executed;
  1895. break;
  1896. default:
  1897. $target = array_keys($rs);
  1898. break;
  1899. }
  1900. $recordset = null;
  1901. if( !is_null($target) ) // minor fix - 20090716 - franciscom
  1902. {
  1903. foreach($target as $idx)
  1904. {
  1905. $elem=$rs[$idx];
  1906. if( $active_status=='ALL' ||
  1907. $active_status='ACTIVE' && $elem['active'] ||
  1908. $active_status='INACTIVE' && $elem['active']==0 )
  1909. {
  1910. $recordset[$elem['tcversion_id']][$elem['testplan_id']][$elem['platform_id']]=$elem;
  1911. }
  1912. }
  1913. }
  1914. if( !is_null($recordset) )
  1915. {
  1916. ksort($recordset);
  1917. }
  1918. return $recordset;
  1919. }
  1920. // -------------------------------------------------------------------------------
  1921. /**
  1922. * @param string stringID external test case ID
  1923. * a string on the form XXXXXGNN where:
  1924. * XXXXX: test case prefix, exists one for each test project
  1925. * G: glue character
  1926. * NN: test case number (generated using testprojects.tc_counter field)
  1927. *
  1928. * @return internal id (node id in nodes_hierarchy)
  1929. * 0 -> test case prefix OK, but external id does not exists
  1930. * -1 -> test case prefix KO
  1931. *
  1932. * 20080818 - franciscom - Dev Note
  1933. * I'm a feeling regarding performance of this function.
  1934. * Surelly adding a new column to tcversions (prefix) will simplify a lot this function.
  1935. * Other choice (that I refuse to implement time ago) is to add prefix field
  1936. * as a new nodes_hierarchy column.
  1937. * This must be discussed with dev team if we got performance bottleneck trying
  1938. * to get internal id from external one.
  1939. *
  1940. * @internal Revisions:
  1941. * 20091229 - eloff - BUGID 3021 fixed error when tc prefix contains glue character
  1942. * 20090608 - franciscom - fixed error on management of numeric part (externalID)
  1943. * 20080126 - franciscom - BUGID 1313
  1944. */
  1945. function getInternalID($stringID,$glueCharacter = null)
  1946. {
  1947. $internalID = 0;
  1948. if (is_null($glueCharacter))
  1949. {
  1950. $cfg = config_get('testcase_cfg');
  1951. $glueCharacter = $cfg->glue_character;
  1952. }
  1953. // Find the last glue char
  1954. $gluePos = strrpos($stringID, $glueCharacter);
  1955. $status_ok = ($gluePos !== false);
  1956. if($status_ok)
  1957. {
  1958. $internalID = -1;
  1959. $rawTestCasePrefix = substr($stringID, 0, $gluePos);
  1960. $rawExternalID = substr($stringID, $gluePos+1);
  1961. $externalID = is_numeric($rawExternalID) ? intval($rawExternalID) : 0;
  1962. // Check first if Test Project prefix is valid, if not abort
  1963. $testCasePrefix = $this->db->prepare_string($rawTestCasePrefix);
  1964. $sql = "SELECT id FROM {$this->tables['testprojects']} " .
  1965. "WHERE prefix = '" . $testCasePrefix . "'";
  1966. $tproject_info = $this->db->get_recordset($sql);
  1967. $status_ok = !is_null($tproject_info);
  1968. }
  1969. if( $status_ok )
  1970. {
  1971. $internalID = 0;
  1972. // get all test cases with requested external ID on all test projects.
  1973. // we do not have way to work only on one test project.
  1974. $sql = "SELECT DISTINCT NH.parent_id AS tcase_id" .
  1975. " FROM {$this->tables['tcversions']} TCV, {$this->tables['nodes_hierarchy']} NH" .
  1976. " WHERE TCV.id = NH.id " .
  1977. " AND TCV.tc_external_id = {$externalID}";
  1978. $testCases = $this->db->fetchRowsIntoMap($sql,'tcase_id');
  1979. if(!is_null($testCases))
  1980. {
  1981. $tproject_id = $tproject_info[0]['id'];
  1982. foreach($testCases as $tcaseID => $value)
  1983. {
  1984. $path2root = $this->tree_manager->get_path($tcaseID);
  1985. if($tproject_id == $path2root[0]['parent_id'])
  1986. {
  1987. $internalID = $tcaseID;
  1988. break;
  1989. }
  1990. }
  1991. }
  1992. }
  1993. return $internalID;
  1994. }
  1995. /*
  1996. function: filterByKeyword
  1997. given a test case id (or an array of test case id)
  1998. and a keyword filter, returns for the test cases given in input
  1999. only which pass the keyword filter criteria.
  2000. args :
  2001. returns:
  2002. */
  2003. function filterByKeyword($id,$keyword_id=0, $keyword_filter_type='OR')
  2004. {
  2005. $keyword_filter= '' ;
  2006. $subquery='';
  2007. // test case filter
  2008. if( is_array($id) )
  2009. {
  2010. $testcase_filter = " AND testcase_id IN (" . implode(',',$id) . ")";
  2011. }
  2012. else
  2013. {
  2014. $testcase_filter = " AND testcase_id = {$id} ";
  2015. }
  2016. if( is_array($keyword_id) )
  2017. {
  2018. $keyword_filter = " AND keyword_id IN (" . implode(',',$keyword_id) . ")";
  2019. if($keyword_filter_type == 'AND')
  2020. {
  2021. $subquery = "AND testcase_id IN (" .
  2022. " SELECT MAFALDA.testcase_id FROM
  2023. ( SELECT COUNT(testcase_id) AS HITS,testcase_id
  2024. FROM {$this->tables['keywords']} K, {$this->tables['testcase_keywords']}
  2025. WHERE keyword_id = K.id
  2026. {$keyword_filter}
  2027. GROUP BY testcase_id ) AS MAFALDA " .
  2028. " WHERE MAFALDA.HITS=" . count($keyword_id) . ")";
  2029. $keyword_filter ='';
  2030. }
  2031. }
  2032. else if( $keyword_id > 0 )
  2033. {
  2034. $keyword_filter = " AND keyword_id = {$keyword_id} ";
  2035. }
  2036. $map_keywords = null;
  2037. $sql = " SELECT testcase_id,keyword_id,keyword
  2038. FROM {$this->tables['keywords']} K, {$this->tables['testcase_keywords']}
  2039. WHERE keyword_id = K.id
  2040. {$testcase_filter}
  2041. {$keyword_filter} {$subquery}
  2042. ORDER BY keyword ASC ";
  2043. // $map_keywords = $this->db->fetchRowsIntoMap($sql,'testcase_id');
  2044. $map_keywords = $this->db->fetchMapRowsIntoMap($sql,'testcase_id','keyword_id');
  2045. return($map_keywords);
  2046. } //end function
  2047. // -------------------------------------------------------------------------------
  2048. // Keyword related methods
  2049. // -------------------------------------------------------------------------------
  2050. /*
  2051. function: getKeywords
  2052. args :
  2053. returns:
  2054. */
  2055. function getKeywords($tcID,$kwID = null,$column = 'keyword_id',$orderByClause = null)
  2056. {
  2057. $sql = "SELECT keyword_id,keywords.keyword,keywords.notes,testcase_id
  2058. FROM {$this->tables['testcase_keywords']} testcase_keywords, {$this->tables['keywords']} keywords
  2059. WHERE keyword_id = keywords.id AND testcase_id ";
  2060. $bCumulative = 0;
  2061. if (is_array($tcID))
  2062. {
  2063. $sql .= " IN (".implode(",",$tcID).")";
  2064. $bCumulative = 1;
  2065. }
  2066. else
  2067. {
  2068. $sql .= "= {$tcID}";
  2069. }
  2070. if (!is_null($kwID))
  2071. {
  2072. $sql .= " AND keyword_id = {$kwID}";
  2073. }
  2074. if (!is_null($orderByClause))
  2075. {
  2076. $sql .= $orderByClause;
  2077. }
  2078. $tcKeywords = $this->db->fetchRowsIntoMap($sql,$column,$bCumulative);
  2079. return $tcKeywords;
  2080. }
  2081. /*
  2082. function: get_keywords_map
  2083. args: id: testcase id
  2084. [order_by_clause]: default: '' -> no order choosen
  2085. must be an string with complete clause, i.e.
  2086. 'ORDER BY keyword'
  2087. returns: map with keywords information
  2088. key: keyword id
  2089. value: map with following keys.
  2090. */
  2091. function get_keywords_map($id,$order_by_clause='')
  2092. {
  2093. $sql = "SELECT keyword_id,keywords.keyword
  2094. FROM {$this->tables['testcase_keywords']} testcase_keywords, {$this->tables['keywords']} keywords
  2095. WHERE keyword_id = keywords.id ";
  2096. if (is_array($id))
  2097. $sql .= " AND testcase_id IN (".implode(",",$id).") ";
  2098. else
  2099. $sql .= " AND testcase_id = {$id} ";
  2100. $sql .= $order_by_clause;
  2101. $map_keywords = $this->db->fetchColumnsIntoMap($sql,'keyword_id','keyword');
  2102. return $map_keywords;
  2103. }
  2104. /*
  2105. function:
  2106. args :
  2107. returns:
  2108. */
  2109. function addKeyword($id,$kw_id,$audit=self::AUDIT_ON)
  2110. {
  2111. $kw = $this->getKeywords($id,$kw_id);
  2112. if (sizeof($kw))
  2113. {
  2114. return 1;
  2115. }
  2116. $sql = " INSERT INTO {$this->tables['testcase_keywords']} (testcase_id,keyword_id) " .
  2117. " VALUES ($id,$kw_id)";
  2118. $result = ($this->db->exec_query($sql) ? 1 : 0);
  2119. if ($result)
  2120. {
  2121. $tcInfo = $this->tree_manager->get_node_hierarchy_info($id);
  2122. $keyword = tlKeyword::getByID($this->db,$kw_id);
  2123. if ($keyword && $tcInfo && $audit == self::AUDIT_ON)
  2124. {
  2125. logAuditEvent(TLS("audit_keyword_assigned_tc",$keyword->name,$tcInfo['name']),
  2126. "ASSIGN",$id,"nodes_hierarchy");
  2127. }
  2128. }
  2129. return $result;
  2130. }
  2131. /*
  2132. function:
  2133. args :
  2134. returns:
  2135. */
  2136. function addKeywords($id,$kw_ids,$audit = self::AUDIT_ON)
  2137. {
  2138. $status_ok = 1;
  2139. $num_kws = sizeof($kw_ids);
  2140. for($idx = 0; $idx < $num_kws; $idx++)
  2141. {
  2142. $status_ok = $status_ok && $this->addKeyword($id,$kw_ids[$idx],$audit);
  2143. }
  2144. return $status_ok;
  2145. }
  2146. /*
  2147. function: set's the keywords of the given testcase to the passed keywords
  2148. args :
  2149. returns:
  2150. */
  2151. function setKeywords($id,$kw_ids,$audit = self::AUDIT_ON)
  2152. {
  2153. $result = $this->deleteKeywords($id);
  2154. if ($result && sizeof($kw_ids))
  2155. {
  2156. $result = $this->addKeywords($id,$kw_ids);
  2157. }
  2158. return $result;
  2159. }
  2160. /**
  2161. *
  2162. *
  2163. * mappings is only useful when source_id and target_id do not belong to same Test Project.
  2164. * Because keywords are defined INSIDE a Test Project, ID will be different for same keyword
  2165. * in a different Test Project.
  2166. *
  2167. */
  2168. function copyKeywordsTo($id,$destID,$mappings)
  2169. {
  2170. $status_ok = true;
  2171. $this->deleteKeywords($destID);
  2172. $sourceItems = $this->getKeywords($id);
  2173. if( !is_null($sourceItems) )
  2174. {
  2175. // build item id list
  2176. $keySet = array_keys($sourceItems);
  2177. foreach($keySet as $itemPos => $itemID)
  2178. {
  2179. if( isset($mappings[$itemID]) )
  2180. {
  2181. $keySet[$itemPos] = $mappings[$itemID];
  2182. }
  2183. $status_ok = $status_ok && $this->addKeyword($destID,$keySet[$itemPos]);
  2184. }
  2185. }
  2186. return $status_ok;
  2187. }
  2188. /*
  2189. function:
  2190. args :
  2191. returns:
  2192. */
  2193. function deleteKeywords($tcID,$kwID = null,$audit=self::AUDIT_ON)
  2194. {
  2195. $sql = " DELETE FROM {$this->tables['testcase_keywords']} WHERE testcase_id = {$tcID} ";
  2196. if (!is_null($kwID))
  2197. {
  2198. if(is_array($kwID))
  2199. {
  2200. $sql .= " AND keyword_id IN (" . implode(',',$kwID) . ")";
  2201. $key4log=$kwID;
  2202. }
  2203. else
  2204. {
  2205. $sql .= " AND keyword_id = {$kwID}";
  2206. $key4log = array($kwID);
  2207. }
  2208. }
  2209. else
  2210. {
  2211. $key4log = array_keys((array)$this->get_keywords_map($tcID));
  2212. }
  2213. $result = $this->db->exec_query($sql);
  2214. if ($result)
  2215. {
  2216. $tcInfo = $this->tree_manager->get_node_hierarchy_info($tcID);
  2217. if ($tcInfo && $key4log)
  2218. {
  2219. foreach($key4log as $key2get)
  2220. {
  2221. $keyword = tlKeyword::getByID($this->db,$key2get);
  2222. if ($keyword && $audit==self::AUDIT_ON)
  2223. {
  2224. logAuditEvent(TLS("audit_keyword_assignment_removed_tc",$keyword->name,$tcInfo['name']),
  2225. "ASSIGN",$tcID,"nodes_hierarchy");
  2226. }
  2227. }
  2228. }
  2229. }
  2230. return $result;
  2231. }
  2232. // -------------------------------------------------------------------------------
  2233. // END Keyword related methods
  2234. // -------------------------------------------------------------------------------
  2235. /*
  2236. function: get_executions
  2237. get information about all execution for a testcase version, on a testplan
  2238. on a build. Execution results are ordered by execution timestamp.
  2239. Is possible to filter certain executions
  2240. Is possible to choose Ascending/Descending order of results. (order by exec timestamp).
  2241. args : id: testcase (node id) - can be single value or array.
  2242. version_id: tcversion id (node id) - can be single value or array.
  2243. tplan_id: testplan id
  2244. build_id: if null -> do not filter by build_id
  2245. platform_id: if null -> do not filter by build_id
  2246. options: default null, map with options.
  2247. [exec_id_order] default: 'DESC' - range: ASC,DESC
  2248. [exec_to_exclude]: default: null -> no filter
  2249. can be single value or array, this exec id will be EXCLUDED.
  2250. returns: map
  2251. key: tcversion id
  2252. value: array where every element is a map with following keys
  2253. name: testcase name
  2254. testcase_id
  2255. id: tcversion_id
  2256. version
  2257. summary: testcase spec. summary
  2258. steps: testcase spec. steps
  2259. expected_results: testcase spec. expected results
  2260. execution_type: see const.inc.php TESTCASE_EXECUTION_TYPE_ constants
  2261. importance
  2262. author_id: tcversion author
  2263. creation_ts: timestamp of creation
  2264. updater_id: last updater of specification
  2265. modification_ts:
  2266. active: tcversion active status
  2267. is_open: tcversion open status
  2268. tester_login
  2269. tester_first_name
  2270. tester_last_name
  2271. tester_id
  2272. execution_id
  2273. status: execution status
  2274. execution_notes
  2275. execution_ts
  2276. execution_run_type: see const.inc.php TESTCASE_EXECUTION_TYPE_ constants
  2277. build_id
  2278. build_name
  2279. build_is_active
  2280. build_is_open
  2281. platform_id
  2282. platform_name
  2283. */
  2284. function get_executions($id,$version_id,$tplan_id,$build_id,$platform_id,$options=null)
  2285. {
  2286. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2287. $my['options'] = array('exec_id_order' => 'DESC', 'exec_to_exclude' => null);
  2288. $my['options'] = array_merge($my['options'], (array)$options);
  2289. $filterKeys = array('build_id','platform_id');
  2290. foreach($filterKeys as $key)
  2291. {
  2292. $filterBy[$key] = '';
  2293. if( !is_null($$key) )
  2294. {
  2295. $itemSet = implode(',', (array)$$key);
  2296. $filterBy[$key] = " AND e.{$key} IN ({$itemSet}) ";
  2297. }
  2298. }
  2299. // --------------------------------------------------------------------
  2300. if( is_array($id) )
  2301. {
  2302. $tcid_list = implode(",",$id);
  2303. $where_clause = " WHERE NHA.parent_id IN ({$tcid_list}) ";
  2304. }
  2305. else
  2306. {
  2307. $where_clause = " WHERE NHA.parent_id = {$id} ";
  2308. }
  2309. if( is_array($version_id) )
  2310. {
  2311. $versionid_list = implode(",",$version_id);
  2312. $where_clause .= " AND tcversions.id IN ({$versionid_list}) ";
  2313. }
  2314. else
  2315. {
  2316. if($version_id != self::ALL_VERSIONS)
  2317. {
  2318. $where_clause .= " AND tcversions.id = {$version_id} ";
  2319. }
  2320. }
  2321. if( !is_null($my['options']['exec_to_exclude']) )
  2322. {
  2323. if( is_array($my['options']['exec_to_exclude']))
  2324. {
  2325. if(count($my['options']['exec_to_exclude']) > 0 )
  2326. {
  2327. $exec_id_list = implode(",",$my['options']['exec_to_exclude']);
  2328. $where_clause .= " AND e.id NOT IN ({$exec_id_list}) ";
  2329. }
  2330. }
  2331. else
  2332. {
  2333. $where_clause .= " AND e.id <> {$exec_id_list} ";
  2334. }
  2335. }
  2336. // --------------------------------------------------------------------
  2337. // 20090517 - to manage deleted users i need to change:
  2338. // users.id AS tester_id => e.tester_id AS tester_id
  2339. // 20090214 - franciscom - e.execution_type -> e.execution_run_type
  2340. //
  2341. $sql="/* $debugMsg */ SELECT NHB.name,NHA.parent_id AS testcase_id, tcversions.*,
  2342. users.login AS tester_login,
  2343. users.first AS tester_first_name,
  2344. users.last AS tester_last_name,
  2345. e.tester_id AS tester_id,
  2346. e.id AS execution_id, e.status,e.tcversion_number,
  2347. e.notes AS execution_notes, e.execution_ts, e.execution_type AS execution_run_type,
  2348. e.build_id AS build_id,
  2349. b.name AS build_name, b.active AS build_is_active, b.is_open AS build_is_open,
  2350. e.platform_id,p.name AS platform_name
  2351. FROM {$this->tables['nodes_hierarchy']} NHA
  2352. JOIN {$this->tables['nodes_hierarchy']} NHB ON NHA.parent_id = NHB.id
  2353. JOIN {$this->tables['tcversions']} tcversions ON NHA.id = tcversions.id
  2354. JOIN {$this->tables['executions']} e ON NHA.id = e.tcversion_id
  2355. AND e.testplan_id = {$tplan_id}
  2356. {$filterBy['build_id']} {$filterBy['platform_id']}
  2357. JOIN {$this->tables['builds']} b ON e.build_id=b.id
  2358. LEFT OUTER JOIN {$this->tables['users']} users ON users.id = e.tester_id
  2359. LEFT OUTER JOIN {$this->tables['platforms']} p ON p.id = e.platform_id
  2360. $where_clause
  2361. ORDER BY NHA.node_order ASC, NHA.parent_id ASC, execution_id {$my['options']['exec_id_order']}";
  2362. $recordset = $this->db->fetchArrayRowsIntoMap($sql,'id');
  2363. return($recordset ? $recordset : null);
  2364. }
  2365. /*
  2366. function: get_last_execution
  2367. args :
  2368. returns: map:
  2369. key: tcversions.id
  2370. value: map with following keys:
  2371. execution_id
  2372. status: execution status
  2373. execution_type: see const.inc.php TESTCASE_EXECUTION_TYPE_ constants
  2374. name: testcase name
  2375. testcase_id
  2376. tsuite_id: parent testsuite of testcase (node id)
  2377. id: tcversion id (node id)
  2378. version
  2379. summary: testcase spec. summary
  2380. steps: testcase spec. steps
  2381. expected_results: testcase spec. expected results
  2382. execution_type: type of execution desired
  2383. importance
  2384. author_id: tcversion author
  2385. creation_ts: timestamp of creation
  2386. updater_id: last updater of specification.
  2387. modification_ts
  2388. active: tcversion active status
  2389. is_open: tcversion open status
  2390. tester_login
  2391. tester_first_name
  2392. tester_last_name
  2393. tester_id
  2394. execution_notes
  2395. execution_ts
  2396. execution_run_type: how the execution was really done
  2397. build_id
  2398. build_name
  2399. build_is_active
  2400. build_is_open
  2401. rev:
  2402. 20090815 - franciscom - added platform_id argument
  2403. 20090716 - franciscom - added options argument, removed get_no_executions
  2404. 20080103 - franciscom - added execution_type
  2405. */
  2406. function get_last_execution($id,$version_id,$tplan_id,$build_id,$platform_id,$options=null)
  2407. {
  2408. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2409. $resultsCfg = config_get('results');
  2410. $status_not_run=$resultsCfg['status_code']['not_run'];
  2411. $filterKeys = array('build_id','platform_id');
  2412. foreach($filterKeys as $key)
  2413. {
  2414. $filterBy[$key] = '';
  2415. if( !is_null($$key) )
  2416. {
  2417. $itemSet = implode(',', (array)$$key);
  2418. $filterBy[$key] = " AND e.{$key} IN ({$itemSet}) ";
  2419. }
  2420. }
  2421. $where_clause_1 = '';
  2422. $where_clause_2 = '';
  2423. $add_columns='';
  2424. $add_groupby='';
  2425. $cumulativeMode=0;
  2426. $group_by = '';
  2427. // getNoExecutions: 1 -> if testcase/version_id has not been executed return anyway
  2428. // standard return structure.
  2429. // 0 -> default
  2430. //
  2431. // groupByBuild: 0 -> default, get last execution on ANY BUILD, then for a testcase/version_id
  2432. // only a record will be present on return struture.
  2433. // GROUP BY must be done ONLY BY tcversion_id
  2434. //
  2435. // 1 -> get last execution on EACH BUILD.
  2436. // GROUP BY must be done BY tcversion_id,build_id
  2437. //
  2438. $localOptions=array('getNoExecutions' => 0, 'groupByBuild' => 0);
  2439. if(!is_null($options) && is_array($options))
  2440. {
  2441. $localOptions=array_merge($localOptions,$options);
  2442. }
  2443. if( is_array($id) )
  2444. {
  2445. $tcid_list = implode(",",$id);
  2446. $where_clause = " WHERE NHA.parent_id IN ({$tcid_list}) ";
  2447. }
  2448. else
  2449. {
  2450. $where_clause = " WHERE NHA.parent_id = {$id} ";
  2451. }
  2452. if( is_array($version_id) )
  2453. {
  2454. $versionid_list = implode(",",$version_id);
  2455. $where_clause_1 = $where_clause . " AND NHA.id IN ({$versionid_list}) ";
  2456. $where_clause_2 = $where_clause . " AND tcversions.id IN ({$versionid_list}) ";
  2457. }
  2458. else
  2459. {
  2460. if($version_id != self::ALL_VERSIONS)
  2461. {
  2462. $where_clause_1 = $where_clause . " AND NHA.id = {$version_id} ";
  2463. $where_clause_2 = $where_clause . " AND tcversions.id = {$version_id} ";
  2464. }
  2465. }
  2466. // This logic (is mine - franciscom) must be detailed better!!!!!
  2467. $group_by = ' GROUP BY tcversion_id ';
  2468. $add_fields = ', e.tcversion_id AS tcversion_id';
  2469. if( $localOptions['groupByBuild'] )
  2470. {
  2471. $add_fields .= ', e.build_id';
  2472. $group_by .= ', e.build_id';
  2473. $cumulativeMode = 1;
  2474. // Hummm!!! I do not understand why this can be needed
  2475. $where_clause_1 = $where_clause;
  2476. $where_clause_2 = $where_clause;
  2477. }
  2478. // $group_by .= $localOptions['groupByBuild'] ? $add_groupby : '';
  2479. // $group_by = $set_group_by ? ' GROUP BY tcversion_id ' : '';
  2480. // $group_by = ($group_by == '' && $add_groupby != '') ? ' GROUP BY ' : $group_by;
  2481. // we may be need to remove tcversion filter ($set_group_by==false)
  2482. // $add_field = $set_group_by ? ', e.tcversion_id AS tcversion_id' : '';
  2483. // $add_field = $localOptions['groupByBuild'] ? '' : ', e.tcversion_id AS tcversion_id';
  2484. // $where_clause_1 = $localOptions['groupByBuild'] ? $where_clause : $where_clause_1;
  2485. // $where_clause_2 = $localOptions['groupByBuild'] ? $where_clause : $where_clause_2;
  2486. // get list of max exec id, to be used filter in next query
  2487. // Here we can get:
  2488. // a) one record for each tcversion_id (ignoring build)
  2489. // b) one record for each tcversion_id,build
  2490. //
  2491. $sql="/* $debugMsg */ " .
  2492. " SELECT COALESCE(MAX(e.id),0) AS execution_id {$add_fields}" .
  2493. " FROM {$this->tables['nodes_hierarchy']} NHA " .
  2494. " JOIN {$this->tables['executions']} e ON NHA.id = e.tcversion_id AND e.testplan_id = {$tplan_id} " .
  2495. " {$filterBy['build_id']} {$filterBy['platform_id']}" .
  2496. " AND e.status IS NOT NULL " .
  2497. " $where_clause_1 {$group_by}";
  2498. // 20090716 - order of columns changed
  2499. $recordset = $this->db->fetchColumnsIntoMap($sql,'execution_id','tcversion_id');
  2500. $and_exec_id='';
  2501. if( !is_null($recordset) && count($recordset) > 0)
  2502. {
  2503. $the_list = implode(",", array_keys($recordset));
  2504. if($the_list != '')
  2505. {
  2506. if( count($recordset) > 1 )
  2507. {
  2508. $and_exec_id = " AND e.id IN ($the_list) ";
  2509. }
  2510. else
  2511. {
  2512. $and_exec_id = " AND e.id = $the_list ";
  2513. }
  2514. }
  2515. }
  2516. $executions_join=" JOIN {$this->tables['executions']} e ON NHA.id = e.tcversion_id " .
  2517. " AND e.testplan_id = {$tplan_id} {$and_exec_id} {$filterBy['build_id']} " .
  2518. " {$filterBy['platform_id']} ";
  2519. if( $localOptions['getNoExecutions'] )
  2520. {
  2521. $executions_join = " LEFT OUTER " . $executions_join;
  2522. }
  2523. else
  2524. {
  2525. // @TODO understand if this condition is really needed - 20090716 - franciscom
  2526. $executions_join .= " AND e.status IS NOT NULL ";
  2527. }
  2528. // 20090517 - to manage deleted users i need to change:
  2529. // users.id AS tester_id => e.tester_id AS tester_id
  2530. // 20090214 - franciscom - we need tcversions.execution_type and executions.execution_type
  2531. // 20090208 - franciscom
  2532. // found bug due to use of tcversions.*, because field execution_type
  2533. // exist on both execution and tcversion table.
  2534. // At least with Postgres tcversions.execution_type was used always
  2535. //
  2536. // 20080103 - franciscom - added execution_type in recordset
  2537. // 20060921 - franciscom -
  2538. // added NHB.parent_id to get same order as in the navigator tree
  2539. //
  2540. $sql= "/* $debugMsg */ SELECT e.id AS execution_id, " .
  2541. " COALESCE(e.status,'{$status_not_run}') AS status, " .
  2542. " e.execution_type AS execution_run_type," .
  2543. " NHB.name,NHA.parent_id AS testcase_id, NHB.parent_id AS tsuite_id," .
  2544. " tcversions.id,tcversions.tc_external_id,tcversions.version,tcversions.summary," .
  2545. " tcversions.preconditions," .
  2546. // " tcversions.steps,tcversions.expected_results,tcversions.importance,tcversions.author_id," .
  2547. " tcversions.importance,tcversions.author_id," .
  2548. " tcversions.creation_ts,tcversions.updater_id,tcversions.modification_ts,tcversions.active," .
  2549. " tcversions.is_open,tcversions.execution_type," .
  2550. " users.login AS tester_login,users.first AS tester_first_name," .
  2551. " users.last AS tester_last_name, e.tester_id AS tester_id," .
  2552. " e.notes AS execution_notes, e.execution_ts, e.build_id,e.tcversion_number," .
  2553. " builds.name AS build_name, builds.active AS build_is_active, builds.is_open AS build_is_open," .
  2554. " e.platform_id,p.name AS platform_name" .
  2555. " FROM {$this->tables['nodes_hierarchy']} NHA" .
  2556. " JOIN {$this->tables['nodes_hierarchy']} NHB ON NHA.parent_id = NHB.id" .
  2557. " JOIN {$this->tables['tcversions']} tcversions ON NHA.id = tcversions.id" .
  2558. " {$executions_join}" .
  2559. " LEFT OUTER JOIN {$this->tables['builds']} builds ON builds.id = e.build_id" .
  2560. " AND builds.testplan_id = {$tplan_id}" .
  2561. " LEFT OUTER JOIN {$this->tables['users']} users ON users.id = e.tester_id " .
  2562. " LEFT OUTER JOIN {$this->tables['platforms']} p ON p.id = e.platform_id" .
  2563. " $where_clause_2" .
  2564. " ORDER BY NHB.parent_id ASC, NHA.node_order ASC, NHA.parent_id ASC, execution_id DESC";
  2565. $recordset = $this->db->fetchRowsIntoMap($sql,'id',$cumulativeMode);
  2566. // Multiple Test Case Steps Feature
  2567. if( !is_null($recordset) )
  2568. {
  2569. $itemSet = array_keys($recordset);
  2570. foreach( $itemSet as $sdx)
  2571. {
  2572. $step_set = $this->get_steps($recordset[$sdx]['id']);
  2573. $recordset[$sdx]['steps'] = $step_set;
  2574. }
  2575. }
  2576. return($recordset ? $recordset : null);
  2577. }
  2578. /*
  2579. function: exportTestCaseDataToXML
  2580. args :
  2581. returns:
  2582. rev:
  2583. * 20100315 - amitkhullar - Added options for Requirements and CFields for Export.
  2584. * 20100105 - franciscom - added execution_type, importance
  2585. * 20090204 - franciscom - added export of node_order
  2586. * 20080206 - franciscom - added externalid
  2587. */
  2588. function exportTestCaseDataToXML($tcase_id,$tcversion_id,$tproject_id=null,
  2589. $bNoXMLHeader = false,$optExport = array())
  2590. {
  2591. static $reqMgr;
  2592. static $keywordMgr;
  2593. if( is_null($reqMgr) )
  2594. {
  2595. $reqMgr = new requirement_mgr($this->db);
  2596. $keywordMgr = new tlKeyword();
  2597. }
  2598. $tc_data = $this->get_by_id($tcase_id,$tcversion_id);
  2599. if (!$tproject_id)
  2600. {
  2601. $tproject_id = $this->getTestProjectFromTestCase($tcase_id);
  2602. }
  2603. // Get Custom Field Data
  2604. if ($optExport['CFIELDS'])
  2605. {
  2606. $cfMap = $this->get_linked_cfields_at_design($tcase_id,null,null,$tproject_id);
  2607. // ||yyy||-> tags, {{xxx}} -> attribute
  2608. // tags and attributes receive different treatment on exportDataToXML()
  2609. //
  2610. // each UPPER CASE word in this map KEY, MUST HAVE AN OCCURENCE on $elemTpl
  2611. // value is a key inside $tc_data[0]
  2612. //
  2613. if( !is_null($cfMap) && count($cfMap) > 0 )
  2614. {
  2615. $cfRootElem = "<custom_fields>{{XMLCODE}}</custom_fields>";
  2616. $cfElemTemplate = "\t" . "<custom_field>\n" .
  2617. "\t<name><![CDATA[||NAME||]]></name>\n" .
  2618. "\t<value><![CDATA[||VALUE||\n]]></value>\n</custom_field>\n";
  2619. $cfDecode = array ("||NAME||" => "name","||VALUE||" => "value");
  2620. $tc_data[0]['xmlcustomfields'] = exportDataToXML($cfMap,$cfRootElem,$cfElemTemplate,$cfDecode,true);
  2621. }
  2622. }
  2623. // Get Keywords
  2624. if ($optExport['KEYWORDS'])
  2625. {
  2626. $keywords = $this->getKeywords($tcase_id);
  2627. if(!is_null($keywords))
  2628. {
  2629. $xmlKW = "<keywords>" . $keywordMgr->toXMLString($keywords,true) . "</keywords>";
  2630. $tc_data[0]['xmlkeywords'] = $xmlKW;
  2631. }
  2632. }
  2633. // Get Requirements
  2634. if ($optExport['REQS'])
  2635. {
  2636. $requirements = $reqMgr->get_all_for_tcase($tcase_id);
  2637. if( !is_null($requirements) && count($requirements) > 0 )
  2638. {
  2639. $reqRootElem = "\t<requirements>\n{{XMLCODE}}\t</requirements>\n";
  2640. $reqElemTemplate = "\t\t<requirement>\n" .
  2641. "\t\t\t<req_spec_title><![CDATA[||REQ_SPEC_TITLE||]]></req_spec_title>\n" .
  2642. "\t\t\t<doc_id><![CDATA[||REQ_DOC_ID||]]></doc_id>\n" .
  2643. "\t\t\t<title><![CDATA[||REQ_TITLE||]]></title>\n" .
  2644. "\t\t</requirement>\n";
  2645. $reqDecode = array ("||REQ_SPEC_TITLE||" => "req_spec_title",
  2646. "||REQ_DOC_ID||" => "req_doc_id","||REQ_TITLE||" => "title");
  2647. $tc_data[0]['xmlrequirements'] = exportDataToXML($requirements,$reqRootElem,$reqElemTemplate,$reqDecode,true);
  2648. }
  2649. }
  2650. // ------------------------------------------------------------------------------------
  2651. // BUGID 3695 - missing execution_type
  2652. // Multiple Test Case Steps Feature
  2653. $stepRootElem = "<steps>{{XMLCODE}}</steps>";
  2654. $stepTemplate = "\n" . '<step>' . "\n" .
  2655. "\t<step_number><![CDATA[||STEP_NUMBER||]]></step_number>\n" .
  2656. "\t<actions><![CDATA[||ACTIONS||]]></actions>\n" .
  2657. "\t<expectedresults><![CDATA[||EXPECTEDRESULTS||]]></expectedresults>\n" .
  2658. "\t<execution_type><![CDATA[||EXECUTIONTYPE||]]></execution_type>\n" .
  2659. "</step>\n";
  2660. $stepInfo = array("||STEP_NUMBER||" => "step_number",
  2661. "||ACTIONS||" => "actions",
  2662. "||EXPECTEDRESULTS||" => "expected_results",
  2663. "||EXECUTIONTYPE||" => "execution_type" );
  2664. $stepSet = $tc_data[0]['steps'];
  2665. $xmlsteps = exportDataToXML($stepSet,$stepRootElem,$stepTemplate,$stepInfo,true);
  2666. $tc_data[0]['xmlsteps'] = $xmlsteps;
  2667. // ------------------------------------------------------------------------------------
  2668. $rootElem = "{{XMLCODE}}";
  2669. if (isset($optExport['ROOTELEM']))
  2670. {
  2671. $rootElem = $optExport['ROOTELEM'];
  2672. }
  2673. $elemTpl = "\n".'<testcase internalid="{{TESTCASE_ID}}" name="{{NAME}}">' . "\n" .
  2674. "\t<node_order><![CDATA[||NODE_ORDER||]]></node_order>\n" .
  2675. "\t<externalid><![CDATA[||EXTERNALID||]]></externalid>\n" .
  2676. "\t<summary><![CDATA[||SUMMARY||]]></summary>\n" .
  2677. "\t<preconditions><![CDATA[||PRECONDITIONS||]]></preconditions>\n" .
  2678. "\t<execution_type><![CDATA[||EXECUTIONTYPE||]]></execution_type>\n" .
  2679. "\t<importance><![CDATA[||IMPORTANCE||]]></importance>\n" .
  2680. "||STEPS||\n" .
  2681. "||KEYWORDS||||CUSTOMFIELDS||||REQUIREMENTS||</testcase>\n";
  2682. // ||yyy||-> tags, {{xxx}} -> attribute
  2683. // tags and attributes receive different treatment on exportDataToXML()
  2684. //
  2685. // each UPPER CASE word in this map KEY, MUST HAVE AN OCCURENCE on $elemTpl
  2686. // value is a key inside $tc_data[0]
  2687. //
  2688. $info = array("{{TESTCASE_ID}}" => "testcase_id",
  2689. "{{NAME}}" => "name",
  2690. "||NODE_ORDER||" => "node_order",
  2691. "||EXTERNALID||" => "tc_external_id",
  2692. "||SUMMARY||" => "summary",
  2693. "||PRECONDITIONS||" => "preconditions",
  2694. "||EXECUTIONTYPE||" => "execution_type",
  2695. "||IMPORTANCE||" => "importance",
  2696. "||STEPS||" => "xmlsteps",
  2697. "||KEYWORDS||" => "xmlkeywords",
  2698. "||CUSTOMFIELDS||" => "xmlcustomfields",
  2699. "||REQUIREMENTS||" => "xmlrequirements");
  2700. $xmlTC = exportDataToXML($tc_data,$rootElem,$elemTpl,$info,$bNoXMLHeader);
  2701. return $xmlTC;
  2702. }
  2703. /*
  2704. function: get_version_exec_assignment
  2705. get information about user that has been assigned
  2706. test case version for execution on a testplan
  2707. args : tcversion_id: test case version id
  2708. tplan_id
  2709. returns: map
  2710. key: tcversion_id
  2711. value: map with following keys:
  2712. tcversion_id
  2713. feature_id: identifies row on table testplan_tcversions.
  2714. user_id: user that has reponsibility to execute this tcversion_id.
  2715. null/empty string is nodoby has been assigned
  2716. type type of assignment.
  2717. 1 -> testcase_execution.
  2718. See assignment_types tables for updated information
  2719. about other types of assignemt available.
  2720. status assignment status
  2721. See assignment_status tables for updated information.
  2722. 1 -> open
  2723. 2 -> closed
  2724. 3 -> completed
  2725. 4 -> todo_urgent
  2726. 5 -> todo
  2727. assigner_id: who has assigned execution to user_id.
  2728. */
  2729. function get_version_exec_assignment($tcversion_id,$tplan_id)
  2730. {
  2731. $sql = "SELECT T.tcversion_id AS tcversion_id,T.id AS feature_id,T.platform_id, " .
  2732. " UA.user_id,UA.type,UA.status,UA.assigner_id ".
  2733. " FROM {$this->tables['testplan_tcversions']} T " .
  2734. " LEFT OUTER JOIN {$this->tables['user_assignments']} UA ON UA.feature_id = T.id " .
  2735. " WHERE T.testplan_id={$tplan_id} " .
  2736. " AND T.tcversion_id = {$tcversion_id} " .
  2737. " AND (UA.type=" . $this->assignment_types['testcase_execution']['id'] .
  2738. " OR UA.type IS NULL) ";
  2739. // $recordset = $this->db->fetchRowsIntoMap($sql,'tcversion_id');
  2740. $recordset = $this->db->fetchMapRowsIntoMap($sql,'tcversion_id','platform_id');
  2741. return $recordset;
  2742. }
  2743. /**
  2744. * get_assigned_to_user()
  2745. * Given a user and a tesplan id, get all test case version id linked to
  2746. * test plan, that has been assigned for execution to user.
  2747. *
  2748. * @param int user_id
  2749. *
  2750. * @param mixed tproject_id list of test project id to search.
  2751. * int or array
  2752. *
  2753. * @param array [tplan_id] list of test plan id to search.
  2754. * null => all test plans
  2755. *
  2756. * @param object [options] options->mode='full_path'
  2757. * testcase name full path will be returned
  2758. * Only available when acces_keys ='testplan_testcase'
  2759. *
  2760. * options->access_keys
  2761. * possible values: 'testplan_testcase','testcase_testplan'
  2762. * changes access key in result map of maps.
  2763. * if not defined or null -> 'testplan_testcase'
  2764. *
  2765. * @param object [filters] 'tplan_status' => 'active','inactive','all'
  2766. *
  2767. *
  2768. * @return map key: (test plan id or test case id depending on options->access_keys,
  2769. * default is test plan).
  2770. *
  2771. * value: map key: (test case id or test plan id depending on options->access_keys,
  2772. * default is test case).
  2773. * value:
  2774. *
  2775. * @since 20090131 - franciscom
  2776. *
  2777. * @internal revision
  2778. * 20100813 - asimon - deactivated last slash on full path
  2779. * to remove it from test suite name in "tc assigned to user" tables
  2780. * 20100802 - asimon - 3647
  2781. * 20100731 - asimon - added option to load assignments for all users,
  2782. * added user_id, build_id, platform_id to SELECT part of statement
  2783. * 20100722 - asimon - BUGID 3406 - modified statement to get build name
  2784. * 20100712 - asimon - inserted missing semicolon
  2785. * 20100708 - franciscom - BUGID 3575 - add plaftorm in output set
  2786. */
  2787. function get_assigned_to_user($user_id,$tproject_id,$tplan_id=null,$options=null, $filters=null)
  2788. {
  2789. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  2790. $my['filters'] = array( 'tplan_status' => 'all');
  2791. $my['filters'] = array_merge($my['filters'], (array)$filters);
  2792. // to load assignments for all users OR one given user
  2793. $user_sql = ($user_id != TL_USER_ANYBODY) ? " AND UA.user_id = {$user_id} " : "";
  2794. $filters = "";
  2795. $has_options=!is_null($options);
  2796. $access_key=array('testplan_id','testcase_id');
  2797. $sql="/* $debugMsg */ SELECT TPROJ.id as testproject_id,TPTCV.testplan_id,TPTCV.tcversion_id, " .
  2798. " TCV.version,TCV.tc_external_id, NHTC.id AS testcase_id, NHTC.name, TPROJ.prefix, " .
  2799. " UA.creation_ts ,UA.deadline_ts, UA.user_id as user_id, " .
  2800. " COALESCE(PLAT.name,'') AS platform_name, COALESCE(PLAT.id,0) AS platform_id, " .
  2801. " (TPTCV.urgency * TCV.importance) AS priority, BUILDS.name as build_name, " .
  2802. " BUILDS.id as build_id " .
  2803. " FROM {$this->tables['user_assignments']} UA " .
  2804. " JOIN {$this->tables['testplan_tcversions']} TPTCV ON TPTCV.id = UA.feature_id " .
  2805. " JOIN {$this->tables['tcversions']} TCV ON TCV.id=TPTCV.tcversion_id " .
  2806. " JOIN {$this->tables['nodes_hierarchy']} NHTCV ON NHTCV.id = TCV.id " .
  2807. " JOIN {$this->tables['nodes_hierarchy']} NHTC ON NHTC.id = NHTCV.parent_id " .
  2808. " JOIN {$this->tables['nodes_hierarchy']} NHTPLAN ON NHTPLAN.id=TPTCV.testplan_id " .
  2809. " JOIN {$this->tables['testprojects']} TPROJ ON TPROJ.id = NHTPLAN.parent_id " .
  2810. " JOIN {$this->tables['testplans']} TPLAN ON TPLAN.id = TPTCV.testplan_id " .
  2811. " JOIN {$this->tables['builds']} BUILDS ON BUILDS.id = UA.build_id " .
  2812. " LEFT OUTER JOIN {$this->tables['platforms']} PLAT ON PLAT.id = TPTCV.platform_id " .
  2813. " WHERE UA.type={$this->assignment_types['testcase_execution']['id']} " .
  2814. //" AND UA.user_id = {$user_id} " .
  2815. " {$user_sql} " .
  2816. " AND TPROJ.id IN (" . implode(',', array($tproject_id)) .") " ;
  2817. if( !is_null($tplan_id) )
  2818. {
  2819. $filters .= " AND TPTCV.testplan_id IN (" . implode(',',$tplan_id) . ") ";
  2820. }
  2821. // BUGID 3647
  2822. if (isset($my['filters']['build_id'])) {
  2823. $filters .= " AND UA.build_id = {$my['filters']['build_id']} ";
  2824. }
  2825. switch($my['filters']['tplan_status'])
  2826. {
  2827. case 'all':
  2828. break;
  2829. case 'active':
  2830. $filters .= " AND TPLAN.active = 1 ";
  2831. break;
  2832. case 'inactive':
  2833. $filters .= " AND TPLAN.active = 0 ";
  2834. break;
  2835. }
  2836. $sql .= $filters;
  2837. if( $has_options && isset($options->access_keys) )
  2838. {
  2839. switch($options->access_keys)
  2840. {
  2841. case 'testplan_testcase':
  2842. break;
  2843. case 'testcase_testplan':
  2844. $access_key=array('testcase_id','testplan_id');
  2845. break;
  2846. }
  2847. }
  2848. $rs=$this->db->fetchMapRowsIntoMap($sql,$access_key[0],$access_key[1],database::CUMULATIVE);
  2849. if( $has_options && !is_null($rs))
  2850. {
  2851. if( isset($options->mode) )
  2852. {
  2853. switch($options->mode)
  2854. {
  2855. case 'full_path':
  2856. if( !isset($options->access_keys) ||
  2857. (is_null($options->access_keys) || $options->access_keys='testplan_testcase') )
  2858. {
  2859. $tcaseSet=null;
  2860. $main_keys = array_keys($rs);
  2861. foreach($main_keys as $maccess_key)
  2862. {
  2863. $sec_keys = array_keys($rs[$maccess_key]);
  2864. foreach($sec_keys as $saccess_key)
  2865. {
  2866. // is enough I process first element
  2867. $item = $rs[$maccess_key][$saccess_key][0];
  2868. if(!isset($tcaseSet[$item['testcase_id']]))
  2869. {
  2870. $tcaseSet[$item['testcase_id']]=$item['testcase_id'];
  2871. }
  2872. }
  2873. }
  2874. $path_info = $this->tree_manager->get_full_path_verbose($tcaseSet);
  2875. // Remove test project piece and convert to string
  2876. $flat_path=null;
  2877. foreach($path_info as $tcase_id => $pieces)
  2878. {
  2879. unset($pieces[0]);
  2880. // 20100813 - asimon - deactivated last slash on path
  2881. // to remove it from test suite name in "tc assigned to user" tables
  2882. $flat_path[$tcase_id]=implode('/',$pieces);
  2883. }
  2884. $main_keys = array_keys($rs);
  2885. foreach($main_keys as $idx)
  2886. {
  2887. $sec_keys = array_keys($rs[$idx]);
  2888. foreach($sec_keys as $jdx)
  2889. {
  2890. $third_keys = array_keys($rs[$idx][$jdx]);
  2891. foreach($third_keys as $tdx)
  2892. {
  2893. $fdx = $rs[$idx][$jdx][$tdx]['testcase_id'];
  2894. $rs[$idx][$jdx][$tdx]['tcase_full_path']=$flat_path[$fdx];
  2895. }
  2896. }
  2897. }
  2898. }
  2899. break;
  2900. }
  2901. }
  2902. }
  2903. return $rs;
  2904. }
  2905. /*
  2906. function: update_active_status
  2907. args : id: testcase id
  2908. tcversion_id
  2909. active_status: 1 -> active / 0 -> inactive
  2910. returns: 1 -> everything ok.
  2911. 0 -> some error
  2912. */
  2913. function update_active_status($id,$tcversion_id,$active_status)
  2914. {
  2915. $sql = " UPDATE {$this->tables['tcversions']} tcversions SET active={$active_status}" .
  2916. " WHERE tcversions.id = {$tcversion_id}";
  2917. $result = $this->db->exec_query($sql);
  2918. return $result ? 1: 0;
  2919. }
  2920. /*
  2921. function: update_order
  2922. args : id: testcase id
  2923. order
  2924. returns: -
  2925. */
  2926. function update_order($id,$order)
  2927. {
  2928. $result=$this->tree_manager->change_order_bulk(array($order => $id));
  2929. return $result ? 1: 0;
  2930. }
  2931. /*
  2932. function: update_external_id
  2933. args : id: testcase id
  2934. external_id
  2935. returns: -
  2936. */
  2937. function update_external_id($id,$external_id)
  2938. {
  2939. $sql="UPDATE {$this->tables['tcversions']} " .
  2940. "SET tc_external_id={$external_id} " .
  2941. "WHERE id IN ( SELECT id FROM {$this->tables['nodes_hierarchy']} WHERE parent_id={$id} ) ";
  2942. $result=$this->db->exec_query($sql);
  2943. return $result ? 1: 0;
  2944. }
  2945. /**
  2946. * Copy attachments from source testcase to target testcase
  2947. *
  2948. **/
  2949. function copy_attachments($source_id,$target_id)
  2950. {
  2951. $this->attachmentRepository->copyAttachments($source_id,$target_id,$this->attachmentTableName);
  2952. }
  2953. /**
  2954. * copyReqAssignmentTo
  2955. * copy requirement assignments for $from test case id to $to test case id
  2956. *
  2957. * mappings is only useful when source_id and target_id do not belong to same Test Project.
  2958. * Because keywords are defined INSIDE a Test Project, ID will be different for same keyword
  2959. * in a different Test Project.
  2960. *
  2961. */
  2962. function copyReqAssignmentTo($from,$to,$mappings)
  2963. {
  2964. static $req_mgr;
  2965. if( is_null($req_mgr) )
  2966. {
  2967. $req_mgr=new requirement_mgr($this->db);
  2968. }
  2969. $itemSet=$req_mgr->get_all_for_tcase($from);
  2970. if( !is_null($itemSet) )
  2971. {
  2972. $loop2do=count($itemSet);
  2973. for($idx=0; $idx < $loop2do; $idx++)
  2974. {
  2975. if( isset($mappings[$itemSet[$idx]['id']]) )
  2976. {
  2977. $items[$idx]=$mappings[$itemSet[$idx]['id']];
  2978. }
  2979. else
  2980. {
  2981. $items[$idx]=$itemSet[$idx]['id'];
  2982. }
  2983. }
  2984. $req_mgr->assign_to_tcase($items,$to);
  2985. }
  2986. }
  2987. /**
  2988. *
  2989. *
  2990. */
  2991. private function getShowViewerActions($mode)
  2992. {
  2993. // fine grain control of operations
  2994. $viewerActions= new stdClass();
  2995. $viewerActions->edit='no';
  2996. $viewerActions->delete_testcase='no';
  2997. $viewerActions->delete_version='no';
  2998. $viewerActions->deactivate='no';
  2999. $viewerActions->create_new_version='no';
  3000. $viewerActions->export='no';
  3001. $viewerActions->move='no';
  3002. $viewerActions->copy='no';
  3003. $viewerActions->add2tplan='no';
  3004. switch ($mode)
  3005. {
  3006. case 'editOnExec':
  3007. $viewerActions->edit='yes';
  3008. // 20100530 - franciscom - $viewerActions->create_new_version='yes';
  3009. break;
  3010. case 'editDisabled':
  3011. break;
  3012. default:
  3013. foreach($viewerActions as $key => $value)
  3014. {
  3015. $viewerActions->$key='yes';
  3016. }
  3017. break;
  3018. }
  3019. return $viewerActions;
  3020. }
  3021. /**
  3022. * given an executio id delete execution and related data.
  3023. *
  3024. */
  3025. function deleteExecution($executionID)
  3026. {
  3027. $whereClause = " WHERE execution_id = {$executionID} ";
  3028. $sql = array("DELETE FROM {$this->tables['execution_bugs']} {$whereClause} ",
  3029. "DELETE FROM {$this->tables['cfield_execution_values']} {$whereClause} ",
  3030. "DELETE FROM {$this->tables['executions']} WHERE id = {$executionID}" );
  3031. foreach ($sql as $the_stm)
  3032. {
  3033. $result = $this->db->exec_query($the_stm);
  3034. if (!$result)
  3035. {
  3036. break;
  3037. }
  3038. }
  3039. }
  3040. // ---------------------------------------------------------------------------------------
  3041. // Custom field related functions
  3042. // ---------------------------------------------------------------------------------------
  3043. /*
  3044. function: get_linked_cfields_at_design
  3045. Get all linked custom fields that must be available at design time.
  3046. Remember that custom fields are defined at system wide level, and
  3047. has to be linked to a testproject, in order to be used.
  3048. args: id: testcase id
  3049. [parent_id]: node id of parent testsuite of testcase.
  3050. need to understand to which testproject the testcase belongs.
  3051. this information is vital, to get the linked custom fields.
  3052. Presence /absence of this value changes starting point
  3053. on procedure to build tree path to get testproject id.
  3054. null -> use testcase_id as starting point.
  3055. !is_null -> use this value as starting point.
  3056. [$filters]:default: null
  3057. map with keys:
  3058. [show_on_execution]: default: null
  3059. 1 -> filter on field show_on_execution=1
  3060. include ONLY custom fields that can be viewed
  3061. while user is execution testcases.
  3062. 0 or null -> don't filter
  3063. [show_on_testplan_design]: default: null
  3064. 1 -> filter on field show_on_testplan_design=1
  3065. include ONLY custom fields that can be viewed
  3066. while user is designing test plan.
  3067. 0 or null -> don't filter
  3068. [location] new concept used to define on what location on screen
  3069. custom field will be designed.
  3070. Initally used with CF available for Test cases, to
  3071. implement pre-requisites.
  3072. null => no filtering
  3073. More comments/instructions on cfield_mgr->get_linked_cfields_at_design()
  3074. returns: map/hash
  3075. key: custom field id
  3076. value: map with custom field definition and value assigned for choosen testcase,
  3077. with following keys:
  3078. id: custom field id
  3079. name
  3080. label
  3081. type: custom field type
  3082. possible_values: for custom field
  3083. default_value
  3084. valid_regexp
  3085. length_min
  3086. length_max
  3087. show_on_design
  3088. enable_on_design
  3089. show_on_execution
  3090. enable_on_execution
  3091. display_order
  3092. value: value assigned to custom field for this testcase
  3093. null if for this testcase custom field was never edited.
  3094. node_id: testcase id
  3095. null if for this testcase, custom field was never edited.
  3096. rev :
  3097. 20070302 - check for $id not null, is not enough, need to check is > 0
  3098. */
  3099. function get_linked_cfields_at_design($id,$parent_id=null,$filters=null,$tproject_id = null)
  3100. {
  3101. if (!$tproject_id)
  3102. {
  3103. $tproject_id = $this->getTestProjectFromTestCase($id,$parent_id);
  3104. }
  3105. $cf_map = $this->cfield_mgr->get_linked_cfields_at_design($tproject_id,
  3106. self::ENABLED,$filters,'testcase',$id);
  3107. return $cf_map;
  3108. }
  3109. /*
  3110. function: getTestProjectFromTestCase
  3111. args: id: testcase id
  3112. [parent_id]: node id of parent testsuite of testcase.
  3113. need to understand to which testproject the testcase belongs.
  3114. this information is vital, to get the linked custom fields.
  3115. Presence /absence of this value changes starting point
  3116. on procedure to build tree path to get testproject id.
  3117. null -> use testcase_id as starting point.
  3118. !is_null -> use this value as starting point.
  3119. */
  3120. function getTestProjectFromTestCase($id,$parent_id)
  3121. {
  3122. $the_path = $this->tree_manager->get_path( (!is_null($id) && $id > 0) ? $id : $parent_id);
  3123. $path_len = count($the_path);
  3124. $tproject_id = ($path_len > 0)? $the_path[0]['parent_id'] : $parent_id;
  3125. return $tproject_id;
  3126. }
  3127. /*
  3128. function: html_table_of_custom_field_inputs
  3129. Return html code, implementing a table with custom fields labels
  3130. and html inputs, for choosen testcase.
  3131. Used to manage user actions on custom fields values.
  3132. args: $id: IMPORTANT:
  3133. we can receive 0 in this arguments and THERE IS NOT A problem
  3134. if parent_id arguments has a value.
  3135. Because argument id or parent_id are used to understand what is
  3136. testproject where test case belong, in order to get custom fields
  3137. assigned/linked to test project.
  3138. [parent_id]: node id of parent testsuite of testcase.
  3139. need to undertad to which testproject the testcase belongs.
  3140. this information is vital, to get the linked custom fields.
  3141. Presence /absence of this value changes starting point
  3142. on procedure to build tree path to get testproject id.
  3143. null -> use testcase_id as starting point.
  3144. !is_null -> use this value as starting point.
  3145. [$scope]: 'design' -> use custom fields that can be used at design time (specification)
  3146. 'execution' -> use custom fields that can be used at execution time.
  3147. [$name_suffix]: must start with '_' (underscore).
  3148. Used when we display in a page several items
  3149. example:
  3150. during test case execution, several test cases
  3151. during testplan design (assign test case to testplan).
  3152. that have the same custom fields.
  3153. In this kind of situation we can use the item id as name suffix.
  3154. [link_id]: default null
  3155. scope='testplan_design'.
  3156. link_id=testplan_tcversions.id this value is also part of key
  3157. to access CF values on new table that hold values assigned
  3158. to CF used on the 'tesplan_design' scope.
  3159. scope='execution'
  3160. link_id=execution id
  3161. [tplan_id]: default null
  3162. used when scope='execution' and YOU NEED to get input with value
  3163. related to link_id
  3164. [tproject_id]: default null
  3165. used to speedup feature when this value is available.
  3166. returns: html string
  3167. rev: 20080811 - franciscom - BUGID 1650 (REQ)
  3168. */
  3169. function html_table_of_custom_field_inputs($id,$parent_id=null,$scope='design',$name_suffix='',
  3170. $link_id=null,$tplan_id=null,
  3171. $tproject_id = null,$filters=null)
  3172. {
  3173. $cf_smarty = '';
  3174. // BUGID 1650
  3175. $cf_scope=trim($scope);
  3176. $method_name='get_linked_cfields_at_' . $cf_scope;
  3177. switch($cf_scope)
  3178. {
  3179. case 'testplan_design':
  3180. $cf_map = $this->$method_name($id,$parent_id,null,$link_id,null,$tproject_id);
  3181. break;
  3182. case 'design':
  3183. // added $filters
  3184. $cf_map = $this->$method_name($id,$parent_id,$filters,$tproject_id);
  3185. break;
  3186. case 'execution':
  3187. $cf_map = $this->$method_name($id,$parent_id,null,$link_id,$tplan_id,$tproject_id);
  3188. break;
  3189. }
  3190. if(!is_null($cf_map))
  3191. {
  3192. $cf_smarty = "<table>";
  3193. foreach($cf_map as $cf_id => $cf_info)
  3194. {
  3195. // true => do not create input in audit log
  3196. $label=str_replace(TL_LOCALIZE_TAG,'',lang_get($cf_info['label'],null,true));
  3197. // Want to give an html id to <td> used as labelHolder, to use it in Javascript
  3198. // logic to validate CF content
  3199. $cf_html_string = $this->cfield_mgr->string_custom_field_input($cf_info,$name_suffix);
  3200. // extract input html id
  3201. $dummy = explode(' ', strstr($cf_html_string,'id="custom_field_'));
  3202. $td_label_id = str_replace('id="', 'id="label_', $dummy[0]);
  3203. $cf_smarty .= "<tr><td class=\"labelHolder\" {$td_label_id}>" . htmlspecialchars($label) .
  3204. ":</td><td>{$cf_html_string}</td></tr>\n";
  3205. }
  3206. $cf_smarty .= "</table>";
  3207. }
  3208. return $cf_smarty;
  3209. }
  3210. /*
  3211. function: html_table_of_custom_field_values
  3212. Return html code, implementing a table with custom fields labels
  3213. and custom fields values, for choosen testcase.
  3214. You can think of this function as some sort of read only version
  3215. of html_table_of_custom_field_inputs.
  3216. args: $id: Very Important!!!
  3217. scope='design' -> this is a testcase id
  3218. scope='execution' -> this is a testcase VERSION id
  3219. scope='testplan_design' -> this is a testcase VERSION id
  3220. [$scope]: 'design' -> use custom fields that can be used at design time (specification)
  3221. 'execution' -> use custom fields that can be used at execution time.
  3222. 'testplan_design'
  3223. [$filters]:default: null
  3224. map with keys:
  3225. [show_on_execution]: default: null
  3226. 1 -> filter on field show_on_execution=1
  3227. include ONLY custom fields that can be viewed
  3228. while user is execution testcases.
  3229. 0 or null -> don't filter
  3230. [show_on_testplan_design]: default: null
  3231. 1 -> filter on field show_on_testplan_design=1
  3232. include ONLY custom fields that can be viewed
  3233. while user is designing test plan.
  3234. 0 or null -> don't filter
  3235. [location] new concept used to define on what location on screen
  3236. custom field will be designed.
  3237. Initally used with CF available for Test cases, to
  3238. implement pre-requisites.
  3239. null => no filtering
  3240. More comments/instructions on cfield_mgr->get_linked_cfields_at_design()
  3241. [$execution_id]: null -> get values for all executions availables for testcase
  3242. !is_null -> only get values or this execution_id
  3243. [$testplan_id]: null -> get values for any tesplan to with testcase is linked
  3244. !is_null -> get values only for this testplan.
  3245. [$tproject_id]
  3246. [$formatOptions]
  3247. [$link_id]: default null
  3248. used only when scope='testplan_design'.
  3249. link_id=testplan_tcversions.id this value is also part of key
  3250. to access CF values on new table that hold values assigned
  3251. to CF used on the 'tesplan_design' scope.
  3252. returns: html string
  3253. */
  3254. function html_table_of_custom_field_values($id,$scope='design',$filters=null,$execution_id=null,
  3255. $testplan_id=null,$tproject_id = null,
  3256. $formatOptions=null,$link_id=null)
  3257. {
  3258. $td_style='class="labelHolder"' ;
  3259. $add_table=true;
  3260. $table_style='';
  3261. if( !is_null($formatOptions) )
  3262. {
  3263. $td_style=isset($formatOptions['td_css_style']) ? $formatOptions['td_css_style'] : $td_style;
  3264. $add_table=isset($formatOptions['add_table']) ? $formatOptions['add_table'] : true;
  3265. $table_style=isset($formatOptions['table_css_style']) ? $formatOptions['table_css_style'] : $table_style;
  3266. }
  3267. $cf_smarty = '';
  3268. $location=null; // no filter
  3269. $filterKey='location';
  3270. if( isset($filters[$filterKey]) && !is_null($filters[$filterKey]) )
  3271. {
  3272. $location = $filters[$filterKey];
  3273. }
  3274. switch($scope)
  3275. {
  3276. case 'design':
  3277. $cf_map = $this->get_linked_cfields_at_design($id,null,$filters,$tproject_id);
  3278. break;
  3279. case 'testplan_design':
  3280. $cf_map = $this->get_linked_cfields_at_testplan_design($id,null,$filters,$link_id,
  3281. $testplan_id,$tproject_id);
  3282. break;
  3283. case 'execution':
  3284. $cf_map = $this->get_linked_cfields_at_execution($id,null,$filters,$execution_id,
  3285. $testplan_id,$tproject_id,$location);
  3286. break;
  3287. }
  3288. if(!is_null($cf_map))
  3289. {
  3290. foreach($cf_map as $cf_id => $cf_info)
  3291. {
  3292. // if user has assigned a value, then node_id is not null
  3293. if(isset($cf_info['node_id']) )
  3294. {
  3295. // true => do not create input in audit log
  3296. $label=str_replace(TL_LOCALIZE_TAG,'',lang_get($cf_info['label'],null,true));
  3297. $cf_smarty .= "<tr><td {$td_style}> " .
  3298. htmlspecialchars($label) . ":</td><td>" .
  3299. $this->cfield_mgr->string_custom_field_value($cf_info,$id) .
  3300. "</td></tr>\n";
  3301. }
  3302. }
  3303. if((trim($cf_smarty) != "") && $add_table)
  3304. {
  3305. $cf_smarty = "<table {$table_style}>" . $cf_smarty . "</table>";
  3306. }
  3307. }
  3308. return $cf_smarty;
  3309. } // function end
  3310. /*
  3311. function: get_linked_cfields_at_execution
  3312. args: $id
  3313. [$parent_id]
  3314. [$show_on_execution]: default: null
  3315. 1 -> filter on field show_on_execution=1
  3316. 0 or null -> don't filter
  3317. //@TODO - 20090718 - franciscom
  3318. // this filter has any sense ?
  3319. // review and remove if needed
  3320. [$execution_id]: null -> get values for all executions availables for testcase
  3321. !is_null -> only get values or this execution_id
  3322. [$testplan_id]: null -> get values for any tesplan to with testcase is linked
  3323. !is_null -> get values only for this testplan.
  3324. [$tproject_id]:
  3325. returns: hash
  3326. key: custom field id
  3327. value: map with custom field definition, with keys:
  3328. id: custom field id
  3329. name
  3330. label
  3331. type
  3332. possible_values
  3333. default_value
  3334. valid_regexp
  3335. length_min
  3336. length_max
  3337. show_on_design
  3338. enable_on_design
  3339. show_on_execution
  3340. enable_on_execution
  3341. display_order
  3342. */
  3343. function get_linked_cfields_at_execution($id,$parent_id=null,$show_on_execution=null,
  3344. $execution_id=null,$testplan_id=null,
  3345. $tproject_id = null, $location=null)
  3346. {
  3347. $thisMethod=__FUNCTION__;
  3348. if (!$tproject_id)
  3349. {
  3350. $tproject_id = $this->getTestProjectFromTestCase($id,$parent_id);
  3351. }
  3352. // VERY IMPORTANT WARNING:
  3353. // I'm setting node type to test case, but $id is the tcversion_id, because
  3354. // execution data is related to tcversion NO testcase
  3355. //
  3356. $cf_map = $this->cfield_mgr->$thisMethod($tproject_id,self::ENABLED,'testcase',
  3357. $id,$execution_id,$testplan_id,'id',
  3358. $location);
  3359. return $cf_map;
  3360. }
  3361. /*
  3362. function: copy_cfields_design_values
  3363. Get all cfields linked to any testcase of this testproject
  3364. with the values presents for $from_id, testcase we are using as
  3365. source for our copy.
  3366. args: from_id: source testcase id
  3367. to_id: target testcase id
  3368. returns: -
  3369. */
  3370. function copy_cfields_design_values($from_id,$to_id)
  3371. {
  3372. // Get all cfields linked to any testcase of this test project
  3373. // with the values presents for $from_id, testcase we are using as
  3374. // source for our copy
  3375. $cfmap_from=$this->get_linked_cfields_at_design($from_id);
  3376. $cfield=null;
  3377. if( !is_null($cfmap_from) )
  3378. {
  3379. foreach($cfmap_from as $key => $value)
  3380. {
  3381. $cfield[$key]=array("type_id" => $value['type'], "cf_value" => $value['value']);
  3382. }
  3383. }
  3384. $this->cfield_mgr->design_values_to_db($cfield,$to_id,null,'tcase_copy_cfields');
  3385. }
  3386. /*
  3387. function: get_linked_cfields_at_testplan_design
  3388. args: $id
  3389. [$parent_id]
  3390. [$filters]:default: null
  3391. map with keys:
  3392. [show_on_execution]: default: null
  3393. 1 -> filter on field show_on_execution=1
  3394. include ONLY custom fields that can be viewed
  3395. while user is execution testcases.
  3396. 0 or null -> don't filter
  3397. [show_on_testplan_design]: default: null
  3398. 1 -> filter on field show_on_testplan_design=1
  3399. include ONLY custom fields that can be viewed
  3400. while user is designing test plan.
  3401. 0 or null -> don't filter
  3402. More comments/instructions on cfield_mgr->get_linked_cfields_at_design()
  3403. [$link_id]:
  3404. [$testplan_id]: null -> get values for any tesplan to with testcase is linked
  3405. !is_null -> get values only for this testplan.
  3406. returns: hash
  3407. key: custom field id
  3408. value: map with custom field definition, with keys:
  3409. id: custom field id
  3410. name
  3411. label
  3412. type
  3413. possible_values
  3414. default_value
  3415. valid_regexp
  3416. length_min
  3417. length_max
  3418. show_on_design
  3419. enable_on_design
  3420. show_on_execution
  3421. enable_on_execution
  3422. display_order
  3423. */
  3424. function get_linked_cfields_at_testplan_design($id,$parent_id=null,$filters=null,
  3425. $link_id=null,$testplan_id=null,$tproject_id = null)
  3426. {
  3427. if (!$tproject_id)
  3428. {
  3429. $tproject_id = $this->getTestProjectFromTestCase($id,$parent_id);
  3430. }
  3431. // Warning:
  3432. // I'm setting node type to test case, but $id is the tcversion_id, because
  3433. // link data is related to tcversion NO testcase
  3434. //
  3435. $cf_map = $this->cfield_mgr->get_linked_cfields_at_testplan_design($tproject_id,self::ENABLED,'testcase',
  3436. $id,$link_id,$testplan_id);
  3437. return $cf_map;
  3438. }
  3439. /**
  3440. * returns map with key: verbose location (see custom field class $locations
  3441. * value: array with fixed key 'location'
  3442. * value: location code
  3443. *
  3444. */
  3445. function buildCFLocationMap()
  3446. {
  3447. $ret = $this->cfield_mgr->buildLocationMap('testcase');
  3448. return $ret;
  3449. }
  3450. /**
  3451. * given a set of test cases, will return a map with
  3452. * test suites name that form test case path to root test suite.
  3453. *
  3454. * example:
  3455. *
  3456. * communication devices [ID 4]
  3457. * |__ Subspace channels [ID 20]
  3458. * |
  3459. * |__ TestCase100
  3460. * |
  3461. * |__ short range devices [ID 21]
  3462. * |__ TestCase1
  3463. * |__ TestCase2
  3464. *
  3465. * if test case set: TestCase100,TestCase1
  3466. *
  3467. * 4 Communications
  3468. * 20 Communications/Subspace channels
  3469. * 21 Communications/Subspace channels/short range devices
  3470. *
  3471. *
  3472. * returns map with key: test suite id
  3473. * value: test suite path to root
  3474. *
  3475. *
  3476. */
  3477. function getPathLayered($tcaseSet)
  3478. {
  3479. $xtree=null;
  3480. foreach($tcaseSet as $item)
  3481. {
  3482. $path_info = $this->tree_manager->get_path($item);
  3483. $testcase = end($path_info);
  3484. // This check is useful when you have several test cases with same parent test suite
  3485. if( !isset($xtree[$testcase['parent_id']]['value']) )
  3486. {
  3487. $level=0;
  3488. foreach($path_info as $elem)
  3489. {
  3490. $level++;
  3491. $prefix = isset($xtree[$elem['parent_id']]['value']) ? ($xtree[$elem['parent_id']]['value'] . '/') : '';
  3492. if( $elem['node_table'] == 'testsuites' )
  3493. {
  3494. $xtree[$elem['id']]['value'] = $prefix . $elem['name'];
  3495. $xtree[$elem['id']]['level']=$level;
  3496. }
  3497. }
  3498. }
  3499. }
  3500. return $xtree;
  3501. } // getPathLayered($tcaseSet)
  3502. /**
  3503. *
  3504. *
  3505. */
  3506. function getPathTopSuite($tcaseSet)
  3507. {
  3508. $xtmas=null;
  3509. foreach($tcaseSet as $item)
  3510. {
  3511. $path_info = $this->tree_manager->get_path($item);
  3512. $top = current($path_info);
  3513. $xtmas[$item] = array( 'name' => $top['name'], 'id' => $top['id']);
  3514. }
  3515. return $xtmas;
  3516. } // getPathTopSuite($tcaseSet)
  3517. /*
  3518. function: getByPathName
  3519. pathname format
  3520. Test Project Name::SuiteName::SuiteName::...::Test case name
  3521. args: $pathname
  3522. returns: hash
  3523. */
  3524. function getByPathName($pathName,$pathSeparator='::')
  3525. {
  3526. $recordset = null;
  3527. $retval=null;
  3528. // First get root -> test project name and leaf => test case name
  3529. $parts = explode($pathSeparator,$pathName);
  3530. $partsQty = count($parts);
  3531. $tprojectName = $parts[0];
  3532. $tsuiteName = $parts[$partsQty-2];
  3533. $tcaseName = end($parts);
  3534. // get all testcases on test project with this name and parent test suite
  3535. $recordset = $this->get_by_name($tcaseName, $tsuiteName ,$tprojectName);
  3536. if( !is_null($recordset) && count($recordset) > 0 )
  3537. {
  3538. foreach($recordset as $value)
  3539. {
  3540. $dummy = $this->tree_manager->get_full_path_verbose($value['id']);
  3541. $sx = implode($pathSeparator,current($dummy)) . $pathSeparator . $tcaseName;
  3542. if( strcmp($pathName,$sx ) == 0 )
  3543. {
  3544. $retval = $value;
  3545. break;
  3546. }
  3547. }
  3548. }
  3549. return $retval;
  3550. }
  3551. /**
  3552. *
  3553. *
  3554. */
  3555. function buildDirectWebLink($base_href,$id,$tproject_id=null)
  3556. {
  3557. list($external_id,$prefix,$glue,$tc_number) = $this->getExternalID($id,$tproject_id);
  3558. $dl = $base_href . 'linkto.php?tprojectPrefix=' . urlencode($prefix) .
  3559. '&item=testcase&id=' . urlencode($external_id);
  3560. return $dl;
  3561. }
  3562. /**
  3563. *
  3564. *
  3565. */
  3566. function getExternalID($id,$tproject_id=null,$prefix=null)
  3567. {
  3568. static $cfg;
  3569. if( is_null($cfg) )
  3570. {
  3571. $cfg = config_get('testcase_cfg');
  3572. }
  3573. if( is_null($prefix) )
  3574. {
  3575. list($prefix,$root) = $this->getPrefix($id,$tproject_id);
  3576. }
  3577. $info = $this->get_last_version_info($id, array('output' => 'minimun'));
  3578. $external = $info['tc_external_id'];
  3579. $identity = $prefix . $cfg->glue_character . $external;
  3580. return array($identity,$prefix,$cfg->glue_character,$external);
  3581. }
  3582. /**
  3583. * returns just name, tc_external_id, version.
  3584. * this info is normally enough for user feednack.
  3585. *
  3586. * @param int $id test case id
  3587. * @param int $version_id test case version id
  3588. *
  3589. * @return array with one element with keys: name,version,tc_external_id
  3590. */
  3591. function get_basic_info($id,$version_id)
  3592. {
  3593. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3594. $sql = "/* $debugMsg */ " .
  3595. " SELECT NH_TCASE.id, NH_TCASE.name, TCV.version, TCV.tc_external_id " .
  3596. " FROM {$this->tables['nodes_hierarchy']} NH_TCASE " .
  3597. " JOIN {$this->tables['nodes_hierarchy']} NH_TCV ON NH_TCV.parent_id = NH_TCASE.id" .
  3598. " JOIN {$this->tables['tcversions']} TCV ON TCV.id = NH_TCV.id ";
  3599. $where_clause = " WHERE TCV.id = {$version_id} ";
  3600. $where_clause .= (!is_null($id) && $id > 0) ? " AND NH_TCASE .id = {$id} " : "";
  3601. $sql .= $where_clause;
  3602. $result = $this->db->get_recordset($sql);
  3603. return $result;
  3604. }
  3605. /**
  3606. *
  3607. *
  3608. */
  3609. function create_step($tcversion_id,$step_number,$actions,$expected_results,
  3610. $execution_type=TESTCASE_EXECUTION_TYPE_MANUAL)
  3611. {
  3612. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3613. $ret = array();
  3614. // defensive programming
  3615. $dummy = $this->db->prepare_int($execution_type);
  3616. $dummy = (isset($this->execution_types[$dummy])) ? $dummy : TESTCASE_EXECUTION_TYPE_MANUAL;
  3617. $item_id = $this->tree_manager->new_node($tcversion_id,$this->node_types_descr_id['testcase_step']);
  3618. $sql = "/* $debugMsg */ INSERT INTO {$this->tables['tcsteps']} " .
  3619. " (id,step_number,actions,expected_results,execution_type) " .
  3620. " VALUES({$item_id},{$step_number},'" . $this->db->prepare_string($actions) . "','" .
  3621. $this->db->prepare_string($expected_results) . "', " . $this->db->prepare_int($dummy) . ")";
  3622. $result = $this->db->exec_query($sql);
  3623. $ret = array('msg' => 'ok', 'id' => $item_id, 'status_ok' => 1, 'sql' => $sql);
  3624. if (!$result)
  3625. {
  3626. $ret['msg'] = $this->db->error_msg();
  3627. $ret['status_ok']=0;
  3628. $ret['id']=-1;
  3629. }
  3630. return $ret;
  3631. }
  3632. /**
  3633. *
  3634. *
  3635. * @internal Revisions
  3636. * 20100821 - franciscom - added options
  3637. */
  3638. function get_steps($tcversion_id,$step_number=0,$options=null)
  3639. {
  3640. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3641. $my['options'] = array( 'fields2get' => '*', 'accessKey' => null);
  3642. $my['options'] = array_merge($my['options'], (array)$options);
  3643. $step_filter = $step_number > 0 ? " AND step_number = {$step_number} " : "";
  3644. $safe_tcversion_id = $this->db->prepare_int($tcversion_id);
  3645. $sql = "/* $debugMsg */ " .
  3646. " SELECT TCSTEPS.{$my['options']['fields2get']} " .
  3647. " FROM {$this->tables['tcsteps']} TCSTEPS " .
  3648. " JOIN {$this->tables['nodes_hierarchy']} NH_STEPS " .
  3649. " ON NH_STEPS.id = TCSTEPS.id " .
  3650. " WHERE NH_STEPS.parent_id = {$safe_tcversion_id} {$step_filter} ORDER BY step_number";
  3651. if( is_null($my['options']['accessKey']) )
  3652. {
  3653. $result = $this->db->get_recordset($sql);
  3654. }
  3655. else
  3656. {
  3657. $result = $this->db->fetchRowsIntoMap($sql,$my['options']['accessKey']);
  3658. }
  3659. return $result;
  3660. }
  3661. /**
  3662. *
  3663. *
  3664. */
  3665. function get_step_by_id($step_id)
  3666. {
  3667. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3668. $sql = "/* $debugMsg */ " .
  3669. " SELECT TCSTEPS.* FROM {$this->tables['tcsteps']} TCSTEPS " .
  3670. " JOIN {$this->tables['nodes_hierarchy']} NH_STEPS " .
  3671. " ON NH_STEPS.id = TCSTEPS.id " .
  3672. " WHERE TCSTEPS.id = {$step_id} ";
  3673. $result = $this->db->get_recordset($sql);
  3674. return is_null($result) ? $result : $result[0];
  3675. }
  3676. function get_step_numbers($tcversion_id)
  3677. {
  3678. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3679. $sql = "/* $debugMsg */ " .
  3680. " SELECT TCSTEPS.id, TCSTEPS.step_number FROM {$this->tables['tcsteps']} TCSTEPS " .
  3681. " JOIN {$this->tables['nodes_hierarchy']} NH_STEPS " .
  3682. " ON NH_STEPS.id = TCSTEPS.id " .
  3683. " WHERE NH_STEPS.parent_id = {$tcversion_id} ORDER BY step_number";
  3684. $result = $this->db->fetchRowsIntoMap($sql,'step_number');
  3685. return $result;
  3686. }
  3687. /**
  3688. *
  3689. *
  3690. */
  3691. function get_latest_step_number($tcversion_id)
  3692. {
  3693. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3694. $sql = "/* $debugMsg */ " .
  3695. " SELECT MAX(TCSTEPS.step_number) AS max_step FROM {$this->tables['tcsteps']} TCSTEPS " .
  3696. " JOIN {$this->tables['nodes_hierarchy']} NH_STEPS " .
  3697. " ON NH_STEPS.id = TCSTEPS.id " .
  3698. " WHERE NH_STEPS.parent_id = {$tcversion_id} ";
  3699. $result = $this->db->get_recordset($sql);
  3700. $max_step = (!is_null($result) && isset($result[0]['max_step']) )? $result[0]['max_step'] : 0;
  3701. return $max_step;
  3702. }
  3703. /**
  3704. *
  3705. *
  3706. * @internal Revisions
  3707. * 20100821 - franciscom - $step_id can be an array
  3708. */
  3709. function delete_step_by_id($step_id)
  3710. {
  3711. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3712. $sql = array();
  3713. $whereClause = " WHERE id IN (" . implode(',',(array)$step_id) . ")";
  3714. $sqlSet[] = "/* $debugMsg */ DELETE FROM {$this->tables['tcsteps']} {$whereClause} ";
  3715. $sqlSet[] = "/* $debugMsg */ DELETE FROM {$this->tables['nodes_hierarchy']} {$whereClause} ";
  3716. foreach($sqlSet as $sql)
  3717. {
  3718. $this->db->exec_query($sql);
  3719. }
  3720. }
  3721. /**
  3722. *
  3723. *
  3724. */
  3725. function set_step_number($step_number)
  3726. {
  3727. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3728. foreach($step_number as $step_id => $value)
  3729. {
  3730. $sql = "/* $debugMsg */ UPDATE {$this->tables['tcsteps']} TC_STEP " .
  3731. " SET step_number = {$value} WHERE TC_STEP.id = {$step_id} ";
  3732. $this->db->exec_query($sql);
  3733. }
  3734. }
  3735. /**
  3736. *
  3737. *
  3738. */
  3739. function update_step($step_id,$step_number,$actions,$expected_results,$execution_type)
  3740. {
  3741. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3742. $ret = array();
  3743. $sql = "/* $debugMsg */ UPDATE {$this->tables['tcsteps']} " .
  3744. " SET step_number=" . $this->db->prepare_int($step_number) . "," .
  3745. " actions='" . $this->db->prepare_string($actions) . "', " .
  3746. " expected_results='" . $this->db->prepare_string($expected_results) . "', " .
  3747. " execution_type = " . $this->db->prepare_int($execution_type) .
  3748. " WHERE id = " . $this->db->prepare_int($step_id);
  3749. $result = $this->db->exec_query($sql);
  3750. $ret = array('msg' => 'ok', 'status_ok' => 1, 'sql' => $sql);
  3751. if (!$result)
  3752. {
  3753. $ret['msg'] = $this->db->error_msg();
  3754. $ret['status_ok']=0;
  3755. }
  3756. return $ret;
  3757. }
  3758. /**
  3759. * get by external id
  3760. *
  3761. */
  3762. function get_by_external($external_id, $parent_id)
  3763. {
  3764. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3765. $recordset = null;
  3766. $sql = "/* $debugMsg */ " .
  3767. " SELECT DISTINCT NH_TCASE.id,NH_TCASE.name,NH_TCASE_PARENT.id AS parent_id," .
  3768. " NH_TCASE_PARENT.name AS tsuite_name, TCV.tc_external_id " .
  3769. " FROM {$this->tables['nodes_hierarchy']} NH_TCASE, " .
  3770. " {$this->tables['nodes_hierarchy']} NH_TCASE_PARENT, " .
  3771. " {$this->tables['nodes_hierarchy']} NH_TCVERSIONS," .
  3772. " {$this->tables['tcversions']} TCV " .
  3773. " WHERE NH_TCVERSIONS.id=TCV.id " .
  3774. " AND NH_TCVERSIONS.parent_id=NH_TCASE.id " .
  3775. " AND NH_TCASE_PARENT.id=NH_TCASE.parent_id " .
  3776. " AND NH_TCASE.node_type_id = {$this->my_node_type} " .
  3777. " AND TCV.tc_external_id=$external_id ";
  3778. $sql .= " AND NH_TCASE_PARENT.id = {$parent_id}" ;
  3779. $recordset = $this->db->fetchRowsIntoMap($sql,'id');
  3780. return $recordset;
  3781. }
  3782. /**
  3783. * for a given set of test cases, search on the ACTIVE version set, and returns for each test case,
  3784. * an map with: the corresponding MAX(version number), oher info
  3785. *
  3786. * @internal Revisions
  3787. * 20100417 - franciscom - added importance on output data
  3788. */
  3789. function get_last_active_version($id,$options=null)
  3790. {
  3791. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3792. $recordset = null;
  3793. $itemSet = implode(',',(array)$id);
  3794. $my['options'] = array( 'max_field' => 'tcversion_id', 'access_key' => 'tcversion_id');
  3795. $my['options'] = array_merge($my['options'], (array)$options);
  3796. switch($my['options']['max_field'])
  3797. {
  3798. case 'version':
  3799. $maxClause = " SELECT MAX(TCV.version) AS version ";
  3800. $selectClause = " SELECT TCV.version AS version ";
  3801. break;
  3802. case 'tcversion_id':
  3803. $maxClause = " SELECT MAX(TCV.id) AS tcversion_id ";
  3804. $selectClause = " SELECT TCV.id AS tcversion_id ";
  3805. break;
  3806. }
  3807. $sql = "/* $debugMsg */ " .
  3808. " {$maxClause}, NH_TCVERSION.parent_id AS testcase_id " .
  3809. " FROM {$this->tables['tcversions']} TCV " .
  3810. " JOIN {$this->tables['nodes_hierarchy']} NH_TCVERSION " .
  3811. " ON NH_TCVERSION.id = TCV.id AND TCV.active=1 " .
  3812. " AND NH_TCVERSION.parent_id IN ({$itemSet}) " .
  3813. " GROUP BY NH_TCVERSION.parent_id " .
  3814. " ORDER BY NH_TCVERSION.parent_id ";
  3815. // $recordset = $this->db->fetchRowsIntoMap($sql,$my['options']['access_key']);
  3816. // HERE FIXED access keys
  3817. $recordset = $this->db->fetchRowsIntoMap($sql,'tcversion_id');
  3818. if( !is_null($recordset) )
  3819. {
  3820. $keySet = implode(',',array_keys($recordset));
  3821. $sql = "/* $debugMsg */ " .
  3822. " {$selectClause}, NH_TCVERSION.parent_id AS testcase_id, " .
  3823. " TCV.version,TCV.execution_type,TCV.importance " .
  3824. " FROM {$this->tables['tcversions']} TCV " .
  3825. " JOIN {$this->tables['nodes_hierarchy']} NH_TCVERSION " .
  3826. " ON NH_TCVERSION.id = TCV.id AND NH_TCVERSION.id IN ({$keySet}) ";
  3827. $recordset = $this->db->fetchRowsIntoMap($sql,$my['options']['access_key']);
  3828. }
  3829. return $recordset;
  3830. }
  3831. /**
  3832. *
  3833. */
  3834. function filter_tcversions_by_exec_type($tcversion_id,$exec_type,$options=null)
  3835. {
  3836. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3837. $recordset = null;
  3838. $itemSet = implode(',',(array)$tcversion_id);
  3839. $my['options'] = array( 'access_key' => 'tcversion_id');
  3840. $my['options'] = array_merge($my['options'], (array)$options);
  3841. $sql = "/* $debugMsg */ " .
  3842. " SELECT TCV.id AS tcversion_id, NH_TCVERSION.parent_id AS testcase_id, TCV.version " .
  3843. " FROM {$this->tables['tcversions']} TCV " .
  3844. " JOIN {$this->tables['nodes_hierarchy']} NH_TCVERSION " .
  3845. " ON NH_TCVERSION.id = TCV.id AND TCV.execution_type={$exec_type}" .
  3846. " AND NH_TCVERSION.id IN ({$itemSet}) ";
  3847. $recordset = $this->db->fetchRowsIntoMap($sql,$my['options']['access_key']);
  3848. return $recordset;
  3849. }
  3850. /**
  3851. *
  3852. *
  3853. */
  3854. function filter_tcversions($tcversion_id,$filters,$options=null)
  3855. {
  3856. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3857. $recordset = null;
  3858. $itemSet = implode(',',(array)$tcversion_id);
  3859. $my['options'] = array( 'access_key' => 'tcversion_id');
  3860. $my['options'] = array_merge($my['options'], (array)$options);
  3861. $sql = "/* $debugMsg */ " .
  3862. " SELECT TCV.id AS tcversion_id, NH_TCVERSION.parent_id AS testcase_id, TCV.version " .
  3863. " FROM {$this->tables['tcversions']} TCV " .
  3864. " JOIN {$this->tables['nodes_hierarchy']} NH_TCVERSION " .
  3865. " ON NH_TCVERSION.id = TCV.id ";
  3866. if ( !is_null($filters) )
  3867. {
  3868. foreach($filters as $key => $value)
  3869. {
  3870. if( !is_null($value) )
  3871. {
  3872. $sql .= " AND TCV.{$key}={$value} ";
  3873. }
  3874. }
  3875. }
  3876. $sql .= " AND NH_TCVERSION.id IN ({$itemSet}) ";
  3877. $recordset = $this->db->fetchRowsIntoMap($sql,$my['options']['access_key']);
  3878. return $recordset;
  3879. }
  3880. /**
  3881. * given a test case version id, the provided steps will be analized in order
  3882. * to update whole steps/expected results structure for test case version.
  3883. * This can result in some step removed, other updated and other new created.
  3884. *
  3885. * @internal Revisions
  3886. * 20100821 - franciscom - needed to fix import feature (BUGID 3634).
  3887. */
  3888. function update_tcversion_steps($tcversion_id,$steps)
  3889. {
  3890. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  3891. // delete all current steps (if any exists)
  3892. // Attention:
  3893. // After addition of test case steps feature, a test case version can be root of
  3894. // a subtree that contains the steps.
  3895. // Remember we are using (at least on Postgres FK => we need to delete in a precise order
  3896. $stepSet = $this->get_steps($tcversion_id,0,array('fields2get' => 'id', 'accessKey' => 'id'));
  3897. if( count($stepSet) > 0 )
  3898. {
  3899. $this->delete_step_by_id(array_keys($stepSet));
  3900. }
  3901. // Now insert steps
  3902. $loop2do = count($steps);
  3903. for($idx=0; $idx < $loop2do; $idx++)
  3904. {
  3905. $this->create_step($tcversion_id,$steps[$idx]['step_number'],$steps[$idx]['actions'],
  3906. $steps[$idx]['expected_results'],$steps[$idx]['execution_type']);
  3907. }
  3908. }
  3909. } // end class
  3910. ?>