PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/application/datamapper/nestedsets.php

https://bitbucket.org/matyhaty/senses-designertravelv3
PHP | 1397 lines | 630 code | 182 blank | 585 comment | 104 complexity | 529cedb43c867ef555f92342b812be13 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * Nested Sets Extension for DataMapper classes.
  4. *
  5. * Nested Sets DataMapper model
  6. *
  7. * @license MIT License
  8. * @package DMZ-Included-Extensions
  9. * @category DMZ
  10. * @author WanWizard
  11. * @info Based on nstrees by Rolf Brugger, edutech
  12. * http://www.edutech.ch/contribution/nstrees
  13. * @version 1.0
  14. */
  15. // --------------------------------------------------------------------------
  16. /**
  17. * DMZ_Nestedsets Class
  18. *
  19. * @package DMZ-Included-Extensions
  20. */
  21. class DMZ_Nestedsets {
  22. /**
  23. * name of the tree node left index field
  24. *
  25. * @var string
  26. * @access private
  27. */
  28. private $_leftindex = 'left_id';
  29. /**
  30. * name of the tree node right index field
  31. *
  32. * @var string
  33. * @access private
  34. */
  35. private $_rightindex = 'right_id';
  36. /**
  37. * name of the tree root id field. Used when the tree contains multiple roots
  38. *
  39. * @var string
  40. * @access private
  41. */
  42. private $_rootfield = 'root_id';
  43. /**
  44. * value of the root field we need to filter on
  45. *
  46. * @var string
  47. * @access private
  48. */
  49. private $_rootindex = NULL;
  50. /**
  51. * name of the tree node symlink index field
  52. *
  53. * @var string
  54. * @access private
  55. */
  56. private $_symlinkindex = 'symlink_id';
  57. /**
  58. * name of the tree node name field, used to build a path string
  59. *
  60. * @var string
  61. * @access private
  62. */
  63. private $_nodename = NULL;
  64. /**
  65. * indicates with pointers need to be used
  66. *
  67. * @var string
  68. * @access private
  69. */
  70. private $use_symlink_pointers = TRUE;
  71. // -----------------------------------------------------------------
  72. /**
  73. * Class constructor
  74. *
  75. * @param mixed optional, array of load-time options or NULL
  76. * @param object the DataMapper object
  77. * @return void
  78. * @access public
  79. */
  80. function __construct( $options = array(), $object = NULL )
  81. {
  82. // do we have the datamapper object
  83. if ( ! is_null($object) )
  84. {
  85. // update the config
  86. $this->tree_config($object, $options);
  87. }
  88. }
  89. // -----------------------------------------------------------------
  90. /**
  91. * runtime configuration of this nestedsets tree
  92. *
  93. * @param object the DataMapper object
  94. * @param mixed optional, array of options or NULL
  95. * @return object the updated DataMapper object
  96. * @access public
  97. */
  98. function tree_config($object, $options = array() )
  99. {
  100. // make sure the load-time options parameter is an array
  101. if ( ! is_array($options) )
  102. {
  103. $options = array();
  104. }
  105. // make sure the model options parameter is an array
  106. if ( ! isset($object->nestedsets) OR ! is_array($object->nestedsets) )
  107. {
  108. $object->nestedsets = array();
  109. }
  110. // loop through all options
  111. foreach( array( $object->nestedsets, $options ) as $optarray )
  112. {
  113. foreach( $optarray as $key => $value )
  114. {
  115. switch ( $key )
  116. {
  117. case 'name':
  118. $this->_nodename = (string) $value;
  119. break;
  120. case 'symlink':
  121. $this->_symlinkindex = (string) $value;
  122. break;
  123. case 'left':
  124. $this->_leftindex = (string) $value;
  125. break;
  126. case 'right':
  127. $this->_rightindex = (string) $value;
  128. break;
  129. case 'root':
  130. $this->_rootfield = (string) $value;
  131. break;
  132. case 'value':
  133. $this->_rootindex = (int) $value;
  134. break;
  135. case 'follow':
  136. $this->use_symlink_pointers = (bool) $value;
  137. break;
  138. default:
  139. break;
  140. }
  141. }
  142. }
  143. }
  144. // -----------------------------------------------------------------
  145. /**
  146. * select a specific root if the table contains multiple trees
  147. *
  148. * @param object the DataMapper object
  149. * @return object the updated DataMapper object
  150. * @access public
  151. */
  152. function select_root($object, $tree = NULL)
  153. {
  154. // set the filter value
  155. $this->_rootindex = $tree;
  156. // return the object
  157. return $object;
  158. }
  159. // -----------------------------------------------------------------
  160. // Tree constructors
  161. // -----------------------------------------------------------------
  162. /**
  163. * create a new tree root
  164. *
  165. * @param object the DataMapper object
  166. * @return object the updated DataMapper object
  167. * @access public
  168. */
  169. function new_root($object)
  170. {
  171. // set the pointers for the root object
  172. $object->id = NULL;
  173. $object->{$this->_leftindex} = 1;
  174. $object->{$this->_rightindex} = 2;
  175. // add a root index if needed
  176. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  177. {
  178. $object->{$this->_rootfield} = $this->_rootindex;
  179. }
  180. // create the new tree root, and return the updated object
  181. return $this->_insertNew($object);
  182. }
  183. // -----------------------------------------------------------------
  184. /**
  185. * creates a new first child of 'node'
  186. *
  187. * @param object the DataMapper object
  188. * @param object the parent node
  189. * @return object the updated DataMapper object
  190. * @access public
  191. */
  192. function new_first_child($object, $node = NULL)
  193. {
  194. // a node passed?
  195. if ( is_null($node) )
  196. {
  197. // no, use the object itself
  198. $node = $object->get_clone();
  199. }
  200. // we need a valid node for this to work
  201. if ( ! $node->exists() )
  202. {
  203. return $node;
  204. }
  205. // set the pointers for the root object
  206. $object->id = NULL;
  207. $object->{$this->_leftindex} = $node->{$this->_leftindex} + 1;
  208. $object->{$this->_rightindex} = $node->{$this->_leftindex} + 2;
  209. // add a root index if needed
  210. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  211. {
  212. $object->{$this->_rootfield} = $this->_rootindex;
  213. }
  214. // shift nodes to make room for the new child
  215. $this->_shiftRLValues($node, $object->{$this->_leftindex}, 2);
  216. // create the new tree node, and return the updated object
  217. return $this->_insertNew($object);
  218. }
  219. // -----------------------------------------------------------------
  220. /**
  221. * creates a new last child of 'node'
  222. *
  223. * @param object the DataMapper object
  224. * @param object the parent node
  225. * @return object the updated DataMapper object
  226. * @access public
  227. */
  228. function new_last_child($object, $node = NULL)
  229. {
  230. // a node passed?
  231. if ( is_null($node) )
  232. {
  233. // no, use the object itself
  234. $node = $object->get_clone();
  235. }
  236. // we need a valid node for this to work
  237. if ( ! $node->exists() )
  238. {
  239. return $node;
  240. }
  241. // set the pointers for the root object
  242. $object->id = NULL;
  243. $object->{$this->_leftindex} = $node->{$this->_rightindex};
  244. $object->{$this->_rightindex} = $node->{$this->_rightindex} + 1;
  245. // add a root index if needed
  246. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  247. {
  248. $object->{$this->_rootfield} = $this->_rootindex;
  249. }
  250. // shift nodes to make room for the new child
  251. $this->_shiftRLValues($node, $object->{$this->_leftindex}, 2);
  252. // create the new tree node, and return the updated object
  253. return $this->_insertNew($object);
  254. }
  255. // -----------------------------------------------------------------
  256. /**
  257. * creates a new sibling before 'node'
  258. *
  259. * @param object the DataMapper object
  260. * @param object the sibling node
  261. * @return object the updated DataMapper object
  262. * @access public
  263. */
  264. function new_previous_sibling($object, $node = NULL)
  265. {
  266. // a node passed?
  267. if ( is_null($node) )
  268. {
  269. // no, use the object itself
  270. $node = $object->get_clone();
  271. }
  272. // we need a valid node for this to work
  273. if ( ! $node->exists() )
  274. {
  275. return $node;
  276. }
  277. // set the pointers for the root object
  278. $object->id = NULL;
  279. $object->{$this->_leftindex} = $node->{$this->_leftindex};
  280. $object->{$this->_rightindex} = $node->{$this->_leftindex} + 1;
  281. // add a root index if needed
  282. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  283. {
  284. $object->{$this->_rootfield} = $this->_rootindex;
  285. }
  286. // shift nodes to make room for the new sibling
  287. $this->_shiftRLValues($node, $object->{$this->_leftindex}, 2);
  288. // create the new tree node, and return the updated object
  289. return $this->_insertNew($object);
  290. }
  291. // -----------------------------------------------------------------
  292. /**
  293. * creates a new sibling after 'node'
  294. *
  295. * @param object the DataMapper object
  296. * @param object the sibling node
  297. * @return object the updated DataMapper object
  298. * @access public
  299. */
  300. function new_next_sibling($object, $node = NULL)
  301. {
  302. // a node passed?
  303. if ( is_null($node) )
  304. {
  305. // no, use the object itself
  306. $node = $object->get_clone();
  307. }
  308. // we need a valid node for this to work
  309. if ( ! $node->exists() )
  310. {
  311. return $node;
  312. }
  313. // set the pointers for the root object
  314. $object->id = NULL;
  315. $object->{$this->_leftindex} = $node->{$this->_rightindex} + 1;
  316. $object->{$this->_rightindex} = $node->{$this->_rightindex} + 2;
  317. // add a root index if needed
  318. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  319. {
  320. $object->{$this->_rootfield} = $this->_rootindex;
  321. }
  322. // shift nodes to make room for the new sibling
  323. $this->_shiftRLValues($node, $object->{$this->_leftindex}, 2);
  324. // create the new tree node, and return the updated object
  325. return $this->_insertNew($object);
  326. }
  327. // -----------------------------------------------------------------
  328. // Tree queries
  329. // -----------------------------------------------------------------
  330. /**
  331. * returns the root of the (selected) tree
  332. *
  333. * @param object the DataMapper object
  334. * @return object the updated DataMapper object
  335. * @access public
  336. */
  337. function get_root($object)
  338. {
  339. // add a root index if needed
  340. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  341. {
  342. $object->db->where($this->_rootfield, $this->_rootindex);
  343. }
  344. // get the tree's root node
  345. return $object->where($this->_leftindex, 1)->get();
  346. }
  347. // -----------------------------------------------------------------
  348. /**
  349. * returns the parent of the child 'node'
  350. *
  351. * @param object the DataMapper object
  352. * @param object the child node
  353. * @return object the updated DataMapper object
  354. * @access public
  355. */
  356. function get_parent($object, $node = NULL)
  357. {
  358. // a node passed?
  359. if ( is_null($node) )
  360. {
  361. // no, use the object itself
  362. $node =& $object;
  363. }
  364. // we need a valid node for this to work
  365. if ( ! $node->exists() )
  366. {
  367. return $node;
  368. }
  369. // add a root index if needed
  370. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  371. {
  372. $object->db->where($this->_rootfield, $this->_rootindex);
  373. }
  374. // get the node's parent node
  375. $object->where($this->_leftindex . ' <', $node->{$this->_leftindex});
  376. $object->where($this->_rightindex . ' >', $node->{$this->_rightindex});
  377. return $object->order_by($this->_rightindex, 'asc')->limit(1)->get();
  378. }
  379. // -----------------------------------------------------------------
  380. /**
  381. * returns the node with the requested left index pointer
  382. *
  383. * @param object the DataMapper object
  384. * @param integer a node's left index value
  385. * @return object the updated DataMapper object
  386. * @access public
  387. */
  388. function get_node_where_left($object, $left_id)
  389. {
  390. // add a root index if needed
  391. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  392. {
  393. $object->db->where($this->_rootfield, $this->_rootindex);
  394. }
  395. // get the node's parent node
  396. $object->where($this->_leftindex, $left_id);
  397. return $object->get();
  398. }
  399. // -----------------------------------------------------------------
  400. /**
  401. * returns the node with the requested right index pointer
  402. *
  403. * @param object the DataMapper object
  404. * @param integer a node's right index value
  405. * @return object the updated DataMapper object
  406. * @access public
  407. */
  408. function get_node_where_right($object, $right_id)
  409. {
  410. // add a root index if needed
  411. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  412. {
  413. $object->db->where($this->_rootfield, $this->_rootindex);
  414. }
  415. // get the node's parent node
  416. $object->where($this->_rightindex, $right_id);
  417. return $object->get();
  418. }
  419. // -----------------------------------------------------------------
  420. /**
  421. * returns the first child of the given node
  422. *
  423. * @param object the DataMapper object
  424. * @param object the parent node
  425. * @return object the updated DataMapper object
  426. * @access public
  427. */
  428. function get_first_child($object, $node = NULL)
  429. {
  430. // a node passed?
  431. if ( is_null($node) )
  432. {
  433. // no, use the object itself
  434. $node =& $object;
  435. }
  436. // we need a valid node for this to work
  437. if ( ! $node->exists() )
  438. {
  439. return $node;
  440. }
  441. // add a root index if needed
  442. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  443. {
  444. $object->db->where($this->_rootfield, $this->_rootindex);
  445. }
  446. // get the node's first child node
  447. $object->where($this->_leftindex, $node->{$this->_leftindex}+1);
  448. return $object->get();
  449. }
  450. // -----------------------------------------------------------------
  451. /**
  452. * returns the last child of the given node
  453. *
  454. * @param object the DataMapper object
  455. * @param object the parent node
  456. * @return object the updated DataMapper object
  457. * @access public
  458. */
  459. function get_last_child($object, $node = NULL)
  460. {
  461. // a node passed?
  462. if ( is_null($node) )
  463. {
  464. // no, use the object itself
  465. $node =& $object;
  466. }
  467. // we need a valid node for this to work
  468. if ( ! $node->exists() )
  469. {
  470. return $node;
  471. }
  472. // add a root index if needed
  473. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  474. {
  475. $object->db->where($this->_rootfield, $this->_rootindex);
  476. }
  477. // get the node's last child node
  478. $object->where($this->_rightindex, $node->{$this->_rightindex}-1);
  479. return $object->get();
  480. }
  481. // -----------------------------------------------------------------
  482. /**
  483. * returns the previous sibling of the given node
  484. *
  485. * @param object the DataMapper object
  486. * @param object the sibling node
  487. * @return object the updated DataMapper object
  488. * @access public
  489. */
  490. function get_previous_sibling($object, $node = NULL)
  491. {
  492. // a node passed?
  493. if ( is_null($node) )
  494. {
  495. // no, use the object itself
  496. $node =& $object;
  497. }
  498. // we need a valid node for this to work
  499. if ( ! $node->exists() )
  500. {
  501. return $node;
  502. }
  503. // add a root index if needed
  504. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  505. {
  506. $object->db->where($this->_rootfield, $this->_rootindex);
  507. }
  508. // get the node's previous sibling node
  509. $object->where($this->_rightindex, $node->{$this->_leftindex}-1);
  510. return $object->get();
  511. }
  512. // -----------------------------------------------------------------
  513. /**
  514. * returns the next sibling of the given node
  515. *
  516. * @param object the DataMapper object
  517. * @param object the sibling node
  518. * @return object the updated DataMapper object
  519. * @access public
  520. */
  521. function get_next_sibling($object, $node = NULL)
  522. {
  523. // a node passed?
  524. if ( is_null($node) )
  525. {
  526. // no, use the object itself
  527. $node =& $object;
  528. }
  529. // we need a valid node for this to work
  530. if ( ! $node->exists() )
  531. {
  532. return $node;
  533. }
  534. // add a root index if needed
  535. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  536. {
  537. $object->db->where($this->_rootfield, $this->_rootindex);
  538. }
  539. // get the node's next sibling node
  540. $object->where($this->_leftindex, $node->{$this->_rightindex}+1);
  541. return $object->get();
  542. }
  543. // -----------------------------------------------------------------
  544. // Boolean tree functions
  545. // -----------------------------------------------------------------
  546. /**
  547. * check if the object is a valid tree node
  548. *
  549. * @param object the DataMapper object
  550. * @return boolean
  551. * @access public
  552. */
  553. function is_valid_node($object)
  554. {
  555. if ( ! $object->exists() )
  556. {
  557. return FALSE;
  558. }
  559. elseif ( ! isset($object->{$this->_leftindex}) OR ! is_numeric($object->{$this->_leftindex}) OR $object->{$this->_leftindex} <=0 )
  560. {
  561. return FALSE;
  562. }
  563. elseif ( ! isset($object->{$this->_rightindex}) OR ! is_numeric($object->{$this->_rightindex}) OR $object->{$this->_rightindex} <=0 )
  564. {
  565. return FALSE;
  566. }
  567. elseif ( $object->{$this->_leftindex} >= $object->{$this->_rightindex} )
  568. {
  569. return FALSE;
  570. }
  571. elseif ( ! empty($this->_rootfield) && ! in_array($this->_rootfield, $object->fields) )
  572. {
  573. return FALSE;
  574. }
  575. elseif ( ! empty($this->_rootfield) && ( ! is_numeric($object->{$this->_rootfield}) OR $object->{$this->_rootfield} <=0 ) )
  576. {
  577. return FALSE;
  578. }
  579. // all looks well...
  580. return TRUE;
  581. }
  582. // -----------------------------------------------------------------
  583. /**
  584. * check if the object is a tree root
  585. *
  586. * @param object the DataMapper object
  587. * @return boolean
  588. * @access public
  589. */
  590. function is_root($object)
  591. {
  592. return ( $object->exists() && $this->is_valid_node($object) && $object->{$this->_leftindex} == 1 );
  593. }
  594. // -----------------------------------------------------------------
  595. /**
  596. * check if the object is a tree leaf (node with no children)
  597. *
  598. * @param object the DataMapper object
  599. * @return boolean
  600. * @access public
  601. */
  602. function is_leaf($object)
  603. {
  604. return ( $object->exists() && $this->is_valid_node($object) && $object->{$this->_rightindex} - $object->{$this->_leftindex} == 1 );
  605. }
  606. // -----------------------------------------------------------------
  607. /**
  608. * check if the object is a child node
  609. *
  610. * @param object the DataMapper object
  611. * @return boolean
  612. * @access public
  613. */
  614. function is_child($object)
  615. {
  616. return ( $object->exists() && $this->is_valid_node($object) && $object->{$this->_leftindex} > 1 );
  617. }
  618. // -----------------------------------------------------------------
  619. /**
  620. * check if the object is a child of node
  621. *
  622. * @param object the DataMapper object
  623. * @param object the parent node
  624. * @return boolean
  625. * @access public
  626. */
  627. function is_child_of($object, $node = NULL)
  628. {
  629. // validate the objects
  630. if ( ! $this->is_valid_node($object) OR ! $this->is_valid_node($node) ) {
  631. return FALSE;
  632. }
  633. return ( $object->{$this->_leftindex} > $node->{$this->_leftindex} && $object->{$this->_rightindex} < $node->{$this->_rightindex} );
  634. }
  635. // -----------------------------------------------------------------
  636. /**
  637. * check if the object is the parent of node
  638. *
  639. * @param object the DataMapper object
  640. * @param object the parent node
  641. * @return boolean
  642. * @access public
  643. */
  644. function is_parent_of($object, $node = NULL)
  645. {
  646. // validate the objects
  647. if ( ! $this->is_valid_node($object) OR ! $this->is_valid_node($node) )
  648. {
  649. return FALSE;
  650. }
  651. // fetch the parent of our child node
  652. $parent = $node->get_clone()->get_parent();
  653. return ( $parent->id === $object->id );
  654. }
  655. // -----------------------------------------------------------------
  656. /**
  657. * check if the object has a parent
  658. *
  659. * Note: this is an alias for is_child()
  660. *
  661. * @param object the DataMapper object
  662. * @return boolean
  663. * @access public
  664. */
  665. function has_parent($object)
  666. {
  667. return $this->is_child($object);
  668. }
  669. // -----------------------------------------------------------------
  670. /**
  671. * check if the object has children
  672. *
  673. * Note: this is an alias for ! is_leaf()
  674. *
  675. * @param object the DataMapper object
  676. * @return boolean
  677. * @access public
  678. */
  679. function has_children($object)
  680. {
  681. return $this->is_leaf($object) ? FALSE : TRUE;
  682. }
  683. // -----------------------------------------------------------------
  684. /**
  685. * check if the object has a previous silbling
  686. *
  687. * @param object the DataMapper object
  688. * @return boolean
  689. * @access public
  690. */
  691. function has_previous_sibling($object)
  692. {
  693. // fetch the result using a clone
  694. $node = $object->get_clone();
  695. return $this->is_valid_node($node->get_previous_sibling($object));
  696. }
  697. // -----------------------------------------------------------------
  698. /**
  699. * check if the object has a next silbling
  700. *
  701. * @param object the DataMapper object
  702. * @return boolean
  703. * @access public
  704. */
  705. function has_next_sibling($object)
  706. {
  707. // fetch the result using a clone
  708. $node = $object->get_clone();
  709. return $this->is_valid_node($node->get_next_sibling($object));
  710. }
  711. // -----------------------------------------------------------------
  712. // Integer tree functions
  713. // -----------------------------------------------------------------
  714. /**
  715. * return the count of the objects children
  716. *
  717. * @param object the DataMapper object
  718. * @return integer
  719. * @access public
  720. */
  721. function count_children($object)
  722. {
  723. return ( $object->exists() ? (($object->{$this->_rightindex} - $object->{$this->_leftindex} - 1) / 2) : FALSE );
  724. }
  725. // -----------------------------------------------------------------
  726. /**
  727. * return the node level, where the root = 0
  728. *
  729. * @param object the DataMapper object
  730. * @return mixed integer, of FALSE in case no valid object was passed
  731. * @access public
  732. */
  733. function level($object)
  734. {
  735. if ( $object->exists() )
  736. {
  737. // add a root index if needed
  738. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  739. {
  740. $object->db->where($this->_rootfield, $this->_rootindex);
  741. }
  742. $object->where($this->_leftindex.' <', $object->{$this->_leftindex});
  743. $object->where($this->_rightindex.' >', $object->{$this->_rightindex});
  744. return $object->count();
  745. }
  746. else
  747. {
  748. return FALSE;
  749. }
  750. }
  751. // -----------------------------------------------------------------
  752. // Tree reorganisation
  753. // -----------------------------------------------------------------
  754. /**
  755. * move the object as next sibling of 'node'
  756. *
  757. * @param object the DataMapper object
  758. * @param object the sibling node
  759. * @return object the updated DataMapper object
  760. * @access public
  761. */
  762. function make_next_sibling_of($object, $node)
  763. {
  764. if ( ! $this->is_root($node) )
  765. {
  766. return $this->_moveSubtree($object, $node, $node->{$this->_rightindex}+1);
  767. }
  768. else
  769. {
  770. return FALSE;
  771. }
  772. }
  773. // -----------------------------------------------------------------
  774. /**
  775. * move the object as previous sibling of 'node'
  776. *
  777. * @param object the DataMapper object
  778. * @param object the sibling node
  779. * @return object the updated DataMapper object
  780. * @access public
  781. */
  782. function make_previous_sibling_of($object, $node)
  783. {
  784. if ( ! $this->is_root($node) )
  785. {
  786. return $this->_moveSubtree($object, $node, $node->{$this->_leftindex});
  787. }
  788. else
  789. {
  790. return FALSE;
  791. }
  792. }
  793. // -----------------------------------------------------------------
  794. /**
  795. * move the object as first child of 'node'
  796. *
  797. * @param object the DataMapper object
  798. * @param object the sibling node
  799. * @return object the updated DataMapper object
  800. * @access public
  801. */
  802. function make_first_child_of($object, $node)
  803. {
  804. return $this->_moveSubtree($object, $node, $node->{$this->_leftindex}+1);
  805. }
  806. // -----------------------------------------------------------------
  807. /**
  808. * move the object as last child of 'node'
  809. *
  810. * @param object the DataMapper object
  811. * @param object the sibling node
  812. * @return object the updated DataMapper object
  813. * @access public
  814. */
  815. function make_last_child_of($object, $node)
  816. {
  817. return $this->_moveSubtree($object, $node, $node->{$this->_rightindex});
  818. }
  819. // -----------------------------------------------------------------
  820. // Tree destructors
  821. // -----------------------------------------------------------------
  822. /**
  823. * deletes the entire tree structure including all records
  824. *
  825. * @param object the DataMapper object
  826. * @param mixed optional, id of the tree to delete
  827. * @return object the updated DataMapper object
  828. * @access public
  829. */
  830. function remove_tree($object, $tree_id = NULL)
  831. {
  832. // if we have multiple roots
  833. if ( in_array($this->_rootfield, $object->fields) )
  834. {
  835. // was a tree id passed?
  836. if ( ! is_null($tree_id) )
  837. {
  838. // only delete the selected one
  839. $object->db->where($this->_rootfield, $tree_id)->delete($object->table);
  840. }
  841. elseif ( ! is_null($this->_rootindex) )
  842. {
  843. // only delete the selected one
  844. $object->db->where($this->_rootfield, $this->_rootindex)->delete($object->table);
  845. }
  846. else
  847. {
  848. // delete them all
  849. $object->db->truncate($object->table);
  850. }
  851. }
  852. else
  853. {
  854. // delete them all
  855. $object->db->truncate($object->table);
  856. }
  857. // return the cleared object
  858. return $object->clear();
  859. }
  860. // -----------------------------------------------------------------
  861. /**
  862. * deletes the current object, and all childeren
  863. *
  864. * @param object the DataMapper object
  865. * @return object the updated DataMapper object
  866. * @access public
  867. */
  868. function remove_node($object)
  869. {
  870. // we need a valid node to do this
  871. if ( $object->exists() )
  872. {
  873. // if we have multiple roots
  874. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  875. {
  876. // only delete the selected one
  877. $object->db->where($this->_rootfield, $this->_rootindex);
  878. }
  879. // clone the object, we need to it shift later
  880. $clone = $object->get_clone();
  881. // select the node and all children
  882. $object->db->where($this->_leftindex . ' >=', $object->{$this->_leftindex});
  883. $object->db->where($this->_rightindex . ' <=', $object->{$this->_rightindex});
  884. // delete them all
  885. $object->db->delete($object->table);
  886. // re-index the tree
  887. $this->_shiftRLValues($clone, $object->{$this->_rightindex} + 1, $clone->{$this->_leftindex} - $object->{$this->_rightindex} -1);
  888. }
  889. // return the cleared object
  890. return $object->clear();
  891. }
  892. // -----------------------------------------------------------------
  893. // dump methods
  894. // -----------------------------------------------------------------
  895. /**
  896. * returns the tree in a key-value format suitable for html dropdowns
  897. *
  898. * @param object the DataMapper object
  899. * @param string optional, name of the column to use
  900. * @param boolean if true, the object itself (root of the dump) will not be included
  901. * @return array
  902. * @access public
  903. */
  904. public function dump_dropdown($object, $field = FALSE, $skip_root = TRUE)
  905. {
  906. // check if a specific field has been requested
  907. if ( empty($field) OR ! isset($this->fields[$field]) )
  908. {
  909. // no field given, check if a generic name is defined
  910. if ( ! empty($this->_nodename) )
  911. {
  912. // yes, so use it
  913. $field = $this->_nodename;
  914. }
  915. else
  916. {
  917. // can't continue without a name
  918. return FALSE;
  919. }
  920. }
  921. // fetch the tree as an array
  922. $tree = $this->dump_tree($object, NULL, 'array', $skip_root);
  923. // storage for the result
  924. $result = array();
  925. if ( $tree )
  926. {
  927. // loop trough the tree
  928. foreach ( $tree as $key => $value )
  929. {
  930. $result[$value['__id']] = str_repeat('&nbsp;', ($value['__level']) * 3) . ($value['__level'] ? '&raquo; ' : '') . $value[$field];
  931. }
  932. }
  933. // return the result
  934. return $result;
  935. }
  936. // -----------------------------------------------------------------
  937. /**
  938. * dumps the entire tree in HTML or TAB formatted output
  939. *
  940. * @param object the DataMapper object
  941. * @param array list of columns to include in the dump
  942. * @param string type of output requested, possible values 'html', 'tab', 'csv', 'array' ('array' = default)
  943. * @param boolean if true, the object itself (root of the dump) will not be included
  944. * @return mixed
  945. * @access public
  946. */
  947. public function dump_tree($object, $attributes = NULL, $type = 'array', $skip_root = TRUE)
  948. {
  949. if ( $this->is_valid_node($object) )
  950. {
  951. // do we need a sub-selection of attributes?
  952. if ( is_array($attributes) )
  953. {
  954. // make sure required fields are present
  955. $fields = array_merge($attributes, array('id', $this->_leftindex, $this->_rightindex));
  956. if ( ! empty($this->_nodename) && ! isset($fields[$this->_nodename] ) )
  957. {
  958. $fields[] = $this->_nodename;
  959. }
  960. // add a select
  961. $object->db->select($fields);
  962. }
  963. // create the where clause for this query
  964. if ( $skip_root === TRUE )
  965. {
  966. // select only all children
  967. $object->db->where($this->_leftindex . ' >', $object->{$this->_leftindex});
  968. $object->db->where($this->_rightindex . ' <', $object->{$this->_rightindex});
  969. $level = -1;
  970. }
  971. else
  972. {
  973. // select the node and all children
  974. $object->db->where($this->_leftindex . ' >=', $object->{$this->_leftindex});
  975. $object->db->where($this->_rightindex . ' <=', $object->{$this->_rightindex});
  976. $level = -2;
  977. }
  978. // if we have multiple roots
  979. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  980. {
  981. // only delete the selected one
  982. $object->db->where($this->_rootfield, $this->_rootindex);
  983. }
  984. // fetch the result
  985. $result = $object->db->order_by($this->_leftindex)->get($object->table)->result_array();
  986. // store the last left pointer
  987. $last_left = $object->{$this->_leftindex};
  988. // create the path
  989. if ( ! empty($this->_nodename) )
  990. {
  991. $path = array( $object->{$this->_nodename} );
  992. }
  993. else
  994. {
  995. $path = array();
  996. }
  997. // add level and path to the result
  998. foreach ( $result as $key => $value )
  999. {
  1000. // for now, just store the ID
  1001. $result[$key]['__id'] = $value['id'];
  1002. // calculate the nest level of this node
  1003. $level += $last_left - $value[$this->_leftindex] + 2;
  1004. $last_left = $value[$this->_leftindex];
  1005. $result[$key]['__level'] = $level;
  1006. // create the relative path to this node
  1007. $result[$key]['__path'] = '';
  1008. if ( ! empty($this->_nodename) )
  1009. {
  1010. $path[$level] = $value[$this->_nodename];
  1011. for ( $i = 0; $i <= $level; $i++ )
  1012. {
  1013. $result[$key]['__path'] .= '/' . $path[$i];
  1014. }
  1015. }
  1016. }
  1017. // convert the result to output
  1018. if ( in_array($type, array('tab', 'csv', 'html')) )
  1019. {
  1020. // storage for the result
  1021. $convert = '';
  1022. // loop through the elements
  1023. foreach ( $result as $key => $value )
  1024. {
  1025. // prefix based on requested type
  1026. switch ($type)
  1027. {
  1028. case 'tab';
  1029. $convert .= str_repeat("\t", $value['__level'] * 4 );
  1030. break;
  1031. case 'csv';
  1032. break;
  1033. case 'html';
  1034. $convert .= str_repeat("&nbsp;", $value['__level'] * 4 );
  1035. break;
  1036. }
  1037. // print the attributes requested
  1038. if ( ! is_null($attributes) )
  1039. {
  1040. $att = reset($attributes);
  1041. while($att){
  1042. if ( is_numeric($value[$att]) )
  1043. {
  1044. $convert .= $value[$att];
  1045. }
  1046. else
  1047. {
  1048. $convert .= '"'.$value[$att].'"';
  1049. }
  1050. $att = next($attributes);
  1051. if ($att)
  1052. {
  1053. $convert .= ($type == 'csv' ? "," : " ");
  1054. }
  1055. }
  1056. }
  1057. // postfix based on requested type
  1058. switch ($type)
  1059. {
  1060. case 'tab';
  1061. $convert .= "\n";
  1062. break;
  1063. case 'csv';
  1064. $convert .= "\n";
  1065. break;
  1066. case 'html';
  1067. $convert .= "<br />";
  1068. break;
  1069. }
  1070. }
  1071. return $convert;
  1072. }
  1073. else
  1074. {
  1075. return $result;
  1076. }
  1077. }
  1078. return FALSE;
  1079. }
  1080. // -----------------------------------------------------------------
  1081. // internal methods
  1082. // -----------------------------------------------------------------
  1083. /**
  1084. * makes room for a new node (or nodes) by shifting the left and right
  1085. * id's of nodes with larger values than our object by $delta
  1086. *
  1087. * note that $delta can also be negative!
  1088. *
  1089. * @param object the DataMapper object
  1090. * @param integer left value of the start node
  1091. * @param integer number of positions to shift
  1092. * @return object the updated DataMapper object
  1093. * @access private
  1094. */
  1095. private function _shiftRLValues($object, $first, $delta)
  1096. {
  1097. // we need a valid object
  1098. if ( $object->exists() )
  1099. {
  1100. // if we have multiple roots
  1101. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  1102. {
  1103. // select the correct one
  1104. $object->where($this->_rootfield, $this->_rootindex);
  1105. }
  1106. // set the delta
  1107. $delta = $delta >= 0 ? (' + '.$delta) : (' - '.(abs($delta)));
  1108. // select the range
  1109. $object->where($this->_leftindex.' >=', $first);
  1110. $object->update(array($this->_leftindex => $this->_leftindex.$delta), FALSE);
  1111. // if we have multiple roots
  1112. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  1113. {
  1114. // select the correct one
  1115. $object->where($this->_rootfield, $this->_rootindex);
  1116. }
  1117. // select the range
  1118. $object->where($this->_rightindex.' >=', $first);
  1119. $object->update(array($this->_rightindex => $this->_rightindex.$delta), FALSE);
  1120. }
  1121. // return the object
  1122. return $object;
  1123. }
  1124. // -----------------------------------------------------------------
  1125. /**
  1126. * shifts a range of nodes up or down the left and right index by $delta
  1127. *
  1128. * note that $delta can also be negative!
  1129. *
  1130. * @param object the DataMapper object
  1131. * @param integer left value of the start node
  1132. * @param integer right value of the end node
  1133. * @param integer number of positions to shift
  1134. * @return object the updated DataMapper object
  1135. * @access private
  1136. */
  1137. private function _shiftRLRange($object, $first, $last, $delta)
  1138. {
  1139. // we need a valid object
  1140. if ( $object->exists() )
  1141. {
  1142. // if we have multiple roots
  1143. if ( in_array($this->_rootfield, $object->fields) && ! is_null($this->_rootindex) )
  1144. {
  1145. // select the correct one
  1146. $object->where($this->_rootfield, $this->_rootindex);
  1147. }
  1148. // select the range
  1149. $object->where($this->_leftindex.' >=', $first);
  1150. $object->where($this->_rightindex.' <=', $last);
  1151. // set the delta
  1152. $delta = $delta >= 0 ? (' + '.$delta) : (' - '.(abs($delta)));
  1153. $object->update(array($this->_leftindex => $this->_leftindex.$delta, $this->_rightindex => $this->_rightindex.$delta), FALSE);
  1154. }
  1155. // return the object
  1156. return $object;
  1157. }
  1158. // -----------------------------------------------------------------
  1159. /**
  1160. * inserts a new record into the tree
  1161. *
  1162. * @param object the DataMapper object
  1163. * @return object the updated DataMapper object
  1164. * @access private
  1165. */
  1166. private function _insertNew($object)
  1167. {
  1168. // for now, just save the object
  1169. $object->save();
  1170. // return the object
  1171. return $object;
  1172. }
  1173. // -----------------------------------------------------------------
  1174. /**
  1175. * move a section of the tree to another location within the tree
  1176. *
  1177. * @param object the DataMapper object we're going to move
  1178. * @param integer the destination node's left id value
  1179. * @return object the updated DataMapper object
  1180. * @access private
  1181. */
  1182. private function _moveSubtree($object, $node, $destination_id)
  1183. {
  1184. // if we have multiple roots
  1185. if ( in_array($this->_rootfield, $object->fields) )
  1186. {
  1187. // make sure both nodes are part of the same tree
  1188. if ( $object->{$this->_rootfield} != $node->{$this->_rootfield} )
  1189. {
  1190. return FALSE;
  1191. }
  1192. }
  1193. // determine the size of the tree to move
  1194. $treesize = $object->{$this->_rightindex} - $object->{$this->_leftindex} + 1;
  1195. // get the objects left- and right pointers
  1196. $left_id = $object->{$this->_leftindex};
  1197. $right_id = $object->{$this->_rightindex};
  1198. // shift to make some space
  1199. $this->_shiftRLValues($node, $destination_id, $treesize);
  1200. // correct pointers if there were shifted to
  1201. if ($object->{$this->_leftindex} >= $destination_id)
  1202. {
  1203. $left_id += $treesize;
  1204. $right_id += $treesize;
  1205. }
  1206. // enough room now, start the move
  1207. $this->_shiftRLRange($node, $left_id, $right_id, $destination_id - $left_id);
  1208. // and correct index values after the source
  1209. $this->_shiftRLValues($object, $right_id + 1, -$treesize);
  1210. // return the object
  1211. return $object->get_by_id($object->id);
  1212. }
  1213. }
  1214. /* End of file nestedsets.php */
  1215. /* Location: ./application/datamapper/nestedsets.php */