PageRenderTime 55ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/functions/requirement_mgr.class.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 2089 lines | 1147 code | 263 blank | 679 comment | 125 complexity | ea40fd2ca7d96919ade961a7e77b93fc MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  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. * Filename $RCSfile: requirement_mgr.class.php,v $
  7. *
  8. * @version $Revision: 1.85 $
  9. * @modified $Date: 2010/06/24 17:25:53 $ by $Author: asimon83 $
  10. * @author Francisco Mancardi
  11. *
  12. * Manager for requirements.
  13. * Requirements are children of a requirement specification (requirements container)
  14. *
  15. * rev:
  16. * 20100520 - franciscom - BUGID 2169 - customFieldValuesAsXML() new method_exists
  17. * 20100511 - franciscom - createFromXML() new method
  18. * 20100509 - franciscom - update() interface changes.
  19. * 20100324 - asimon - BUGID 1748 - Moved init_relation_type_select here from reqView.php
  20. * as it is now used from multiple files.
  21. * 20100323 - asimon - BUGID 1748 - added count_relations()
  22. * 20100319 - asimon - BUGID 1748 - added functions for requirement relations:
  23. * get_relations(), get_all_relation_labels(), delete_relation(),
  24. * add_relation(), check_if_relation_exists(), delete_all_relations()
  25. * modified delete() to also delete all relations with req
  26. * 20100309 - franciscom - get_by_id() removed useless code pointed by BUGID 3254
  27. * 20100124 - franciscom - BUGID 0003089: Req versions new attributes - active and open
  28. * new methods updateActive(),updateOpen()
  29. * 20091228 - franciscom - exportReqToXML() - added expected_coverage
  30. * refactoring for feature req versioning
  31. * 20091227 - franciscom - delete() now manage version_id
  32. * 20091225 - franciscom - new method - generateDocID()
  33. * 20091216 - franciscom - create_tc_from_requirement() interface changes
  34. * 20091215 - asimon - added new method getByDocID()
  35. * 20091209 - asimon - contrib for testcase creation, BUGID 2996
  36. * 20091208 - franciscom - contrib by julian - BUGID 2995
  37. * 20091207 - franciscom - create() added node_order
  38. * 20091202 - franciscom - create(), update()
  39. * added contribution by asimon83/mx-julian that creates
  40. * links inside scope field.
  41. * 20091125 - franciscom - expected_coverage management
  42. * 20090525 - franciscom - avoid getDisplayName() crash due to deleted user
  43. * 20090514 - franciscom - BUGID 2491
  44. * 20090506 - franciscom - refactoring continued
  45. * 20090505 - franciscom - refactoring started.
  46. * removed use of REQ.node_order and title.
  47. * this fields must be managed on NH table
  48. *
  49. * 20090401 - franciscom - BUGID 2316
  50. * 20090315 - franciscom - added require_once '/attachments.inc.php' to avoid autoload() bug
  51. * delete() - fixed delete order due to FK.
  52. * 20090222 - franciscom - exportReqToXML() - (will be available for TL 1.9)
  53. * 20081129 - franciscom - BUGID 1852 - bulk_assignment()
  54. */
  55. // Needed to use extends tlObjectWithAttachments, If not present autoload fails.
  56. require_once( dirname(__FILE__) . '/attachments.inc.php');
  57. class requirement_mgr extends tlObjectWithAttachments
  58. {
  59. var $db;
  60. var $cfield_mgr;
  61. var $my_node_type;
  62. var $tree_mgr;
  63. var $node_types_descr_id;
  64. var $node_types_id_descr;
  65. var $attachmentTableName;
  66. // 20100220 - franciscom - I'm will work only on XML
  67. // then remove other formats till other dev do refactor
  68. var $import_file_types = array("csv" => "CSV",
  69. "csv_doors" => "CSV (Doors)",
  70. "XML" => "XML",
  71. "DocBook" => "DocBook");
  72. var $export_file_types = array("XML" => "XML");
  73. const AUTOMATIC_ID=0;
  74. const ALL_VERSIONS=0;
  75. const LATEST_VERSION=-1;
  76. /*
  77. function: requirement_mgr
  78. contructor
  79. args: db: reference to db object
  80. returns: instance of requirement_mgr
  81. */
  82. function __construct(&$db)
  83. {
  84. $this->db = &$db;
  85. $this->cfield_mgr=new cfield_mgr($this->db);
  86. $this->tree_mgr = new tree($this->db);
  87. $this->attachmentTableName = 'requirements';
  88. tlObjectWithAttachments::__construct($this->db,$this->attachmentTableName);
  89. $this->node_types_descr_id= $this->tree_mgr->get_available_node_types();
  90. $this->node_types_id_descr=array_flip($this->node_types_descr_id);
  91. $this->my_node_type=$this->node_types_descr_id['requirement'];
  92. $this->object_table=$this->tables['requirements'];
  93. }
  94. /*
  95. function: get_export_file_types
  96. getter
  97. args: -
  98. returns: map
  99. key: export file type code
  100. value: export file type verbose description
  101. */
  102. function get_export_file_types()
  103. {
  104. return $this->export_file_types;
  105. }
  106. /*
  107. function: get_impor_file_types
  108. getter
  109. args: -
  110. returns: map
  111. key: import file type code
  112. value: import file type verbose description
  113. */
  114. function get_import_file_types()
  115. {
  116. return $this->import_file_types;
  117. }
  118. /*
  119. function: get_by_id
  120. args: id: requirement id (can be an array)
  121. [version_id]: requirement version id (can be an array)
  122. [version_number]:
  123. [options]
  124. returns: null if query fails
  125. map with requirement info
  126. */
  127. function get_by_id($id,$version_id=self::ALL_VERSIONS,$version_number=1,$options=null,$filters=null)
  128. {
  129. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  130. $my['options'] = array('order_by' => " ORDER BY REQV.version DESC ");
  131. $my['options'] = array_merge($my['options'], (array)$options);
  132. // null => do not filter
  133. $my['filters'] = array('status' => null, 'type' => null);
  134. $my['filters'] = array_merge($my['filters'], (array)$filters);
  135. $filter_clause = '';
  136. $dummy[]=''; // trick to make implode() work
  137. foreach( $my['filters'] as $field2filter => $value)
  138. {
  139. if( !is_null($value) )
  140. {
  141. $dummy[] = " {$field2filter} = '{$value}' ";
  142. }
  143. }
  144. if( count($dummy) > 1)
  145. {
  146. $filter_clause = implode(" AND ",$dummy);
  147. }
  148. $where_clause = " WHERE NH_REQV.parent_id ";
  149. if( ($id_is_array=is_array($id)) )
  150. {
  151. $where_clause .= "IN (" . implode(",",$id) . ") ";
  152. }
  153. else
  154. {
  155. $where_clause .= " = {$id} ";
  156. }
  157. if(is_array($version_id))
  158. {
  159. $versionid_list = implode(",",$version_id);
  160. $where_clause .= " AND REQV.id IN ({$versionid_list}) ";
  161. }
  162. else
  163. {
  164. if( is_null($version_id) )
  165. {
  166. // search by "human" version number
  167. $where_clause .= " AND REQV.version = {$version_number} ";
  168. }
  169. else
  170. {
  171. if($version_id != self::ALL_VERSIONS && $version_id != self::LATEST_VERSION)
  172. {
  173. $where_clause .= " AND REQV.id = {$version_id} ";
  174. }
  175. }
  176. }
  177. $sql = " /* $debugMsg */ SELECT REQ.id,REQ.srs_id,REQ.req_doc_id," .
  178. " REQV.scope,REQV.status,REQV.type,REQV.active," .
  179. " REQV.is_open,REQV.author_id,REQV.version,REQV.id AS version_id," .
  180. " REQV.expected_coverage,REQV.creation_ts,REQV.modifier_id," .
  181. " REQV.modification_ts,NH_REQ.name AS title, REQ_SPEC.testproject_id, " .
  182. " NH_RSPEC.name AS req_spec_title, REQ_SPEC.doc_id AS req_spec_doc_id, NH_REQ.node_order " .
  183. " FROM {$this->object_table} REQ " .
  184. " JOIN {$this->tables['nodes_hierarchy']} NH_REQ ON NH_REQ.id = REQ.id " .
  185. " JOIN {$this->tables['nodes_hierarchy']} NH_REQV ON NH_REQV.parent_id = NH_REQ.id ".
  186. " JOIN {$this->tables['req_versions']} REQV ON REQV.id = NH_REQV.id " .
  187. " JOIN {$this->tables['req_specs']} REQ_SPEC ON REQ_SPEC.id = REQ.srs_id " .
  188. " JOIN {$this->tables['nodes_hierarchy']} NH_RSPEC ON NH_RSPEC.id = REQ_SPEC.id " .
  189. $where_clause . $filter_clause . $my['options']['order_by'];
  190. if ($version_id != self::LATEST_VERSION)
  191. {
  192. $recordset = $this->db->get_recordset($sql);
  193. }
  194. else
  195. {
  196. // 20090413 - franciscom -
  197. // But, how performance wise can be do this, instead of using MAX(version)
  198. // and a group by?
  199. //
  200. // 20100309 - franciscom -
  201. // if $id was a list then this will return something USELESS
  202. //
  203. if( !$id_is_array )
  204. {
  205. $recordset = array($this->db->fetchFirstRow($sql));
  206. }
  207. else
  208. {
  209. // Write to event viewer ???
  210. }
  211. }
  212. $rs = null;
  213. $userCache = null; // key: user id, value: display name
  214. if(!is_null($recordset))
  215. {
  216. // Decode users
  217. $rs = $recordset;
  218. $key2loop = array_keys($recordset);
  219. $labels['undefined'] = lang_get('undefined');
  220. $user_keys = array('author' => 'author_id', 'modifier' => 'modifier_id');
  221. foreach( $key2loop as $key )
  222. {
  223. foreach( $user_keys as $ukey => $userid_field)
  224. {
  225. $rs[$key][$ukey] = '';
  226. if(trim($rs[$key][$userid_field]) != "")
  227. {
  228. if( !isset($userCache[$rs[$key][$userid_field]]) )
  229. {
  230. $user = tlUser::getByID($this->db,$rs[$key][$userid_field]);
  231. $rs[$key][$ukey] = $user ? $user->getDisplayName() : $labels['undefined'];
  232. $userCache[$rs[$key][$userid_field]] = $rs[$key][$ukey];
  233. }
  234. else
  235. {
  236. $rs[$key][$ukey] = $userCache[$rs[$key][$userid_field]];
  237. }
  238. }
  239. }
  240. }
  241. }
  242. return $rs;
  243. }
  244. /*
  245. function: create
  246. args: srs_id: req spec id, parent of requirement to be created
  247. reqdoc_id
  248. title
  249. scope
  250. user_id: author
  251. [status]
  252. [type]
  253. [expected_coverage]
  254. [node_order]
  255. returns: map with following keys:
  256. status_ok -> 1/0
  257. msg -> some simple message, useful when status_ok ==0
  258. id -> id of new requirement.
  259. @internal revision
  260. 20091202 - franciscom - added contribution by asimon83/mx-julian
  261. */
  262. function create($srs_id,$reqdoc_id,$title, $scope, $user_id,
  263. $status = TL_REQ_STATUS_VALID, $type = TL_REQ_TYPE_INFO,
  264. $expected_coverage=1,$node_order=0)
  265. {
  266. $result = array( 'id' => 0, 'status_ok' => 0, 'msg' => 'ko');
  267. $field_size = config_get('field_size');
  268. $reqdoc_id = trim_and_limit($reqdoc_id,$field_size->req_docid);
  269. $title = trim_and_limit($title,$field_size->req_title);
  270. $tproject_id = $this->tree_mgr->getTreeRoot($srs_id);
  271. $op = $this->check_basic_data($srs_id,$tproject_id,$title,$reqdoc_id);
  272. $result['msg'] = $op['status_ok'] ? $result['msg'] : $op['msg'];
  273. if( $op['status_ok'] )
  274. {
  275. $result = $this->create_req_only($srs_id,$reqdoc_id,$title,$user_id,$node_order);
  276. if($result["status_ok"])
  277. {
  278. if (config_get('internal_links')->enable )
  279. {
  280. $scope = req_link_replace($this->db, $scope, $tproject_id);
  281. }
  282. $version_number = 1;
  283. $op = $this->create_version($result['id'],$version_number,$scope,$user_id,
  284. $status,$type,intval($expected_coverage));
  285. $result['msg'] = $op['status_ok'] ? $result['msg'] : $op['msg'];
  286. }
  287. }
  288. return $result;
  289. } // function end
  290. /*
  291. function: update
  292. args: id: requirement id
  293. version_id
  294. reqdoc_id
  295. title
  296. scope
  297. user_id: author
  298. status
  299. type
  300. $expected_coverage
  301. [skip_controls]
  302. returns: map: keys : status_ok, msg
  303. @internal revision
  304. 20091202 - franciscom -
  305. */
  306. function update($id,$version_id,$reqdoc_id,$title, $scope, $user_id, $status, $type,
  307. $expected_coverage,$node_order=null,$tproject_id=null,$skip_controls=0)
  308. {
  309. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  310. $result['status_ok'] = 1;
  311. $result['msg'] = 'ok';
  312. $db_now = $this->db->db_now();
  313. $field_size=config_get('field_size');
  314. // get SRSid, needed to do controls
  315. $rs=$this->get_by_id($id,$version_id);
  316. $req = $rs[0];
  317. $srs_id=$req['srs_id'];
  318. // try to avoid function calls when data is available on caller
  319. $tproject_id = is_null($tproject_id) ? $this->tree_mgr->getTreeRoot($srs_id): $tproject_id;
  320. /* contribution by asimon83/mx-julian */
  321. if (config_get('internal_links')->enable )
  322. {
  323. $scope = req_link_replace($this->db, $scope, $tproject_id);
  324. }
  325. /* end contribution by asimon83/mx-julian */
  326. $reqdoc_id=trim_and_limit($reqdoc_id,$field_size->req_docid);
  327. $title=trim_and_limit($title,$field_size->req_title);
  328. $chk=$this->check_basic_data($srs_id,$tproject_id,$title,$reqdoc_id,$id);
  329. if($chk['status_ok'] || $skip_controls)
  330. {
  331. $sql = array();
  332. $q = "/* $debugMsg */ UPDATE {$this->tables['nodes_hierarchy']} " .
  333. " SET name='" . $this->db->prepare_string($title) . "'";
  334. if( !is_null($node_order) )
  335. {
  336. $q .= ', node_order= ' . abs(intval($node_order));
  337. }
  338. $sql[] = $q . " WHERE id={$id}";
  339. $sql[] = "/* $debugMsg */ UPDATE {$this->tables['requirements']} " .
  340. " SET req_doc_id='" . $this->db->prepare_string($reqdoc_id) . "'" .
  341. " WHERE id={$id}";
  342. $sql[] = "/* $debugMsg */ UPDATE {$this->tables['req_versions']} " .
  343. " SET scope='" . $this->db->prepare_string($scope) . "', " .
  344. " status='" . $this->db->prepare_string($status) . "', " .
  345. " type='" . $this->db->prepare_string($type) . "', " .
  346. " modifier_id={$user_id}, modification_ts={$db_now}, " .
  347. " expected_coverage={$expected_coverage} " .
  348. " WHERE id={$version_id}";
  349. foreach($sql as $stm)
  350. {
  351. $qres = $this->db->exec_query($stm);
  352. if( !$qres )
  353. {
  354. $result['status_ok'] = 0;
  355. $result['msg'] = $this->db->error_msg;
  356. $result['sql'] = $stm;
  357. break;
  358. }
  359. }
  360. } // if($chk['status_ok'] || $skip_controls)
  361. else
  362. {
  363. $result['status_ok']=$chk['status_ok'];
  364. $result['msg']=$chk['msg'];
  365. }
  366. return $result;
  367. } //function end
  368. /*
  369. function: delete
  370. Requirement
  371. Requirement link to testcases
  372. Requirement relations
  373. Requirement custom fields values
  374. Attachments
  375. args: id: can be one id, or an array of id
  376. returns:
  377. */
  378. function delete($id,$version_id = self::ALL_VERSIONS)
  379. {
  380. $children = null;
  381. $where_clause_coverage = '';
  382. $where_clause_this = '';
  383. $deleteAll = false;
  384. $result = null;
  385. $doIt = true;
  386. if(is_array($id))
  387. {
  388. $id_list = implode(',',$id);
  389. $where_clause_coverage = " WHERE req_id IN ({$id_list})";
  390. $where_clause_this = " WHERE id IN ({$id_list})";
  391. }
  392. else
  393. {
  394. $where_clause_coverage = " WHERE req_id = {$id}";
  395. $where_clause_this = " WHERE id = {$id}";
  396. }
  397. // When deleting only one version, we need to check if we need to delete
  398. // requirement also.
  399. $children[] = $version_id;
  400. if( $version_id == self::ALL_VERSIONS)
  401. {
  402. $deleteAll = true;
  403. // I'm trying to speedup the next deletes
  404. $sql="SELECT NH.id FROM {$this->tables['nodes_hierarchy']} NH WHERE NH.parent_id ";
  405. if( is_array($id) )
  406. {
  407. $sql .= " IN (" .implode(',',$id) . ") ";
  408. }
  409. else
  410. {
  411. $sql .= " = {$id} ";
  412. }
  413. $children_rs=$this->db->fetchRowsIntoMap($sql,'id');
  414. $children = array_keys($children_rs);
  415. // Delete Custom fields
  416. $this->cfield_mgr->remove_all_design_values_from_node($id);
  417. // delete dependencies with test specification
  418. $sql = "DELETE FROM {$this->tables['req_coverage']} " . $where_clause_coverage;
  419. $result = $this->db->exec_query($sql);
  420. if ($result)
  421. {
  422. $doIt = true;
  423. $the_ids = is_array($id) ? $id : array($id);
  424. foreach($the_ids as $key => $value)
  425. {
  426. $result = $this->attachmentRepository->deleteAttachmentsFor($value,$this->attachmentTableName);
  427. }
  428. }
  429. }
  430. // Delete version info
  431. if( $doIt )
  432. {
  433. $where_clause_children = " WHERE id IN (" .implode(',',$children) . ") ";
  434. $sql = "DELETE FROM {$this->tables['req_versions']} " . $where_clause_children;
  435. $result = $this->db->exec_query($sql);
  436. $sql = "DELETE FROM {$this->tables['nodes_hierarchy']} " . $where_clause_children;
  437. $result = $this->db->exec_query($sql);
  438. }
  439. if( $deleteAll && $result)
  440. {
  441. $sql = "DELETE FROM {$this->object_table} " . $where_clause_this;
  442. $result = $this->db->exec_query($sql);
  443. $sql = "DELETE FROM {$this->tables['nodes_hierarchy']} " . $where_clause_this;
  444. $result = $this->db->exec_query($sql);
  445. //also delete relations to other requirements
  446. $this->delete_all_relations($id);
  447. }
  448. $result = (!$result) ? lang_get('error_deleting_req') : 'ok';
  449. return $result;
  450. }
  451. /** collect coverage of Requirement
  452. * @param string $req_id ID of req.
  453. * @return assoc_array list of test cases [id, title]
  454. *
  455. * rev: 20080226 - franciscom - get test case external id
  456. */
  457. function get_coverage($id)
  458. {
  459. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  460. $sql = "/* $debugMsg */ SELECT DISTINCT NHA.id,NHA.name,TCV.tc_external_id " .
  461. " FROM {$this->tables['nodes_hierarchy']} NHA, " .
  462. " {$this->tables['nodes_hierarchy']} NHB, " .
  463. " {$this->tables['tcversions']} TCV, " .
  464. " {$this->tables['req_coverage']} RC " .
  465. " WHERE RC.testcase_id = NHA.id " .
  466. " AND NHB.parent_id=NHA.id " .
  467. " AND TCV.id=NHB.id " .
  468. " AND RC.req_id={$id}";
  469. return $this->db->get_recordset($sql);
  470. }
  471. /*
  472. function: check_basic_data
  473. do checks on title and reqdoc id, for a requirement
  474. args: srs_id: req spec id (req parent)
  475. title
  476. reqdoc_id
  477. [id]: default null
  478. returns: map
  479. keys: status_ok
  480. msg
  481. */
  482. function check_basic_data($srs_id,$tproject_id,$title,$reqdoc_id,$id = null)
  483. {
  484. $req_cfg = config_get('req_cfg');
  485. $my_srs_id=$srs_id;
  486. $ret['status_ok'] = 1;
  487. $ret['msg'] = '';
  488. if ($title == "")
  489. {
  490. $ret['status_ok'] = 0;
  491. $ret['msg'] = lang_get("warning_empty_req_title");
  492. }
  493. if ($reqdoc_id == "")
  494. {
  495. $ret['status_ok'] = 0;
  496. $ret['msg'] .= " " . lang_get("warning_empty_reqdoc_id");
  497. }
  498. if($ret['status_ok'])
  499. {
  500. $ret['msg'] = 'ok';
  501. $rs = $this->getByDocID($reqdoc_id,$tproject_id);
  502. if(!is_null($rs) && (is_null($id) || !isset($rs[$id])))
  503. {
  504. $ret['msg'] = sprintf(lang_get("warning_duplicate_reqdoc_id"),$reqdoc_id);
  505. $ret['status_ok'] = 0;
  506. }
  507. }
  508. return $ret;
  509. }
  510. /*
  511. function: create_tc_from_requirement
  512. create testcases using requirements as input
  513. args:
  514. returns:
  515. */
  516. function create_tc_from_requirement($mixIdReq,$srs_id, $user_id, $tproject_id = null, $tc_count=null)
  517. {
  518. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  519. $tcase_mgr = new testcase($this->db);
  520. $tsuite_mgr = new testsuite($this->db);
  521. $req_cfg = config_get('req_cfg');
  522. $field_size = config_get('field_size');
  523. $auto_testsuite_name = $req_cfg->default_testsuite_name;
  524. $node_descr_type=$this->tree_mgr->get_available_node_types();
  525. $empty_steps = null;
  526. $empty_preconditions = ''; // fix for BUGID 2995
  527. $labels['tc_created'] = lang_get('tc_created');
  528. $output = null;
  529. $reqSet = is_array($mixIdReq) ? $mixIdReq : array($mixIdReq);
  530. /* contribution BUGID 2996, testcase creation */
  531. if( is_null($tproject_id) || $tproject_id == 0 )
  532. {
  533. $tproject_id = $this->tree_mgr->getTreeRoot($srs_id);
  534. }
  535. if ( $req_cfg->use_req_spec_as_testsuite_name )
  536. {
  537. $full_path = $this->tree_mgr->get_path($srs_id);
  538. $addition = " (" . lang_get("testsuite_title_addition") . ")";
  539. $truncate_limit = $field_size->testsuite_name - strlen($addition);
  540. // REQ_SPEC_A
  541. // |-- REQ_SPEC_A1
  542. // |-- REQ_SPEC_A2
  543. // |- REQ100
  544. // |- REQ101
  545. //
  546. // We will try to check if a test suite has already been created for
  547. // top REQ_SPEC_A (we do search using automatic generated name as search criteria).
  548. // If not => we need to create all path till leaves (REQ100 and REQ200)
  549. //
  550. //
  551. // First search: we use test project
  552. $parent_id = $tproject_id;
  553. $deep_create = false;
  554. foreach($full_path as $key => $node)
  555. {
  556. // follow hierarchy of test suites to create
  557. $tsuiteInfo = null;
  558. $testsuite_name = substr($node['name'],0,$truncate_limit). $addition;
  559. if( !$deep_create )
  560. {
  561. // child test suite with this name, already exists on current parent ?
  562. // At first a failure we will not check anymore an proceed with deep create
  563. $sql="/* $debugMsg */ SELECT id,name FROM {$this->tables['nodes_hierarchy']} NH " .
  564. " WHERE name='" . $this->db->prepare_string($testsuite_name) . "' " .
  565. " AND node_type_id=" . $node_descr_type['testsuite'] .
  566. " AND parent_id = {$parent_id} ";
  567. // If returns more that one record use ALWAYS first
  568. $tsuiteInfo = $this->db->fetchRowsIntoMap($sql,'id');
  569. }
  570. if( is_null($tsuiteInfo) )
  571. {
  572. $tsuiteInfo = $tsuite_mgr->create($parent_id,$testsuite_name,$req_cfg->testsuite_details);
  573. $output[] = sprintf(lang_get('testsuite_name_created'), $testsuite_name);
  574. $deep_create = true;
  575. }
  576. else
  577. {
  578. $tsuiteInfo = current($tsuiteInfo);
  579. $tsuite_id = $tsuiteInfo['id'];
  580. }
  581. $tsuite_id = $tsuiteInfo['id']; // last value here will be used as parent for test cases
  582. $parent_id = $tsuite_id;
  583. }
  584. $output[]=sprintf(lang_get('created_on_testsuite'), $testsuite_name);
  585. }
  586. else
  587. {
  588. // don't use req_spec as testsuite name
  589. // Warning:
  590. // We are not maintaining hierarchy !!!
  591. $sql=" SELECT id FROM {$this->tables['nodes_hierarchy']} NH " .
  592. " WHERE name='" . $this->db->prepare_string($auto_testsuite_name) . "' " .
  593. " AND parent_id=" . $testproject_id . " " .
  594. " AND node_type_id=" . $node_descr_type['testsuite'];
  595. $result = $this->db->exec_query($sql);
  596. if ($this->db->num_rows($result) == 1) {
  597. $row = $this->db->fetch_array($result);
  598. $tsuite_id = $row['id'];
  599. $label = lang_get('created_on_testsuite');
  600. } else {
  601. // not found -> create
  602. tLog('test suite:' . $auto_testsuite_name . ' was not found.');
  603. $new_tsuite=$tsuite_mgr->create($testproject_id,$auto_testsuite_name,$req_cfg->testsuite_details);
  604. $tsuite_id=$new_tsuite['id'];
  605. $label = lang_get('testsuite_name_created');
  606. }
  607. $output[]=sprintf($label, $auto_testsuite_name);
  608. }
  609. /* end contribution */
  610. // create TC
  611. $createOptions = array();
  612. $createOptions['check_names_for_duplicates'] = config_get('check_names_for_duplicates');
  613. $createOptions['action_on_duplicate_name'] = config_get('action_on_duplicate_name');
  614. $testcase_importance_default = config_get('testcase_importance_default');
  615. // compute test case order
  616. $testcase_order = config_get('treemenu_default_testcase_order');
  617. $nt2exclude=array('testplan' => 'exclude_me','requirement_spec'=> 'exclude_me','requirement'=> 'exclude_me');
  618. $siblings = $this->tree_mgr->get_children($tsuite_id,$nt2exclude);
  619. if( !is_null($siblings) )
  620. {
  621. $dummy = end($siblings);
  622. $testcase_order = $dummy['node_order'];
  623. }
  624. foreach ($reqSet as $reqID)
  625. {
  626. $reqData = $this->get_by_id($reqID,requirement_mgr::LATEST_VERSION);
  627. $count = (!is_null($tc_count)) ? $tc_count[$reqID] : 1;
  628. $reqData = $reqData[0];
  629. // Generate name with progessive
  630. $instance=1;
  631. $getOptions = array('check_criteria' => 'like','access_key' => 'name');
  632. $itemSet = $tcase_mgr->getDuplicatesByName($reqData['title'],$tsuite_id,$getOptions);
  633. $nameSet = null;
  634. if( !is_null($itemSet) )
  635. {
  636. $nameSet = array_flip(array_keys($itemSet));
  637. }
  638. for ($idx = 0; $idx < $count; $idx++)
  639. {
  640. $testcase_order++;
  641. // We have a little problem to work on:
  642. // suppose you have created:
  643. // TC [1]
  644. // TC [2]
  645. // TC [3]
  646. // If we delete TC [2]
  647. // When I got siblings il will got 2, if I create new progressive using next,
  648. // it will be 3 => I will get duplicated name.
  649. //
  650. // Seems better option can be:
  651. // Get all siblings names, put on array, create name an check if exists, if true
  652. // generate a new name.
  653. // This may be at performance level is better than create name then check on db,
  654. // because this approach will need more queries to DB
  655. //
  656. $tcase_name = $reqData['title'] . " [{$instance}]";
  657. if( !is_null($nameSet) )
  658. {
  659. while( isset($nameSet[$tcase_name]) )
  660. {
  661. $instance++;
  662. $tcase_name = $reqData['title'] . " [{$instance}]";
  663. }
  664. }
  665. $nameSet[$tcase_name]=$tcase_name;
  666. // 20100106 - franciscom - multiple test case steps feature - removed expected_results
  667. // Julian - BUGID 2995
  668. $tcase = $tcase_mgr->create($tsuite_id,$tcase_name,$req_cfg->testcase_summary_prefix . $reqData['scope'] ,
  669. $empty_preconditions, $empty_steps,$user_id,null,
  670. $testcase_order,testcase::AUTOMATIC_ID,TESTCASE_EXECUTION_TYPE_MANUAL,
  671. $testcase_importance_default,$createOptions);
  672. $tcase_name = $tcase['new_name'] == '' ? $tcase_name : $tcase['new_name'];
  673. $output[]=sprintf($labels['tc_created'], $tcase_name);
  674. // create coverage dependency
  675. if (!$this->assign_to_tcase($reqData['id'],$tcase['id']) )
  676. {
  677. $output[] = 'Test case: ' . $tcase_name . " was not created";
  678. }
  679. }
  680. }
  681. return $output;
  682. }
  683. /*
  684. function: assign_to_tcase
  685. assign requirement(s) to test case
  686. args: req_id: can be an array of requirement id
  687. testcase_id
  688. returns: 1/0
  689. rev: 20090401 - franciscom - BUGID 2316 - refactored
  690. */
  691. function assign_to_tcase($req_id,$testcase_id)
  692. {
  693. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  694. $output = 0;
  695. if($testcase_id && $req_id)
  696. {
  697. $items = (array)$req_id;
  698. $in_clause = implode(",",$items);
  699. $sql = " /* $debugMsg */ SELECT req_id,testcase_id FROM {$this->tables['req_coverage']} " .
  700. " WHERE req_id IN ({$in_clause}) AND testcase_id = {$testcase_id}";
  701. $coverage = $this->db->fetchRowsIntoMap($sql,'req_id');
  702. $loop2do=count($items);
  703. for($idx=0; $idx < $loop2do; $idx++)
  704. {
  705. if( is_null($coverage) || !isset($coverage[$items[$idx]]) )
  706. {
  707. $sql = "INSERT INTO {$this->tables['req_coverage']} (req_id,testcase_id) " .
  708. "VALUES ({$items[$idx]},{$testcase_id})";
  709. $result = $this->db->exec_query($sql);
  710. if ($this->db->affected_rows() == 1)
  711. {
  712. $output = 1;
  713. $tcInfo = $this->tree_mgr->get_node_hierarchy_info($testcase_id);
  714. $reqInfo = $this->tree_mgr->get_node_hierarchy_info($items[$idx]);
  715. if($tcInfo && $reqInfo)
  716. {
  717. logAuditEvent(TLS("audit_req_assigned_tc",$reqInfo['name'],$tcInfo['name']),
  718. "ASSIGN",$this->object_table);
  719. }
  720. }
  721. }
  722. else
  723. {
  724. $output = 1;
  725. }
  726. }
  727. }
  728. return $output;
  729. }
  730. /*
  731. function: unassign_from_tcase
  732. args: req_id
  733. testcase_id
  734. returns:
  735. */
  736. function unassign_from_tcase($req_id,$testcase_id)
  737. {
  738. $output = 0;
  739. $sql = " DELETE FROM {$this->tables['req_coverage']} " .
  740. " WHERE req_id={$req_id} " .
  741. " AND testcase_id={$testcase_id}";
  742. $result = $this->db->exec_query($sql);
  743. if ($result && $this->db->affected_rows() == 1)
  744. {
  745. $tcInfo = $this->tree_mgr->get_node_hierarchy_info($testcase_id);
  746. $reqInfo = $this->tree_mgr->get_node_hierarchy_info($req_id);
  747. if($tcInfo && $reqInfo)
  748. {
  749. logAuditEvent(TLS("audit_req_assignment_removed_tc",$reqInfo['name'],
  750. $tcInfo['name']),"ASSIGN",$this->object_table);
  751. }
  752. $output = 1;
  753. }
  754. return $output;
  755. }
  756. /*
  757. function: bulk_assignment
  758. assign N requirements to M test cases
  759. Do not write audit info
  760. args: req_id: can be an array
  761. testcase_id: can be an array
  762. returns: number of assignments done
  763. */
  764. function bulk_assignment($req_id,$testcase_id)
  765. {
  766. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  767. $insertCounter=0; // just for debug
  768. $requirementSet=$req_id;
  769. $tcaseSet=$testcase_id;
  770. if( !is_array($req_id) )
  771. {
  772. $requirementSet=array($req_id);
  773. }
  774. if( !is_array($testcase_id) )
  775. {
  776. $tcaseSet=array($testcase_id);
  777. }
  778. $req_list=implode(",",$requirementSet);
  779. $tcase_list=implode(",",$tcaseSet);
  780. // Get coverage for this set of requirements and testcase, to be used
  781. // to understand if insert if needed
  782. $sql = " /* $debugMsg */ SELECT req_id,testcase_id FROM {$this->tables['req_coverage']} " .
  783. " WHERE req_id IN ({$req_list}) AND testcase_id IN ({$tcase_list})";
  784. $coverage = $this->db->fetchMapRowsIntoMap($sql,'req_id','testcase_id');
  785. $insert_sql = "INSERT INTO {$this->tables['req_coverage']} (req_id,testcase_id) ";
  786. foreach($tcaseSet as $tcid)
  787. {
  788. foreach($requirementSet as $reqid)
  789. {
  790. if( !isset($coverage[$reqid][$tcid]) )
  791. {
  792. $insertCounter++;
  793. $sql = $insert_sql . "VALUES ({$reqid},{$tcid})";
  794. $this->db->exec_query($sql);
  795. }
  796. }
  797. }
  798. return $insertCounter;
  799. }
  800. /*
  801. function: get_relationships
  802. args :
  803. returns:
  804. */
  805. function get_relationships($req_id)
  806. {
  807. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  808. $sql = " /* $debugMsg */ SELECT nodes_hierarchy.id,nodes_hierarchy.name " .
  809. " FROM {$this->tables['nodes_hierarchy']} nodes_hierarchy, " .
  810. " {$this->tables['req_coverage']} req_coverage " .
  811. " WHERE req_coverage.testcase_id = nodes_hierarchy.id " .
  812. " AND req_coverage.req_id={$req_id}";
  813. return ($this->db->get_recordset($sql));
  814. }
  815. /*
  816. function: get_all_for_tcase
  817. get all requirements assigned to a test case
  818. A filter can be applied to do search on all req spec,
  819. or only on one.
  820. args: testcase_id
  821. [srs_id]: default 'all'
  822. returns:
  823. */
  824. function get_all_for_tcase($testcase_id, $srs_id = 'all')
  825. {
  826. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  827. $sql = " /* $debugMsg */ SELECT REQ.id,REQ.req_doc_id,NHA.name AS title, " .
  828. " NHB.name AS req_spec_title,REQ_COVERAGE.testcase_id " .
  829. " FROM {$this->object_table} REQ, " .
  830. " {$this->tables['req_coverage']} REQ_COVERAGE," .
  831. " {$this->tables['nodes_hierarchy']} NHA," .
  832. " {$this->tables['nodes_hierarchy']} NHB," .
  833. " {$this->tables['req_specs']} RSPEC " ;
  834. $idList = implode(",",(array)$testcase_id);
  835. $sql .= " WHERE REQ_COVERAGE.testcase_id IN (" . $idList . ")";
  836. $sql .= " AND REQ.srs_id=RSPEC.id AND REQ_COVERAGE.req_id=REQ.id " .
  837. " AND NHA.id=REQ.id AND NHB.id=RSPEC.id " ;
  838. // if only for one specification is required
  839. if ($srs_id != 'all')
  840. {
  841. $sql .= " AND REQ.srs_id=" . $srs_id;
  842. }
  843. if (is_array($testcase_id))
  844. {
  845. return $this->db->fetchRowsIntoMap($sql,'testcase_id',true);
  846. }
  847. else
  848. {
  849. return $this->db->get_recordset($sql);
  850. }
  851. }
  852. /*
  853. function:
  854. args :
  855. returns:
  856. */
  857. function check_title($title)
  858. {
  859. $ret = array('status_ok' => 1, 'msg' => 'ok');
  860. if ($title == "")
  861. {
  862. $ret['status_ok'] = 0;
  863. $ret['msg'] = lang_get("warning_empty_req_title");
  864. }
  865. return $ret;
  866. }
  867. /*
  868. function:
  869. args :
  870. $nodes: array with req_id in order
  871. returns:
  872. */
  873. function set_order($map_id_order)
  874. {
  875. $this->tree_mgr->change_order_bulk($map_id_order);
  876. }
  877. /**
  878. * exportReqToXML
  879. *
  880. * @param int $id requirement id
  881. * @param int $tproject_id: optional default null.
  882. * useful to get custom fields (when this feature will be developed).
  883. *
  884. * @return string with XML code
  885. *
  886. */
  887. function exportReqToXML($id,$tproject_id=null)
  888. {
  889. // BUGID 2169
  890. $cfXML = $this->customFieldValuesAsXML($id,$tproject_id);
  891. $rootElem = "{{XMLCODE}}";
  892. $elemTpl = "\t" . "<requirement>" .
  893. "\n\t\t" . "<docid><![CDATA[||DOCID||]]></docid>" .
  894. "\n\t\t" . "<title><![CDATA[||TITLE||]]></title>" .
  895. "\n\t\t" . "<node_order><![CDATA[||NODE_ORDER||]]></node_order>".
  896. "\n\t\t" . "<description><![CDATA[\n||DESCRIPTION||\n]]></description>".
  897. "\n\t\t" . "<status><![CDATA[||STATUS||]]></status>" .
  898. "\n\t\t" . "<type><![CDATA[||TYPE||]]></type>" .
  899. "\n\t\t" . "<expected_coverage><![CDATA[||EXPECTED_COVERAGE||]]></expected_coverage>" .
  900. "\n\t\t" . $cfXML .
  901. "\n\t" . "</requirement>" . "\n";
  902. $info = array ( "||DOCID||" => "req_doc_id",
  903. "||TITLE||" => "title",
  904. "||DESCRIPTION||" => "scope",
  905. "||STATUS||" => "status",
  906. "||TYPE||" => "type",
  907. "||NODE_ORDER||" => "node_order",
  908. "||EXPECTED_COVERAGE||" => "expected_coverage",
  909. );
  910. $req = $this->get_by_id($id,requirement_mgr::LATEST_VERSION);
  911. $reqData[] = $req[0];
  912. $xmlStr = exportDataToXML($reqData,$rootElem,$elemTpl,$info,true);
  913. return $xmlStr;
  914. }
  915. /**
  916. * xmlToMapRequirement
  917. *
  918. */
  919. function xmlToMapRequirement($xml_item)
  920. {
  921. // Attention: following PHP Manual SimpleXML documentation, Please remember to cast
  922. // before using data from $xml,
  923. if( is_null($xml_item) )
  924. {
  925. return null;
  926. }
  927. $dummy=array();
  928. foreach($xml_item->attributes() as $key => $value)
  929. {
  930. $dummy[$key] = (string)$value; // See PHP Manual SimpleXML documentation.
  931. }
  932. $dummy['node_order'] = (int)$xml_item->node_order;
  933. $dummy['title'] = (string)$xml_item->title;
  934. $dummy['docid'] = (string)$xml_item->docid;
  935. $dummy['description'] = (string)$xml_item->description;
  936. $dummy['status'] = (string)$xml_item->status;
  937. $dummy['type'] = (string)$xml_item->type;
  938. $dummy['expected_coverage'] = (int)$xml_item->expected_coverage;
  939. if( property_exists($xml_item,'custom_fields') )
  940. {
  941. $dummy['custom_fields']=array();
  942. foreach($xml_item->custom_fields->children() as $key)
  943. {
  944. $dummy['custom_fields'][(string)$key->name]= (string)$key->value;
  945. }
  946. }
  947. return $dummy;
  948. }
  949. /**
  950. * createFromXML
  951. *
  952. */
  953. function createFromXML($xml,$tproject_id,$parent_id,$author_id,$filters = null,$options=null)
  954. {
  955. $user_feedback = null;
  956. $req = $this->xmlToMapRequirement($xml);
  957. $copy_req = null;
  958. $getOptions = array('output' => 'minimun');
  959. $has_filters = !is_null($filters);
  960. $my['options'] = array( 'actionOnDuplicate' => "update");
  961. $my['options'] = array_merge($my['options'], (array)$options);
  962. $labels = array('import_req_created' => '','import_req_skipped' =>'', 'import_req_updated' => '');
  963. foreach($labels as $key => $dummy)
  964. {
  965. $labels[$key] = lang_get($key);
  966. }
  967. // Check:
  968. // If item with SAME DOCID exists inside container
  969. // If there is a hit
  970. // We will follow user option: update,create new version
  971. //
  972. // If do not exist check must be repeated, but on WHOLE test project
  973. // If there is a hit -> we can not create
  974. // else => create
  975. //
  976. $getOptions = array('output' => 'minimun');
  977. $msgID = 'import_req_skipped';
  978. $check_in_reqspec = $this->getByDocID($req['docid'],$tproject_id,$parent_id,$getOptions);
  979. if(is_null($check_in_reqspec))
  980. {
  981. $check_in_tproject = $this->getByDocID($req['docid'],$tproject_id,null,$getOptions);
  982. if(is_null($check_in_tproject))
  983. {
  984. $this->create($parent_id,$req['docid'],$req['title'],$req['description'],
  985. $author_id,$req['status'],$req['type'],$req['expected_coverage'],
  986. $req['node_order']);
  987. $msgID = 'import_req_created';
  988. }
  989. else
  990. {
  991. // Can not have req with same req doc id on another branch => BLOCK
  992. // What to do if is Frozen ??? -> now ignore and update anyway
  993. $msgID = 'import_req_skipped';
  994. }
  995. }
  996. else
  997. {
  998. // Need to get Last Version no matter active or not.
  999. // What to do if is Frozen ??? -> now ignore and update anyway
  1000. $reqID = key($check_in_reqspec);
  1001. $last_version = $this->get_last_version_info($reqID);
  1002. $result = $this->update($reqID,$last_version['id'],$req['docid'],$req['title'],$req['description'],
  1003. $author_id,$req['status'],$req['type'],$req['expected_coverage'],
  1004. $req['node_order']);
  1005. $msgID = 'import_req_updated';
  1006. }
  1007. $user_feedback = array('doc_id' => $req['docid'],'title' => $req['title'],
  1008. 'import_status' => sprintf($labels[$msgID],$req['docid']));
  1009. return $user_feedback;
  1010. }
  1011. // ---------------------------------------------------------------------------------------
  1012. // Custom field related functions
  1013. // ---------------------------------------------------------------------------------------
  1014. /*
  1015. function: get_linked_cfields
  1016. Get all linked custom fields.
  1017. Remember that custom fields are defined at system wide level, and
  1018. has to be linked to a testproject, in order to be used.
  1019. args: id: requirement id
  1020. [parent_id]:
  1021. this information is vital, to get the linked custom fields.
  1022. null -> use requirement_id as starting point.
  1023. !is_null -> use this value as testproject id
  1024. returns: map/hash
  1025. key: custom field id
  1026. value: map with custom field definition and value assigned for choosen requirement,
  1027. with following keys:
  1028. id: custom field id
  1029. name
  1030. label
  1031. type: custom field type
  1032. possible_values: for custom field
  1033. default_value
  1034. valid_regexp
  1035. length_min
  1036. length_max
  1037. show_on_design
  1038. enable_on_design
  1039. show_on_execution
  1040. enable_on_execution
  1041. display_order
  1042. value: value assigned to custom field for this requirement
  1043. null if for this requirement custom field was never edited.
  1044. node_id: requirement id
  1045. null if for this requirement, custom field was never edited.
  1046. rev :
  1047. 20070302 - check for $id not null, is not enough, need to check is > 0
  1048. */
  1049. function get_linked_cfields($id,$parent_id=null)
  1050. {
  1051. $enabled = 1;
  1052. if (!is_null($id) && $id > 0)
  1053. {
  1054. $req_info = $this->get_by_id($id);
  1055. $req_info = $req_info[0];
  1056. $tproject_id = $req_info['testproject_id'];
  1057. }
  1058. else
  1059. {
  1060. $tproject_id = $parent_id;
  1061. }
  1062. $cf_map = $this->cfield_mgr->get_linked_cfields_at_design($tproject_id,$enabled,null,
  1063. 'requirement',$id);
  1064. return $cf_map;
  1065. }
  1066. /*
  1067. function: html_table_of_custom_field_inputs
  1068. Return html code, implementing a table with custom fields labels
  1069. and html inputs, for choosen requirement.
  1070. Used to manage user actions on custom fields values.
  1071. args: $id
  1072. [parent_id]: need to undertad to which testproject the requirement belongs.
  1073. this information is vital, to get the linked custom fields.
  1074. null -> use requirement_id as starting point.
  1075. !is_null -> use this value as starting point.
  1076. [$name_suffix]: must start with '_' (underscore).
  1077. Used when we display in a page several items
  1078. (example during test case execution, several test cases)
  1079. that have the same custom fields.
  1080. In this kind of situation we can use the item id as name suffix.
  1081. returns: html string
  1082. */
  1083. function html_table_of_custom_field_inputs($id,$parent_id=null,$name_suffix='')
  1084. {
  1085. $NO_WARNING_IF_MISSING=true;
  1086. $cf_smarty = '';
  1087. $cf_map = $this->get_linked_cfields($id,$parent_id);
  1088. if(!is_null($cf_map))
  1089. {
  1090. $cf_smarty = "<table>";
  1091. foreach($cf_map as $cf_id => $cf_info)
  1092. {
  1093. $label=str_replace(TL_LOCALIZE_TAG,'',
  1094. lang_get($cf_info['label'],null,$NO_WARNING_IF_MISSING));
  1095. $cf_smarty .= '<tr><td class="labelHolder">' . htmlspecialchars($label) . ":</td><td>" .
  1096. $this->cfield_mgr->string_custom_field_input($cf_info,$name_suffix) .
  1097. "</td></tr>\n";
  1098. }
  1099. $cf_smarty .= "</table>";
  1100. }
  1101. return $cf_smarty;
  1102. }
  1103. /*
  1104. function: html_table_of_custom_field_values
  1105. Return html code, implementing a table with custom fields labels
  1106. and custom fields values, for choosen requirement.
  1107. You can think of this function as some sort of read only version
  1108. of html_table_of_custom_field_inputs.
  1109. args: $id
  1110. returns: html string
  1111. */
  1112. function html_table_of_custom_field_values($id)
  1113. {
  1114. $NO_WARNING_IF_MISSING=true;
  1115. $PID_NO_NEEDED = null;
  1116. $cf_smarty = '';
  1117. $cf_map = $this->get_linked_cfields($id,$PID_NO_NEEDED);
  1118. if(!is_null($cf_map))
  1119. {
  1120. foreach($cf_map as $cf_id => $cf_info)
  1121. {
  1122. // if user has assigned a value, then node_id is not null
  1123. if($cf_info['node_id'])
  1124. {
  1125. $label = str_replace(TL_LOCALIZE_TAG,'',
  1126. lang_get($cf_info['label'],null,$NO_WARNING_IF_MISSING));
  1127. $cf_smarty .= '<tr><td class="labelHolder">' .
  1128. htmlspecialchars($label) . ":</td><td>" .
  1129. $this->cfield_mgr->string_custom_field_value($cf_info,$id) .
  1130. "</td></tr>\n";
  1131. }
  1132. }
  1133. if(trim($cf_smarty) != "")
  1134. {
  1135. $cf_smarty = "<table>" . $cf_smarty . "</table>";
  1136. }
  1137. }
  1138. return $cf_smarty;
  1139. } // function end
  1140. /*
  1141. function: values_to_db
  1142. write values of custom fields.
  1143. args: $hash:
  1144. key: custom_field_<field_type_id>_<cfield_id>.
  1145. Example custom_field_0_67 -> 0=> string field
  1146. $node_id:
  1147. [$cf_map]: hash -> all the custom fields linked and enabled
  1148. that are applicable to the node type of $node_id.
  1149. For the keys not present in $hash, we will write
  1150. an appropriate value according to custom field
  1151. type.
  1152. This is needed because when trying to udpate
  1153. with hash being $_REQUEST, $_POST or $_GET
  1154. some kind of custom fields (checkbox, list, multiple list)
  1155. when has been deselected by user.
  1156. rev:
  1157. */
  1158. function values_to_db($hash,$node_id,$cf_map=null,$hash_type=null)
  1159. {
  1160. $this->cfield_mgr->design_values_to_db($hash,$node_id,$cf_map,$hash_type);
  1161. }
  1162. /**
  1163. * customFieldValuesAsXML
  1164. *
  1165. * @param $id: requirement spec id
  1166. * @param $tproject_id: test project id
  1167. *
  1168. *
  1169. */
  1170. function customFieldValuesAsXML($id,$tproject_id)
  1171. {
  1172. $xml = null;
  1173. $cfMap=$this->get_linked_cfields($id,$tproject_id);
  1174. if( !is_null($cfMap) && count($cfMap) > 0 )
  1175. {
  1176. $xml = $this->cfield_mgr->exportValueAsXML($cfMap);
  1177. }
  1178. return $xml;
  1179. }
  1180. /*
  1181. function: getByDocID
  1182. get req information using document ID as access key.
  1183. args : doc_id:
  1184. [tproject_id]
  1185. [parent_id] -> req spec parent of requirement searched
  1186. default 0 -> case sensivite search
  1187. returns: map.
  1188. key: req spec id
  1189. value: req info, map with following keys:
  1190. id
  1191. doc_id
  1192. testproject_id
  1193. title
  1194. scope
  1195. */
  1196. function getByDocID($doc_id,$tproject_id=null,$parent_id=null, $options = null)
  1197. {
  1198. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  1199. $my['options'] = array( 'check_criteria' => '=', 'access_key' => 'id',
  1200. 'case' => 'sensitive', 'output' => 'standard');
  1201. $my['options'] = array_merge($my['options'], (array)$options);
  1202. $output=null;
  1203. $the_doc_id = $this->db->prepare_string(trim($doc_id));
  1204. switch($my['options']['check_criteria'])
  1205. {
  1206. case '=':
  1207. default:
  1208. $check_criteria = " = '{$the_doc_id}' ";
  1209. break;
  1210. case 'like':
  1211. $check_criteria = " LIKE '{$the_doc_id}%' ";
  1212. break;
  1213. }
  1214. $sql = " /* $debugMsg */ SELECT ";
  1215. switch($my['options']['output'])
  1216. {
  1217. case 'standard':
  1218. $sql .= " REQ.id,REQ.srs_id,REQ.req_doc_id,NH_REQ.name AS title, REQ_SPEC.testproject_id, " .
  1219. " NH_RSPEC.name AS req_spec_title, REQ_SPEC.doc_id AS req_spec_doc_id, NH_REQ.node_order ";
  1220. break;
  1221. case 'minimun':
  1222. $sql .= " REQ.id,REQ.srs_id,REQ.req_doc_id,NH_REQ.name AS title, REQ_SPEC.testproject_id";
  1223. break;
  1224. }
  1225. $sql .= " FROM {$this->object_table} REQ " .
  1226. " /* Get Req info from NH */ " .
  1227. " JOIN {$this->tables['nodes_hierarchy']} NH_REQ ON NH_REQ.id = REQ.id " .
  1228. " JOIN {$this->tables['req_specs']} REQ_SPEC ON REQ_SPEC.id = REQ.srs_id " .
  1229. " JOIN {$this->tables['nodes_hierarchy']} NH_RSPEC ON NH_RSPEC.id = REQ_SPEC.id " .
  1230. " WHERE REQ.req_doc_id {$check_criteria} ";
  1231. if( !is_null($tproject_id) )
  1232. {
  1233. $sql .= " AND REQ_SPEC.testproject_ID={$tproject_id}";
  1234. }
  1235. if( !is_null($parent_id) )
  1236. {
  1237. $sql .= " AND REQ.srs_id={$parent_id}";
  1238. }
  1239. $output = $this->db->fetchRowsIntoMap($sql,$my['options']['access_key']);
  1240. return $output;
  1241. }
  1242. /**
  1243. * Copy a requirement to a new requirement specification
  1244. * requirement DOC ID will be changed because must be unique inside
  1245. * MASTER CONTAINER (test project)
  1246. *
  1247. * @param integer $id: requirement ID
  1248. * @param integer $parent_id: target req spec id (where we want to copy)
  1249. * @param integer $user_id: who is requesting copy
  1250. * @param integer $tproject_id: optional, is null will be computed here
  1251. * @param array $options: map
  1252. *
  1253. */
  1254. function copy_to($id,$parent_id,$user_id,$tproject_id=null,$options=null)
  1255. {
  1256. $new_item = array('id' => -1, 'status_ok' => 0, 'msg' => 'ok', 'mappings' => null);
  1257. $my['options'] = array('copy_also' => null);
  1258. $my['options'] = array_merge($my['options'], (array)$options);
  1259. if( is_null($my['options']['copy_also']) )
  1260. {
  1261. $my['options']['copy_also'] = array('testcase_assignment' => true);
  1262. }
  1263. $root = $tproject_id;
  1264. if( !is_null($root) )
  1265. {
  1266. $reqSpecMgr = new requirement_spec_mgr($this->db);
  1267. $target = $reqSpecMgr->get_by_id($parent_id);
  1268. $root = $target['testproject_id'];
  1269. }
  1270. $target_doc = $this->generateDocID($id,$root);
  1271. $item_versions = $this->get_by_id($id);
  1272. if($item_versions)
  1273. {
  1274. $new_item = $this->create_req_only($parent_id,$target_doc,$item_versions[0]['title'],
  1275. $item_versions[0]['author_id'],$item_versions[0]['node_order']);
  1276. if ($new_item['status_ok'])
  1277. {
  1278. $ret['status_ok']=1;
  1279. $new_item['mappings'][$id] = $new_item['id'];
  1280. foreach($item_versions as $req_version)
  1281. {
  1282. $op = $this->create_version($new_item['id'],$req_version['version'],
  1283. $req_version['scope'],$req_version['author_id'],
  1284. $req_version['status'],$req_version['type'],
  1285. $req_version['expected_coverage']);
  1286. $new_item['mappings'][$req_version['id']] = $op['id'];
  1287. }
  1288. $this->copy_cfields($id,$new_item['id']);
  1289. $this->copy_attachments($id,$new_item['id']);
  1290. if( isset($my['options']['copy_also']['testcase_assignment']) &&
  1291. $my['options']['copy…

Large files files are truncated, but you can click here to view the full file