PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/pear/php/Tree/Memory.php

https://github.com/wrobel/horde-glue
PHP | 1521 lines | 507 code | 125 blank | 889 comment | 90 complexity | ab63c01ec7434cf4cca4c174401ad4a8 MD5 | raw file
Possible License(s): BSD-2-Clause, LGPL-2.1

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

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Wolfram Kriesing <wolfram@kriesing.de> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Memory.php,v 1.34.2.3 2009/03/12 17:19:52 dufuz Exp $
  20. require_once 'Tree/Common.php';
  21. /**
  22. * this class can be used to step through a tree using ['parent'],['child']
  23. * the tree is saved as flat data in a db, where at least the parent
  24. * needs to be given if a previous member is given too then the order
  25. * on a level can be determined too
  26. * actually this class was used for a navigation tree
  27. * now it is extended to serve any kind of tree
  28. * you can unambigiously refer to any element by using the following
  29. * syntax
  30. * tree->data[currentId][<where>]...[<where>]
  31. * <where> can be either "parent", "child", "next" or "previous", this way
  32. * you can "walk" from any point to any other point in the tree
  33. * by using <where> in any order you want
  34. * example (in parentheses the id):
  35. * root
  36. * +---level 1_1 (1)
  37. * | +----level 2_1 (2)
  38. * | +----level 2_2 (3)
  39. * | +-----level 3_1 (4)
  40. * +---level 1_2 (5)
  41. *
  42. * the database table to this structure (without defined order)
  43. * id parentId name
  44. * 1 0 level 1_1
  45. * 2 1 level 2_1
  46. * 3 1 level 2_1
  47. * 4 3 level 3_1
  48. * 5 0 level 1_2
  49. *
  50. * now you can refer to elements for example like this (all examples assume you
  51. * know the structure):
  52. * go from "level 3_1" to "level 1_1": $tree->data[4]['parent']['parent']
  53. * go from "level 3_1" to "level 1_2":
  54. * $tree->data[4]['parent']['parent']['next']
  55. * go from "level 2_1" to "level 3_1": $tree->data[2]['next']['child']
  56. * go from "level 2_2" to "level 2_1": $tree->data[3]['previous']
  57. * go from "level 1_2" to "level 3_1":
  58. * $tree->data[5]['previous']['child']['next']['child']
  59. *
  60. * on a pentium 1.9 GHz 512 MB RAM, Linux 2.4, Apache 1.3.19, PHP 4.0.6
  61. * performance statistics for version 1.26, using examples/Tree/Tree.php
  62. * - reading from DB and preparing took: 0.14958894252777
  63. * - building took: 0.074488043785095
  64. * - buildStructure took: 0.05151903629303
  65. * - setting up the tree time: 0.29579293727875
  66. * - number of elements: 1564
  67. * - deepest level: 17
  68. * so you can use it for tiny-big trees too :-)
  69. * but watch the db traffic, which might be considerable, depending
  70. * on your setup.
  71. *
  72. * FIXXXME there is one really bad thing about the entire class, at some points
  73. * there are references to $this->data returned, or the programmer can even
  74. * access this->data, which means he can change the structure, since this->data
  75. * can not be set to read-only, therefore this->data has to be handled
  76. * with great care!!! never do something like this:
  77. * <code>
  78. * $x = &$tree->data[<some-id>]; $x = $y;
  79. * </code>
  80. * this overwrites the element in the structure !!!
  81. *
  82. *
  83. * @access public
  84. * @author Wolfram Kriesing <wolfram@kriesing.de>
  85. * @version 2001/06/27
  86. * @package Tree
  87. */
  88. class Tree_Memory extends Tree_Common
  89. {
  90. /**
  91. * this array contains the pure data from the DB
  92. * which are always kept, since all other structures will
  93. * only make references on any element
  94. * and those data are extended by the elements 'parent' 'children' etc...
  95. * @var array $data
  96. */
  97. var $data = array();
  98. /**
  99. * this array contains references to this->data but it
  100. * additionally represents the directory structure
  101. * that means the array has as many dimensions as the
  102. * tree structure has levels
  103. * but this array is only used internally from outside you can do
  104. * everything using the node-id's
  105. *
  106. * @var array $structure
  107. * @access private
  108. */
  109. var $structure = array();
  110. /**
  111. * it contains all the parents and their children, where the parentId is the
  112. * key and all the children are the values, this is for speeding up
  113. * the tree-building process
  114. *
  115. * @var array $children
  116. */
  117. var $children = array();
  118. /**
  119. * @access private
  120. * @var boolean saves if tree nodes shall be removed recursively
  121. * @see setRemoveRecursively()
  122. */
  123. var $removeRecursively = false;
  124. /**
  125. * @access public
  126. * @var integer the debug mode, if > 0 then debug info are shown,
  127. * actually those messages only show performance
  128. * times
  129. */
  130. var $debug = 0;
  131. /**
  132. * @see &getNode()
  133. * @see &_getNode()
  134. * @access private
  135. * @var integer variable only used in the method getNode and _getNode
  136. */
  137. var $_getNodeMaxLevel;
  138. /**
  139. * @see &getNode()
  140. * @see &_getNode()
  141. * @access private
  142. * @var integer variable only used in the method getNode and
  143. * _getNode
  144. */
  145. var $_getNodeCurParent;
  146. /**
  147. * the maximum depth of the tree
  148. * @access private
  149. * @var int the maximum depth of the tree
  150. */
  151. var $_treeDepth = 0;
  152. // {{{ Tree_Memory()
  153. /**
  154. * set up this object
  155. *
  156. * @version 2001/06/27
  157. * @access public
  158. * @author Wolfram Kriesing <wolfram@kriesing.de>
  159. * @param mixed this is a DSN for the PEAR::DB, can be
  160. * either an object/string
  161. * @param array additional options you can set
  162. */
  163. function Tree_Memory($type, $dsn = '', $options = array())
  164. {
  165. // set the options for $this
  166. $this->Tree_Options($options);
  167. include_once "Tree/Memory/$type.php";
  168. $className = 'Tree_Memory_'.$type;
  169. $this->dataSourceClass =& new $className($dsn, $options);
  170. // copy the options to be able to get them via getOption(s)
  171. // FIXXME this is not really cool, maybe overwrite
  172. // the *Option* methods!!!
  173. if (isset($this->dataSourceClass->options)) {
  174. $this->options = $this->dataSourceClass->options;
  175. }
  176. }
  177. // }}}
  178. // {{{ switchDataSource()
  179. /**
  180. * use this to switch data sources on the run
  181. * i.e. if you are reading the data from a db-tree and want to save it
  182. * as xml data (which will work one day too)
  183. * or reading the data from an xml file and writing it in the db
  184. * which should already work
  185. *
  186. * @version 2002/01/17
  187. * @access public
  188. * @author Wolfram Kriesing <wolfram@kriesing.de>
  189. * @param string this is a DSN of the for that PEAR::DB uses it
  190. * only that additionally you can add parameters
  191. * like ...?table=test_table to define the table.
  192. * @param array additional options you can set
  193. * @return boolean true on success
  194. */
  195. function switchDataSource($type, $dsn = '', $options = array())
  196. {
  197. $data = $this->getNode();
  198. //$this->Tree($dsn, $options);
  199. $this->Tree_Memory($type, $GLOBALS['dummy'], $options);
  200. // this method prepares data retreived using getNode to be used
  201. // in this type of tree
  202. $this->dataSourceClass->setData($data);
  203. $this->setup();
  204. }
  205. // }}}
  206. // {{{ setupByRawData()
  207. /**
  208. *
  209. *
  210. * @version 2002/01/19
  211. * @access public
  212. * @author Wolfram Kriesing <wolfram@kriesing.de>
  213. * @return boolean true if the setup succeeded
  214. * @
  215. */
  216. function setupByRawData($string)
  217. {
  218. // expects
  219. // for XML an XML-String,
  220. // for DB-a result set, may be or an array, dont know here
  221. // not implemented yet
  222. $res = $this->dataSourceClass->setupByRawData($string);
  223. return $this->_setup($res);
  224. }
  225. // }}}
  226. // {{{ setup()
  227. /**
  228. * @version 2002/01/19
  229. * @access public
  230. * @author Wolfram Kriesing <wolfram@kriesing.de>
  231. * @param array the result of a query which retreives (all)
  232. * the tree data from a source
  233. * @return true or Tree_Error
  234. */
  235. function setup($data = null)
  236. {
  237. if ($this->debug) {
  238. $startTime = split(' ',microtime());
  239. $startTime = $startTime[1]+$startTime[0];
  240. }
  241. if (PEAR::isError($res = $this->dataSourceClass->setup($data))) {
  242. return $res;
  243. }
  244. if ($this->debug) {
  245. $endTime = split(' ',microtime());
  246. $endTime = $endTime[1]+$endTime[0];
  247. echo ' reading and preparing tree data took: '.
  248. ($endTime - $startTime) . '<br>';
  249. }
  250. return $this->_setup($res);
  251. }
  252. // }}}
  253. // {{{ _setup()
  254. /**
  255. * retreive all the navigation data from the db and build the
  256. * tree in the array data and structure
  257. *
  258. * @version 2001/11/20
  259. * @access private
  260. * @author Wolfram Kriesing <wolfram@kriesing.de>
  261. * @return boolean true on success
  262. */
  263. function _setup($setupData)
  264. {
  265. // TODO sort by prevId (parentId,prevId $addQuery) too if it exists
  266. // in the table, or the root might be wrong TODO since the prevId
  267. // of the root should be 0
  268. if (!$setupData) {
  269. return false;
  270. }
  271. //FIXXXXXME validate the structure.
  272. // i.e. a problem occurs, if you give one node, which has a parentId=1,
  273. // it screws up everything!!!
  274. //empty the data structures, since we are reading the data
  275. // from the db (again)
  276. $this->structure = array();
  277. $this->data = array();
  278. $this->children = array();
  279. // build an array where all the parents have their children as a member
  280. // this i do to speed up the buildStructure
  281. $columnNameMappings = $this->getOption('columnNameMaps');
  282. foreach ($setupData as $values) {
  283. if (is_array($values)) {
  284. $this->data[$values['id']] = $values;
  285. $this->children[$values['parentId']][] = $values['id'];
  286. }
  287. }
  288. // walk through all the children on each level and set the
  289. // next/previous relations of those children, since all children
  290. // for "children[$id]" are on the same level we can do
  291. // this here :-)
  292. foreach ($this->children as $children) {
  293. $lastPrevId = 0;
  294. if (count($children)) {
  295. foreach ($children as $key) {
  296. if ($lastPrevId) {
  297. // remember the nextId too, so the build process can
  298. // be speed up
  299. $this->data[$lastPrevId]['nextId'] = $key;
  300. $this->data[$lastPrevId]['next'] = &$this->data[$key];
  301. $this->data[$key]['prevId'] = $lastPrevId;
  302. $this->data[$key]['previous'] = &$this->data[ $lastPrevId ];
  303. }
  304. $lastPrevId = $key;
  305. }
  306. }
  307. }
  308. if ($this->debug) {
  309. $startTime = split(' ',microtime());
  310. $startTime = $startTime[1] + $startTime[0];
  311. }
  312. // when NO prevId is given, sort the entries in each level by the given
  313. // sort order (to be defined) and set the prevId so the build can work
  314. // properly does a prevId exist?
  315. if (!isset($setupData[0]['prevId'])) {
  316. $lastPrevId = 0;
  317. $lastParentId = 0;
  318. $level = 0;
  319. // build the entire recursive relations, so you have 'parentId',
  320. // 'childId', 'nextId', 'prevId' and the references 'child',
  321. // 'parent', 'next', 'previous' set in the property 'data'
  322. foreach($this->data as $key => $value) {
  323. // most if checks in this foreach are for the following reason,
  324. // if not stated otherwise:
  325. // dont make an data[''] or data[0] since this was not read
  326. // from the DB, because id is autoincrement and starts at 1
  327. // and also in an xml tree there can not be an element </>
  328. // i hope :-)
  329. if ($value['parentId']) {
  330. $this->data[$key]['parent'] = &$this->data[$value['parentId']];
  331. // the parent has an extra array which contains a reference
  332. // to all it's children, set it here
  333. $this->data[ $value['parentId']]['children'][] =
  334. &$this->data[$key];
  335. }
  336. // was a child saved (in the above 'if')
  337. // see comment above
  338. if (isset($this->children[$key]) &&
  339. count($this->children[$key])
  340. ) {
  341. // refer to the first child in the [child]
  342. // and [childId] keys
  343. $this->data[$key]['childId'] = $this->children[$key][0];
  344. $this->data[$key]['child'] =
  345. &$this->data[$this->children[$key][0]];
  346. }
  347. $lastParentId = $value['parentId'];
  348. }
  349. }
  350. if ($this->debug) {
  351. $endTime = split(' ',microtime());
  352. $endTime = $endTime[1]+$endTime[0];
  353. echo ' building took: ' . ($endTime - $startTime) . ' <br>';
  354. }
  355. // build the property 'structure'
  356. // empty it, just to be sure everything
  357. //will be set properly
  358. $this->structure = array();
  359. if ($this->debug) {
  360. $startTime = split(' ',microtime());
  361. $startTime = $startTime[1] + $startTime[0];
  362. }
  363. // build all the children that are on the root level, if we wouldnt
  364. // do that. We would have to create a root element with an id 0,
  365. // but since this is not read from the db we dont add another element.
  366. // The user wants to get what he had saved
  367. if (isset($this->children[0])) {
  368. foreach ($this->children[0] as $rootElement) {
  369. $this->buildStructure($rootElement, $this->structure);
  370. }
  371. }
  372. if ($this->debug) {
  373. $endTime = split(' ',microtime());
  374. $endTime = $endTime[1] + $endTime[0];
  375. echo ' buildStructure took: ' . ($endTime - $startTime).' <br>';
  376. }
  377. return true;
  378. }
  379. // }}}
  380. // {{{ add()
  381. /**
  382. * adds _one_ new element in the tree under the given parent
  383. * the values' keys given have to match the db-columns, because the
  384. * value gets inserted in the db directly
  385. * to add an entire node containing children and so on see 'addNode()'
  386. * @see addNode()
  387. * @version 2001/10/09
  388. * @access public
  389. * @author Wolfram Kriesing <wolfram@kriesing.de>
  390. * @param array this array contains the values that shall be inserted
  391. * in the db-table
  392. * @param int the parent id
  393. * @param int the prevId
  394. * @return mixed either boolean false on failure or the id
  395. * of the inserted row
  396. */
  397. function add($newValues, $parentId = 0, $prevId = 0)
  398. {
  399. // see comments in 'move' and 'remove'
  400. if (method_exists($this->dataSourceClass, 'add')) {
  401. return $this->dataSourceClass->add($newValues, $parentId, $prevId);
  402. } else {
  403. return $this->_throwError('method not implemented yet.' ,
  404. __LINE__);
  405. }
  406. }
  407. // }}}
  408. // {{{ remove()
  409. /**
  410. * removes the given node and all children if removeRecursively is on
  411. *
  412. * @version 2002/01/24
  413. * @access public
  414. * @author Wolfram Kriesing <wolfram@kriesing.de>
  415. * @param mixed $id the id of the node to be removed
  416. * @return boolean true on success
  417. */
  418. function remove($id)
  419. {
  420. // if removing recursively is not allowed, which means every child
  421. // should be removed
  422. // then check if this element has a child and return
  423. // "sorry baby cant remove :-) "
  424. if ($this->removeRecursively != true) {
  425. if (isset($this->data[$id]['child'])) {
  426. // TODO raise PEAR warning
  427. return $this->_throwError("Element with id=$id has children ".
  428. "that cant be removed. Set ".
  429. "'setRemoveRecursively' to true to ".
  430. "allow this.",
  431. __LINE__
  432. );
  433. }
  434. }
  435. // see comment in 'move'
  436. // if the prevId is in use we need to update the prevId of the element
  437. // after the one that is removed too, to have the prevId of the one
  438. // that is removed!!!
  439. if (method_exists($this->dataSourceClass, 'remove')) {
  440. return $this->dataSourceClass->remove($id);
  441. } else {
  442. return $this->_throwError('method not implemented yet.', __LINE__);
  443. }
  444. }
  445. // }}}
  446. // {{{ _remove()
  447. /**
  448. * collects the ID's of the elements to be removed
  449. *
  450. * @version 2001/10/09
  451. * @access public
  452. * @author Wolfram Kriesing <wolfram@kriesing.de>
  453. * @param mixed $id the id of the node to be removed
  454. * @return boolean true on success
  455. */
  456. function _remove($element)
  457. {
  458. return $element['id'];
  459. }
  460. // }}}
  461. // {{{ move()
  462. /**
  463. * move an entry under a given parent or behind a given entry.
  464. * !!! the 'move behind another element' is only implemented for nested
  465. * trees now!!!.
  466. * If a newPrevId is given the newParentId is dismissed!
  467. * call it either like this:
  468. * $tree->move(x, y)
  469. * to move the element (or entire tree) with the id x
  470. * under the element with the id y
  471. * or
  472. * <code>
  473. * // ommit the second parameter by setting it to 0
  474. * $tree->move(x, 0, y);
  475. * </code>
  476. * to move the element (or entire tree) with the id x
  477. * behind the element with the id y
  478. * or
  479. * <code>
  480. * $tree->move(array(x1,x2,x3), ...
  481. * </code>
  482. * the first parameter can also be an array of elements that shall
  483. * be moved
  484. * the second and third para can be as described above
  485. *
  486. * @version 2002/06/08
  487. * @access public
  488. * @author Wolfram Kriesing <wolfram@kriesing.de>
  489. * @param integer the id(s) of the element(s) that shall be moved
  490. * @param integer the id of the element which will be the new parent
  491. * @param integer if prevId is given the element with the id idToMove
  492. * shall be moved _behind_ the element
  493. * with id=prevId if it is 0 it will be put at
  494. * the beginning
  495. * @return boolean true for success
  496. */
  497. function move($idsToMove, $newParentId, $newPrevId = 0)
  498. {
  499. settype($idsToMove,'array');
  500. $errors = array();
  501. foreach ($idsToMove as $idToMove) {
  502. $ret = $this->_move($idToMove, $newParentId, $newPrevId);
  503. if (PEAR::isError($ret)) {
  504. $errors[] = $ret;
  505. }
  506. }
  507. // FIXXME return a Tree_Error, not an array !!!!!
  508. if (count($errors)) {
  509. return $errors;
  510. }
  511. return true;
  512. }
  513. // }}}
  514. // {{{ _move()
  515. /**
  516. * this method moves one tree element
  517. *
  518. * @see move()
  519. * @version 2001/10/10
  520. * @access public
  521. * @author Wolfram Kriesing <wolfram@kriesing.de>
  522. * @param integer the id of the element that shall be moved
  523. * @param integer the id of the element which will be
  524. * the new parent
  525. * @param integer if prevId is given the element with the id idToMove
  526. * shall be moved _behind_ the element with id=prevId
  527. * if it is 0 it will be put at the beginning
  528. * @return mixed true for success, Tree_Error on failure
  529. */
  530. function _move($idToMove, $newParentId, $prevId = 0)
  531. {
  532. // itself can not be a parent of itself
  533. if ($idToMove == $newParentId) {
  534. // TODO PEAR-ize error
  535. return TREE_ERROR_INVALID_PARENT;
  536. }
  537. // check if $newParentId is a child (or a child-child ...) of $idToMove
  538. // if so prevent moving, because that is not possible
  539. // does this element have children?
  540. if ($this->hasChildren($idToMove)) {
  541. $allChildren = $this->getChildren($idToMove);
  542. // FIXXME what happens here we are changing $allChildren,
  543. // doesnt this change the property data too??? since getChildren
  544. // (might, not yet) return a reference use while since foreach
  545. // only works on a copy of the data to loop through, but we are
  546. // changing $allChildren in the loop
  547. while (list(, $aChild) = each ($allChildren)) {
  548. // remove the first element because if array_merge is called
  549. // the array pointer seems to be
  550. array_shift($allChildren);
  551. // set to the beginning and this way the beginning is always
  552. // the current element, simply work off and truncate in front
  553. if (@$aChild['children']) {
  554. $allChildren = array_merge($allChildren,
  555. $aChild['children']
  556. );
  557. }
  558. if ($newParentId == $aChild['id']) {
  559. // TODO PEAR-ize error
  560. return TREE_ERROR_INVALID_PARENT;
  561. }
  562. }
  563. }
  564. // what happens if i am using the prevId too, then the db class also
  565. // needs to know where the element should be moved to
  566. // and it has to change the prevId of the element that will be after it
  567. // so we may be simply call some method like 'update' too?
  568. if (method_exists($this->dataSourceClass, 'move')) {
  569. return $this->dataSourceClass->move($idToMove,
  570. $newParentId,
  571. $prevId
  572. );
  573. } else {
  574. return $this->_throwError('method not implemented yet.', __LINE__);
  575. }
  576. }
  577. // }}}
  578. // {{{ update()
  579. /**
  580. * update data in a node
  581. *
  582. * @version 2002/01/29
  583. * @access public
  584. * @author Wolfram Kriesing <wolfram@kriesing.de>
  585. * @param integer the ID of the element that shall be updated
  586. * @param array the data to update
  587. * @return mixed either boolean or
  588. * an error object if the method is not implemented
  589. */
  590. function update($id, $data)
  591. {
  592. if (method_exists($this->dataSourceClass, 'update')) {
  593. return $this->dataSourceClass->update($id,$data);
  594. } else {
  595. return $this->_throwError(
  596. 'method not implemented yet.', __LINE__
  597. );
  598. }
  599. }
  600. // }}}
  601. //
  602. //
  603. // from here all methods are not interacting on the 'dataSourceClass'
  604. //
  605. //
  606. // {{{ buildStructure()
  607. /**
  608. * builds the structure in the parameter $insertIn
  609. * this function works recursively down into depth of the folder structure
  610. * it builds an array which goes as deep as the structure goes
  611. *
  612. * @access public
  613. * @version 2001/05/02
  614. * @author Wolfram Kriesing <wolfram@kriesing.de>
  615. * @param integer the parent for which it's structure shall
  616. * be built
  617. * @param integer the array where to build the structure in
  618. * given as a reference to be sure the substructure is built
  619. * in the same array as passed to the function
  620. * @return boolean returns always true
  621. *
  622. */
  623. function buildStructure($parentId, &$insertIn)
  624. {
  625. // create the element, so it exists in the property "structure"
  626. // also if there are no children below
  627. $insertIn[$parentId] = array();
  628. // set the level, since we are walking through the structure here.
  629. // Anyway we can do this here, instead of up in the setup method :-)
  630. // always set the level to one higher than the parent's level, easy ha?
  631. // this applies only to the root element(s)
  632. if (isset($this->data[$parentId]['parent']['level'])) {
  633. $this->data[$parentId]['level'] =
  634. $this->data[$parentId]['parent']['level']+1;
  635. if ($this->data[$parentId]['level']>$this->_treeDepth) {
  636. $this->_treeDepth = $this->data[$parentId]['level'];
  637. }
  638. } else {
  639. // set first level number to 0
  640. $this->data[$parentId]['level'] = 0;
  641. }
  642. if (isset($this->children[$parentId])
  643. && count($this->children[$parentId])) {
  644. // go thru all the folders
  645. foreach ($this->children[$parentId] as $child) {
  646. // build the structure under this folder,
  647. // use the current folder as the new parent and call
  648. // build recursively to build all the children by calling build
  649. // with $insertIn[someindex] the array is filled
  650. // since the array was empty before
  651. $this->buildStructure($child, $insertIn[$parentId]);
  652. }
  653. }
  654. return true;
  655. }
  656. // }}}
  657. // {{{ walk()
  658. /**
  659. * this method only serves to call the _walk method and
  660. * reset $this->walkReturn that will be returned by all the walk-steps
  661. *
  662. * @version 2001/11/25
  663. * @access public
  664. * @author Wolfram Kriesing <wolfram@kriesing.de>
  665. * @param mixed the name of the function to call for each walk step,
  666. * or an array for a method, where [0] is the method name
  667. * and [1] the object
  668. * @param array the id to start walking through the tree, everything
  669. * below is walked through
  670. * @param string the return of all the walk data will be of the given
  671. * type (values: string, array)
  672. * @return mixed this is all the return data collected from all
  673. * the walk-steps
  674. */
  675. function walk($walkFunction, $id = 0, $returnType = 'string')
  676. {
  677. // by default all of structure is used
  678. $useNode = $this->structure;
  679. if ($id == 0) {
  680. $keys = array_keys($this->structure);
  681. $id = $keys[0];
  682. } else {
  683. // get the path, to be able to go to the element in this->structure
  684. $path = $this->getPath($id);
  685. // pop off the last element, since it is the one requested
  686. array_pop($path);
  687. // start at the root of structure
  688. $curNode = $this->structure;
  689. foreach ($path as $node) {
  690. // go as deep into structure as path defines
  691. $curNode = $curNode[$node['id']];
  692. }
  693. // empty it first, so we dont have the other stuff in there
  694. // from before
  695. $useNode = array();
  696. // copy only the branch of the tree that the parameter
  697. // $id requested
  698. $useNode[$id] = $curNode[$id];
  699. }
  700. // a new walk starts, unset the return value
  701. unset($this->walkReturn);
  702. return $this->_walk($walkFunction, $useNode, $returnType);
  703. }
  704. // }}}
  705. // {{{ _walk()
  706. /**
  707. * walks through the entire tree and returns the current element and the level
  708. * so a user can use this to build a treemap or whatever
  709. *
  710. * @version 2001/06/xx
  711. * @access private
  712. * @author Wolfram Kriesing <wolfram@kriesing.de>
  713. * @param mixed the name of the function to call for each walk step,
  714. * or an array for a method, where [0] is the method name
  715. * and [1] the object
  716. *
  717. * @param array the reference in the this->structure, to walk
  718. * everything below
  719. * @param string the return of all the walk data will be
  720. * of the given type (values: string, array, ifArray)
  721. * @return mixed this is all the return data collected from all
  722. * the walk-steps
  723. */
  724. function _walk($walkFunction, &$curLevel, $returnType)
  725. {
  726. if (count($curLevel)) {
  727. foreach ($curLevel as $key => $value) {
  728. $ret = call_user_func($walkFunction, $this->data[$key]);
  729. switch ($returnType) {
  730. case 'array':
  731. $this->walkReturn[] = $ret;
  732. break;
  733. // this only adds the element if the $ret is an array
  734. // and contains data
  735. case 'ifArray':
  736. if (is_array($ret)) {
  737. $this->walkReturn[] = $ret;
  738. }
  739. break;
  740. default:
  741. $this->walkReturn.= $ret;
  742. break;
  743. }
  744. $this->_walk($walkFunction, $value, $returnType);
  745. }
  746. }
  747. return $this->walkReturn;
  748. }
  749. // }}}
  750. // {{{ addNode()
  751. /**
  752. * Adds multiple elements. You have to pass those elements
  753. * in a multidimensional array which represents the tree structure
  754. * as it shall be added (this array can of course also simply contain
  755. * one element).
  756. * The following array $x passed as the parameter
  757. * $x[0] = array('name' => 'bla',
  758. * 'parentId' => '30',
  759. * array('name' => 'bla1',
  760. * 'comment' => 'foo',
  761. * array('name' => 'bla2'),
  762. * array('name' => 'bla2_1')
  763. * ),
  764. * array('name'=>'bla1_1'),
  765. * );
  766. * $x[1] = array('name' => 'fooBla',
  767. * 'parentId' => '30');
  768. *
  769. * would add the following tree (or subtree/node) under the parent
  770. * with the id 30 (since 'parentId'=30 in $x[0] and in $x[1]):
  771. * +--bla
  772. * | +--bla1
  773. * | | +--bla2
  774. * | | +--bla2_1
  775. * | +--bla1_1
  776. * +--fooBla
  777. *
  778. * @see add()
  779. * @version 2001/12/19
  780. * @access public
  781. * @author Wolfram Kriesing <wolfram@kriesing.de>
  782. * @param array the tree to be inserted, represents the tree structure,
  783. * see add() for the exact member of each node
  784. * @return mixed either boolean false on failure
  785. * or the id of the inserted row
  786. */
  787. function addNode($node)
  788. {
  789. if (count($node)) {
  790. foreach ($node as $aNode) {
  791. $newNode = array();
  792. // this should always have data, if not the passed
  793. // structure has an error
  794. foreach ($aNode as $name => $value) {
  795. // collect the data that need to go in the DB
  796. if (!is_array($value)) {
  797. $newEntry[$name] = $value;
  798. } else {
  799. // collect the children
  800. $newNode[] = $value;
  801. }
  802. }
  803. // add the element and get the id, that it got, to have
  804. // the parentId for the children
  805. $insertedId = $this->add($newEntry);
  806. // if inserting suceeded, we have received the id
  807. // under which we can insert the children
  808. if ($insertedId!= false) {
  809. // if there are children, set their parentId.
  810. // So they kknow where they belong in the tree
  811. if (count($newNode)) {
  812. foreach($newNode as $key => $aNewNode) {
  813. $newNode[$key]['parentId'] = $insertedId;
  814. }
  815. }
  816. // call yourself recursively to insert the children
  817. // and its children
  818. $this->addNode($newNode);
  819. }
  820. }
  821. }
  822. }
  823. // }}}
  824. // {{{ getPath()
  825. /**
  826. * gets the path to the element given by its id
  827. * !!! ATTENTION watch out that you never change any of the data returned,
  828. * since they are references to the internal property $data
  829. *
  830. * @access public
  831. * @version 2001/10/10
  832. * @access public
  833. * @author Wolfram Kriesing <wolfram@kriesing.de>
  834. * @param mixed the id of the node to get the path for
  835. * @return array this array contains all elements from the root
  836. * to the element given by the id
  837. *
  838. */
  839. function getPath($id)
  840. {
  841. // empty the path, to be clean
  842. $path = array();
  843. // FIXXME may its better to use a for(level) to count down,
  844. // since a while is always a little risky
  845. // until there are no more parents
  846. while (@$this->data[$id]['parent']) {
  847. // curElement is already a reference, so save it in path
  848. $path[] = &$this->data[$id];
  849. // get the next parent id, for the while to retreive the parent's parent
  850. $id = $this->data[$id]['parent']['id'];
  851. }
  852. // dont forget the last one
  853. $path[] = &$this->data[$id];
  854. return array_reverse($path);
  855. }
  856. // }}}
  857. // {{{ setRemoveRecursively()
  858. /**
  859. * sets the remove-recursively mode, either true or false
  860. *
  861. * @version 2001/10/09
  862. * @access public
  863. * @author Wolfram Kriesing <wolfram@kriesing.de>
  864. * @param boolean set to true if removing a tree level
  865. * shall remove all it's children and theit children
  866. *
  867. */
  868. function setRemoveRecursively($case=true)
  869. {
  870. $this->removeRecursively = $case;
  871. }
  872. // }}}
  873. // {{{ _getElement()
  874. /**
  875. *
  876. *
  877. * @version 2002/01/21
  878. * @access private
  879. * @author Wolfram Kriesing <wolfram@kriesing.de>
  880. * @param int the element ID
  881. *
  882. */
  883. function &_getElement($id, $what = '')
  884. {
  885. // We should not return false, since that might be a value of the
  886. // element that is requested.
  887. $element = null;
  888. if ($what == '') {
  889. $element = $this->data[$id];
  890. }
  891. $elementId = $this->_getElementId($id, $what);
  892. if ($elementId !== null) {
  893. $element = $this->data[$elementId];
  894. }
  895. // we should not return false, since that might be a value
  896. // of the element that is requested
  897. return $element;
  898. }
  899. // }}}
  900. // {{{ _getElementId()
  901. /**
  902. *
  903. *
  904. * @version 2002/01/21
  905. * @access private
  906. * @author Wolfram Kriesing <wolfram@kriesing.de>
  907. * @param int the element ID
  908. *
  909. */
  910. function _getElementId($id, $what)
  911. {
  912. if (isset($this->data[$id][$what]) && $this->data[$id][$what]) {
  913. return $this->data[$id][$what]['id'];
  914. }
  915. return null;
  916. }
  917. // }}}
  918. // {{{ getElement()
  919. /**
  920. * gets an element as a reference
  921. *
  922. * @version 2002/01/21
  923. * @access private
  924. * @author Wolfram Kriesing <wolfram@kriesing.de>
  925. * @param int the element ID
  926. *
  927. */
  928. function &getElement($id)
  929. {
  930. return $element = &$this->_getElement($id);
  931. }
  932. // }}}
  933. // {{{ getElementContent()
  934. /**
  935. *
  936. *
  937. * @version 2002/02/06
  938. * @access private
  939. * @author Wolfram Kriesing <wolfram@kriesing.de>
  940. * @param mixed either the id of an element
  941. * or the path to the element
  942. *
  943. */
  944. function getElementContent($idOrPath, $fieldName)
  945. {
  946. if (is_string($idOrPath)) {
  947. $idOrPath = $this->getIdByPath($idOrPath);
  948. }
  949. return $this->data[$idOrPath][$fieldName];
  950. }
  951. // }}}
  952. // {{{ getElementsContent()
  953. /**
  954. *
  955. *
  956. * @version 2002/02/06
  957. * @access private
  958. * @author Wolfram Kriesing <wolfram@kriesing.de>
  959. * @param int the element ID
  960. *
  961. */
  962. function getElementsContent($ids, $fieldName) {
  963. // i dont know if this method is not just overloading the file.
  964. // Since it only serves my lazyness
  965. // is this effective here? i can also loop in the calling code!?
  966. $fields = array();
  967. if (is_array($ids) && count($ids)) {
  968. foreach ($ids as $aId) {
  969. $fields[] = $this->getElementContent($aId, $fieldName);
  970. }
  971. }
  972. return $fields;
  973. }
  974. // }}}
  975. // {{{ getElementByPath()
  976. /**
  977. * gets an element given by it's path as a reference
  978. *
  979. * @version 2002/01/21
  980. * @access public
  981. * @author Wolfram Kriesing <wolfram@kriesing.de>
  982. * @param string the path to search for
  983. * @param integer the id where to search for the path
  984. * @param string the name of the key that contains the node name
  985. * @param string the path separator
  986. * @return integer the id of the searched element
  987. *
  988. */
  989. function &getElementByPath($path, $startId = 0, $nodeName = 'name', $seperator = '/')
  990. {
  991. $element = null;
  992. $id = $this->getIdByPath($path,$startId);
  993. if ($id) {
  994. $element = &$this->getElement($id);
  995. }
  996. // return null since false might be interpreted as id 0
  997. return $element;
  998. }
  999. // }}}
  1000. // {{{ getLevel()
  1001. /**
  1002. * get the level, which is how far below the root are we?
  1003. *
  1004. * @version 2001/11/25
  1005. * @access public
  1006. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1007. * @param mixed $id the id of the node to get the level for
  1008. *
  1009. */
  1010. function getLevel($id)
  1011. {
  1012. return $this->data[$id]['level'];
  1013. }
  1014. // }}}
  1015. // {{{ getChild()
  1016. /**
  1017. * returns the child if the node given has one
  1018. * !!! ATTENTION watch out that you never change any of the data returned,
  1019. * since they are references to the internal property $data
  1020. *
  1021. * @version 2001/11/27
  1022. * @access public
  1023. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1024. * @param mixed the id of the node to get the child for
  1025. *
  1026. */
  1027. function &getChild($id)
  1028. {
  1029. return $element = &$this->_getElement($id, 'child');
  1030. }
  1031. // }}}
  1032. // {{{ getParent()
  1033. /**
  1034. * returns the child if the node given has one
  1035. * !!! ATTENTION watch out that you never change any of the data returned,
  1036. * since they are references to the internal property $data
  1037. *
  1038. * @version 2001/11/27
  1039. * @access public
  1040. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1041. * @param mixed $id the id of the node to get the child for
  1042. *
  1043. */
  1044. function &getParent($id)
  1045. {
  1046. return $element = &$this->_getElement($id, 'parent');
  1047. }
  1048. // }}}
  1049. // {{{ getNext()
  1050. /**
  1051. * returns the next element if the node given has one
  1052. * !!! ATTENTION watch out that you never change any of the data returned,
  1053. * since they are references to the internal property $data
  1054. *
  1055. * @version 2002/01/17
  1056. * @access public
  1057. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1058. * @param mixed the id of the node to get the child for
  1059. * @return mixed reference to the next element or false if there is none
  1060. */
  1061. function &getNext($id)
  1062. {
  1063. return $element = &$this->_getElement($id, 'next');
  1064. }
  1065. // }}}
  1066. // {{{ getPrevious()
  1067. /**
  1068. * returns the previous element if the node given has one
  1069. * !!! ATTENTION watch out that you never change any of the data returned,
  1070. * since they are references to the internal property $data
  1071. *
  1072. * @version 2002/02/05
  1073. * @access public
  1074. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1075. * @param mixed the id of the node to get the child for
  1076. * @return mixed reference to the next element or false if there is none
  1077. */
  1078. function &getPrevious($id)
  1079. {
  1080. return $element = &$this->_getElement($id, 'previous');
  1081. }
  1082. // }}}
  1083. // {{{ getNode()
  1084. /**
  1085. * returns the node for the given id
  1086. * !!! ATTENTION watch out that you never change any of the data returned,
  1087. * since they are references to the internal property $data
  1088. *
  1089. * @version 2001/11/28
  1090. * @access public
  1091. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1092. * @param mixed $id the id of the node to get
  1093. *
  1094. */
  1095. /*
  1096. function &getNode($id)
  1097. {
  1098. //return $element = &$this->_getElement($id);
  1099. }
  1100. */
  1101. // }}}
  1102. // {{{ getIdByPath()
  1103. /**
  1104. * return the id of the element which is referenced by $path
  1105. * this is useful for xml-structures, like: getIdByPath('/root/sub1/sub2')
  1106. * this requires the structure to use each name uniquely
  1107. * if this is not given it will return the first proper path found
  1108. * i.e. there should only be one path /x/y/z
  1109. *
  1110. * @version 2001/11/28
  1111. * @access public
  1112. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1113. * @param string $path the path to search for
  1114. * @param integer $startId the id where to search for the path
  1115. * @param string $nodeName the name of the key that contains the node name
  1116. * @param string $seperator the path seperator
  1117. * @return integer the id of the searched element
  1118. *
  1119. */
  1120. function getIdByPath($path, $startId = 0, $nodeName = 'name', $seperator = '/')
  1121. // should this method be called getElementIdByPath ????
  1122. {
  1123. // if no start ID is given get the root
  1124. if ($startId == 0) {
  1125. $startId = $this->getFirstRootId();
  1126. } else { // if a start id is given, get its first child to start searching there
  1127. $startId = $this->getChildId($startId);
  1128. if ($startId==false) { // is there a child to this element?
  1129. return false;
  1130. }
  1131. }
  1132. if (strpos($path,$seperator) === 0) { // if a seperator is at the beginning strip it off
  1133. $path = substr($path,strlen($seperator));
  1134. }
  1135. $nodes = explode($seperator, $path);
  1136. $curId = $startId;
  1137. foreach ($nodes as $key => $aNodeName) {
  1138. $nodeFound = false;
  1139. do {
  1140. if ($this->data[$curId][$nodeName] == $aNodeName) {
  1141. $nodeFound = true;
  1142. // do only save the child if we are not already at the end of path
  1143. // because then we need curId to return it
  1144. if ($key < (count($nodes) - 1)) {
  1145. $curId = $this->getChildId($curId);
  1146. }
  1147. break;
  1148. }
  1149. $curId = $this->getNextId($curId);
  1150. } while($curId);
  1151. if ($nodeFound == false) {
  1152. return false;
  1153. }
  1154. }
  1155. return $curId;
  1156. // FIXXME to be implemented
  1157. }
  1158. // }}}
  1159. // {{{ getFirstRoot()
  1160. /**
  1161. * this gets the first element that is in the root node
  1162. * i think that there can't be a "getRoot" method since there might
  1163. * be multiple number of elements in the root node, at least the
  1164. * way it works now
  1165. *
  1166. * @access public
  1167. * @version 2001/12/10
  1168. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1169. * @return returns the first root element
  1170. */
  1171. function &getFirstRoot()
  1172. {
  1173. // could also be reset($this->data) i think since php keeps the order
  1174. // ... but i didnt try
  1175. reset($this->structure);
  1176. return $this->data[key($this->structure)];
  1177. }
  1178. // }}}
  1179. // {{{ getRoot()
  1180. /**
  1181. * since in a nested tree there can only be one root
  1182. * which i think (now) is correct, we also need an alias for this method
  1183. * this also makes all the methods in Tree_Common, which access the
  1184. * root element work properly!
  1185. *
  1186. * @access public
  1187. * @version 2002/07/26
  1188. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1189. * @return returns the first root element
  1190. */
  1191. function &getRoot()
  1192. {
  1193. return $this->getFirstRoot();
  1194. }
  1195. // }}}
  1196. // {{{ getRoot()
  1197. /**
  1198. * gets the tree under the given element in one array, sorted
  1199. * so you can go through the elements from begin to end and list them
  1200. * as they are in the tree, where every child (until the deepest) is retreived
  1201. *
  1202. * @see &_getNode()
  1203. * @access public
  1204. * @version 2001/12/17
  1205. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1206. * @param integer the id where to start walking
  1207. * @param integer this number says how deep into
  1208. * the structure the elements shall be
  1209. * retreived
  1210. * @return array sorted as listed in the tree
  1211. */
  1212. function &getNode($startId=0, $depth=0)
  1213. {
  1214. if ($startId == 0) {
  1215. $level = 0;
  1216. } else {
  1217. $level = $this->getLevel($startId);
  1218. }
  1219. $this->_getNodeMaxLevel = $depth ? ($depth + $level) : 0 ;
  1220. //!!! $this->_getNodeCurParent = $this->data['parent']['id'];
  1221. // if the tree is empty dont walk through it
  1222. if (!count($this->data)) {
  1223. return;
  1224. }
  1225. $ret = $this->walk(array(&$this,'_getNode'), $startId, 'ifArray');
  1226. return $ret;
  1227. }
  1228. // }}}
  1229. // {{{ _getNode()
  1230. /**
  1231. * this is used for walking through the tree structure
  1232. * until a given level, this method should only be used by getNode
  1233. *
  1234. * @see &getNode()
  1235. * @see walk()
  1236. * @see _walk()
  1237. * @access private
  1238. * @version 2001/12/17
  1239. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1240. * @param array the node passed by _walk
  1241. * @return mixed either returns the node, or nothing
  1242. * if the level _getNodeMaxLevel is reached
  1243. */
  1244. function &_getNode(&$node)
  1245. {
  1246. if ($this->_getNodeMaxLevel) {
  1247. if ($this->getLevel($node['id']) < $this->_getNodeMaxLevel) {
  1248. return $node;
  1249. }
  1250. return;
  1251. }
  1252. return $node;
  1253. }
  1254. // }}}
  1255. // {{{ getChildren()
  1256. /**
  1257. * returns the children of the given ids
  1258. *
  1259. * @version 2001/12/17
  1260. * @access public
  1261. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1262. * @param integer $id the id of the node to check for children
  1263. * @param integer the children of how many levels shall be returned
  1264. * @return boolean true if the node has children
  1265. */
  1266. function getChildren($ids, $levels = 1)
  1267. {
  1268. //FIXXME $levels to be implemented
  1269. $ret = array();
  1270. if (is_array($ids)) {
  1271. foreach ($ids as $aId) {
  1272. if ($this->hasChildren($aId)) {
  1273. $ret[$aId] = $this->data[$aId]['children'];
  1274. }
  1275. }
  1276. } else {
  1277. if ($this->hasChildren($ids)) {
  1278. $ret = $this->data[$ids]['children'];
  1279. }
  1280. }
  1281. return $ret;
  1282. }
  1283. // }}}
  1284. // {{{ isNode()
  1285. /**
  1286. * returns if the given element is a valid node
  1287. *
  1288. * @version 2001/12/21
  1289. * @access public
  1290. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1291. * @param integer $id the id of the node to check for children
  1292. * @return boolean true if the node has children
  1293. */
  1294. function isNode($id = 0)
  1295. {
  1296. return isset($this->data[$id]);
  1297. }
  1298. // }}}
  1299. // {{{ varDump()
  1300. /**
  1301. * this is for debugging, dumps the entire data-array
  1302. * an extra method is needed, since this array contains recursive
  1303. * elements which make a normal print_f or var_dump not show all the data
  1304. *
  1305. * @version 2002/01/21
  1306. * @access public
  1307. * @author Wolfram Kriesing <wolfram@kriesing.de>
  1308. * @params mixed either the id of the node to dump, this will dump
  1309. * everything below the given node or an array of nodes
  1310. * to dump. This only dumps the elements passed
  1311. * as an array. 0 or no parameter if the entire tree shall
  1312. * be dumped if you want to dump only a single element
  1313. * pass it as an array using array($element).
  1314. */
  1315. function varDump($

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