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

/horde-3.3.13/lib/Horde/DataTree.php

#
PHP | 1635 lines | 690 code | 161 blank | 784 comment | 114 complexity | 76aa6f420de90b4612610aaab9e3d2e7 MD5 | raw file
Possible License(s): LGPL-2.0

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

  1. <?php
  2. /**
  3. * @package Horde_DataTree
  4. *
  5. * $Horde: framework/DataTree/DataTree.php,v 1.151.2.24 2009/01/06 15:23:01 jan Exp $
  6. */
  7. /** List every object in an array, similar to PEAR/html/menu.php. */
  8. define('DATATREE_FORMAT_TREE', 1);
  9. /** Get a full list - an array of keys. */
  10. define('DATATREE_FORMAT_FLAT', 2);
  11. /** The root element (top-level parent) of each DataTree group. */
  12. define('DATATREE_ROOT', -1);
  13. /** Build a normal select query. */
  14. define('DATATREE_BUILD_SELECT', 0);
  15. /** Build a count only query. */
  16. define('DATATREE_BUILD_COUNT', 1);
  17. /** Build an attribute only query. */
  18. define('DATATREE_BUILD_VALUES', 2);
  19. define('DATATREE_BUILD_VALUES_COUNT', 3);
  20. /**
  21. * The DataTree:: class provides a common abstracted interface into the
  22. * various backends for the Horde DataTree system.
  23. *
  24. * A piece of data is just a title that is saved in the page for the null
  25. * driver or can be saved in a database to be accessed from everywhere. Every
  26. * stored object must have a different name (inside each groupid).
  27. *
  28. * Required values for $params:<pre>
  29. * 'group' -- Define each group of objects we want to build.</pre>
  30. *
  31. * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  32. *
  33. * See the enclosed file COPYING for license information (LGPL). If you
  34. * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
  35. *
  36. * @author Stephane Huther <shuther1@free.fr>
  37. * @author Chuck Hagenbuch <chuck@horde.org>
  38. * @package Horde_DataTree
  39. */
  40. class DataTree {
  41. /**
  42. * Array of all data: indexed by id. The format is:
  43. * array(id => 'name' => name, 'parent' => parent).
  44. *
  45. * @var array
  46. */
  47. var $_data = array();
  48. /**
  49. * A hash that can be used to map a full object name
  50. * (parent:child:object) to that object's unique ID.
  51. *
  52. * @var array
  53. */
  54. var $_nameMap = array();
  55. /**
  56. * Actual attribute sorting hash.
  57. *
  58. * @var array
  59. */
  60. var $_sortHash = null;
  61. /**
  62. * Hash containing connection parameters.
  63. *
  64. * @var array
  65. */
  66. var $_params = array();
  67. /**
  68. * Constructor.
  69. *
  70. * @param array $params A hash containing any additional configuration or
  71. * connection parameters a subclass might need.
  72. * We always need 'group', a string that defines the
  73. * prefix for each set of hierarchical data.
  74. */
  75. function DataTree($params = array())
  76. {
  77. $this->_params = $params;
  78. }
  79. /**
  80. * Returns a parameter of this DataTree instance.
  81. *
  82. * @param string $param The parameter to return.
  83. *
  84. * @return mixed The parameter's value or null if it doesn't exist.
  85. */
  86. function getParam($param)
  87. {
  88. return isset($this->_params[$param]) ? $this->_params[$param] : null;
  89. }
  90. /**
  91. * Removes an object.
  92. *
  93. * @param string $object The object to remove.
  94. * @param boolean $force Force removal of every child object?
  95. *
  96. * @return TODO
  97. */
  98. function remove($object, $force = false)
  99. {
  100. if (is_a($object, 'DataTreeObject')) {
  101. $object = $object->getName();
  102. }
  103. if (!$this->exists($object)) {
  104. return PEAR::raiseError($object . ' does not exist');
  105. }
  106. $children = $this->getNumberOfChildren($object);
  107. if ($children) {
  108. /* TODO: remove children if $force == true */
  109. return PEAR::raiseError(sprintf(ngettext("Cannot remove, %d child exists.", "Cannot remove, %d children exist.", count($children)), count($children)));
  110. }
  111. $id = $this->getId($object);
  112. $pid = $this->getParent($object);
  113. $order = $this->_data[$id]['order'];
  114. unset($this->_data[$id], $this->_nameMap[$id]);
  115. // Shift down the order positions.
  116. $this->_reorder($pid, $order);
  117. return $id;
  118. }
  119. /**
  120. * Removes all DataTree objects owned by a certain user.
  121. *
  122. * @abstract
  123. *
  124. * @param string $user A user name.
  125. *
  126. * @return TODO
  127. */
  128. function removeUserData($user)
  129. {
  130. return PEAR::raiseError('not supported');
  131. }
  132. /**
  133. * Move an object to a new parent.
  134. *
  135. * @param mixed $object The object to move.
  136. * @param string $newparent The new parent object. Defaults to the root.
  137. *
  138. * @return mixed True on success, PEAR_Error on error.
  139. */
  140. function move($object, $newparent = null)
  141. {
  142. $cid = $this->getId($object);
  143. if (is_a($cid, 'PEAR_Error')) {
  144. return PEAR::raiseError(sprintf('Object to move does not exist: %s', $cid->getMessage()));
  145. }
  146. if (!is_null($newparent)) {
  147. $pid = $this->getId($newparent);
  148. if (is_a($pid, 'PEAR_Error')) {
  149. return PEAR::raiseError(sprintf('New parent does not exist: %s', $pid->getMessage()));
  150. }
  151. } else {
  152. $pid = DATATREE_ROOT;
  153. }
  154. $this->_data[$cid]['parent'] = $pid;
  155. return true;
  156. }
  157. /**
  158. * Change an object's name.
  159. *
  160. * @param mixed $old_object The old object.
  161. * @param string $new_object_name The new object name.
  162. *
  163. * @return mixed True on success, PEAR_Error on error.
  164. */
  165. function rename($old_object, $new_object_name)
  166. {
  167. /* Check whether the object exists at all */
  168. if (!$this->exists($old_object)) {
  169. return PEAR::raiseError($old_object . ' does not exist');
  170. }
  171. /* Check for duplicates - get parent and create new object
  172. * name */
  173. $parent = $this->getName($this->getParent($old_object));
  174. if ($this->exists($parent . ':' . $new_object_name)) {
  175. return PEAR::raiseError('Duplicate name ' . $new_object_name);
  176. }
  177. /* Replace the old name with the new one in the cache */
  178. $old_object_id = $this->getId($old_object);
  179. $this->_data[$old_object_id]['name'] = $new_object_name;
  180. return true;
  181. }
  182. /**
  183. * Changes the order of the children of an object.
  184. *
  185. * @abstract
  186. *
  187. * @param string $parent The full id path of the parent object.
  188. * @param mixed $order If an array it specifies the new positions for
  189. * all child objects.
  190. * If an integer and $cid is specified, the position
  191. * where the child specified by $cid is inserted. If
  192. * $cid is not specified, the position gets deleted,
  193. * causing the following positions to shift up.
  194. * @param integer $cid See $order.
  195. *
  196. * @return TODO
  197. */
  198. function reorder($parents, $order = null, $cid = null)
  199. {
  200. return PEAR::raiseError('not supported');
  201. }
  202. /**
  203. * Change order of children of an object.
  204. *
  205. * @param string $pid The parent object id string path.
  206. * @param mixed $order Specific new order position or an array containing
  207. * the new positions for the given parent.
  208. * @param integer $cid If provided indicates insertion of a new child to
  209. * the parent to avoid incrementing it when
  210. * shifting up all other children's order. If not
  211. * provided indicates deletion, so shift all other
  212. * positions down one.
  213. */
  214. function _reorder($pid, $order = null, $cid = null)
  215. {
  216. if (!is_array($order) && !is_null($order)) {
  217. // Single update (add/del).
  218. if (is_null($cid)) {
  219. // No id given so shuffle down.
  220. foreach ($this->_data as $c_key => $c_val) {
  221. if ($this->_data[$c_key]['parent'] == $pid &&
  222. $this->_data[$c_key]['order'] > $order) {
  223. --$this->_data[$c_key]['order'];
  224. }
  225. }
  226. } else {
  227. // We have an id so shuffle up.
  228. foreach ($this->_data as $c_key => $c_val) {
  229. if ($c_key != $cid &&
  230. $this->_data[$c_key]['parent'] == $pid &&
  231. $this->_data[$c_key]['order'] >= $order) {
  232. ++$this->_data[$c_key]['order'];
  233. }
  234. }
  235. }
  236. } elseif (is_array($order) && count($order)) {
  237. // Multi update.
  238. foreach ($order as $order_position => $cid) {
  239. $this->_data[$cid]['order'] = $order_position;
  240. }
  241. }
  242. }
  243. /**
  244. * Explicitly set the order for a datatree object.
  245. *
  246. * @abstract
  247. *
  248. * @param integer $id The datatree object id to change.
  249. * @param integer $order The new order.
  250. *
  251. * @return TODO
  252. */
  253. function setOrder($id, $order)
  254. {
  255. return PEAR::raiseError('not supported');
  256. }
  257. /**
  258. * Dynamically determines the object class.
  259. *
  260. * @param array $attributes The set of attributes that contain the class
  261. * information. Defaults to DataTreeObject.
  262. *
  263. * @return TODO
  264. */
  265. function _defineObjectClass($attributes)
  266. {
  267. $class = 'DataTreeObject';
  268. if (!is_array($attributes)) {
  269. return $class;
  270. }
  271. foreach ($attributes as $attr) {
  272. if ($attr['name'] == 'DataTree') {
  273. switch ($attr['key']) {
  274. case 'objectClass':
  275. $class = $attr['value'];
  276. break;
  277. case 'objectType':
  278. $result = explode('/', $attr['value']);
  279. $class = $GLOBALS['registry']->callByPackage($result[0], 'defineClass', array('type' => $result[1]));
  280. break;
  281. }
  282. }
  283. }
  284. return $class;
  285. }
  286. /**
  287. * Returns a DataTreeObject (or subclass) object of the data in the
  288. * object defined by $object.
  289. *
  290. * @param string $object The object to fetch: 'parent:sub-parent:name'.
  291. * @param string $class Subclass of DataTreeObject to use. Defaults to
  292. * DataTreeObject. Null forces the driver to look
  293. * into the attributes table to determine the
  294. * subclass to use. If none is found it uses
  295. * DataTreeObject.
  296. *
  297. * @return TODO
  298. */
  299. function &getObject($object, $class = 'DataTreeObject')
  300. {
  301. if (empty($object)) {
  302. $error = PEAR::raiseError('No object requested.');
  303. return $error;
  304. }
  305. $this->_load($object);
  306. if (!$this->exists($object)) {
  307. $error = PEAR::raiseError($object . ' not found.');
  308. return $error;
  309. }
  310. return $this->_getObject($this->getId($object), $object, $class);
  311. }
  312. /**
  313. * Returns a DataTreeObject (or subclass) object of the data in the
  314. * object with the ID $id.
  315. *
  316. * @param integer $id An object id.
  317. * @param string $class Subclass of DataTreeObject to use. Defaults to
  318. * DataTreeObject. Null forces the driver to look
  319. * into the attributes table to determine the
  320. * subclass to use. If none is found it uses
  321. * DataTreeObject.
  322. *
  323. * @return TODO
  324. */
  325. function &getObjectById($id, $class = 'DataTreeObject')
  326. {
  327. if (empty($id)) {
  328. $object = PEAR::raiseError('No id requested.');
  329. return $object;
  330. }
  331. $result = $this->_loadById($id);
  332. if (is_a($result, 'PEAR_Error')) {
  333. return $result;
  334. }
  335. return $this->_getObject($id, $this->getName($id), $class);
  336. }
  337. /**
  338. * Helper function for getObject() and getObjectById().
  339. *
  340. * @access private
  341. */
  342. function &_getObject($id, $name, $class)
  343. {
  344. $use_attributes = is_null($class) || is_callable(array($class, '_fromAttributes'));
  345. if ($use_attributes) {
  346. $attributes = $this->getAttributes($id);
  347. if (is_a($attributes, 'PEAR_Error')) {
  348. return $attributes;
  349. }
  350. if (is_null($class)) {
  351. $class = $this->_defineObjectClass($attributes);
  352. }
  353. }
  354. if (!class_exists($class)) {
  355. $error = PEAR::raiseError($class . ' not found.');
  356. return $error;
  357. }
  358. $dataOb = new $class($name);
  359. $dataOb->setDataTree($this);
  360. /* If the class has a _fromAttributes method, load data from
  361. * the attributes backend. */
  362. if ($use_attributes) {
  363. $dataOb->_fromAttributes($attributes);
  364. } else {
  365. /* Otherwise load it from the old data storage field. */
  366. $dataOb->setData($this->getData($id));
  367. }
  368. $dataOb->setOrder($this->getOrder($name));
  369. return $dataOb;
  370. }
  371. /**
  372. * Returns an array of DataTreeObject (or subclass) objects
  373. * corresponding to the objects in $ids, with the object
  374. * names as the keys of the array.
  375. *
  376. * @param array $ids An array of object ids.
  377. * @param string $class Subclass of DataTreeObject to use. Defaults to
  378. * DataTreeObject. Null forces the driver to look
  379. * into the attributes table to determine the
  380. * subclass to use. If none is found it uses
  381. * DataTreeObject.
  382. *
  383. * @return TODO
  384. */
  385. function &getObjects($ids, $class = 'DataTreeObject')
  386. {
  387. $result = $this->_loadById($ids);
  388. if (is_a($result, 'PEAR_Error')) {
  389. return $result;
  390. }
  391. $defineClass = is_null($class);
  392. $attributes = $defineClass || is_callable(array($class, '_fromAttributes'));
  393. if ($attributes) {
  394. $data = $this->getAttributes($ids);
  395. } else {
  396. $data = $this->getData($ids);
  397. }
  398. $objects = array();
  399. foreach ($ids as $id) {
  400. $name = $this->getName($id);
  401. if (!empty($name) && !empty($data[$id])) {
  402. if ($defineClass) {
  403. $class = $this->_defineObjectClass($data[$id]);
  404. }
  405. if (!class_exists($class)) {
  406. return PEAR::raiseError($class . ' not found.');
  407. }
  408. $objects[$name] = new $class($name);
  409. $objects[$name]->setDataTree($this);
  410. if ($attributes) {
  411. $objects[$name]->_fromAttributes($data[$id]);
  412. } else {
  413. $objects[$name]->setData($data[$id]);
  414. }
  415. $objects[$name]->setOrder($this->getOrder($name));
  416. }
  417. }
  418. return $objects;
  419. }
  420. /**
  421. * Export a list of objects.
  422. *
  423. * @param constant $format Format of the export
  424. * @param string $startleaf The name of the leaf from which we start
  425. * the export tree.
  426. * @param boolean $reload Re-load the requested chunk? Defaults to
  427. * false (only what is currently loaded).
  428. * @param string $rootname The label to use for the root element.
  429. * Defaults to DATATREE_ROOT.
  430. * @param integer $maxdepth The maximum number of levels to return.
  431. * Defaults to DATATREE_ROOT, which is no
  432. * limit.
  433. * @param boolean $loadTree Load a tree starting at $root, or just the
  434. * requested level and direct parents?
  435. * Defaults to single level.
  436. * @param string $sortby_name Attribute name to use for sorting.
  437. * @param string $sortby_key Attribute key to use for sorting.
  438. * @param integer $direction Sort direction:
  439. * 0 - ascending
  440. * 1 - descending
  441. *
  442. * @return mixed The tree representation of the objects, or a PEAR_Error
  443. * on failure.
  444. */
  445. function get($format, $startleaf = DATATREE_ROOT, $reload = false,
  446. $rootname = DATATREE_ROOT, $maxdepth = -1, $loadTree = false,
  447. $sortby_name = null, $sortby_key = null, $direction = 0)
  448. {
  449. $out = array();
  450. /* Set sorting hash */
  451. if (!is_null($sortby_name)) {
  452. $this->_sortHash = DataTree::sortHash($startleaf, $sortby_name, $sortby_key, $direction);
  453. }
  454. $this->_load($startleaf, $loadTree, $reload, $sortby_name, $sortby_key, $direction);
  455. switch ($format) {
  456. case DATATREE_FORMAT_TREE:
  457. $startid = $this->getId($startleaf, $maxdepth);
  458. if (is_a($startid, 'PEAR_Error')) {
  459. return $startid;
  460. }
  461. $this->_extractAllLevelTree($out, $startid, $maxdepth);
  462. break;
  463. case DATATREE_FORMAT_FLAT:
  464. $startid = $this->getId($startleaf);
  465. if (is_a($startid, 'PEAR_Error')) {
  466. return $startid;
  467. }
  468. $this->_extractAllLevelList($out, $startid, $maxdepth);
  469. if (!empty($out[DATATREE_ROOT])) {
  470. $out[DATATREE_ROOT] = $rootname;
  471. }
  472. break;
  473. default:
  474. return PEAR::raiseError('Not supported');
  475. }
  476. if (!is_null($this->_sortHash)) {
  477. /* Reset sorting hash. */
  478. $this->_sortHash = null;
  479. /* Reverse since the attribute sorting combined with tree up-ward
  480. * sorting produces a reversed object order. */
  481. $out = array_reverse($out, true);
  482. }
  483. return $out;
  484. }
  485. /**
  486. * Counts objects.
  487. *
  488. * @param string $startleaf The name of the leaf from which we start
  489. * counting.
  490. *
  491. * @return integer The number of the objects below $startleaf.
  492. */
  493. function count($startleaf = DATATREE_ROOT)
  494. {
  495. return $this->_count($startleaf);
  496. }
  497. /**
  498. * Create attribute sort hash
  499. *
  500. * @since Horde 3.1
  501. *
  502. * @param string $root The name of the leaf from which we start
  503. * the export tree.
  504. * @param string $sortby_name Attribute name to use for sorting.
  505. * @param string $sortby_key Attribute key to use for sorting.
  506. * @param integer $direction Sort direction:
  507. * 0 - ascending
  508. * 1 - descending
  509. *
  510. * @return string The sort hash.
  511. */
  512. function sortHash($root, $sortby_name = null, $sortby_key = null,
  513. $direction = 0)
  514. {
  515. return sprintf('%s-%s-%s-%s', $root, $sortby_name, $sortby_key, $direction);
  516. }
  517. /**
  518. * Export a list of objects just like get() above, but uses an
  519. * object id to fetch the list of objects.
  520. *
  521. * @param constant $format Format of the export.
  522. * @param string $startleaf The id of the leaf from which we start the
  523. * export tree.
  524. * @param boolean $reload Reload the requested chunk? Defaults to
  525. * false (only what is currently loaded).
  526. * @param string $rootname The label to use for the root element.
  527. * Defaults to DATATREE_ROOT.
  528. * @param integer $maxdepth The maximum number of levels to return
  529. * Defaults to -1, which is no limit.
  530. *
  531. * @return mixed The tree representation of the objects, or a PEAR_Error
  532. * on failure.
  533. */
  534. function getById($format, $startleaf = DATATREE_ROOT, $reload = false,
  535. $rootname = DATATREE_ROOT, $maxdepth = -1)
  536. {
  537. $this->_loadById($startleaf);
  538. $out = array();
  539. switch ($format) {
  540. case DATATREE_FORMAT_TREE:
  541. $this->_extractAllLevelTree($out, $startleaf, $maxdepth);
  542. break;
  543. case DATATREE_FORMAT_FLAT:
  544. $this->_extractAllLevelList($out, $startleaf, $maxdepth);
  545. if (!empty($out[DATATREE_ROOT])) {
  546. $out[DATATREE_ROOT] = $rootname;
  547. }
  548. break;
  549. default:
  550. return PEAR::raiseError('Not supported');
  551. }
  552. return $out;
  553. }
  554. /**
  555. * Returns a list of all groups (root nodes) of the data tree.
  556. *
  557. * @abstract
  558. *
  559. * @return mixed The group IDs or PEAR_Error on error.
  560. */
  561. function getGroups()
  562. {
  563. return PEAR::raiseError('not supported');
  564. }
  565. /**
  566. * Retrieve data for an object from the datatree_data field.
  567. *
  568. * @abstract
  569. *
  570. * @param integer $cid The object id to fetch, or an array of object ids.
  571. *
  572. * @return TODO
  573. */
  574. function getData($cid)
  575. {
  576. return PEAR::raiseError('not supported');
  577. }
  578. /**
  579. * Import a list of objects. Used by drivers to populate the internal
  580. * $_data array.
  581. *
  582. * @param array $data The data to import.
  583. * @param string $charset The charset to convert the object name from.
  584. *
  585. * @return TODO
  586. */
  587. function set($data, $charset = null)
  588. {
  589. $cids = array();
  590. foreach ($data as $id => $cat) {
  591. if (!is_null($charset)) {
  592. $cat[1] = String::convertCharset($cat[1], $charset);
  593. }
  594. $cids[$cat[0]] = $cat[1];
  595. $cparents[$cat[0]] = $cat[2];
  596. $corders[$cat[0]] = $cat[3];
  597. $sorders[$cat[0]] = $id;
  598. }
  599. foreach ($cids as $id => $name) {
  600. $this->_data[$id]['name'] = $name;
  601. $this->_data[$id]['order'] = $corders[$id];
  602. if (!is_null($this->_sortHash)) {
  603. $this->_data[$id]['sorter'][$this->_sortHash] = $sorders[$id];
  604. }
  605. if (!empty($cparents[$id])) {
  606. $parents = explode(':', substr($cparents[$id], 1));
  607. $par = $parents[count($parents) - 1];
  608. $this->_data[$id]['parent'] = $par;
  609. if (!empty($this->_nameMap[$par])) {
  610. // If we've already loaded the direct parent of
  611. // this object, use that to find the full name.
  612. $this->_nameMap[$id] = $this->_nameMap[$par] . ':' . $name;
  613. } else {
  614. // Otherwise, run through parents one by one to
  615. // build it up.
  616. $this->_nameMap[$id] = '';
  617. foreach ($parents as $parID) {
  618. if (!empty($cids[$parID])) {
  619. $this->_nameMap[$id] .= ':' . $cids[$parID];
  620. }
  621. }
  622. $this->_nameMap[$id] = substr($this->_nameMap[$id], 1) . ':' . $name;
  623. }
  624. } else {
  625. $this->_data[$id]['parent'] = DATATREE_ROOT;
  626. $this->_nameMap[$id] = $name;
  627. }
  628. }
  629. return true;
  630. }
  631. /**
  632. * Extract one level of data for a parent leaf, sorted first by
  633. * their order and then by name. This function is a way to get a
  634. * collection of $leaf's children.
  635. *
  636. * @param string $leaf Name of the parent from which to start.
  637. *
  638. * @return array TODO
  639. */
  640. function _extractOneLevel($leaf = DATATREE_ROOT)
  641. {
  642. $out = array();
  643. foreach ($this->_data as $id => $vals) {
  644. if ($vals['parent'] == $leaf) {
  645. $out[$id] = $vals;
  646. }
  647. }
  648. uasort($out, array($this, (is_null($this->_sortHash)) ? '_cmp' : '_cmpSorted'));
  649. return $out;
  650. }
  651. /**
  652. * Extract all levels of data, starting from a given parent
  653. * leaf in the datatree.
  654. *
  655. * @access private
  656. *
  657. * @note If nothing is returned that means there is no child, but
  658. * don't forget to add the parent if any subsequent operations are
  659. * required!
  660. *
  661. * @param array $out This is an iterating function, so $out is
  662. * passed by reference to contain the result.
  663. * @param string $parent The name of the parent from which to begin.
  664. * @param integer $maxdepth Max of levels of depth to check.
  665. *
  666. * @return TODO
  667. */
  668. function _extractAllLevelTree(&$out, $parent = DATATREE_ROOT,
  669. $maxdepth = -1)
  670. {
  671. if ($maxdepth == 0) {
  672. return false;
  673. }
  674. $out[$parent] = true;
  675. $k = $this->_extractOneLevel($parent);
  676. foreach (array_keys($k) as $object) {
  677. if (!is_array($out[$parent])) {
  678. $out[$parent] = array();
  679. }
  680. $out[$parent][$object] = true;
  681. $this->_extractAllLevelTree($out[$parent], $object, $maxdepth - 1);
  682. }
  683. }
  684. /**
  685. * Extract all levels of data, starting from any parent in
  686. * the tree.
  687. *
  688. * Returned array format: array(parent => array(child => true))
  689. *
  690. * @access private
  691. *
  692. * @param array $out This is an iterating function, so $out is
  693. * passed by reference to contain the result.
  694. * @param string $parent The name of the parent from which to begin.
  695. * @param integer $maxdepth Max number of levels of depth to check.
  696. *
  697. * @return TODO
  698. */
  699. function _extractAllLevelList(&$out, $parent = DATATREE_ROOT,
  700. $maxdepth = -1)
  701. {
  702. if ($maxdepth == 0) {
  703. return false;
  704. }
  705. // This is redundant most of the time, so make sure we need to
  706. // do it.
  707. if (empty($out[$parent])) {
  708. $out[$parent] = $this->getName($parent);
  709. }
  710. foreach (array_keys($this->_extractOneLevel($parent)) as $object) {
  711. $out[$object] = $this->getName($object);
  712. $this->_extractAllLevelList($out, $object, $maxdepth - 1);
  713. }
  714. }
  715. /**
  716. * Returns a child's direct parent ID.
  717. *
  718. * @param mixed $child Either the object, an array containing the
  719. * path elements, or the object name for which
  720. * to look up the parent's ID.
  721. *
  722. * @return mixed The unique ID of the parent or PEAR_Error on error.
  723. */
  724. function getParent($child)
  725. {
  726. if (is_a($child, 'DataTreeObject')) {
  727. $child = $child->getName();
  728. }
  729. $id = $this->getId($child);
  730. if (is_a($id, 'PEAR_Error')) {
  731. return $id;
  732. }
  733. return $this->getParentById($id);
  734. }
  735. /**
  736. * Get a $child's direct parent ID.
  737. *
  738. * @param integer $childId Get the parent of this object.
  739. *
  740. * @return mixed The unique ID of the parent or PEAR_Error on error.
  741. */
  742. function getParentById($childId)
  743. {
  744. $this->_loadById($childId);
  745. return isset($this->_data[$childId]) ?
  746. $this->_data[$childId]['parent'] :
  747. PEAR::raiseError($childId . ' not found');
  748. }
  749. /**
  750. * Get a list of parents all the way up to the root object for
  751. * $child.
  752. *
  753. * @param mixed $child The name of the child
  754. * @param boolean $getids If true, return parent IDs; otherwise, return
  755. * names.
  756. *
  757. * @return mixed [child] [parent] in a tree format or PEAR_Error.
  758. */
  759. function getParents($child, $getids = false)
  760. {
  761. $pid = $this->getParent($child);
  762. if (is_a($pid, 'PEAR_Error')) {
  763. return PEAR::raiseError('Parents not found: ' . $pid->getMessage());
  764. }
  765. $pname = $this->getName($pid);
  766. $parents = ($getids) ? array($pid => true) : array($pname => true);
  767. if ($pid != DATATREE_ROOT) {
  768. if ($getids) {
  769. $parents[$pid] = $this->getParents($pname, $getids);
  770. } else {
  771. $parents[$pname] = $this->getParents($pname, $getids);
  772. }
  773. }
  774. return $parents;
  775. }
  776. /**
  777. * Get a list of parents all the way up to the root object for
  778. * $child.
  779. *
  780. * @param integer $childId The id of the child.
  781. * @param array $parents The array, as we build it up.
  782. *
  783. * @return array A flat list of all of the parents of $child,
  784. * hashed in $id => $name format.
  785. */
  786. function getParentList($childId, $parents = array())
  787. {
  788. $pid = $this->getParentById($childId);
  789. if (is_a($pid, 'PEAR_Error')) {
  790. return PEAR::raiseError('Parents not found: ' . $pid->getMessage());
  791. }
  792. if ($pid != DATATREE_ROOT) {
  793. $parents[$pid] = $this->getName($pid);
  794. $parents = $this->getParentList($pid, $parents);
  795. }
  796. return $parents;
  797. }
  798. /**
  799. * Get a parent ID string (id:cid format) for the specified object.
  800. *
  801. * @param mixed $object The object to return a parent string for.
  802. *
  803. * @return string|PEAR_Error The ID "path" to the parent object or
  804. * PEAR_Error on failure.
  805. */
  806. function getParentIdString($object)
  807. {
  808. $ptree = $this->getParents($object, true);
  809. if (is_a($ptree, 'PEAR_Error')) {
  810. return $ptree;
  811. }
  812. $pids = '';
  813. while ((list($id, $parent) = each($ptree)) && is_array($parent)) {
  814. $pids = ':' . $id . $pids;
  815. $ptree = $parent;
  816. }
  817. return $pids;
  818. }
  819. /**
  820. * Get the number of children an object has, only counting immediate
  821. * children, not grandchildren, etc.
  822. *
  823. * @param mixed $parent Either the object or the name for which to count
  824. * the children, defaults to the root
  825. * (DATATREE_ROOT).
  826. *
  827. * @return integer
  828. */
  829. function getNumberOfChildren($parent = DATATREE_ROOT)
  830. {
  831. if (is_a($parent, 'DataTreeObject')) {
  832. $parent = $parent->getName();
  833. }
  834. $this->_load($parent);
  835. $out = $this->_extractOneLevel($this->getId($parent));
  836. return is_array($out) ? count($out) : 0;
  837. }
  838. /**
  839. * Check if an object exists or not. The root element DATATREE_ROOT always
  840. * exists.
  841. *
  842. * @param mixed $object The name of the object.
  843. *
  844. * @return boolean True if the object exists, false otherwise.
  845. */
  846. function exists($object)
  847. {
  848. if (empty($object)) {
  849. return false;
  850. }
  851. if (is_a($object, 'DataTreeObject')) {
  852. $object = $object->getName();
  853. } elseif (is_array($object)) {
  854. $object = implode(':', $object);
  855. }
  856. if ($object == DATATREE_ROOT) {
  857. return true;
  858. }
  859. if (array_search($object, $this->_nameMap) !== false) {
  860. return true;
  861. }
  862. // Consult the backend directly.
  863. return $this->_exists($object);
  864. }
  865. /**
  866. * Get the name of an object from its id.
  867. *
  868. * @param integer $id The id for which to look up the name.
  869. *
  870. * @return string TODO
  871. */
  872. function getName($id)
  873. {
  874. /* If no id or if id is a PEAR error, return null. */
  875. if (empty($id) || is_a($id, 'PEAR_Error')) {
  876. return null;
  877. }
  878. /* If checking name of root, return DATATREE_ROOT. */
  879. if ($id == DATATREE_ROOT) {
  880. return DATATREE_ROOT;
  881. }
  882. /* If found in the name map, return the name. */
  883. if (isset($this->_nameMap[$id])) {
  884. return $this->_nameMap[$id];
  885. }
  886. /* Not found in name map, consult the backend. */
  887. return $this->_getName($id);
  888. }
  889. /**
  890. * Get the id of an object from its name.
  891. *
  892. * @param mixed $name Either the object, an array containing the
  893. * path elements, or the object name for which
  894. * to look up the id.
  895. *
  896. * @return string
  897. */
  898. function getId($name)
  899. {
  900. /* Check if $name is not a string. */
  901. if (is_a($name, 'DataTreeObject')) {
  902. /* DataTreeObject, get the string name. */
  903. $name = $name->getName();
  904. } elseif (is_array($name)) {
  905. /* Path array, implode to get the string name. */
  906. $name = implode(':', $name);
  907. }
  908. /* If checking id of root, return DATATREE_ROOT. */
  909. if ($name == DATATREE_ROOT) {
  910. return DATATREE_ROOT;
  911. }
  912. /* Flip the name map to look up the id using the name as key. */
  913. if (($id = array_search($name, $this->_nameMap)) !== false) {
  914. return $id;
  915. }
  916. /* Not found in name map, consult the backend. */
  917. $id = $this->_getId($name);
  918. if (is_null($id)) {
  919. return PEAR::raiseError($name . ' does not exist');
  920. }
  921. return $id;
  922. }
  923. /**
  924. * Get the order position of an object.
  925. *
  926. * @param mixed $child Either the object or the name.
  927. *
  928. * @return mixed The object's order position or a PEAR error on failure.
  929. */
  930. function getOrder($child)
  931. {
  932. if (is_a($child, 'DataTreeObject')) {
  933. $child = $child->getName();
  934. }
  935. $id = $this->getId($child);
  936. if (is_a($id, 'PEAR_Error')) {
  937. return $id;
  938. }
  939. $this->_loadById($id);
  940. return isset($this->_data[$id]['order']) ?
  941. $this->_data[$id]['order'] :
  942. null;
  943. }
  944. /**
  945. * Replace all occurences of ':' in an object name with '.'.
  946. *
  947. * @param string $name The name of the object.
  948. *
  949. * @return string The encoded name.
  950. */
  951. function encodeName($name)
  952. {
  953. return str_replace(':', '.', $name);
  954. }
  955. /**
  956. * Get the short name of an object, returns only the last portion of the
  957. * full name. For display purposes only.
  958. *
  959. * @static
  960. *
  961. * @param string $name The name of the object.
  962. *
  963. * @return string The object's short name.
  964. */
  965. function getShortName($name)
  966. {
  967. /* If there are several components to the name, explode and get the
  968. * last one, otherwise just return the name. */
  969. if (strpos($name, ':') !== false) {
  970. $name = explode(':', $name);
  971. $name = array_pop($name);
  972. }
  973. return $name;
  974. }
  975. /**
  976. * Returns a tree sorted by the specified attribute name and/or key.
  977. *
  978. * @abstract
  979. *
  980. * @since Horde 3.1
  981. *
  982. * @param string $root Which portion of the tree to sort.
  983. * Defaults to all of it.
  984. * @param boolean $loadTree Sort the tree starting at $root, or just the
  985. * requested level and direct parents?
  986. * Defaults to single level.
  987. * @param string $sortby_name Attribute name to use for sorting.
  988. * @param string $sortby_key Attribute key to use for sorting.
  989. * @param integer $direction Sort direction:
  990. * 0 - ascending
  991. * 1 - descending
  992. *
  993. * @return array TODO
  994. */
  995. function getSortedTree($root, $loadTree = false, $sortby_name = null,
  996. $sortby_key = null, $direction = 0)
  997. {
  998. return PEAR::raiseError('not supported');
  999. }
  1000. /**
  1001. * Adds an object.
  1002. *
  1003. * @abstract
  1004. *
  1005. * @param mixed $object The object to add (string or
  1006. * DataTreeObject).
  1007. * @param boolean $id_as_name True or false to indicate if object ID is to
  1008. * be used as object name. Used in situations
  1009. * where there is no available unique input for
  1010. * object name.
  1011. *
  1012. * @return TODO
  1013. */
  1014. function add($object, $id_as_name = false)
  1015. {
  1016. return PEAR::raiseError('not supported');
  1017. }
  1018. /**
  1019. * Add an object.
  1020. *
  1021. * @private
  1022. *
  1023. * @param string $name The short object name.
  1024. * @param integer $id The new object's unique ID.
  1025. * @param integer $pid The unique ID of the object's parent.
  1026. * @param integer $order The ordering data for the object.
  1027. *
  1028. * @access protected
  1029. *
  1030. * @return TODO
  1031. */
  1032. function _add($name, $id, $pid, $order = '')
  1033. {
  1034. $this->_data[$id] = array('name' => $name,
  1035. 'parent' => $pid,
  1036. 'order' => $order);
  1037. $this->_nameMap[$id] = $name;
  1038. /* Shift along the order positions. */
  1039. $this->_reorder($pid, $order, $id);
  1040. return true;
  1041. }
  1042. /**
  1043. * Retrieve data for an object from the horde_datatree_attributes
  1044. * table.
  1045. *
  1046. * @abstract
  1047. *
  1048. * @param integer | array $cid The object id to fetch,
  1049. * or an array of object ids.
  1050. *
  1051. * @return array A hash of attributes, or a multi-level hash
  1052. * of object ids => their attributes.
  1053. */
  1054. function getAttributes($cid)
  1055. {
  1056. return PEAR::raiseError('not supported');
  1057. }
  1058. /**
  1059. * Returns the number of objects matching a set of attribute criteria.
  1060. *
  1061. * @abstract
  1062. *
  1063. * @see buildAttributeQuery()
  1064. *
  1065. * @param array $criteria The array of criteria.
  1066. * @param string $parent The parent node to start searching from.
  1067. * @param boolean $allLevels Return all levels, or just the direct
  1068. * children of $parent? Defaults to all levels.
  1069. * @param string $restrict Only return attributes with the same
  1070. * attribute_name or attribute_id.
  1071. *
  1072. * @return TODO
  1073. */
  1074. function countByAttributes($criteria, $parent = DATATREE_ROOT,
  1075. $allLevels = true, $restrict = 'name')
  1076. {
  1077. return PEAR::raiseError('not supported');
  1078. }
  1079. /**
  1080. * Returns a set of object ids based on a set of attribute criteria.
  1081. *
  1082. * @abstract
  1083. *
  1084. * @see buildAttributeQuery()
  1085. *
  1086. * @param array $criteria The array of criteria.
  1087. * @param string $parent The parent node to start searching from.
  1088. * @param boolean $allLevels Return all levels, or just the direct
  1089. * children of $parent? Defaults to all levels.
  1090. * @param string $restrict Only return attributes with the same
  1091. * attribute_name or attribute_id.
  1092. * @param integer $from The object to start to fetching
  1093. * @param integer $count The number of objects to fetch
  1094. * @param string $sortby_name Attribute name to use for sorting.
  1095. * @param string $sortby_key Attribute key to use for sorting.
  1096. * @param integer $direction Sort direction:
  1097. * 0 - ascending
  1098. * 1 - descending
  1099. *
  1100. * @return TODO
  1101. */
  1102. function getByAttributes($criteria, $parent = DATATREE_ROOT,
  1103. $allLevels = true, $restrict = 'name', $from = 0,
  1104. $count = 0, $sortby_name = null,
  1105. $sortby_key = null, $direction = 0)
  1106. {
  1107. return PEAR::raiseError('not supported');
  1108. }
  1109. /**
  1110. * Sorts IDs by attribute values. IDs without attributes will be added to
  1111. * the end of the sorted list.
  1112. *
  1113. * @abstract
  1114. *
  1115. * @param array $unordered_ids Array of ids to sort.
  1116. * @param array $sortby_name Attribute name to use for sorting.
  1117. * @param array $sortby_key Attribute key to use for sorting.
  1118. * @param array $direction Sort direction:
  1119. * 0 - ascending
  1120. * 1 - descending
  1121. *
  1122. * @return array Sorted ids.
  1123. */
  1124. function sortByAttributes($unordered_ids, $sortby_name = null,
  1125. $sortby_key = null, $direction = 0)
  1126. {
  1127. return PEAR::raiseError('not supported');
  1128. }
  1129. /**
  1130. * Update the data in an object. Does not change the object's
  1131. * parent or name, just serialized data or attributes.
  1132. *
  1133. * @abstract
  1134. *
  1135. * @param DataTree $object A DataTree object.
  1136. *
  1137. * @return TODO
  1138. */
  1139. function updateData($object)
  1140. {
  1141. return PEAR::raiseError('not supported');
  1142. }
  1143. /**
  1144. * Sort two objects by their order field, and if that is the same,
  1145. * alphabetically (case insensitive) by name.
  1146. *
  1147. * You never call this function; it's used in uasort() calls. Do
  1148. * NOT use usort(); you'll lose key => value associations.
  1149. *
  1150. * @private
  1151. *
  1152. * @param array $a The first object
  1153. * @param array $b The second object
  1154. *
  1155. * @return integer 1 if $a should be first,
  1156. * -1 if $b should be first,
  1157. * 0 if they are entirely equal.
  1158. */
  1159. function _cmp($a, $b)
  1160. {
  1161. if ($a['order'] > $b['order']) {
  1162. return 1;
  1163. } elseif ($a['order'] < $b['order']) {
  1164. return -1;
  1165. } else {
  1166. return strcasecmp($a['name'], $b['name']);
  1167. }
  1168. }
  1169. /**
  1170. * Sorts two objects by their sorter hash field.
  1171. *
  1172. * You never call this function; it's used in uasort() calls. Do NOT use
  1173. * usort(); you'll lose key => value associations.
  1174. *
  1175. * @since Horde 3.1
  1176. *
  1177. * @private
  1178. *
  1179. * @param array $a The first object
  1180. * @param array $b The second object
  1181. *
  1182. * @return integer 1 if $a should be first,
  1183. * -1 if $b should be first,
  1184. * 0 if they are entirely equal.
  1185. */
  1186. function _cmpSorted($a, $b)
  1187. {
  1188. return intval($a['sorter'][$this->_sortHash] < $b['sorter'][$this->_sortHash]);
  1189. }
  1190. /**
  1191. * Attempts to return a concrete DataTree instance based on $driver.
  1192. *
  1193. * @param mixed $driver The type of concrete DataTree subclass to return.
  1194. * This is based on the storage driver ($driver). The
  1195. * code is dynamically included. If $driver is an array,
  1196. * then we will look in $driver[0]/lib/DataTree/ for
  1197. * the subclass implementation named $driver[1].php.
  1198. * @param array $params A hash containing any additional configuration or
  1199. * connection parameters a subclass might need.
  1200. * Here, we need 'group' = a string that defines
  1201. * top-level groups of objects.
  1202. *
  1203. * @return DataTree The newly created concrete DataTree instance, or false
  1204. * on an error.
  1205. */
  1206. function &factory($driver, $params = null)
  1207. {
  1208. $driver = basename($driver);
  1209. if (is_null($params)) {
  1210. $params = Horde::getDriverConfig('datatree', $driver);
  1211. }
  1212. if (empty($driver)) {
  1213. $driver = 'null';
  1214. }
  1215. include_once 'Horde/DataTree/' . $driver . '.php';
  1216. $class = 'DataTree_' . $driver;
  1217. if (class_exists($class)) {
  1218. $dt = new $class($params);
  1219. $result = $dt->_init();
  1220. if (is_a($result, 'PEAR_Error')) {
  1221. include_once 'Horde/DataTree/null.php';
  1222. $dt = new DataTree_null($params);
  1223. }
  1224. } else {
  1225. $dt = PEAR::raiseError('Class definition of ' . $class . ' not found.');
  1226. }
  1227. return $dt;
  1228. }
  1229. /**
  1230. * Attempts to return a reference to a concrete DataTree instance based on
  1231. * $driver.
  1232. *
  1233. * It will only create a new instance if no DataTree instance with the same
  1234. * parameters currently exists.
  1235. *
  1236. * This should be used if multiple DataTree sources (and, thus, multiple
  1237. * DataTree instances) are required.
  1238. *
  1239. * This method must be invoked as: $var = &DataTree::singleton();
  1240. *
  1241. * @param mixed $driver Type of concrete DataTree subclass to return,
  1242. * based on storage driver ($driver). The code is
  1243. * dynamically included. If $driver is an array, then
  1244. * look in $driver[0]/lib/DataTree/ for subclass
  1245. * implementation named $driver[1].php.
  1246. * @param array $params A hash containing any additional configuration or
  1247. * connection parameters a subclass might need.
  1248. *
  1249. * @return DataTree The concrete DataTree reference, or false on an error.
  1250. */
  1251. function &singleton($driver, $params = null)
  1252. {
  1253. static $instances = array();
  1254. if (is_null($params)) {
  1255. $params = Horde::getDriverConfig('datatree', $driver);
  1256. }
  1257. $signature = serialize(array($driver, $params));
  1258. if (!isset($instances[$signature])) {
  1259. $instances[$signature] = &DataTree::factory($driver, $params);
  1260. }
  1261. return $instances[$signature];
  1262. }
  1263. }
  1264. /**
  1265. * Class that can be extended to save arbitrary information as part of a stored
  1266. * object.
  1267. *
  1268. * @author Stephane Huther <shuther1@free.fr>
  1269. * @author Chuck Hagenbuch <chuck@horde.org>
  1270. * @since Horde 2.1
  1271. * @package Horde_DataTree
  1272. */
  1273. class DataTreeObject {
  1274. /**
  1275. * This object's DataTree instance.
  1276. *
  1277. * @var DataTree
  1278. */
  1279. var $datatree;
  1280. /**
  1281. * Key-value hash that will be serialized.
  1282. *
  1283. * @see getData()
  1284. * @var array
  1285. */
  1286. var $data = array();
  1287. /**
  1288. * The unique name of this object.
  1289. * These names have the same requirements as other object names - they must
  1290. * be unique, etc.
  1291. *
  1292. * @var string
  1293. */
  1294. var $name;
  1295. /**
  1296. * If this object has ordering data, store it here.
  1297. *
  1298. * @var integer
  1299. */
  1300. var $order = null;
  1301. /**
  1302. * DataTreeObject constructor.
  1303. * Just sets the $name parameter.
  1304. *
  1305. * @param string $name The object name.
  1306. */
  1307. function DataTreeObject($name)
  1308. {
  1309. $this->setName($name);
  1310. }
  1311. /**
  1312. * Sets the {@link DataTree} instance used to retrieve this object.
  1313. *
  1314. * @param DataTree $datatree A {@link DataTree} instance.
  1315. */
  1316. function setDataTree(&$datatree)
  1317. {
  1318. $this->datatree = &$datatree;
  1319. }
  1320. /**
  1321. * Gets the name of this object.
  1322. *
  1323. * @return string The object name.
  1324. */
  1325. function getName()
  1326. {
  1327. return $this->name;
  1328. }
  1329. /**
  1330. * Sets the name of this object.
  1331. *
  1332. * NOTE: Use with caution. This may throw out of sync the cached datatree
  1333. * tables if not used properly.
  1334. *
  1335. * @param string $name The name to set this object's name to.
  1336. */
  1337. function setName($name)
  1338. {
  1339. $this->name = $name;
  1340. }
  1341. /**
  1342. * Gets the short name of this object.
  1343. * For display purposes only.
  1344. *
  1345. * @return string The object's short name.
  1346. */
  1347. function getShortName()
  1348. {
  1349. return DataTree::getShortName($this->name);
  1350. }
  1351. /**
  1352. * Gets the ID of this object.
  1353. *
  1354. * @return string The object's ID.
  1355. */
  1356. function getId()
  1357. {
  1358. return $this->datatree->getId($this);
  1359. }
  1360. /**
  1361. * Gets the data array.
  1362. *
  1363. * @return array The internal data array.
  1364. */
  1365. function getData()
  1366. {
  1367. return $this->data;
  1368. }
  1369. /**
  1370. * Sets the data array.
  1371. *
  1372. * @param array The data array to store internally.
  1373. */
  1374. function setData($data)
  1375. {
  1376. $this->data = $data;
  1377. }
  1378. /**
  1379. * Sets the order of this object in its object collection.
  1380. *
  1381. * @param integer $order
  1382. */
  1383. function setOrder($order)
  1384. {
  1385. $this->order = $order;
  1386. }
  1387. /**
  1388. * Returns this object's parent.
  1389. *
  1390. * @param string $class Subclass of DataTreeObject to use. Defaults to
  1391. * DataTreeObject. Null forces the driver to look
  1392. * into the attributes table to determine the
  1393. * subclass to use. If none is found it uses
  1394. * DataTreeObject.
  1395. *
  1396. * @return DataTreeObject This object's parent
  1397. */
  1398. function &getParent($class = 'DataTreeObject')
  1399. {
  1400. $id = $this->datatree->getParent($this);
  1401. if (is_a($id, 'PEAR_Error')) {
  1402. return $id;
  1403. }
  1404. return $this->datatree->getObjectById($id, $class);
  1405. }
  1406. /**
  1407. * Returns a child of this object.
  1408. *
  1409. * @param string $name The child's name.
  1410. * @param boolean $autocreate If true and no child with the given name
  1411. * exists, one gets created.
  1412. */
  1413. function &getChild($name, $autocreate = true)
  1414. {
  1415. $name = $this->getShortName() . ':' . $name;
  1416. /* If the child shouldn't get created, we don't check for its
  1417. * existance to return the "not found" error of
  1418. * getObject(). */
  1419. if (!$autocreate || $this->datatree->exists($name)) {
  1420. $child = &$this->datatree->getObject($name);
  1421. } else {
  1422. $child = new DataTreeObject($name);
  1423. $child->setDataTree($this->datatree);
  1424. $this->datatree->add($child);
  1425. }
  1426. return $child;
  1427. }
  1428. /**
  1429. * Saves any changes to this object to the backend permanently. New objects
  1430. * are added instead.
  1431. *
  1432. * @return boolean|PEAR_Error PE…

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