Overview

Packages

  • None
  • Trean

Classes

  • DataTreeObject_Folder
  • SearchForm
  • Trean
  • Trean_Bookmark
  • Trean_Bookmarks
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
   4:  *
   5:  * See the enclosed file LICENSE for license information (BSD). If you did not
   6:  * did not receive this file, see http://www.horde.org/licenses/bsdl.php.
   7:  *
   8:  * @author  Ben Chavet <ben@horde.org>
   9:  * @package Trean
  10:  */
  11: class Trean_Bookmarks
  12: {
  13:     /**
  14:      * Pointer to a Horde_DataTree instance to manage/store shares
  15:      *
  16:      * @var Horde_DataTree
  17:      */
  18:     var $_datatree;
  19: 
  20:     /**
  21:      * A cache of all shares that have been retrieved, so we don't hit the
  22:      * backend again and again for them.
  23:      *
  24:      * @var array
  25:      */
  26:     var $_cache = array();
  27: 
  28:     /**
  29:      * Id-name-map of already cached share objects.
  30:      *
  31:      * @var array
  32:      */
  33:     var $_shareMap = array();
  34: 
  35:     /**
  36:      * Cache used for listFolders/getFolders().
  37:      *
  38:      * @var array
  39:      */
  40:     var $_listcache = array();
  41: 
  42:     /**
  43:      * Caches the number of share matching certain criteria.
  44:      *
  45:      * @see countShares()
  46:      * @var array
  47:      */
  48:     var $_counts = array();
  49: 
  50:     /**
  51:      * A list of objects that we're currently sorting, for reference during the
  52:      * sorting algorithm.
  53:      *
  54:      * @var array
  55:      */
  56:     var $_sortList;
  57: 
  58:     /**
  59:      * Constructor.
  60:      */
  61:     function Trean_Bookmarks()
  62:     {
  63:         global $conf, $registry;
  64: 
  65:         if (empty($conf['datatree']['driver'])) {
  66:             throw new Horde_Exception('You must configure a Horde_DataTree backend to use Trean.');
  67:         }
  68: 
  69:         $driver = $conf['datatree']['driver'];
  70:         $this->_datatree = Horde_DataTree::singleton(
  71:             $driver,
  72:             array_merge(Horde::getDriverConfig('datatree', $driver), array('group' => 'horde.shares.trean'))
  73:         );
  74: 
  75:         try {
  76:             Horde::callHook('share_init', array($this, 'trean'));
  77:         } catch (Horde_Exception_HookNotSet $e) {}
  78:     }
  79: 
  80:     /**
  81:      * Search all folders that the user has permissions to.
  82:      */
  83:     function searchBookmarks($search_criteria, $search_operator = 'OR',
  84:                              $sortby = 'title', $sortdir = 0, $from = 0, $count = 0)
  85:     {
  86:         // Validate the search operator (AND or OR).
  87:         switch ($search_operator) {
  88:         case 'AND':
  89:         case 'OR':
  90:             break;
  91: 
  92:         default:
  93:             $search_operator = 'AND';
  94:         }
  95: 
  96:         // Get the folder ids to search.
  97:         $folderIds = $this->listFolders($GLOBALS['registry']->getAuth(), Horde_Perms::READ);
  98: 
  99:         $clauses = array();
 100:         $values = array();
 101:         foreach ($search_criteria as $criterion) {
 102:             $clause = $GLOBALS['trean_db']->buildClause(
 103:                 'bookmark_' . $criterion[0],
 104:                 $criterion[1],
 105:                 Horde_String::convertCharset($criterion[2],
 106:                                              $GLOBALS['conf']['sql']['charset'],
 107:                                              'UTF-8'),
 108:                 true,
 109:                 isset($criterion[3]) ? $criterion[3] : array());
 110:             $clauses[] = $clause[0];
 111:             $values = array_merge($values, $clause[1]);
 112:         }
 113: 
 114:         $GLOBALS['trean_db']->setLimit($count, $from);
 115: 
 116:         $sql = 'SELECT bookmark_id, folder_id, bookmark_url, bookmark_title, bookmark_description,
 117:                        bookmark_clicks, bookmark_rating
 118:                 FROM trean_bookmarks
 119:                 WHERE folder_id IN (' . implode(',', $folderIds) . ')
 120:                       AND (' . implode(' ' . $search_operator . ' ', $clauses) . ')
 121:                 ORDER BY bookmark_' . $sortby . ($sortdir ? ' DESC' : '');
 122:         $query = $GLOBALS['trean_db']->prepare($sql);
 123:         if (is_a($query, 'PEAR_Error')) {
 124:             Horde::logMessage($query, __FILE__, __LINE__, PEAR_LOG_ERR);
 125:             return array();
 126:         }
 127: 
 128:         $result = $query->execute($values);
 129:         if (is_a($result, 'PEAR_Error')) {
 130:             Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
 131:             return array();
 132:         }
 133: 
 134:         return Trean_Bookmarks::resultSet($result->fetchAll(MDB2_FETCHMODE_ASSOC));
 135:     }
 136: 
 137:     /**
 138:      * Sort bookmarks from all folders the user can access by a
 139:      * specific criteria.
 140:      */
 141:     function sortBookmarks($sortby = 'title', $sortdir = 0, $from = 0, $count = 10)
 142:     {
 143:         // List the folders to search.
 144:         $folderIds = $this->listFolders($GLOBALS['registry']->getAuth(), Horde_Perms::READ);
 145: 
 146:         // Make sure $sortby is a valid field.
 147:         switch ($sortby) {
 148:         case 'rating':
 149:         case 'clicks':
 150:             break;
 151: 
 152:         default:
 153:             $sortby = 'title';
 154:         }
 155: 
 156:         if ($count > 100) {
 157:             return PEAR::raiseError('Max of 100 results');
 158:         }
 159: 
 160:         $GLOBALS['trean_db']->setLimit($count, $from);
 161:         return Trean_Bookmarks::resultSet($GLOBALS['trean_db']->queryAll('
 162:             SELECT bookmark_id, folder_id, bookmark_url, bookmark_title, bookmark_description,
 163:                    bookmark_clicks, bookmark_rating
 164:             FROM trean_bookmarks
 165:             WHERE folder_id IN (' . implode(',', $folderIds) . ')
 166:             ORDER BY bookmark_' . $sortby . ($sortdir ? ' DESC' : ''), null, MDB2_FETCHMODE_ASSOC));
 167:     }
 168: 
 169:     /**
 170:      * Returns the number of bookmarks in all folders.
 171:      *
 172:      * @return integer  The number of all bookmarks.
 173:      */
 174:     function countBookmarks()
 175:     {
 176:         $folderIds = $this->listFolders($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT);
 177:         $sql = 'SELECT COUNT(*) FROM trean_bookmarks WHERE folder_id IN (' . implode(',', $folderIds) . ')';
 178:         return $GLOBALS['trean_db']->queryOne($sql);
 179:     }
 180: 
 181:     /**
 182:      * Return counts on grouping bookmarks by a specific property.
 183:      */
 184:     function groupBookmarks($groupby)
 185:     {
 186:         $folderIds = $this->listFolders($GLOBALS['registry']->getAuth(), Horde_Perms::READ);
 187: 
 188:         switch ($groupby) {
 189:         case 'status':
 190:             $sql = 'SELECT bookmark_http_status AS status, COUNT(*) AS count
 191:                     FROM trean_bookmarks
 192:                     WHERE folder_id IN (' . implode(',', $folderIds) . ')
 193:                     GROUP BY bookmark_http_status';
 194:             break;
 195: 
 196:         default:
 197:             return array();
 198:         }
 199: 
 200:         return $GLOBALS['trean_db']->queryAll($sql, null, MDB2_FETCHMODE_ASSOC);
 201:     }
 202: 
 203:     /**
 204:      * Returns an array of DataTreeObject_Folder objects corresponding to the
 205:      * given set of unique IDs, with the details retrieved appropriately.
 206:      *
 207:      * @param array $cids  The array of ids to retrieve.
 208:      *
 209:      * @return array  The requested shares.
 210:      */
 211:     function getShares($cids)
 212:     {
 213:         $all_shares = array();
 214:         $missing_ids = array();
 215:         foreach ($cids as $cid) {
 216:             if (isset($this->_shareMap[$cid])) {
 217:                 $all_shares[$this->_shareMap[$cid]] = $this->_cache[$this->_shareMap[$cid]];
 218:             } else {
 219:                 $missing_ids[] = $cid;
 220:             }
 221:         }
 222: 
 223:         if (count($missing_ids)) {
 224:             $shares = $this->_datatree->getObjects($missing_ids, 'DataTreeObject_Folder');
 225:             if (is_a($shares, 'PEAR_Error')) {
 226:                 return $shares;
 227:             }
 228: 
 229:             $keys = array_keys($shares);
 230:             foreach ($keys as $key) {
 231:                 if (is_a($shares[$key], 'PEAR_Error')) {
 232:                     return $shares[$key];
 233:                 }
 234: 
 235:                 $shares[$key]->setShareOb($this);
 236:                 $all_shares[$key] = $shares[$key];
 237:                 $this->_cache[$key] = $shares[$key];
 238:                 $this->_shareMap[$shares[$key]->getId()] = $key;
 239:             }
 240:         }
 241: 
 242:         return $all_shares;
 243:     }
 244: 
 245:     /**
 246:      * Checks if a share exists in the system.
 247:      *
 248:      * @param string $share  The share to check.
 249:      *
 250:      * @return boolean  True if the share exists, false otherwise.
 251:      */
 252:     function exists($share)
 253:     {
 254:         if (isset($this->_cache[$share])) {
 255:             return true;
 256:         }
 257: 
 258:         return $this->_datatree->exists($share);
 259:     }
 260: 
 261:     /**
 262:      * Returns the folder ids that $userid has access to.
 263:      *
 264:      * @param string  $userid       The userid of the user to check access for.
 265:      * @param integer $perm         The level of permissions required.
 266:      * @param string  $parent       The parent share to start searching at.
 267:      * @param boolean $allLevels    Return all levels, or just the direct
 268:      *                              children of $parent? Defaults to all
 269:      *                              levels.
 270:      *
 271:      * @return array The folder ids $userid has access to.
 272:      */
 273:     function listFolders($userid, $perm = Horde_Perms::SHOW, $parent = null, $allLevels = true)
 274:     {
 275:         if (is_null($parent)) {
 276:             $parent = DATATREE_ROOT;
 277:         }
 278: 
 279:         $key = serialize(array($userid, $perm, $parent, $allLevels));
 280:         if (empty($this->_listCache[$key])) {
 281:             $criteria = $this->_getShareCriteria($userid, $perm);
 282:             $sharelist = $this->_datatree->getByAttributes(
 283:                 $criteria, $parent, $allLevels, 'id', 0, 0, 'name'
 284:             );
 285:             if (is_a($sharelist, 'PEAR_Error')) {
 286:                 return $sharelist;
 287:             }
 288:             $this->_listCache[$key] = array_keys($sharelist);
 289:         }
 290: 
 291:         return $this->_listCache[$key];
 292:     }
 293: 
 294:     /**
 295:      * Returns an array of all folders that $userid has access to.
 296:      *
 297:      * @param string  $userid       The userid of the user to check access for.
 298:      * @param integer $perm         The level of permissions required.
 299:      * @param string  $parent       The parent share to start searching at.
 300:      * @param boolean $allLevels    Return all levels, or just the direct
 301:      *                              children of $parent? Defaults to all
 302:      *                              levels.
 303:      *
 304:      * @return array  The shares the user has access to.
 305:      */
 306:     function getFolders($userid, $perm = Horde_Perms::SHOW, $parent = null, $allLevels = true)
 307:     {
 308:         $folderIds = $this->listFolders($userid, $perm, $parent, $allLevels);
 309:         if (!count($folderIds) || is_a($folderIds, 'PEAR_Error')) {
 310:             return $folderIds;
 311:         }
 312: 
 313:         /* Make sure getShares() didn't return an error. */
 314:         $shares = $this->getShares($folderIds);
 315:         if (is_a($shares, 'PEAR_Error')) {
 316:             return $shares;
 317:         }
 318: 
 319:         $this->_sortList = $shares;
 320:         uasort($shares, array($this, '_sortShares'));
 321:         $this->_sortList = null;
 322: 
 323:         try {
 324:             return Horde::callHook('share_list', array($userid, $perm, null, $shares));
 325:         } catch (Horde_Exception_HookNotSet $e) {
 326:             return $shares;
 327:         }
 328:     }
 329: 
 330:     /**
 331:      * Returns a new folder object.
 332:      *
 333:      * @param string $name       The folder's internal name.
 334:      * @param array $properties  The folder's initial properties. If set, a
 335:      *                           'name' value is expected.
 336:      *
 337:      * @return DataTreeObject_Folder  A new folder object.
 338:      */
 339:     function newFolder($name, $properties = null)
 340:     {
 341:         if (empty($name)) {
 342:             $error = PEAR::raiseError(_("Folder names must be non-empty"));
 343:             return $error;
 344:         }
 345: 
 346:         $folder = new DataTreeObject_Folder($name);
 347:         $folder->setDataTree($this->_datatree);
 348:         $folder->setShareOb($this);
 349:         $folder->set('owner', $GLOBALS['registry']->getAuth());
 350:         $folder->set('name', isset($properties['name']) ? $properties['name'] : '');
 351:         return $folder;
 352:     }
 353: 
 354:     /**
 355:      * Returns a DataTreeObject_Folder object corresponding to the given unique
 356:      * ID, with the details retrieved appropriately.
 357:      *
 358:      * @param string $cid  The id of the folder to retrieve.
 359:      *
 360:      * @return DataTreeObject_Folder The requested folder.
 361:      */
 362:     function getFolder($cid)
 363:     {
 364:         if (isset($this->_shareMap[$cid])) {
 365:             $share = $this->_cache[$this->_shareMap[$cid]];
 366:         } else {
 367:             $share = $this->_datatree->getObjectById($cid, 'DataTreeObject_Folder');
 368:             if (!is_a($share, 'PEAR_Error')) {
 369:                 $share->setShareOb($this);
 370:                 $name = $share->getName();
 371:                 $this->_cache[$name] = $share;
 372:                 $this->_shareMap[$cid] = $name;
 373:             }
 374:         }
 375: 
 376:         return $share;
 377:     }
 378: 
 379:     /**
 380:      * Returns the bookmark corresponding to the given id.
 381:      *
 382:      * @param integer $id  The ID of the bookmark to retrieve.
 383:      *
 384:      * @return Trean_Bookmark  The bookmark object corresponding to the given name.
 385:      */
 386:     function getBookmark($id)
 387:     {
 388:         $bookmark = $GLOBALS['trean_db']->queryRow('
 389:             SELECT bookmark_id, folder_id, bookmark_url, bookmark_title, bookmark_description,
 390:                    bookmark_clicks, bookmark_rating
 391:             FROM trean_bookmarks
 392:             WHERE bookmark_id = ' . (int)$id, null, MDB2_FETCHMODE_ASSOC);
 393:         if (is_null($bookmark)) {
 394:             return PEAR::raiseError('not found');
 395:         } elseif (is_a($bookmark, 'PEAR_Error')) {
 396:             return $bookmark;
 397:         } else {
 398:             $bookmark = $this->resultSet(array($bookmark));
 399:             return array_pop($bookmark);
 400:         }
 401:     }
 402: 
 403:     /**
 404:      * Stores a new folder permanently.
 405:      *
 406:      * @param DataTreeObject_Folder $folder  The folder to add.
 407:      */
 408:     function addFolder($folder)
 409:     {
 410:         if (!is_a($folder, 'DataTreeObject_Folder')) {
 411:             return PEAR::raiseError('Folders must be DataTreeObject_Folder objects or extend that class.');
 412:         }
 413: 
 414:         /* Give the owner full access */
 415:         $perm = $GLOBALS['injector']->getInstance('Horde_Core_Perms')->newPermission($folder->getName());
 416:         $perm->addUserPermission($folder->get('owner'), Horde_Perms::SHOW, false);
 417:         $perm->addUserPermission($folder->get('owner'), Horde_Perms::READ, false);
 418:         $perm->addUserPermission($folder->get('owner'), Horde_Perms::EDIT, false);
 419:         $perm->addUserPermission($folder->get('owner'), Horde_Perms::DELETE, false);
 420: 
 421:         $folder->setPermission($perm, false);
 422: 
 423:         try {
 424:             $result = Horde::callHook('share_add', array($folder));
 425:         } catch (Horde_Exception_HookNotSet $e) {}
 426: 
 427:         $result = $this->_datatree->add($folder);
 428:         if (is_a($result, 'PEAR_Error')) {
 429:             return $result;
 430:         }
 431: 
 432:         /* Store new share in the caches. */
 433:         $id = $folder->getId();
 434:         $name = $folder->getName();
 435:         $this->_cache[$name] = $folder;
 436:         $this->_shareMap[$id] = $name;
 437: 
 438:         /* Reset caches that depend on unknown criteria. */
 439:         $this->_listCache = array();
 440:         $this->_counts = array();
 441: 
 442:         return $result;
 443:     }
 444: 
 445:     /**
 446:      * Removes a folder.
 447:      *
 448:      * @param DataTreeObject_Folder $folder  The folder to
 449:      *                                       remove.
 450:      * @param boolean $force                 Force the removal of
 451:      *                                       every child?
 452:      */
 453:     function removeFolder($folder, $force = false)
 454:     {
 455:         if (!is_a($folder, 'DataTreeObject_Folder')) {
 456:             return PEAR::raiseError('Folders must be DataTreeObject_Folder objects or extend that class.');
 457:         }
 458: 
 459:         try {
 460:             $result = Horde::callHook('share_remove', array($folder));
 461:         } catch (Horde_Exception_HookNotSet $e) {}
 462: 
 463:         return $this->_datatree->remove($folder, $force);
 464:     }
 465: 
 466:     /**
 467:      * Removes a Trean_Bookmark from the backend.
 468:      *
 469:      * @param Trean_Bookmark $bookmark  The bookmark to remove.
 470:      */
 471:     function removeBookmark($bookmark)
 472:     {
 473:         /* Make sure $bookmark is a Trean_Bookmark; if not, try
 474:          * loading it. */
 475:         if (!is_a($bookmark, 'Trean_Bookmark')) {
 476:             $b = $this->getBookmark($bookmark);
 477:             if (is_a($b, 'PEAR_Error')) {
 478:                 return $b;
 479:             }
 480:             $bookmark = $b;
 481:         }
 482: 
 483:         /* Check permissions. */
 484:         $folder = $this->getFolder($bookmark->folder);
 485:         if (!$folder->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE)) {
 486:             return PEAR::raiseError('permission denied');
 487:         }
 488: 
 489:         /* TODO: Decrement favicon refcount. */
 490: 
 491:         /* Delete from SQL. */
 492:         $GLOBALS['trean_db']->exec('DELETE FROM trean_bookmarks WHERE bookmark_id = ' . (int)$bookmark->id);
 493: 
 494:         return true;
 495:     }
 496: 
 497:     /**
 498:      * Returns the id of the folder $name.
 499:      *
 500:      * @param string $name  A folder name.
 501:      *
 502:      * @return integer  A folder id.
 503:      */
 504:     function getId($name)
 505:     {
 506:         return $this->_datatree->getId($name);
 507:     }
 508: 
 509:     /**
 510:      * Move $folder to be a child of $new_parent.
 511:      */
 512:     function move($folder, $new_parent)
 513:     {
 514:         if (!is_a($folder, 'DataTreeObject_Folder')) {
 515:             return PEAR::raiseError('Folders must be DataTreeObject_Folder objects or extend that class.');
 516:         }
 517:         if (!is_a($new_parent, 'DataTreeObject_Folder')) {
 518:             return PEAR::raiseError('Folders must be DataTreeObject_Folder objects or extend that class.');
 519:         }
 520:         return $this->_datatree->move($folder, $new_parent);
 521:     }
 522: 
 523:     /**
 524:      * Create Trean_Bookmark objects for each row in a SQL result.
 525:      * @static
 526:      */
 527:     function resultSet($bookmarks)
 528:     {
 529:         if (is_null($bookmarks)) {
 530:             return array();
 531:         } elseif (is_a($bookmarks, 'PEAR_Error')) {
 532:             return $bookmarks;
 533:         }
 534: 
 535:         $objects = array();
 536:         foreach ($bookmarks as $bookmark) {
 537:             foreach ($bookmark as $key => $value)
 538:             if (!empty($value) && !is_numeric($value)) {
 539:                 $cvBookmarks[$key] = Horde_String::convertCharset(
 540:                     $value, $GLOBALS['conf']['sql']['charset'], 'UTF-8');
 541:             } else {
 542:                 $cvBookmarks[$key] = $value;
 543:             }
 544:             $objects[] = new Trean_Bookmark($cvBookmarks);
 545:         }
 546:         return $objects;
 547:     }
 548: 
 549:     /**
 550:      * Utility function to be used with uasort() for sorting arrays of
 551:      * Trean_Bookmarks objects.
 552:      * Example:<code>
 553:      * uasort($list, array('Trean_Bookmarks', '_sortShares'));
 554:      * </code>
 555:      *
 556:      * @access private
 557:      */
 558:     function _sortShares($a, $b)
 559:     {
 560:         $aParts = explode(':', $a->getName());
 561:         $bParts = explode(':', $b->getName());
 562: 
 563:         $min = min(count($aParts), count($bParts));
 564:         $idA = '';
 565:         $idB = '';
 566:         for ($i = 0; $i < $min; $i++) {
 567:             if ($idA) {
 568:                 $idA .= ':';
 569:                 $idB .= ':';
 570:             }
 571:             $idA .= $aParts[$i];
 572:             $idB .= $bParts[$i];
 573: 
 574:             if ($idA != $idB) {
 575:                 $curA = isset($this->_sortList[$idA]) ? $this->_sortList[$idA]->get('name') : '';
 576:                 $curB = isset($this->_sortList[$idB]) ? $this->_sortList[$idB]->get('name') : '';
 577:                 return strnatcasecmp($curA, $curB);
 578:             }
 579:         }
 580: 
 581:         return count($aParts) > count($bParts);
 582:     }
 583: 
 584:     /**
 585:      * Returns an array of criteria for querying shares.
 586:      *
 587:      * @param string  $userid      The userid of the user to check access for.
 588:      * @param integer $perm        The level of permissions required.
 589:      *
 590:      * @return array  The criteria tree for fetching this user's shares.
 591:      */
 592:     function _getShareCriteria($userid, $perm = Horde_Perms::SHOW)
 593:     {
 594:         if (!empty($userid)) {
 595:             $criteria = array(
 596:                 'OR' => array(
 597:                     // (owner == $userid)
 598:                     array(
 599:                         'AND' => array(
 600:                             array('field' => 'name', 'op' => '=', 'test' => 'owner'),
 601:                             array('field' => 'value', 'op' => '=', 'test' => $userid))),
 602: 
 603:                     // (name == perm_users and key == $userid and val & $perm)
 604:                     array(
 605:                         'AND' => array(
 606:                             array('field' => 'name', 'op' => '=', 'test' => 'perm_users'),
 607:                             array('field' => 'key', 'op' => '=', 'test' => $userid),
 608:                             array('field' => 'value', 'op' => '&', 'test' => $perm))),
 609: 
 610:                     // (name == perm_creator and val & $perm)
 611:                     array(
 612:                         'AND' => array(
 613:                             array('field' => 'name', 'op' => '=', 'test' => 'perm_creator'),
 614:                             array('field' => 'value', 'op' => '&', 'test' => $perm))),
 615: 
 616:                     // (name == perm_default and val & $perm)
 617:                     array(
 618:                         'AND' => array(
 619:                             array('field' => 'name', 'op' => '=', 'test' => 'perm_default'),
 620:                             array('field' => 'value', 'op' => '&', 'test' => $perm)))));
 621: 
 622:             // If the user has any group memberships, check for those also.
 623:             $groups = $GLOBALS['injector']
 624:                 ->getInstance('Horde_Group')
 625:                 ->listGroups($userid);
 626:             if (is_array($groups) && count($groups)) {
 627:                 // (name == perm_groups and key in ($groups) and val & $perm)
 628:                 $criteria['OR'][] = array(
 629:                     'AND' => array(
 630:                         array('field' => 'name', 'op' => '=', 'test' => 'perm_groups'),
 631:                         array('field' => 'key', 'op' => 'IN', 'test' => array_keys($groups)),
 632:                         array('field' => 'value', 'op' => '&', 'test' => $perm)));
 633:             }
 634:         } else {
 635:             $criteria = array(
 636:                 'AND' => array(
 637:                      array('field' => 'name', 'op' => '=', 'test' => 'perm_guest'),
 638:                      array('field' => 'value', 'op' => '&', 'test' => $perm)));
 639:         }
 640: 
 641:         return $criteria;
 642:     }
 643: 
 644: }
 645: 
 646: /**
 647:  * Extension of the Horde_DataTreeObject class for storing bookmark folders.
 648:  *
 649:  * @author  Mike Cochrane <mike@graftonhall.co.nz>
 650:  * @package Trean
 651:  */
 652: class DataTreeObject_Folder extends Horde_DataTreeObject {
 653: 
 654:     /**
 655:      * The Trean_Bookmarks object which this share came from - needed
 656:      * for updating data in the backend to make changes stick, etc.
 657:      *
 658:      * @var Trean_Bookmarks
 659:      */
 660:     var $_shareOb;
 661: 
 662:     /**
 663:      * The DataTreeObject_Folder constructor. Just makes sure to call the parent
 664:      * constructor so that the share's name is set properly.
 665:      *
 666:      * @param string $id  The id of the share.
 667:      */
 668:     function DataTreeObject_Folder($id)
 669:     {
 670:         parent::__construct($id);
 671:         if (is_null($this->data)) {
 672:             $this->data = array();
 673:         }
 674:     }
 675: 
 676:     /**
 677:      * Returns the properties that need to be serialized.
 678:      *
 679:      * @return array  List of serializable properties.
 680:      */
 681:     function __sleep()
 682:     {
 683:         $properties = get_object_vars($this);
 684:         unset($properties['datatree'], $properties['_shareOb']);
 685:         $properties = array_keys($properties);
 686:         return $properties;
 687:     }
 688: 
 689:     /**
 690:      * Associates a Trean_Bookmarks object with this share.
 691:      *
 692:      * @param Trean_Bookmarks $shareOb The Trean_Bookmarks object.
 693:      */
 694:     function setShareOb($shareOb)
 695:     {
 696:         $this->_shareOb = $shareOb;
 697:     }
 698: 
 699:     /**
 700:      * Checks to see if a user has a given permission.
 701:      *
 702:      * @param string $userid       The userid of the user.
 703:      * @param integer $permission  A Horde_Perms::* constant to test for.
 704:      * @param string $creator      The creator of the event.
 705:      *
 706:      * @return boolean  Whether or not $userid has $permission.
 707:      */
 708:     function hasPermission($userid, $permission, $creator = null)
 709:     {
 710:         if ($userid == $this->get('owner')) {
 711:             return true;
 712:         }
 713: 
 714:         $perms = $GLOBALS['injector']->getInstance('Horde_Perms');
 715:         return $perms->hasPermission($this->getPermission(), $userid, $permission, $creator);
 716:     }
 717: 
 718:     /**
 719:      * TODO
 720:      *
 721:      * @param TODO
 722:      * @param boolean $update  TODO
 723:      *
 724:      * @return TODO
 725:      */
 726:     function setPermission($perm, $update = true)
 727:     {
 728:         $this->data['perm'] = $perm->getData();
 729:         if ($update) {
 730:             return $this->save();
 731:         }
 732:         return true;
 733:     }
 734: 
 735:     /**
 736:      * TODO
 737:      *
 738:      * @return Horde_Perms_Permission
 739:      */
 740:     function getPermission()
 741:     {
 742:         $perm = new Horde_Perms_Permission($this->getName());
 743:         $perm->data = isset($this->data['perm']) ? $this->data['perm'] : array();
 744: 
 745:         return $perm;
 746:     }
 747: 
 748:     /**
 749:      * Forces all children of this share to inherit the permissions set on this
 750:      * share.
 751:      *
 752:      * @return TODO
 753:      */
 754:     function inheritPermissions()
 755:     {
 756:         $c_list = $this->datatree->get(DATATREE_FORMAT_FLAT, $this->getName(), true);
 757:         if (is_a($c_list, 'PEAR_Error') || !$c_list) {
 758:             // If we got back an error or an empty array, just return it.
 759:             return $c_list;
 760:         }
 761:         unset($c_list[$this->getName()]);
 762: 
 763:         $children = $this->_shareOb->getShares(array_keys($c_list));
 764:         if (is_a($children, 'PEAR_Error')) {
 765:             return $children;
 766:         }
 767: 
 768:         $perm = $this->getPermission();
 769:         foreach ($children as $child) {
 770:             $child->setPermission($perm);
 771:         }
 772: 
 773:         return true;
 774:     }
 775: 
 776:     /**
 777:      * Sets one of the attributes of the object.
 778:      *
 779:      * @param string   The attribute to set.
 780:      * @param mixed    The value for $attribute.
 781:      * @param boolean  Determines whether the backend should be updated or not.
 782:      */
 783:     function set($attribute, $value, $update = false)
 784:     {
 785:         parent::set($attribute, $value);
 786:         if ($update) {
 787:             $this->save();
 788:         }
 789:     }
 790: 
 791:     /**
 792:      * Adds a bookmark to this folder.
 793:      *
 794:      * @param array $properties The initial properties for the new
 795:      *                          bookmark. Expected values are
 796:      *                          'bookmark_url', 'bookmark_title', and
 797:      *                          'bookmark_description'.
 798:      *
 799:      * @return  The id of the new bookmark.
 800:      */
 801:     function addBookmark($properties)
 802:     {
 803:         $properties['folder_id'] = $this->getId();
 804:         $bookmark = new Trean_Bookmark($properties);
 805:         return $bookmark->save();
 806:     }
 807: 
 808:     /**
 809:      * Adds a child folder to this folder.
 810:      *
 811:      * @param array $properties  The initial properties for the new folder.
 812:      *                           Expected value is 'name'.
 813:      *
 814:      * @return  The id of the new folder.
 815:      */
 816:     function addFolder($properties)
 817:     {
 818:         $folder = $this->_shareOb->newFolder($this->getName() . ':' . strval(new Horde_Support_Uuid()), $properties);
 819:         $this->_shareOb->addFolder($folder);
 820:         return $this->datatree->getId($folder);
 821:     }
 822: 
 823:     /**
 824:      * Returns the id of this folder's parent folder.
 825:      */
 826:     function getParent()
 827:     {
 828:         $parent = $this->datatree->getParent($this->getName());
 829:         return ($parent == DATATREE_ROOT) ? null : $parent;
 830:     }
 831: 
 832:     /**
 833:      * Lists the bookmarks in this folder.
 834:      *
 835:      * @param integer $from   The bookmark to start fetching.
 836:      * @param integer $count  The numer of bookmarks to return.
 837:      */
 838:     function listBookmarks($sortby = 'title', $sortdir = 0, $from = 0, $count = 0)
 839:     {
 840:         // Make sure $sortby is a valid field.
 841:         switch ($sortby) {
 842:         case 'rating':
 843:         case 'clicks':
 844:             break;
 845: 
 846:         default:
 847:             $sortby = 'title';
 848:         }
 849: 
 850:         $GLOBALS['trean_db']->setLimit($count, $from);
 851:         return Trean_Bookmarks::resultSet($GLOBALS['trean_db']->queryAll('
 852:             SELECT bookmark_id, folder_id, bookmark_url, bookmark_title, bookmark_description,
 853:                    bookmark_clicks, bookmark_rating
 854:             FROM trean_bookmarks
 855:             WHERE folder_id = ' . (int)$this->getId() . '
 856:             ORDER BY bookmark_' . $sortby . ($sortdir ? ' DESC' : ''), null, MDB2_FETCHMODE_ASSOC));
 857:     }
 858: 
 859:     /**
 860:      * Maps this object's attributes from the data array into a format that we
 861:      * can store in the attributes storage backend.
 862:      *
 863:      * @access protected
 864:      *
 865:      * @param boolean $permsonly  Only process permissions? Lets subclasses
 866:      *                            override part of this method while handling
 867:      *                            their additional attributes seperately.
 868:      *
 869:      * @return array  The attributes array.
 870:      */
 871:     function _toAttributes($permsonly = false)
 872:     {
 873:         // Default to no attributes.
 874:         $attributes = array();
 875: 
 876:         foreach ($this->data as $key => $value) {
 877:             if ($key == 'perm') {
 878:                 foreach ($value as $type => $perms) {
 879:                     if (is_array($perms)) {
 880:                         foreach ($perms as $member => $perm) {
 881:                             $attributes[] = array('name' => 'perm_' . $type,
 882:                                                   'key' => $member,
 883:                                                   'value' => $perm);
 884:                         }
 885:                     } else {
 886:                         $attributes[] = array('name' => 'perm_' . $type,
 887:                                               'key' => '',
 888:                                               'value' => $perms);
 889:                     }
 890:                 }
 891:             } elseif (!$permsonly) {
 892:                 $attributes[] = array('name' => $key,
 893:                                       'key' => '',
 894:                                       'value' => $value);
 895:             }
 896:         }
 897: 
 898:         return $attributes;
 899:     }
 900: 
 901:     /**
 902:      * Takes in a list of attributes from the backend and maps it to our
 903:      * internal data array.
 904:      *
 905:      * @access protected
 906:      *
 907:      * @param array $attributes   The list of attributes from the backend
 908:      *                            (attribute name, key, and value).
 909:      * @param boolean $permsonly  Only process permissions? Lets subclasses
 910:      *                            override part of this method while handling
 911:      *                            their additional attributes seperately.
 912:      */
 913:     function _fromAttributes($attributes, $permsonly = false)
 914:     {
 915:         // Initialize data array.
 916:         $this->data['perm'] = array();
 917: 
 918:         foreach ($attributes as $attr) {
 919:             if (substr($attr['name'], 0, 4) == 'perm') {
 920:                 if (!empty($attr['key'])) {
 921:                     $this->data['perm'][substr($attr['name'], 5)][$attr['key']] = $attr['value'];
 922:                 } else {
 923:                     $this->data['perm'][substr($attr['name'], 5)] = $attr['value'];
 924:                 }
 925:             } elseif (!$permsonly) {
 926:                 $this->data[$attr['name']] = $attr['value'];
 927:             }
 928:         }
 929:     }
 930: 
 931: }
 932: 
 933: /**
 934:  * @author  Ben Chavet <ben@horde.org>
 935:  * @package Trean
 936:  */
 937: class Trean_Bookmark {
 938: 
 939:     var $id = null;
 940:     var $url = null;
 941:     var $title = '';
 942:     var $description = '';
 943:     var $clicks = 0;
 944:     var $rating = 0;
 945:     var $http_status = null;
 946:     var $folder;
 947:     var $favicon;
 948: 
 949:     function Trean_Bookmark($bookmark = array())
 950:     {
 951:         if ($bookmark) {
 952:             $this->url = $bookmark['bookmark_url'];
 953:             $this->title = $bookmark['bookmark_title'];
 954:             $this->description = $bookmark['bookmark_description'];
 955:             $this->folder = $bookmark['folder_id'];
 956: 
 957:             if (!empty($bookmark['bookmark_id'])) {
 958:                 $this->id = (int)$bookmark['bookmark_id'];
 959:             }
 960:             if (!empty($bookmark['bookmark_clicks'])) {
 961:                 $this->clicks = (int)$bookmark['bookmark_clicks'];
 962:             }
 963:             if (!empty($bookmark['bookmark_rating'])) {
 964:                 $this->rating = (int)$bookmark['bookmark_rating'];
 965:             }
 966:             if (!empty($bookmark['bookmark_http_status'])) {
 967:                 $this->http_status = $bookmark['bookmark_http_status'];
 968:             }
 969:         }
 970:     }
 971: 
 972:     /**
 973:      * Copy this bookmark into $folder.
 974:      */
 975:     function copyTo($folder)
 976:     {
 977:         if (!is_a($folder, 'DataTreeObject_Folder')) {
 978:             return PEAR::raiseError('Folders must be DataTreeObject_Folder objects or extend that class.');
 979:         }
 980: 
 981:         return $folder->addBookmark(array('bookmark_url' => $this->url,
 982:                                           'bookmark_title' => $this->title,
 983:                                           'bookmark_description' => $this->description));
 984:     }
 985: 
 986:     /**
 987:      * Save bookmark.
 988:      */
 989:     function save()
 990:     {
 991:         if ($this->id) {
 992:             // Update an existing bookmark.
 993:             $update = $GLOBALS['trean_db']->prepare('
 994:                 UPDATE trean_bookmarks
 995:                 SET folder_id = ?,
 996:                     bookmark_url = ?,
 997:                     bookmark_title = ?,
 998:                     bookmark_description = ?,
 999:                     bookmark_clicks = ?,
1000:                     bookmark_rating = ?
1001:                 WHERE bookmark_id = ?',
1002:                 array('integer', 'text', 'text', 'text', 'integer', 'integer', 'integer')
1003:             );
1004:             if (is_a($update, 'PEAR_Error')) {
1005:                 return $update;
1006:             }
1007:             $result = $update->execute(array($this->folder,
1008:                                              Horde_String::convertCharset($this->url, 'UTF-8', $GLOBALS['conf']['sql']['charset']),
1009:                                              Horde_String::convertCharset($this->title, 'UTF-8', $GLOBALS['conf']['sql']['charset']),
1010:                                              Horde_String::convertCharset($this->description, 'UTF-8', $GLOBALS['conf']['sql']['charset']),
1011:                                              $this->clicks,
1012:                                              $this->rating,
1013:                                              $this->id));
1014:             if (is_a($result, 'PEAR_Error')) {
1015:                 Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
1016:             }
1017:             return $result;
1018:         }
1019: 
1020:         if (!$this->folder || !strlen($this->url)) {
1021:             return PEAR::raiseError('Incomplete bookmark');
1022:         }
1023: 
1024:         // Saving a new bookmark.
1025:         $bookmark_id = $GLOBALS['trean_db']->nextId('trean_bookmarks');
1026:         if (is_a($bookmark_id, 'PEAR_Error')) {
1027:             Horde::logMessage($bookmark_id, __FILE__, __LINE__, PEAR_LOG_ERR);
1028:             return $bookmark_id;
1029:         }
1030: 
1031:         $insert = $GLOBALS['trean_db']->prepare('
1032:             INSERT INTO trean_bookmarks
1033:                 (bookmark_id, folder_id, bookmark_url, bookmark_title, bookmark_description,
1034:                  bookmark_clicks, bookmark_rating)
1035:             VALUES (?, ?, ?, ?, ?, ?, ?)',
1036:             array('integer', 'integer', 'text', 'text', 'text', 'integer', 'integer')
1037:         );
1038:         if (is_a($insert, 'PEAR_Error')) {
1039:             return $insert;
1040:         }
1041: 
1042:         $result = $insert->execute(array($bookmark_id,
1043:                                          $this->folder,
1044:                                          Horde_String::convertCharset($this->url, 'UTF-8', $GLOBALS['conf']['sql']['charset']),
1045:                                          Horde_String::convertCharset($this->title, 'UTF-8', $GLOBALS['conf']['sql']['charset']),
1046:                                          Horde_String::convertCharset($this->description, 'UTF-8', $GLOBALS['conf']['sql']['charset']),
1047:                                          $this->clicks,
1048:                                          $this->rating,
1049:         ));
1050:         if (is_a($result, 'PEAR_Error')) {
1051:             Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
1052:             return $result;
1053:         }
1054: 
1055:         $this->id = (int)$bookmark_id;
1056:         return $this->id;
1057:     }
1058: 
1059: }
1060: 
API documentation generated by ApiGen