package.xml0000664000175000017500000004624612611721515013460 0ustar mrubinskmrubinsk content pear.horde.org Tagging application This application provides tagging support for the other Horde applications. Chuck Hagenbuch chuck chuck@horde.org yes Michael J Rubinsky mrubinsk mrubinsk@horde.org yes 2015-10-21 2.0.5 2.0.0 stable stable BSD-2-Clause * [mjr] Fix bug that was causing tag stats to be incorrectly incremented (Bug #14112). * [mjr] Add Content_Objects_Manager::delete(). * [jan] Add unit tests for Oracle. 5.3.0 6.0.0alpha1 6.0.0alpha1 1.7.0 Horde_Core pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Date pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Exception pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Db pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Injector pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Rdo pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Util pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 gettext json Horde_Argv pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Controller pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_ElasticSearch pear.horde.org 1.0.0 2.0.0alpha1 2.0.0alpha1 horde Role pear.horde.org 1.0.0alpha1 1.0.0 alpha alpha 2011-03-09 BSD-2-Clause * First alpha release for Horde 4. 1.0.0beta1 1.0.0 beta beta 2011-03-16 BSD-2-Clause * First beta release for Horde 4. 1.0.0RC1 1.0.0 beta beta 2011-03-23 BSD-2-Clause * First release candidate for Horde 4. 1.0.0RC2 1.0.0 beta beta 2011-03-30 BSD-2-Clause * Second release candidate for Horde 4. * [jan] Rename all scripts to be prefixed with content- (Request #9647). 1.0.0 1.0.0 stable stable 2011-04-06 BSD-2-Clause * First stable release for Horde 4. * [jan] Fix case-insensitive filtering of duplicate tags (Bug #9617). 1.0.1 1.0.0 stable stable 2011-08-05 BSD-2-Clause * [mjr] Enforce that object and type names are always taken as a string (Bug #10171). * [mjr] Updated unit tests to new test structure. * [mjr] Prevent tagging with empty tags. 1.0.2 1.0.0 stable stable 2011-08-11 BSD-2-Clause * [mjr] Fix broken tag cloud queries due to missing GROUP BY fields (Bug #10419) * [mjr] Prevent tagging with empty strings. 1.0.3 1.0.0 stable stable 2011-10-14 BSD-2-Clause * [mjr] Honor the limit and radius parameters. * [mjr] Fix getObjects method when passing object_id (Bug# 10439). * [mjr] Fix including all non-aggregate fields in GROUP BY clause (Bug# 10419). * [jan] Fix commandline scripts (Bug #10656). 1.0.4 1.0.0 stable stable 2011-10-14 BSD-2-Clause * 2012-08-29 2.0.0beta1 2.0.0beta1 beta beta BSD-2-Clause * First beta release for Horde 5. 2.0.0 2.0.0 stable stable 2012-10-30 BSD-2-Clause * First stable release for Horde 5. 2.0.1 2.0.0 stable stable 2012-11-15 BSD-2-Clause * [jan] Add missing optional dependency on Horde_ElasticSearch. * [jan] Catch exceptions from Horde_ElasticSearch. 2.0.2 2.0.0 stable stable 2013-02-11 BSD-2-Clause * [mjr] Fix logic that could possibly lead to incorrect content types being applied to objects (Bug #12016). 2.0.3 2.0.0 stable stable 2013-07-16 BSD-2-Clause * [mjr] Fix obtaining tag cloud information when filtering by objectIds. 2.0.4 2.0.0 stable stable 2014-06-02 BSD-2-Clause * [jan] Fix date format for 'created' column. 2.0.5 2.0.0 stable stable 2015-10-21 BSD-2-Clause * [mjr] Fix bug that was causing tag stats to be incorrectly incremented (Bug #14112). * [mjr] Add Content_Objects_Manager::delete(). * [jan] Add unit tests for Oracle. content-2.0.5/app/controllers/ApplicationController.php0000664000175000017500000000055712611721515023616 0ustar mrubinskmrubinsktagger = $GLOBALS['injector']->getInstance('Content_Tagger'); } } content-2.0.5/app/controllers/TagController.php0000664000175000017500000001033412611721515022060 0ustar mrubinskmrubinskresults = $this->tagger->getTags(array( 'q' => $this->params->q, 'typeId' => $this->params->typeId, 'userId' => $this->params->userId, 'objectId' => $this->params->objectId, )); $this->_render(); } /** */ public function recentTags() { $this->results = $this->tagger->getRecentTags(array( 'limit' => 10, 'typeId' => $this->params->typeId, 'objectId' => $this->params->objectId, )); $this->_render(); } public function searchUsers() { } public function recentUsers() { } public function searchObjects() { } public function recentObjects() { } /** * Add a tag */ public function tag() { // The route configuration enforces POST or PUT only, but double-check here. } /** * Remove a tag */ public function untag() { // The route configuration enforces POST or DELETE only, but double-check here. } protected function _render() { switch ((string)$this->_request->getFormat()) { case 'html': $this->render(); break; case 'atom': case 'rss': $method = '_' . $this->_action . 'Feed'; $this->$method(); break; case 'json': default: $this->renderText(json_encode($this->results)); break; } } protected function _recentTagsFeed() { $entries = array(); foreach ($this->results as $tag) { $entries[] = array( 'id' => 'tag/' . $tag['tag_id'], /* @TODO use routes to get the full URI here */ 'title' => $tag['tag_name'], 'updated' => $tag['created'], ); } $format = $this->_request->getFormat(); $class = 'Horde_Feed_' . ucfirst((string)$this->_request->getFormat()); $feed = new $class(array( 'id' => 'tags/recent', /* @TODO Use routes to get url to this search */ 'title' => 'Recent tags', 'updated' => $this->_request->getTimestamp(), 'entry' => $entries, )); header('Content-type: ' . $format->string); $this->renderText($feed->saveXml()); } protected function _recentObjectsFeed() { $entries = array(); foreach ($this->results as $object) { $entries[] = array( 'id' => 'object/' . $object['object_id'], /* @TODO use routes to get the full URI here */ 'title' => $object['object_name'], 'updated' => $object['created'], ); } $format = $this->_request->getFormat(); $class = 'Horde_Feed_' . ucfirst((string)$this->_request->getFormat()); $feed = new $class(array( 'id' => 'objects/recent', /* @TODO Use routes to get url to this search */ 'title' => 'Recent objects', 'updated' => $this->_request->getTimestamp(), 'entry' => $entries, )); header('Content-type: ' . $format->string); $this->renderText($feed->saveXml()); } protected function _recentUsersFeed() { $entries = array(); foreach ($this->results as $user) { $entries[] = array( 'id' => 'user/' . $user['user_id'], /* @TODO use routes to get the full URI here */ 'title' => $user['user_name'], 'updated' => $user['created'], ); } $format = $this->_request->getFormat(); $class = 'Horde_Feed_' . ucfirst((string)$this->_request->getFormat()); $feed = new $class(array( 'id' => 'users/recent', /* @TODO Use routes to get url to this search */ 'title' => 'Recent users', 'updated' => $this->_request->getTimestamp(), 'entry' => $entries, )); header('Content-type: ' . $format->string); $this->renderText($feed->saveXml()); } } content-2.0.5/app/views/Tag/recentTags.html.php0000664000175000017500000000035512611721515021647 0ustar mrubinskmrubinsk content-2.0.5/app/views/Tag/searchTags.html.php0000664000175000017500000000027012611721515021630 0ustar mrubinskmrubinsk content-2.0.5/bin/content-object-add0000775000175000017500000000246312611721515017610 0ustar mrubinskmrubinsk#!/usr/bin/env php get('horde_dir', null, 'pear.horde.org') . '/content/'; } require_once $baseDir . 'lib/Application.php'; Horde_Registry::appInit('content', array('cli' => true)); $options = array( new Horde_Argv_Option('-i', '--id', array('type' => 'int')), new Horde_Argv_Option('-t', '--type-id', array('type' => 'int')), ); $parser = new Horde_Argv_Parser(array('optionList' => $options)); list($opts, $positional) = $parser->parseArgs(); if (!$opts->id || !$opts->type_id) { throw new InvalidArgumentException('id and type-id are both required'); } require CONTENT_BASE . '/lib/Objects/Object.php'; require CONTENT_BASE . '/lib/Objects/ObjectMapper.php'; $m = new Content_ObjectMapper($injector->getInstance('Horde_Db_Adapter')); $i = $m->create(array('object_name' => $opts->id, 'type_id' => $opts->type_id, )); echo 'Created new object with id ' . $i->object_id . ' for ' . $i->type_id . ':' . $i->object_name . ".\n"; exit(0); content-2.0.5/bin/content-object-delete0000775000175000017500000000210712611721515020315 0ustar mrubinskmrubinsk#!/usr/bin/env php get('horde_dir', null, 'pear.horde.org') . '/content/'; } require_once $baseDir . 'lib/Application.php'; Horde_Registry::appInit('content', array('cli' => true)); $options = array( new Horde_Argv_Option('-m', '--object-id', array('type' => 'int')), ); $parser = new Horde_Argv_Parser(array('optionList' => $options)); list($opts, $positional) = $parser->parseArgs(); if (!$opts->object_id) { throw new InvalidArgumentException('object_id is required'); } $m = new Content_ObjectMapper($injector->getInstance('Horde_Db_Adapter')); if ($m->delete($opts->object_id)) { echo 'Deleted object with id ' . $opts->object_id . ".\n"; exit(0); } else { echo 'Object #' . $opts->object_id . " not found.\n"; exit(1); } content-2.0.5/bin/content-tag0000775000175000017500000000222212611721515016360 0ustar mrubinskmrubinsk#!/usr/bin/env php get('horde_dir', null, 'pear.horde.org') . '/content/'; } require_once $baseDir . 'lib/Application.php'; Horde_Registry::appInit('content', array('cli' => true)); $options = array( new Horde_Argv_Option('-u', '--user-id', array('type' => 'int')), new Horde_Argv_Option('-o', '--object-id', array('type' => 'int')), ); $parser = new Horde_Argv_Parser(array('optionList' => $options)); list($opts, $tags) = $parser->parseArgs(); if (!$opts->user_id || !$opts->object_id) { throw new InvalidArgumentException('user-id and object-id are both required'); } if (!count($tags)) { throw new InvalidArgumentException('List at least one tag to add.'); } /* @TODO Switch to using the TagController */ $injector->getInstance('Content_Tagger') ->tag($opts->user_id, $opts->object_id, $tags); exit(0); content-2.0.5/bin/content-tag-add0000775000175000017500000000202212611721515017104 0ustar mrubinskmrubinsk#!/usr/bin/env php get('horde_dir', null, 'pear.horde.org') . '/content/'; } require_once $baseDir . 'lib/Application.php'; Horde_Registry::appInit('content', array('cli' => true)); $parser = new Horde_Argv_Parser(); list($opts, $tags) = $parser->parseArgs(); if (!count($tags)) { throw new InvalidArgumentException('List at least one tag to add.'); } require CONTENT_BASE . '/lib/Tags/Tag.php'; require CONTENT_BASE . '/lib/Tags/TagMapper.php'; $m = new Content_TagMapper($injector->getInstance('Horde_Db_Adapter')); foreach ($tags as $tag) { $t = $m->create(array('tag_name' => $tag)); echo 'Created new tag with id ' . $t->tag_id . ' and name "' . $t->tag_name . "\".\n"; } exit(0); content-2.0.5/bin/content-tag-delete0000775000175000017500000000230712611721515017624 0ustar mrubinskmrubinsk#!/usr/bin/env php get('horde_dir', null, 'pear.horde.org') . '/content/'; } require_once $baseDir . 'lib/Application.php'; Horde_Registry::appInit('content', array('cli' => true)); $parser = new Horde_Argv_Parser(); list($opts, $tags) = $parser->parseArgs(); if (!count($tags)) { throw new InvalidArgumentException('List at least tag to delete.'); } require CONTENT_BASE . '/lib/Tags/Tag.php'; require CONTENT_BASE . '/lib/Tags/TagMapper.php'; $m = new Content_TagMapper($injector->getInstance('Horde_Db_Adapter')); foreach ($tags as $tag) { $t = $m->findOne(array('tag_name' => $tag)); if (!$t) { echo "$tag doesn't seem to exist, skipping it.\n"; continue; } if ($t->delete()) { echo "Delete tag '$tag' (#".$t->tag_id.")\n"; continue; } else { echo "Failed to delete '$tag'\n"; exit(1); } } exit(0); content-2.0.5/bin/content-untag0000775000175000017500000000222712611721515016730 0ustar mrubinskmrubinsk#!/usr/bin/env php get('horde_dir', null, 'pear.horde.org') . '/content/'; } require_once $baseDir . 'lib/Application.php'; Horde_Registry::appInit('content', array('cli' => true)); $options = array( new Horde_Argv_Option('-u', '--user-id', array('type' => 'int')), new Horde_Argv_Option('-i', '--object-id', array('type' => 'int')), ); $parser = new Horde_Argv_Parser(array('optionList' => $options)); list($opts, $tags) = $parser->parseArgs(); if (!$opts->user_id || !$opts->object_id) { throw new InvalidArgumentException('user-id and object-id are both required'); } if (!count($tags)) { throw new InvalidArgumentException('List at least one tag to remove.'); } /* @TODO Switch to using the TagController */ $injector->getInstance('Content_Tagger') ->untag($opts->user_id, $opts->object_id, $tags); exit(0); content-2.0.5/config/routes.php0000664000175000017500000000437612611721515016752 0ustar mrubinskmrubinskconnect('tags', array('controller' => 'tag', 'action' => 'searchTags')); $mapper->connect('tags.:(format)', array('controller' => 'tag', 'action' => 'searchTags')); // Most recent tags. Available query parameters: // typeId: restrict matches to tags that have been applied to objects with type $typeId // userId: restrict matches to tags that have been applied by $userId $mapper->connect('tags/recent', array('controller' => 'tag', 'action' => 'recentTags')); $mapper->connect('tags/recent.:(format)', array('controller' => 'tag', 'action' => 'recentTags')); // List objects. At least a content type, or more specific parameters, are // required; listing all objects is not allowed. $mapper->connect('objects', array('controller' => 'tag', 'action' => 'searchObjects')); $mapper->connect('objects.:(format)', array('controller' => 'tag', 'action' => 'searchObjects')); // List users. Specific parameters are required as listing all users is not // allowed. $mapper->connect('users', array('controller' => 'tag', 'action' => 'searchUsers')); $mapper->connect('users.:(format)', array('controller' => 'tag', 'action' => 'searchUsers')); // Tag an object. Required POST parameters are: tags (array or string list) and // objectId. userId is inferred from the authenticated user: $mapper->connect('tag', array('controller' => 'tag', 'action' => 'tag', 'conditions' => array('method' => array('POST', 'PUT')))); // Untag an object. Required POST parameters are: tags (array or string list) // and objectId. userId is inferred from the authenticated user: $mapper->connect('untag', array('controller' => 'tag', 'action' => 'untag', 'conditions' => array('method' => array('POST', 'DELETE')))); content-2.0.5/docs/CHANGES0000600000175000017500000000263612611721515015361 0ustar mrubinskmrubinsk------ v2.0.5 ------ [mjr] Fix bug that was causing tag stats to be incorrectly incremented (Bug #14112). [mjr] Add Content_Objects_Manager::delete(). [jan] Add unit tests for Oracle. ------ v2.0.4 ------ [jan] Fix date format for 'created' column. ------ v2.0.3 ------ [mjr] Fix obtaining tag cloud information when filtering by objectIds. ------ v2.0.2 ------ [mjr] Fix logic that could possibly lead to incorrect content types being applied to objects (Bug #12016). ------ v2.0.1 ------ [jan] Add missing optional dependency on Horde_ElasticSearch. [jan] Catch exceptions from Horde_ElasticSearch. ------ v2.0.0 ------ [jan] Support Horde 5. ------ v1.0.3 ------ [mjr] Honor the limit and radius parameters. [mjr] Fix getObjects method when passing object_id (Bug# 10439). [mjr] Fix including all non-aggregate fields in GROUP BY clause (Bug# 10419). [jan] Fix commandline scripts (Bug #10656). ------ v1.0.2 ------ [mjr] Fix broken tag cloud queries due to missing GROUP BY fields (Bug #10419) [mjr] Prevent tagging with empty strings. ------ v1.0.1 ------ [mjr] Enforce that object and type names are always taken as a string (Bug #10171). [mjr] Updated unit tests to new test structure. [mjr] Prevent tagging with empty tags. ------ v1.0.0 ------ [jan] Fix case-insensitive filtering of duplicate tags (Bug #9617). [jan] Rename all scripts to be prefixed with content- (Request #9647). content-2.0.5/docs/RELEASE_NOTES0000664000175000017500000000006112611721515016341 0ustar mrubinskmrubinsk * @author Michael Rubinsky * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ /** * @author Chuck Hagenbuch * @author Michael Rubinsky * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_Objects_Manager { /** * Database adapter * * @var Horde_Db_Adapter */ protected $_db; /** * Tables * * @TODO: this should probably be populated by the responsible manager... * @var array */ protected $_tables = array( 'objects' => 'rampage_objects', ); /** * Type manager * * @var Content_Types_Manager */ protected $_typeManager; /** * Constructor * * @param Horde_Db_Adapter $db The db adapter * @param Content_Types_Manager $typeManager A content type manager * * @return Content_Objects_Manager */ public function __construct(Horde_Db_Adapter $db, Content_Types_Manager $typeManager) { $this->_db = $db; $this->_typeManager = $typeManager; } /** * Check for object existence without causing the objects to be created. * Helps save queries for things like tags when we already know the object * doesn't yet exist in rampage tables. * * @param mixed string|array $objects Either an object identifier or an * array of them. * @param mixed $type A type identifier. Either a string * type name or the integer type_id. * * @return mixed Either a hash of object_id => object_names or false if * the object(s) do not exist. * @throws InvalidArgumentException, Content_Exception */ public function exists($objects, $type) { $type = current($this->_typeManager->ensureTypes($type)); if (!is_array($objects)) { $objects = array($objects); } if (!count($objects)) { return array(); } // Ensure we take the object as a string indentifier. foreach ($objects as &$object) { $object = strval($object); } $params = $objects; $params[] = $type; try { $ids = $this->_db->selectAssoc( 'SELECT object_id, object_name FROM ' . $this->_t('objects') . ' WHERE object_name IN (' . str_repeat('?,', count($objects) - 1) . '?)' . ' AND type_id = ?', $params); if ($ids) { return $ids; } } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } return false; } /** * Remove the object. * NOTE: This does not ensure any references to this object were removed. * E.g., does not remove any tags etc... That is client code's * responsibility. * * @param array $objects An array of object identifiers to delete. * @param string $type The type of the objects. All objects must be of * the same type. * * @throws Content_Exception */ public function delete(array $objects, $type) { $type = current($this->_typeManager->ensureTypes($type)); // Ensure we take the object as a string indentifier. foreach ($objects as &$object) { $object = strval($object); } $params = $objects; $params[] = $type; try { $this->_db->delete( 'DELETE FROM ' . $this->_t('objects') . ' WHERE object_name IN (' . str_repeat('?,', count($objects) - 1) . '?)' . ' AND type_id = ?', $params ); } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } } /** * Ensure that an array of objects exist in storage. Create any that don't, * return object_ids for all. All objects in the $objects array must be * of the same content type. * * @param mixed $objects An array of objects (or single obejct value). * Values typed as an integer are assumed to already * be an object_id. * @param mixed $type Either a string type_name or integer type_id * * @return array An array of object_ids. */ public function ensureObjects($objects, $type) { if (!is_array($objects)) { $objects = array($objects); } $objectIds = array(); $objectName = array(); $type = current($this->_typeManager->ensureTypes($type)); // Anything already typed as an integer is assumed to be an object id. foreach ($objects as $objectIndex => $object) { if (is_int($object)) { $objectIds[$objectIndex] = $object; } else { $objectName[$object] = $objectIndex; } } // Get the ids for any objects that already exist. try { if (count($objectName)) { $rows = $this->_db->selectAll( 'SELECT object_id, object_name FROM ' . $this->_t('objects') . ' WHERE object_name IN (' . implode(',', array_map(array($this->_db, 'quoteString'), array_keys($objectName))) . ') AND type_id = ' . $type); foreach ($rows as $row) { $objectIndex = $objectName[$row['object_name']]; unset($objectName[$row['object_name']]); $objectIds[$objectIndex] = $row['object_id']; } } // Create any objects that didn't already exist foreach ($objectName as $object => $objectIndex) { $objectIds[$objectIndex] = $this->_db->insert('INSERT INTO ' . $this->_t('objects') . ' (object_name, type_id) VALUES (' . $this->_db->quoteString($object) . ', ' . $type . ')'); } } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } return $objectIds; } /** * Shortcut for getting a table name. * * @param string $tableType * * @return string Configured table name. */ protected function _t($tableType) { return $this->_db->quoteTableName($this->_tables[$tableType]); } } content-2.0.5/lib/Objects/Object.php0000664000175000017500000000041212611721515017514 0ustar mrubinskmrubinsk * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_Object extends Horde_Rdo_Base { } content-2.0.5/lib/Relationships/Relationship.php0000664000175000017500000000000012611721515022173 0ustar mrubinskmrubinskcontent-2.0.5/lib/Relationships/RelationshipMapper.php0000664000175000017500000000000012611721515023340 0ustar mrubinskmrubinskcontent-2.0.5/lib/Tags/Tag.php0000664000175000017500000000040712611721515016332 0ustar mrubinskmrubinsk * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_Tag extends Horde_Rdo_Base { } content-2.0.5/lib/Tags/TagMapper.php0000664000175000017500000000057712611721515017507 0ustar mrubinskmrubinsk * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_TagMapper extends Horde_Rdo_Mapper { /** * Inflector doesn't support Horde-style tables yet */ protected $_table = 'rampage_tags'; } content-2.0.5/lib/Types/Manager.php0000664000175000017500000000545312611721515017405 0ustar mrubinskmrubinsk * @author Michael Rubinsky * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_Types_Manager { /** * Database adapter * @var Horde_Db_Adapter */ protected $_db; /** * Tables * @var array */ protected $_tables = array( 'types' => 'rampage_types', ); public function __construct(Horde_Db_Adapter $db) { $this->_db = $db; } /** * Ensure that an array of types exist in storage. Create any that don't, * return type_ids for all. * * @param mixed $types An array of types or single type value. Values typed * as an integer are assumed to already be an type_id. * * @return array An array of type_ids. * @throws Content_Exception */ public function ensureTypes($types) { if (!is_array($types)) { $types = array($types); } $typeIds = array(); $typeName = array(); // Anything already typed as an integer is assumed to be a type id. foreach ($types as $typeIndex => $type) { if (is_int($type)) { $typeIds[$typeIndex] = $type; } else { $typeName[$type] = $typeIndex; } } try { // Get the ids for any types that already exist. if (count($typeName)) { $rows = $this->_db->selectAssoc('SELECT type_id, type_name FROM ' . $this->_t('types') . ' WHERE type_name IN (' . implode(',', array_map(array($this->_db, 'quoteString'), array_keys($typeName))) . ')'); foreach ($rows as $id => $type) { $typeIndex = $typeName[$type]; unset($typeName[$type]); $typeIds[$typeIndex] = (int)$id; } } // Create any types that didn't already exist foreach ($typeName as $type => $typeIndex) { $typeIds[$typeIndex] = intval($this->_db->insert( 'INSERT INTO ' . $this->_t('types') . ' (type_name) VALUES (' . $this->_db->quoteString($type) . ')')); } } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } return $typeIds; } /** * Shortcut for getting a table name. * * @param string $tableType * * @return string Configured table name. */ protected function _t($tableType) { return $this->_db->quoteTableName($this->_tables[$tableType]); } } content-2.0.5/lib/Users/Manager.php0000664000175000017500000000551012611721515017374 0ustar mrubinskmrubinsk * @author Michael Rubinsky * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_Users_Manager { /** * Database adapter * @var Horde_Db_Adapter */ protected $_db; /** * Tables * @var array */ protected $_tables = array( 'users' => 'rampage_users', ); public function __construct(Horde_Db_Adapter $db) { $this->_db = $db; } /** * Ensure that an array of users exist in storage. Create any that don't, * return user_ids for all. * * @param array $users An array of users. Values typed as an integer * are assumed to already be an user_id. * * @return array An array of user_ids. */ public function ensureUsers($users) { if (!is_array($users)) { $users = array($users); } $userIds = array(); $userName = array(); // Anything already typed as an integer is assumed to be a user id. foreach ($users as $userIndex => $user) { if (is_int($user)) { $userIds[$userIndex] = $user; } else { $userName[$user] = $userIndex; } } // Get the ids for any users that already exist. try { if (count($userName)) { $userName; $sql = 'SELECT user_id, user_name FROM ' . $this->_t('users') . ' WHERE user_name IN (' . implode(',', array_map(array($this, 'toDriver'), array_keys($userName))) . ')'; foreach ($this->_db->selectAll($sql) as $row) { $userIndex = $userName[$row['user_name']]; unset($userName[$row['user_name']]); $userIds[$userIndex] = $row['user_id']; } } // Create any users that didn't already exist foreach ($userName as $user => $userIndex) { $userIds[$userIndex] = $this->_db->insert('INSERT INTO ' . $this->_t('users') . ' (user_name) VALUES (' . $this->toDriver($user) . ')'); } } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } return $userIds; } /** * Shortcut for getting a table name. * * @param string $tableType * * @return string Configured table name. */ protected function _t($tableType) { return $this->_db->quoteTableName($this->_tables[$tableType]); } public function toDriver($value) { return $this->_db->quoteString(Horde_String::convertCharset($value, 'UTF-8', $this->_db->getOption('charset'))); } } content-2.0.5/lib/Application.php0000664000175000017500000000204112611721515017160 0ustar mrubinskmrubinsk * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ class Content_Indexer { /** * ElasticSearch client * @var Horde_ElasticSearch_Client */ protected $_es; /** * User manager object * @var Content_Users_Manager */ protected $_userManager; /** * Type management object * @var Content_Types_Manager */ protected $_typeManager; /** * Object manager * @var Content_Objects_Manager */ protected $_objectManager; /** * Constructor */ public function __construct(Horde_ElasticSearch_Client $es, Content_Users_Manager $userManager, Content_Types_Manager $typeManager, Content_Objects_Manager $objectManager) { $this->_es = $es; $this->_userManager = $userManager; $this->_typeManager = $typeManager; $this->_objectManager = $objectManager; } public function index($index, $type, $id, $data) { try { $this->_es->add($index, $type, $id, $data); } catch (Horde_ElasticSearch_Exception $e) { throw new Content_Exception($e); } } public function search($index, $type, $query) { try { return $this->_es->search($index, $type, $query); } catch (Horde_ElasticSearch_Exception $e) { throw new Content_Exception($e); } } /** * Convenience method - if $object is an array, it is taken as an array of * 'object' and 'type' to pass to objectManager::ensureObjects() if it's a * scalar value, it's taken as the object_id and simply returned. */ protected function _ensureObject($object) { if (is_array($object)) { $object = current($this->_objectManager->ensureObjects( $object['object'], (int)current($this->_typeManager->ensureTypes($object['type'])))); } return (int)$object; } } content-2.0.5/lib/ObjectMapper.php0000664000175000017500000000112212611721515017267 0ustar mrubinskmrubinsk * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content */ /** * @author Chuck Hagenbuch * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content * * @todo Change name to Content_Objects_Mapper */ class Content_ObjectMapper extends Horde_Rdo_Mapper { /** * Inflector doesn't support Horde-style tables yet */ protected $_table = 'rampage_objects'; } content-2.0.5/lib/Tagger.php0000664000175000017500000011614612611721515016142 0ustar mrubinskmrubinsk * @author Michael J. Rubinsky * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Horde_Content * * References: * http://forge.mysql.com/wiki/TagSchema * http://www.slideshare.net/edbond/tagging-and-folksonomy-schema-design-for-scalability-and-performance * http://blog.thinkphp.de/archives/124-An-alternative-Approach-to-Tagging.html * http://code.google.com/p/freetag/ * * @TODO: * need to add type_id to the rampage_tagged table for performance? * need stat tables by type_id? * * Potential features: * Infer data from combined tags (capital + washington d.c. - http://www.slideshare.net/kakul/tagging-web-2-expo-2008/) * Normalize tag text (http://tagsonomy.com/index.php/interview-with-gordon-luk-freetag/) */ class Content_Tagger { /** * Database connection * @var Horde_Db_Adapter */ protected $_db; /** * Tables * @var array */ protected $_tables = array( 'tags' => 'rampage_tags', 'tagged' => 'rampage_tagged', 'objects' => 'rampage_objects', 'tag_stats' => 'rampage_tag_stats', 'user_tag_stats' => 'rampage_user_tag_stats', 'users' => 'rampage_users', 'types' => 'rampage_types', ); /** * User manager object * @var Content_Users_Manager */ protected $_userManager; /** * Type management object * @var Content_Types_Manager */ protected $_typeManager; /** * Object manager * @var Content_Objects_Manager */ protected $_objectManager; /** * Default radius for relationship queries. * @var integer */ protected $_defaultRadius = 10; /** * Constructor */ public function __construct(Horde_Db_Adapter $db, Content_Users_Manager $userManager, Content_Types_Manager $typeManager, Content_Objects_Manager $objectManager) { $this->_db = $db; $this->_userManager = $userManager; $this->_typeManager = $typeManager; $this->_objectManager = $objectManager; } /** * Adds a tag or several tags to an object_id. This method does not * remove other tags. * * @param mixed $userId The user tagging the object. * @param mixed $objectId The object id to tag or an array containing * the object_name and type. * @param array $tags An array of tag name or ids. * @param Horde_Date $created The datetime of the tagging operation. * * @return void */ public function tag($userId, $objectId, $tags, Horde_Date $created = null) { if (is_null($created)) { $created = date('Y-m-d H:i:s'); } else { $created = $created->format('Y-m-d H:i:s'); } // Make sure the object exists $objectId = $this->_ensureObject($objectId); // Validate/ensure the parameters $userId = current($this->_userManager->ensureUsers($userId)); foreach ($this->ensureTags($tags) as $tagId) { if (!$this->_db->selectValue('SELECT 1 from ' . $this->_t('tagged') . ' WHERE user_id = ? AND object_id = ? AND tag_id = ?', array((int)$userId, (int)$objectId, (int)$tagId))) { try { $this->_db->insert( 'INSERT INTO ' . $this->_t('tagged') . ' (user_id, object_id, tag_id, created) VALUES (?, ?, ?, ?)', array((int)$userId, (int)$objectId, (int)$tagId, $created)); } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } // increment tag stats if (!$this->_db->update('UPDATE ' . $this->_t('tag_stats') . ' SET count = count + 1 WHERE tag_id = ' . (int)$tagId)) { $this->_db->insert('INSERT INTO ' . $this->_t('tag_stats') . ' (tag_id, count) VALUES (' . (int)$tagId . ', 1)', null, null, 'tag_id', $tagId); } // increment user-tag stats if (!$this->_db->update('UPDATE ' . $this->_t('user_tag_stats') . ' SET count = count + 1 WHERE user_id = ' . (int)$userId . ' AND tag_id = ' . (int)$tagId)) { $this->_db->insert('INSERT INTO ' . $this->_t('user_tag_stats') . ' (user_id, tag_id, count) VALUES (' . (int)$userId . ', ' . (int)$tagId . ', 1)'); } } } } /** * Undo a user's tagging of an object. * * @param mixed $userId The user who tagged the object. * @param mixed $objectId The object to remove the tag from. * @param array $tags An array of tag name or ids to remove. */ public function untag($userId, $objectId, $tags) { // Ensure parameters $userId = current($this->_userManager->ensureUsers($userId)); $objectId = $this->_ensureObject($objectId); foreach ($this->ensureTags($tags) as $tagId) { if ($this->_db->delete('DELETE FROM ' . $this->_t('tagged') . ' WHERE user_id = ? AND object_id = ? AND tag_id = ?', array($userId, $objectId, $tagId))) { $this->_db->update('UPDATE ' . $this->_t('tag_stats') . ' SET count = count - 1 WHERE tag_id = ?', array($tagId)); $this->_db->update('UPDATE ' . $this->_t('user_tag_stats') . ' SET count = count - 1 WHERE user_id = ? AND tag_id = ?', array($userId, $tagId)); } } // Cleanup $this->_db->delete('DELETE FROM ' . $this->_t('tag_stats') . ' WHERE count = 0'); $this->_db->delete('DELETE FROM ' . $this->_t('user_tag_stats') . ' WHERE count = 0'); } /** * Remove all occurrences of a specific tag from an object regardless of * the username who tagged the object originally. * * @param mixed $obejctId The object identifier @see Content_Tagger::tag() * @param mixed $tags The tags to remove. @see Content_Tagger::tag() * * @return void */ public function removeTagFromObject($objectId, $tags) { $objectId = $this->_ensureObject($objectId); if (!is_array($tags)) { $tags = array($tags); } foreach ($this->ensureTags($tags) as $tagId) { // Get the users who have tagged this so we can update the stats $users = $this->_db->selectValues('SELECT user_id, tag_id FROM ' . $this->_t('tagged') . ' WHERE object_id = ? AND tag_id = ?', array($objectId, $tagId)); // Delete the tags if ($this->_db->delete('DELETE FROM ' . $this->_t('tagged') . ' WHERE object_id = ? AND tag_id = ?', array($objectId, $tagId))) { // Update the stats $this->_db->update('UPDATE ' . $this->_t('tag_stats') . ' SET count = count - ' . count($users) . ' WHERE tag_id = ?', array($tagId)); $this->_db->update('UPDATE ' . $this->_t('user_tag_stats') . ' SET count = count - 1 WHERE user_id IN(' . str_repeat('?, ', count($users) - 1) . '?) AND tag_id = ?', array_merge($users, array($tagId))); // Housekeeping $this->_db->delete('DELETE FROM ' . $this->_t('tag_stats') . ' WHERE count = 0'); $this->_db->delete('DELETE FROM ' . $this->_t('user_tag_stats') . ' WHERE count = 0'); } } } /** * Obtain all the tags for a given set of objects. * * @param mixed string|array $objects A local object id or an array of * local object id strings. * @param mixed $type Either a string type description, or * an integer content type_id * * @return array An array in the form of: *
     *      array('localobjectId' => array('tagone', 'tagtwo'),
     *            'anotherobjectid' => array('anothertag', 'yetanother'))
     * 
*/ public function getTagsByObjects($objects, $type) { $object_ids = $this->_objectManager->exists($objects, $type); $results = array(); if (!$object_ids) { foreach ($objects as $id) { $results[$id] = array(); } } else { $sql = 'SELECT DISTINCT tag_name, tagged.object_id FROM ' . $this->_t('tags') . ' t INNER JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id AND tagged.object_id IN (' . str_repeat('?,', count($object_ids) - 1) . '?)'; $tags = $this->_db->selectAll($sql, array_keys($object_ids)); foreach ($tags as $tag) { if (empty($results[$object_ids[$tag['object_id']]])) { $results[$object_ids[$tag['object_id']]] = array(); } $results[$object_ids[$tag['object_id']]][] = $tag['tag_name']; } } return $results; } /** * Retrieve tags based on criteria. * * @param array $args Search criteria: * q Starts-with search on tag_name. * limit Maximum number of tags to return. * offset Offset the results. Only useful for paginating, and not recommended. * userId Only return tags that have been applied by a specific user. * typeId Only return tags that have been applied by a specific object type. * objectId Only return tags that have been applied to a specific object. * * @return array An array of tags, id => name. */ public function getTags($args) { if (isset($args['objectId'])) { // Don't create the object just because we're trying to load an // objects's tags - just check if the object is there. Assume if we // have an integer, it's a valid object_id. if (is_array($args['objectId'])) { $args['objectId'] = $this->_objectManager->exists($args['objectId']['object'], $args['objectId']['type']); if ($args['objectId']) { $args['objectId'] = current(array_keys($args['objectId'])); } } if (!$args['objectId']) { return array(); } $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tags') . ' t INNER JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id AND tagged.object_id = ' . (int)$args['objectId']; } elseif (isset($args['userId']) && isset($args['typeId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tags') . ' t INNER JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id AND tagged.user_id = ' . (int)$args['userId'] . ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } elseif (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id WHERE tagged.user_id = ' . (int)$args['userId']; $haveWhere = true; } elseif (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId'] . ' INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id'; } elseif (isset($args['tagId'])) { $radius = isset($args['limit']) ? (int)$args['limit'] : $this->_defaultRadius; unset($args['limit']); $inner = $this->_db->addLimitOffset('SELECT object_id FROM ' . $this->_t('tagged') . ' WHERE tag_id = ' . (int)$args['tagId'], array('limit' => $radius)); $sql = $this->_db->addLimitOffset('SELECT DISTINCT tagged2.tag_id AS tag_id, tag_name FROM (' . $inner . ') tagged1 INNER JOIN ' . $this->_t('tagged') . ' tagged2 ON tagged1.object_id = tagged2.object_id INNER JOIN ' . $this->_t('tags') . ' t ON tagged2.tag_id = t.tag_id', array('limit' => $args['limit'])); } else { $sql = 'SELECT DISTINCT t.tag_id, tag_name FROM ' . $this->_t('tags') . ' t JOIN ' . $this->_t('tagged') . ' tagged ON t.tag_id = tagged.tag_id'; } if (isset($args['q']) && strlen($args['q'])) { // @TODO tossing a where clause in won't work with all query modes $sql .= (!empty($haveWhere) ? ' AND' : ' WHERE') . ' tag_name LIKE ' . $this->_db->quoteString($args['q'] . '%'); } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAssoc($sql); } /** * Generate a tag cloud. Same syntax as getTags, except that fetching a * cloud for a userId + objectId combination doesn't make sense - the counts * would all be one. In addition, this method returns counts for each tag. * * @param array $args Search criteria: * - limit: (integer) Maximum number of tags to return. * - offset: (integet) Offset the results. Only useful for paginating, and * not recommended. * - userId: (string) Only return tags that have been applied by a * specific user. * - typeId: (array) Only return tags that have been applied by specific * object types. * - objectId: (array) Only return tags that have been applied to specific * objects all objects must be of the same type and * specified by typeId. * - tagIds: (array) Only return information on specific tag * (an array of tag ids). * * @return array An array of hashes, each containing tag_id, tag_name, and count. */ public function getTagCloud($args = array()) { if (isset($args['objectId'])) { if (empty($args['typeId'])) { throw new Content_Exception('Specified objectId, but failed to specify typeId for getTagCloud call.'); } $args['objectId'] = $this->_objectManager->ensureObjects($args['objectId'], $args['typeId']); $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id WHERE tagged.object_id IN (' . implode(',', $args['objectId']) . ') GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['userId']) && isset($args['typeId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); // This doesn't use a stat table, so may be slow. $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id IN (' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id WHERE tagged.user_id = ' . (int)$args['userId'] . ' GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql = 'SELECT t.tag_id AS tag_id, tag_name, count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('user_tag_stats') . ' uts ON t.tag_id = uts.tag_id AND uts.user_id = ' . (int)$args['userId'] . ' GROUP BY t.tag_id, tag_name, count'; } elseif (isset($args['tagIds']) && isset($args['typeId'])) { $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); // This doesn't use a stat table, so may be slow. $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id IN(' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id AND t.tag_id IN (' . implode(', ', $args['tagIds']) . ') GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['typeId'])) { $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); // This doesn't use a stat table, so may be slow. $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id IN(' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id GROUP BY t.tag_id, t.tag_name'; } elseif (isset($args['tagIds'])) { $ids = $this->_checkTags($args['tagIds'], false); $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' ts ON t.tag_id = ts.tag_id WHERE t.tag_id IN (' . implode(', ', $ids) . ') GROUP BY t.tag_id, t.tag_name'; } else { $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' ts ON t.tag_id = ts.tag_id GROUP BY t.tag_id, t.tag_name'; } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql . ' ORDER BY count DESC', array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } try { $rows = $this->_db->selectAll($sql); $results = array(); foreach ($rows as $row) { $results[$row['tag_id']] = $row; } return $results; } catch (Exception $e) { throw new Content_Exception($e); } } /** * Get the most recently used tags. * * @param array $args Search criteria: * limit Maximum number of tags to return. * offset Offset the results. Only useful for paginating, and not recommended. * userId Only return tags that have been used by a specific user. * typeId Only return tags applied to objects of a specific type. * * @return array */ public function getRecentTags($args = array()) { $sql = 'SELECT tagged.tag_id AS tag_id, tag_name, MAX(created) AS created FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('tags') . ' t ON tagged.tag_id = t.tag_id'; if (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } if (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql .= ' WHERE tagged.user_id = ' . (int)$args['userId']; } $sql .= ' GROUP BY tagged.tag_id, tag_name ORDER BY created DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAll($sql); } /** * Get objects matching search criteria. * * @param array $args Search criteria: * limit Maximum number of objects to return. * offset Offset the results. Only useful for paginating, and not recommended. * tagId Return objects related through one or more tags. * notTagId Don't return objects tagged with one or more tags. * typeId Only return objects with a specific type. * objectId Return objects with the same tags as $objectId. * userId Limit results to objects tagged by a specific user. * radius Radius setting for relationship queries e.g., objectId * * @return array An array of object ids. */ public function getObjects($args) { if (isset($args['objectId'])) { if (is_array($args['objectId'])) { $args['objectId'] = current($this->_objectManager->ensureObjects( $args['objectId']['object'], $args['objectId']['type'])); } $radius = isset($args['radius']) ? (int)$args['radius'] : $this->_defaultRadius; $inner = $this->_db->addLimitOffset( 'SELECT tag_id, object_id FROM ' . $this->_t('tagged') . ' WHERE object_id = ' . (int)$args['objectId'], array('limit' => $radius)); $sql = 'SELECT t2.object_id AS object_id, object_name FROM (' . $inner . ') t1 INNER JOIN ' . $this->_t('tagged') . ' t2 ON t1.tag_id = t2.tag_id INNER JOIN ' . $this->_t('objects') . ' objects ON objects.object_id = t2.object_id WHERE t2.object_id != ' . (int)$args['objectId'] . ' GROUP BY t2.object_id, object_name'; if (!empty($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, $args['limit']); } } elseif (isset($args['tagId'])) { $tags = is_array($args['tagId']) ? array_values($args['tagId']) : array($args['tagId']); $count = count($tags); if (!$count) { return array(); } $notTags = isset($args['notTagId']) ? (is_array($args['notTagId']) ? array_values($args['notTagId']) : array($args['notTagId'])) : array(); $notCount = count($notTags); $sql = 'SELECT DISTINCT tagged.object_id AS object_id, object_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('objects') . ' objects ON objects.object_id = tagged.object_id'; if (!empty($args['typeId'])) { $args['typeId'] = $this->_typeManager->ensureTypes($args['typeId']); } if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' INNER JOIN ' . $this->_t('tagged') . ' tagged' . $i . ' ON tagged.object_id = tagged' . $i . '.object_id'; } } if ($notCount) { // Left joins for tags we want to exclude. for ($j = 0; $j < $notCount; $j++) { $sql .= ' LEFT JOIN ' . $this->_t('tagged') . ' not_tagged' . $j . ' ON tagged.object_id = not_tagged' . $j . '.object_id AND not_tagged' . $j . '.tag_id = ' . (int)$notTags[$j]; } } $sql .= ' WHERE tagged.tag_id = ' . (int)$tags[0]; if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' AND tagged' . $i . '.tag_id = ' . (int)$tags[$i]; } } if ($notCount) { for ($j = 0; $j < $notCount; $j++) { $sql .= ' AND not_tagged' . $j . '.object_id IS NULL'; } } if (!empty($args['typeId']) && count($args['typeId'])) { $sql .= ' AND objects.type_id IN (' . implode(', ', $args['typeId']) . ')'; } if (array_key_exists('userId', $args)) { $args['userId'] = $this->_userManager->ensureUsers($args['userId']); $sql .= ' AND tagged.user_id IN (' . implode(', ', $args['userId']) . ')'; } } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAssoc($sql); } /** * Return objects related to the given object via tags, along with a * similarity rank. * * @param mixed $object_id The object to find relations for. * @param array $args * limit Maximum number of objects to return (default 10). * userId Only return objects that have been tagged by a specific user. * typeId Only return objects of a specific type. * threshold Number of tags-in-common objects must have to match (default 1). * * @return array An array of (client) object ids => similarity rank */ public function getSimilarObjects($object_id, array $args = array()) { $defaults = array('limit' => 10, 'threshold' => 1); $args = array_merge($defaults, $args); if (is_array($object_id)) { $object_id = $this->_objectManager->exists($object_id['object'], $object_id['type']); if (!$object_id) { return array(); } $object_id = current(array_keys($object_id)); } elseif (!is_int($object_id)) { throw new InvalidArgumentException(_("Missing or invalid object type parameter.")); } $threshold = intval($args['threshold']); $max_objects = intval($args['limit']); if (!isset($object_id) || !($object_id > 0)) { return array(); } if ($threshold <= 0 || $max_objects <= 0) { return array(); } /* Get the object's tags */ $tagObjects = $this->getTags(array('objectId' => $object_id)); $tagArray = array_keys($tagObjects); $numTags = count($tagArray); if ($numTags == 0) { return array(); // Return empty set of matches } $sql = 'SELECT objects.object_name, COUNT(matches.object_id) AS num_common_tags FROM ' . $this->_t('tagged') . ' matches INNER JOIN ' . $this->_t('tags') . ' tags ON (tags.tag_id = matches.tag_id)'; if (!empty($args['userId'])) { $sql .= ' INNER JOIN ' . $this->_t('users') . ' users ON users.user_id = matches.user_id AND users.user_name = ' . $this->_db->quoteString($args['userId']); } $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON objects.object_id = matches.object_id'; if (!empty($args['typeId'])) { $sql .= ' INNER JOIN ' . $this->_t('types') . ' types ON types.type_id=objects.type_id AND types.type_name = ' . $this->_db->quoteString($args['typeId']); } $sql .= ' WHERE tags.tag_id IN (' . implode(',', $tagArray) . ') AND matches.object_id <> ' . (int)$object_id .' GROUP BY objects.object_name HAVING COUNT(matches.object_id) >= ' . $threshold . ' ORDER BY num_common_tags DESC'; $sql = $this->_db->addLimitOffset($sql, array('limit' => $max_objects)); try { return $this->_db->selectAssoc($sql); } catch (Horde_Db_Exception $e) { throw new Content_Exception($e); } } /** * Get the most recently tagged objects. * * @param array $args Search criteria: * limit Maximum number of objects to return. * offset Offset the results. Only useful for paginating, and not recommended. * userId Only return objects that have been tagged by a specific user. * typeId Only return objects of a specific object type. * * @return array */ public function getRecentObjects($args = array()) { $sql = 'SELECT tagged.object_id AS object_id, MAX(created) AS created FROM ' . $this->_t('tagged') . ' tagged'; if (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } if (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $sql .= ' WHERE tagged.user_id = ' . (int)$args['userId']; } $sql .= ' GROUP BY tagged.object_id ORDER BY created DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAll($sql); } /** * Find users through objects, tags, or other users. */ public function getUsers($args) { if (isset($args['objectId'])) { $args['objectId'] = $this->_ensureObject($args['objectId']); $sql = 'SELECT t.user_id, user_name FROM ' . $this->_t('tagged') . ' t INNER JOIN ' . $this->_t('users') . ' u ON t.user_id = u.user_id WHERE object_id = ' . (int)$args['objectId']; } elseif (isset($args['userId'])) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $radius = isset($args['radius']) ? (int)$args['radius'] : $this->_defaultRadius; $sql = 'SELECT others.user_id, user_name FROM ' . $this->_t('tagged') . ' others INNER JOIN ' . $this->_t('users') . ' u ON u.user_id = others.user_id INNER JOIN (SELECT tag_id FROM ' . $this->_t('tagged') . ' WHERE user_id = ' . (int)$args['userId'] . ' GROUP BY tag_id HAVING COUNT(tag_id) >= ' . $radius . ') self ON others.tag_id = self.tag_id GROUP BY others.user_id'; } elseif (isset($args['tagId'])) { $tags = $this->ensureTags($args['tagId']); //$tags = is_array($args['tagId']) ? array_values($args['tagId']) : array($args['tagId']); $count = count($tags); if (!$count) { return array(); } $notTags = isset($args['notTagId']) ? (is_array($args['notTagId']) ? array_values($args['notTagId']) : array($args['notTagId'])) : array(); $notCount = count($notTags); $sql = 'SELECT DISTINCT tagged.user_id, user_name FROM ' . $this->_t('tagged') . ' tagged INNER JOIN ' . $this->_t('users') . ' u ON u.user_id = tagged.user_id '; if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' INNER JOIN ' . $this->_t('tagged') . ' tagged' . $i . ' ON tagged.user_id = tagged' . $i . '.user_id'; } } if ($notCount) { // Left joins for tags we want to exclude. for ($j = 0; $j < $notCount; $j++) { $sql .= ' LEFT JOIN ' . $this->_t('tagged') . ' not_tagged' . $j . ' ON tagged.user_id = not_tagged' . $j . '.user_id AND not_tagged' . $j . '.tag_id = ' . (int)$notTags[$j]; } } $sql .= ' WHERE tagged.tag_id = ' . (int)$tags[0]; if ($count > 1) { for ($i = 1; $i < $count; $i++) { $sql .= ' AND tagged' . $i . '.tag_id = ' . (int)$tags[$i]; } } if ($notCount) { for ($j = 0; $j < $notCount; $j++) { $sql .= ' AND not_tagged' . $j . '.user_id IS NULL'; } } } if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAssoc($sql); } /** * Get the users who have most recently tagged objects. * * @param array $args Search criteria: * limit Maximum number of users to return. * offset Offset the results. Only useful for paginating, and not recommended. * typeId Only return users who have tagged objects of a specific object type. * * @return array */ public function getRecentUsers($args = array()) { $sql = 'SELECT tagged.user_id AS user_id, MAX(created) AS created FROM ' . $this->_t('tagged') . ' tagged'; if (isset($args['typeId'])) { $args['typeId'] = current($this->_typeManager->ensureTypes($args['typeId'])); $sql .= ' INNER JOIN ' . $this->_t('objects') . ' objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId']; } $sql .= ' GROUP BY tagged.user_id ORDER BY created DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0)); } return $this->_db->selectAll($sql); } /** * Return users related to a given user along with a similarity rank. */ public function getSimilarUsers($args) { $args['userId'] = current($this->_userManager->ensureUsers($args['userId'])); $radius = isset($args['radius']) ? (int)$args['radius'] : $this->_defaultRadius; $sql = 'SELECT others.user_id, (others.count - self.count) AS rank FROM ' . $this->_t('user_tag_stats') . ' others INNER JOIN (SELECT tag_id, count FROM ' . $this->_t('user_tag_stats') . ' WHERE user_id = ' . (int)$args['userId'] . ' AND count >= ' . $radius . ') self ON others.tag_id = self.tag_id ORDER BY rank DESC'; if (isset($args['limit'])) { $sql = $this->_db->addLimitOffset($sql, array('limit' => $args['limit'])); } return $this->_db->selectAssoc($sql); } /** * Check if tags exists, optionally create them if they don't and return ids * for all that exist (including those that are optionally created). * * @param string|array $tags The tag names to check. * @param boolean $create If true, create the tag in the tags table. * * @return array A hash of tag_name => tag_id values. */ protected function _checkTags($tags, $create = true) { if (empty($tags)) { return array(); } if (!is_array($tags)) { $tags = is_int($tags) ? array($tags) : $this->splitTags($tags); } $tagIds = array(); // Anything already typed as an integer is assumed to be a tag id. foreach ($tags as $tag) { if (is_int($tag)) { $tagIds[$tag] = $tag; continue; } // Don't attempt to tag with an empty value if (!strlen(trim($tag))) { continue; } // Get the ids for any tags that already exist. $sql = 'SELECT tag_id FROM ' . $this->_t('tags') . ' WHERE LOWER(tag_name) = LOWER(' . $this->toDriver($tag) . ')'; if ($id = $this->_db->selectValue($sql)) { $tagIds[$tag] = (int)$id; } elseif ($create) { // Create any tags that didn't already exist $tagIds[$tag] = (int)$this->_db->insert('INSERT INTO ' . $this->_t('tags') . ' (tag_name) VALUES (' . $this->toDriver($tag) . ')'); } } return $tagIds; } /** * Ensure that an array of tags exist, create any that don't, and * return ids for all of them. * * @param array $tags Array of tag names or ids. * * @return array Hash of tag_name => tag_id values. */ public function ensureTags($tags) { return $this->_checkTags($tags); } /** * */ public function getTagIds($tags) { return $this->_checkTags($tags, false); } /** * Split a string into an array of tag names, respecting tags with spaces * and ones that are quoted in some way. For example: * this, "somecompany, llc", "and ""this"" w,o.rks", foo bar * * Would parse to: * array('this', 'somecompany, llc', 'and "this" w,o.rks', 'foo bar') * * @param string $text String to split into 1 or more tags. * * @return array Split tag array. */ public function splitTags($text) { // From http://drupal.org/project/community_tags $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; preg_match_all($regexp, $text, $matches); $tags = array(); foreach (array_unique($matches[1]) as $tag) { // Remove escape codes $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag))); if (strlen($tag)) { $tags[] = $tag; } } return $tags; } /** * Retrieve a set of tags with relationships to the specified set * of tags. * * @param array $ids An array of tag_ids. * @param integer $object The object type to limit to. * @param string $user The user to limit to. * * @return array A hash of tag_id -> tag_name */ public function browseTags($ids, $object_type, $user) { if (!count($ids)) { return array(); } $sql = 'SELECT DISTINCT t.tag_id, t.tag_name FROM ' . $this->_t('tagged') . ' as r, ' . $this->_t('objects') . ' as i, ' . $this->_t('tags') . ' as t'; for ($i = 0; $i < count($ids); $i++) { $sql .= ',' . $this->_t('tagged') . ' as r' . $i; } $sql .= ' WHERE r.tag_id = t.tag_id AND r.object_id = i.object_id'; for ($i = 0; $i < count($ids); $i++) { $sql .= ' AND r' . $i . '.object_id = r.object_id AND r.tag_id != ' . (int)$ids[$i] . ' AND r' . $i . '.tag_id = ' . (int)$ids[$i]; } /* Note that we don't convertCharset here, it's done in listTagInfo */ $tags = $this->_db->selectAssoc($sql); foreach ($tags as $key => &$value) { $value = Horde_String::convertCharset($value, $this->_db->getOption('charset'), 'UTF-8'); } return $tags; } /** * Convenience method - if $object is an array, it is taken as an array of * 'object' and 'type' to pass to objectManager::ensureObjects() if it's a * scalar value, it's taken as the object_id and simply returned. */ protected function _ensureObject($object) { if (is_array($object)) { $object = current($this->_objectManager->ensureObjects( $object['object'], (int)current($this->_typeManager->ensureTypes($object['type'])))); } return (int)$object; } /** * Shortcut for getting a table name. * * @param string $tableType * * @return string Configured table name. */ protected function _t($tableType) { return $this->_db->quoteTableName($this->_tables[$tableType]); } public function toDriver($value) { return $this->_db->quoteString(Horde_String::convertCharset($value, 'UTF-8', $this->_db->getOption('charset'))); } } content-2.0.5/migration/1_rampage_base_tables.php0000664000175000017500000000327112611721515022326 0ustar mrubinskmrubinsktables(); if (!in_array('rampage_types', $tableList)) { // rampage_types $t = $this->createTable('rampage_types', array('autoincrementKey' => 'type_id')); $t->column('type_name', 'string', array('limit' => 255, 'null' => false)); $t->end(); $this->addIndex('rampage_types', array('type_name'), array('name' => 'rampage_objects_type_name', 'unique' => true)); } if (!in_array('rampage_objects', $tableList)) { // rampage_objects $t = $this->createTable('rampage_objects', array('autoincrementKey' => 'object_id')); $t->column('object_name', 'string', array('limit' => 255, 'null' => false)); $t->column('type_id', 'integer', array('null' => false, 'unsigned' => true)); $t->end(); $this->addIndex('rampage_objects', array('type_id', 'object_name'), array('name' => 'rampage_objects_type_object_name', 'unique' => true)); } if (!in_array('rampage_users', $tableList)) { // rampage_users $t = $this->createTable('rampage_users', array('autoincrementKey' => 'user_id')); $t->column('user_name', 'string', array('limit' => 255, 'null' => false)); $t->end(); $this->addIndex('rampage_users', array('user_name'), array('name' => 'rampage_users_user_name', 'unique' => true)); } } public function down() { $this->dropTable('rampage_types'); $this->dropTable('rampage_objects'); $this->dropTable('rampage_users'); } } content-2.0.5/migration/2_rampage_tag_tables.php0000664000175000017500000000536112611721515022172 0ustar mrubinskmrubinsktables(); if (!in_array('rampage_tags', $tableList)) { // rampage_tags $t = $this->createTable('rampage_tags', array('autoincrementKey' => 'tag_id')); $t->column('tag_name', 'string', array('limit' => 255, 'null' => false)); $t->end(); $this->addIndex('rampage_tags', array('tag_name'), array('name' => 'rampage_tags_tag_name', 'unique' => true)); } if (!in_array('rampage_tagged', $tableList)) { // rampage_tagged $t = $this->createTable('rampage_tagged', array('autoincrementKey' => false)); $t->column('user_id', 'integer', array('null' => false, 'unsigned' => true)); $t->column('object_id', 'integer', array('null' => false, 'unsigned' => true)); $t->column('tag_id', 'integer', array('null' => false, 'unsigned' => true)); $t->column('created', 'datetime'); $t->primaryKey(array('user_id', 'object_id', 'tag_id')); $t->end(); $this->addIndex('rampage_tagged', array('object_id'), array('name' => 'rampage_tagged_object_id')); $this->addIndex('rampage_tagged', array('tag_id'), array('name' => 'rampage_tagged_tag_id')); $this->addIndex('rampage_tagged', array('created'), array('name' => 'rampage_tagged_created')); } if (!in_array('rampage_tag_stats', $tableList)) { // rampage_tag_stats $t = $this->createTable('rampage_tag_stats', array('autoincrementKey' => false)); $t->column('tag_id', 'integer', array('null' => false, 'unsigned' => true)); $t->column('count', 'integer', array('unsigned' => true)); $t->primaryKey(array('tag_id')); $t->end(); } if (!in_array('rampage_user_tag_stats', $tableList)) { // rampage_user_tag_stats $t = $this->createTable('rampage_user_tag_stats', array('autoincrementKey' => false)); $t->column('user_id', 'integer', array('null' => false, 'unsigned' => true)); $t->column('tag_id', 'integer', array('null' => false, 'unsigned' => true)); $t->column('count', 'integer', array('unsigned' => true)); $t->primaryKey(array('user_id', 'tag_id')); $t->end(); $this->addIndex('rampage_user_tag_stats', array('tag_id'), array('name' => 'rampage_user_tag_stats_tag_id')); } } public function down() { $this->dropTable('rampage_tags'); $this->dropTable('rampage_tagged'); $this->dropTable('rampage_tag_stats'); $this->dropTable('rampage_user_tag_stats'); } } content-2.0.5/test/Content/Sql/Pdo/MysqlTest.php0000664000175000017500000000136312611721515021774 0ustar mrubinskmrubinsk * @category Horde * @package Content * @subpackage UnitTests * @license http://www.horde.org/licenses/bsd BSD */ class Content_Sql_Pdo_SqliteTest extends Content_Test_Sql_Base { public static function setUpBeforeClass() { $factory_db = new Horde_Test_Factory_Db(); try { self::$db = $factory_db->create(); parent::setUpBeforeClass(); } catch (Horde_Test_Exception $e) { self::$reason = 'Sqlite not available'; } } }content-2.0.5/test/Content/Sql/Base.php0000664000175000017500000001041612611721515020156 0ustar mrubinskmrubinsk_create(); $objects = self::$db->selectAll('SELECT * FROM rampage_objects'); $this->assertEquals(4, count($objects)); // If these aren't strings, then ids were taken as names. foreach ($objects as $object) { $this->assertInternalType('string', $object['object_name']); } $types = self::$db->selectAll('SELECT * FROM rampage_types'); $this->assertEquals(2, count($types)); foreach ($types as $type) { $this->assertInternalType('string', $type['type_name']); } } /** * @depends testCreate */ public function testEnsureTags() { $this->_testEnsureTags(); } public function testEnsureTypes() { $this->_testEnsureTypes(); } /** * @depends testCreate */ public function testFullTagCloudSimple() { $this->_testFullTagCloudSimple(); } /** * @depends testCreate */ public function testTagCloudByType() { $this->_testTagCloudByType(); } /** * @depends testCreate */ public function testTagCloudByUser() { $this->_testTagCloudByUser(); } /** * @depends testCreate */ public function testTagCloudByUserType() { $this->_testTagCloudByUserType(); } /** * @depends testCreate */ public function testTagCloudByTagType() { $this->_testTagCloudByTagType(); } /** * @depends testCreate */ public function testTagCloudByTagIds() { $this->_testTagCloudByTagIds(); } /** * @depends testCreate */ public function testGetRecentTags() { $this->_testGetRecentTags(); } /** * @depends testCreate */ public function testGetRecentTagsByUser() { $this->_testGetRecentTagsByUser(); } /** * @depends testCreate */ public function testGetRecentObjects() { $this->_testGetRecentObjects(); } /** * @depends testCreate */ public function testGetRecentTagsByType() { $this->_testGetRecentTagsByType(); } /** * @depends testCreate */ public function testGetRecentObjectsByUser() { $this->_testGetRecentObjectsByUser(); } /** * @depends testCreate */ public function testGetRecentObjectsByType() { $this->_testGetRecentObjectsByType(); } /** * * @depends testCreate */ public function testGetRecentUsers() { $this->_testGetRecentUsers(); } /** * @depends testCreate */ public function testGetRecentUsersByType() { $this->_testGetRecentUsersByType(); } /** * @depends testCreate */ public function testUntag() { $this->_testUntag(); } public static function setUpBeforeClass() { self::$injector = new Horde_Injector(new Horde_Injector_TopLevel()); self::$injector->setInstance('Horde_Db_Adapter', self::$db); // FIXME: get migration directory if not running from Git checkout. self::$migrator = new Horde_Db_Migration_Migrator( self::$db, null, //$logger, array('migrationsPath' => __DIR__ . '/../../../migration', 'schemaTableName' => 'content_test_schema')); self::$migrator->up(); self::$tagger = self::$injector->getInstance('Content_Tagger'); self::$type_mgr = self::$injector->createInstance('Content_Types_Manager'); } public static function tearDownAfterClass() { if (self::$migrator) { self::$migrator->down(); } self::$db = null; parent::tearDownAfterClass(); } public function setUp() { if (!self::$db) { $this->markTestSkipped(self::$reason); } } }content-2.0.5/test/Content/Sql/MysqliTest.php0000664000175000017500000000160012611721515021415 0ustar mrubinskmrubinsk * @category Horde * @package Content * @subpackage UnitTests * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 */ class Content_Sql_MysqliTest extends Content_Test_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('mysqli')) { self::$reason = 'No mysqli extension'; return; } $config = self::getConfig('CONTENT_SQL_MYSQLI_TEST_CONFIG', __DIR__ . '/..'); if ($config && !empty($config['content']['sql']['mysqli'])) { self::$db = new Horde_Db_Adapter_Mysqli($config['content']['sql']['mysqli']); parent::setUpBeforeClass(); } } } content-2.0.5/test/Content/Sql/MysqlTest.php0000664000175000017500000000157112611721515021253 0ustar mrubinskmrubinsk * @category Horde * @package Content * @subpackage UnitTests * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 */ class Content_Sql_MysqlTest extends Content_Test_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('mysql')) { self::$reason = 'No mysql extension'; return; } $config = self::getConfig('CONTENT_SQL_MYSQL_TEST_CONFIG', __DIR__ . '/..'); if ($config && !empty($config['content']['sql']['mysql'])) { self::$db = new Horde_Db_Adapter_Mysql($config['content']['sql']['mysql']); parent::setUpBeforeClass(); } } } content-2.0.5/test/Content/Sql/Oci8Test.php0000664000175000017500000000156212611721515020750 0ustar mrubinskmrubinsk * @category Horde * @package Content * @subpackage UnitTests * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 */ class Content_Sql_Oci8Test extends Content_Test_Sql_Base { public static function setUpBeforeClass() { if (!extension_loaded('oci8')) { self::$reason = 'No oci8 extension'; return; } $config = self::getConfig('CONTENT_SQL_OCI8_TEST_CONFIG', __DIR__ . '/..'); if ($config && !empty($config['content']['sql']['oci8'])) { self::$db = new Horde_Db_Adapter_Oci8($config['content']['sql']['oci8']); parent::setUpBeforeClass(); } } } content-2.0.5/test/Content/AllTests.php0000664000175000017500000000013212611721515020272 0ustar mrubinskmrubinskrun(); content-2.0.5/test/Content/Autoload.php0000664000175000017500000000112412611721515020311 0ustar mrubinskmrubinsk * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 * @link http://pear.horde.org/index.php?package=Content */ Horde_Test_Autoload::addPrefix('Content', __DIR__ . '/../../lib'); content-2.0.5/test/Content/Base.php0000664000175000017500000003343412611721515017424 0ustar mrubinskmrubinsk * @author Chuck Hagenbuch * @category Horde * @package Content * @subpackage UnitTests */ class Content_Test_Base extends Horde_Test_Case { /** * @static Content_Tagger */ static $tagger; static $type_mgr; /** * Primes the fixture, and tests basic tagging functionality where all * bits of data are new (user, type, object, tag).. * */ protected function _create() { $this->_testEmpty(); // user alice tags an event named 'party' with the tag 'personal' and // an event named 'anniversary' with the tag 'personal' self::$tagger->tag('alice', array('type' => 'event', 'object' => 'party'), 'play', new Horde_Date('2008-01-01T00:10:00')); // user alice tags an event named 'office hours' with the tag 'work' self::$tagger->tag('alice', array('type' => 'event', 'object' => 'office hours'), 'work', new Horde_Date('2008-01-01T00:05:00')); // user bob tags a blog named 'daring fireball' with the tag 'apple' self::$tagger->tag('bob', array('type' => 'blog', 'object' => 'daring fireball'), 'apple', new Horde_Date('2008-01-01T00:20:00')); // Two users have tagged the same object, with the same tag self::$tagger->tag('alice', array('type' => 'event', 'object' => 'anniversary'), 'personal', new Horde_Date('2009-01-01T00:05:00')); self::$tagger->tag('bob', array('type' => 'event', 'object' => 'anniversary'), 'personal', new Horde_Date('2009-01-01T00:06:00')); } protected function _testEmpty() { // Basic check that no data exists. $this->assertEmpty(self::$tagger->getTags(array())); $this->assertEmpty(self::$tagger->getRecentTags()); $this->assertEmpty(self::$tagger->getRecentObjects()); } /** * Test types * */ protected function _testEnsureTypes() { $this->assertEquals(array(0 => 1, 1 => 2), self::$type_mgr->ensureTypes(array('event', 'blog'))); $this->assertEquals(array(0 => 2, 1 => 1), self::$type_mgr->ensureTypes(array('blog', 'event'))); $this->assertEquals(array(0 => 3, 1 => 2), self::$type_mgr->ensureTypes(array('foo', 'blog'))); } /** * Test ensureTags. * * 1 => play * 2 => work * 3 => apple * 4 => personal */ protected function _testEnsureTags() { // Test passing tag_ids to ensureTags $this->assertEquals(array(1 => 1), self::$tagger->ensureTags(1)); $this->assertEquals(array(1 => 1), self::$tagger->ensureTags(array(1))); $this->assertEquals(array(1 => 1, 2 => 2), self::$tagger->ensureTags(array(1, 2))); // Test passing tag names $this->assertEquals(array('work' => 2), self::$tagger->ensureTags('work')); $this->assertEquals(array('work' => 2), self::$tagger->ensureTags(array('work'))); $this->assertEquals(array('work' => 2, 'play' => 1), self::$tagger->ensureTags(array('work', 'play'))); // Test mixed $this->assertEquals(array(1 => 1, 'play' => 1), self::$tagger->ensureTags(array(1, 'play'))); $this->assertEquals(array(2 => 2, 'work' => 2), self::$tagger->ensureTags(array('work', 2))); $this->assertEquals(array(1 => 1, 'work' => 2), self::$tagger->ensureTags(array(1, 'work'))); } protected function _testFullTagCloudSimple() { $expected = array( '1' => array( 'tag_id' => 1, 'tag_name' => 'play', 'count' => 1 ), '2' => array( 'tag_id' => 2, 'tag_name' => 'work', 'count' => 1 ), '3' => array( 'tag_id' => 3, 'tag_name' => 'apple', 'count' => 1 ), '4' => array( 'tag_id' => 4, 'tag_name' => 'personal', 'count' => 2 ) ); $cloud = self::$tagger->getTagCloud(); $this->assertEquals($expected, $cloud); } protected function _testTagCloudByType() { $expected = array( '3' => array( 'tag_id' => 3, 'tag_name' => 'apple', 'count' => 1 ) ); $cloud = self::$tagger->getTagCloud(array('typeId' => 'blog')); $this->assertEquals($expected, $cloud); } protected function _testTagCloudByUser() { $expected = array( '3' => array( 'tag_id' => 3, 'tag_name' => 'apple', 'count' => 1 ), '4' => array( 'tag_id' => 4, 'tag_name' => 'personal', 'count' => 1 ) ); $cloud = self::$tagger->getTagCloud(array('userId' => 'bob')); $this->assertEquals($expected, $cloud); } protected function _testTagCloudByUserType() { $expected = array( '1' => array( 'tag_id' => 1, 'tag_name' => 'play', 'count' => 1 ), '2' => array( 'tag_id' => 2, 'tag_name' => 'work', 'count' => 1 ), '4' => array( 'tag_id' => 4, 'tag_name' => 'personal', 'count' => 1 ) ); $cloud = self::$tagger->getTagCloud(array('userId' => 'alice', 'typeId' => 'event')); $this->assertEquals($expected, $cloud); } protected function _testTagCloudByTagType() { $expected = array( '2' => array( 'tag_id' => 2, 'tag_name' => 'work', 'count' => 1 ) ); $cloud = self::$tagger->getTagCloud(array('tagIds' => array(2), 'typeId' => 'event')); $this->assertEquals($expected, $cloud); } protected function _testTagCloudByTagIds() { $expected = array( '2' => array( 'tag_id' => 2, 'tag_name' => 'work', 'count' => 1 ), '4' => array( 'tag_id' => 4, 'tag_name' => 'personal', 'count' => 2 ) ); $cloud = self::$tagger->getTagCloud(array('tagIds' => array(2, 4))); $this->assertEquals($expected, $cloud); } protected function _testGetRecentTags() { $recent = self::$tagger->getRecentTags(); $this->assertEquals(4, count($recent)); $this->assertEquals(4, $recent[0]['tag_id']); $this->assertEquals('personal', $recent[0]['tag_name']); $date = new Horde_Date($recent[0]['created']); $this->assertEquals(1230764760, $date->timestamp()); } protected function _testGetRecentTagsByUser() { $recent = self::$tagger->getRecentTags(array('userId' => 1)); $this->assertEquals(3, count($recent)); $recent = self::$tagger->getRecentTags(array('userId' => 2)); $this->assertEquals(2, count($recent)); $recent = self::$tagger->getRecentTags(array('userId' => 'alice')); $this->assertEquals(3, count($recent)); } protected function _testGetRecentTagsByType() { $recent = self::$tagger->getRecentTags(array('typeId' => 'event')); $this->assertEquals(3, count($recent)); } protected function _testGetRecentObjects() { $recent = self::$tagger->getRecentObjects(); $this->assertEquals(4, count($recent)); $this->assertEquals(4, $recent[0]['object_id']); $date = new Horde_Date($recent[0]['created'], 'UTC'); $this->assertEquals(1230764760, $date->timestamp()); } protected function _testUntag() { self::$tagger->untag('alice', array('type' => 'event', 'object' => 'party'), 'play'); $count = self::$tagger->getRecentTags(); $this->assertEquals(3, count($count)); //readd self::$tagger->tag('alice', array('type' => 'event', 'object' => 'party'), 'play', new Horde_Date('2008-01-01T00:10:00')); $count = self::$tagger->getRecentTags(); $this->assertEquals(4, count($count)); } /** * @TODO: SHould validate the values too, not just the count. */ protected function _testGetRecentObjectsByUser() { // alice has 3 recent objects $recent = self::$tagger->getRecentObjects(array('userId' => 'alice')); $this->assertEquals(3, count($recent)); // bob has 2 $recent = self::$tagger->getRecentObjects(array('userId' => 'bob')); $this->assertEquals(2, count($recent)); // just for kicks, test using the user id, not name. $recent = self::$tagger->getRecentObjects(array('userId' => 1)); $this->assertEquals(3, count($recent)); } protected function _testGetRecentObjectsByType() { $recent = self::$tagger->getRecentObjects(array('typeId' => 1)); $this->assertEquals(3, count($recent)); $recent = self::$tagger->getRecentObjects(array('typeId' => 2)); $this->assertEquals(1, count($recent)); } protected function _testGetRecentUsers() { $recent = self::$tagger->getRecentUsers(); $this->assertEquals(2, count($recent)); } protected function _testGetRecentUsersByType() { $recent = self::$tagger->getRecentUsers(array('typeId' => 1)); $this->assertEquals(2, count($recent)); $recent = self::$tagger->getRecentUsers(array('typeId' => 2)); $this->assertEquals(1, count($recent)); } /** * Test obtaining objects that are tagged with the same tags as the provided * object. * * See Bug: 10439 */ public function testGetObjectsByObjectId() { self::$tagger->tag('mike', array('type' => 'event', 'object' => 'irene'), 'hurricane', new Horde_Date('2011-08-28T00:01:00')); self::$tagger->tag('mike', array('type' => 'event', 'object' => 'floyd'), 'hurricane', new Horde_Date('1999-09-07T00:02:00')); $object = self::$tagger->getObjects(array('objectId' => array('type' => 'event', 'object' => 'irene'))); $this->assertEquals('floyd', current($object)); } public function testDuplicateTagsByCase() { // These tests don't work at the moment, because SQLite sucks at // non-ascii comparing. /* self::$tagger->tag('mike', 1, 'TYÖ'); self::$tagger->tag('mike', 1, 'TYÖ'); self::$tagger->tag('mike', 1, 'työ'); self::$tagger->tag('mike', 1, 'työ'); */ // Use older timestamps to avoid interfering with the later tests self::$tagger->tag('mike', array('type' => 'foo', 'object' => 'xyz'), 'foo', new Horde_Date('2008-01-01T00:05:00')); self::$tagger->tag('alice', array('type' => 'foo', 'object' => 'xyz'), 'FOO', new Horde_Date('2008-01-01T00:05:00')); self::$tagger->tag('alice', array('type' => 'foo', 'object' => 'xyz'), array('test', 'TEST'), new Horde_Date('2008-01-01T00:05:00')); $this->assertEquals(2, count(self::$tagger->getTags(array('objectId' => array('type' => 'foo', 'object' => 'xyz'))))); } public function testGetRecentTagsLimit() { // Create 100 tags on 100 tag_ids, with tag_id = t1 being applied // most recently, and so on. Prepend "t" to each tag to force the // creation of tags that don't yet exist in the test database. for ($i = 1; $i <= 100; $i++) { self::$tagger->tag(1, 1, "t$i", new Horde_Date(strtotime('now - ' . $i . ' minutes'))); } $recentLimit = self::$tagger->getRecentTags(array('limit' => 25)); $this->assertEquals(25, count($recentLimit)); $this->assertEquals('t1', $recentLimit[0]['tag_name']); } /** * @depends testGetRecentTagsLimit */ public function testGetRecentTagsOffset() { $recentOffset = self::$tagger->getRecentTags(array('limit' => 25, 'offset' => 25)); $this->assertEquals(25, count($recentOffset)); $this->assertEquals('t26', $recentOffset[0]['tag_name']); } public function testGetRecentObjectsLimit() { // Create 100 tags on 100 object_ids, with object_id = 1 being tagged // most recently, and so on. for ($i = 1; $i <= 100; $i++) { self::$tagger->tag(1, $i, 1, new Horde_Date(strtotime('now - ' . $i . ' minutes'))); } $recentLimit = self::$tagger->getRecentObjects(array('limit' => 25)); $this->assertEquals(25, count($recentLimit)); $this->assertEquals(1, $recentLimit[0]['object_id']); } /** * @depend testGetRecentTagsOffset */ public function testGetRecentObjectsOffset() { $recentOffset = self::$tagger->getRecentObjects(array('limit' => 25, 'offset' => 25)); $this->assertEquals(25, count($recentOffset)); $this->assertEquals(26, $recentOffset[0]['object_id']); } public function testGetRecentUsersLimit() { // Create 100 tags by 100 user_ids, with user_id = 1 tagging // most recently, and so on. for ($i = 1; $i <= 100; $i++) { self::$tagger->tag($i, 1, 1, new Horde_Date(strtotime('now - ' . $i . ' minutes'))); } $recentLimit = self::$tagger->getRecentUsers(array('limit' => 25)); $this->assertEquals(25, count($recentLimit)); $this->assertEquals(1, $recentLimit[0]['user_id']); } /** * @depend testGetRecentUsersLimit */ public function testGetRecentUsersOffset() { $recentOffset = self::$tagger->getRecentUsers(array('limit' => 25, 'offset' => 25)); $this->assertEquals(25, count($recentOffset)); $this->assertEquals(26, $recentOffset[0]['user_id']); } } content-2.0.5/test/Content/bootstrap.php0000664000175000017500000000014312611721515020556 0ustar mrubinskmrubinsk content-2.0.5/.htaccess0000664000175000017500000000027112611721515015237 0ustar mrubinskmrubinsk RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php [QSA,L]