PageRenderTime 61ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/functions/testsuite.class.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 1386 lines | 845 code | 127 blank | 414 comment | 57 complexity | 335810b6351a3953f4c31d1d1c1ffb19 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 franciscom
  8. * @copyright 2005-2009, TestLink community
  9. * @version CVS: $Id: testsuite.class.php,v 1.98 2010/07/15 16:45:41 franciscom Exp $
  10. * @link http://www.teamst.org/index.php
  11. *
  12. * @internal Revisions:
  13. *
  14. * 20100602 - franciscom - BUGID 3498 - get_by_name() - missing JOIN
  15. * 20100328 - franciscom - get_by_id() interface and return set changes
  16. * get_children() - new method - contribution - BUGID 2645
  17. * 20100315 - amitkhullar - Added options for CFields for Export.
  18. * 20100227 - franciscom - BUGID 0003233: After test suite edit, display of Test suite do not
  19. * have upload button enabled for attachment
  20. * 20100210 - franciscom - keywords XML export refactored
  21. * 20100209 - franciscom - changes in delete_subtree_objects() call due to BUGID 3147
  22. * 20100204 - franciscom - copy_to() refactoring
  23. * 20100201 - franciscom - get_testcases_deep() - added external_id in output
  24. * 20091122 - franciscom - item template logic refactored - read_file() removed
  25. * 20090821 - franciscom - BUGID 0002781
  26. * 20090801 - franciscom - BUGID 2767 Duplicate testsuite name error message issue
  27. * 20090514 - franciscom - typo bug on html_table_of_custom_field_inputs()
  28. * 20090330 - franciscom - changes in calls to get_linked_cfields_at_design()
  29. * 20090329 - franciscom - html_table_of_custom_field_values()
  30. * 20090209 - franciscom - new method - get_children_testcases()
  31. * 20090208 - franciscom - get_testcases_deep() - interface changes
  32. * 20090207 - franciscom - update() - added duplicated name check
  33. * fixed problem in create() due new argument
  34. * 20090204 - franciscom - exportTestSuiteDataToXML() - added node_order
  35. * 20090106 - franciscom - BUGID - exportTestSuiteDataToXML()
  36. * export custom field values
  37. *
  38. * 20080106 - franciscom - viewer_edit_new() changes to use user templates
  39. * to fill details when creating a new test suites.
  40. * new private method related to this feature:
  41. * _initializeWebEditors(), read_file()
  42. *
  43. * 20080105 - franciscom - copy_to() changed return type
  44. * minor bug on copy_to. (tcversion nodes were not excluded).
  45. *
  46. * 20071111 - franciscom - new method get_subtree();
  47. * 20071101 - franciscom - import_file_types, export_file_types
  48. *
  49. * 20070826 - franciscom - minor fix html_table_of_custom_field_values()
  50. * 20070602 - franciscom - added nt copy on copy_to() method
  51. * using testcase copy_attachment() method.
  52. * added delete attachments.
  53. * added remove of custom field values
  54. * (design) when removing test suite.
  55. *
  56. * 20070501 - franciscom - added localization of custom field labels
  57. * added use of htmlspecialchars() on labels
  58. *
  59. * 20070324 - franciscom - create() interface changes
  60. * get_by_id()changes in result set
  61. *
  62. * 20070204 - franciscom - fixed minor GUI bug on html_table_of_custom_field_inputs()
  63. *
  64. * 20070116 - franciscom - BUGID 543
  65. * 20070102 - franciscom - changes to delete_deep() to support custom fields
  66. * 20061230 - franciscom - custom field management
  67. */
  68. /** include support for attachments */
  69. require_once( dirname(__FILE__) . '/attachments.inc.php');
  70. require_once( dirname(__FILE__) . '/files.inc.php');
  71. /**
  72. * Test Suite CRUD functionality
  73. * @package TestLink
  74. */
  75. class testsuite extends tlObjectWithAttachments
  76. {
  77. const NODE_TYPE_FILTER_OFF=null;
  78. const CHECK_DUPLICATE_NAME=1;
  79. const DONT_CHECK_DUPLICATE_NAME=0;
  80. const DEFAULT_ORDER=0;
  81. const USE_RECURSIVE_MODE = 1;
  82. /** @var database handler */
  83. var $db;
  84. var $tree_manager;
  85. var $node_types_descr_id;
  86. var $node_types_id_descr;
  87. var $my_node_type;
  88. var $cfield_mgr;
  89. private $object_table;
  90. var $import_file_types = array("XML" => "XML");
  91. var $export_file_types = array("XML" => "XML");
  92. // Node Types (NT)
  93. var $nt2exclude=array('testplan' => 'exclude_me',
  94. 'requirement_spec'=> 'exclude_me',
  95. 'requirement'=> 'exclude_me');
  96. var $nt2exclude_children=array('testcase' => 'exclude_my_children',
  97. 'requirement_spec'=> 'exclude_my_children');
  98. /**
  99. * testplan class constructor
  100. *
  101. * @param resource &$db reference to database handler
  102. */
  103. function testsuite(&$db)
  104. {
  105. $this->db = &$db;
  106. $this->tree_manager = new tree($this->db);
  107. $this->node_types_descr_id=$this->tree_manager->get_available_node_types();
  108. $this->node_types_id_descr=array_flip($this->node_types_descr_id);
  109. $this->my_node_type=$this->node_types_descr_id['testsuite'];
  110. $this->cfield_mgr=new cfield_mgr($this->db);
  111. // ATTENTION:
  112. // second argument is used to set $this->attachmentTableName,property that this calls
  113. // get from his parent
  114. // tlObjectWithAttachments::__construct($this->db,'nodes_hierarchy');
  115. parent::__construct($this->db,"nodes_hierarchy");
  116. // Must be setted AFTER call to parent constructor
  117. $this->object_table = $this->tables['testsuites'];
  118. }
  119. /*
  120. returns: map
  121. key: export file type code
  122. value: export file type verbose description
  123. */
  124. function get_export_file_types()
  125. {
  126. return $this->export_file_types;
  127. }
  128. /*
  129. function: get_impor_file_types
  130. getter
  131. args: -
  132. returns: map
  133. key: import file type code
  134. value: import file type verbose description
  135. */
  136. function get_import_file_types()
  137. {
  138. return $this->import_file_types;
  139. }
  140. /*
  141. args :
  142. $parent_id
  143. $name
  144. $details
  145. [$check_duplicate_name]
  146. [$action_on_duplicate_name]
  147. [$order]
  148. returns: hash
  149. $ret['status_ok'] -> 0/1
  150. $ret['msg']
  151. $ret['id'] -> when status_ok=1, id of the new element
  152. rev :
  153. 20070324 - BUGID 710
  154. */
  155. function create($parent_id,$name,$details,$order=null,
  156. $check_duplicate_name=0,
  157. $action_on_duplicate_name='allow_repeat')
  158. {
  159. $prefix_name_for_copy = config_get('prefix_name_for_copy');
  160. if( is_null($order) )
  161. {
  162. $node_order = config_get('treemenu_default_testsuite_order');
  163. }
  164. else
  165. {
  166. $node_order = $order;
  167. }
  168. $name = trim($name);
  169. $ret = array('status_ok' => 1, 'id' => 0, 'msg' => 'ok');
  170. if ($check_duplicate_name)
  171. {
  172. $check = $this->tree_manager->nodeNameExists($name,$this->my_node_type,null,$parent_id);
  173. if( $check['status'] == 1)
  174. {
  175. if ($action_on_duplicate_name == 'block')
  176. {
  177. $ret['status_ok'] = 0;
  178. // BUGID 2767
  179. $ret['msg'] = sprintf(lang_get('component_name_already_exists'),$name);
  180. }
  181. else
  182. {
  183. $ret['status_ok'] = 1;
  184. if ($action_on_duplicate_name == 'generate_new')
  185. {
  186. $ret['status_ok'] = 1;
  187. $desired_name=$name;
  188. $name = config_get('prefix_name_for_copy') . " " . $desired_name ;
  189. $ret['msg'] = sprintf(lang_get('created_with_new_name'),$name,$desired_name);
  190. }
  191. }
  192. }
  193. }
  194. if ($ret['status_ok'])
  195. {
  196. // get a new id
  197. $tsuite_id = $this->tree_manager->new_node($parent_id,$this->my_node_type,
  198. $name,$node_order);
  199. $sql = "INSERT INTO {$this->tables['testsuites']} (id,details) " .
  200. " VALUES ({$tsuite_id},'" . $this->db->prepare_string($details) . "')";
  201. $result = $this->db->exec_query($sql);
  202. if ($result)
  203. {
  204. $ret['id'] = $tsuite_id;
  205. }
  206. }
  207. return $ret;
  208. }
  209. /**
  210. * update
  211. *
  212. */
  213. function update($id, $name, $details, $parent_id=null)
  214. {
  215. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  216. $ret['status_ok']=0;
  217. $ret['msg']='';
  218. $check = $this->tree_manager->nodeNameExists($name,$this->my_node_type,$id,$parent_id);
  219. if($check['status']==0)
  220. {
  221. $sql = "/* $debugMsg */ UPDATE {$this->tables['testsuites']} " .
  222. " SET details = '" . $this->db->prepare_string($details) . "'" .
  223. " WHERE id = {$id}";
  224. $result = $this->db->exec_query($sql);
  225. if ($result)
  226. {
  227. $sql = "/* $debugMsg */ UPDATE {$this->tables['nodes_hierarchy']} SET name='" .
  228. $this->db->prepare_string($name) . "' WHERE id= {$id}";
  229. $result = $this->db->exec_query($sql);
  230. }
  231. $ret['status_ok']=1;
  232. $ret['msg']='ok';
  233. if (!$result)
  234. {
  235. $ret['msg'] = $this->db->error_msg();
  236. }
  237. }
  238. else
  239. {
  240. $ret['msg']=$check['msg'];
  241. }
  242. return $ret;
  243. }
  244. /**
  245. * Delete a Test suite, deleting:
  246. * - Children Test Cases
  247. * - Test Suite Attachments
  248. * - Test Suite Custom fields
  249. * - Test Suite Keywords
  250. *
  251. * IMPORTANT/CRITIC:
  252. * this can used to delete a Test Suite that contains ONLY Test Cases.
  253. *
  254. * This function is needed by tree class method: delete_subtree_objects()
  255. *
  256. * To delete a Test Suite that contains other Test Suites delete_deep()
  257. * must be used.
  258. *
  259. * ATTENTION: may be in future this can be refactored, and written better.
  260. *
  261. */
  262. function delete($id)
  263. {
  264. $tcase_mgr = New testcase($this->db);
  265. $tsuite_info = $this->get_by_id($id);
  266. $testcases=$this->get_children_testcases($id);
  267. if (!is_null($testcases))
  268. {
  269. foreach($testcases as $the_key => $elem)
  270. {
  271. $tcase_mgr->delete($elem['id']);
  272. }
  273. }
  274. // What about keywords ???
  275. $this->cfield_mgr->remove_all_design_values_from_node($id);
  276. $this->deleteAttachments($id); //inherited
  277. $this->deleteKeywords($id);
  278. $sql = "DELETE FROM {$this->object_table} WHERE id={$id}";
  279. $result = $this->db->exec_query($sql);
  280. $sql = "DELETE FROM {$this->tables['nodes_hierarchy']} WHERE id={$id}";
  281. $result = $this->db->exec_query($sql);
  282. }
  283. /*
  284. function: get_by_name
  285. args : name: testsuite name
  286. returns: array where every element is a map with following keys:
  287. id: testsuite id (node id)
  288. details
  289. name: testsuite name
  290. @internal Revisions
  291. 20100602 - BUGID 3498
  292. */
  293. function get_by_name($name)
  294. {
  295. $sql = " SELECT TS.*, NH.name, NH.parent_id " .
  296. " FROM {$this->tables['testsuites']} TS " .
  297. " JOIN {$this->tables['nodes_hierarchy']} NH " .
  298. " ON NH.id = TS.id " .
  299. " WHERE NH.name = '" . $this->db->prepare_string($name) . "'";
  300. $recordset = $this->db->get_recordset($sql);
  301. return $recordset;
  302. }
  303. /*
  304. function: get_by_id
  305. get info for one (or several) test suite(s)
  306. args : id: testsuite id
  307. returns: map with following keys:
  308. id: testsuite id (node id) (can be an array)
  309. details
  310. name: testsuite name
  311. rev :
  312. 20100328 - BUGID 2645 - contribution - added parent_id
  313. 20070324 - added node_order in result set
  314. */
  315. function get_by_id($id, $order_by = '')
  316. {
  317. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  318. $sql = "/* $debugMsg */ SELECT TS.*, NH.name, NH.node_type_id, NH.node_order, NH.parent_id " .
  319. " FROM {$this->tables['testsuites']} TS, " .
  320. " {$this->tables['nodes_hierarchy']} NH WHERE TS.id = NH.id AND TS.id ";
  321. $sql .= is_array($id) ? " IN (" . implode(',',$id) . ")" : " = {$id} ";
  322. $sql .= $order_by;
  323. $rs = $this->db->fetchRowsIntoMap($sql,'id');
  324. if( !is_null($rs) )
  325. {
  326. $rs = count($rs) == 1 ? current($rs) : $rs;
  327. }
  328. return $rs;
  329. }
  330. /*
  331. function: get_all()
  332. get array of info for every test suite without any kind of filter.
  333. Every array element contains an assoc array with test suite info
  334. args : -
  335. returns: array
  336. */
  337. function get_all()
  338. {
  339. $sql = " SELECT testsuites.*, nodes_hierarchy.name " .
  340. " FROM {$this->tables['testsuites']} testsuites, " .
  341. " {$this->tables['nodes_hierarchy']} nodes_hierarchy " .
  342. " WHERE testsuites.id = nodes_hierarchy.id";
  343. $recordset = $this->db->get_recordset($sql);
  344. return($recordset);
  345. }
  346. /**
  347. * show()
  348. *
  349. * args: smarty [reference]
  350. * id
  351. * sqlResult [default = '']
  352. * action [default = 'update']
  353. * modded_item_id [default = 0]
  354. *
  355. * returns: -
  356. *
  357. **/
  358. function show(&$smarty,$guiObj,$template_dir, $id, $options=null,
  359. $sqlResult = '', $action = 'update',$modded_item_id = 0)
  360. {
  361. $gui = is_null($guiObj) ? new stdClass() : $guiObj;
  362. $gui->cf = '';
  363. $gui->sqlResult = '';
  364. $gui->sqlAction = '';
  365. // 20100314 - franciscom
  366. $gui->refreshTree = property_exists($gui,'refreshTree') ? $gui->refreshTree : false;
  367. // BUGID 0003233: After test suite edit, display of Test suite do not
  368. // have upload button enabled for attachment
  369. // $my['options'] = array('show_mode' => 'readonly');
  370. $my['options'] = array('show_mode' => 'readwrite');
  371. $my['options'] = array_merge($my['options'], (array)$options);
  372. $gui->modify_tc_rights = has_rights($this->db,"mgt_modify_tc");
  373. if($my['options']['show_mode'] == 'readonly')
  374. {
  375. $gui->modify_tc_rights = 'no';
  376. }
  377. if($sqlResult)
  378. {
  379. $gui->sqlResult = $sqlResult;
  380. $gui->sqlAction = $action;
  381. }
  382. $gui->container_data = $this->get_by_id($id);
  383. $gui->moddedItem = $gui->container_data;
  384. if ($modded_item_id)
  385. {
  386. $gui->moddedItem = $this->get_by_id($modded_item_id);
  387. }
  388. $gui->cf = $this->html_table_of_custom_field_values($id);
  389. $gui->keywords_map = $this->get_keywords_map($id,' ORDER BY keyword ASC ');
  390. $gui->attachmentInfos = getAttachmentInfosFrom($this,$id);
  391. $gui->id = $id;
  392. $gui->idpage_title = lang_get('testsuite');
  393. $gui->level = 'testsuite';
  394. $smarty->assign('gui',$gui);
  395. $smarty->display($template_dir . 'containerView.tpl');
  396. }
  397. /*
  398. function: viewer_edit_new
  399. Implements user interface (UI) for edit testuite and
  400. new/create testsuite operations.
  401. args : smarty [reference]
  402. webEditorHtmlNames
  403. oWebEditor: rich editor object (today is FCK editor)
  404. action
  405. parent_id: testsuite parent id on tree.
  406. [id]
  407. [messages]: default null
  408. map with following keys
  409. [result_msg]: default: null used to give information to user
  410. [user_feedback]: default: null used to give information to user
  411. // [$userTemplateCfg]: configurations, Example: testsuite template usage
  412. [$userTemplateKey]: main Key to access item template configuration
  413. [$userInput]
  414. returns: -
  415. rev :
  416. 20090801 - franciscom - added $userInput
  417. 20080105 - franciscom - added $userTemplateCfg
  418. 20071202 - franciscom - interface changes -> template_dir
  419. */
  420. function viewer_edit_new(&$smarty,$template_dir,$webEditorHtmlNames, $oWebEditor, $action, $parent_id,
  421. $id=null, $messages=null, $userTemplateKey=null, $userInput=null)
  422. {
  423. $internalMsg = array('result_msg' => null, 'user_feedback' => null);
  424. $the_data = null;
  425. $name = '';
  426. if( !is_null($messages) )
  427. {
  428. $internalMsg = array_merge($internalMsg, $messages);
  429. }
  430. $useUserInput = is_null($userInput) ? 0 : 1;
  431. $cf_smarty=-2; // MAGIC must be explained
  432. $pnode_info=$this->tree_manager->get_node_hierarchy_info($parent_id);
  433. $parent_info['description']=lang_get($this->node_types_id_descr[$pnode_info['node_type_id']]);
  434. $parent_info['name']=$pnode_info['name'];
  435. $a_tpl = array( 'edit_testsuite' => 'containerEdit.tpl',
  436. 'new_testsuite' => 'containerNew.tpl',
  437. 'add_testsuite' => 'containerNew.tpl');
  438. $the_tpl = $a_tpl[$action];
  439. $smarty->assign('sqlResult', $internalMsg['result_msg']);
  440. $smarty->assign('containerID',$parent_id);
  441. $smarty->assign('user_feedback', $internalMsg['user_feedback'] );
  442. if( $useUserInput )
  443. {
  444. $webEditorData = $userInput;
  445. }
  446. else
  447. {
  448. $the_data = null;
  449. $name = '';
  450. if ($action == 'edit_testsuite')
  451. {
  452. $the_data = $this->get_by_id($id);
  453. $name=$the_data['name'];
  454. $smarty->assign('containerID',$id);
  455. }
  456. $webEditorData = $the_data;
  457. }
  458. // Custom fields
  459. $cf_smarty = $this->html_table_of_custom_field_inputs($id,$parent_id);
  460. // webeditor
  461. // 20090503 - now templates will be also used after 'add_testsuite', when
  462. // presenting a new test suite with all other fields empty.
  463. if( !$useUserInput )
  464. {
  465. if( ($action == 'new_testsuite' || $action == 'add_testsuite') && !is_null($userTemplateKey) )
  466. {
  467. // need to understand if need to use templates
  468. $webEditorData=$this->_initializeWebEditors($webEditorHtmlNames,$userTemplateKey);
  469. }
  470. }
  471. foreach ($webEditorHtmlNames as $key)
  472. {
  473. // Warning:
  474. // the data assignment will work while the keys in $the_data are identical
  475. // to the keys used on $oWebEditor.
  476. $of = &$oWebEditor[$key];
  477. $of->Value = isset($webEditorData[$key]) ? $webEditorData[$key] : null;
  478. $smarty->assign($key, $of->CreateHTML());
  479. }
  480. $smarty->assign('cf',$cf_smarty);
  481. $smarty->assign('parent_info', $parent_info);
  482. $smarty->assign('level', 'testsuite');
  483. $smarty->assign('name',$name);
  484. $smarty->assign('container_data',$the_data);
  485. $smarty->display($template_dir . $the_tpl);
  486. }
  487. /*
  488. function: copy_to
  489. deep copy one testsuite to another parent (testsuite or testproject).
  490. args : id: testsuite id (source or copy)
  491. parent_id:
  492. user_id: who is requesting copy operation
  493. [check_duplicate_name]: default: 0 -> do not check
  494. 1 -> check for duplicate when doing copy
  495. What to do if duplicate exists, is controlled
  496. by action_on_duplicate_name argument.
  497. [action_on_duplicate_name argument]: default: 'allow_repeat'.
  498. Used when check_duplicate_name=1.
  499. Specifies how to react if duplicate name exists.
  500. returns: map with foloowing keys:
  501. status_ok: 0 / 1
  502. msg: 'ok' if status_ok == 1
  503. id: new created if everything OK, -1 if problems.
  504. rev :
  505. 20090821 - franciscom - BUGID 0002781
  506. 20070324 - BUGID 710
  507. */
  508. function copy_to($id, $parent_id, $user_id,$options=null,$mappings=null)
  509. {
  510. $my['options'] = array('check_duplicate_name' => 0,
  511. 'action_on_duplicate_name' => 'allow_repeat',
  512. 'copyKeywords' => 0, 'copyRequirements' => 0);
  513. $my['options'] = array_merge($my['options'], (array)$options);
  514. $my['mappings'] = array();
  515. $my['mappings'] = array_merge($my['mappings'], (array)$mappings);
  516. $copyTCaseOpt = array('copy_also' =>
  517. array('keyword_assignments' => $my['options']['copyKeywords'],
  518. 'requirement_assignments' => $my['options']['copyRequirements']) );
  519. $copyOptions = array('keyword_assignments' => $my['options']['copyKeywords']);
  520. $tcase_mgr = new testcase($this->db);
  521. $tsuite_info = $this->get_by_id($id);
  522. $op = $this->create($parent_id,$tsuite_info['name'],$tsuite_info['details'],
  523. $tsuite_info['node_order'],$my['options']['check_duplicate_name'],
  524. $my['options']['action_on_duplicate_name']);
  525. $op['mappings'][$id] = $op['id'];
  526. $new_tsuite_id = $op['id'];
  527. // Work on root of these subtree
  528. // Attachments
  529. // Keyword assignment
  530. // Custom Field values
  531. $this->copy_attachments($id,$new_tsuite_id);
  532. if( $my['options']['copyKeywords'] )
  533. {
  534. $kmap = isset($my['mappings']['keywords']) ? $my['mappings']['keywords'] : null;
  535. $this->copy_keyword_assignment($id,$new_tsuite_id,$kmap);
  536. }
  537. $this->copy_cfields_values($id,$new_tsuite_id);
  538. $my['filters'] = array('exclude_children_of' => array('testcase' => 'exclude my children'));
  539. $subtree = $this->tree_manager->get_subtree($id,$my['filters']);
  540. if (!is_null($subtree))
  541. {
  542. $parent_decode=array();
  543. $parent_decode[$id]=$new_tsuite_id;
  544. foreach($subtree as $the_key => $elem)
  545. {
  546. $the_parent_id=$parent_decode[$elem['parent_id']];
  547. switch ($elem['node_type_id'])
  548. {
  549. case $this->node_types_descr_id['testcase']:
  550. $tcOp = $tcase_mgr->copy_to($elem['id'],$the_parent_id,$user_id,$copyTCaseOpt);
  551. $op['mappings'] += $tcOp['mappings'];
  552. break;
  553. case $this->node_types_descr_id['testsuite']:
  554. $tsuite_info = $this->get_by_id($elem['id']);
  555. $ret = $this->create($the_parent_id,$tsuite_info['name'],
  556. $tsuite_info['details'],$tsuite_info['node_order']);
  557. $parent_decode[$elem['id']] = $ret['id'];
  558. $op['mappings'][$elem['id']] = $ret['id'];
  559. $tcase_mgr->copy_attachments($elem['id'],$ret['id']);
  560. if( $my['options']['copyKeywords'] )
  561. {
  562. $this->copy_keyword_assignment($elem['id'],$ret['id'],$kmap);
  563. }
  564. $this->copy_cfields_values($elem['id'],$ret['id']);
  565. break;
  566. }
  567. }
  568. }
  569. return $op;
  570. }
  571. /*
  572. function: get_subtree
  573. Get subtree that has choosen testsuite as root.
  574. Only nodes of type:
  575. testsuite and testcase are explored and retrieved.
  576. args: id: testsuite id
  577. [recursive_mode]: default false
  578. returns: map
  579. see tree->get_subtree() for details.
  580. */
  581. function get_subtree($id,$recursive_mode=false)
  582. {
  583. $my['options']=array('recursive' => $recursive_mode);
  584. $my['filters'] = array('exclude_node_types' => $this->nt2exclude,
  585. 'exclude_children_of' => $this->nt2exclude_children);
  586. $subtree = $this->tree_manager->get_subtree($id,$my['filters'],$my['options']);
  587. return $subtree;
  588. }
  589. /*
  590. function: get_testcases_deep
  591. get all test cases in the test suite and all children test suites
  592. no info about tcversions is returned.
  593. args : id: testsuite id
  594. [details]: default 'simple'
  595. Structure of elements in returned array, changes according to
  596. this argument:
  597. 'only_id'
  598. Array that contains ONLY testcase id, no other info.
  599. 'simple'
  600. Array where each element is a map with following keys.
  601. id: testcase id
  602. parent_id: testcase parent (a test suite id).
  603. node_type_id: type id, for a testcase node
  604. node_order
  605. node_table: node table, for a testcase.
  606. name: testcase name
  607. external_id:
  608. 'full'
  609. Complete info about testcase for LAST TCVERSION
  610. TO BE IMPLEMENTED
  611. returns: array
  612. */
  613. function get_testcases_deep($id, $details = 'simple')
  614. {
  615. $tcase_mgr = new testcase($this->db);
  616. $testcases = null;
  617. $subtree = $this->get_subtree($id);
  618. $only_id=($details=='only_id') ? true : false;
  619. $doit=!is_null($subtree);
  620. $parentSet=null;
  621. if($doit)
  622. {
  623. $testcases = array();
  624. $tcNodeType = $this->node_types_descr_id['testcase'];
  625. $prefix = null;
  626. foreach ($subtree as $the_key => $elem)
  627. {
  628. if($elem['node_type_id'] == $tcNodeType)
  629. {
  630. if ($only_id)
  631. {
  632. $testcases[] = $elem['id'];
  633. }
  634. else
  635. {
  636. // After first call passing $prefix with right value, avoids a function call
  637. // inside of getExternalID();
  638. list($identity,$prefix,$glueChar,$external) = $tcase_mgr->getExternalID($elem['id'],null,$prefix);
  639. $elem['external_id'] = $identity;
  640. $testcases[]= $elem;
  641. $parentSet[$elem['parent_id']]=$elem['parent_id'];
  642. }
  643. }
  644. }
  645. $doit = count($testcases) > 0;
  646. }
  647. if($doit && $details=='full')
  648. {
  649. $parentNodes=$this->tree_manager->get_node_hierarchy_info($parentSet);
  650. $rs=array();
  651. foreach($testcases as $idx => $value)
  652. {
  653. $item=$tcase_mgr->get_last_version_info($value['id']);
  654. $item['tcversion_id']=$item['id'];
  655. $tsuite['tsuite_name']=$parentNodes[$value['parent_id']]['name'];
  656. unset($item['id']);
  657. $rs[]=$value+$item+$tsuite;
  658. }
  659. $testcases=$rs;
  660. }
  661. return $testcases;
  662. }
  663. /**
  664. * get_children_testcases
  665. * get only test cases with parent=testsuite without doing a deep search
  666. *
  667. */
  668. function get_children_testcases($id, $details = 'simple')
  669. {
  670. $testcases=null;
  671. $only_id=($details=='only_id') ? true : false;
  672. $subtree=$this->tree_manager->get_children($id,array('testsuite' => 'exclude_me'));
  673. $doit=!is_null($subtree);
  674. if($doit)
  675. {
  676. $tsuite=$this->get_by_id($id);
  677. $tsuiteName=$tsuite['name'];
  678. $testcases = array();
  679. foreach ($subtree as $the_key => $elem)
  680. {
  681. if ($only_id)
  682. {
  683. $testcases[] = $elem['id'];
  684. }
  685. else
  686. {
  687. $testcases[]= $elem;
  688. }
  689. }
  690. $doit = count($testcases) > 0;
  691. }
  692. if($doit && $details=='full')
  693. {
  694. $rs=array();
  695. $tcase_mgr = new testcase($this->db);
  696. foreach($testcases as $idx => $value)
  697. {
  698. $item=$tcase_mgr->get_last_version_info($value['id']);
  699. $item['tcversion_id']=$item['id'];
  700. $parent['tsuite_name']=$tsuiteName;
  701. unset($item['id']);
  702. $rs[]=$value+$item+$tsuite;
  703. }
  704. $testcases=$rs;
  705. }
  706. return $testcases;
  707. }
  708. /*
  709. function: delete_deep
  710. args : $id
  711. returns:
  712. rev :
  713. 20070602 - franciscom
  714. added delete attachments
  715. */
  716. function delete_deep($id)
  717. {
  718. // BUGID 3147 - Delete test project with requirements defined crashed with memory exhausted
  719. $this->tree_manager->delete_subtree_objects($id,$id,'',array('testcase' => 'exclude_tcversion_nodes'));
  720. $this->delete($id);
  721. } // end function
  722. /*
  723. function: initializeWebEditors
  724. args:
  725. returns:
  726. */
  727. private function _initializeWebEditors($WebEditors,$itemTemplateCfgKey)
  728. {
  729. $wdata=array();
  730. foreach ($WebEditors as $key => $html_name)
  731. {
  732. $wdata[$html_name] = getItemTemplateContents($itemTemplateCfgKey, $html_name, '');
  733. }
  734. return $wdata;
  735. }
  736. /*
  737. function: getKeywords
  738. Get keyword assigned to a testsuite.
  739. Uses table object_keywords.
  740. Attention:
  741. probably write on obejct_keywords has not been implemented yet,
  742. then right now thie method can be useless.
  743. args: id: testsuite id
  744. kw_id: [default = null] the optional keyword id
  745. returns: null if nothing found.
  746. array, every elemen is map with following structure:
  747. id
  748. keyword
  749. notes
  750. rev :
  751. 20070116 - franciscom - BUGID 543
  752. */
  753. function getKeywords($id,$kw_id = null)
  754. {
  755. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  756. $sql = "/* $debugMsg */ SELECT keyword_id,keywords.keyword, notes " .
  757. " FROM {$this->tables['object_keywords']}, {$this->tables['keywords']} keywords " .
  758. " WHERE keyword_id = keywords.id AND fk_id = {$id}";
  759. if (!is_null($kw_id))
  760. {
  761. $sql .= " AND keyword_id = {$kw_id}";
  762. }
  763. $map_keywords = $this->db->fetchRowsIntoMap($sql,'keyword_id');
  764. return($map_keywords);
  765. }
  766. /*
  767. function: get_keywords_map
  768. All keywords for a choosen testsuite
  769. Attention:
  770. probably write on obejct_keywords has not been implemented yet,
  771. then right now thie method can be useless.
  772. args :id: testsuite id
  773. [order_by_clause]: default: '' -> no order choosen
  774. must be an string with complete clause, i.e.
  775. 'ORDER BY keyword'
  776. returns: map: key: keyword_id
  777. value: keyword
  778. */
  779. function get_keywords_map($id,$order_by_clause='')
  780. {
  781. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  782. $sql = "/* $debugMsg */ SELECT keyword_id,keywords.keyword " .
  783. " FROM {$this->tables['object_keywords']}, {$this->tables['keywords']} keywords " .
  784. " WHERE keyword_id = keywords.id ";
  785. if (is_array($id))
  786. {
  787. $sql .= " AND fk_id IN (".implode(",",$id).") ";
  788. }
  789. else
  790. {
  791. $sql .= " AND fk_id = {$id} ";
  792. }
  793. $sql .= $order_by_clause;
  794. $map_keywords = $this->db->fetchColumnsIntoMap($sql,'keyword_id','keyword');
  795. return($map_keywords);
  796. }
  797. /**
  798. *
  799. *
  800. */
  801. function addKeyword($id,$kw_id)
  802. {
  803. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  804. $status = 1;
  805. $kw = $this->getKeywords($id,$kw_id);
  806. if( ($doLink = !sizeof($kw)) )
  807. {
  808. $sql = "/* $debugMsg */ INSERT INTO {$this->tables['object_keywords']} " .
  809. " (fk_id,fk_table,keyword_id) VALUES ($id,'nodes_hierarchy',$kw_id)";
  810. $status = $this->db->exec_query($sql) ? 1 : 0;
  811. }
  812. return $status;
  813. }
  814. /*
  815. function: addKeywords
  816. args :
  817. returns:
  818. */
  819. function addKeywords($id,$kw_ids)
  820. {
  821. $status = 1;
  822. $num_kws = sizeof($kw_ids);
  823. for($idx = 0; $idx < $num_kws; $idx++)
  824. {
  825. $status = $status && $this->addKeyword($id,$kw_ids[$idx]);
  826. }
  827. return($status);
  828. }
  829. /*
  830. function: deleteKeywords
  831. args :
  832. returns:
  833. */
  834. function deleteKeywords($id,$kw_id = null)
  835. {
  836. $sql = " DELETE FROM {$this->tables['object_keywords']} WHERE fk_id = {$id} ";
  837. if (!is_null($kw_id))
  838. {
  839. $sql .= " AND keyword_id = {$kw_id}";
  840. }
  841. return($this->db->exec_query($sql));
  842. }
  843. /*
  844. function: exportTestSuiteDataToXML
  845. args :
  846. returns:
  847. rev: 20090204 - franciscom - added node_order
  848. */
  849. function exportTestSuiteDataToXML($container_id,$tproject_id,$optExport = array())
  850. {
  851. static $keywordMgr;
  852. if(is_null($keywordMgr))
  853. {
  854. $keywordMgr = new tlKeyword();
  855. }
  856. // echo __FUNCTION__ . '<br>';
  857. $xmlTC = null;
  858. $doRecursion = isset($optExport['RECURSIVE']) ? $optExport['RECURSIVE'] : 0;
  859. if($doRecursion)
  860. {
  861. $cfXML = null;
  862. $kwXML = null;
  863. $tsuiteData = $this->get_by_id($container_id);
  864. if (@$optExport['KEYWORDS'])
  865. {
  866. $kwMap = $this->getKeywords($container_id);
  867. if ($kwMap)
  868. {
  869. $kwXML = "<keywords>" . $keywordMgr->toXMLString($kwMap,true) . "</keywords>";
  870. }
  871. }
  872. if ($optExport['CFIELDS'])
  873. {
  874. // 20090106 - franciscom - custom fields
  875. $cfMap=$this->get_linked_cfields_at_design($container_id,null,null,$tproject_id);
  876. if( !is_null($cfMap) && count($cfMap) > 0 )
  877. {
  878. $cfRootElem = "<custom_fields>{{XMLCODE}}</custom_fields>";
  879. $cfElemTemplate = "\t" . '<custom_field><name><![CDATA[' . "\n||NAME||\n]]>" . "</name>" .
  880. '<value><![CDATA['."\n||VALUE||\n]]>".'</value></custom_field>'."\n";
  881. $cfDecode = array ("||NAME||" => "name","||VALUE||" => "value");
  882. $cfXML = exportDataToXML($cfMap,$cfRootElem,$cfElemTemplate,$cfDecode,true);
  883. }
  884. }
  885. $xmlTC = "<testsuite name=\"" . htmlspecialchars($tsuiteData['name']). '" >' .
  886. "\n<node_order><![CDATA[{$tsuiteData['node_order']}]]></node_order>\n" .
  887. "<details><![CDATA[{$tsuiteData['details']}]]> \n{$kwXML}{$cfXML}</details>";
  888. }
  889. else
  890. {
  891. $xmlTC = "<testcases>";
  892. }
  893. $test_spec = $this->get_subtree($container_id,self::USE_RECURSIVE_MODE);
  894. $childNodes = isset($test_spec['childNodes']) ? $test_spec['childNodes'] : null ;
  895. $tcase_mgr=null;
  896. if( !is_null($childNodes) )
  897. {
  898. $loop_qty=sizeof($childNodes);
  899. for($idx = 0;$idx < $loop_qty;$idx++)
  900. {
  901. $cNode = $childNodes[$idx];
  902. $nTable = $cNode['node_table'];
  903. if ($doRecursion && $nTable == 'testsuites')
  904. {
  905. $xmlTC .= $this->exportTestSuiteDataToXML($cNode['id'],$tproject_id,$optExport);
  906. }
  907. else if ($nTable == 'testcases')
  908. {
  909. if( is_null($tcase_mgr) )
  910. {
  911. $tcase_mgr = new testcase($this->db);
  912. }
  913. $xmlTC .= $tcase_mgr->exportTestCaseDataToXML($cNode['id'],testcase::LATEST_VERSION,
  914. $tproject_id,true,$optExport);
  915. }
  916. }
  917. }
  918. $xmlTC .= $doRecursion ? "</testsuite>" : "</testcases>";
  919. return $xmlTC;
  920. }
  921. // -------------------------------------------------------------------------------
  922. // Custom field related methods
  923. // -------------------------------------------------------------------------------
  924. /*
  925. function: get_linked_cfields_at_design
  926. args: $id
  927. [$parent_id]:
  928. [$show_on_execution]: default: null
  929. 1 -> filter on field show_on_execution=1
  930. 0 or null -> don't filter
  931. returns: hash
  932. rev :
  933. 20061231 - franciscom - added $parent_id
  934. */
  935. function get_linked_cfields_at_design($id,$parent_id=null,$show_on_execution=null,$tproject_id = null)
  936. {
  937. if (!$tproject_id)
  938. {
  939. $tproject_id = $this->getTestProjectFromTestSuite($id,$parent_id);
  940. }
  941. $filters=array('show_on_execution' => $show_on_execution);
  942. $enabled = 1;
  943. $cf_map = $this->cfield_mgr->get_linked_cfields_at_design($tproject_id,$enabled,$filters,
  944. 'testsuite',$id);
  945. return $cf_map;
  946. }
  947. /**
  948. * getTestProjectFromTestSuite()
  949. *
  950. */
  951. function getTestProjectFromTestSuite($id,$parent_id)
  952. {
  953. $tproject_id = $this->tree_manager->getTreeRoot( (!is_null($id) && $id > 0) ? $id : $parent_id);
  954. return $tproject_id;
  955. }
  956. /*
  957. function: get_linked_cfields_at_execution
  958. args: $id
  959. [$parent_id]
  960. [$show_on_execution]: default: null
  961. 1 -> filter on field show_on_execution=1
  962. 0 or null -> don't filter
  963. returns: hash
  964. rev :
  965. 20061231 - franciscom - added $parent_id
  966. */
  967. function get_linked_cfields_at_execution($id,$parent_id=null,$show_on_execution=null)
  968. {
  969. $filters=array('show_on_execution' => $show_on_execution);
  970. $enabled=1;
  971. $the_path=$this->tree_manager->get_path(!is_null($id) ? $id : $parent_id);
  972. $path_len=count($the_path);
  973. $tproject_id=($path_len > 0)? $the_path[$path_len-1]['parent_id'] : $parent_id;
  974. $cf_map=$this->cfield_mgr->get_linked_cfields_at_design($tproject_id,$enabled,$filters,
  975. 'testsuite',$id);
  976. return($cf_map);
  977. }
  978. /*
  979. function: html_table_of_custom_field_inputs
  980. args: $id
  981. [$parent_id]: need when you call this method during the creation
  982. of a test suite, because the $id will be 0 or null.
  983. [$scope]: 'design','execution'
  984. returns: html string
  985. */
  986. function html_table_of_custom_field_inputs($id,$parent_id=null,$scope='design')
  987. {
  988. $cf_smarty='';
  989. if( $scope=='design' )
  990. {
  991. $cf_map=$this->get_linked_cfields_at_design($id,$parent_id);
  992. }
  993. else
  994. {
  995. $cf_map=$this->get_linked_cfields_at_execution($id,$parent_id);
  996. }
  997. if( !is_null($cf_map) )
  998. {
  999. foreach($cf_map as $cf_id => $cf_info)
  1000. {
  1001. // true => do not create input in audit log
  1002. $label=str_replace(TL_LOCALIZE_TAG,'',lang_get($cf_info['label'],null,true));
  1003. $cf_smarty .= '<tr><td class="labelHolder">' . htmlspecialchars($label) . "</td><td>" .
  1004. $this->cfield_mgr->string_custom_field_input($cf_info) .
  1005. "</td></tr>\n";
  1006. } //foreach($cf_map
  1007. }
  1008. if(trim($cf_smarty) != "")
  1009. {
  1010. $cf_smarty = "<table>" . $cf_smarty . "</table>";
  1011. }
  1012. return($cf_smarty);
  1013. }
  1014. /*
  1015. function: html_table_of_custom_field_values
  1016. args: $id
  1017. [$scope]: 'design','execution'
  1018. [$show_on_execution]: default: null
  1019. 1 -> filter on field show_on_execution=1
  1020. 0 or null -> don't filter
  1021. returns: html string
  1022. */
  1023. function html_table_of_custom_field_values($id,$scope='design',$show_on_execution=null,
  1024. $tproject_id = null,$formatOptions=null)
  1025. {
  1026. $filters=array('show_on_execution' => $show_on_execution);
  1027. $td_style='class="labelHolder"' ;
  1028. $add_table=true;
  1029. $table_style='';
  1030. if( !is_null($formatOptions) )
  1031. {
  1032. $td_style=isset($formatOptions['td_css_style']) ? $formatOptions['td_css_style'] : $td_style;
  1033. $add_table=isset($formatOptions['add_table']) ? $formatOptions['add_table'] : true;
  1034. $table_style=isset($formatOptions['table_css_style']) ? $formatOptions['table_css_style'] : $table_style;
  1035. }
  1036. $cf_smarty='';
  1037. $parent_id=null;
  1038. if( $scope=='design' )
  1039. {
  1040. $cf_map = $this->get_linked_cfields_at_design($id,$parent_id,$filters,$tproject_id);
  1041. }
  1042. else
  1043. {
  1044. // Important: remember that for Test Suite, custom field value CAN NOT BE changed at execution time
  1045. // just displayed.
  1046. // @TODO: schlundus, can this be speed up with tprojectID?
  1047. $cf_map=$this->get_linked_cfields_at_execution($id);
  1048. }
  1049. if( !is_null($cf_map) )
  1050. {
  1051. foreach($cf_map as $cf_id => $cf_info)
  1052. {
  1053. // if user has assigned a value, then node_id is not null
  1054. if($cf_info['node_id'])
  1055. {
  1056. // true => do not create input in audit log
  1057. $label=str_replace(TL_LOCALIZE_TAG,'',lang_get($cf_info['label'],null,true));
  1058. $cf_smarty .= "<tr><td {$td_style} >" . htmlspecialchars($label) . "</td><td>" .
  1059. $this->cfield_mgr->string_custom_field_value($cf_info,$id) .
  1060. "</td></tr>\n";
  1061. }
  1062. }
  1063. }
  1064. if((trim($cf_smarty) != "") && $add_table)
  1065. {
  1066. $cf_smarty = "<table {$table_style}>" . $cf_smarty . "</table>";
  1067. }
  1068. return($cf_smarty);
  1069. } // function end
  1070. /**
  1071. * Copy attachments from source test suite to target test suite
  1072. *
  1073. **/
  1074. function copy_attachments($source_id,$target_id)
  1075. {
  1076. $this->attachmentRepository->copyAttachments($source_id,$target_id,$this->attachmentTableName);
  1077. }
  1078. /**
  1079. * Copy keyword assignment
  1080. * mappings is only useful when source_id and target_id do not belong to same Test Project.
  1081. * Because keywords are defined INSIDE a Test Project, ID will be different for same keyword
  1082. * in a different Test Project
  1083. *
  1084. **/
  1085. function copy_keyword_assignment($source_id,$target_id,$mappings)
  1086. {
  1087. // Get source_id keyword assignment
  1088. $sourceItems = $this->getKeywords($source_id);
  1089. if( !is_null($sourceItems) )
  1090. {
  1091. // build item id list
  1092. $keySet = array_keys($sourceItems);
  1093. foreach($keySet as $itemPos => $itemID)
  1094. {
  1095. if( isset($mappings[$itemID]) )
  1096. {
  1097. $keySet[$itemPos] = $mappings[$itemID];
  1098. }
  1099. }
  1100. $this->addKeywords($target_id,$keySet);
  1101. }
  1102. }
  1103. /**
  1104. * Copy Custom Fields values
  1105. *
  1106. **/
  1107. function copy_cfields_values($source_id,$target_id)
  1108. {
  1109. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1110. // Get source_id cfields assignment
  1111. $sourceItems = $this->cfield_mgr->getByLinkID($source_id,array('scope' => 'design'));
  1112. if( !is_null($sourceItems) )
  1113. {
  1114. $sql = "/* $debugMsg */ " .
  1115. " INSERT INTO {$this->tables['cfield_design_values']} " .
  1116. " (field_id,value,node_id) " .
  1117. " SELECT field_id,value,{$target_id} AS target_id" .
  1118. " FROM {$this->tables['cfield_design_values']} " .
  1119. " WHERE node_id = {$source_id} ";
  1120. $this->db->exec_query($sql);
  1121. }
  1122. }
  1123. /**
  1124. * get_children
  1125. * get test suites with parent = testsuite with given id
  1126. *
  1127. */
  1128. function get_children($id)
  1129. {
  1130. $itemSet = null;
  1131. $subtree = $this->tree_manager->get_children($id, array('testcase' => 'exclude_me'));
  1132. if(!is_null($subtree) && count($subtree) > 0)
  1133. {
  1134. foreach( $subtree as $the_key => $elem)
  1135. {
  1136. $itemKeys[] = $elem['id'];
  1137. }
  1138. $itemSet = $this->get_by_id($itemKeys, 'ORDER BY node_order');
  1139. }
  1140. return $itemSet;
  1141. }
  1142. } // end class
  1143. ?>