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

/lib/functions/tree.class.php

https://bitbucket.org/pfernandez/testlink1.9.6
PHP | 1367 lines | 674 code | 121 blank | 572 comment | 73 complexity | 672d013e1c05c1cd9e4a0f7652a3276c 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
  8. * @copyright 2005-2009, TestLink community
  9. * @version CVS: $Id: tree.class.php,v 1.86 2010/04/11 08:47:21 franciscom Exp $
  10. * @link http://www.teamst.org/index.php
  11. *
  12. * @internal Revisions:
  13. *
  14. * 20100317 - franciscom - get_node_hierarchy_info() interface changes.
  15. * 20100306 - franciscom - get_subtree_list() new argument to change output type
  16. * new method() - getAllItemsID - BUGID 0003003: EXTJS does not count # req's
  17. * 20100209 - franciscom - BUGID 3147 - Delete test project with requirements defined crashed with memory exhausted
  18. * 20091220 - franciscom - new method createHierarchyMap()
  19. * 20090926 - franciscom - get_subtree() - interface changes
  20. * 20090923 - franciscom - get_full_path_verbose() - fixed bug
  21. * 20090905 - franciscom - get_full_path_verbose() new options
  22. * 20090801 - franciscom - new method nodeNameExists()
  23. * 20090726 - franciscom - BUGID 2728
  24. * 20090607 - franciscom - refactoring to manage table prefix
  25. * 20090413 - franciscom - BUGID - get_full_path_verbose() interface changes
  26. * 20090313 - franciscom - added getTreeRoot()
  27. * 20090207 - franciscom - new method check_name_is_unique()
  28. * 20081227 - franciscom - new method - get_full_path_verbose()
  29. */
  30. /**
  31. * @package TestLink
  32. */
  33. class tree extends tlObject
  34. {
  35. // configurable values - pseudoconstants
  36. var $node_types = array( 1 => 'testproject','testsuite',
  37. 'testcase','tcversion','testplan',
  38. 'requirement_spec','requirement','req_version');
  39. // key: node type id, value: class name
  40. var $class_name = array( 1 => 'testproject','testsuite',
  41. 'testcase',null,'testplan',
  42. 'requirement_spec_mgr','requirement_mgr',null);
  43. var $node_descr_id = array();
  44. var $node_tables = array('testproject' => 'testprojects',
  45. 'testsuite' => 'testsuites',
  46. 'testplan' => 'testplans',
  47. 'testcase' => 'testcases',
  48. 'tcversion' => 'tcversions',
  49. 'requirement_spec' =>'req_specs',
  50. 'requirement' => 'requirements',
  51. 'req_version' => 'req_versions');
  52. var $ROOT_NODE_TYPE_ID = 1;
  53. var $ROOT_NODE_PARENT_ID = NULL;
  54. /** @var resource database handler */
  55. var $db;
  56. /**
  57. * Class costructor
  58. * @param resource &$db reference to database handler
  59. */
  60. function __construct(&$db)
  61. {
  62. parent::__construct();
  63. $this->db = &$db;
  64. $this->node_descr_id = array_flip($this->node_types);
  65. $this->object_table = $this->tables['nodes_hierarchy'];
  66. }
  67. /**
  68. * get info from node_types table, regarding node types
  69. * that can be used in a tree.
  70. *
  71. * @return array map
  72. * key: description: single human friendly string describing node type
  73. * value: numeric code used to identify a node type
  74. */
  75. function get_available_node_types()
  76. {
  77. static $s_nodeTypes;
  78. if (!$s_nodeTypes)
  79. {
  80. $sql = " SELECT * FROM {$this->tables['node_types']} ";
  81. $s_nodeTypes = $this->db->fetchColumnsIntoMap($sql,"description","id");
  82. }
  83. return $s_nodeTypes;
  84. }
  85. /**
  86. * creates a new root node in the hierarchy table.
  87. * root node is tree starting point.
  88. *
  89. * @param string $name node name; default=''
  90. * @return integer node ID
  91. */
  92. function new_root_node($name = '')
  93. {
  94. $this->new_node(null,$this->ROOT_NODE_TYPE_ID,$name,1);
  95. return $this->db->insert_id($this->object_table);
  96. }
  97. /*
  98. function: new_node
  99. creates a new node in the hierarchy table.
  100. root node is tree starting point.
  101. args : parent_id: node id of new node parent
  102. node_type_id: node type
  103. [name]: node name. default=''
  104. [node_order]= order on tree structure. default=0
  105. [node_id]= id to assign to new node, if you don't want
  106. id bein created automatically.
  107. default=0 -> id must be created automatically.
  108. returns: node_id of the new node created
  109. */
  110. function new_node($parent_id,$node_type_id,$name='',$node_order=0,$node_id=0)
  111. {
  112. $sql = "INSERT INTO {$this->object_table} " .
  113. "(name,node_type_id,node_order";
  114. $values=" VALUES('" . $this->db->prepare_string($name). "'," .
  115. " {$node_type_id}," . intval($node_order);
  116. if ($node_id)
  117. {
  118. $sql .= ",id";
  119. $values .= ",{$node_id}";
  120. }
  121. if(is_null($parent_id))
  122. {
  123. $sql .= ") {$values} )";
  124. }
  125. else
  126. {
  127. $sql .= ",parent_id) {$values},{$parent_id})";
  128. }
  129. $this->db->exec_query($sql);
  130. return ($this->db->insert_id($this->object_table));
  131. }
  132. /*
  133. get all node hierarchy info from hierarchy table
  134. returns: node_id of the new node created
  135. */
  136. /*
  137. function: get_node_hierarchy_info
  138. returns the row from nodes_hierarchy table that has
  139. node_id as id.
  140. get all node hierarchy info from hierarchy table
  141. args : node_id: node id
  142. can be an array
  143. [parent_id]
  144. returns:
  145. */
  146. function get_node_hierarchy_info($node_id,$parent_id = null)
  147. {
  148. $sql = "SELECT * FROM {$this->object_table} WHERE id";
  149. $getidx=-1;
  150. $result=null;
  151. if( is_array($node_id) )
  152. {
  153. $sql .= " IN (" . implode(",",$node_id) . ") ";
  154. $result=$this->db->fetchRowsIntoMap($sql,'id');
  155. }
  156. else
  157. {
  158. $sql .= "= {$node_id}";
  159. if( !is_null($parent_id) )
  160. {
  161. $sql .= " AND parent_id={$parent_id} ";
  162. }
  163. $rs=$this->db->get_recordset($sql);
  164. $result=!is_null($rs) ? $rs[0] : null;
  165. }
  166. return $result;
  167. }
  168. /*
  169. function: get_subtree_list()
  170. get a string representing a list, where elements are separated
  171. by comma, with all nodes in tree starting on node_id.
  172. node is can be considered as root of subtree.
  173. args : node_id: root of subtree
  174. node_type_id: null => no filter
  175. if present ONLY NODES OF this type will be ANALIZED and traversed
  176. Example:
  177. TREE
  178. |__ TSUITE_1
  179. |
  180. |__TSUITE_2
  181. | |__TC_XZ
  182. |
  183. |__TC1
  184. |__TC2
  185. node_type_id = TC and ROOT=Tree => output=NULL
  186. node_type_id = TC and ROOT=TSUITE_1 => output=TC1,TC2
  187. output: null => list, not null => array
  188. returns: output=null => list (string with nodes_id, using ',' as list separator).
  189. output != null => array
  190. */
  191. function get_subtree_list($node_id,$node_type_id=null,$output=null)
  192. {
  193. $nodes = array();
  194. $this->_get_subtree_list($node_id,$nodes,$node_type_id);
  195. $node_list = is_null($output) ? implode(',',$nodes) : $nodes;
  196. return($node_list);
  197. }
  198. /*
  199. function: _get_subtree_list()
  200. private function (name start with _), that using recursion
  201. get an array with all nodes in tree starting on node_id.
  202. node is can be considered as root of subtree.
  203. args : node_id: root of subtree
  204. returns: array with nodes_id
  205. */
  206. function _get_subtree_list($node_id,&$node_list,$node_type_id=null)
  207. {
  208. $sql = "SELECT * from {$this->object_table} WHERE parent_id = {$node_id}";
  209. if( !is_null($node_type_id) )
  210. {
  211. $sql .= " AND node_type_id = {$node_type_id} ";
  212. }
  213. $result = $this->db->exec_query($sql);
  214. if (!$result || !$this->db->num_rows($result))
  215. {
  216. return;
  217. }
  218. while($row = $this->db->fetch_array($result))
  219. {
  220. $node_list[] = $row['id'];
  221. $this->_get_subtree_list($row['id'],$node_list,$node_type_id);
  222. }
  223. }
  224. /*
  225. function: delete_subtree
  226. delete all element on tree structure that forms a subtree
  227. that has as root or starting point node_id.
  228. args : node_id: root of subtree
  229. returns: array with nodes_id
  230. */
  231. function delete_subtree($node_id)
  232. {
  233. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  234. $children = $this->get_subtree_list($node_id);
  235. $id2del = $node_id;
  236. if($children != "")
  237. {
  238. $id2del .= ",{$children}";
  239. }
  240. $sql = "/* $debugMsg */ DELETE FROM {$this->object_table} WHERE id IN ({$id2del})";
  241. $result = $this->db->exec_query($sql);
  242. }
  243. /*
  244. function: get_path
  245. get list of nodes to traverse when you want to move
  246. from node A (node at level N) to node B (node at level M),
  247. where MUST BE ALLWAYS M < N, and remembering that level for root node is the minimun.
  248. This means path on tree backwards (to the upper levels).
  249. An array is used to represent list.
  250. Last array element contains data regarding Node A, first element (element with index 0)
  251. is data regarding child of node B.
  252. What data is returned depends on value of optional argument 'format'.
  253. Attention:
  254. 1 - destination node (node B) will be NOT INCLUDED in result.
  255. 2 - This is refactoring of original get_path method.
  256. args : node_id: start of path
  257. [to_node_id]: destination node. default null -> path to tree root.
  258. [format]: default 'full'
  259. defines type of elements of result array.
  260. format='full'
  261. Element is a map with following keys:
  262. id
  263. parent_id
  264. node_type_id
  265. node_order
  266. node_table
  267. name
  268. Example
  269. Is tree is :
  270. null
  271. \
  272. id=1 <--- Tree Root
  273. |
  274. + ------+
  275. / \ \
  276. id=9 id=2 id=8
  277. \
  278. id=3
  279. \
  280. id=4
  281. get_path(4), returns:
  282. (
  283. [0] => Array([id] => 2
  284. [parent_id] => 1
  285. [node_type_id] => 2
  286. [node_order] => 1
  287. [node_table] => testsuites
  288. [name] => TS1)
  289. [1] => Array([id] => 3
  290. [parent_id] => 2
  291. [node_type_id] => 2
  292. [node_order] => 1
  293. [node_table] => testsuites
  294. [name] => TS2)
  295. [2] => Array([id] => 4
  296. [parent_id] => 3
  297. [node_type_id] => 3
  298. [node_order] => 0
  299. [node_table] => testcases
  300. [name] => TC1)
  301. )
  302. format='simple'
  303. every element is a number=PARENT ID, array index = value
  304. For the above example result will be:
  305. (
  306. [1] => 1
  307. [2] => 2
  308. [3] => 3
  309. )
  310. returns: array
  311. */
  312. function get_path($node_id,$to_node_id = null,$format = 'full')
  313. {
  314. $the_path = array();
  315. $this->_get_path($node_id,$the_path,$to_node_id,$format);
  316. if( !is_null($the_path) && count($the_path) > 0 )
  317. {
  318. $the_path=array_reverse($the_path);
  319. }
  320. return $the_path;
  321. }
  322. /*
  323. function: _get_path
  324. This is refactoring of original get_path method.
  325. Attention:
  326. returns node in inverse order, that was done for original get_path
  327. args : node_id: start of path
  328. node_list: passed by reference, to build the result.
  329. [to_node_id]: destination node. default null -> path to tree root.
  330. [format]: default 'full'
  331. returns: array
  332. */
  333. function _get_path($node_id,&$node_list,$to_node_id=null,$format='full')
  334. {
  335. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  336. // look up the parent of this node
  337. $sql = "/* $debugMsg */ " .
  338. " SELECT * from {$this->object_table} " .
  339. " WHERE id = {$node_id} ";
  340. $result = $this->db->exec_query($sql);
  341. if( $this->db->num_rows($result) == 0 )
  342. {
  343. $node_list=null;
  344. return;
  345. }
  346. while ( $row = $this->db->fetch_array($result) )
  347. {
  348. // only continue if this $node isn't the root node
  349. // (that's the node with no parent)
  350. if ($row['parent_id'] != '' && $row['id'] != $to_node_id)
  351. {
  352. // Getting data from the node specific table
  353. $node_table = $this->node_tables[$this->node_types[$row['node_type_id']]];
  354. // the last part of the path to $node, is the name
  355. // of the parent of $node
  356. switch($format)
  357. {
  358. case 'full':
  359. $node_list[] = array('id' => $row['id'],
  360. 'parent_id' => $row['parent_id'],
  361. 'node_type_id' => $row['node_type_id'],
  362. 'node_order' => $row['node_order'],
  363. 'node_table' => $node_table,
  364. 'name' => $row['name'] );
  365. break;
  366. case 'simple':
  367. // Warning: starting node is NOT INCLUDED in node_list
  368. $node_list[$row['parent_id']] = $row['parent_id'];
  369. break;
  370. case 'points':
  371. $node_list[] = $row['id'];
  372. break;
  373. }
  374. // if( $format == "full" )
  375. // {
  376. // $node_list[] = array('id' => $row['id'],
  377. // 'parent_id' => $row['parent_id'],
  378. // 'node_type_id' => $row['node_type_id'],
  379. // 'node_order' => $row['node_order'],
  380. // 'node_table' => $node_table,
  381. // 'name' => $row['name'] );
  382. // }
  383. // else
  384. // {
  385. // $node_list[$row['parent_id']] = $row['parent_id'];
  386. // }
  387. // we should add the path to the parent of this node to the path
  388. $this->_get_path($row['parent_id'],$node_list,$to_node_id,$format);
  389. }
  390. }
  391. }
  392. /*
  393. function: change_parent
  394. change node parent, using this method you implement move operation.
  395. args : node_id: node/nodes that need(s) to changed.
  396. mixed type: single id or array containing set of id.
  397. parent_id: new parent
  398. returns: 1 -> operation OK
  399. rev : 20080330 - franciscom - changed node_id type, to allow bulk operation.
  400. */
  401. function change_parent($node_id, $parent_id)
  402. {
  403. $debugMsg='Class:' .__CLASS__ . ' - Method:' . __FUNCTION__ . ' :: ';
  404. if( is_array($node_id) )
  405. {
  406. $id_list = implode(",",$node_id);
  407. $where_clause = " WHERE id IN ($id_list) ";
  408. }
  409. else
  410. {
  411. $where_clause=" WHERE id = {$node_id}";
  412. }
  413. $sql = "/* $debugMsg */ UPDATE {$this->object_table} SET parent_id = {$parent_id} {$where_clause}";
  414. $result = $this->db->exec_query($sql);
  415. return $result ? 1 : 0;
  416. }
  417. /*
  418. function: get_children
  419. get nodes that have id as parent node.
  420. Children can be filtering according to node type.
  421. args : id: node
  422. [exclude_node_types]: map
  423. key: verbose description of node type to exclude.
  424. see get_available_node_types.
  425. value: anything is ok
  426. returns: array of maps that contain children nodes.
  427. map structure:
  428. id
  429. name
  430. parent_id
  431. node_type_id
  432. node_order
  433. node_table
  434. */
  435. function get_children($id,$exclude_node_types=null)
  436. {
  437. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  438. $sql = "/* $debugMsg */ SELECT * from {$this->object_table} " .
  439. " WHERE parent_id = {$id} ORDER BY node_order,id";
  440. $node_list=array();
  441. $result = $this->db->exec_query($sql);
  442. if( $this->db->num_rows($result) == 0 )
  443. {
  444. return(null);
  445. }
  446. while ( $row = $this->db->fetch_array($result) )
  447. {
  448. $node_table = $this->node_tables[$this->node_types[$row['node_type_id']]];
  449. if( !isset($exclude_node_types[$this->node_types[$row['node_type_id']]]))
  450. {
  451. $node_list[] = array('id' => $row['id'], 'parent_id' => $row['parent_id'],
  452. 'node_type_id' => $row['node_type_id'],
  453. 'node_order' => $row['node_order'],
  454. 'node_table' => $node_table,'name' => $row['name']);
  455. }
  456. }
  457. return ($node_list);
  458. }
  459. /*
  460. function: change_order_bulk
  461. change order for all nodes is present in nodes array.
  462. Order of node in tree, is set to position node has in nodes array.
  463. args :
  464. nodes: array where value is node_id. Node order = node position on array
  465. returns: -
  466. */
  467. function change_order_bulk($nodes)
  468. {
  469. foreach($nodes as $order => $node_id)
  470. {
  471. $order = abs(intval($order));
  472. $node_id = intval($node_id);
  473. $sql = "UPDATE {$this->object_table} SET node_order = {$order} WHERE id = {$node_id}";
  474. $result = $this->db->exec_query($sql);
  475. }
  476. }
  477. /*
  478. function: change_child_order
  479. will change order of children of parent id, to position
  480. choosen node on top or bottom of children.
  481. args:
  482. parent_id: node used as root of a tree.
  483. node_id: node which we want to reposition
  484. $top_bottom: possible values 'top', 'bottom'
  485. [exclude_node_types]: map
  486. key: verbose description of node type to exclude.
  487. see get_available_node_types.
  488. value: anything is ok
  489. returns: -
  490. */
  491. function change_child_order($parent_id,$node_id,$top_bottom,$exclude_node_types=null)
  492. {
  493. $node_type_filter='';
  494. if( !is_null($exclude_node_types) )
  495. {
  496. $types=implode("','",array_keys($exclude_node_types));
  497. $node_type_filter=" AND NT.description NOT IN ('{$types}') ";
  498. }
  499. $sql = " SELECT NH.id, NH.node_order, NH.name " .
  500. " FROM {$this->object_table} NH, {$this->tables['node_types']} NT " .
  501. " WHERE NH.node_type_id=NT.id " .
  502. " AND NH.parent_id = {$parent_id} AND NH.id <> {$node_id} " .
  503. $node_type_filter .
  504. " ORDER BY NH.node_order,NH.id";
  505. $children=$this->db->get_recordset($sql);
  506. switch ($top_bottom)
  507. {
  508. case 'top':
  509. $no[]=$node_id;
  510. if( !is_null($children) )
  511. {
  512. foreach($children as $key => $value)
  513. {
  514. $no[]=$value['id'];
  515. }
  516. }
  517. break;
  518. case 'bottom':
  519. $new_order=$this->getBottomOrder($parent_id)+1;
  520. $no[$new_order]=$node_id;
  521. break;
  522. }
  523. $this->change_order_bulk($no);
  524. }
  525. /*
  526. function: getBottomOrder
  527. given a node id to be used as parent, returns the max(node_order) from the children nodes.
  528. We consider this bottom order.
  529. args: parentID:
  530. returns: order
  531. */
  532. function getBottomOrder($parentID)
  533. {
  534. $sql="SELECT MAX(node_order) AS top_order" .
  535. " FROM {$this->object_table} " .
  536. " WHERE parent_id={$parentID} " .
  537. " GROUP BY parent_id";
  538. $rs=$this->db->get_recordset($sql);
  539. return $rs[0]['top_order'];
  540. }
  541. /*
  542. function: get_subtree
  543. Giving a node_id, get the nodes that forma s subtree that
  544. has node_id as root or starting point.
  545. Is possible to exclude:
  546. branches that has as staring node, node of certain types.
  547. children of some node types.
  548. full branches.
  549. args :
  550. [filters] map with following keys
  551. [exclude_node_types]: map/hash.
  552. default: null -> no exclusion filter will be applied.
  553. Branches starting with nodes of type detailed, will not be
  554. visited => no information will be returned.
  555. key: verbose description of node type to exclude.
  556. (see get_available_node_types).
  557. value: can be any value, because is not used,anyway is suggested
  558. to use 'exclude_me' as value.
  559. Example:
  560. array('testplan' => 'exclude_me')
  561. Node of type tesplan, will be excluded.
  562. [exclude_children_of]: map/hash
  563. default: null -> no exclusion filter will be applied.
  564. When traversing tree if the type of a node child, of node under analisys,
  565. is contained in this map, traversing of branch starting with this child node
  566. will not be done.
  567. key: verbose description of node type to exclude.
  568. (see get_available_node_types).
  569. value: can be any value, because is not used,anyway is suggested
  570. to use 'exclude_my_children' as value.
  571. Example:
  572. array('testcase' => 'exclude_my_children')
  573. Children of testcase nodes, (tcversion nodes) will be EXCLUDED.
  574. [exclude_branches]: map/hash.
  575. default: null -> no exclusion filter will be applied.
  576. key: node id.
  577. value: anything is ok.
  578. When traversing tree branches that have these node is, will
  579. not be visited => no information will be retrieved.
  580. [and_not_in_clause]: sql filter to include in sql sentence used to retrieve nodes.
  581. default: null -> no action taken.
  582. [options]: map with following keys
  583. [recursive]: changes structure of returned structure.
  584. default: false -> a flat array will be generated
  585. true -> a map with recursive structure will be generated.
  586. false returns array, every element is a map with following keys:
  587. id
  588. parent_id
  589. node_type_id
  590. node_order
  591. node_table
  592. name
  593. true returns a map, with only one element
  594. key: childNodes.
  595. value: array, that represents a tree branch.
  596. Array elements are maps with following keys:
  597. id
  598. parent_id
  599. node_type_id
  600. node_order
  601. node_table
  602. name
  603. childNodes -> (array)
  604. returns: array or map
  605. rev:
  606. 20090311 - franciscom
  607. changed management of order_cfg.
  608. 20080614 - franciscom
  609. added key_type arguments, useful only fo recursive mode
  610. */
  611. function get_subtree($node_id,$filters=null,$options=null)
  612. {
  613. $my['filters'] = array('exclude_node_types' => null, 'exclude_children_of' => null,
  614. 'exclude_branches' => null,'and_not_in_clause' => '');
  615. $my['options'] = array('recursive' => false, 'order_cfg' => array("type" =>'spec_order'),
  616. 'key_type' => 'std');
  617. // Cast to array to handle $options = null
  618. $my['filters'] = array_merge($my['filters'], (array)$filters);
  619. $my['options'] = array_merge($my['options'], (array)$options);
  620. $the_subtree = array();
  621. // Generate NOT IN CLAUSE to exclude some node types
  622. $not_in_clause = $my['filters']['and_not_in_clause'];
  623. if(!is_null($my['filters']['exclude_node_types']))
  624. {
  625. $exclude = array();
  626. foreach($my['filters']['exclude_node_types'] as $the_key => $elem)
  627. {
  628. $exclude[] = $this->node_descr_id[$the_key];
  629. }
  630. $not_in_clause .= " AND node_type_id NOT IN (" . implode(",",$exclude) . ")";
  631. }
  632. if ($my['options']['recursive'])
  633. {
  634. $this->_get_subtree_rec($node_id,$the_subtree,$not_in_clause,
  635. $my['filters']['exclude_children_of'],
  636. $my['filters']['exclude_branches'],
  637. $my['options']['order_cfg'],$my['options']['key_type']);
  638. }
  639. else
  640. {
  641. $this->_get_subtree($node_id,$the_subtree,$not_in_clause,
  642. $my['filters']['exclude_children_of'],
  643. $my['filters']['exclude_branches'],$my['options']['order_cfg']);
  644. }
  645. return $the_subtree;
  646. }
  647. // 20061008 - franciscom - added ID in order by clause
  648. //
  649. // 20060312 - franciscom
  650. // Changed and improved following some Andreas Morsing advice.
  651. //
  652. // I would like this method will be have PRIVate scope, but seems not possible in PHP4
  653. // that's why I've prefixed with _
  654. //
  655. function _get_subtree($node_id,&$node_list,$and_not_in_clause='',
  656. $exclude_children_of=null,
  657. $exclude_branches=null,
  658. $order_cfg=array("type" =>'spec_order'))
  659. {
  660. switch($order_cfg['type'] )
  661. {
  662. case 'spec_order':
  663. $sql = " SELECT * from {$this->object_table} " .
  664. " WHERE parent_id = {$node_id} {$and_not_in_clause}" .
  665. " ORDER BY node_order,id";
  666. break;
  667. case 'exec_order':
  668. // REMEMBER THAT DISTINCT IS NOT NEEDED when you does UNION
  669. $sql="SELECT s* FROM ( SELECT NH.node_order AS spec_order," .
  670. " NH.node_order AS node_order, NH.id, NH.parent_id," .
  671. " NH.name, NH.node_type_id" .
  672. " FROM {$this->object_table} NH, {$this->tables['node_types']} NT" .
  673. " WHERE parent_id = {$node_id}" .
  674. " AND NH.node_type_id=NT.id" .
  675. " AND NT.description <> 'testcase' {$and_not_in_clause}" .
  676. " UNION" .
  677. " SELECT NHA.node_order AS spec_order, " .
  678. " T.node_order AS node_order, NHA.id, NHA.parent_id, " .
  679. " NHA.name, NHA.node_type_id" .
  680. " FROM {$this->object_table} NHA, {$this->object_table} NHB," .
  681. " {$this->tables['testplan_tcversions']} T,{$this->tables['node_types']} NT" .
  682. " WHERE NHA.id=NHB.parent_id " .
  683. " AND NHA.node_type_id=NT.id" .
  684. " AND NHB.id=T.tcversion_id " .
  685. " AND NT.description = 'testcase'" .
  686. " AND NHA.parent_id = {$node_id}" .
  687. " AND T.testplan_id = {$order_cfg['tplan_id']}) AC" .
  688. " ORDER BY node_order,spec_order,id";
  689. break;
  690. }
  691. $result = $this->db->exec_query($sql);
  692. if( $this->db->num_rows($result) == 0 )
  693. {
  694. return;
  695. }
  696. while ( $row = $this->db->fetch_array($result) )
  697. {
  698. if( !isset($exclude_branches[$row['id']]) )
  699. {
  700. $node_table = $this->node_tables[$this->node_types[$row['node_type_id']]];
  701. $node_list[] = array('id' => $row['id'],
  702. 'parent_id' => $row['parent_id'],
  703. 'node_type_id' => $row['node_type_id'],
  704. 'node_order' => $row['node_order'],
  705. 'node_table' => $node_table,
  706. 'name' => $row['name']);
  707. // Basically we use this because:
  708. // 1. Sometimes we don't want the children if the parent is a testcase,
  709. // due to the version management
  710. //
  711. // 2. Sometime we want to exclude all descendants (branch) of a node.
  712. //
  713. // [franciscom]:
  714. // I think ( but I have no figures to backup my thoughts) doing this check and
  715. // avoiding the function call is better that passing a condition that will result
  716. // in a null result set.
  717. //
  718. //
  719. if( !isset($exclude_children_of[$this->node_types[$row['node_type_id']]]) &&
  720. !isset($exclude_branches[$row['id']]) )
  721. {
  722. $this->_get_subtree($row['id'],$node_list,$and_not_in_clause,
  723. $exclude_children_of,$exclude_branches,$order_cfg);
  724. }
  725. }
  726. }
  727. } // function end
  728. // 20061008 - franciscom - added ID in order by clause
  729. //
  730. function _get_subtree_rec($node_id,&$pnode,$and_not_in_clause = '',
  731. $exclude_children_of = null,$exclude_branches = null,
  732. $order_cfg = array("type" =>'spec_order'),$key_type = 'std')
  733. {
  734. static $s_testCaseNodeTypeID;
  735. if (!$s_testCaseNodeTypeID)
  736. {
  737. $s_testCaseNodeTypeID = $this->node_descr_id['testcase'];
  738. }
  739. switch($order_cfg['type'])
  740. {
  741. case 'spec_order':
  742. $sql = " SELECT * FROM {$this->object_table} " .
  743. " WHERE parent_id = {$node_id} {$and_not_in_clause}" .
  744. " ORDER BY node_order,id";
  745. break;
  746. case 'exec_order':
  747. // REMEMBER THAT DISTINCT IS NOT NEEDED when you does UNION
  748. $sql="SELECT * FROM ( SELECT NH.node_order AS spec_order," .
  749. " NH.node_order AS node_order, NH.id, NH.parent_id," .
  750. " NH.name, NH.node_type_id " .
  751. " FROM {$this->tables['nodes_hierarchy']} NH" .
  752. " WHERE parent_id = {$node_id}" .
  753. " AND node_type_id <> {$s_testCaseNodeTypeID} {$and_not_in_clause}" .
  754. " UNION" .
  755. " SELECT NHA.node_order AS spec_order, " .
  756. " T.node_order AS node_order, NHA.id, NHA.parent_id, " .
  757. " NHA.name, NHA.node_type_id " .
  758. " FROM {$this->tables['nodes_hierarchy']} NHA, " .
  759. " {$this->tables['nodes_hierarchy']} NHB," .
  760. " {$this->tables['testplan_tcversions']} T" .
  761. " WHERE NHA.id=NHB.parent_id " .
  762. " AND NHA.node_type_id = {$s_testCaseNodeTypeID}" .
  763. " AND NHB.id=T.tcversion_id " .
  764. " AND NHA.parent_id = {$node_id}" .
  765. " AND T.testplan_id = {$order_cfg['tplan_id']}) AC" .
  766. " ORDER BY node_order,spec_order,id";
  767. break;
  768. }
  769. $children_key = 'childNodes';
  770. $result = $this->db->exec_query($sql);
  771. while($row = $this->db->fetch_array($result))
  772. {
  773. $rowID = $row['id'];
  774. $nodeTypeID = $row['node_type_id'];
  775. $nodeType = $this->node_types[$nodeTypeID];
  776. if(!isset($exclude_branches[$rowID]))
  777. {
  778. switch($key_type)
  779. {
  780. case 'std':
  781. $node_table = $this->node_tables[$nodeType];
  782. $node = array('id' => $rowID,
  783. 'parent_id' => $row['parent_id'],
  784. 'node_type_id' => $nodeTypeID,
  785. 'node_order' => $row['node_order'],
  786. 'node_table' => $node_table,
  787. 'name' => $row['name'],
  788. $children_key => null);
  789. break;
  790. case 'extjs':
  791. $node = array('text' => $row['name'],
  792. 'id' => $rowID,
  793. 'parent_id' => $row['parent_id'],
  794. 'node_type_id' => $nodeTypeID,
  795. 'position' => $row['node_order'],
  796. $children_key => null,
  797. 'leaf' => false);
  798. switch($nodeType)
  799. {
  800. case 'testproject':
  801. case 'testsuite':
  802. $node[$children_key] = null;
  803. break;
  804. case 'testcase':
  805. $node['leaf'] = true;
  806. break;
  807. }
  808. break;
  809. }
  810. // Basically we use this because:
  811. // 1. Sometimes we don't want the children if the parent is a testcase,
  812. // due to the version management
  813. //
  814. // 2. Sometime we want to exclude all descendants (branch) of a node.
  815. //
  816. // [franciscom]:
  817. // I think ( but I have no figures to backup my thoughts) doing this check and
  818. // avoiding the function call is better that passing a condition that will result
  819. // in a null result set.
  820. //
  821. //
  822. if(!isset($exclude_children_of[$nodeType]) &&
  823. !isset($exclude_branches[$rowID]))
  824. {
  825. $this->_get_subtree_rec($rowID,$node,$and_not_in_clause,
  826. $exclude_children_of,$exclude_branches,
  827. $order_cfg,$key_type);
  828. }
  829. $pnode[$children_key][] = $node;
  830. } // if(!isset($exclude_branches[$rowID]))
  831. } //while
  832. }
  833. /*
  834. function: get_full_path_verbose
  835. args:
  836. returns:
  837. */
  838. function get_full_path_verbose(&$items,$options=null)
  839. {
  840. $debugMsg='Class:' .__CLASS__ . ' - Method:' . __FUNCTION__ . ' :: ';
  841. $goto_root=null;
  842. $path_to=null;
  843. $all_nodes=array();
  844. $path_format = 'simple';
  845. $output_format = 'simple';
  846. if( !is_null($options) )
  847. {
  848. $path_format = isset($options['include_starting_point']) ? 'points' : $path_format;
  849. $output_format = isset($options['output_format']) ? $options['output_format'] : $output_format;
  850. }
  851. foreach((array)$items as $item_id)
  852. {
  853. $path_to[$item_id]['name']=$this->get_path($item_id,$goto_root,$path_format);
  854. $all_nodes = array_merge($all_nodes,$path_to[$item_id]['name']);
  855. }
  856. // BUGID 2728 - added check to avoid crash
  857. if( !is_null($all_nodes) && count($all_nodes) > 0)
  858. {
  859. // get only different items, to get descriptions
  860. $unique_nodes=implode(',',array_unique($all_nodes));
  861. $sql="/* $debugMsg */ " .
  862. " SELECT id,name FROM {$this->tables['nodes_hierarchy']} WHERE id IN ({$unique_nodes})";
  863. $decode=$this->db->fetchRowsIntoMap($sql,'id');
  864. foreach($path_to as $key => $elem)
  865. {
  866. foreach($elem['name'] as $idx => $node_id)
  867. {
  868. $path_to[$key]['name'][$idx]=$decode[$node_id]['name'];
  869. $path_to[$key]['node_id'][$idx]=$node_id;
  870. }
  871. }
  872. }
  873. else
  874. {
  875. $path_to=null;
  876. }
  877. if( !is_null($path_to) )
  878. {
  879. switch ($output_format)
  880. {
  881. case 'id_name':
  882. break;
  883. case 'simple':
  884. default:
  885. $keySet = array_keys($path_to);
  886. foreach($keySet as $key)
  887. {
  888. $path_to[$key] = $path_to[$key]['name'];
  889. }
  890. break;
  891. }
  892. }
  893. return $path_to;
  894. }
  895. /**
  896. * check if there is a sibbling node of same type that has same name
  897. *
  898. * @param string name: name to check
  899. * @param int node_type_id: node types to check.
  900. * @param int id: optional. exclude this node id from result set
  901. * this is useful when you want to check for name
  902. * existence during an update operation.
  903. * Using id you get node parent, to get sibblings.
  904. * If null parent_id argument must be present
  905. *
  906. * @param int parent_id: optional. Mandatory if id is null
  907. * Used to get children nodes to check for
  908. * name existence.
  909. *
  910. *
  911. * @return map ret: ret['status']=1 if name exists
  912. * 0 if name does not exist
  913. * ret['msg']= localized message
  914. *
  915. */
  916. function nodeNameExists($name,$node_type_id,$id=null,$parent_id=null)
  917. {
  918. $debugMsg='Class:' .__CLASS__ . ' - Method:' . __FUNCTION__ . ' :: ';
  919. $ret['status'] = 0;
  920. $ret['msg'] = '';
  921. if( is_null($id) && is_null($parent_id) )
  922. {
  923. $msg = $debugMsg . 'Error on call $id and $parent_id can not be both null';
  924. throw new Exception($msg);
  925. }
  926. $additionalFilters = '';
  927. $parentNodeID=$parent_id;
  928. if( !is_null($id) )
  929. {
  930. // Try to get parent id if not provided on method call.
  931. if( is_null($parentNodeID) )
  932. {
  933. $sql = "/* {$debugMsg} */ " .
  934. " SELECT parent_id FROM {$this->object_table} NHA " .
  935. " WHERE NHA.id = {$id} ";
  936. $rs = $this->db->get_recordset($sql);
  937. $parentNodeID=$rs[0]['parent_id'];
  938. }
  939. $additionalFilters = " AND NHA.id <> {$id} ";
  940. }
  941. $sql = "/* {$debugMsg} */ " .
  942. " SELECT count(0) AS qty FROM {$this->object_table} NHA " .
  943. " WHERE NHA.node_type_id = {$node_type_id} " .
  944. " AND NHA.name = '" . $this->db->prepare_string($name) . "'" .
  945. " AND NHA.parent_id = {$parentNodeID} {$additionalFilters} ";
  946. $rs = $this->db->get_recordset($sql);
  947. if( $rs[0]['qty'] > 0)
  948. {
  949. $ret['status'] = 1;
  950. $ret['msg'] = sprintf(lang_get('name_already_exists'),$name);
  951. }
  952. return $ret;
  953. }
  954. /**
  955. * getTreeRoot()
  956. *
  957. */
  958. function getTreeRoot($node_id)
  959. {
  960. $path = $this->get_path($node_id);
  961. $path_len = count($path);
  962. $root_node_id = ($path_len > 0)? $path[0]['parent_id'] : $node_id;
  963. return $root_node_id;
  964. }
  965. /**
  966. * delete_subtree_objects()
  967. *
  968. * ATTENTION: subtree root node ($node_id) IS NOT DELETED.
  969. *
  970. * BUGID 3147 - Delete test project with requirements defined crashed with memory exhausted
  971. */
  972. function delete_subtree_objects($root_id,$node_id,$and_not_in_clause = '',$exclude_children_of = null,
  973. $exclude_branches = null)
  974. {
  975. static $debugMsg;
  976. if( is_null($debugMsg) )
  977. {
  978. $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;
  979. }
  980. $sql = "/* $debugMsg */ SELECT NH.* FROM {$this->object_table} NH " .
  981. " WHERE NH.parent_id = {$node_id} {$and_not_in_clause} ";
  982. $rs = $this->db->get_recordset($sql);
  983. if( !is_null($rs) )
  984. {
  985. foreach($rs as $row)
  986. {
  987. $rowID = $row['id'];
  988. $nodeTypeID = $row['node_type_id'];
  989. $nodeType = $this->node_types[$nodeTypeID];
  990. $nodeClassName = $this->class_name[$nodeTypeID];
  991. if(!isset($exclude_branches[$rowID]))
  992. {
  993. // Basically we use this because:
  994. // 1. Sometimes we don't want the children if the parent is a testcase,
  995. // due to the version management
  996. //
  997. // 2. Sometime we want to exclude all descendants (branch) of a node.
  998. //
  999. if(!isset($exclude_children_of[$nodeType]) && !isset($exclude_branches[$rowID]))
  1000. {
  1001. $this->delete_subtree_objects(null,$rowID,$and_not_in_clause,
  1002. $exclude_children_of,$exclude_branches);
  1003. }
  1004. else
  1005. {
  1006. // For us in this method context this node is a leaf => just delete
  1007. if( !is_null($nodeClassName) )
  1008. {
  1009. $item_mgr = new $nodeClassName($this->db);
  1010. $item_mgr->delete($rowID);
  1011. }
  1012. }
  1013. } // if(!isset($exclude_branches[$rowID]))
  1014. } //while
  1015. }
  1016. // Must delete myself if I'm empty, only if I'm not subtree root.
  1017. // Done this way to avoid infinte recursion for some type of nodes
  1018. // that use this method as it's delete method. (example testproject).
  1019. if( !is_null($root_id) && ($node_id != $root_id) )
  1020. {
  1021. $children = $this->db->get_recordset($sql);
  1022. if( is_null($children) || count($children) == 0 )
  1023. {
  1024. $sql2 = "/* $debugMsg */ SELECT NH.* FROM {$this->object_table} NH " .
  1025. " WHERE NH.id = {$node_id}";
  1026. $node_info = $this->db->get_recordset($sql2);
  1027. if( isset($this->class_name[$node_info[0]['node_type_id']]) )
  1028. {
  1029. $className = $this->class_name[$node_info[0]['node_type_id']];
  1030. if( !is_null($className) )
  1031. {
  1032. $item_mgr = new $className($this->db);
  1033. $item_mgr->delete($node_id);
  1034. }
  1035. }
  1036. else
  1037. {
  1038. // need to signal error - TO BE DONE
  1039. }
  1040. }
  1041. } // if( $node_id != $root_id )
  1042. }
  1043. /*
  1044. [$mode]: dotted -> $level number of dot characters are appended to
  1045. the left of item name to create an indent effect.
  1046. Level indicates on what tree layer item is positioned.
  1047. Example:
  1048. null
  1049. \
  1050. id=1 <--- Tree Root = Level 0
  1051. |
  1052. + ------+
  1053. / \ \
  1054. id=9 id=2 id=8 <----- Level 1
  1055. \
  1056. id=3 <----- Level 2
  1057. \
  1058. id=4 <----- Level 3
  1059. key: item id (= node id on tree).
  1060. value: every array element is an string, containing item name.
  1061. Result example:
  1062. 2 .TS1
  1063. 3 ..TS2
  1064. 9 .20071014-16:22:07 TS1
  1065. 10 ..TS2
  1066. array -> key: item id (= node id on tree).
  1067. value: every array element is a map with the following keys
  1068. 'name', 'level'
  1069. 2 array(name => 'TS1',level => 1)
  1070. 3 array(name => 'TS2',level => 2)
  1071. 9 array(name => '20071014-16:22:07 TS1',level =>1)
  1072. 10 array(name => 'TS2', level => 2)
  1073. */
  1074. function createHierarchyMap($array2map,$mode='dotted')
  1075. {
  1076. $hmap=array();
  1077. $the_level = 1;
  1078. $level = array();
  1079. $pivot = $array2map[0];
  1080. foreach($array2map as $elem)
  1081. {
  1082. $current = $elem;
  1083. if ($pivot['id'] == $current['parent_id'])
  1084. {
  1085. $the_level++;
  1086. $level[$current['parent_id']]=$the_level;
  1087. }
  1088. else if ($pivot['parent_id'] != $current['parent_id'])
  1089. {
  1090. $the_level = $level[$current['parent_id']];
  1091. }
  1092. switch($mode)
  1093. {
  1094. case 'dotted':
  1095. $hmap[$current['id']] = str_repeat('.',$the_level) . $current['name'];
  1096. break;
  1097. case 'array':
  1098. $hmap[$current['id']] = array('name' => $current['name'], 'level' =>$the_level);
  1099. break;
  1100. }
  1101. // update pivot
  1102. $level[$current['parent_id']]= $the_level;
  1103. $pivot=$elem;
  1104. }
  1105. return $hmap;
  1106. }
  1107. /**
  1108. * getAllItemsID
  1109. *
  1110. * @internal revisions
  1111. * based on code from testproject->get_all_testcases_id
  1112. *
  1113. */
  1114. function getAllItemsID($parentList,&$itemSet,$coupleTypes)
  1115. {
  1116. static $debugMsg;
  1117. if (!$debugMsg)
  1118. {
  1119. }
  1120. $sql = "/* $debugMsg */ " .
  1121. " SELECT id,node_type_id from {$this->tables['nodes_hierarchy']} " .
  1122. " WHERE parent_id IN ({$parentList})";
  1123. $sql .= " AND node_type_id IN ({$coupleTypes['target']},{$coupleTypes['container']}) ";
  1124. $result = $this->db->exec_query($sql);
  1125. if ($result)
  1126. {
  1127. $containerSet = array();
  1128. while($row = $this->db->fetch_array($result))
  1129. {
  1130. if ($row['node_type_id'] == $coupleTypes['target'])
  1131. {
  1132. $itemSet[] = $row['id'];
  1133. }
  1134. else
  1135. {
  1136. $containerSet[] = $row['id'];
  1137. }
  1138. }
  1139. if (sizeof($containerSet))
  1140. {
  1141. $containerSet = implode(",",$containerSet);
  1142. $this->getAllItemsID($containerSet,$itemSet,$coupleTypes);
  1143. }
  1144. }
  1145. }
  1146. }// end class
  1147. ?>