Need to run the (non-destructive) 2010-07-30_migrate_tags_to_content.php script to move
existing ansel tags to content.
$layout_html = $layout->toHtml();
$title = _("Photo Galleries");
-Ansel_Tags::clearSearch();
+Ansel_Search_Tag::clearSearch();
require ANSEL_BASE . '/templates/common-header.inc';
require ANSEL_TEMPLATES . '/menu.inc';
echo '<div id="menuBottom"><a href="' . Horde::applicationUrl('browse_edit.php') . '">' . _("Add Content") . '</a></div><div class="clear"> </div>';
--- /dev/null
+<?php
+/**
+ * Ansel_Ajax_Imple_EditGalleryFaces:: class for performing Ajax discovery of
+ * an entire gallery's images.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * @author Duck <duck@obala.net>
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ *
+ * @package Ansel
+ */
+class Ansel_Ajax_Imple_EditGalleryFaces extends Horde_Ajax_Imple_Base
+{
+ /**
+ * Attach these actions to the view
+ *
+ */
+ public function attach()
+ {
+ Horde::addScriptFile('editfaces.js');
+ $url = $this->_getUrl('EditFaces', 'ansel');
+ $js = array();
+ $js[] = "Ansel.ajax['editFaces'] = {'url':'" . $url . "', text: {loading:'" . _("Loading...") . "'}};";
+ $image_id = $this->_params['image_id'];
+ /* Start by getting the faces */
+ $faces = $GLOBALS['injector']->getInstance('Ansel_Faces');
+ $name = '';
+ $autocreate = true;
+ $result = $faces->getImageFacesData($image_id);
+ if (empty($result)) {
+ $image = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImage($this->_params['image_id']);
+ $image->createView('screen');
+ $result = $faces->getFromPicture($this->_params['image_id'], $autocreate);
+ }
+ if (!empty($result)) {
+ $customurl = Horde::applicationUrl('faces/custom.php');
+ $url = (!empty($args['url']) ? urldecode($args['url']) : '');
+ Horde::startBuffer();
+ require_once ANSEL_TEMPLATES . '/faces/image.inc';
+ return Horde::endBuffer();
+ } else {
+ return _("No faces found");
+ }
+
+ Horde::addInlineScript($js, 'dom');
+ }
+
+ function handle($args, $post)
+ {
+ if (Horde_Auth::getAuth()) {
+ $action = $args['action'];
+ $image_id = (int)$post['image'];
+ $reload = empty($post['reload']) ? 0 : $post['reload'];
+
+ if (empty($action)) {
+ return array('response' => 0);
+ }
+
+ $faces = $GLOBALS['injector']->getInstance('Ansel_Faces');
+ switch($action) {
+ case 'process':
+ // process - detects all faces in the image.
+ $name = '';
+ $autocreate = true;
+ $result = $faces->getImageFacesData($image_id);
+ // Attempt to get faces from the picture if we don't already have results,
+ // or if we were asked to explicitly try again.
+ if (($reload || empty($result))) {
+ $image = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImage($image_id);
+ $image->createView('screen');
+ $result = $faces->getFromPicture($image_id, $autocreate);
+ }
+ if (!empty($result)) {
+ $imgdir = Horde_Themes::img(null, 'horde');
+ $customurl = Horde::applicationUrl('faces/custom.php');
+ $url = (!empty($args['url']) ? urldecode($args['url']) : '');
+ Horde::startBuffer();
+ require_once ANSEL_TEMPLATES . '/faces/image.inc';
+ $html = Horde::endBuffer();
+ return array('response' => 1,
+ 'message' => $html);
+ } else {
+ return array('response' => 1,
+ 'message' => _("No faces found"));
+ }
+ break;
+
+ case 'delete':
+ // delete - deletes a single face from an image.
+ $face_id = (int)$post['face'];
+ $image = &$GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImage($image_id);
+ $gallery = &$GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($image->gallery);
+ if (!$gallery->hasPermission(Horde_Auth::getAuth(), Horde_Perms::EDIT)) {
+ throw new Horde_Exception('Access denied editing the photo.');
+ }
+
+ $faces = $GLOBALS['injector']->getInstance('Ansel_Faces');
+ $faces->delete($image, $face_id);
+ break;
+
+ case 'setname':
+ // setname - sets the name of a single image.
+ $face_id = (int)$post['face'];
+ if (!$face_id) {
+ return array('response' => 0);
+ }
+
+ $name = $post['facename'];
+ $image = &$GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImage($image_id);
+ $gallery = &$GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($image->gallery);
+ if (!$gallery->hasPermission(Horde_Auth::getAuth(), Horde_Perms::EDIT)) {
+ throw new Horde_Exception('You are not allowed to edit this photo');
+ }
+
+ $faces = $GLOBALS['injector']->getInstance('Ansel_Faces');
+ $result = $faces->setName($face_id, $name);
+ return array('response' => 1,
+ 'message' => Ansel_Faces::getFaceTile($face_id));
+ break;
+ }
+ }
+ }
+
+}
if (!empty($tags)) {
$tags = rawurldecode($post['tags']);
$tags = explode(',', $tags);
-
- /* Get current tags so we don't overwrite them */
- $etags = Ansel_Tags::readTags($id, $type);
- $tags = array_keys(array_flip(array_merge($tags, array_values($etags))));
- $resource->setTags($tags);
+ $GLOBALS['injector']->getInstance('Ansel_Tagger')->tag($id, $tags, $GLOBALS['registry']->getAuth(), $type);
/* Get the tags again since we need the newly added tag_ids */
- $newTags = $resource->getTags();
+ $newTags = $GLOBALS['injector']->getInstance('Ansel_Tagger')->getTags($id, $type);
if (count($newTags)) {
- $newTags = Ansel_Tags::listTagInfo(array_keys($newTags));
+ $newTags = $GLOBALS['injector']->getInstance('Ansel_Tagger')->getTagInfo(array_keys($newTags));
}
return array('response' => 1,
break;
case 'remove':
- $existingTags = $resource->getTags();
- unset($existingTags[$tags]);
- $resource->setTags($existingTags);
+ $GLOBALS['injector']->getInstance('Ansel_Tagger')->untag($resource->id, (int)$tags, $type);
+ $existingTags = $GLOBALS['injector']->getInstance('Ansel_Tagger')->getTags($resource->id, $type);
if (count($existingTags)) {
- $newTags = Ansel_Tags::listTagInfo(array_keys($existingTags));
+ $newTags = $GLOBALS['injector']->getInstance('Ansel_Tagger')->getTagInfo(array_keys($existingTags));
} else {
$newTags = array();
}
-
return array('response' => 1,
'message' => $this->_getTagHtml($newTags,
$parent->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)));
private function _getTagHtml($tags, $hasEdit)
{
global $registry;
- $links = Ansel_Tags::getTagLinks($tags, 'add');
+ $links = Ansel::getTagLinks($tags, 'add');
$html = '<ul>';
- foreach ($tags as $tag_id => $taginfo) {
- $html .= '<li>' . $links[$tag_id]->link(array('title' => sprintf(ngettext("%d photo", "%d photos", $taginfo['total']), $taginfo['total']))) . htmlspecialchars($taginfo['tag_name']) . '</a>' . ($hasEdit ? '<a href="#" onclick="removeTag(' . $tag_id . ');">' . Horde::img('delete-small.png', _("Remove Tag")) . '</a>' : '') . '</li>';
+ foreach ($tags as $taginfo) {
+ $tag_id = $taginfo['tag_id'];
+ $html .= '<li>' . $links[$tag_id]->link(array('title' => sprintf(ngettext("%d photo", "%d photos", $taginfo['count']), $taginfo['count']))) . htmlspecialchars($taginfo['tag_name']) . '</a>' . ($hasEdit ? '<a href="#" onclick="removeTag(' . $tag_id . ');">' . Horde::img('delete-small.png', _("Remove Tag")) . '</a>' : '') . '</li>';
}
$html .= '</ul>';
return $html;
return '<script type="text/javascript" src="' . $imple->getUrl() . '"></script><div id="' . $domid . '"></div>';
}
+ /**
+ * Get the URL for a tag search link
+ *
+ * @TODO: Move this to Tagger
+ *
+ * @param array $tags The tag ids to link to
+ * @param string $action The action we want to perform with this tag.
+ * @param string $owner The owner we want to filter the results by
+ *
+ * @return string The URL for this tag and action
+ */
+ static public function getTagLinks($tags, $action = 'add', $owner = null)
+ {
+
+ $results = array();
+ foreach ($tags as $id => $taginfo) {
+ $params = array('view' => 'Results',
+ 'tag' => $taginfo['tag_name']);
+ if (!empty($owner)) {
+ $params['owner'] = $owner;
+ }
+ if ($action != 'add') {
+ $params['actionID'] = $action;
+ }
+ $link = Ansel::getUrlFor('view', $params, true);
+ $results[$id] = $link;
+ }
+
+ return $results;
+ }
+
}
throw new Horde_Exception('You must configure a Horde_Image driver to use Ansel');
}
+ /* For now, autoloading the Content_* classes depend on there being a
+ * registry entry for the 'content' application that contains at least
+ * the fileroot entry. */
+ $GLOBALS['injector']->getInstance('Horde_Autoloader')->addClassPathMapper(new Horde_Autoloader_ClassPathMapper_Prefix('/^Content_/', $GLOBALS['registry']->get('fileroot', 'content') . '/lib/'));
+ if (!class_exists('Content_Tagger')) {
+ throw new Horde_Exception('The Content_Tagger class could not be found. Make sure the registry entry for the Content system is present.');
+ }
+
$binders = array(
'Ansel_Styles' => new Ansel_Injector_Binder_Styles(),
'Ansel_Faces' => new Ansel_Injector_Binder_Faces(),
global $registry;
/* Get the tags */
- $tags = Ansel_Tags::listTagInfo(null, $this->_params['count']);
+ $tags = $GLOBALS['injector']->getInstance('Ansel_Tagger')->getCloud(null, $this->_params['count']);
if (count($tags)) {
$cloud = new Horde_Core_Ui_TagCloud();
foreach ($tags as $id => $tag) {
$link = Ansel::getUrlFor('view', array('view' => 'Results',
'tag' => $tag['tag_name']));
- $cloud->addElement($tag['tag_name'], $link, $tag['total']);
+ $cloud->addElement($tag['tag_name'], $link, $tag['count']);
}
$html = $cloud->buildHTML();
} else {
'image_type' => $img->getType(),
'image_uploaded_date' => $img->uploaded));
/* Copy any tags */
- // Since we know that the tags already exist, no need to
- // go through Ansel_Tags::writeTags() - this saves us a SELECT query
- // for each tag - just write the data into the DB ourselves.
$tags = $img->getTags();
- $query = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_images_tags (image_id, tag_id) VALUES(' . $newId . ',?);');
- if ($query instanceof PEAR_Error) {
- throw new Ansel_Exception($query);
- }
- foreach ($tags as $tag_id => $tag_name) {
- $result = $query->execute($tag_id);
- if ($result instanceof PEAR_Error) {
- throw new Ansel_Exception($result);
- }
- }
- $query->free();
+ $GLOBALS['injector']->getInstance('Ansel_Tagger')->tag($newId, $tags, $gallery->get('owner'), 'image');
/* exif data */
// First check to see if the exif data was present in the raw data.
*/
public function getTags() {
if ($this->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) {
- return Ansel_Tags::readTags($this->id, 'gallery');
+ return $GLOBALS['injector']->getInstance('Ansel_Tagger')->getTags($this->id, 'gallery');
} else {
throw new Horde_Exception(_("Access denied viewing this gallery."));
}
public function setTags($tags)
{
if ($this->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
- return Ansel_Tags::writeTags($this->id, $tags, 'gallery');
+ return $GLOBALS['injector']->getInstance('Ansel_Tagger')->tag($this->id, $tags, $this->get('owner'), 'gallery');
} else {
throw new Horde_Exception(_("Access denied adding tags to this gallery."));
}
/**
* Save image details to storage.
*
- * @TODO: Move all SQL queries to Ansel_Storage::
- *
* @return integer image id
* @throws Ansel_Exception
*/
}
$gallery = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($this->gallery);
if ($gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) {
- return Ansel_Tags::readTags($this->id);
+ return $GLOBALS['injector']->getInstance('Ansel_Tagger')->getTags($this->id, 'image');
} else {
throw new Horde_Exception_PermissionDenied(_("Access denied viewing this photo."));
}
if ($gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
// Clear the local cache.
$this->_tags = array();
- Ansel_Tags::writeTags($this->id, $tags);
+ $GLOBALS['injector']->getInstance('Ansel_Tagger')->tag($this->id, $tags, $gallery->get('owner'), 'image');
} else {
throw new Horde_Exception_PermissionDenied(_("Access denied adding tags to this photo."));
}
--- /dev/null
+<?php
+/**
+ * Ansel_Search_Tags:: class provides logic for dealing with tag searching.
+ *
+ * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @category Horde
+ * @license http://www.fsf.org/copyleft/gpl.html GPL
+ * @package Ansel
+ */
+class Ansel_Search_Tag
+{
+ /**
+ * Array of tag_name => tag_id hashes for the current search.
+ * Tags are always added to the search by name and stored by name=>id.
+ *
+ * @var array
+ */
+ protected $_tags = array();
+
+ /**
+ * Total count of all resources that match (both Galleries and Images).
+ *
+ * @var integer
+ */
+ protected $_totalCount = null;
+
+ /**
+ * The user whose resources we are searching.
+ *
+ * @var string
+ */
+ protected $_owner = '';
+
+ /**
+ * Dirty flag
+ *
+ * @var boolean
+ */
+ protected $_dirty = false;
+
+ /**
+ * Results cache. Holds the results of the current search.
+ *
+ * @var array
+ */
+ protected $_results = array();
+
+ /**
+ * The Ansel_Tagger object.
+ *
+ * @var Ansel_Tagger
+ */
+ protected $_tagger;
+
+ /**
+ * Constructor
+ *
+ * @param array $tags An array of tag names to match. If null is passed
+ * then the tags will be loaded from the session.
+ * @param string $owner Restrict search to resources owned by specified
+ * owner.
+ *
+ * @return Ansel_Search_Tag
+ */
+ public function __construct(Ansel_Tagger $tagger, $tags = null, $owner = null)
+ {
+ $this->_tagger = $tagger;
+ if (!empty($tags)) {
+ $this->_tags = $this->_tagger->getTagIds($tags);
+ } else {
+ $this->_tags = (!empty($_SESSION['ansel_tags_search']) ? $_SESSION['ansel_tags_search'] : array());
+ }
+
+ $this->_owner = $owner;
+
+ }
+
+ /**
+ * Save the current search to the session
+ *
+ */
+ public function save()
+ {
+ $_SESSION['ansel_tags_search'] = $this->_tags;
+ $this->_dirty = false;
+ }
+
+ /**
+ * Fetch the matching resources that should appear on the current page
+ *
+ * @TODO: Implement an Interface that Ansel_Gallery and Ansel_Image should
+ * implement that the client search code will use.
+ *
+ * @return Array of Ansel_Images and Ansel_Galleries
+ */
+ public function getSlice($page, $perpage)
+ {
+ global $conf, $registry;
+
+ /* Refresh the search */
+ $this->runSearch();
+ $totals = $this->count();
+
+ /* First, the galleries */
+ $gstart = $page * $perpage;
+ $gresults = array_slice($this->_results['galleries'], $gstart, $perpage);
+
+ /* Instantiate the Gallery objects */
+ $galleries = array();
+ foreach ($gresults as $gallery) {
+ $galleries[] = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($gallery);
+ }
+
+ /* Do we need to get images? */
+ $istart = max(0, $page * $perpage - $totals['galleries']);
+ $count = $perpage - count($galleries);
+ if ($count > 0) {
+ $iresults = array_slice($this->_results['images'], $istart, $count);
+ $images = count($iresults) ? array_values($GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImages(array('ids' => $iresults))) : array();
+ if (($conf['comments']['allow'] == 'all' || ($conf['comments']['allow'] == 'authenticated' && $GLOBALS['registry']->getAuth())) &&
+ $registry->hasMethod('forums/numMessagesBatch')) {
+
+ $ids = array_keys($images);
+ $ccounts = $GLOBALS['registry']->call('forums/numMessagesBatch', array($ids, 'ansel'));
+ if (!($ccounts instanceof PEAR_Error)) {
+ foreach ($images as $image) {
+ $image->commentCount = (!empty($ccounts[$image->id]) ? $ccounts[$image->id] : 0);
+ }
+ }
+ }
+ } else {
+ $images = array();
+ }
+
+ return array_merge($galleries, $images);
+ }
+
+ /**
+ * Add a tag to the cumulative tag search
+ *
+ * @param string $tag The tag name to add.
+ *
+ * @return void
+ */
+ public function addTag($tag)
+ {
+ $tag_id = (int)current($this->_tagger->getTagIds($tag));
+ if (array_search($tag_id, $this->_tags) === false) {
+ $this->_tags[$tag] = $tag_id;
+ $this->_dirty = true;
+ }
+ }
+
+ /**
+ * Remove a tag from the cumulative search
+ *
+ * @param string $tag The tag name to remove.
+ *
+ * @return void
+ */
+ public function removeTag($tag)
+ {
+ if (!empty($this->_tags[$tag])) {
+ unset($this->_tags[$tag]);
+ $this->_dirty = true;
+ }
+ }
+
+ /**
+ * Get the list of currently choosen tags
+ *
+ * @return array An array of selected tag_name => tag_id hashes.
+ */
+ public function getTags()
+ {
+ return $this->_tags;
+ }
+
+ /**
+ * Get breadcrumb style navigation html for choosen tags
+ *
+ * @TODO: Remove the html generation to the view class
+ *
+ * @return string The html representing the tag trail for browsing tags.
+ */
+ public function getTagTrail()
+ {
+ global $registry;
+
+ $html = '<ul class="tag-list">';
+
+ /* Use the local cache to preserve the order */
+ $count = 0;
+ foreach ($this->_tags as $tagname => $tagid) {
+ $remove_url = Horde::applicationUrl('view.php', true)->add(
+ array('view' => 'Results',
+ 'tag' => $tagname,
+ 'actionID' => 'remove'));
+ if (!empty($this->_owner)) {
+ $remove_url->add('owner', $this->_owner);
+ }
+ $delete_label = sprintf(_("Remove %s from search"), htmlspecialchars($tagname));
+ $html .= '<li>' . htmlspecialchars($tagname) . $remove_url->link(array('title' => $delete_label)) . Horde::img('delete-small.png', $delete_label) . '</a></li>';
+ }
+
+ return $html . '</ul>';
+ }
+
+ /**
+ * Get the total number of tags included in this search.
+ *
+ * @return integer The number of tags used in the current search.
+ */
+ public function tagCount()
+ {
+ return count($this->_tags);
+ }
+
+ /**
+ * Get the total number of resources that match.
+ *
+ * @return array Hash containing totals for both 'galleries' and 'images'.
+ */
+ public function count()
+ {
+ if (!is_array($this->_tags) || !count($this->_tags)) {
+ return 0;
+ }
+
+ $count = array('galleries' => count($this->_results['galleries']), 'images' => count($this->_results['images']));
+ $this->_totalCount = $count;
+
+ return $count;
+ }
+
+ /**
+ * Get a list of tags related to this search
+ *
+ * @return array An array tag_id => {tag_name, total}
+ */
+ public function getRelatedTags()
+ {
+ $tags = $this->_tagger->browseTags($this->getTags(), $this->_owner);
+ $search = new Ansel_Search_Tag($this->_tagger, null, $this->_owner);
+ $results = array();
+ foreach ($tags as $id => $tag) {
+ $search->addTag($tag);
+ $search->runSearch();
+ $count = $search->count();
+ if ($count['images'] + $count['galleries'] > 0) {
+ $results[$id] = array('tag_name' => $tag, 'total' => $count['images'] + $count['galleries']);
+ }
+ $search->removeTag($tag);
+ }
+
+ /* Get the results sorted by available totals for this user */
+ uasort($results, array($this, '_sortTagInfo'));
+ return $results;
+ }
+
+ /**
+ * Perform, and cache the search.
+ *
+ */
+ public function runSearch()
+ {
+ if (!empty($this->_owner)) {
+ $filter = array('user' => $this->_owner);
+ } else {
+ $filter = array();
+ }
+ if (empty($this->_results) || $this->_dirty) {
+ $this->_results = $this->_tagger
+ ->search($this->_tags, $filter);
+ }
+ }
+
+ /**
+ * Clears the session cache of tags currently included in the search.
+ */
+ static public function clearSearch()
+ {
+ unset($_SESSION['ansel_tags_search']);
+ }
+
+ /**
+ * Helper for uasort. Sorts the results by count.
+ *
+ */
+ private function _sortTagInfo($a, $b)
+ {
+ return $a['total'] < $b['total'];
+ }
+
+}
\ No newline at end of file
}
/* Fill up the new gallery */
- // TODO: New private method to bulk load these (it's done this way
- // since the data is stored in the Share_Object class keyed by the
- // DB specific fields and set() translates them.
foreach ($attributes as $key => $value) {
$gallery->set($key, $value);
}
--- /dev/null
+<?php
+/**
+ * The Ansel_Tagger:: class provides logic for dealing with tags within Ansel
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @license http://www.fsf.org/copyleft/gpl.html GPL
+ * @package Ansel
+ */
+class Ansel_Tagger
+{
+ /**
+ * Local cache of the type name => ids from Content, so we don't have to
+ * query for them each time.
+ *
+ * @var array
+ */
+ protected $_type_ids = array();
+
+ /**
+ * Local reference to the tagger.
+ *
+ * @var Content_Tagger
+ */
+ protected $_tagger;
+
+ /**
+ * Constructor.
+ *
+ * @return Ansel_Tagger
+ */
+ public function __construct(Content_Tagger $tagger)
+ {
+ /* Remember the types to avoid having Content query them again. */
+ $key = 'ansel.tagger.type_ids';
+ $ids = $GLOBALS['injector']->getInstance('Horde_Cache')->get($key, 360);
+ if ($ids) {
+ $this->_type_ids = unserialize($ids);
+ } else {
+ $type_mgr = $GLOBALS['injector']->getInstance('Content_Types_Manager');
+ $types = $type_mgr->ensureTypes(array('gallery', 'image'));
+ $this->_type_ids = array('gallery' => (int)$types[0],
+ 'image' => (int)$types[1]);
+ $GLOBALS['injector']->getInstance('Horde_Cache')->set($key, serialize($this->_type_ids));
+ }
+
+ $this->_tagger = $tagger;
+ }
+
+ /**
+ * Tags an ansel object with any number of tags.
+ *
+ * @param string $localId The identifier of the ansel object.
+ * @param string|array $tags Either a single tag string or an array of
+ * tags.
+ * @param string $owner The tag owner (should normally be the owner
+ * of the resource).
+ * @param string $content_type The type of object we are tagging
+ * (gallery/image).
+ *
+ * @return void
+ * @throws Ansel_Exception
+ */
+ public function tag($localId, $tags, $owner, $content_type = 'image')
+ {
+ if (!is_array($tags)) {
+ $tags = $this->_tagger->splitTags($tags);
+ }
+
+ try {
+ $this->_tagger->tag(
+ $owner,
+ array('object' => $localId,
+ 'type' => $this->_type_ids[$content_type]),
+ $tags);
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Retrieves the tags on given object(s).
+ *
+ * @param mixed $localId Either the identifier of the ansel object or
+ * an array of identifiers.
+ * @param string $type The type of object $localId represents.
+ *
+ * @return array A tag_id => tag_name hash, possibly wrapped in a localid hash.
+ * @throws Ansel_Exception
+ */
+ public function getTags($localId, $type = 'image')
+ {
+ try {
+ if (is_array($localId)) {
+ return $this->_tagger->getTagsByObjects($localId, $type);
+ }
+
+ return $this->_tagger->getTags(array('objectId' => array('object' => $localId, 'type' => $this->_type_ids[$type])));
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Retrieve a set of tags that are related to the specifed set. A tag is
+ * related if resources tagged with the specified set are also tagged with
+ * the tag being considered. Used to "browse" tagged resources.
+ *
+ * @param array $tags An array of tags to check. This would represent the
+ * current "directory" of tags while browsing.
+ * @param string $user The resource must be owned by this user.
+ *
+ * @return array An tag_id => tag_name hash
+ */
+ public function browseTags($tags, $user)
+ {
+ try {
+ $tags = array_values($this->_tagger->getTagIds($tags));
+ $gtags = $this->_tagger->browseTags($tags, $this->_type_ids['gallery'], $user);
+ $itags = $this->_tagger->browseTags($tags, $this->_type_ids['image'], $user);
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ /* Can't use array_merge here since it would renumber the array keys */
+ foreach ($gtags as $id => $name) {
+ if (empty($itags[$id])) {
+ $itags[$id] = $name;
+ }
+ }
+
+ return $itags;
+ }
+
+ /**
+ * Get tag ids for the specified tag names.
+ *
+ * @param string|array $tags Either a tag_name or array of tag_names.
+ *
+ * @return array A tag_id => tag_name hash.
+ * @throws Ansel_Exception
+ */
+ public function getTagIds($tags)
+ {
+ try {
+ return $this->_tagger->getTagIds($tags);
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Untag a resource.
+ *
+ * Removes the tag regardless of the user that added the tag.
+ *
+ * @param string $localId The ansel object identifier.
+ * @param mixed $tags Either a tag_id, tag_name or an array.
+ * @param string $content_type The type of object that $localId represents.
+ *
+ * @return void
+ */
+ public function untag($localId, $tags, $content_type = 'image')
+ {
+ try {
+ $this->_tagger->removeTagFromObject(
+ array('object' => $localId, 'type' => $this->_type_ids[$content_type]), $tags);
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Tags the given resource with *only* the tags provided, removing any
+ * tags that are already present but not in the list.
+ *
+ * @param string $localId The identifier for the ansel object.
+ * @param mixed $tags Either a tag_id, tag_name, or array of tag_ids.
+ * @param string $owner The tag owner - should normally be the resource
+ * owner.
+ * @param $content_type The type of object that $localId represents.
+ */
+ public function replaceTags($localId, $tags, $owner, $content_type = 'image')
+ {
+ /* First get a list of existing tags. */
+ $existing_tags = $this->getTags($localId, $content_type);
+
+ if (!is_array($tags)) {
+ $tags = $this->_tagger->splitTags($tags);
+ }
+ $remove = array();
+ foreach ($existing_tags as $tag_id => $existing_tag) {
+ $found = false;
+ foreach ($tags as $tag_text) {
+ if ($existing_tag == $tag_text) {
+ $found = true;
+ break;
+ }
+ }
+ /* Remove any tags that were not found in the passed in list. */
+ if (!$found) {
+ $remove[] = $tag_id;
+ }
+ }
+
+ $this->untag($localId, $remove, $content_type);
+ $add = array();
+ foreach ($tags as $tag_text) {
+ $found = false;
+ foreach ($existing_tags as $existing_tag) {
+ if ($tag_text == $existing_tag) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ $add[] = $tag_text;
+ }
+ }
+
+ $this->tag($localId, $add, $owner, $content_type);
+ }
+
+ /**
+ * Returns tags belonging to the current user beginning with $token.
+ *
+ * Used for autocomplete code.
+ *
+ * @param string $token The token to match the start of the tag with.
+ *
+ * @return A tag_id => tag_name hash
+ * @throws Ansel_Exception
+ */
+ public function listTags($token)
+ {
+ try {
+ return $this->_tagger->getTags(array('q' => $token, 'userId' => $GLOBALS['registry']->getAuth()));
+ } catch (Content_Tagger $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Returns the data needed to build a tag cloud based on the specified
+ * user's tag dataset.
+ *
+ * @param string $user The user whose tags should be included.
+ * If null, all users' tags are returned.
+ * @param integer $limit The maximum number of tags to include.
+ *
+ * @return Array An array of hashes, each containing tag_id, tag_name, and count.
+ * @throws Ansel_Exception
+ */
+ public function getCloud($user, $limit = 5)
+ {
+ $filter = array('limit' => $limit,
+ 'typeId' => array_values($this->_type_ids));
+ if (!empty($user)) {
+ $filter['userId'] = $user;
+ }
+ try {
+ return $this->_tagger->getTagCloud($filter);
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Returns cloud-like information, but only for a specified set of tags.
+ * Useful for displaying the counts of other images tagged with the same
+ * tag as the currently displayed image.
+ *
+ * @param array $tags An array of either tag names or ids.
+ * @param integer $limit Limit results to this many.
+ *
+ * @return array An array of hashes, tag_id, tag_name, and count.
+ * @throws Ansel_Exception
+ */
+ public function getTagInfo($tags, $limit = 500, $type = null)
+ {
+ $filter = array('typeId' => empty($type) ? array_values($this->_type_ids) : $this->_type_ids[$type],
+ 'tagIds' => $tags,
+ 'limit' => $limit);
+
+ try {
+ return $this->_tagger->getTagCloud($filter);
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+ }
+
+ /**
+ * Searches for resources that are tagged with all of the requested tags.
+ *
+ * @param array $tags Either a tag_id, tag_name or an array.
+ * @param array $filter Array of filter parameters.
+ * - type (string) - 'gallery' or 'image'
+ * - user (array) - only include objects owned by
+ * these users.
+ *
+ * @return A hash of 'calendars' and 'events' that each contain an array
+ * of calendar_ids and event_uids respectively.
+ * @throws Ansel_Exception
+ */
+ public function search($tags, $filter = array())
+ {
+ $args = array();
+
+ /* These filters are mutually exclusive */
+ if (array_key_exists('user', $filter)) {
+ $args['userId'] = $filter['user'];
+ } elseif (!empty($filter['gallery'])) {
+ // Only events located in specific calendar(s)
+ if (!is_array($filter['gallery'])) {
+ $filter['gallry'] = array($filter['gallery']);
+ }
+ $args['gallery'] = $filter['gallery'];
+ }
+
+ try {
+ /* Add the tags to the search */
+ $args['tagId'] = $this->_tagger->getTagIds($tags);
+
+ /* Restrict to images or galleries */
+ $gal_results = $image_results = array();
+ if (empty($filter['type']) || $filter['type'] == 'gallery') {
+ $args['typeId'] = $this->_type_ids['gallery'];
+ $gal_results = $this->_tagger->getObjects($args);
+ }
+
+ if (empty($filter['type']) || $filter['type'] == 'image') {
+ $args['typeId'] = $this->_type_ids['image'];
+ $image_results = $this->_tagger->getObjects($args);
+ }
+ } catch (Content_Exception $e) {
+ throw new Ansel_Exception($e);
+ }
+
+ /* TODO: Filter out images whose gallery has already matched? */
+ $results = array('galleries' => array_values($gal_results),
+ 'images' => array_values($image_results));
+
+ return $results;
+ }
+
+}
+++ /dev/null
-<?php
-/**
- * Classes for dealing with tags within Ansel
- *
- * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author Michael J. Rubinsky <mrubinsk@horde.org>
- * @category Horde
- * @license http://www.fsf.org/copyleft/gpl.html GPL
- * @package Ansel
- */
-
-/**
- * Static helper class for writing/reading tag values
- *
- * @TODO: Move tag storage to Content_Tagger
- * @static
- */
-class Ansel_Tags
-{
- /**
- * Write out the tags for a specific resource.
- *
- * @param int $resource_id The resource we are tagging.
- * @param array $tags An array of tags.
- * @param string $resource_type The type of resource (image or gallery)
- *
- * @return boolean True on success
- * @throws InvalidArgumentException
- * @throws Ansel_Exception
- */
- static public function writeTags($resource_id, $tags, $resource_type = 'image')
- {
- // First, make sure all tag names exist in the DB.
- $tagkeys = array();
- $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_tags (tag_id, tag_name) VALUES(?, ?)');
- foreach ($tags as $tag) {
- if (!empty($tag)) {
- if (!preg_match("/^[a-zA-Z0-9%_+.!*',()~-]*$/", $tag)) {
- throw new InvalidArgumentException('Invalid characters in tag.');
- }
- $tag = Horde_String::lower(trim($tag));
- $sql = $GLOBALS['ansel_db']->prepare('SELECT tag_id FROM ansel_tags WHERE tag_name = ?');
- $result = $sql->execute(Horde_String::convertCharset($tag, $GLOBALS['registry']->getCharset(), $GLOBALS['conf']['sql']['charset']));
- $results = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
- $result->free();
-
- if (empty($results)) {
- $id = $GLOBALS['ansel_db']->nextId('ansel_tags');
- $result = $insert->execute(array($id, Horde_String::convertCharset($tag, $GLOBALS['registry']->getCharset(), $GLOBALS['conf']['sql']['charset'])));
- $tagkeys[] = $id;
- } elseif ($results instanceof PEAR_Error) {
- Horde::logMessage($results->getMessage(), 'ERR');
- throw new Ansel_Exception($results);
- } else {
- $tagkeys[] = $results['tag_id'];
- }
- }
- }
- $insert->free();
-
- if ($resource_type == 'image') {
- $delete = $GLOBALS['ansel_db']->prepare('DELETE FROM ansel_images_tags WHERE image_id = ?');
- $query = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_images_tags (image_id, tag_id) VALUES(?, ?)');
- } else {
- $delete = $GLOBALS['ansel_db']->prepare('DELETE FROM ansel_galleries_tags WHERE gallery_id = ?');
- $query = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_galleries_tags (gallery_id, tag_id) VALUES(?, ?)');
- }
- Horde::logMessage('SQL query by Ansel_Tags::writeTags: ' . $query->query, 'DEBUG');
- $delete->execute(array($resource_id));
- foreach ($tagkeys as $key) {
- $query->execute(array($resource_id, $key));
- }
-
- $delete->free();
- $query->free();
-
- /* We should clear at least any of our cached counts */
- Ansel_Tags::clearCache();
- return true;
- }
-
- /**
- * Retrieve the tags for a specified resource.
- *
- * @param integer $resource_id The resource to get tags for.
- * @param string $resource_type The type of resource (gallery or image)
- *
- * @return mixed An array of tags
- */
- static public function readTags($resource_id, $resource_type = 'image')
- {
- if ($resource_type == 'image') {
- $stmt = $GLOBALS['ansel_db']->prepare('SELECT ansel_tags.tag_id, tag_name FROM ansel_tags INNER JOIN ansel_images_tags ON ansel_images_tags.tag_id = ansel_tags.tag_id WHERE ansel_images_tags.image_id = ?');
- } else {
- $stmt = $GLOBALS['ansel_db']->prepare('SELECT ansel_tags.tag_id, tag_name FROM ansel_tags INNER JOIN ansel_galleries_tags ON ansel_galleries_tags.tag_id = ansel_tags.tag_id WHERE ansel_galleries_tags.gallery_id = ?');
- }
- if ($stmt instanceof PEAR_Error) {
- throw new Ansel_Exception($stmt);
- }
- Horde::logMessage('SQL query by Ansel_Tags::readTags ' . $stmt->query, 'DEBUG');
- $result = $stmt->execute((int)$resource_id);
- $tags = $result->fetchAll(MDB2_FETCHMODE_ASSOC, true);
- foreach ($tags as $id => $tag) {
- $tags[$id] = Horde_String::convertCharset(
- $tag, $GLOBALS['conf']['sql']['charset']);
- }
- $stmt->free();
- $result->free();
-
- return $tags;
- }
-
- /**
- * Retrieve the list of used tag_names, tag_ids and the total number
- * of resources that are linked to that tag.
- *
- * @param array $tags An optional array of tag_ids. If omitted, all tags
- * will be included.
- * @param integer $limit Limit the number of tags returned to this value.
- *
- * @return Array An array containing tag_name, and total
- */
- static public function listTagInfo($tags = null, $limit = 500)
- {
- global $conf;
- // Only return the full list if $tags is omitted, not if
- // an empty array is passed
- if (is_array($tags) && count($tags) == 0) {
- return array();
- }
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $cache_key = 'ansel_taginfo_' . (!is_null($tags) ? md5(serialize($tags) . $limit) : $limit);
- $cvalue = $GLOBALS['injector']->getInstance('Horde_Cache')->get($cache_key, $conf['cache']['default_lifetime']);
- if ($cvalue) {
- return unserialize($cvalue);
- }
- }
-
- $sql = 'SELECT tn.tag_id, tag_name, COUNT(tag_name) as total FROM ansel_tags as tn INNER JOIN (SELECT tag_id FROM ansel_galleries_tags UNION ALL SELECT tag_id FROM ansel_images_tags) as t ON t.tag_id = tn.tag_id ';
- if (!is_null($tags) && is_array($tags)) {
- $sql .= 'WHERE tn.tag_id IN (' . implode(',', $tags) . ') ';
- }
- $sql .= 'GROUP BY tn.tag_id, tag_name ORDER BY total DESC';
- if ($limit > 0) {
- $GLOBALS['ansel_db']->setLimit((int)$limit);
- }
-
- $results = $GLOBALS['ansel_db']->queryAll($sql, null, MDB2_FETCHMODE_ASSOC, true);
- foreach ($results as $id => $taginfo) {
- $results[$id]['tag_name'] = Horde_String::convertCharset(
- $taginfo['tag_name'], $GLOBALS['conf']['sql']['charset']);
- }
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['injector']->getInstance('Horde_Cache')->set($cache_key, serialize($results));
- }
-
- return $results;
- }
-
- /**
- * Search for resources matching the specified criteria
- *
- * @param array $ids An array of tag_ids to search for.
- * @param int $max The maximum number of resources to return.
- * @param int $from The number to start from
- * @param string $resource_type Either 'images', 'galleries', or 'all'.
- * @param string $user Limit the result set to resources
- * owned by this user.
- *
- * @return array An array of image_ids and gallery_ids
- */
- static public function searchTagsById($ids, $max = 0, $from = 0,
- $resource_type = 'all', $user = null)
- {
- if (!is_array($ids) || !count($ids)) {
- return array('galleries' => array(), 'images' => array());
- }
-
- $skey = md5(serialize($ids) . $from . $resource_type . $max . $user);
-
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $key = $GLOBALS['registry']->getAuth() . '__anseltagsearches';
- $cvalue = $GLOBALS['injector']->getInstance('Horde_Cache')->get($key, 300);
- $cvalue = @unserialize($cvalue);
- if (!$cvalue) {
- $cvalue = array();
- }
- if (!empty($cvalue[$skey])) {
- return $cvalue[$skey];
- }
- }
-
- $ids = array_values($ids);
- $results = array();
- /* Retrieve any images that match */
- if ($resource_type != 'galleries') {
- $sql = 'SELECT image_id, count(tag_id) FROM ansel_images_tags '
- . 'WHERE tag_id IN (' . implode(',', $ids) . ') GROUP BY '
- . 'image_id HAVING count(tag_id) = ' . count($ids);
-
- Horde::logMessage('SQL query by Ansel_Tags::searchTags: ' . $sql, 'DEBUG');
- $GLOBALS['ansel_db']->setLimit($max, $from);
- $images = $GLOBALS['ansel_db']->queryCol($sql);
- if ($images instanceof PEAR_Error) {
- Horde::logMessage($images, 'ERR');
- $results['images'] = array();
- } else {
- /* Check permissions and filter on $user if required */
- $imgs = array();
- foreach ($images as $id) {
- try {
- $img = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImage($id);
- $gal = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($img->gallery);
- $owner = $gal->get('owner');
- if ($gal->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW) &&
- (!isset($user) || (isset($user) && $owner && $owner == $user))) {
- $imgs[] = $id;
- }
- } catch (Ansel_Exception $e) {
- Horde::logMessage($e->getMessage(), 'ERR');
- break;
- }
- }
- $results['images'] = $imgs;
- }
- }
-
- /* Now get the galleries that match */
- if ($resource_type != 'images') {
- $results['galleries'] = array();
- $sql = 'SELECT gallery_id, count(tag_id) FROM ansel_galleries_tags '
- . 'WHERE tag_id IN (' . implode(',', $ids) . ') GROUP BY '
- . 'gallery_id HAVING count(tag_id) = ' . count($ids);
-
- Horde::logMessage('SQL query by Ansel_Tags::searchTags: ' . $sql, 'DEBUG');
- $GLOBALS['ansel_db']->setLimit($max, $from);
-
- $galleries = $GLOBALS['ansel_db']->queryCol($sql);
- if ($galleries instanceof PEAR_Error) {
- Horde::logMessage($galleries, 'ERR');
- } else {
- /* Check perms */
- foreach ($galleries as $id) {
- try {
- $gallery = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($id);
- } catch (Ansel_Exception $e) {
- Horde::logMessage($e->getMessage(), 'ERR');
- continue;
- }
- if ($gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW) && (!isset($user) || (isset($user) && $gallery->get('owner') && $gallery->get('owner') == $user))) {
- $results['galleries'][] = $id;
- }
- }
- }
- }
-
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $cvalue[$skey] = $results;
- $GLOBALS['injector']->getInstance('Horde_Cache')->set($key, serialize($cvalue));
- }
-
- return $results;
- }
-
- /**
- * Search for resources matching a specified set of tags
- * and optionally limit the result set to resources owned by
- * a specific user.
- *
- * @param array $names An array of tag strings to search for.
- * @param int $max The maximum number of resources to return.
- * @param int $from The resource to start at.
- * @param string $resource_type Either 'images', 'galleries', or 'all'.
- * @param string $user Limit the result set to resources owned by
- * specified user.
- *
- * @return mixed An array of image_ids and gallery_ids
- */
- static public function searchTags($names = array(), $max = 10, $from = 0,
- $resource_type = 'all', $user = null)
- {
- /* Get the tag_ids */
- $ids = Ansel_Tags::getTagIds($names);
- return Ansel_Tags::searchTagsbyId($ids, $max, $from, $resource_type,
- $user);
- }
-
- /**
- * Retrieve a set of tags with relationships to the specified set
- * of tags.
- *
- * @param array $tags An array of tag_ids
- *
- * @return mixed A hash of tag_id -> tag_name | PEAR_Error
- */
- static public function getRelatedTags($ids)
- {
- if (!count($ids)) {
- return array();
- }
-
- /* Build the monster SQL statement.*/
- $sql = 'SELECT DISTINCT t.tag_id, t.tag_name FROM ansel_images_tags as r, ansel_images as i, ansel_tags as t';
- for ($i = 0; $i < count($ids); $i++) {
- $sql .= ',ansel_images_tags as r' . $i;
- }
- $sql .= ' WHERE r.tag_id = t.tag_id AND r.image_id = i.image_id';
- for ($i = 0; $i < count($ids); $i++) {
- $sql .= ' AND r' . $i . '.image_id = r.image_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 */
- $imgtags = $GLOBALS['ansel_db']->queryAll($sql, null, MDB2_FETCHMODE_ASSOC, true);
-
- /* Now get the galleries. */
- $table = 'ansel_shares';
- $sql = 'SELECT DISTINCT t.tag_id, t.tag_name FROM ansel_galleries_tags as r, ' . $table . ' AS i, ansel_tags as t';
- for ($i = 0; $i < count($ids); $i++) {
- $sql .= ', ansel_galleries_tags as r' . $i;
- }
- $sql .= ' WHERE r.tag_id = t.tag_id AND r.gallery_id = i.share_id';
- for ($i = 0; $i < count($ids); $i++) {
- for ($i = 0; $i < count($ids); $i++) {
- $sql .= ' AND r' . $i . '.gallery_id = r.gallery_id AND r.tag_id != ' . (int)$ids[$i] . ' AND r' . $i . '.tag_id = ' . (int)$ids[$i];
- }
- }
- $galtags = $GLOBALS['ansel_db']->queryAll($sql, null, MDB2_FETCHMODE_ASSOC, true);
-
- /* Can't use array_merge here since it would renumber the array keys */
- foreach ($galtags as $id => $name) {
- if (empty($imgtags[$id])) {
- $imgtags[$id] = $name;
- }
- }
-
- /* Get an array of tag info sorted by total */
- $tagids = array_keys($imgtags);
- if (count($tagids)) {
- $imgtags = Ansel_Tags::listTagInfo($tagids);
- }
-
- return $imgtags;
- }
-
- /**
- * Get the URL for a tag link
- *
- * @param array $tags The tag ids to link to
- * @param string $action The action we want to perform with this tag.
- * @param string $owner The owner we want to filter the results by
- *
- * @return string The URL for this tag and action
- */
- static public function getTagLinks($tags, $action = 'add', $owner = null)
- {
- $results = array();
- foreach ($tags as $id => $taginfo) {
- $params = array('view' => 'Results',
- 'tag' => $taginfo['tag_name']);
- if (!empty($owner)) {
- $params['owner'] = $owner;
- }
- if ($action != 'add') {
- $params['actionID'] = $action;
- }
- $link = Ansel::getUrlFor('view', $params, true);
- $results[$id] = $link;
- }
-
- return $results;
- }
-
- /**
- * Get a list of tag_ids from a list of tag_names
- *
- * @param array $tags An array of tag_names
- *
- * @return array An array of tag_names => tag_ids
- */
- static public function getTagIds($tags)
- {
- if (!count($tags)) {
- return array();
- }
- $stmt = $GLOBALS['ansel_db']->prepare('SELECT ansel_tags.tag_name, ansel_tags.tag_id FROM ansel_tags WHERE ansel_tags.tag_name IN (' . str_repeat('?, ', count($tags) - 1) . '?)');
- $result = $stmt->execute(array_values($tags));
- $ids = $result->fetchAll(MDB2_FETCHMODE_ASSOC, true);
- $newIds = array();
- foreach ($ids as $tag => $id) {
- $newIds[Horde_String::convertCharset($tag, $GLOBALS['conf']['sql']['charset'])] = $id;
- }
- $result->free();
- $stmt->free();
-
- return $newIds;
- }
-
- /**
- * Get the tag names from ids
- *
- * @param array $ids An array of tag ids
- *
- * @return array A hash of tag_id => tag_names
- */
- static public function getTagNames($ids)
- {
- if (!count($ids)) {
- return array();
- }
- $stmt = $GLOBALS['ansel_db']->prepare('SELECT t.tag_id, t.tag_name FROM ansel_tags as t WHERE t.tag_id IN(' . str_repeat('?, ', count($ids) - 1) . '?)');
- $result = $stmt->execute(array_values($ids));
- $tags = $result->fetchAll(MDB2_FETCHMODE_ASSOC, true);
- foreach ($tags as $id => $tag) {
- $tags[$id] = Horde_String::convertCharset(
- $tag, $GLOBALS['conf']['sql']['charset']);
- }
- $result->free();
- $stmt->free();
-
- return $tags;
- }
-
- /**
- * Retrieve an Ansel_Tags_Search object
- *
- * @return Ansel_Tags_Search
- * @TODO: refactor into Ansel_Search
- */
- static public function getSearch($tags = null, $owner = null)
- {
- return new Ansel_Tags_Search($tags, $owner);
- }
-
- /**
- * Clear the session cache
- */
- static public function clearSearch()
- {
- unset($_SESSION['ansel_tags_search']);
- }
-
- static public function clearCache()
- {
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['injector']->getInstance('Horde_Cache')->expire($GLOBALS['registry']->getAuth() . '__anseltagsearches');
- }
- }
-}
-
-/**
- * Class that represents a slice of a tag search
- *
- * @TODO: Move this to Ansel_Search_Tags
- */
-class Ansel_Tags_Search {
-
- var $_tags = array();
- var $_totalCount = null;
- var $_owner = null;
- var $_dirty = false;
-
- /**
- * Constructor
- *
- * @param array $tags An array of tag_ids to match. If null is passed then
- * the tags will be loaded from the session.
- */
- function Ansel_Tags_Search($tags = null, $owner = null)
- {
- if (!empty($tags)) {
- $this->_tags = $tags;
- } else {
- $this->_tags = (!empty($_SESSION['ansel_tags_search']) ? $_SESSION['ansel_tags_search'] : array());
- }
-
- $this->_owner = $owner;
- }
-
- /**
- * Save the current search to the session
- */
- function save()
- {
- $_SESSION['ansel_tags_search'] = $this->_tags;
- $this->_dirty = false;
- }
-
- /**
- * Fetch the matching resources that should appear on the current page
- *
- * @return Array of Ansel_Images and Ansel_Galleries
- */
- function getSlice($page, $perpage)
- {
- global $conf, $registry;
-
- $results = array();
- $totals = $this->count();
-
- /* First, the galleries */
- $gstart = $page * $perpage;
- $gresults = Ansel_Tags::searchTagsById($this->_tags,
- $perpage,
- $gstart,
- 'galleries',
- $this->_owner);
- $galleries = array();
- foreach ($gresults['galleries'] as $gallery) {
- $galleries[] = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getGallery($gallery);
- }
-
- /* Do we need to get images? */
- $istart = max(0, $page * $perpage - $totals['galleries']);
- $count = $perpage - count($galleries);
- if ($count > 0) {
- $iresults = Ansel_Tags::searchTagsById($this->_tags,
- $count,
- $istart,
- 'images',
- $this->_owner);
-
- $images = count($iresults['images']) ? array_values($GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImages(array('ids' => $iresults['images']))) : array();
- if (($conf['comments']['allow'] == 'all' || ($conf['comments']['allow'] == 'authenticated' && $GLOBALS['registry']->getAuth())) &&
- $registry->hasMethod('forums/numMessagesBatch')) {
-
- $ids = array_keys($images);
- $ccounts = $GLOBALS['registry']->call('forums/numMessagesBatch', array($ids, 'ansel'));
- if (!($ccounts instanceof PEAR_Error)) {
- foreach ($images as $image) {
- $image->commentCount = (!empty($ccounts[$image->id]) ? $ccounts[$image->id] : 0);
- }
- }
- }
- } else {
- $images = array();
- }
- return array_merge($galleries, $images);
- }
-
- /**
- * Add a tag to the cumulative tag search
- */
- function addTag($tag_id)
- {
- if (array_search($tag_id, $this->_tags) === false) {
- $this->_tags[] = $tag_id;
- $this->_dirty = true;
- }
- }
-
- /**
- * Remove a tag from the cumulative search
- */
- function removeTag($tag_id)
- {
- $key = array_search($tag_id, $this->_tags);
- if ($tag_id === false) {
- return;
- } else {
- unset($this->_tags[$key]);
- $this->_tags = array_values($this->_tags);
- $this->_dirty = true;
- }
- }
-
- /**
- * Get the list of currently choosen tags
- */
- function getTags()
- {
- return $this->_tags;
- }
-
- /**
- * Get breadcrumb style navigation html for choosen tags
- *
- */
- function getTagTrail()
- {
- global $registry;
-
- $tags = Ansel_Tags::getTagNames($this->_tags);
- $html = '<ul class="tag-list">';
-
- /* Use the local cache to preserve the order */
- $count = 0;
- foreach ($this->_tags as $tagid) {
- $remove_url = Horde::applicationUrl('view.php', true)->add(
- array('view' => 'Results',
- 'tag' => $tags[$tagid],
- 'actionID' => 'remove'));
- if (!empty($this->_owner)) {
- $remove_url->add('owner', $this->_owner);
- }
- $delete_label = sprintf(_("Remove %s from search"), htmlspecialchars($tags[$tagid]));
- $html .= '<li>' . htmlspecialchars($tags[$tagid]) . $remove_url->link(array('title' => $delete_label)) . Horde::img('delete-small.png', $delete_label) . '</a></li>';
- }
-
- return $html . '</ul>';
- }
-
- /**
- * Get the total number of tags included in this search.
- */
- function tagCount()
- {
- return count($this->_tags);
- }
-
- /**
- * Get the total number of resources that match.
- *
- * @return array Hash containing totals for both 'galleries' and 'images'.
- */
- function count()
- {
- if (!is_array($this->_tags) || !count($this->_tags)) {
- return 0;
- }
-
- /* First see if we already calculated for the current page load */
- if ($this->_totalCount && !$this->_dirty) {
- return $this->_totalCount;
- }
-
- /* Can't perform a COUNT query since we have to check perms */
- $results = Ansel_Tags::searchTagsById($this->_tags, 0, 0, 'all',
- $this->_owner);
- $count = array('galleries' => count($results['galleries']), 'images' => count($results['images']));
- $this->_totalCount = $count;
- return $count;
- }
-
- /**
- * Get a list of tags related to this search
- */
- function getRelatedTags()
- {
- $tags = Ansel_Tags::getRelatedTags($this->getTags());
- /* Make sure that we have actual results for each tag since
- * some tags may exist on only images/galleries to which we
- * have no perms */
- $search = Ansel_Tags::getSearch(null, $this->_owner);
- $results = array();
- foreach ($tags as $id => $tag) {
- $search->addTag($id);
- $count = $search->count();
- if ($count['images'] + $count['galleries'] > 0) {
- $results[$id] = array('tag_name' => $tag['tag_name'], 'total' => $count['images'] + $count['galleries']);
- }
- $search->removeTag($id);
- }
-
- /* Get the results sorted by available totals for this user */
- uasort($results, array($this, '_sortTagInfo'));
- return $results;
- }
-
- /**
- */
- function _sortTagInfo($a, $b)
- {
- return $a['total'] < $b['total'];
- }
-
-}
/**
* The gallery id for this view's gallery
- *
+ * (Convienience instead of $this->view->gallery->id)
+ *
* @var integer
*/
- public $galleryId; // TODO: probably can remove this (get the id from the view's gallery)
-
+ public $galleryId;
/**
* Gallery slug for current gallery.
*
* The Ansel_View_Results:: class wraps display of images/galleries from
* multiple parent sources..
*
- * @author Michael Rubinsky <mrubinsk@horde.org>
- * @package Ansel
+ * Copyright 2007-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @category Horde
+ * @license http://www.fsf.org/copyleft/gpl.html GPL
+ * @package Ansel
*/
class Ansel_View_Results extends Ansel_View_Base
{
*/
protected $_owner;
+ /**
+ * The current page
+ *
+ * @var integer
+ */
private $_page;
+
+ /**
+ * Number of resources per page.
+ *
+ * @var integer
+ */
private $_perPage;
/**
$notification = $GLOBALS['injector']->getInstance('Horde_Notification');
$ansel_storage = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope();
- $this->_owner = Horde_Util::getFormData('owner', null);
- $this->_search = Ansel_Tags::getSearch(null, $this->_owner);
+ $this->_owner = Horde_Util::getFormData('owner', '');
+ //@TODO: Inject the search object when we have more then just a tag search
+ $this->_search = new Ansel_Search_Tag($GLOBALS['injector']->getInstance('Ansel_Tagger'), null, $this->_owner);
$this->_page = Horde_Util::getFormData('page', 0);
$action = Horde_Util::getFormData('actionID', '');
$image_id = Horde_Util::getFormData('image');
try {
$result = $gallery->removeImage($image);
$notification->push(_("Deleted the photo."), 'horde.success');
- Ansel_Tags::clearCache();
} catch (Ansel_Exception $e) {
$notification->push(
sprintf(_("There was a problem deleting photos: %s"), $e->getMessage()), 'horde.error');
case 'remove':
$tag = Horde_Util::getFormData('tag');
if (isset($tag)) {
- $tag = Ansel_Tags::getTagIds(array($tag));
- $tag = array_pop($tag);
$this->_search->removeTag($tag);
$this->_search->save();
}
default:
$tag = Horde_Util::getFormData('tag');
if (isset($tag)) {
- $tag = Ansel_Tags::getTagIds(array($tag));
- $tag = array_pop($tag);
$this->_search->addTag($tag);
$this->_search->save();
}
if ($conf['tags']['relatedtags']) {
$rtags = $this->_search->getRelatedTags();
$rtaghtml = '<ul>';
- $links = Ansel_Tags::getTagLinks($rtags, 'add');
+
+ $links = Ansel::getTagLinks($rtags, 'add');
foreach ($rtags as $id => $taginfo) {
if (!empty($this->_owner)) {
$links[$id]->add('owner', $this->_owner);
$vars = Horde_Variables::getDefaultVariables();
$option_move = $option_copy = $ansel_storage->countGalleries(Horde_Perms::EDIT);
-
$this->_pagestart = ($this->_page * $this->_perPage) + 1;
$this->_pageend = min($this->_pagestart + $numimages - 1, $this->_pagestart + $this->_perPage - 1);
$this->_pager = new Horde_Core_Ui_Pager('page', $vars, array('num' => $total,
// Generate the top ajax action links and attach the edit actions. Falls
// back on going to the find all faces in gallery page if no js...
// although, currently, *that* page requires js as well so...
- // TODO: A way to 'close', or go back to, the normal widget view.
if ($this->_view->gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
$link_text = (empty($images) ? _("Find faces") : _("Edit faces"));
$html .= Horde::applicationUrl('faces/gallery.php')->add('gallery', $this->_view->gallery->id)->link(
class Ansel_Widget_SimilarPhotos extends Ansel_Widget_Base
{
/**
- * @TODO
+ * Array of views that this widget may appear in.
*
* @var unknown_type
*/
*
* @TODO Rethink the way we determine if an image is related. This one is
* not ideal, as it just pops tags off the tag list until all the tags
- * match. This could miss many related images.
+ * match. This could miss many related images. Maybe make this random?
*
* @return string The HTML
*/
$html = '';
$tags = array_values($this->_view->resource->getTags());
- $imgs = Ansel_Tags::searchTags($tags);
-
+ $imgs = $GLOBALS['injector']->getInstance('Ansel_Tagger')->search($tags);
while (count($imgs['images']) <= 5 && count($tags)) {
array_pop($tags);
- $newImgs = Ansel_Tags::searchTags($tags);
+ $newImgs =$GLOBALS['injector']->getInstance('Ansel_Tagger')->search($tags);
$imgs['images'] = array_merge($imgs['images'], $newImgs['images']);
}
if (count($imgs['images'])) {
/* Clear the tag cache? */
if (Horde_Util::getFormData('havesearch', 0) == 0) {
- Ansel_Tags::clearSearch();
+ Ansel_Search_Tag::clearSearch();
}
+ $tagger = $GLOBALS['injector']->getInstance('Ansel_Tagger');
$hasEdit = $this->_view->gallery->hasPermission($GLOBALS['registry']->getAuth(),
Horde_Perms::EDIT);
$owner = $this->_view->gallery->get('owner');
- $tags = $this->_view->resource->getTags();
+ $tags = $tagger->getTags((int)$this->_view->resource->id, $this->_resourceType);
if (count($tags)) {
- $tags = Ansel_Tags::listTagInfo(array_keys($tags));
+ $tags = $tagger->getTagInfo(array_keys($tags), 500, $this->_resourceType);
}
- $links = Ansel_Tags::getTagLinks($tags, 'add', $owner);
+ $links = Ansel::getTagLinks($tags, 'add', $owner);
$html = '<ul>';
- foreach ($tags as $tag_id => $taginfo) {
- $html .= '<li>' . $links[$tag_id]->link(array('title' => sprintf(ngettext("%d photo", "%d photos", $taginfo['total']), $taginfo['total']))) . htmlspecialchars($taginfo['tag_name']) . '</a>' . ($hasEdit ? '<a href="#" onclick="removeTag(' . $tag_id . ');">' . Horde::img('delete-small.png', _("Remove Tag")) . '</a>' : '') . '</li>';
+ foreach ($tags as $taginfo) {
+ $tag_id = $taginfo['tag_id'];
+ $html .= '<li>' . $links[$tag_id]->link(array('title' => sprintf(ngettext("%d photo", "%d photos", $taginfo['count']), $taginfo['count']))) . htmlspecialchars($taginfo['tag_name']) . '</a>' . ($hasEdit ? '<a href="#" onclick="removeTag(' . $tag_id . ');">' . Horde::img('delete-small.png', _("Remove Tag")) . '</a>' : '') . '</li>';
}
$html .= '</ul>';
break;
case 'tag':
- $tag_id = array_values(Ansel_Tags::getTagIds(array($id)));
- $images = Ansel_Tags::searchTagsById($tag_id, 10, 0, 'images');
- $tag_id = array_pop($tag_id);
+ $filter = array('typeId' => 'image',
+ 'limit' => 10);
+ $images = $GLOBALS['injector']->getInstance('Ansel_Tagger')->search(array($id), $filter);
+
try {
$images = $GLOBALS['injector']->getInstance('Ansel_Storage')->getScope()->getImages(array('ids' => $images['images']));
} catch (Ansel_Exception $e) {
$images = array();
}
if (count($images)) {
- $tag_id = $tag_id[0];
$images = array_values($images);
$params = array('last_modified' => $images[0]->uploaded,
'name' => sprintf(_("Photos tagged with %s on %s"),
--- /dev/null
+#!/usr/bin/env php
+<?php
+/**
+ * Script for migrating Ansel 1.x tags to the Content_Tagger system in Ansel 2.
+ *
+ * Warning: This script may take a LONG time, depending on the number of users
+ * and images.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ */
+require_once dirname(__FILE__) . '/../../lib/Application.php';
+Horde_Registry::appInit('ansel', array('authentication' => 'none', 'cli' => true));
+
+/* Gallery tags */
+$sql = 'SELECT gallery_id, tag_name, share_owner FROM ansel_shares RIGHT JOIN '
+ . 'ansel_galleries_tags ON ansel_shares.share_id = ansel_galleries_tags.gallery_id '
+ . 'LEFT JOIN ansel_tags ON ansel_tags.tag_id = ansel_galleries_tags.tag_id;';
+
+// Maybe iterate over results and aggregate them by user and gallery so we can
+// tag all tags for a single gallery at once. Probably not worth it for a one
+// time upgrade script.
+$cli->message('Migrating gallery tags. This may take a while.', 'cli.message');
+$rows = $ansel_db->queryAll($sql);
+foreach ($rows as $row) {
+ $GLOBALS['injector']->getInstance('Ansel_Tagger')->tag($row[0], $row[1], $row[2], 'gallery');
+}
+$cli->message('Gallery tags finished.', 'cli.success');
+
+$sql = 'SELECT ansel_images.image_id, tag_name, share_owner FROM ansel_images '
+ . 'RIGHT JOIN ansel_images_tags ON ansel_images.image_id = ansel_images_tags.image_id '
+ . 'LEFT JOIN ansel_shares ON ansel_shares.share_id = ansel_images.gallery_id '
+ . 'LEFT JOIN ansel_tags ON ansel_tags.tag_id = ansel_images_tags.tag_id';
+$cli->message('Migrating image tags. This may take even longer...', 'cli.message');
+$rows = $ansel_db->queryAll($sql);
+foreach ($rows as $row) {
+ $GLOBALS['injector']->getInstance('Ansel_Tagger')->tag($row[0], $row[1], $row[2], 'image');
+}
+$cli->message('Image tags finished.', 'cli.success');
+
</td>
<td width="20%" valign="top">
<div id="anselWidgets">
- <?php /* Tags if we are using related */ if ($conf['tags']['relatedtags']): ?>
+ <?php if ($conf['tags']['relatedtags']): ?>
<div style="background-color:<?php echo $styleDef['background'] ?>;">
<h2 class="header tagTitle"><?php echo _("Related Tags") ?></h2>
<div id="tags"><?php echo $rtaghtml ?></div>
--- /dev/null
+<?php
+class Content_Exception extends Horde_Exception_Prior {
+}
\ No newline at end of file
foreach ($this->ensureTags($tags) as $tagId) {
try {
- $this->_db->insert('INSERT INTO ' . $this->_t('tagged') . ' (user_id, object_id, tag_id, created)
+ $this->_db->insert('INSERT INTO ' . $this->_t('tagged') . ' (user_id, object_id, tag_id, created)
VALUES (' . (int)$userId . ',' . (int)$objectId . ',' . (int)$tagId . ',' . $this->_db->quote($created) . ')');
} catch (Horde_Db_Exception $e) {
// @TODO should make sure it's a duplicate and re-throw if not
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));
if (!$args['objectId']) {
return array();
}
+
$sql = 'SELECT DISTINCT t.tag_id AS tag_id, tag_name FROM ' . $this->_t('tags') . ' AS t INNER JOIN ' . $this->_t('tagged') . ' AS 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']));
* 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.
+ * typeId Only return tags that have been applied by specific object types.
* objectId Only return tags that have been applied to a specific object.
+ * tagIds Only return information on specific tag (an array of tag names or tag ids)
*
* @return array An array of hashes, each containing tag_id, tag_name, and count.
*/
$sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' AS tagged INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id WHERE tagged.object_id = ' . (int)$args['objectId'] . ' GROUP BY t.tag_id';
} elseif (isset($args['userId']) && isset($args['typeId'])) {
$args['userId'] = current($this->_userManager->ensureUsers($args['userId']));
- $args['typeId'] = current($this->_typeManager->ensureTypes($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') . ' AS tagged INNER JOIN ' . $this->_t('objects') . ' AS objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId'] . ' INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id WHERE tagged.user_id = ' . (int)$args['user_id'] . ' GROUP BY t.tag_id';
+ $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' AS tagged INNER JOIN ' . $this->_t('objects') . ' AS objects ON tagged.object_id = objects.object_id AND objects.type_id IN (' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id WHERE tagged.user_id = ' . (int)$args['user_id'] . ' GROUP BY t.tag_id';
} 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') . ' AS tagged INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('user_tag_stats') . ' AS 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') . ' AS tagged INNER JOIN ' . $this->_t('objects') . ' AS objects ON tagged.object_id = objects.object_id AND objects.type_id IN(' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id AND t.tag_id IN (' . implode(', ', $args['tagIds']) . ') GROUP BY t.tag_id';
} elseif (isset($args['typeId'])) {
- $args['typeId'] = current($this->_typeManager->ensureTypes($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') . ' AS tagged INNER JOIN ' . $this->_t('objects') . ' AS objects ON tagged.object_id = objects.object_id AND objects.type_id = ' . (int)$args['typeId'] . ' INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id GROUP BY t.tag_id';
+ $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' AS tagged INNER JOIN ' . $this->_t('objects') . ' AS objects ON tagged.object_id = objects.object_id AND objects.type_id IN(' . implode(',', $args['typeId']) . ') INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id GROUP BY t.tag_id';
+ } 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') . ' AS tagged INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' AS ts ON t.tag_id = ts.tag_id WHERE t.tag_id IN (' . implode(', ', $ids) . ') GROUP BY t.tag_id';
} else {
- $sql = 'SELECT t.tag_id AS tag_id, tag_name, count FROM ' . $this->_t('tagged') . ' AS tagged INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' AS ts ON t.tag_id = ts.tag_id GROUP BY t.tag_id';
+ $sql = 'SELECT t.tag_id AS tag_id, tag_name, COUNT(*) AS count FROM ' . $this->_t('tagged') . ' AS tagged INNER JOIN ' . $this->_t('tags') . ' AS t ON tagged.tag_id = t.tag_id INNER JOIN ' . $this->_t('tag_stats') . ' AS ts ON t.tag_id = ts.tag_id GROUP BY t.tag_id';
}
if (isset($args['limit'])) {
$sql = $this->_db->addLimitOffset($sql . ' ORDER BY count DESC', array('limit' => $args['limit'], 'offset' => isset($args['offset']) ? $args['offset'] : 0));
}
- return $this->_db->selectAll($sql);
+ 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);
+ }
}
/**
if (array_key_exists('userId', $args)) {
$args['userId'] = $this->_userManager->ensureUsers($args['userId']);
- $sql .= ' AND tagged.user_id IN ( ' . implode(', ', $args['userId']) . ')';
+ $sql .= ' AND tagged.user_id IN (' . implode(', ', $args['userId']) . ')';
}
}
/**
* Check if tags exists, optionally create then if they don't and return
* ids for all that exist (including those that are optionally created).
- *
- * @param <type> $tags
- * @param <type> $create
- * @return <type> \
+ *
+ * @param string|array $tags The tag names to check.
+ * @param boolean $create If true, create the tag in the tags table.
+ *
+ * @return array
*/
protected function _checkTags($tags, $create = true)
{
}
/**
+ * 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 = $GLOBALS['ansel_db']->queryAll($sql, null, MDB2_FETCHMODE_ASSOC, true);
+
+ 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.