Overview

Packages

  • DataTree

Classes

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