* @author Michael J. Rubinsky <mrubinsk@horde.org>
* @package Ansel
*/
-class Ansel {
-
+class Ansel
+{
/**
* Build initial Ansel javascript object.
*
}
}
-
-/**
- * Class to encapsulate a single gallery. Implemented as an extension of
- * the Horde_Share_Object class.
- *
- * @author Michael J. Rubinsky <mrubinsk@horde.org>
- * @package Ansel
- */
-class Ansel_Gallery extends Horde_Share_Object_sql_hierarchical {
-
- /**
- * Cache the Gallery Id - to match the Ansel_Image interface
- */
- var $id;
-
- /**
- * The gallery mode helper
- *
- * @var Ansel_Gallery_Mode object
- */
- var $_modeHelper;
-
- /**
- *
- */
- function __sleep()
- {
- $properties = get_object_vars($this);
- unset($properties['_shareOb']);
- unset($properties['_modeHelper']);
- $properties = array_keys($properties);
- return $properties;
- }
-
- function __wakeup()
- {
- $this->setShareOb($GLOBALS['ansel_storage']->shares);
- $mode = $this->get('view_mode');
- $this->_setModeHelper($mode);
- }
-
- /**
- * The Ansel_Gallery constructor.
- *
- * @param string $name The name of the gallery
- */
- function Ansel_Gallery($attributes = array())
- {
- /* Existing gallery? */
- if (!empty($attributes['share_id'])) {
- $this->id = (int)$attributes['share_id'];
- }
-
- /* Pass on up the chain */
- parent::Horde_Share_Object_sql_hierarchical($attributes);
- $this->setShareOb($GLOBALS['ansel_storage']->shares);
- $mode = isset($attributes['attribute_view_mode']) ? $attributes['attribute_view_mode'] : 'Normal';
- $this->_setModeHelper($mode);
- }
-
- /**
- * Check for special capabilities of this gallery.
- *
- */
- function hasFeature($feature)
- {
-
- // First check for purely Ansel_Gallery features
- // Currently we have none of these.
-
- // Delegate to the modeHelper
- return $this->_modeHelper->hasFeature($feature);
-
- }
-
- /**
- * Simple factory to retrieve the proper mode object.
- *
- * @param string $type The mode to use
- *
- * @return Ansel_Gallery_Mode object
- */
- function _setModeHelper($type = 'Normal')
- {
- $type = basename($type);
- $class = 'Ansel_GalleryMode_' . $type;
- $this->_modeHelper = new $class($this);
- $this->_modeHelper->init();
- }
-
- /**
- * Checks if the user can download the full photo
- *
- * @return boolean Whether or not user can download full photos
- */
- function canDownload()
- {
- if (Horde_Auth::getAuth() == $this->data['share_owner'] || Horde_Auth::isAdmin('ansel:admin')) {
- return true;
- }
-
- switch ($this->data['attribute_download']) {
- case 'all':
- return true;
-
- case 'authenticated':
- return Horde_Auth::isAuthenticated();
-
- case 'edit':
- return $this->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT);
-
- case 'hook':
- return Horde::callHook('_ansel_hook_can_download', array($this->id));
-
- default:
- return false;
- }
- }
-
- /**
- * Saves any changes to this object to the backend permanently.
- *
- * @return mixed true || PEAR_Error on failure.
- */
- function _save()
- {
- // Check for invalid characters in the slug.
- if (!empty($this->data['attribute_slug']) &&
- preg_match('/[^a-zA-Z0-9_@]/', $this->data['attribute_slug'])) {
-
- return PEAR::raiseError(
- sprintf(_("Could not save gallery, the slug, \"%s\", contains invalid characters."),
- $this->data['attribute_slug']));
- }
-
- // Check for slug uniqueness
- $slugGalleryId = $GLOBALS['ansel_storage']->slugExists($this->data['attribute_slug']);
- if ($slugGalleryId > 0 && $slugGalleryId <> $this->id) {
- return PEAR::raiseError(sprintf(_("Could not save gallery, the slug, \"%s\", already exists."),
- $this->data['attribute_slug']));
- }
-
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->expire('Ansel_Gallery' . $this->id);
- }
- return parent::_save();
- }
-
- /**
- * Update the gallery image count.
- *
- * @param integer $images Number of images in action
- * @param boolean $add Action to take (add or remove)
- * @param integer $gallery_id Gallery id to update images for
- */
- function _updateImageCount($images, $add = true, $gallery_id = null)
- {
- // We do the query directly here to avoid having to instantiate a
- // gallery object just to increment/decrement one value in the table.
- $sql = 'UPDATE ' . $this->_shareOb->_table
- . ' SET attribute_images = attribute_images '
- . ($add ? ' + ' : ' - ') . $images . ' WHERE share_id = '
- . ($gallery_id ? $gallery_id : $this->id);
-
- // Make sure to update the local value as well, so it doesn't get
- // overwritten by any other updates from ->set() calls.
- if (is_null($gallery_id) || $gallery_id === $this->id) {
- if ($add) {
- $this->data['attribute_images'] += $images;
- } else {
- $this->data['attribute_images'] -= $images;
- }
- }
-
- /* Need to expire the cache for the gallery that was changed */
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $id = (is_null($gallery_id) ? $this->id : $gallery_id);
- $GLOBALS['cache']->expire('Ansel_Gallery' . $id);
- }
-
- return $this->_shareOb->_write_db->exec($sql);
-
- }
-
- /**
- * Add an image to this gallery.
- *
- * @param array $image_data The image to add. Required keys include
- * 'image_caption', and 'data'. Optional keys
- * include 'image_filename' and 'image_type'
- *
- * @param boolean $default Make this image the new default tile image.
- *
- * @return integer The id of the new image.
- */
- function addImage($image_data, $default = false)
- {
- global $conf;
-
- /* Normal is the only view mode that can accurately update gallery counts */
- $vMode = $this->get('view_mode');
- if ($vMode != 'Normal') {
- $this->_setModeHelper('Normal');
- }
-
- $resetStack = false;
- if (!isset($image_data['image_filename'])) {
- $image_data['image_filename'] = 'Untitled';
- }
- $image_data['gallery_id'] = $this->id;
- $image_data['image_sort'] = $this->countImages();
-
- /* Create the image object */
- $image = new Ansel_Image($image_data);
- $result = $image->save();
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- if (empty($image_data['image_id'])) {
- $this->_updateImageCount(1);
- if ($this->countImages() < 5) {
- $resetStack = true;
- }
- }
-
- /* Should this be the default image? */
- if (!$default && $this->data['attribute_default_type'] == 'auto') {
- $this->data['attribute_default'] = $image->id;
- $resetStack = true;
- } elseif ($default) {
- $this->data['attribute_default'] = $image->id;
- $this->data['default_type'] = 'manual';
- }
-
- /* Reset the gallery default image stacks if needed. */
- if ($resetStack) {
- $this->clearStacks();
- }
-
- /* Update the modified flag and save gallery changes */
- $this->data['attribute_last_modified'] = time();
-
- /* Save all changes to the gallery */
- $this->save();
-
- /* Return to the proper view mode */
- if ($vMode != 'Normal') {
- $this->_setModeHelper($vMode);
- }
-
- /* Return the ID of the new image. */
- return $image->id;
- }
-
- /**
- * Clear all of this gallery's default image stacks from the VFS and the
- * gallery's data store.
- *
- */
- function clearStacks()
- {
- $ids = @unserialize($this->data['attribute_default_prettythumb']);
- if (is_array($ids)) {
- foreach ($ids as $imageId) {
- $this->removeImage($imageId, true);
- }
- }
-
- // Using the set function here so we can efficently update the db
- $this->set('default_prettythumb', '', true);
- }
-
- /**
- * Removes all generated and cached 'prettythumb' thumbnails for this
- * gallery
- *
- */
- function clearThumbs()
- {
- $images = $this->listImages();
- foreach ($images as $id) {
- $image = $this->getImage($id);
- $image->deleteCache('prettythumb');
- }
- }
-
- /**
- * Removes all generated and cached views for this gallery
- *
- */
- function clearViews()
- {
- $images = $this->listImages();
- foreach ($images as $id) {
- $image = $this->getImage($id);
- $image->deleteCache('all');
- }
- }
-
- /**
- * Move images from this gallery to a new gallery.
- *
- * @param array $images An array of image ids.
- * @param Ansel_Gallery $gallery The gallery to move the images to.
- *
- * @return integer | PEAR_Error The number of images moved, or an error message.
- */
- function moveImagesTo($images, $gallery)
- {
- return $this->_modeHelper->moveImagesTo($images, $gallery);
- }
-
- /**
- * Copy image and related data to specified gallery.
- *
- * @param array $images An array of image ids.
- * @param Ansel_Gallery $gallery The gallery to copy images to.
- *
- * @return integer | PEAR_Error The number of images copied or error message
- */
- function copyImagesTo($images, $gallery)
- {
- if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
- return PEAR::raiseError(
- sprintf(_("Access denied copying photos to \"%s\"."),
- $gallery->get('name')));
- }
-
- $db = $this->_shareOb->_write_db;
- $imgCnt = 0;
- foreach ($images as $imageId) {
- $img = &$this->getImage($imageId);
- // Note that we don't pass the tags when adding the image..see below
- $newId = $gallery->addImage(array(
- 'image_caption' => $img->caption,
- 'data' => $img->raw(),
- 'image_filename' => $img->filename,
- 'image_type' => $img->getType(),
- 'image_uploaded_date' => $img->uploaded));
- if (is_a($newId, 'PEAR_Error')) {
- return $newId;
- }
- /* 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 = $this->_shareOb->_write_db->prepare('INSERT INTO ansel_images_tags (image_id, tag_id) VALUES(' . $newId . ',?);');
- if (is_a($query, 'PEAR_Error')) {
- return $query;
- }
- foreach ($tags as $tag_id => $tag_name) {
- $result = $query->execute($tag_id);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- }
- $query->free();
-
- /* exif data */
- // First check to see if the exif data was present in the raw data.
- $count = $db->queryOne('SELECT COUNT(image_id) FROM ansel_image_attributes WHERE image_id = ' . (int) $newId . ';');
- if ($count == 0) {
- $exif = $db->queryAll('SELECT attr_name, attr_value FROM ansel_image_attributes WHERE image_id = ' . (int) $imageId . ';',null, MDB2_FETCHMODE_ASSOC);
- if (is_array($exif) && count($exif) > 0) {
- $insert = $db->prepare('INSERT INTO ansel_image_attributes (image_id, attr_name, attr_value) VALUES (?, ?, ?)');
- if (is_a($insert, 'PEAR_Error')) {
- return $insert;
- }
- foreach ($exif as $attr){
- $result = $insert->execute(array($newId, $attr['attr_name'], $attr['attr_value']));
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- }
- $insert->free();
- }
- }
- ++$imgCnt;
- }
-
- return $imgCnt;
- }
-
- /**
- * Set the order of an image in this gallery.
- *
- * @param integer $imageId The image to sort.
- * @param integer $pos The sort position of the image.
- */
- function setImageOrder($imageId, $pos)
- {
- return $this->_shareOb->_write_db->exec('UPDATE ansel_images SET image_sort = ' . (int)$pos . ' WHERE image_id = ' . (int)$imageId);
- }
-
- /**
- * Remove the given image from this gallery.
- *
- * @param mixed $image Image to delete. Can be an Ansel_Image
- * or an image ID.
- *
- * @return boolean True on success, false on failure.
- */
- function removeImage($image, $isStack = false)
- {
- return $this->_modeHelper->removeImage($image, $isStack);
- }
-
- /**
- * Returns this share's owner's Identity object.
- *
- * @return Identity object for the owner of this gallery.
- */
- function getOwner()
- {
- require_once 'Horde/Identity.php';
- $identity = Identity::singleton('none', $this->data['share_owner']);
- return $identity;
- }
-
- /**
- * Output the HTML for this gallery's tile.
- *
- * @param Ansel_Gallery $parent The parent Ansel_Gallery object
- * @param string $style A named gallery style to use.
- * @param boolean $mini Force the use of a mini thumbnail?
- * @param array $params Any additional parameters the Ansel_Tile
- * object may need.
- */
- function getTile($parent = null, $style = null, $mini = false,
- $params = array())
- {
- if (!is_null($parent) && is_null($style)) {
- $style = $parent->getStyle();
- } else {
- $style = Ansel::getStyleDefinition($style);
- }
-
- if (!empty($view_url)) {
- $view_url = str_replace('%g', $this->id, $view_url);
- }
-
- return Ansel_Tile_Gallery::getTile($this, $style, $mini, $params);
- }
-
- /**
- * Get the children of this gallery.
- *
- * @param integer $perm The permissions to limit to.
- * @param integer $from The child to start at.
- * @param integer $to The child to end with.
- * @param boolean $noauto Prevent auto
- *
- * @return A mixed array of Ansel_Gallery and Ansel_Image objects that are
- * children of this gallery.
- */
- function getGalleryChildren($perm = PERMS_SHOW, $from = 0, $to = 0, $noauto = true)
- {
- return $this->_modeHelper->getGalleryChildren($perm, $from, $to, $noauto);
- }
-
-
- /**
- * Return the count of this gallery's children
- *
- * @param integer $perm The permissions to require.
- * @param boolean $galleries_only Only include galleries, no images.
- *
- * @return integer The count of this gallery's children.
- */
- function countGalleryChildren($perm = PERMS_SHOW, $galleries_only = false, $noauto = true)
- {
- return $this->_modeHelper->countGalleryChildren($perm, $galleries_only, $noauto);
- }
-
- /**
- * Lists a slice of the image ids in this gallery.
- *
- * @param integer $from The image to start listing.
- * @param integer $count The numer of images to list.
- *
- * @return mixed An array of image_ids | PEAR_Error
- */
- function listImages($from = 0, $count = 0)
- {
- return $this->_modeHelper->listImages($from, $count);
- }
-
- /**
- * Gets a slice of the images in this gallery.
- *
- * @param integer $from The image to start fetching.
- * @param integer $count The numer of images to return.
- *
- * @param mixed An array of Ansel_Image objects | PEAR_Error
- */
- function getImages($from = 0, $count = 0)
- {
- return $this->_modeHelper->getImages($from, $count);
- }
-
- /**
- * Return the most recently added images in this gallery.
- *
- * @param integer $limit The maximum number of images to return.
- *
- * @return mixed An array of Ansel_Image objects | PEAR_Error
- */
- function getRecentImages($limit = 10)
- {
- return $GLOBALS['ansel_storage']->getRecentImages(array($this->id),
- $limit);
- }
-
- /**
- * Returns the image in this gallery corresponding to the given id.
- *
- * @param integer $id The ID of the image to retrieve.
- *
- * @return Ansel_Image The image object corresponding to the given id.
- */
- function &getImage($id)
- {
- return $GLOBALS['ansel_storage']->getImage($id);
- }
-
- /**
- * Checks if the gallery has any subgallery
- */
- function hasSubGalleries()
- {
- return $this->_modeHelper->hasSubGalleries();
- }
-
- /**
- * Returns the number of images in this gallery and, optionally, all
- * sub-galleries.
- *
- * @param boolean $subgalleries Determines whether subgalleries should
- * be counted or not.
- *
- * @return integer number of images in this gallery
- */
- function countImages($subgalleries = false)
- {
- return $this->_modeHelper->countImages($subgalleries);
- }
-
- /**
- * Returns the default image for this gallery.
- *
- * @param string $style Force the use of this style, if it's available
- * otherwise use whatever style is choosen for this
- * gallery. If prettythumbs are not available then
- * we always use ansel_default style.
- *
- * @return mixed The image_id of the default image or false.
- */
- function getDefaultImage($style = null)
- {
- // Check for explicitly requested style
- if (!is_null($style)) {
- $gal_style = Ansel::getStyleDefinition($style);
- } else {
- // Use gallery's default.
- $gal_style = $this->getStyle();
- if (!isset($GLOBALS['ansel_styles'][$gal_style['name']])) {
- $gal_style = $GLOBALS['ansel_styles']['ansel_default'];
- }
- }
- Horde::logMessage(sprintf("using gallery style: %s in Ansel::getDefaultImage()", $gal_style['name']), __FILE__, __LINE__, PEAR_LOG_DEBUG);
- if (!empty($gal_style['default_galleryimage_type']) &&
- $gal_style['default_galleryimage_type'] != 'plain') {
-
- $thumbstyle = $gal_style['default_galleryimage_type'];
- $styleHash = $this->_getViewHash($thumbstyle, $style);
-
- // First check for the existence of a default image in the style
- // we are looking for.
- if (!empty($this->data['attribute_default_prettythumb'])) {
- $thumbs = @unserialize($this->data['attribute_default_prettythumb']);
- }
- if (!isset($thumbs) || !is_array($thumbs)) {
- $thumbs = array();
- }
-
- if (!empty($thumbs[$styleHash])) {
- return $thumbs[$styleHash];
- }
-
- // Don't already have one, must generate it.
- $params = array('gallery' => $this, 'style' => $gal_style);
- $iview = Ansel_ImageView::factory(
- $gal_style['default_galleryimage_type'], $params);
-
- if (!is_a($iview, 'PEAR_Error')) {
- $img = $iview->create();
- if (!is_a($img, 'PEAR_Error')) {
- // Note the gallery_id is negative for generated stacks
- $iparams = array('image_filename' => $this->get('name'),
- 'image_caption' => $this->get('name'),
- 'data' => $img->raw(),
- 'image_sort' => 0,
- 'gallery_id' => -$this->id);
- $newImg = new Ansel_Image($iparams);
- $newImg->save();
- $prettyData = serialize(
- array_merge($thumbs,
- array($styleHash => $newImg->id)));
-
- $this->set('default_prettythumb', $prettyData, true);
- return $newImg->id;
- } else {
- Horde::logMessage($img, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- } else {
- // Might not support the requested style...try ansel_default
- // but protect against infinite recursion.
- Horde::logMessage($iview, __FILE__, __LINE__, PEAR_LOG_DEBUG);
- if ($style != 'ansel_default') {
- return $this->getDefaultImage('ansel_default');
- }
- Horde::logMessage($iview, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- } else {
- // We are just using an image thumbnail for the gallery default.
- if ($this->countImages()) {
- if (!empty($this->data['attribute_default']) &&
- $this->data['attribute_default'] > 0) {
-
- return $this->data['attribute_default'];
- }
- $keys = $this->listImages();
- if (is_a($keys, 'PEAR_Error')) {
- return $keys;
- }
- $this->data['attribute_default'] = $keys[count($keys) - 1];
- $this->data['attribute_default_type'] = 'auto';
- $this->save();
- return $keys[count($keys) - 1];
- }
-
- if ($this->hasSubGalleries()) {
- // Fall through to a default image of a sub gallery.
- $galleries = $GLOBALS['ansel_storage']->listGalleries(
- PERMS_SHOW, null, $this, false);
- if ($galleries && !is_a($galleries, 'PEAR_Error')) {
- foreach ($galleries as $galleryId => $gallery) {
- if ($default_img = $gallery->getDefaultImage($style)) {
- return $default_img;
- }
- }
- }
- }
- }
- return false;
- }
-
- /**
- * Returns this gallery's tags.
- */
- function getTags() {
- if ($this->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) {
- return Ansel_Tags::readTags($this->id, 'gallery');
- } else {
- return PEAR::raiseError(_("Access denied viewing this gallery."));
- }
- }
-
- /**
- * Set/replace this gallery's tags.
- *
- * @param array $tags AN array of tag names to associate with this image.
- */
- function setTags($tags)
- {
- if ($this->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
- return Ansel_Tags::writeTags($this->id, $tags, 'gallery');
- } else {
- return PEAR::raiseError(_("Access denied adding tags to this gallery."));
- }
- }
-
- /**
- * Return the style definition for this gallery. Returns the first available
- * style in this order: Explicitly configured style if available, if
- * configured style is not available, use ansel_default. If nothing has
- * been configured, the user's selected default is attempted.
- *
- * @return array The style definition array.
- */
- function getStyle()
- {
- if (empty($this->data['attribute_style'])) {
- $style = $GLOBALS['prefs']->getValue('default_gallerystyle');
- } else {
- $style = $this->data['attribute_style'];
- }
- return Ansel::getStyleDefinition($style);
-
- }
-
- /**
- * Return a hash key for the given view and style.
- *
- * @param string $view The view (thumb, prettythumb etc...)
- * @param string $style The named style.
- *
- * @return string A md5 hash suitable for use as a key.
- */
- function _getViewHash($view, $style = null)
- {
- if (is_null($style)) {
- $style = $this->getStyle();
- } else {
- $style = Ansel::getStyleDefinition($style);
- }
- if ($view != 'screen' && $view != 'thumb' && $view != 'mini' &&
- $view != 'full') {
-
- $view = md5($style['thumbstyle'] . '.' . $style['background']);
- }
- return $view;
- }
- /**
- * Checks to see if a user has a given permission.
- *
- * @param string $userid The userid of the user.
- * @param integer $permission A PERMS_* constant to test for.
- * @param string $creator The creator of the event.
- *
- * @return boolean Whether or not $userid has $permission.
- */
- function hasPermission($userid, $permission, $creator = null)
- {
- if ($userid == $this->data['share_owner'] ||
- Horde_Auth::isAdmin('ansel:admin')) {
-
- return true;
- }
-
-
- return $GLOBALS['perms']->hasPermission($this->getPermission(),
- $userid, $permission, $creator);
- }
-
- /**
- * Check user age limtation
- *
- * @return boolean
- */
- function isOldEnough()
- {
- if ($this->data['share_owner'] == Horde_Auth::getAuth() ||
- empty($GLOBALS['conf']['ages']['limits']) ||
- empty($this->data['attribute_age'])) {
-
- return true;
- }
-
- // Do we have the user age already cheked?
- if (!isset($_SESSION['ansel']['user_age'])) {
- $_SESSION['ansel']['user_age'] = 0;
- } elseif ($_SESSION['ansel']['user_age'] >= $this->data['attribute_age']) {
- return true;
- }
-
- // Can we hook user's age?
- if ($GLOBALS['conf']['ages']['hook'] && Horde_Auth::isAuthenticated()) {
- $result = Horde::callHook('_ansel_hook_user_age');
- if (is_int($result)) {
- $_SESSION['ansel']['user_age'] = $result;
- }
- }
-
- return ($_SESSION['ansel']['user_age'] >= $this->data['attribute_age']);
- }
-
- /**
- * Determine if we need to unlock a password protected gallery
- *
- * @return boolean
- */
- function hasPasswd()
- {
- if (Horde_Auth::getAuth() == $this->get('owner') || Horde_Auth::isAdmin('ansel:admin')) {
- return false;
- }
-
- $passwd = $this->get('passwd');
- if (empty($passwd) ||
- (!empty($_SESSION['ansel']['passwd'][$this->id])
- && $_SESSION['ansel']['passwd'][$this->id] = md5($this->get('passwd')))) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Sets this gallery's parent gallery.
- *
- * @TODO: Check how this interacts with date galleries - shouldn't be able
- * to remove a subgallery from a date gallery anyway, but just incase
- * @param mixed $parent An Ansel_Gallery or a gallery_id.
- *
- * @return mixed Ture || PEAR_Error
- */
- function setParent($parent)
- {
- /* Make sure we have a gallery object */
- if (!is_null($parent) && !is_a($parent, 'Ansel_Gallery')) {
- $parent = $GLOBALS['ansel_storage']->getGallery($parent);
- if (is_a($parent, 'PEAR_Error')) {
- return $parent;
- }
- }
-
- /* Check this now since we don't know if we are updating the DB or not */
- $old = $this->getParent();
- $reset_has_subgalleries = false;
- if (!is_null($old)) {
- $cnt = $old->countGalleryChildren(PERMS_READ, true);
- if ($cnt == 1) {
- /* Count is 1, and we are about to delete it */
- $reset_has_subgalleries = true;
- }
- }
-
- /* Call the parent class method */
- $result = parent::setParent($parent);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- /* Tell the parent the good news */
- if (!is_null($parent) && !$parent->get('has_subgalleries')) {
- return $parent->set('has_subgalleries', '1', true);
- }
- Horde::logMessage('Ansel_Gallery parent successfully set', __FILE__,
- __LINE__, PEAR_LOG_DEBUG);
-
- /* Gallery parent changed, safe to change the parent's attributes */
- if ($reset_has_subgalleries) {
- $old->set('has_subgalleries', 0, true);
- }
-
- return true;
- }
-
- /**
- * Sets an attribute value in this object.
- *
- * @param string $attribute The attribute to set.
- * @param mixed $value The value for $attribute.
- * @param boolean $update Commit only this change to storage.
- *
- * @return mixed True if setting the attribute did succeed, a PEAR_Error
- * otherwise.
- */
- function set($attribute, $value, $update = false)
- {
- /* Translate the keys */
- if ($attribute == 'owner') {
- $driver_key = 'share_owner';
- } else {
- $driver_key = 'attribute_' . $attribute;
- }
-
- if ($driver_key == 'attribute_view_mode' &&
- !empty($this->data[$driver_key]) &&
- $value != $this->data[$driver_key]) {
-
- $mode = isset($attributes['attribute_view_mode']) ? $attributes['attribute_view_mode'] : 'Normal';
- $this->_setModeHelper($mode);
- }
-
- $this->data[$driver_key] = $value;
-
- /* Update the backend, but only this current change */
- if ($update) {
- $db = $this->_shareOb->_write_db;
- // Manually convert the charset since we're not going through save()
- $data = $this->_shareOb->_toDriverCharset(array($driver_key => $value));
- $query = $db->prepare('UPDATE ' . $this->_shareOb->_table . ' SET ' . $driver_key . ' = ? WHERE share_id = ?', null, MDB2_PREPARE_MANIP);
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->expire('Ansel_Gallery' . $this->id);
- }
- $result = $query->execute(array($data[$driver_key], $this->id));
- $query->free();
-
- return $result;
- }
-
- return true;
- }
-
- function setDate($date)
- {
- $this->_modeHelper->setDate($date);
- }
-
- function getDate()
- {
- return $this->_modeHelper->getDate();
- }
-
- /**
- * Get an array describing where this gallery is in a breadcrumb trail.
- *
- * @return An array of 'title' and 'navdata' hashes with the [0] element
- * being the deepest part.
- */
- function getGalleryCrumbData()
- {
- return $this->_modeHelper->getGalleryCrumbData();
- }
-
-}
-
-/**
- * Class to describe a single Ansel image.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @author Michael J. Rubinsky <mrubinsk@horde.org>
- * @package Ansel
- */
-class Ansel_Image {
-
- /**
- * @var integer The gallery id of this image's parent gallery
- */
- var $gallery;
-
- /**
- * @var Horde_Image Horde_Image object for this image.
- */
- var $_image;
-
- var $id = null;
- var $filename = 'Untitled';
- var $caption = '';
- var $type = 'image/jpeg';
-
- /**
- * timestamp of uploaded date
- *
- * @var integer
- */
- var $uploaded;
-
- var $sort;
- var $commentCount;
- var $facesCount;
- var $lat;
- var $lng;
- var $location;
- var $geotag_timestamp;
-
- var $_dirty;
-
-
- /**
- * Timestamp of original date.
- *
- * @var integer
- */
- var $originalDate;
-
- /**
- * Holds an array of tags for this image
- * @var array
- */
- var $_tags = array();
-
- var $_loaded = array();
- var $_data = array();
-
- /**
- * Cache the raw EXIF data locally
- *
- * @var array
- */
- var $_exif = array();
-
- /**
- * TODO: refactor Ansel_Image to use a ::get() method like Ansel_Gallery
- * instead of direct instance variable access and all the nonsense below.
- *
- * @param unknown_type $image
- * @return Ansel_Image
- */
- function Ansel_Image($image = array())
- {
- if ($image) {
- $this->filename = $image['image_filename'];
- $this->caption = $image['image_caption'];
- $this->sort = $image['image_sort'];
- $this->gallery = $image['gallery_id'];
-
- // New image?
- if (!empty($image['image_id'])) {
- $this->id = $image['image_id'];
- }
-
- if (!empty($image['data'])) {
- $this->_data['full'] = $image['data'];
- }
-
- if (!empty($image['image_uploaded_date'])) {
- $this->uploaded = $image['image_uploaded_date'];
- } else {
- $this->uploaded = time();
- }
-
- if (!empty($image['image_type'])) {
- $this->type = $image['image_type'];
- }
-
- if (!empty($image['tags'])) {
- $this->_tags = $image['tags'];
- }
-
- if (!empty($image['image_faces'])) {
- $this->facesCount = $image['image_faces'];
- }
-
- $this->location = !empty($image['image_location']) ? $image['image_location'] : '';
-
- // The following may have to be rewritten by EXIF.
- // EXIF requires both an image id and a stream, so we can't
- // get EXIF data before we save the image to the VFS.
- if (!empty($image['image_original_date'])) {
- $this->originalDate = $image['image_original_date'];
- } else {
- $this->originalDate = $this->uploaded;
- }
- $this->lat = !empty($image['image_latitude']) ? $image['image_latitude'] : '';
- $this->lng = !empty($image['image_longitude']) ? $image['image_longitude'] : '';
- $this->geotag_timestamp = !empty($image['image_geotag_date']) ? $image['image_geotag_date'] : '0';
- }
-
- $this->_image = Ansel::getImageObject();
- $this->_image->reset();
- }
-
- /**
- * Return the vfs path for this image.
- *
- * @param string $view The view we want.
- * @param string $style A named gallery style.
- *
- * @return string The vfs path for this image.
- */
- function getVFSPath($view = 'full', $style = null)
- {
- $view = $this->_getViewHash($view, $style);
- return '.horde/ansel/'
- . substr(str_pad($this->id, 2, 0, STR_PAD_LEFT), -2)
- . '/' . $view;
- }
-
- /**
- * Returns the file name of this image as used in the VFS backend.
- *
- * @return string This image's VFS file name.
- */
- function getVFSName($view)
- {
- $vfsname = $this->id;
-
- if ($view == 'full' && $this->type) {
- $type = strpos($this->type, '/') === false ? 'image/' . $this->type : $this->type;
- if ($ext = Horde_Mime_Magic::mimeToExt($type)) {
- $vfsname .= '.' . $ext;
- }
- } elseif (($GLOBALS['conf']['image']['type'] == 'jpeg') || $view == 'screen') {
- $vfsname .= '.jpg';
- } else {
- $vfsname .= '.png';
- }
-
- return $vfsname;
- }
-
- /**
- * Loads the given view into memory.
- *
- * @param string $view Which view to load.
- * @param string $style The named gallery style.
- *
- * @return mixed True || PEAR_Error
- */
- function load($view = 'full', $style = null)
- {
- // If this is a new image that hasn't been saved yet, we will
- // already have the full data loaded. If we auto-rotate the image
- // then there is no need to save it just to load it again.
- if ($view == 'full' && !empty($this->_data['full'])) {
- $this->_image->loadString('original', $this->_data['full']);
- $this->_loaded['full'] = true;
- return true;
- }
-
- $viewHash = $this->_getViewHash($view, $style);
- /* If we've already loaded the data, just return now. */
- if (!empty($this->_loaded[$viewHash])) {
- return true;
- }
-
- $result = $this->createView($view, $style);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- /* If createView() had to resize the full image, we've already
- * loaded the data, so return now. */
- if (!empty($this->_loaded[$viewHash])) {
- return;
- }
-
- /* We've definitely successfully loaded the image now. */
- $this->_loaded[$viewHash] = true;
-
- /* Get the VFS info. */
- $vfspath = $this->getVFSPath($view, $style);
- if (is_a($vfspath, 'PEAR_Error')) {
- return $vfspath;
- }
-
- /* Read in the requested view. */
- $data = $GLOBALS['ansel_vfs']->read($vfspath, $this->getVFSName($view));
- if (is_a($data, 'PEAR_Error')) {
- Horde::logMessage($date, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $data;
- }
-
- $this->_data[$viewHash] = $data;
- $this->_image->loadString($vfspath . '/' . $this->id, $data);
- return true;
- }
-
- /**
- * Check if an image view exists and returns the vfs name complete with
- * the hash directory name prepended if appropriate.
- *
- * @param integer $id Image id to check
- * @param string $view Which view to check for
- * @param string $style A named gallery style
- *
- * @return mixed False if image does not exists | string vfs name
- *
- * @static
- */
- function viewExists($id, $view, $style)
- {
- /* We cannot check empty styles since we cannot get the hash */
- if (empty($style)) {
- return false;
- }
-
- /* Get the VFS path. */
- $view = Ansel_Gallery::_getViewHash($view, $style);
-
- /* Can't call the various vfs methods here, since this method needs
- to be called statically */
- $vfspath = '.horde/ansel/' . substr(str_pad($id, 2, 0, STR_PAD_LEFT), -2) . '/' . $view;
-
- /* Get VFS name */
- $vfsname = $id . '.';
- if ($GLOBALS['conf']['image']['type'] == 'jpeg' || $view == 'screen') {
- $vfsname .= 'jpg';
- } else {
- $vfsname .= 'png';
- }
-
- if ($GLOBALS['ansel_vfs']->exists($vfspath, $vfsname)) {
- return $view . '/' . $vfsname;
- } else {
- return false;
- }
- }
-
- /**
- * Creates and caches the given view.
- *
- * @param string $view Which view to create.
- * @param string $style A named gallery style
- */
- function createView($view, $style = null)
- {
- // HACK: Need to replace the image object with a JPG typed image if
- // we are generating a screen image. Need to do the replacement
- // and do it *here* for BC reasons with Horde_Image...and this
- // needs to be done FIRST, since the view might already be cached
- // in the VFS.
- if ($view == 'screen' && $GLOBALS['conf']['image']['type'] != 'jpeg') {
- $this->_image = Ansel::getImageObject(array('type' => 'jpeg'));
- $this->_image->reset();
- }
-
- /* Get the VFS info. */
- $vfspath = $this->getVFSPath($view, $style);
- if ($GLOBALS['ansel_vfs']->exists($vfspath, $this->getVFSName($view))) {
- return true;
- }
-
- $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'),
- $this->getVFSName('full'));
- if (is_a($data, 'PEAR_Error')) {
- Horde::logMessage($data, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $data;
- }
- $this->_image->loadString($this->getVFSPath('full') . '/' . $this->id, $data);
- $styleDef = Ansel::getStyleDefinition($style);
- if ($view == 'prettythumb') {
- $viewType = $styleDef['thumbstyle'];
- } else {
- $viewType = $view;
- }
- $iview = Ansel_ImageView::factory($viewType, array('image' => $this,
- 'style' => $style));
-
- if (is_a($iview, 'PEAR_Error')) {
- // It could be we don't support the requested effect, try
- // ansel_default before giving up.
- if ($view == 'prettythumb') {
- $iview = Ansel_ImageView::factory(
- 'thumb', array('image' => $this,
- 'style' => 'ansel_default'));
-
- if (is_a($iview, 'PEAR_Error')) {
- return $iview;
- }
- }
- }
-
- $res = $iview->create();
- if (is_a($res, 'PEAR_Error')) {
- return $res;
- }
-
- $view = $this->_getViewHash($view, $style);
-
- $this->_data[$view] = $this->_image->raw();
- $this->_image->loadString($vfspath . '/' . $this->id,
- $this->_data[$view]);
- $this->_loaded[$view] = true;
- $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view),
- $this->_data[$view], true);
-
- // Autowatermark the screen view
- if ($view == 'screen' &&
- $GLOBALS['prefs']->getValue('watermark_auto') &&
- $GLOBALS['prefs']->getValue('watermark_text') != '') {
-
- $this->watermark('screen');
- $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view),
- $this->_image->_data);
- }
-
- return true;
- }
-
- /**
- * Writes the current data to vfs, used when creating a new image
- */
- function _writeData()
- {
- $this->_dirty = false;
- return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath('full'),
- $this->getVFSName('full'),
- $this->_data['full'], true);
- }
-
- /**
- * Change the image data. Deletes old cache and writes the new
- * data to the VFS. Used when updating an image
- *
- * @param string $data The new data for this image.
- * @param string $view If specified, the $data represents only this
- * particular view. Cache will not be deleted.
- */
- function updateData($data, $view = 'full')
- {
- if (is_a($data, 'PEAR_Error')) {
- return $data;
- }
-
- /* Delete old cached data if we are replacing the full image */
- if ($view == 'full') {
- $this->deleteCache();
- }
-
- return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath($view),
- $this->getVFSName($view),
- $data, true);
- }
-
- /**
- * Update the geotag data
- */
- function geotag($lat, $lng, $location = '')
- {
- $this->lat = $lat;
- $this->lng = $lng;
- $this->location = $location;
- $this->geotag_timestamp = time();
- $this->save();
- }
-
- /**
- * Save basic image details
- *
- * @TODO: Move all SQL queries to Ansel_Storage::?
- */
- function save()
- {
- /* If we have an id, then it's an existing image.*/
- if ($this->id) {
- $update = $GLOBALS['ansel_db']->prepare('UPDATE ansel_images SET image_filename = ?, image_type = ?, image_caption = ?, image_sort = ?, image_original_date = ?, image_latitude = ?, image_longitude = ?, image_location = ?, image_geotag_date = ? WHERE image_id = ?');
- if (is_a($update, 'PEAR_Error')) {
- Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $update;
- }
- $result = $update->execute(array(Horde_String::convertCharset($this->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
- $this->type,
- Horde_String::convertCharset($this->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
- $this->sort,
- $this->originalDate,
- $this->lat,
- $this->lng,
- $this->location,
- $this->geotag_timestamp,
- $this->id));
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR);
- } else {
- $update->free();
- }
- return $result;
- }
-
- /* Saving a new Image */
- if (!$this->gallery || !strlen($this->filename) || !$this->type) {
- $error = PEAR::raiseError(_("Incomplete photo"));
- Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
-
- /* Get the next image_id */
- $image_id = $GLOBALS['ansel_db']->nextId('ansel_images');
- if (is_a($image_id, 'PEAR_Error')) {
- return $image_id;
- }
-
- /* Prepare the SQL statement */
- $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_images (image_id, gallery_id, image_filename, image_type, image_caption, image_uploaded_date, image_sort, image_original_date, image_latitude, image_longitude, image_location, image_geotag_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
- if (is_a($insert, 'PEAR_Error')) {
- Horde::logMessage($insert, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $insert;
- }
-
- /* Perform the INSERT */
- $result = $insert->execute(array($image_id,
- $this->gallery,
- Horde_String::convertCharset($this->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
- $this->type,
- Horde_String::convertCharset($this->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
- $this->uploaded,
- $this->sort,
- $this->originalDate,
- $this->lat,
- $this->lng,
- $this->location,
- (empty($this->lat) ? 0 : $this->uploaded)));
- $insert->free();
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $result;
- }
-
- /* Keep the image_id */
- $this->id = $image_id;
-
- /* The EXIF functions require a stream, so we need to save before we read */
- $this->_writeData();
-
- /* Get the EXIF data if we are not a gallery key image. */
- if ($this->gallery > 0) {
- $needUpdate = $this->_getEXIF();
- }
-
- /* Create tags from exif data if desired */
- $fields = @unserialize($GLOBALS['prefs']->getValue('exif_tags'));
- if ($fields) {
- $this->_exifToTags($fields);
- }
-
- /* Save the tags */
- if (count($this->_tags)) {
- $result = $this->setTags($this->_tags);
- if (is_a($result, 'PEAR_Error')) {
- // Since we got this far, the image has been added, so
- // just log the tag failure.
- Horde::logMessage($result, __LINE__, __FILE__, PEAR_LOG_ERR);
- }
- }
-
- /* Save again if EXIF changed any values */
- if (!empty($needUpdate)) {
- $this->save();
- }
-
- return $this->id;
- }
-
- /**
- * Replace this image's image data.
- *
- */
- function replace($imageData)
- {
- /* Reset the data array and remove all cached images */
- $this->_data = array();
- $this->reset();
-
- /* Remove attributes */
- $result = $GLOBALS['ansel_db']->exec('DELETE FROM ansel_image_attributes WHERE image_id = ' . (int)$this->id);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERROR);
- return $result;
- }
- /* Load the new image data */
- $this->_getEXIF();
- $this->updateData($imageData);
-
- return true;
- }
-
- /**
- * Adds specified EXIF fields to this image's tags. Called during image
- * upload/creation.
- *
- * @param array $fields An array of EXIF fields to import as a tag.
- *
- */
- function _exifToTags($fields = array())
- {
- $tags = array();
- foreach ($fields as $field) {
- if (!empty($this->_exif[$field])) {
- if (substr($field, 0, 8) == 'DateTime') {
- $d = new Horde_Date(strtotime($this->_exif[$field]));
- $tags[] = $d->format("Y-m-d");
- } else {
- $tags[] = $this->_exif[$field];
- }
- }
- }
-
- $this->_tags = array_merge($this->_tags, $tags);
- }
-
- /**
- * Reads the EXIF data from the image and stores in _exif array() as well
- * also populates any local properties that come from the EXIF data.
- *
- * @return mixed true if any local properties were modified, false otherwise, PEAR_Error on failure
- */
- function _getEXIF()
- {
- /* Clear the local copy */
- $this->_exif = array();
-
- /* Get the data */
- $imageFile = $GLOBALS['ansel_vfs']->readFile($this->getVFSPath('full'),
- $this->getVFSName('full'));
- if (is_a($imageFile, 'PEAR_Error')) {
- return $imageFile;
- }
- $exif = Horde_Image_Exif::factory($GLOBALS['conf']['exif'], $GLOBALS['conf']['exif']['params']);
- $exif_fields = $exif->getData($imageFile);
-
- /* Flag to determine if we need to resave the image data */
- $needUpdate = false;
-
- /* Populate any local properties that come from EXIF
- * Save any geo data to a seperate table as well */
- if (!empty($exif_fields['GPSLatitude'])) {
- $this->lat = $exif_fields['GPSLatitude'];
- $this->lng = $exif_fields['GPSLongitude'];
- $this->geotag_timestamp = time();
- $needUpdate = true;
- }
-
- if (!empty($exif_fields['DateTimeOriginal'])) {
- $this->originalDate = $exif_fields['DateTimeOriginal'];
- $needUpdate = true;
- }
-
- /* Attempt to autorotate based on Orientation field */
- $this->_autoRotate();
-
- /* Save attributes. */
- $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_image_attributes (image_id, attr_name, attr_value) VALUES (?, ?, ?)');
- foreach ($exif_fields as $name => $value) {
- $result = $insert->execute(array($this->id, $name, Horde_String::convertCharset($value, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset'])));
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- /* Cache it locally */
- $this->_exif[$name] = Horde_Image_Exif::getHumanReadable($name, $value);
- }
- $insert->free();
-
-
- return $needUpdate;
- }
-
- /**
- * Autorotate based on EXIF orientation field. Updates the data in memory
- * only.
- *
- */
- function _autoRotate()
- {
- if (isset($this->_exif['Orientation']) && $this->_exif['Orientation'] != 1) {
- switch ($this->_exif['Orientation']) {
- case 2:
- $this->mirror();
- break;
-
- case 3:
- $this->rotate('full', 180);
- break;
-
- case 4:
- $this->mirror();
- $this->rotate('full', 180);
- break;
-
- case 5:
- $this->flip();
- $this->rotate('full', 90);
- break;
-
- case 6:
- $this->rotate('full', 90);
- break;
-
- case 7:
- $this->mirror();
- $this->rotate('full', 90);
- break;
-
- case 8:
- $this->rotate('full', 270);
- break;
- }
-
- if ($this->_dirty) {
- $this->_exif['Orientation'] = 1;
- $this->data['full'] = $this->raw();
- $this->_writeData();
- }
- }
- }
-
- /**
- * Reset the image, removing all loaded views.
- */
- function reset()
- {
- $this->_image->reset();
- $this->_loaded = array();
- }
-
- /**
- * Deletes the specified cache file.
- *
- * If none is specified, deletes all of the cache files.
- *
- * @param string $view Which cache file to delete.
- */
- function deleteCache($view = 'all')
- {
- /* Delete cached screen image. */
- if ($view == 'all' || $view == 'screen') {
- $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('screen'),
- $this->getVFSName('screen'));
- }
-
- /* Delete cached thumbnail. */
- if ($view == 'all' || $view == 'thumb') {
- $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('thumb'),
- $this->getVFSName('thumb'));
- }
-
- /* Delete cached mini image. */
- if ($view == 'all' || $view == 'mini') {
- $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('mini'),
- $this->getVFSName('mini'));
- }
-
- if ($view == 'all' || $view == 'prettythumb') {
-
- /* No need to try to delete a hash we already removed */
- $deleted = array();
-
- /* Need to generate hashes for each possible style */
- $styles = Horde::loadConfiguration('styles.php', 'styles', 'ansel');
- foreach ($styles as $style) {
- $hash = md5($style['thumbstyle'] . '.' . $style['background']);
- if (empty($deleted[$hash])) {
- $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath($hash),
- $this->getVFSName($hash));
- $deleted[$hash] = true;
- }
- }
- }
- }
-
- /**
- * Returns the raw data for the given view.
- *
- * @param string $view Which view to return.
- */
- function raw($view = 'full')
- {
- if ($this->_dirty) {
- return $this->_image->raw();
- } else {
- $this->load($view);
- return $this->_data[$view];
- }
- }
-
- /**
- * Sends the correct HTTP headers to the browser to download this image.
- *
- * @param string $view The view to download.
- */
- function downloadHeaders($view = 'full')
- {
- global $browser, $conf;
-
- $filename = $this->filename;
- if ($view != 'full') {
- if ($ext = Horde_Mime_Magic::mimeToExt('image/' . $conf['image']['type'])) {
- $filename .= '.' . $ext;
- }
- }
-
- $browser->downloadHeaders($filename);
- }
-
- /**
- * Display the requested view.
- *
- * @param string $view Which view to display.
- * @param string $style Force use of this gallery style.
- */
- function display($view = 'full', $style = null)
- {
- if ($view == 'full' && !$this->_dirty) {
-
- // Check full photo permissions
- $gallery = $GLOBALS['ansel_storage']->getGallery($this->gallery);
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
- if (!$gallery->canDownload()) {
- return PEAR::RaiseError(sprintf(_("Access denied downloading photos from \"%s\"."), $gallery->get('name')));
- }
-
- $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'),
- $this->getVFSName('full'));
-
- if (is_a($data, 'PEAR_Error')) {
- return $data;
- }
- echo $data;
- return;
- }
-
- if (is_a($result = $this->load($view, $style), 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $result;
- }
-
- $this->_image->display();
- }
-
- /**
- * Wraps the given view into a file.
- *
- * @param string $view Which view to wrap up.
- */
- function toFile($view = 'full')
- {
- if (is_a(($result = $this->load($view)), 'PEAR_Error')) {
- return $result;
- }
- return $this->_image->toFile($this->_dirty ? false : $this->_data[$view]);
- }
-
- /**
- * Returns the dimensions of the given view.
- *
- * @param string $view The view (size) to check dimensions for.
- */
- function getDimensions($view = 'full')
- {
- if (is_a(($result = $this->load($view)), 'PEAR_Error')) {
- return $result;
- }
- return $this->_image->getDimensions();
- }
-
- /**
- * Rotates the image.
- *
- * @param string $view The view (size) to work with.
- * @param integer $angle What angle to rotate the image by.
- */
- function rotate($view = 'full', $angle)
- {
- $this->load($view);
- $this->_dirty = true;
- $this->_image->rotate($angle);
- }
-
- function crop($x1, $y1, $x2, $y2)
- {
- $this->_dirty = true;
- $this->_image->crop($x1, $y1, $x2, $y2);
- }
-
- /**
- * Converts the image to grayscale.
- *
- * @param string $view The view (size) to work with.
- */
- function grayscale($view = 'full')
- {
- $this->load($view);
- $this->_dirty = true;
- $this->_image->grayscale();
- }
-
- /**
- * Watermarks the image.
- *
- * @param string $view The view (size) to work with.
- * @param string $watermark String to use as the watermark.
- */
- function watermark($view = 'full', $watermark = null, $halign = null,
- $valign = null, $font = null)
- {
- if (empty($watermark)) {
- $watermark = $GLOBALS['prefs']->getValue('watermark_text');
- }
-
- if (empty($halign)) {
- $halign = $GLOBALS['prefs']->getValue('watermark_horizontal');
- }
-
- if (empty($valign)) {
- $valign = $GLOBALS['prefs']->getValue('watermark_vertical');
- }
-
- if (empty($font)) {
- $font = $GLOBALS['prefs']->getValue('watermark_font');
- }
-
- if (empty($watermark)) {
- require_once 'Horde/Identity.php';
- $identity = Identity::singleton();
- $name = $identity->getValue('fullname');
- if (empty($name)) {
- $name = Horde_Auth::getAuth();
- }
- $watermark = sprintf(_("(c) %s %s"), date('Y'), $name);
- }
-
- $this->load($view);
- $this->_dirty = true;
- $params = array('text' => $watermark,
- 'halign' => $halign,
- 'valign' => $valign,
- 'fontsize' => $font);
- if (!empty($GLOBALS['conf']['image']['font'])) {
- $params['font'] = $GLOBALS['conf']['image']['font'];
- }
- $this->_image->addEffect('TextWatermark', $params);
- }
-
- /**
- * Flips the image.
- *
- * @param string $view The view (size) to work with.
- */
- function flip($view = 'full')
- {
- $this->load($view);
- $this->_dirty = true;
- $this->_image->flip();
- }
-
- /**
- * Mirrors the image.
- *
- * @param string $view The view (size) to work with.
- */
- function mirror($view = 'full')
- {
- $this->load($view);
- $this->_dirty = true;
- $this->_image->mirror();
- }
-
- /**
- * Returns this image's tags.
- *
- * @return mixed An array of tags | PEAR_Error
- * @see Ansel_Tags::readTags()
- */
- function getTags()
- {
- global $ansel_storage;
-
- if (count($this->_tags)) {
- return $this->_tags;
- }
- $gallery = $ansel_storage->getGallery($this->gallery);
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
- if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) {
- $res = Ansel_Tags::readTags($this->id);
- if (!is_a($res, 'PEAR_Error')) {
- $this->_tags = $res;
- return $this->_tags;
- } else {
- return $res;
- }
- } else {
- return PEAR::raiseError(_("Access denied viewing this photo."));
- }
- }
-
- /**
- * Set/replace this image's tags.
- *
- * @param array $tags An array of tag names to associate with this image.
- */
- function setTags($tags)
- {
- global $ansel_storage;
-
- $gallery = $ansel_storage->getGallery(abs($this->gallery));
- if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
- // Clear the local cache.
- $this->_tags = array();
- return Ansel_Tags::writeTags($this->id, $tags);
- } else {
- return PEAR::raiseError(_("Access denied adding tags to this photo."));
- }
- }
-
- /**
- * Get the Ansel_View_Image_Thumb object
- *
- * @param Ansel_Gallery $parent The parent Ansel_Gallery object.
- * @param string $style A named gallery style to use.
- * @param boolean $mini Force the use of a mini thumbnail?
- * @param array $params Any additional parameters the Ansel_Tile
- * object may need.
- *
- */
- function getTile($parent = null, $style = null, $mini = false,
- $params = array())
- {
- if (!is_null($parent) && is_null($style)) {
- $style = $parent->getStyle();
- } else {
- $style = Ansel::getStyleDefinition($style);
- }
-
- return Ansel_Tile_Image::getTile($this, $style, $mini, $params);
- }
-
- /**
- * Get the image type for the requested view.
- */
- function getType($view = 'full')
- {
- if ($view == 'full') {
- return $this->type;
- } elseif ($view == 'screen') {
- return 'image/jpg';
- } else {
- return 'image/' . $GLOBALS['conf']['image']['type'];
- }
- }
-
- /**
- * Return a hash key for the given view and style.
- *
- * @param string $view The view (thumb, prettythumb etc...)
- * @param string $style The named style.
- *
- * @return string A md5 hash suitable for use as a key.
- */
- function _getViewHash($view, $style = null)
- {
- global $ansel_storage;
-
- // These views do not care about style...just return the $view value.
- if ($view == 'screen' || $view == 'thumb' || $view == 'mini' ||
- $view == 'full') {
-
- return $view;
- }
- if (is_null($style)) {
- $gallery = $ansel_storage->getGallery(abs($this->gallery));
- if (is_a($gallery, 'PEAR_Error')) {
- return $gallery;
- }
- $style = $gallery->getStyle();
- } else {
- $style = Ansel::getStyleDefinition($style);
- }
-
- $view = md5($style['thumbstyle'] . '.' . $style['background']);
- return $view;
- }
-
- /**
- * Get the image attributes from the backend.
- *
- * @param Ansel_Image $image The image to retrieve attributes for.
- * attributes for.
- * @param boolean $format Format the EXIF data. If false, the raw data
- * is returned.
- *
- * @return array The EXIF data.
- * @static
- */
- function getAttributes($format = false)
- {
- $attributes = $GLOBALS['ansel_storage']->getImageAttributes($this->id);
- $fields = Horde_Image_Exif::getFields();
- $output = array();
-
- foreach ($fields as $field => $data) {
- if (!isset($attributes[$field])) {
- continue;
- }
- $value = Horde_Image_Exif::getHumanReadable($field, Horde_String::convertCharset($attributes[$field], $GLOBALS['conf']['sql']['charset']));
- if (!$format) {
- $output[$field] = $value;
- } else {
- $description = isset($data['description']) ? $data['description'] : $field;
- $output[] = '<td><strong>' . $description . '</strong></td><td>' . htmlspecialchars($value, ENT_COMPAT, Horde_Nls::getCharset()) . '</td>';
- }
- }
-
- return $output;
- }
-
-}
-
-/**
- * Class for interfacing with back end data storage.
- *
- * @author Michael J. Rubinsky <mrubinsk@horde.org>
- *
- * @package Ansel
- */
-class Ansel_Storage {
-
- var $_scope = 'ansel';
- var $_db = null;
- var $galleries = array();
-
- /**
- * The Horde_Shares object to use for this scope.
- *
- * @var Horde_Share
- */
- var $shares = null;
-
- /* Local cache of retrieved images */
- var $images = array();
-
- function Ansel_Storage($scope = null)
- {
- /* Check for a scope other than the default Ansel scope.*/
- if (!is_null($scope)) {
- $this->_scope = $scope;
- }
-
- /* This is the only supported share backend for Ansel */
- $this->shares = Horde_Share::singleton($this->_scope,
- 'sql_hierarchical');
-
- /* Ansel_Gallery is just a subclass of Horde_Share_Object */
- $this->shares->_shareObject = 'Ansel_Gallery';
-
- /* Database handle */
- $this->_db = $GLOBALS['ansel_db'];
- }
-
- /**
- * Create and initialise a new gallery object.
- *
- * @param array $attributes The gallery attributes
- * @param object Perms $perm The permissions for the gallery if the
- * defaults are not desirable.
- * @param mixed $parent The gallery id of the parent (if any)
- *
- * @return Ansel_Gallery A new gallery object or PEAR_Error.
- */
- function createGallery($attributes = array(), $perm = null, $parent = null)
- {
- /* Required values. */
- if (empty($attributes['owner'])) {
- $attributes['owner'] = Horde_Auth::getAuth();
- }
- if (empty($attributes['name'])) {
- $attributes['name'] = _("Unnamed");
- }
- if (empty($attributes['desc'])) {
- $attributes['desc'] = '';
- }
-
- /* Default values */
- $attributes['default_type'] = isset($attributes['default_type']) ? $attributes['default_type'] : 'auto';
- $attributes['default'] = isset($attributes['default']) ? (int)$attributes['default'] : 0;
- $attributes['default_prettythumb'] = isset($attributes['default_prettythumb']) ? $attributes['default_prettythumb'] : '';
- $attributes['style'] = isset($attributes['style']) ? $attributes['style'] : $GLOBALS['prefs']->getValue('default_gallerystyle');
- $attributes['category'] = isset($attributes['category']) ? $attributes['category'] : $GLOBALS['prefs']->getValue('default_category');
- $attributes['date_created'] = time();
- $attributes['last_modified'] = $attributes['date_created'];
- $attributes['images'] = isset($attributes['images']) ? (int)$attributes['images'] : 0;
- $attributes['slug'] = isset($attributes['slug']) ? $attributes['slug'] : '';
- $attributes['age'] = isset($attributes['age']) ? (int)$attributes['age'] : 0;
- $attributes['download'] = isset($attributes['download']) ? $attributes['download'] : $GLOBALS['prefs']->getValue('default_download');
- $attributes['view_mode'] = isset($attributes['view_mode']) ? $attributes['view_mode'] : 'Normal';
- $attributes['passwd'] = isset($attributes['passwd']) ? $attributes['passwd'] : '';
-
- /* Don't pass tags to the share creation method */
- if (isset($attributes['tags'])) {
- $tags = $attributes['tags'];
- unset($attributes['tags']);
- } else {
- $tags = array();
- }
-
- /* Check for slug uniqueness */
- if (!empty($attributes['slug']) &&
- $this->slugExists($attributes['slug'])) {
- return PEAR::raiseError(sprintf(_("The slug \"%s\" already exists."),
- $attributes['slug']));
- }
-
- /* Create the gallery */
- $gallery = $this->shares->newShare('');
- if (is_a($gallery, 'PEAR_Error')) {
- Horde::logMessage($gallery, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $gallery;
- }
- Horde::logMessage('New Ansel_Gallery object instantiated', __FILE__, __LINE__, PEAR_LOG_DEBUG);
-
- /* Set the gallery's parent if needed */
- if (!is_null($parent)) {
- $result = $gallery->setParent($parent);
-
- /* Clear the parent from the cache */
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->expire('Ansel_Gallery' . $parent);
- }
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $result;
- }
- }
-
- /* 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);
- }
-
- /* Save it to storage */
- $result = $this->shares->addShare($gallery);
- if (is_a($result, 'PEAR_Error')) {
- $error = sprintf(_("The gallery \"%s\" could not be created: %s"),
- $attributes['name'], $result->getMessage());
- Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
- return PEAR::raiseError($error);
- }
-
- /* Convenience */
- $gallery->id = $gallery->getId();
-
- /* Add default permissions. */
- if (empty($perm)) {
- $perm = $gallery->getPermission();
-
- /* Default permissions for logged in users */
- switch ($GLOBALS['prefs']->getValue('default_permissions')) {
- case 'read':
- $perms = PERMS_SHOW | PERMS_READ;
- break;
- case 'edit':
- $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT;
- break;
- case 'none':
- $perms = 0;
- break;
- }
- $perm->addDefaultPermission($perms, false);
-
- /* Default guest permissions */
- switch ($GLOBALS['prefs']->getValue('guest_permissions')) {
- case 'read':
- $perms = PERMS_SHOW | PERMS_READ;
- break;
- case 'none':
- default:
- $perms = 0;
- break;
- }
- $perm->addGuestPermission($perms, false);
-
- /* Default user groups permissions */
- switch ($GLOBALS['prefs']->getValue('group_permissions')) {
- case 'read':
- $perms = PERMS_SHOW | PERMS_READ;
- break;
- case 'edit':
- $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT;
- break;
- case 'delete':
- $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT | PERMS_DELETE;
- break;
- case 'none':
- default:
- $perms = 0;
- break;
- }
-
- if ($perms) {
- $groups = Group::singleton();
- $group_list = $groups->getGroupMemberships(Horde_Auth::getAuth());
- if (!is_a($group_list, 'PEAR_Error') && count($group_list)) {
- foreach ($group_list as $group_id => $group_name) {
- $perm->addGroupPermission($group_id, $perms, false);
- }
- }
- }
- }
- $gallery->setPermission($perm, true);
-
- /* Initial tags */
- if (count($tags)) {
- $gallery->setTags($tags);
- }
-
- return $gallery;
- }
-
- /**
- * Check that a slug exists.
- *
- * @param string $slug The slug name
- *
- * @return integer The share_id the slug represents, or 0 if not found.
- */
- function slugExists($slug)
- {
- // An empty slug should never match.
- if (!strlen($slug)) {
- return 0;
- }
-
- $stmt = $this->_db->prepare('SELECT share_id FROM '
- . $this->shares->_table . ' WHERE attribute_slug = ?');
-
- if (is_a($stmt, 'PEAR_Error')) {
- Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR);
- return 0;
- }
-
- $result = $stmt->execute($slug);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- }
- if (!$result->numRows()) {
- return 0;
- }
-
- $slug = $result->fetchRow();
-
- $result->free();
- $stmt->free();
-
- return $slug[0];
- }
-
- /**
- * Retrieve an Ansel_Gallery given the gallery's slug
- *
- * @param string $slug The gallery slug
- * @param array $overrides An array of attributes that should be overridden
- * when the gallery is returned.
- *
- * @return mixed Ansel_Gallery object | PEAR_Error
- */
- function &getGalleryBySlug($slug, $overrides = array())
- {
- $id = $this->slugExists($slug);
- if ($id) {
- return $this->getGallery($id, $overrides);
- } else {
- return PEAR::raiseError(sprintf(_("Gallery %s not found."), $slug));
- }
- }
-
- /**
- * Retrieve an Ansel_Gallery given the share id
- *
- * @param integer $gallery_id The share_id to fetch
- * @param array $overrides An array of attributes that should be
- * overridden when the gallery is returned.
- *
- * @return mixed Ansel_Gallery | PEAR_Error
- */
- function &getGallery($gallery_id, $overrides = array())
- {
- // avoid cache server hits
- if (isset($this->galleries[$gallery_id]) && !count($overrides)) {
- return $this->galleries[$gallery_id];
- }
-
- if (!count($overrides) && $GLOBALS['conf']['ansel_cache']['usecache'] &&
- ($gallery = $GLOBALS['cache']->get('Ansel_Gallery' . $gallery_id, $GLOBALS['conf']['cache']['default_lifetime'])) !== false) {
-
- $this->galleries[$gallery_id] = unserialize($gallery);
-
- return $this->galleries[$gallery_id];
- }
-
- $result = &$this->shares->getShareById($gallery_id);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- $this->galleries[$gallery_id] = &$result;
-
- // Don't cache if we have overridden anything
- if (!count($overrides)) {
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->set('Ansel_Gallery' . $gallery_id, serialize($result));
- }
- } else {
- foreach ($overrides as $key => $value) {
- $this->galleries[$gallery_id]->set($key, $value, false);
- }
- }
- return $this->galleries[$gallery_id];
- }
-
- /**
- * Retrieve an array of Ansel_Gallery objects for the given slugs.
- *
- * @param array $slugs The gallery slugs
- *
- * @return mixed Array of Ansel_Gallery objects | PEAR_Error
- */
- function getGalleriesBySlugs($slugs)
- {
- $sql = 'SELECT share_id FROM ' . $this->shares->_table
- . ' WHERE attribute_slug IN (' . str_repeat('?, ', count($slugs) - 1) . '?)';
-
- $stmt = $this->shares->_db->prepare($sql);
- if (is_a($stmt, 'PEAR_Error')) {
- return $stmt;
- }
- $result = $stmt->execute($slugs);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
- $ids = array_values($result->fetchCol());
- $shares = $this->shares->getShares($ids);
-
- $stmt->free();
- $result->free();
-
- return $shares;
- }
-
- /**
- * Retrieve an array of Ansel_Gallery objects for the requested ids
- */
- function getGalleries($ids)
- {
- return $this->shares->getShares($ids);
- }
-
- /**
- * Empties a gallery of all images.
- *
- * @param Ansel_Gallery $gallery The ansel gallery to empty.
- */
- function emptyGallery($gallery)
- {
- $images = $gallery->listImages();
- foreach ($images as $image) {
- // Pretend we are a stack so we don't update the images count
- // for every image deletion, since we know the end result will
- // be zero.
- $gallery->removeImage($image, true);
- }
- $gallery->set('images', 0, true);
-
- // Clear the OtherGalleries widget cache
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->expire('Ansel_OtherGalleries' . $gallery->get('owner'));
- }
-
- }
-
- /**
- * Removes an Ansel_Gallery.
- *
- * @param Ansel_Gallery $gallery The gallery to delete
- *
- * @return mixed True || PEAR_Error
- */
- function removeGallery($gallery)
- {
- /* Get any children and empty them */
- $children = $gallery->getChildren(null, true);
- if (is_a($children, 'PEAR_Error')) {
- return $children;
- }
- foreach ($children as $child) {
- $this->emptyGallery($child);
- $child->setTags(array());
- }
-
- /* Now empty the selected gallery of images */
- $this->emptyGallery($gallery);
-
- /* Clear all the tags. */
- $gallery->setTags(array());
-
- /* Get the parent, if it exists, before we delete the gallery. */
- $parent = $gallery->getParent();
- $id = $gallery->id;
-
- /* Delete the gallery from storage */
- $result = $this->shares->removeShare($gallery);
- if (is_a($result, 'PEAR_Error')) {
- return $result;
- }
-
- /* Expire the cache */
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->expire('Ansel_Gallery' . $id);
- }
- unset($this->galleries[$id]);
-
- /* See if we need to clear the has_subgalleries field */
- if (is_a($parent, 'Ansel_Gallery')) {
- if (!$parent->countChildren(PERMS_SHOW, false)) {
- $parent->set('has_subgalleries', 0, true);
-
- if ($GLOBALS['conf']['ansel_cache']['usecache']) {
- $GLOBALS['cache']->expire('Ansel_Gallery' . $parent->id);
- }
- unset($this->galleries[$id]);
- }
- }
-
- return true;
- }
-
- /**
- * Returns the image corresponding to the given id.
- *
- * @param integer $id The ID of the image to retrieve.
- *
- * @return Ansel_Image The image object corresponding to the given name.
- */
- function &getImage($id)
- {
- if (isset($this->images[$id])) {
- return $this->images[$id];
- }
-
- $q = $this->_db->prepare('SELECT ' . $this->_getImageFields() . ' FROM ansel_images WHERE image_id = ?');
- if (is_a($q, 'PEAR_Error')) {
- Horde::logMessage($q, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $q;
- }
- $result = $q->execute((int)$id);
- if (is_a($result, 'PEAR_Error')) {
- Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $result;
- }
- $image = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
- $q->free();
- $result->free();
- if (is_null($image)) {
- return PEAR::raiseError(_("Photo not found"));
- } elseif (is_a($image, 'PEAR_Error')) {
- Horde::logMessage($image, __FILE__, __LINE__, PEAR_LOG_ERR);
- return $image;
- } else {
- $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']);
- $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']);
- $this->images[$id] = new Ansel_Image($image);
-
- return $this->images[$id];
- }
- }
-
- /**
- * Returns the images corresponding to the given ids.
- *
- * @param array $ids An array of image ids.
- *
- * @return array of Ansel_Image objects.
- */
- function getImages($ids, $preserve_order = false)
- {
- if (is_array($ids) && count($ids) > 0) {
- $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images WHERE image_id IN (';
- $i = 1;
- $cnt = count($ids);
- foreach ($ids as $id) {
- $sql .= (int)$id . (($i++ < $cnt) ? ',' : ');');
- }
-
- $images = $this->_db->query($sql);
- if (is_a($images, 'PEAR_Error')) {
- return $images;
- } elseif ($images->numRows() == 0) {
- $images->free();
- return PEAR::raiseError(_("Photos not found"));
- }
-
- $return = array();
- while ($image = $images->fetchRow(MDB2_FETCHMODE_ASSOC)) {
- $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']);
- $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']);
- $return[$image['image_id']] = new Ansel_Image($image);
- $this->images[(int)$image['image_id']] = &$return[$image['image_id']];
- }
- $images->free();
-
- /* Need to get comment counts if comments are enabled */
- $ccounts = $this->_getImageCommentCounts(array_keys($return));
- if (!is_a($ccounts, 'PEAR_Error') && count($ccounts)) {
- foreach ($return as $key => $image) {
- $return[$key]->commentCount = (!empty($ccounts[$key]) ? $ccounts[$key] : 0);
- }
- }
-
- /* Preserve the order the ids were passed in) */
- if ($preserve_order) {
- foreach ($ids as $id) {
- $ordered[$id] = $return[$id];
- }
- return $ordered;
- }
- return $return;
- } else {
- return array();
- }
- }
-
- function _getImageCommentCounts($ids)
- {
- global $conf, $registry;
-
- /* Need to get comment counts if comments are enabled */
- if (($conf['comments']['allow'] == 'all' || ($conf['comments']['allow'] == 'authenticated' && Horde_Auth::getAuth())) &&
- $registry->hasMethod('forums/numMessagesBatch')) {
-
- return $registry->call('forums/numMessagesBatch',
- array($ids, 'ansel'));
- }
-
- return array();
- }
-
- /**
- * Return a list of image ids of the most recently added images.
- *
- * @param array $galleries An array of gallery ids to search in. If
- * left empty, will search all galleries
- * with PERMS_SHOW.
- * @param integer $limit The maximum number of images to return
- * @param string $slugs An array of gallery slugs.
- * @param string $where Additional where clause
- *
- * @return array An array of Ansel_Image objects
- */
- function getRecentImages($galleries = array(), $limit = 10, $slugs = array())
- {
- $results = array();
-
- if (!count($galleries) && !count($slugs)) {
- $sql = 'SELECT DISTINCT ' . $this->_getImageFields('i') . ' FROM ansel_images i, '
- . str_replace('WHERE' , ' WHERE i.gallery_id = s.share_id AND (', substr($this->shares->_getShareCriteria(Horde_Auth::getAuth()), 5)) . ')';
- } elseif (!count($slugs) && count($galleries)) {
- // Searching by gallery_id
- $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images '
- . 'WHERE gallery_id IN ('
- . str_repeat('?, ', count($galleries) - 1) . '?) ';
- } elseif (count($slugs)) {
- // Searching by gallery_slug so we need to join the share table
- $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images LEFT JOIN '
- . $this->shares->_table . ' ON ansel_images.gallery_id = '
- . $this->shares->_table . '.share_id ' . 'WHERE attribute_slug IN ('
- . str_repeat('?, ', count($slugs) - 1) . '?) ';
- } else {
- return array();
- }
-
- $sql .= ' ORDER BY image_uploaded_date DESC LIMIT ' . (int)$limit;
- $query = $this->_db->prepare($sql);
- if (is_a($query, 'PEAR_Error')) {
- return $query;
- }
-
- if (count($slugs)) {
- $images = $query->execute($slugs);
- } else {
- $images = $query->execute($galleries);
- }
- $query->free();
- if (is_a($images, 'PEAR_Error')) {
- return $images;
- } elseif ($images->numRows() == 0) {
- return array();
- }
-
- while ($image = $images->fetchRow(MDB2_FETCHMODE_ASSOC)) {
- $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']);
- $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']);
- $results[] = new Ansel_Image($image);
- }
-
- $images->free();
- return $results;
- }
-
- /**
- * Check if a gallery exists. Need to do this here instead of Horde_Share
- * since Horde_Share::exists() takes a share_name, not a share_id plus we
- * might also be checking by gallery_slug and this is more efficient than
- * a listShares() call for one gallery.
- *
- * @param integer $gallery_id The gallery id
- * @param string $slug The gallery slug
- *
- * @return mixed true | false | PEAR_Error
- */
- function galleryExists($gallery_id, $slug = null)
- {
- if (empty($slug)) {
- return (bool)$this->_db->queryOne(
- 'SELECT COUNT(share_id) FROM ' . $this->shares->_table
- . ' WHERE share_id = ' . (int)$gallery_id);
- } else {
- return (bool)$this->slugExists($slug);
- }
- }
-
- /**
- * Return a list of categories containing galleries with the given
- * permissions for the current user.
- *
- * @param integer $perm The level of permissions required.
- * @param integer $from The gallery to start listing at.
- * @param integer $count The number of galleries to return.
- *
- * @return mixed List of categories | PEAR_Error
- */
- function listCategories($perm = PERMS_SHOW, $from = 0, $count = 0)
- {
- $sql = 'SELECT DISTINCT attribute_category FROM '
- . $this->shares->_table;
- $results = $this->shares->_db->query($sql);
- if (is_a($results, 'PEAR_Error')) {
- return $results;
- }
- $all_categories = $results->fetchCol('attribute_category');
- $results->free();
- if (count($all_categories) < $from) {
- return array();
- } else {
- $categories = array();
- foreach ($all_categories as $category) {
- $categories[] = Horde_String::convertCharset(
- $category, $GLOBALS['conf']['sql']['charset']);
- }
- if ($count > 0) {
- return array_slice($categories, $from, $count);
- } else {
- return array_slice($categories, $from);
- }
- }
- }
-
- function countCategories($perms = PERMS_SHOW)
- {
- return count($this->listCategories($perms));
- }
-
- /**
- * Return the count of galleries that the user has specified permissions to
- * and that match any of the requested attributes.
- *
- * @param string $userid The user to check access for.
- * @param integer $perm The level of permissions to require for a
- * gallery to return it.
- * @param mixed $attributes Restrict the galleries counted to those
- * matching $attributes. An array of
- * attribute/values pairs or a gallery owner
- * username.
- * @param string $parent The parent share to start counting at.
- * @param boolean $allLevels Return all levels, or just the direct
- * children of $parent? Defaults to all levels.
- */
- function countGalleries($userid, $perm = PERMS_SHOW, $attributes = null,
- $parent = null, $allLevels = true)
- {
- static $counts;
-
- if (is_a($parent, 'Ansel_Gallery')) {
- $parent_id = $parent->getId();
- } else {
- $parent_id = $parent;
- }
-
- $key = "$userid,$perm,$parent_id,$allLevels"
- . serialize($attributes);
- if (isset($counts[$key])) {
- return $counts[$key];
- }
-
- $count = $this->shares->countShares($userid, $perm, $attributes,
- $parent, $allLevels);
-
- $counts[$key] = $count;
-
- return $count;
- }
-
- /**
- * Retrieves the current user's gallery list from storage.
- *
- * @param integer $perm The level of permissions to require for a
- * gallery to return it.
- * @param mixed $attributes Restrict the galleries counted to those
- * matching $attributes. An array of
- * attribute/values pairs or a gallery owner
- * username.
- * @param mixed $parent The parent gallery to start listing at.
- * (Ansel_Gallery, gallery id or null)
- * @param boolean $allLevels Return all levels, or just the direct
- * children of $parent?
- * @param integer $from The gallery to start listing at.
- * @param integer $count The number of galleries to return.
- * @param string $sort_by The field to order the results by.
- * @param integer $direction Sort direction:
- * 0 - ascending
- * 1 - descending
- *
- * @return mixed An array of Ansel_Gallery objects | PEAR_Error
- */
- function listGalleries($perm = PERMS_SHOW,
- $attributes = null,
- $parent = null,
- $allLevels = true,
- $from = 0,
- $count = 0,
- $sort_by = null,
- $direction = 0)
- {
- return $this->shares->listShares(Horde_Auth::getAuth(), $perm, $attributes,
- $from, $count, $sort_by, $direction,
- $parent, $allLevels);
- }
-
- /**
- * Retrieve json data for an arbitrary list of image ids, not necessarily
- * from the same gallery.
- *
- * @param array $images An array of image ids
- * @param string $style A named gallery style to force if requesting
- * pretty thumbs.
- * @param boolean $full Generate full urls
- * @param string $image_view Which image view to use? screen, thumb etc..
- * @param boolean $view_links Include links to the image view
- *
- * @return string The json data || PEAR_Error
- */
- function getImageJson($images, $style = null, $full = false,
- $image_view = 'mini', $view_links = false)
- {
- $galleries = array();
- if (is_null($style)) {
- $style = 'ansel_default';
- }
-
- $json = array();
-
- foreach ($images as $id) {
- $image = $this->getImage($id);
- if (!is_a($image, 'PEAR_Error')) {
- $gallery_id = abs($image->gallery);
-
- if (empty($galleries[$gallery_id])) {
- $galleries[$gallery_id]['gallery'] = $GLOBALS['ansel_storage']->getGallery($gallery_id);
- if (is_a($galleries[$gallery_id]['gallery'], 'PEAR_Error')) {
- return $galleries[$gallery_id];
- }
- }
-
- // Any authentication that needs to take place for any of the
- // images included here MUST have already taken place or the
- // image will not be incldued in the output.
- if (!isset($galleries[$gallery_id]['perm'])) {
- $galleries[$gallery_id]['perm'] =
- ($galleries[$gallery_id]['gallery']->hasPermission(Horde_Auth::getAuth(), PERMS_READ) &&
- $galleries[$gallery_id]['gallery']->isOldEnough() &&
- !$galleries[$gallery_id]['gallery']->hasPasswd());
- }
-
- if ($galleries[$gallery_id]['perm']) {
- $data = array(Ansel::getImageUrl($image->id, $image_view, $full, $style),
- htmlspecialchars($image->filename, ENT_COMPAT, Horde_Nls::getCharset()),
- Horde_Text_Filter::filter($image->caption, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL)),
- $image->id,
- 0);
-
- if ($view_links) {
- $data[] = Ansel::getUrlFor('view',
- array('gallery' => $image->gallery,
- 'image' => $image->id,
- 'view' => 'Image',
- 'slug' => $galleries[$gallery_id]['gallery']->get('slug')),
- $full);
-
- $data[] = Ansel::getUrlFor('view',
- array('gallery' => $image->gallery,
- 'slug' => $galleries[$gallery_id]['gallery']->get('slug'),
- 'view' => 'Gallery'),
- $full);
- }
-
- $json[] = $data;
- }
- }
- }
-
- if (count($json)) {
- return Horde_Serialize::serialize($json, Horde_Serialize::JSON, Horde_Nls::getCharset());
- } else {
- return '';
- }
- }
-
- /**
- * Returns a random Ansel_Gallery from a list fitting the search criteria.
- *
- * @see Ansel_Storage::listGalleries()
- */
- function getRandomGallery($perm = PERMS_SHOW, $attributes = null,
- $parent = null, $allLevels = true)
- {
- $num_galleries = $this->countGalleries(Horde_Auth::getAuth(), $perm,
- $attributes, $parent,
- $allLevels);
- if (!$num_galleries) {
- return $num_galleries;
- }
-
- $galleries = $this->listGalleries($perm, $attributes, $parent,
- $allLevels,
- rand(0, $num_galleries - 1),
- 1);
- $gallery = array_pop($galleries);
- return $gallery;
- }
-
- /**
- * Lists a slice of the image ids in the given gallery.
- *
- * @param integer $gallery_id The gallery to list from.
- * @param integer $from The image to start listing.
- * @param integer $count The numer of images to list.
- * @param mixed $fields The fields to return (either an array of
- * fileds or a single string).
- * @param string $where A SQL where clause ($gallery_id will be
- * ignored if this is non-empty).
- * @param mixed $sort The field(s) to sort by.
- *
- * @return mixed An array of image_ids | PEAR_Error
- */
- function listImages($gallery_id, $from = 0, $count = 0,
- $fields = 'image_id', $where = '', $sort = 'image_sort')
- {
- if (is_array($fields)) {
- $field_count = count($fields);
- $fields = implode(', ', $fields);
- } elseif ($fields == '*') {
- // The count is not important, as long as it's > 1
- $field_count = 2;
- } else {
- $field_count = substr_count($fields, ',') + 1;
- }
-
- if (is_array($sort)) {
- $sort = implode(', ', $sort);
- }
-
- if (!empty($where)) {
- $query_where = 'WHERE ' . $where;
- } else {
- $query_where = 'WHERE gallery_id = ' . $gallery_id;
- }
- $this->_db->setLimit($count, $from);
- $sql = 'SELECT ' . $fields . ' FROM ansel_images ' . $query_where . ' ORDER BY ' . $sort;
- Horde::logMessage('Query by Ansel_Storage::listImages: ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG);
- $results = $this->_db->query('SELECT ' . $fields . ' FROM ansel_images '
- . $query_where . ' ORDER BY ' . $sort);
- if (is_a($results, 'PEAR_Error')) {
- return $results;
- }
- if ($field_count > 1) {
- return $results->fetchAll(MDB2_FETCHMODE_ASSOC, true, true, false);
- } else {
- return $results->fetchCol();
- }
- }
-
- /**
- * Return images' geolocation data.
- *
- * @param array $image_ids An array of image_ids to look up.
- * @param integer $gallery A gallery id. If this is provided, will return
- * all images in the gallery that have geolocation
- * data ($image_ids would be ignored).
- *
- * @return mixed An array of geodata || PEAR_Error
- */
- function getImagesGeodata($image_ids = array(), $gallery = null)
- {
- if ((!is_array($image_ids) || count($image_ids) == 0) && empty($gallery)) {
- return array();
- }
-
- if (!empty($gallery)) {
- $where = 'gallery_id = ' . (int)$gallery . ' AND LENGTH(image_latitude) > 0';
- } elseif (count($image_ids) > 0) {
- $where = 'image_id IN(' . implode(',', $image_ids) . ') AND LENGTH(image_latitude) > 0';
- } else {
- return array();
- }
-
- return $this->listImages(0, 0, 0, array('image_id as id', 'image_id', 'image_latitude', 'image_longitude', 'image_location'), $where);
- }
-
- /**
- * Get image attribtues from ansel_image_attributes table
- *
- * @param $image_id
- * @return unknown_type
- */
- function getImageAttributes($image_id)
- {
- return $GLOBALS['ansel_db']->queryAll('SELECT attr_name, attr_value FROM ansel_image_attributes WHERE image_id = ' . (int)$image_id, null, MDB2_FETCHMODE_ASSOC, true);
- }
-
- /**
- * Like getRecentImages, but returns geotag data for the most recently added
- * images from the current user. Useful for providing images to help locate
- * images at the same place.
- */
- function getRecentImagesGeodata($user = null, $start = 0, $count = 8)
- {
- $galleries = $this->listGalleries('PERMS_EDIT', $user);
- $where = 'gallery_id IN(' . implode(',', array_keys($galleries)) . ') AND LENGTH(image_latitude) > 0 GROUP BY image_latitude, image_longitude';
- return $this->listImages(0, $start, $count, array('image_id as id', 'image_id', 'gallery_id', 'image_latitude', 'image_longitude', 'image_location'), $where, 'image_geotag_date DESC');
- }
-
- function searchLocations($search = '')
- {
- $sql = 'SELECT DISTINCT image_location, image_latitude, image_longitude'
- . ' FROM ansel_images WHERE image_location LIKE "' . $search . '%"';
- $results = $this->_db->query($sql);
- if (is_a($results, 'PEAR_Error')) {
- return $results;
- }
-
- return $results->fetchAll(MDB2_FETCHMODE_ASSOC, true, true, false);
- }
-
- /**
- * Helper function to get a string of field names
- *
- * @return string
- */
- function _getImageFields($alias = '')
- {
- $fields = array('image_id', 'gallery_id', 'image_filename', 'image_type',
- 'image_caption', 'image_uploaded_date', 'image_sort',
- 'image_faces', 'image_original_date', 'image_latitude',
- 'image_longitude', 'image_location', 'image_geotag_date');
- if (!empty($alias)) {
- foreach ($fields as $field) {
- $new[] = $alias . '.' . $field;
- }
- return implode(', ', $new);
- }
-
- return implode(', ', $fields);
- }
-
-}
--- /dev/null
+<?php
+/**
+ * Class to encapsulate a single gallery. Implemented as an extension of
+ * the Horde_Share_Object class.
+ *
+ * Copyright 2001-2009 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>
+ * @package Ansel
+ */
+class Ansel_Gallery extends Horde_Share_Object_sql_hierarchical
+{
+ /**
+ * Cache the Gallery Id - to match the Ansel_Image interface
+ */
+ var $id;
+
+ /**
+ * The gallery mode helper
+ *
+ * @var Ansel_Gallery_Mode object
+ */
+ var $_modeHelper;
+
+ /**
+ *
+ */
+ function __sleep()
+ {
+ $properties = get_object_vars($this);
+ unset($properties['_shareOb']);
+ unset($properties['_modeHelper']);
+ $properties = array_keys($properties);
+ return $properties;
+ }
+
+ function __wakeup()
+ {
+ $this->setShareOb($GLOBALS['ansel_storage']->shares);
+ $mode = $this->get('view_mode');
+ $this->_setModeHelper($mode);
+ }
+
+ /**
+ * The Ansel_Gallery constructor.
+ *
+ * @param string $name The name of the gallery
+ */
+ function Ansel_Gallery($attributes = array())
+ {
+ /* Existing gallery? */
+ if (!empty($attributes['share_id'])) {
+ $this->id = (int)$attributes['share_id'];
+ }
+
+ /* Pass on up the chain */
+ parent::Horde_Share_Object_sql_hierarchical($attributes);
+ $this->setShareOb($GLOBALS['ansel_storage']->shares);
+ $mode = isset($attributes['attribute_view_mode']) ? $attributes['attribute_view_mode'] : 'Normal';
+ $this->_setModeHelper($mode);
+ }
+
+ /**
+ * Check for special capabilities of this gallery.
+ *
+ */
+ function hasFeature($feature)
+ {
+
+ // First check for purely Ansel_Gallery features
+ // Currently we have none of these.
+
+ // Delegate to the modeHelper
+ return $this->_modeHelper->hasFeature($feature);
+
+ }
+
+ /**
+ * Simple factory to retrieve the proper mode object.
+ *
+ * @param string $type The mode to use
+ *
+ * @return Ansel_Gallery_Mode object
+ */
+ function _setModeHelper($type = 'Normal')
+ {
+ $type = basename($type);
+ $class = 'Ansel_GalleryMode_' . $type;
+ $this->_modeHelper = new $class($this);
+ $this->_modeHelper->init();
+ }
+
+ /**
+ * Checks if the user can download the full photo
+ *
+ * @return boolean Whether or not user can download full photos
+ */
+ function canDownload()
+ {
+ if (Horde_Auth::getAuth() == $this->data['share_owner'] || Horde_Auth::isAdmin('ansel:admin')) {
+ return true;
+ }
+
+ switch ($this->data['attribute_download']) {
+ case 'all':
+ return true;
+
+ case 'authenticated':
+ return Horde_Auth::isAuthenticated();
+
+ case 'edit':
+ return $this->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT);
+
+ case 'hook':
+ return Horde::callHook('_ansel_hook_can_download', array($this->id));
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Saves any changes to this object to the backend permanently.
+ *
+ * @return mixed true || PEAR_Error on failure.
+ */
+ function _save()
+ {
+ // Check for invalid characters in the slug.
+ if (!empty($this->data['attribute_slug']) &&
+ preg_match('/[^a-zA-Z0-9_@]/', $this->data['attribute_slug'])) {
+
+ return PEAR::raiseError(
+ sprintf(_("Could not save gallery, the slug, \"%s\", contains invalid characters."),
+ $this->data['attribute_slug']));
+ }
+
+ // Check for slug uniqueness
+ $slugGalleryId = $GLOBALS['ansel_storage']->slugExists($this->data['attribute_slug']);
+ if ($slugGalleryId > 0 && $slugGalleryId <> $this->id) {
+ return PEAR::raiseError(sprintf(_("Could not save gallery, the slug, \"%s\", already exists."),
+ $this->data['attribute_slug']));
+ }
+
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->expire('Ansel_Gallery' . $this->id);
+ }
+ return parent::_save();
+ }
+
+ /**
+ * Update the gallery image count.
+ *
+ * @param integer $images Number of images in action
+ * @param boolean $add Action to take (add or remove)
+ * @param integer $gallery_id Gallery id to update images for
+ */
+ function _updateImageCount($images, $add = true, $gallery_id = null)
+ {
+ // We do the query directly here to avoid having to instantiate a
+ // gallery object just to increment/decrement one value in the table.
+ $sql = 'UPDATE ' . $this->_shareOb->_table
+ . ' SET attribute_images = attribute_images '
+ . ($add ? ' + ' : ' - ') . $images . ' WHERE share_id = '
+ . ($gallery_id ? $gallery_id : $this->id);
+
+ // Make sure to update the local value as well, so it doesn't get
+ // overwritten by any other updates from ->set() calls.
+ if (is_null($gallery_id) || $gallery_id === $this->id) {
+ if ($add) {
+ $this->data['attribute_images'] += $images;
+ } else {
+ $this->data['attribute_images'] -= $images;
+ }
+ }
+
+ /* Need to expire the cache for the gallery that was changed */
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $id = (is_null($gallery_id) ? $this->id : $gallery_id);
+ $GLOBALS['cache']->expire('Ansel_Gallery' . $id);
+ }
+
+ return $this->_shareOb->_write_db->exec($sql);
+
+ }
+
+ /**
+ * Add an image to this gallery.
+ *
+ * @param array $image_data The image to add. Required keys include
+ * 'image_caption', and 'data'. Optional keys
+ * include 'image_filename' and 'image_type'
+ *
+ * @param boolean $default Make this image the new default tile image.
+ *
+ * @return integer The id of the new image.
+ */
+ function addImage($image_data, $default = false)
+ {
+ global $conf;
+
+ /* Normal is the only view mode that can accurately update gallery counts */
+ $vMode = $this->get('view_mode');
+ if ($vMode != 'Normal') {
+ $this->_setModeHelper('Normal');
+ }
+
+ $resetStack = false;
+ if (!isset($image_data['image_filename'])) {
+ $image_data['image_filename'] = 'Untitled';
+ }
+ $image_data['gallery_id'] = $this->id;
+ $image_data['image_sort'] = $this->countImages();
+
+ /* Create the image object */
+ $image = new Ansel_Image($image_data);
+ $result = $image->save();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (empty($image_data['image_id'])) {
+ $this->_updateImageCount(1);
+ if ($this->countImages() < 5) {
+ $resetStack = true;
+ }
+ }
+
+ /* Should this be the default image? */
+ if (!$default && $this->data['attribute_default_type'] == 'auto') {
+ $this->data['attribute_default'] = $image->id;
+ $resetStack = true;
+ } elseif ($default) {
+ $this->data['attribute_default'] = $image->id;
+ $this->data['default_type'] = 'manual';
+ }
+
+ /* Reset the gallery default image stacks if needed. */
+ if ($resetStack) {
+ $this->clearStacks();
+ }
+
+ /* Update the modified flag and save gallery changes */
+ $this->data['attribute_last_modified'] = time();
+
+ /* Save all changes to the gallery */
+ $this->save();
+
+ /* Return to the proper view mode */
+ if ($vMode != 'Normal') {
+ $this->_setModeHelper($vMode);
+ }
+
+ /* Return the ID of the new image. */
+ return $image->id;
+ }
+
+ /**
+ * Clear all of this gallery's default image stacks from the VFS and the
+ * gallery's data store.
+ *
+ */
+ function clearStacks()
+ {
+ $ids = @unserialize($this->data['attribute_default_prettythumb']);
+ if (is_array($ids)) {
+ foreach ($ids as $imageId) {
+ $this->removeImage($imageId, true);
+ }
+ }
+
+ // Using the set function here so we can efficently update the db
+ $this->set('default_prettythumb', '', true);
+ }
+
+ /**
+ * Removes all generated and cached 'prettythumb' thumbnails for this
+ * gallery
+ *
+ */
+ function clearThumbs()
+ {
+ $images = $this->listImages();
+ foreach ($images as $id) {
+ $image = $this->getImage($id);
+ $image->deleteCache('prettythumb');
+ }
+ }
+
+ /**
+ * Removes all generated and cached views for this gallery
+ *
+ */
+ function clearViews()
+ {
+ $images = $this->listImages();
+ foreach ($images as $id) {
+ $image = $this->getImage($id);
+ $image->deleteCache('all');
+ }
+ }
+
+ /**
+ * Move images from this gallery to a new gallery.
+ *
+ * @param array $images An array of image ids.
+ * @param Ansel_Gallery $gallery The gallery to move the images to.
+ *
+ * @return integer | PEAR_Error The number of images moved, or an error message.
+ */
+ function moveImagesTo($images, $gallery)
+ {
+ return $this->_modeHelper->moveImagesTo($images, $gallery);
+ }
+
+ /**
+ * Copy image and related data to specified gallery.
+ *
+ * @param array $images An array of image ids.
+ * @param Ansel_Gallery $gallery The gallery to copy images to.
+ *
+ * @return integer | PEAR_Error The number of images copied or error message
+ */
+ function copyImagesTo($images, $gallery)
+ {
+ if (!$gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
+ return PEAR::raiseError(
+ sprintf(_("Access denied copying photos to \"%s\"."),
+ $gallery->get('name')));
+ }
+
+ $db = $this->_shareOb->_write_db;
+ $imgCnt = 0;
+ foreach ($images as $imageId) {
+ $img = &$this->getImage($imageId);
+ // Note that we don't pass the tags when adding the image..see below
+ $newId = $gallery->addImage(array(
+ 'image_caption' => $img->caption,
+ 'data' => $img->raw(),
+ 'image_filename' => $img->filename,
+ 'image_type' => $img->getType(),
+ 'image_uploaded_date' => $img->uploaded));
+ if (is_a($newId, 'PEAR_Error')) {
+ return $newId;
+ }
+ /* 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 = $this->_shareOb->_write_db->prepare('INSERT INTO ansel_images_tags (image_id, tag_id) VALUES(' . $newId . ',?);');
+ if (is_a($query, 'PEAR_Error')) {
+ return $query;
+ }
+ foreach ($tags as $tag_id => $tag_name) {
+ $result = $query->execute($tag_id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ $query->free();
+
+ /* exif data */
+ // First check to see if the exif data was present in the raw data.
+ $count = $db->queryOne('SELECT COUNT(image_id) FROM ansel_image_attributes WHERE image_id = ' . (int) $newId . ';');
+ if ($count == 0) {
+ $exif = $db->queryAll('SELECT attr_name, attr_value FROM ansel_image_attributes WHERE image_id = ' . (int) $imageId . ';',null, MDB2_FETCHMODE_ASSOC);
+ if (is_array($exif) && count($exif) > 0) {
+ $insert = $db->prepare('INSERT INTO ansel_image_attributes (image_id, attr_name, attr_value) VALUES (?, ?, ?)');
+ if (is_a($insert, 'PEAR_Error')) {
+ return $insert;
+ }
+ foreach ($exif as $attr){
+ $result = $insert->execute(array($newId, $attr['attr_name'], $attr['attr_value']));
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ $insert->free();
+ }
+ }
+ ++$imgCnt;
+ }
+
+ return $imgCnt;
+ }
+
+ /**
+ * Set the order of an image in this gallery.
+ *
+ * @param integer $imageId The image to sort.
+ * @param integer $pos The sort position of the image.
+ */
+ function setImageOrder($imageId, $pos)
+ {
+ return $this->_shareOb->_write_db->exec('UPDATE ansel_images SET image_sort = ' . (int)$pos . ' WHERE image_id = ' . (int)$imageId);
+ }
+
+ /**
+ * Remove the given image from this gallery.
+ *
+ * @param mixed $image Image to delete. Can be an Ansel_Image
+ * or an image ID.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ function removeImage($image, $isStack = false)
+ {
+ return $this->_modeHelper->removeImage($image, $isStack);
+ }
+
+ /**
+ * Returns this share's owner's Identity object.
+ *
+ * @return Identity object for the owner of this gallery.
+ */
+ function getOwner()
+ {
+ require_once 'Horde/Identity.php';
+ $identity = Identity::singleton('none', $this->data['share_owner']);
+ return $identity;
+ }
+
+ /**
+ * Output the HTML for this gallery's tile.
+ *
+ * @param Ansel_Gallery $parent The parent Ansel_Gallery object
+ * @param string $style A named gallery style to use.
+ * @param boolean $mini Force the use of a mini thumbnail?
+ * @param array $params Any additional parameters the Ansel_Tile
+ * object may need.
+ */
+ function getTile($parent = null, $style = null, $mini = false,
+ $params = array())
+ {
+ if (!is_null($parent) && is_null($style)) {
+ $style = $parent->getStyle();
+ } else {
+ $style = Ansel::getStyleDefinition($style);
+ }
+
+ if (!empty($view_url)) {
+ $view_url = str_replace('%g', $this->id, $view_url);
+ }
+
+ return Ansel_Tile_Gallery::getTile($this, $style, $mini, $params);
+ }
+
+ /**
+ * Get the children of this gallery.
+ *
+ * @param integer $perm The permissions to limit to.
+ * @param integer $from The child to start at.
+ * @param integer $to The child to end with.
+ * @param boolean $noauto Prevent auto
+ *
+ * @return A mixed array of Ansel_Gallery and Ansel_Image objects that are
+ * children of this gallery.
+ */
+ function getGalleryChildren($perm = PERMS_SHOW, $from = 0, $to = 0, $noauto = true)
+ {
+ return $this->_modeHelper->getGalleryChildren($perm, $from, $to, $noauto);
+ }
+
+
+ /**
+ * Return the count of this gallery's children
+ *
+ * @param integer $perm The permissions to require.
+ * @param boolean $galleries_only Only include galleries, no images.
+ *
+ * @return integer The count of this gallery's children.
+ */
+ function countGalleryChildren($perm = PERMS_SHOW, $galleries_only = false, $noauto = true)
+ {
+ return $this->_modeHelper->countGalleryChildren($perm, $galleries_only, $noauto);
+ }
+
+ /**
+ * Lists a slice of the image ids in this gallery.
+ *
+ * @param integer $from The image to start listing.
+ * @param integer $count The numer of images to list.
+ *
+ * @return mixed An array of image_ids | PEAR_Error
+ */
+ function listImages($from = 0, $count = 0)
+ {
+ return $this->_modeHelper->listImages($from, $count);
+ }
+
+ /**
+ * Gets a slice of the images in this gallery.
+ *
+ * @param integer $from The image to start fetching.
+ * @param integer $count The numer of images to return.
+ *
+ * @param mixed An array of Ansel_Image objects | PEAR_Error
+ */
+ function getImages($from = 0, $count = 0)
+ {
+ return $this->_modeHelper->getImages($from, $count);
+ }
+
+ /**
+ * Return the most recently added images in this gallery.
+ *
+ * @param integer $limit The maximum number of images to return.
+ *
+ * @return mixed An array of Ansel_Image objects | PEAR_Error
+ */
+ function getRecentImages($limit = 10)
+ {
+ return $GLOBALS['ansel_storage']->getRecentImages(array($this->id),
+ $limit);
+ }
+
+ /**
+ * Returns the image in this gallery corresponding to the given id.
+ *
+ * @param integer $id The ID of the image to retrieve.
+ *
+ * @return Ansel_Image The image object corresponding to the given id.
+ */
+ function &getImage($id)
+ {
+ return $GLOBALS['ansel_storage']->getImage($id);
+ }
+
+ /**
+ * Checks if the gallery has any subgallery
+ */
+ function hasSubGalleries()
+ {
+ return $this->_modeHelper->hasSubGalleries();
+ }
+
+ /**
+ * Returns the number of images in this gallery and, optionally, all
+ * sub-galleries.
+ *
+ * @param boolean $subgalleries Determines whether subgalleries should
+ * be counted or not.
+ *
+ * @return integer number of images in this gallery
+ */
+ function countImages($subgalleries = false)
+ {
+ return $this->_modeHelper->countImages($subgalleries);
+ }
+
+ /**
+ * Returns the default image for this gallery.
+ *
+ * @param string $style Force the use of this style, if it's available
+ * otherwise use whatever style is choosen for this
+ * gallery. If prettythumbs are not available then
+ * we always use ansel_default style.
+ *
+ * @return mixed The image_id of the default image or false.
+ */
+ function getDefaultImage($style = null)
+ {
+ // Check for explicitly requested style
+ if (!is_null($style)) {
+ $gal_style = Ansel::getStyleDefinition($style);
+ } else {
+ // Use gallery's default.
+ $gal_style = $this->getStyle();
+ if (!isset($GLOBALS['ansel_styles'][$gal_style['name']])) {
+ $gal_style = $GLOBALS['ansel_styles']['ansel_default'];
+ }
+ }
+ Horde::logMessage(sprintf("using gallery style: %s in Ansel::getDefaultImage()", $gal_style['name']), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if (!empty($gal_style['default_galleryimage_type']) &&
+ $gal_style['default_galleryimage_type'] != 'plain') {
+
+ $thumbstyle = $gal_style['default_galleryimage_type'];
+ $styleHash = $this->_getViewHash($thumbstyle, $style);
+
+ // First check for the existence of a default image in the style
+ // we are looking for.
+ if (!empty($this->data['attribute_default_prettythumb'])) {
+ $thumbs = @unserialize($this->data['attribute_default_prettythumb']);
+ }
+ if (!isset($thumbs) || !is_array($thumbs)) {
+ $thumbs = array();
+ }
+
+ if (!empty($thumbs[$styleHash])) {
+ return $thumbs[$styleHash];
+ }
+
+ // Don't already have one, must generate it.
+ $params = array('gallery' => $this, 'style' => $gal_style);
+ $iview = Ansel_ImageView::factory(
+ $gal_style['default_galleryimage_type'], $params);
+
+ if (!is_a($iview, 'PEAR_Error')) {
+ $img = $iview->create();
+ if (!is_a($img, 'PEAR_Error')) {
+ // Note the gallery_id is negative for generated stacks
+ $iparams = array('image_filename' => $this->get('name'),
+ 'image_caption' => $this->get('name'),
+ 'data' => $img->raw(),
+ 'image_sort' => 0,
+ 'gallery_id' => -$this->id);
+ $newImg = new Ansel_Image($iparams);
+ $newImg->save();
+ $prettyData = serialize(
+ array_merge($thumbs,
+ array($styleHash => $newImg->id)));
+
+ $this->set('default_prettythumb', $prettyData, true);
+ return $newImg->id;
+ } else {
+ Horde::logMessage($img, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ } else {
+ // Might not support the requested style...try ansel_default
+ // but protect against infinite recursion.
+ Horde::logMessage($iview, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ if ($style != 'ansel_default') {
+ return $this->getDefaultImage('ansel_default');
+ }
+ Horde::logMessage($iview, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ } else {
+ // We are just using an image thumbnail for the gallery default.
+ if ($this->countImages()) {
+ if (!empty($this->data['attribute_default']) &&
+ $this->data['attribute_default'] > 0) {
+
+ return $this->data['attribute_default'];
+ }
+ $keys = $this->listImages();
+ if (is_a($keys, 'PEAR_Error')) {
+ return $keys;
+ }
+ $this->data['attribute_default'] = $keys[count($keys) - 1];
+ $this->data['attribute_default_type'] = 'auto';
+ $this->save();
+ return $keys[count($keys) - 1];
+ }
+
+ if ($this->hasSubGalleries()) {
+ // Fall through to a default image of a sub gallery.
+ $galleries = $GLOBALS['ansel_storage']->listGalleries(
+ PERMS_SHOW, null, $this, false);
+ if ($galleries && !is_a($galleries, 'PEAR_Error')) {
+ foreach ($galleries as $galleryId => $gallery) {
+ if ($default_img = $gallery->getDefaultImage($style)) {
+ return $default_img;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns this gallery's tags.
+ */
+ function getTags() {
+ if ($this->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) {
+ return Ansel_Tags::readTags($this->id, 'gallery');
+ } else {
+ return PEAR::raiseError(_("Access denied viewing this gallery."));
+ }
+ }
+
+ /**
+ * Set/replace this gallery's tags.
+ *
+ * @param array $tags AN array of tag names to associate with this image.
+ */
+ function setTags($tags)
+ {
+ if ($this->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
+ return Ansel_Tags::writeTags($this->id, $tags, 'gallery');
+ } else {
+ return PEAR::raiseError(_("Access denied adding tags to this gallery."));
+ }
+ }
+
+ /**
+ * Return the style definition for this gallery. Returns the first available
+ * style in this order: Explicitly configured style if available, if
+ * configured style is not available, use ansel_default. If nothing has
+ * been configured, the user's selected default is attempted.
+ *
+ * @return array The style definition array.
+ */
+ function getStyle()
+ {
+ if (empty($this->data['attribute_style'])) {
+ $style = $GLOBALS['prefs']->getValue('default_gallerystyle');
+ } else {
+ $style = $this->data['attribute_style'];
+ }
+ return Ansel::getStyleDefinition($style);
+
+ }
+
+ /**
+ * Return a hash key for the given view and style.
+ *
+ * @param string $view The view (thumb, prettythumb etc...)
+ * @param string $style The named style.
+ *
+ * @return string A md5 hash suitable for use as a key.
+ */
+ function _getViewHash($view, $style = null)
+ {
+ if (is_null($style)) {
+ $style = $this->getStyle();
+ } else {
+ $style = Ansel::getStyleDefinition($style);
+ }
+ if ($view != 'screen' && $view != 'thumb' && $view != 'mini' &&
+ $view != 'full') {
+
+ $view = md5($style['thumbstyle'] . '.' . $style['background']);
+ }
+ return $view;
+ }
+ /**
+ * Checks to see if a user has a given permission.
+ *
+ * @param string $userid The userid of the user.
+ * @param integer $permission A PERMS_* constant to test for.
+ * @param string $creator The creator of the event.
+ *
+ * @return boolean Whether or not $userid has $permission.
+ */
+ function hasPermission($userid, $permission, $creator = null)
+ {
+ if ($userid == $this->data['share_owner'] ||
+ Horde_Auth::isAdmin('ansel:admin')) {
+
+ return true;
+ }
+
+
+ return $GLOBALS['perms']->hasPermission($this->getPermission(),
+ $userid, $permission, $creator);
+ }
+
+ /**
+ * Check user age limtation
+ *
+ * @return boolean
+ */
+ function isOldEnough()
+ {
+ if ($this->data['share_owner'] == Horde_Auth::getAuth() ||
+ empty($GLOBALS['conf']['ages']['limits']) ||
+ empty($this->data['attribute_age'])) {
+
+ return true;
+ }
+
+ // Do we have the user age already cheked?
+ if (!isset($_SESSION['ansel']['user_age'])) {
+ $_SESSION['ansel']['user_age'] = 0;
+ } elseif ($_SESSION['ansel']['user_age'] >= $this->data['attribute_age']) {
+ return true;
+ }
+
+ // Can we hook user's age?
+ if ($GLOBALS['conf']['ages']['hook'] && Horde_Auth::isAuthenticated()) {
+ $result = Horde::callHook('_ansel_hook_user_age');
+ if (is_int($result)) {
+ $_SESSION['ansel']['user_age'] = $result;
+ }
+ }
+
+ return ($_SESSION['ansel']['user_age'] >= $this->data['attribute_age']);
+ }
+
+ /**
+ * Determine if we need to unlock a password protected gallery
+ *
+ * @return boolean
+ */
+ function hasPasswd()
+ {
+ if (Horde_Auth::getAuth() == $this->get('owner') || Horde_Auth::isAdmin('ansel:admin')) {
+ return false;
+ }
+
+ $passwd = $this->get('passwd');
+ if (empty($passwd) ||
+ (!empty($_SESSION['ansel']['passwd'][$this->id])
+ && $_SESSION['ansel']['passwd'][$this->id] = md5($this->get('passwd')))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets this gallery's parent gallery.
+ *
+ * @TODO: Check how this interacts with date galleries - shouldn't be able
+ * to remove a subgallery from a date gallery anyway, but just incase
+ * @param mixed $parent An Ansel_Gallery or a gallery_id.
+ *
+ * @return mixed Ture || PEAR_Error
+ */
+ function setParent($parent)
+ {
+ /* Make sure we have a gallery object */
+ if (!is_null($parent) && !is_a($parent, 'Ansel_Gallery')) {
+ $parent = $GLOBALS['ansel_storage']->getGallery($parent);
+ if (is_a($parent, 'PEAR_Error')) {
+ return $parent;
+ }
+ }
+
+ /* Check this now since we don't know if we are updating the DB or not */
+ $old = $this->getParent();
+ $reset_has_subgalleries = false;
+ if (!is_null($old)) {
+ $cnt = $old->countGalleryChildren(PERMS_READ, true);
+ if ($cnt == 1) {
+ /* Count is 1, and we are about to delete it */
+ $reset_has_subgalleries = true;
+ }
+ }
+
+ /* Call the parent class method */
+ $result = parent::setParent($parent);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ /* Tell the parent the good news */
+ if (!is_null($parent) && !$parent->get('has_subgalleries')) {
+ return $parent->set('has_subgalleries', '1', true);
+ }
+ Horde::logMessage('Ansel_Gallery parent successfully set', __FILE__,
+ __LINE__, PEAR_LOG_DEBUG);
+
+ /* Gallery parent changed, safe to change the parent's attributes */
+ if ($reset_has_subgalleries) {
+ $old->set('has_subgalleries', 0, true);
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets an attribute value in this object.
+ *
+ * @param string $attribute The attribute to set.
+ * @param mixed $value The value for $attribute.
+ * @param boolean $update Commit only this change to storage.
+ *
+ * @return mixed True if setting the attribute did succeed, a PEAR_Error
+ * otherwise.
+ */
+ function set($attribute, $value, $update = false)
+ {
+ /* Translate the keys */
+ if ($attribute == 'owner') {
+ $driver_key = 'share_owner';
+ } else {
+ $driver_key = 'attribute_' . $attribute;
+ }
+
+ if ($driver_key == 'attribute_view_mode' &&
+ !empty($this->data[$driver_key]) &&
+ $value != $this->data[$driver_key]) {
+
+ $mode = isset($attributes['attribute_view_mode']) ? $attributes['attribute_view_mode'] : 'Normal';
+ $this->_setModeHelper($mode);
+ }
+
+ $this->data[$driver_key] = $value;
+
+ /* Update the backend, but only this current change */
+ if ($update) {
+ $db = $this->_shareOb->_write_db;
+ // Manually convert the charset since we're not going through save()
+ $data = $this->_shareOb->_toDriverCharset(array($driver_key => $value));
+ $query = $db->prepare('UPDATE ' . $this->_shareOb->_table . ' SET ' . $driver_key . ' = ? WHERE share_id = ?', null, MDB2_PREPARE_MANIP);
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->expire('Ansel_Gallery' . $this->id);
+ }
+ $result = $query->execute(array($data[$driver_key], $this->id));
+ $query->free();
+
+ return $result;
+ }
+
+ return true;
+ }
+
+ function setDate($date)
+ {
+ $this->_modeHelper->setDate($date);
+ }
+
+ function getDate()
+ {
+ return $this->_modeHelper->getDate();
+ }
+
+ /**
+ * Get an array describing where this gallery is in a breadcrumb trail.
+ *
+ * @return An array of 'title' and 'navdata' hashes with the [0] element
+ * being the deepest part.
+ */
+ function getGalleryCrumbData()
+ {
+ return $this->_modeHelper->getGalleryCrumbData();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Class to describe a single Ansel image.
+ *
+ * Copyright 2001-2009 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 Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Ansel
+ */
+class Ansel_Image
+{
+ /**
+ * @var integer The gallery id of this image's parent gallery
+ */
+ var $gallery;
+
+ /**
+ * @var Horde_Image Horde_Image object for this image.
+ */
+ var $_image;
+
+ var $id = null;
+ var $filename = 'Untitled';
+ var $caption = '';
+ var $type = 'image/jpeg';
+
+ /**
+ * timestamp of uploaded date
+ *
+ * @var integer
+ */
+ var $uploaded;
+
+ var $sort;
+ var $commentCount;
+ var $facesCount;
+ var $lat;
+ var $lng;
+ var $location;
+ var $geotag_timestamp;
+
+ var $_dirty;
+
+
+ /**
+ * Timestamp of original date.
+ *
+ * @var integer
+ */
+ var $originalDate;
+
+ /**
+ * Holds an array of tags for this image
+ * @var array
+ */
+ var $_tags = array();
+
+ var $_loaded = array();
+ var $_data = array();
+
+ /**
+ * Cache the raw EXIF data locally
+ *
+ * @var array
+ */
+ var $_exif = array();
+
+ /**
+ * TODO: refactor Ansel_Image to use a ::get() method like Ansel_Gallery
+ * instead of direct instance variable access and all the nonsense below.
+ *
+ * @param unknown_type $image
+ * @return Ansel_Image
+ */
+ function Ansel_Image($image = array())
+ {
+ if ($image) {
+ $this->filename = $image['image_filename'];
+ $this->caption = $image['image_caption'];
+ $this->sort = $image['image_sort'];
+ $this->gallery = $image['gallery_id'];
+
+ // New image?
+ if (!empty($image['image_id'])) {
+ $this->id = $image['image_id'];
+ }
+
+ if (!empty($image['data'])) {
+ $this->_data['full'] = $image['data'];
+ }
+
+ if (!empty($image['image_uploaded_date'])) {
+ $this->uploaded = $image['image_uploaded_date'];
+ } else {
+ $this->uploaded = time();
+ }
+
+ if (!empty($image['image_type'])) {
+ $this->type = $image['image_type'];
+ }
+
+ if (!empty($image['tags'])) {
+ $this->_tags = $image['tags'];
+ }
+
+ if (!empty($image['image_faces'])) {
+ $this->facesCount = $image['image_faces'];
+ }
+
+ $this->location = !empty($image['image_location']) ? $image['image_location'] : '';
+
+ // The following may have to be rewritten by EXIF.
+ // EXIF requires both an image id and a stream, so we can't
+ // get EXIF data before we save the image to the VFS.
+ if (!empty($image['image_original_date'])) {
+ $this->originalDate = $image['image_original_date'];
+ } else {
+ $this->originalDate = $this->uploaded;
+ }
+ $this->lat = !empty($image['image_latitude']) ? $image['image_latitude'] : '';
+ $this->lng = !empty($image['image_longitude']) ? $image['image_longitude'] : '';
+ $this->geotag_timestamp = !empty($image['image_geotag_date']) ? $image['image_geotag_date'] : '0';
+ }
+
+ $this->_image = Ansel::getImageObject();
+ $this->_image->reset();
+ }
+
+ /**
+ * Return the vfs path for this image.
+ *
+ * @param string $view The view we want.
+ * @param string $style A named gallery style.
+ *
+ * @return string The vfs path for this image.
+ */
+ function getVFSPath($view = 'full', $style = null)
+ {
+ $view = $this->_getViewHash($view, $style);
+ return '.horde/ansel/'
+ . substr(str_pad($this->id, 2, 0, STR_PAD_LEFT), -2)
+ . '/' . $view;
+ }
+
+ /**
+ * Returns the file name of this image as used in the VFS backend.
+ *
+ * @return string This image's VFS file name.
+ */
+ function getVFSName($view)
+ {
+ $vfsname = $this->id;
+
+ if ($view == 'full' && $this->type) {
+ $type = strpos($this->type, '/') === false ? 'image/' . $this->type : $this->type;
+ if ($ext = Horde_Mime_Magic::mimeToExt($type)) {
+ $vfsname .= '.' . $ext;
+ }
+ } elseif (($GLOBALS['conf']['image']['type'] == 'jpeg') || $view == 'screen') {
+ $vfsname .= '.jpg';
+ } else {
+ $vfsname .= '.png';
+ }
+
+ return $vfsname;
+ }
+
+ /**
+ * Loads the given view into memory.
+ *
+ * @param string $view Which view to load.
+ * @param string $style The named gallery style.
+ *
+ * @return mixed True || PEAR_Error
+ */
+ function load($view = 'full', $style = null)
+ {
+ // If this is a new image that hasn't been saved yet, we will
+ // already have the full data loaded. If we auto-rotate the image
+ // then there is no need to save it just to load it again.
+ if ($view == 'full' && !empty($this->_data['full'])) {
+ $this->_image->loadString('original', $this->_data['full']);
+ $this->_loaded['full'] = true;
+ return true;
+ }
+
+ $viewHash = $this->_getViewHash($view, $style);
+ /* If we've already loaded the data, just return now. */
+ if (!empty($this->_loaded[$viewHash])) {
+ return true;
+ }
+
+ $result = $this->createView($view, $style);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ /* If createView() had to resize the full image, we've already
+ * loaded the data, so return now. */
+ if (!empty($this->_loaded[$viewHash])) {
+ return;
+ }
+
+ /* We've definitely successfully loaded the image now. */
+ $this->_loaded[$viewHash] = true;
+
+ /* Get the VFS info. */
+ $vfspath = $this->getVFSPath($view, $style);
+ if (is_a($vfspath, 'PEAR_Error')) {
+ return $vfspath;
+ }
+
+ /* Read in the requested view. */
+ $data = $GLOBALS['ansel_vfs']->read($vfspath, $this->getVFSName($view));
+ if (is_a($data, 'PEAR_Error')) {
+ Horde::logMessage($date, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $data;
+ }
+
+ $this->_data[$viewHash] = $data;
+ $this->_image->loadString($vfspath . '/' . $this->id, $data);
+ return true;
+ }
+
+ /**
+ * Check if an image view exists and returns the vfs name complete with
+ * the hash directory name prepended if appropriate.
+ *
+ * @param integer $id Image id to check
+ * @param string $view Which view to check for
+ * @param string $style A named gallery style
+ *
+ * @return mixed False if image does not exists | string vfs name
+ *
+ * @static
+ */
+ function viewExists($id, $view, $style)
+ {
+ /* We cannot check empty styles since we cannot get the hash */
+ if (empty($style)) {
+ return false;
+ }
+
+ /* Get the VFS path. */
+ $view = Ansel_Gallery::_getViewHash($view, $style);
+
+ /* Can't call the various vfs methods here, since this method needs
+ to be called statically */
+ $vfspath = '.horde/ansel/' . substr(str_pad($id, 2, 0, STR_PAD_LEFT), -2) . '/' . $view;
+
+ /* Get VFS name */
+ $vfsname = $id . '.';
+ if ($GLOBALS['conf']['image']['type'] == 'jpeg' || $view == 'screen') {
+ $vfsname .= 'jpg';
+ } else {
+ $vfsname .= 'png';
+ }
+
+ if ($GLOBALS['ansel_vfs']->exists($vfspath, $vfsname)) {
+ return $view . '/' . $vfsname;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Creates and caches the given view.
+ *
+ * @param string $view Which view to create.
+ * @param string $style A named gallery style
+ */
+ function createView($view, $style = null)
+ {
+ // HACK: Need to replace the image object with a JPG typed image if
+ // we are generating a screen image. Need to do the replacement
+ // and do it *here* for BC reasons with Horde_Image...and this
+ // needs to be done FIRST, since the view might already be cached
+ // in the VFS.
+ if ($view == 'screen' && $GLOBALS['conf']['image']['type'] != 'jpeg') {
+ $this->_image = Ansel::getImageObject(array('type' => 'jpeg'));
+ $this->_image->reset();
+ }
+
+ /* Get the VFS info. */
+ $vfspath = $this->getVFSPath($view, $style);
+ if ($GLOBALS['ansel_vfs']->exists($vfspath, $this->getVFSName($view))) {
+ return true;
+ }
+
+ $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'),
+ $this->getVFSName('full'));
+ if (is_a($data, 'PEAR_Error')) {
+ Horde::logMessage($data, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $data;
+ }
+ $this->_image->loadString($this->getVFSPath('full') . '/' . $this->id, $data);
+ $styleDef = Ansel::getStyleDefinition($style);
+ if ($view == 'prettythumb') {
+ $viewType = $styleDef['thumbstyle'];
+ } else {
+ $viewType = $view;
+ }
+ $iview = Ansel_ImageView::factory($viewType, array('image' => $this,
+ 'style' => $style));
+
+ if (is_a($iview, 'PEAR_Error')) {
+ // It could be we don't support the requested effect, try
+ // ansel_default before giving up.
+ if ($view == 'prettythumb') {
+ $iview = Ansel_ImageView::factory(
+ 'thumb', array('image' => $this,
+ 'style' => 'ansel_default'));
+
+ if (is_a($iview, 'PEAR_Error')) {
+ return $iview;
+ }
+ }
+ }
+
+ $res = $iview->create();
+ if (is_a($res, 'PEAR_Error')) {
+ return $res;
+ }
+
+ $view = $this->_getViewHash($view, $style);
+
+ $this->_data[$view] = $this->_image->raw();
+ $this->_image->loadString($vfspath . '/' . $this->id,
+ $this->_data[$view]);
+ $this->_loaded[$view] = true;
+ $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view),
+ $this->_data[$view], true);
+
+ // Autowatermark the screen view
+ if ($view == 'screen' &&
+ $GLOBALS['prefs']->getValue('watermark_auto') &&
+ $GLOBALS['prefs']->getValue('watermark_text') != '') {
+
+ $this->watermark('screen');
+ $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view),
+ $this->_image->_data);
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes the current data to vfs, used when creating a new image
+ */
+ function _writeData()
+ {
+ $this->_dirty = false;
+ return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath('full'),
+ $this->getVFSName('full'),
+ $this->_data['full'], true);
+ }
+
+ /**
+ * Change the image data. Deletes old cache and writes the new
+ * data to the VFS. Used when updating an image
+ *
+ * @param string $data The new data for this image.
+ * @param string $view If specified, the $data represents only this
+ * particular view. Cache will not be deleted.
+ */
+ function updateData($data, $view = 'full')
+ {
+ if (is_a($data, 'PEAR_Error')) {
+ return $data;
+ }
+
+ /* Delete old cached data if we are replacing the full image */
+ if ($view == 'full') {
+ $this->deleteCache();
+ }
+
+ return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath($view),
+ $this->getVFSName($view),
+ $data, true);
+ }
+
+ /**
+ * Update the geotag data
+ */
+ function geotag($lat, $lng, $location = '')
+ {
+ $this->lat = $lat;
+ $this->lng = $lng;
+ $this->location = $location;
+ $this->geotag_timestamp = time();
+ $this->save();
+ }
+
+ /**
+ * Save basic image details
+ *
+ * @TODO: Move all SQL queries to Ansel_Storage::?
+ */
+ function save()
+ {
+ /* If we have an id, then it's an existing image.*/
+ if ($this->id) {
+ $update = $GLOBALS['ansel_db']->prepare('UPDATE ansel_images SET image_filename = ?, image_type = ?, image_caption = ?, image_sort = ?, image_original_date = ?, image_latitude = ?, image_longitude = ?, image_location = ?, image_geotag_date = ? WHERE image_id = ?');
+ if (is_a($update, 'PEAR_Error')) {
+ Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $update;
+ }
+ $result = $update->execute(array(Horde_String::convertCharset($this->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+ $this->type,
+ Horde_String::convertCharset($this->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+ $this->sort,
+ $this->originalDate,
+ $this->lat,
+ $this->lng,
+ $this->location,
+ $this->geotag_timestamp,
+ $this->id));
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR);
+ } else {
+ $update->free();
+ }
+ return $result;
+ }
+
+ /* Saving a new Image */
+ if (!$this->gallery || !strlen($this->filename) || !$this->type) {
+ $error = PEAR::raiseError(_("Incomplete photo"));
+ Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+
+ /* Get the next image_id */
+ $image_id = $GLOBALS['ansel_db']->nextId('ansel_images');
+ if (is_a($image_id, 'PEAR_Error')) {
+ return $image_id;
+ }
+
+ /* Prepare the SQL statement */
+ $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_images (image_id, gallery_id, image_filename, image_type, image_caption, image_uploaded_date, image_sort, image_original_date, image_latitude, image_longitude, image_location, image_geotag_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
+ if (is_a($insert, 'PEAR_Error')) {
+ Horde::logMessage($insert, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $insert;
+ }
+
+ /* Perform the INSERT */
+ $result = $insert->execute(array($image_id,
+ $this->gallery,
+ Horde_String::convertCharset($this->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+ $this->type,
+ Horde_String::convertCharset($this->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+ $this->uploaded,
+ $this->sort,
+ $this->originalDate,
+ $this->lat,
+ $this->lng,
+ $this->location,
+ (empty($this->lat) ? 0 : $this->uploaded)));
+ $insert->free();
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+
+ /* Keep the image_id */
+ $this->id = $image_id;
+
+ /* The EXIF functions require a stream, so we need to save before we read */
+ $this->_writeData();
+
+ /* Get the EXIF data if we are not a gallery key image. */
+ if ($this->gallery > 0) {
+ $needUpdate = $this->_getEXIF();
+ }
+
+ /* Create tags from exif data if desired */
+ $fields = @unserialize($GLOBALS['prefs']->getValue('exif_tags'));
+ if ($fields) {
+ $this->_exifToTags($fields);
+ }
+
+ /* Save the tags */
+ if (count($this->_tags)) {
+ $result = $this->setTags($this->_tags);
+ if (is_a($result, 'PEAR_Error')) {
+ // Since we got this far, the image has been added, so
+ // just log the tag failure.
+ Horde::logMessage($result, __LINE__, __FILE__, PEAR_LOG_ERR);
+ }
+ }
+
+ /* Save again if EXIF changed any values */
+ if (!empty($needUpdate)) {
+ $this->save();
+ }
+
+ return $this->id;
+ }
+
+ /**
+ * Replace this image's image data.
+ *
+ */
+ function replace($imageData)
+ {
+ /* Reset the data array and remove all cached images */
+ $this->_data = array();
+ $this->reset();
+
+ /* Remove attributes */
+ $result = $GLOBALS['ansel_db']->exec('DELETE FROM ansel_image_attributes WHERE image_id = ' . (int)$this->id);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERROR);
+ return $result;
+ }
+ /* Load the new image data */
+ $this->_getEXIF();
+ $this->updateData($imageData);
+
+ return true;
+ }
+
+ /**
+ * Adds specified EXIF fields to this image's tags. Called during image
+ * upload/creation.
+ *
+ * @param array $fields An array of EXIF fields to import as a tag.
+ *
+ */
+ function _exifToTags($fields = array())
+ {
+ $tags = array();
+ foreach ($fields as $field) {
+ if (!empty($this->_exif[$field])) {
+ if (substr($field, 0, 8) == 'DateTime') {
+ $d = new Horde_Date(strtotime($this->_exif[$field]));
+ $tags[] = $d->format("Y-m-d");
+ } else {
+ $tags[] = $this->_exif[$field];
+ }
+ }
+ }
+
+ $this->_tags = array_merge($this->_tags, $tags);
+ }
+
+ /**
+ * Reads the EXIF data from the image and stores in _exif array() as well
+ * also populates any local properties that come from the EXIF data.
+ *
+ * @return mixed true if any local properties were modified, false otherwise, PEAR_Error on failure
+ */
+ function _getEXIF()
+ {
+ /* Clear the local copy */
+ $this->_exif = array();
+
+ /* Get the data */
+ $imageFile = $GLOBALS['ansel_vfs']->readFile($this->getVFSPath('full'),
+ $this->getVFSName('full'));
+ if (is_a($imageFile, 'PEAR_Error')) {
+ return $imageFile;
+ }
+ $exif = Horde_Image_Exif::factory($GLOBALS['conf']['exif'], $GLOBALS['conf']['exif']['params']);
+ $exif_fields = $exif->getData($imageFile);
+
+ /* Flag to determine if we need to resave the image data */
+ $needUpdate = false;
+
+ /* Populate any local properties that come from EXIF
+ * Save any geo data to a seperate table as well */
+ if (!empty($exif_fields['GPSLatitude'])) {
+ $this->lat = $exif_fields['GPSLatitude'];
+ $this->lng = $exif_fields['GPSLongitude'];
+ $this->geotag_timestamp = time();
+ $needUpdate = true;
+ }
+
+ if (!empty($exif_fields['DateTimeOriginal'])) {
+ $this->originalDate = $exif_fields['DateTimeOriginal'];
+ $needUpdate = true;
+ }
+
+ /* Attempt to autorotate based on Orientation field */
+ $this->_autoRotate();
+
+ /* Save attributes. */
+ $insert = $GLOBALS['ansel_db']->prepare('INSERT INTO ansel_image_attributes (image_id, attr_name, attr_value) VALUES (?, ?, ?)');
+ foreach ($exif_fields as $name => $value) {
+ $result = $insert->execute(array($this->id, $name, Horde_String::convertCharset($value, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset'])));
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ /* Cache it locally */
+ $this->_exif[$name] = Horde_Image_Exif::getHumanReadable($name, $value);
+ }
+ $insert->free();
+
+
+ return $needUpdate;
+ }
+
+ /**
+ * Autorotate based on EXIF orientation field. Updates the data in memory
+ * only.
+ *
+ */
+ function _autoRotate()
+ {
+ if (isset($this->_exif['Orientation']) && $this->_exif['Orientation'] != 1) {
+ switch ($this->_exif['Orientation']) {
+ case 2:
+ $this->mirror();
+ break;
+
+ case 3:
+ $this->rotate('full', 180);
+ break;
+
+ case 4:
+ $this->mirror();
+ $this->rotate('full', 180);
+ break;
+
+ case 5:
+ $this->flip();
+ $this->rotate('full', 90);
+ break;
+
+ case 6:
+ $this->rotate('full', 90);
+ break;
+
+ case 7:
+ $this->mirror();
+ $this->rotate('full', 90);
+ break;
+
+ case 8:
+ $this->rotate('full', 270);
+ break;
+ }
+
+ if ($this->_dirty) {
+ $this->_exif['Orientation'] = 1;
+ $this->data['full'] = $this->raw();
+ $this->_writeData();
+ }
+ }
+ }
+
+ /**
+ * Reset the image, removing all loaded views.
+ */
+ function reset()
+ {
+ $this->_image->reset();
+ $this->_loaded = array();
+ }
+
+ /**
+ * Deletes the specified cache file.
+ *
+ * If none is specified, deletes all of the cache files.
+ *
+ * @param string $view Which cache file to delete.
+ */
+ function deleteCache($view = 'all')
+ {
+ /* Delete cached screen image. */
+ if ($view == 'all' || $view == 'screen') {
+ $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('screen'),
+ $this->getVFSName('screen'));
+ }
+
+ /* Delete cached thumbnail. */
+ if ($view == 'all' || $view == 'thumb') {
+ $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('thumb'),
+ $this->getVFSName('thumb'));
+ }
+
+ /* Delete cached mini image. */
+ if ($view == 'all' || $view == 'mini') {
+ $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath('mini'),
+ $this->getVFSName('mini'));
+ }
+
+ if ($view == 'all' || $view == 'prettythumb') {
+
+ /* No need to try to delete a hash we already removed */
+ $deleted = array();
+
+ /* Need to generate hashes for each possible style */
+ $styles = Horde::loadConfiguration('styles.php', 'styles', 'ansel');
+ foreach ($styles as $style) {
+ $hash = md5($style['thumbstyle'] . '.' . $style['background']);
+ if (empty($deleted[$hash])) {
+ $GLOBALS['ansel_vfs']->deleteFile($this->getVFSPath($hash),
+ $this->getVFSName($hash));
+ $deleted[$hash] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the raw data for the given view.
+ *
+ * @param string $view Which view to return.
+ */
+ function raw($view = 'full')
+ {
+ if ($this->_dirty) {
+ return $this->_image->raw();
+ } else {
+ $this->load($view);
+ return $this->_data[$view];
+ }
+ }
+
+ /**
+ * Sends the correct HTTP headers to the browser to download this image.
+ *
+ * @param string $view The view to download.
+ */
+ function downloadHeaders($view = 'full')
+ {
+ global $browser, $conf;
+
+ $filename = $this->filename;
+ if ($view != 'full') {
+ if ($ext = Horde_Mime_Magic::mimeToExt('image/' . $conf['image']['type'])) {
+ $filename .= '.' . $ext;
+ }
+ }
+
+ $browser->downloadHeaders($filename);
+ }
+
+ /**
+ * Display the requested view.
+ *
+ * @param string $view Which view to display.
+ * @param string $style Force use of this gallery style.
+ */
+ function display($view = 'full', $style = null)
+ {
+ if ($view == 'full' && !$this->_dirty) {
+
+ // Check full photo permissions
+ $gallery = $GLOBALS['ansel_storage']->getGallery($this->gallery);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+ if (!$gallery->canDownload()) {
+ return PEAR::RaiseError(sprintf(_("Access denied downloading photos from \"%s\"."), $gallery->get('name')));
+ }
+
+ $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'),
+ $this->getVFSName('full'));
+
+ if (is_a($data, 'PEAR_Error')) {
+ return $data;
+ }
+ echo $data;
+ return;
+ }
+
+ if (is_a($result = $this->load($view, $style), 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+
+ $this->_image->display();
+ }
+
+ /**
+ * Wraps the given view into a file.
+ *
+ * @param string $view Which view to wrap up.
+ */
+ function toFile($view = 'full')
+ {
+ if (is_a(($result = $this->load($view)), 'PEAR_Error')) {
+ return $result;
+ }
+ return $this->_image->toFile($this->_dirty ? false : $this->_data[$view]);
+ }
+
+ /**
+ * Returns the dimensions of the given view.
+ *
+ * @param string $view The view (size) to check dimensions for.
+ */
+ function getDimensions($view = 'full')
+ {
+ if (is_a(($result = $this->load($view)), 'PEAR_Error')) {
+ return $result;
+ }
+ return $this->_image->getDimensions();
+ }
+
+ /**
+ * Rotates the image.
+ *
+ * @param string $view The view (size) to work with.
+ * @param integer $angle What angle to rotate the image by.
+ */
+ function rotate($view = 'full', $angle)
+ {
+ $this->load($view);
+ $this->_dirty = true;
+ $this->_image->rotate($angle);
+ }
+
+ function crop($x1, $y1, $x2, $y2)
+ {
+ $this->_dirty = true;
+ $this->_image->crop($x1, $y1, $x2, $y2);
+ }
+
+ /**
+ * Converts the image to grayscale.
+ *
+ * @param string $view The view (size) to work with.
+ */
+ function grayscale($view = 'full')
+ {
+ $this->load($view);
+ $this->_dirty = true;
+ $this->_image->grayscale();
+ }
+
+ /**
+ * Watermarks the image.
+ *
+ * @param string $view The view (size) to work with.
+ * @param string $watermark String to use as the watermark.
+ */
+ function watermark($view = 'full', $watermark = null, $halign = null,
+ $valign = null, $font = null)
+ {
+ if (empty($watermark)) {
+ $watermark = $GLOBALS['prefs']->getValue('watermark_text');
+ }
+
+ if (empty($halign)) {
+ $halign = $GLOBALS['prefs']->getValue('watermark_horizontal');
+ }
+
+ if (empty($valign)) {
+ $valign = $GLOBALS['prefs']->getValue('watermark_vertical');
+ }
+
+ if (empty($font)) {
+ $font = $GLOBALS['prefs']->getValue('watermark_font');
+ }
+
+ if (empty($watermark)) {
+ require_once 'Horde/Identity.php';
+ $identity = Identity::singleton();
+ $name = $identity->getValue('fullname');
+ if (empty($name)) {
+ $name = Horde_Auth::getAuth();
+ }
+ $watermark = sprintf(_("(c) %s %s"), date('Y'), $name);
+ }
+
+ $this->load($view);
+ $this->_dirty = true;
+ $params = array('text' => $watermark,
+ 'halign' => $halign,
+ 'valign' => $valign,
+ 'fontsize' => $font);
+ if (!empty($GLOBALS['conf']['image']['font'])) {
+ $params['font'] = $GLOBALS['conf']['image']['font'];
+ }
+ $this->_image->addEffect('TextWatermark', $params);
+ }
+
+ /**
+ * Flips the image.
+ *
+ * @param string $view The view (size) to work with.
+ */
+ function flip($view = 'full')
+ {
+ $this->load($view);
+ $this->_dirty = true;
+ $this->_image->flip();
+ }
+
+ /**
+ * Mirrors the image.
+ *
+ * @param string $view The view (size) to work with.
+ */
+ function mirror($view = 'full')
+ {
+ $this->load($view);
+ $this->_dirty = true;
+ $this->_image->mirror();
+ }
+
+ /**
+ * Returns this image's tags.
+ *
+ * @return mixed An array of tags | PEAR_Error
+ * @see Ansel_Tags::readTags()
+ */
+ function getTags()
+ {
+ global $ansel_storage;
+
+ if (count($this->_tags)) {
+ return $this->_tags;
+ }
+ $gallery = $ansel_storage->getGallery($this->gallery);
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+ if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_READ)) {
+ $res = Ansel_Tags::readTags($this->id);
+ if (!is_a($res, 'PEAR_Error')) {
+ $this->_tags = $res;
+ return $this->_tags;
+ } else {
+ return $res;
+ }
+ } else {
+ return PEAR::raiseError(_("Access denied viewing this photo."));
+ }
+ }
+
+ /**
+ * Set/replace this image's tags.
+ *
+ * @param array $tags An array of tag names to associate with this image.
+ */
+ function setTags($tags)
+ {
+ global $ansel_storage;
+
+ $gallery = $ansel_storage->getGallery(abs($this->gallery));
+ if ($gallery->hasPermission(Horde_Auth::getAuth(), PERMS_EDIT)) {
+ // Clear the local cache.
+ $this->_tags = array();
+ return Ansel_Tags::writeTags($this->id, $tags);
+ } else {
+ return PEAR::raiseError(_("Access denied adding tags to this photo."));
+ }
+ }
+
+ /**
+ * Get the Ansel_View_Image_Thumb object
+ *
+ * @param Ansel_Gallery $parent The parent Ansel_Gallery object.
+ * @param string $style A named gallery style to use.
+ * @param boolean $mini Force the use of a mini thumbnail?
+ * @param array $params Any additional parameters the Ansel_Tile
+ * object may need.
+ *
+ */
+ function getTile($parent = null, $style = null, $mini = false,
+ $params = array())
+ {
+ if (!is_null($parent) && is_null($style)) {
+ $style = $parent->getStyle();
+ } else {
+ $style = Ansel::getStyleDefinition($style);
+ }
+
+ return Ansel_Tile_Image::getTile($this, $style, $mini, $params);
+ }
+
+ /**
+ * Get the image type for the requested view.
+ */
+ function getType($view = 'full')
+ {
+ if ($view == 'full') {
+ return $this->type;
+ } elseif ($view == 'screen') {
+ return 'image/jpg';
+ } else {
+ return 'image/' . $GLOBALS['conf']['image']['type'];
+ }
+ }
+
+ /**
+ * Return a hash key for the given view and style.
+ *
+ * @param string $view The view (thumb, prettythumb etc...)
+ * @param string $style The named style.
+ *
+ * @return string A md5 hash suitable for use as a key.
+ */
+ function _getViewHash($view, $style = null)
+ {
+ global $ansel_storage;
+
+ // These views do not care about style...just return the $view value.
+ if ($view == 'screen' || $view == 'thumb' || $view == 'mini' ||
+ $view == 'full') {
+
+ return $view;
+ }
+ if (is_null($style)) {
+ $gallery = $ansel_storage->getGallery(abs($this->gallery));
+ if (is_a($gallery, 'PEAR_Error')) {
+ return $gallery;
+ }
+ $style = $gallery->getStyle();
+ } else {
+ $style = Ansel::getStyleDefinition($style);
+ }
+
+ $view = md5($style['thumbstyle'] . '.' . $style['background']);
+ return $view;
+ }
+
+ /**
+ * Get the image attributes from the backend.
+ *
+ * @param Ansel_Image $image The image to retrieve attributes for.
+ * attributes for.
+ * @param boolean $format Format the EXIF data. If false, the raw data
+ * is returned.
+ *
+ * @return array The EXIF data.
+ * @static
+ */
+ function getAttributes($format = false)
+ {
+ $attributes = $GLOBALS['ansel_storage']->getImageAttributes($this->id);
+ $fields = Horde_Image_Exif::getFields();
+ $output = array();
+
+ foreach ($fields as $field => $data) {
+ if (!isset($attributes[$field])) {
+ continue;
+ }
+ $value = Horde_Image_Exif::getHumanReadable($field, Horde_String::convertCharset($attributes[$field], $GLOBALS['conf']['sql']['charset']));
+ if (!$format) {
+ $output[$field] = $value;
+ } else {
+ $description = isset($data['description']) ? $data['description'] : $field;
+ $output[] = '<td><strong>' . $description . '</strong></td><td>' . htmlspecialchars($value, ENT_COMPAT, Horde_Nls::getCharset()) . '</td>';
+ }
+ }
+
+ return $output;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Class for interfacing with back end data storage.
+ *
+ * Copyright 2001-2009 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>
+ * @package Ansel
+ */
+class Ansel_Storage
+{
+ var $_scope = 'ansel';
+ var $_db = null;
+ var $galleries = array();
+
+ /**
+ * The Horde_Shares object to use for this scope.
+ *
+ * @var Horde_Share
+ */
+ var $shares = null;
+
+ /* Local cache of retrieved images */
+ var $images = array();
+
+ function Ansel_Storage($scope = null)
+ {
+ /* Check for a scope other than the default Ansel scope.*/
+ if (!is_null($scope)) {
+ $this->_scope = $scope;
+ }
+
+ /* This is the only supported share backend for Ansel */
+ $this->shares = Horde_Share::singleton($this->_scope,
+ 'sql_hierarchical');
+
+ /* Ansel_Gallery is just a subclass of Horde_Share_Object */
+ $this->shares->_shareObject = 'Ansel_Gallery';
+
+ /* Database handle */
+ $this->_db = $GLOBALS['ansel_db'];
+ }
+
+ /**
+ * Create and initialise a new gallery object.
+ *
+ * @param array $attributes The gallery attributes
+ * @param object Perms $perm The permissions for the gallery if the
+ * defaults are not desirable.
+ * @param mixed $parent The gallery id of the parent (if any)
+ *
+ * @return Ansel_Gallery A new gallery object or PEAR_Error.
+ */
+ function createGallery($attributes = array(), $perm = null, $parent = null)
+ {
+ /* Required values. */
+ if (empty($attributes['owner'])) {
+ $attributes['owner'] = Horde_Auth::getAuth();
+ }
+ if (empty($attributes['name'])) {
+ $attributes['name'] = _("Unnamed");
+ }
+ if (empty($attributes['desc'])) {
+ $attributes['desc'] = '';
+ }
+
+ /* Default values */
+ $attributes['default_type'] = isset($attributes['default_type']) ? $attributes['default_type'] : 'auto';
+ $attributes['default'] = isset($attributes['default']) ? (int)$attributes['default'] : 0;
+ $attributes['default_prettythumb'] = isset($attributes['default_prettythumb']) ? $attributes['default_prettythumb'] : '';
+ $attributes['style'] = isset($attributes['style']) ? $attributes['style'] : $GLOBALS['prefs']->getValue('default_gallerystyle');
+ $attributes['category'] = isset($attributes['category']) ? $attributes['category'] : $GLOBALS['prefs']->getValue('default_category');
+ $attributes['date_created'] = time();
+ $attributes['last_modified'] = $attributes['date_created'];
+ $attributes['images'] = isset($attributes['images']) ? (int)$attributes['images'] : 0;
+ $attributes['slug'] = isset($attributes['slug']) ? $attributes['slug'] : '';
+ $attributes['age'] = isset($attributes['age']) ? (int)$attributes['age'] : 0;
+ $attributes['download'] = isset($attributes['download']) ? $attributes['download'] : $GLOBALS['prefs']->getValue('default_download');
+ $attributes['view_mode'] = isset($attributes['view_mode']) ? $attributes['view_mode'] : 'Normal';
+ $attributes['passwd'] = isset($attributes['passwd']) ? $attributes['passwd'] : '';
+
+ /* Don't pass tags to the share creation method */
+ if (isset($attributes['tags'])) {
+ $tags = $attributes['tags'];
+ unset($attributes['tags']);
+ } else {
+ $tags = array();
+ }
+
+ /* Check for slug uniqueness */
+ if (!empty($attributes['slug']) &&
+ $this->slugExists($attributes['slug'])) {
+ return PEAR::raiseError(sprintf(_("The slug \"%s\" already exists."),
+ $attributes['slug']));
+ }
+
+ /* Create the gallery */
+ $gallery = $this->shares->newShare('');
+ if (is_a($gallery, 'PEAR_Error')) {
+ Horde::logMessage($gallery, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $gallery;
+ }
+ Horde::logMessage('New Ansel_Gallery object instantiated', __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Set the gallery's parent if needed */
+ if (!is_null($parent)) {
+ $result = $gallery->setParent($parent);
+
+ /* Clear the parent from the cache */
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->expire('Ansel_Gallery' . $parent);
+ }
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+ }
+
+ /* 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);
+ }
+
+ /* Save it to storage */
+ $result = $this->shares->addShare($gallery);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = sprintf(_("The gallery \"%s\" could not be created: %s"),
+ $attributes['name'], $result->getMessage());
+ Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return PEAR::raiseError($error);
+ }
+
+ /* Convenience */
+ $gallery->id = $gallery->getId();
+
+ /* Add default permissions. */
+ if (empty($perm)) {
+ $perm = $gallery->getPermission();
+
+ /* Default permissions for logged in users */
+ switch ($GLOBALS['prefs']->getValue('default_permissions')) {
+ case 'read':
+ $perms = PERMS_SHOW | PERMS_READ;
+ break;
+ case 'edit':
+ $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT;
+ break;
+ case 'none':
+ $perms = 0;
+ break;
+ }
+ $perm->addDefaultPermission($perms, false);
+
+ /* Default guest permissions */
+ switch ($GLOBALS['prefs']->getValue('guest_permissions')) {
+ case 'read':
+ $perms = PERMS_SHOW | PERMS_READ;
+ break;
+ case 'none':
+ default:
+ $perms = 0;
+ break;
+ }
+ $perm->addGuestPermission($perms, false);
+
+ /* Default user groups permissions */
+ switch ($GLOBALS['prefs']->getValue('group_permissions')) {
+ case 'read':
+ $perms = PERMS_SHOW | PERMS_READ;
+ break;
+ case 'edit':
+ $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT;
+ break;
+ case 'delete':
+ $perms = PERMS_SHOW | PERMS_READ | PERMS_EDIT | PERMS_DELETE;
+ break;
+ case 'none':
+ default:
+ $perms = 0;
+ break;
+ }
+
+ if ($perms) {
+ $groups = Group::singleton();
+ $group_list = $groups->getGroupMemberships(Horde_Auth::getAuth());
+ if (!is_a($group_list, 'PEAR_Error') && count($group_list)) {
+ foreach ($group_list as $group_id => $group_name) {
+ $perm->addGroupPermission($group_id, $perms, false);
+ }
+ }
+ }
+ }
+ $gallery->setPermission($perm, true);
+
+ /* Initial tags */
+ if (count($tags)) {
+ $gallery->setTags($tags);
+ }
+
+ return $gallery;
+ }
+
+ /**
+ * Check that a slug exists.
+ *
+ * @param string $slug The slug name
+ *
+ * @return integer The share_id the slug represents, or 0 if not found.
+ */
+ function slugExists($slug)
+ {
+ // An empty slug should never match.
+ if (!strlen($slug)) {
+ return 0;
+ }
+
+ $stmt = $this->_db->prepare('SELECT share_id FROM '
+ . $this->shares->_table . ' WHERE attribute_slug = ?');
+
+ if (is_a($stmt, 'PEAR_Error')) {
+ Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return 0;
+ }
+
+ $result = $stmt->execute($slug);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ if (!$result->numRows()) {
+ return 0;
+ }
+
+ $slug = $result->fetchRow();
+
+ $result->free();
+ $stmt->free();
+
+ return $slug[0];
+ }
+
+ /**
+ * Retrieve an Ansel_Gallery given the gallery's slug
+ *
+ * @param string $slug The gallery slug
+ * @param array $overrides An array of attributes that should be overridden
+ * when the gallery is returned.
+ *
+ * @return mixed Ansel_Gallery object | PEAR_Error
+ */
+ function &getGalleryBySlug($slug, $overrides = array())
+ {
+ $id = $this->slugExists($slug);
+ if ($id) {
+ return $this->getGallery($id, $overrides);
+ } else {
+ return PEAR::raiseError(sprintf(_("Gallery %s not found."), $slug));
+ }
+ }
+
+ /**
+ * Retrieve an Ansel_Gallery given the share id
+ *
+ * @param integer $gallery_id The share_id to fetch
+ * @param array $overrides An array of attributes that should be
+ * overridden when the gallery is returned.
+ *
+ * @return mixed Ansel_Gallery | PEAR_Error
+ */
+ function &getGallery($gallery_id, $overrides = array())
+ {
+ // avoid cache server hits
+ if (isset($this->galleries[$gallery_id]) && !count($overrides)) {
+ return $this->galleries[$gallery_id];
+ }
+
+ if (!count($overrides) && $GLOBALS['conf']['ansel_cache']['usecache'] &&
+ ($gallery = $GLOBALS['cache']->get('Ansel_Gallery' . $gallery_id, $GLOBALS['conf']['cache']['default_lifetime'])) !== false) {
+
+ $this->galleries[$gallery_id] = unserialize($gallery);
+
+ return $this->galleries[$gallery_id];
+ }
+
+ $result = &$this->shares->getShareById($gallery_id);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $this->galleries[$gallery_id] = &$result;
+
+ // Don't cache if we have overridden anything
+ if (!count($overrides)) {
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->set('Ansel_Gallery' . $gallery_id, serialize($result));
+ }
+ } else {
+ foreach ($overrides as $key => $value) {
+ $this->galleries[$gallery_id]->set($key, $value, false);
+ }
+ }
+ return $this->galleries[$gallery_id];
+ }
+
+ /**
+ * Retrieve an array of Ansel_Gallery objects for the given slugs.
+ *
+ * @param array $slugs The gallery slugs
+ *
+ * @return mixed Array of Ansel_Gallery objects | PEAR_Error
+ */
+ function getGalleriesBySlugs($slugs)
+ {
+ $sql = 'SELECT share_id FROM ' . $this->shares->_table
+ . ' WHERE attribute_slug IN (' . str_repeat('?, ', count($slugs) - 1) . '?)';
+
+ $stmt = $this->shares->_db->prepare($sql);
+ if (is_a($stmt, 'PEAR_Error')) {
+ return $stmt;
+ }
+ $result = $stmt->execute($slugs);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $ids = array_values($result->fetchCol());
+ $shares = $this->shares->getShares($ids);
+
+ $stmt->free();
+ $result->free();
+
+ return $shares;
+ }
+
+ /**
+ * Retrieve an array of Ansel_Gallery objects for the requested ids
+ */
+ function getGalleries($ids)
+ {
+ return $this->shares->getShares($ids);
+ }
+
+ /**
+ * Empties a gallery of all images.
+ *
+ * @param Ansel_Gallery $gallery The ansel gallery to empty.
+ */
+ function emptyGallery($gallery)
+ {
+ $images = $gallery->listImages();
+ foreach ($images as $image) {
+ // Pretend we are a stack so we don't update the images count
+ // for every image deletion, since we know the end result will
+ // be zero.
+ $gallery->removeImage($image, true);
+ }
+ $gallery->set('images', 0, true);
+
+ // Clear the OtherGalleries widget cache
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->expire('Ansel_OtherGalleries' . $gallery->get('owner'));
+ }
+
+ }
+
+ /**
+ * Removes an Ansel_Gallery.
+ *
+ * @param Ansel_Gallery $gallery The gallery to delete
+ *
+ * @return mixed True || PEAR_Error
+ */
+ function removeGallery($gallery)
+ {
+ /* Get any children and empty them */
+ $children = $gallery->getChildren(null, true);
+ if (is_a($children, 'PEAR_Error')) {
+ return $children;
+ }
+ foreach ($children as $child) {
+ $this->emptyGallery($child);
+ $child->setTags(array());
+ }
+
+ /* Now empty the selected gallery of images */
+ $this->emptyGallery($gallery);
+
+ /* Clear all the tags. */
+ $gallery->setTags(array());
+
+ /* Get the parent, if it exists, before we delete the gallery. */
+ $parent = $gallery->getParent();
+ $id = $gallery->id;
+
+ /* Delete the gallery from storage */
+ $result = $this->shares->removeShare($gallery);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ /* Expire the cache */
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->expire('Ansel_Gallery' . $id);
+ }
+ unset($this->galleries[$id]);
+
+ /* See if we need to clear the has_subgalleries field */
+ if (is_a($parent, 'Ansel_Gallery')) {
+ if (!$parent->countChildren(PERMS_SHOW, false)) {
+ $parent->set('has_subgalleries', 0, true);
+
+ if ($GLOBALS['conf']['ansel_cache']['usecache']) {
+ $GLOBALS['cache']->expire('Ansel_Gallery' . $parent->id);
+ }
+ unset($this->galleries[$id]);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the image corresponding to the given id.
+ *
+ * @param integer $id The ID of the image to retrieve.
+ *
+ * @return Ansel_Image The image object corresponding to the given name.
+ */
+ function &getImage($id)
+ {
+ if (isset($this->images[$id])) {
+ return $this->images[$id];
+ }
+
+ $q = $this->_db->prepare('SELECT ' . $this->_getImageFields() . ' FROM ansel_images WHERE image_id = ?');
+ if (is_a($q, 'PEAR_Error')) {
+ Horde::logMessage($q, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $q;
+ }
+ $result = $q->execute((int)$id);
+ if (is_a($result, 'PEAR_Error')) {
+ Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $result;
+ }
+ $image = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
+ $q->free();
+ $result->free();
+ if (is_null($image)) {
+ return PEAR::raiseError(_("Photo not found"));
+ } elseif (is_a($image, 'PEAR_Error')) {
+ Horde::logMessage($image, __FILE__, __LINE__, PEAR_LOG_ERR);
+ return $image;
+ } else {
+ $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']);
+ $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']);
+ $this->images[$id] = new Ansel_Image($image);
+
+ return $this->images[$id];
+ }
+ }
+
+ /**
+ * Returns the images corresponding to the given ids.
+ *
+ * @param array $ids An array of image ids.
+ *
+ * @return array of Ansel_Image objects.
+ */
+ function getImages($ids, $preserve_order = false)
+ {
+ if (is_array($ids) && count($ids) > 0) {
+ $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images WHERE image_id IN (';
+ $i = 1;
+ $cnt = count($ids);
+ foreach ($ids as $id) {
+ $sql .= (int)$id . (($i++ < $cnt) ? ',' : ');');
+ }
+
+ $images = $this->_db->query($sql);
+ if (is_a($images, 'PEAR_Error')) {
+ return $images;
+ } elseif ($images->numRows() == 0) {
+ $images->free();
+ return PEAR::raiseError(_("Photos not found"));
+ }
+
+ $return = array();
+ while ($image = $images->fetchRow(MDB2_FETCHMODE_ASSOC)) {
+ $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']);
+ $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']);
+ $return[$image['image_id']] = new Ansel_Image($image);
+ $this->images[(int)$image['image_id']] = &$return[$image['image_id']];
+ }
+ $images->free();
+
+ /* Need to get comment counts if comments are enabled */
+ $ccounts = $this->_getImageCommentCounts(array_keys($return));
+ if (!is_a($ccounts, 'PEAR_Error') && count($ccounts)) {
+ foreach ($return as $key => $image) {
+ $return[$key]->commentCount = (!empty($ccounts[$key]) ? $ccounts[$key] : 0);
+ }
+ }
+
+ /* Preserve the order the ids were passed in) */
+ if ($preserve_order) {
+ foreach ($ids as $id) {
+ $ordered[$id] = $return[$id];
+ }
+ return $ordered;
+ }
+ return $return;
+ } else {
+ return array();
+ }
+ }
+
+ function _getImageCommentCounts($ids)
+ {
+ global $conf, $registry;
+
+ /* Need to get comment counts if comments are enabled */
+ if (($conf['comments']['allow'] == 'all' || ($conf['comments']['allow'] == 'authenticated' && Horde_Auth::getAuth())) &&
+ $registry->hasMethod('forums/numMessagesBatch')) {
+
+ return $registry->call('forums/numMessagesBatch',
+ array($ids, 'ansel'));
+ }
+
+ return array();
+ }
+
+ /**
+ * Return a list of image ids of the most recently added images.
+ *
+ * @param array $galleries An array of gallery ids to search in. If
+ * left empty, will search all galleries
+ * with PERMS_SHOW.
+ * @param integer $limit The maximum number of images to return
+ * @param string $slugs An array of gallery slugs.
+ * @param string $where Additional where clause
+ *
+ * @return array An array of Ansel_Image objects
+ */
+ function getRecentImages($galleries = array(), $limit = 10, $slugs = array())
+ {
+ $results = array();
+
+ if (!count($galleries) && !count($slugs)) {
+ $sql = 'SELECT DISTINCT ' . $this->_getImageFields('i') . ' FROM ansel_images i, '
+ . str_replace('WHERE' , ' WHERE i.gallery_id = s.share_id AND (', substr($this->shares->_getShareCriteria(Horde_Auth::getAuth()), 5)) . ')';
+ } elseif (!count($slugs) && count($galleries)) {
+ // Searching by gallery_id
+ $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images '
+ . 'WHERE gallery_id IN ('
+ . str_repeat('?, ', count($galleries) - 1) . '?) ';
+ } elseif (count($slugs)) {
+ // Searching by gallery_slug so we need to join the share table
+ $sql = 'SELECT ' . $this->_getImageFields() . ' FROM ansel_images LEFT JOIN '
+ . $this->shares->_table . ' ON ansel_images.gallery_id = '
+ . $this->shares->_table . '.share_id ' . 'WHERE attribute_slug IN ('
+ . str_repeat('?, ', count($slugs) - 1) . '?) ';
+ } else {
+ return array();
+ }
+
+ $sql .= ' ORDER BY image_uploaded_date DESC LIMIT ' . (int)$limit;
+ $query = $this->_db->prepare($sql);
+ if (is_a($query, 'PEAR_Error')) {
+ return $query;
+ }
+
+ if (count($slugs)) {
+ $images = $query->execute($slugs);
+ } else {
+ $images = $query->execute($galleries);
+ }
+ $query->free();
+ if (is_a($images, 'PEAR_Error')) {
+ return $images;
+ } elseif ($images->numRows() == 0) {
+ return array();
+ }
+
+ while ($image = $images->fetchRow(MDB2_FETCHMODE_ASSOC)) {
+ $image['image_filename'] = Horde_String::convertCharset($image['image_filename'], $GLOBALS['conf']['sql']['charset']);
+ $image['image_caption'] = Horde_String::convertCharset($image['image_caption'], $GLOBALS['conf']['sql']['charset']);
+ $results[] = new Ansel_Image($image);
+ }
+
+ $images->free();
+ return $results;
+ }
+
+ /**
+ * Check if a gallery exists. Need to do this here instead of Horde_Share
+ * since Horde_Share::exists() takes a share_name, not a share_id plus we
+ * might also be checking by gallery_slug and this is more efficient than
+ * a listShares() call for one gallery.
+ *
+ * @param integer $gallery_id The gallery id
+ * @param string $slug The gallery slug
+ *
+ * @return mixed true | false | PEAR_Error
+ */
+ function galleryExists($gallery_id, $slug = null)
+ {
+ if (empty($slug)) {
+ return (bool)$this->_db->queryOne(
+ 'SELECT COUNT(share_id) FROM ' . $this->shares->_table
+ . ' WHERE share_id = ' . (int)$gallery_id);
+ } else {
+ return (bool)$this->slugExists($slug);
+ }
+ }
+
+ /**
+ * Return a list of categories containing galleries with the given
+ * permissions for the current user.
+ *
+ * @param integer $perm The level of permissions required.
+ * @param integer $from The gallery to start listing at.
+ * @param integer $count The number of galleries to return.
+ *
+ * @return mixed List of categories | PEAR_Error
+ */
+ function listCategories($perm = PERMS_SHOW, $from = 0, $count = 0)
+ {
+ $sql = 'SELECT DISTINCT attribute_category FROM '
+ . $this->shares->_table;
+ $results = $this->shares->_db->query($sql);
+ if (is_a($results, 'PEAR_Error')) {
+ return $results;
+ }
+ $all_categories = $results->fetchCol('attribute_category');
+ $results->free();
+ if (count($all_categories) < $from) {
+ return array();
+ } else {
+ $categories = array();
+ foreach ($all_categories as $category) {
+ $categories[] = Horde_String::convertCharset(
+ $category, $GLOBALS['conf']['sql']['charset']);
+ }
+ if ($count > 0) {
+ return array_slice($categories, $from, $count);
+ } else {
+ return array_slice($categories, $from);
+ }
+ }
+ }
+
+ function countCategories($perms = PERMS_SHOW)
+ {
+ return count($this->listCategories($perms));
+ }
+
+ /**
+ * Return the count of galleries that the user has specified permissions to
+ * and that match any of the requested attributes.
+ *
+ * @param string $userid The user to check access for.
+ * @param integer $perm The level of permissions to require for a
+ * gallery to return it.
+ * @param mixed $attributes Restrict the galleries counted to those
+ * matching $attributes. An array of
+ * attribute/values pairs or a gallery owner
+ * username.
+ * @param string $parent The parent share to start counting at.
+ * @param boolean $allLevels Return all levels, or just the direct
+ * children of $parent? Defaults to all levels.
+ */
+ function countGalleries($userid, $perm = PERMS_SHOW, $attributes = null,
+ $parent = null, $allLevels = true)
+ {
+ static $counts;
+
+ if (is_a($parent, 'Ansel_Gallery')) {
+ $parent_id = $parent->getId();
+ } else {
+ $parent_id = $parent;
+ }
+
+ $key = "$userid,$perm,$parent_id,$allLevels"
+ . serialize($attributes);
+ if (isset($counts[$key])) {
+ return $counts[$key];
+ }
+
+ $count = $this->shares->countShares($userid, $perm, $attributes,
+ $parent, $allLevels);
+
+ $counts[$key] = $count;
+
+ return $count;
+ }
+
+ /**
+ * Retrieves the current user's gallery list from storage.
+ *
+ * @param integer $perm The level of permissions to require for a
+ * gallery to return it.
+ * @param mixed $attributes Restrict the galleries counted to those
+ * matching $attributes. An array of
+ * attribute/values pairs or a gallery owner
+ * username.
+ * @param mixed $parent The parent gallery to start listing at.
+ * (Ansel_Gallery, gallery id or null)
+ * @param boolean $allLevels Return all levels, or just the direct
+ * children of $parent?
+ * @param integer $from The gallery to start listing at.
+ * @param integer $count The number of galleries to return.
+ * @param string $sort_by The field to order the results by.
+ * @param integer $direction Sort direction:
+ * 0 - ascending
+ * 1 - descending
+ *
+ * @return mixed An array of Ansel_Gallery objects | PEAR_Error
+ */
+ function listGalleries($perm = PERMS_SHOW,
+ $attributes = null,
+ $parent = null,
+ $allLevels = true,
+ $from = 0,
+ $count = 0,
+ $sort_by = null,
+ $direction = 0)
+ {
+ return $this->shares->listShares(Horde_Auth::getAuth(), $perm, $attributes,
+ $from, $count, $sort_by, $direction,
+ $parent, $allLevels);
+ }
+
+ /**
+ * Retrieve json data for an arbitrary list of image ids, not necessarily
+ * from the same gallery.
+ *
+ * @param array $images An array of image ids
+ * @param string $style A named gallery style to force if requesting
+ * pretty thumbs.
+ * @param boolean $full Generate full urls
+ * @param string $image_view Which image view to use? screen, thumb etc..
+ * @param boolean $view_links Include links to the image view
+ *
+ * @return string The json data || PEAR_Error
+ */
+ function getImageJson($images, $style = null, $full = false,
+ $image_view = 'mini', $view_links = false)
+ {
+ $galleries = array();
+ if (is_null($style)) {
+ $style = 'ansel_default';
+ }
+
+ $json = array();
+
+ foreach ($images as $id) {
+ $image = $this->getImage($id);
+ if (!is_a($image, 'PEAR_Error')) {
+ $gallery_id = abs($image->gallery);
+
+ if (empty($galleries[$gallery_id])) {
+ $galleries[$gallery_id]['gallery'] = $GLOBALS['ansel_storage']->getGallery($gallery_id);
+ if (is_a($galleries[$gallery_id]['gallery'], 'PEAR_Error')) {
+ return $galleries[$gallery_id];
+ }
+ }
+
+ // Any authentication that needs to take place for any of the
+ // images included here MUST have already taken place or the
+ // image will not be incldued in the output.
+ if (!isset($galleries[$gallery_id]['perm'])) {
+ $galleries[$gallery_id]['perm'] =
+ ($galleries[$gallery_id]['gallery']->hasPermission(Horde_Auth::getAuth(), PERMS_READ) &&
+ $galleries[$gallery_id]['gallery']->isOldEnough() &&
+ !$galleries[$gallery_id]['gallery']->hasPasswd());
+ }
+
+ if ($galleries[$gallery_id]['perm']) {
+ $data = array(Ansel::getImageUrl($image->id, $image_view, $full, $style),
+ htmlspecialchars($image->filename, ENT_COMPAT, Horde_Nls::getCharset()),
+ Horde_Text_Filter::filter($image->caption, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL)),
+ $image->id,
+ 0);
+
+ if ($view_links) {
+ $data[] = Ansel::getUrlFor('view',
+ array('gallery' => $image->gallery,
+ 'image' => $image->id,
+ 'view' => 'Image',
+ 'slug' => $galleries[$gallery_id]['gallery']->get('slug')),
+ $full);
+
+ $data[] = Ansel::getUrlFor('view',
+ array('gallery' => $image->gallery,
+ 'slug' => $galleries[$gallery_id]['gallery']->get('slug'),
+ 'view' => 'Gallery'),
+ $full);
+ }
+
+ $json[] = $data;
+ }
+ }
+ }
+
+ if (count($json)) {
+ return Horde_Serialize::serialize($json, Horde_Serialize::JSON, Horde_Nls::getCharset());
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns a random Ansel_Gallery from a list fitting the search criteria.
+ *
+ * @see Ansel_Storage::listGalleries()
+ */
+ function getRandomGallery($perm = PERMS_SHOW, $attributes = null,
+ $parent = null, $allLevels = true)
+ {
+ $num_galleries = $this->countGalleries(Horde_Auth::getAuth(), $perm,
+ $attributes, $parent,
+ $allLevels);
+ if (!$num_galleries) {
+ return $num_galleries;
+ }
+
+ $galleries = $this->listGalleries($perm, $attributes, $parent,
+ $allLevels,
+ rand(0, $num_galleries - 1),
+ 1);
+ $gallery = array_pop($galleries);
+ return $gallery;
+ }
+
+ /**
+ * Lists a slice of the image ids in the given gallery.
+ *
+ * @param integer $gallery_id The gallery to list from.
+ * @param integer $from The image to start listing.
+ * @param integer $count The numer of images to list.
+ * @param mixed $fields The fields to return (either an array of
+ * fileds or a single string).
+ * @param string $where A SQL where clause ($gallery_id will be
+ * ignored if this is non-empty).
+ * @param mixed $sort The field(s) to sort by.
+ *
+ * @return mixed An array of image_ids | PEAR_Error
+ */
+ function listImages($gallery_id, $from = 0, $count = 0,
+ $fields = 'image_id', $where = '', $sort = 'image_sort')
+ {
+ if (is_array($fields)) {
+ $field_count = count($fields);
+ $fields = implode(', ', $fields);
+ } elseif ($fields == '*') {
+ // The count is not important, as long as it's > 1
+ $field_count = 2;
+ } else {
+ $field_count = substr_count($fields, ',') + 1;
+ }
+
+ if (is_array($sort)) {
+ $sort = implode(', ', $sort);
+ }
+
+ if (!empty($where)) {
+ $query_where = 'WHERE ' . $where;
+ } else {
+ $query_where = 'WHERE gallery_id = ' . $gallery_id;
+ }
+ $this->_db->setLimit($count, $from);
+ $sql = 'SELECT ' . $fields . ' FROM ansel_images ' . $query_where . ' ORDER BY ' . $sort;
+ Horde::logMessage('Query by Ansel_Storage::listImages: ' . $sql, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $results = $this->_db->query('SELECT ' . $fields . ' FROM ansel_images '
+ . $query_where . ' ORDER BY ' . $sort);
+ if (is_a($results, 'PEAR_Error')) {
+ return $results;
+ }
+ if ($field_count > 1) {
+ return $results->fetchAll(MDB2_FETCHMODE_ASSOC, true, true, false);
+ } else {
+ return $results->fetchCol();
+ }
+ }
+
+ /**
+ * Return images' geolocation data.
+ *
+ * @param array $image_ids An array of image_ids to look up.
+ * @param integer $gallery A gallery id. If this is provided, will return
+ * all images in the gallery that have geolocation
+ * data ($image_ids would be ignored).
+ *
+ * @return mixed An array of geodata || PEAR_Error
+ */
+ function getImagesGeodata($image_ids = array(), $gallery = null)
+ {
+ if ((!is_array($image_ids) || count($image_ids) == 0) && empty($gallery)) {
+ return array();
+ }
+
+ if (!empty($gallery)) {
+ $where = 'gallery_id = ' . (int)$gallery . ' AND LENGTH(image_latitude) > 0';
+ } elseif (count($image_ids) > 0) {
+ $where = 'image_id IN(' . implode(',', $image_ids) . ') AND LENGTH(image_latitude) > 0';
+ } else {
+ return array();
+ }
+
+ return $this->listImages(0, 0, 0, array('image_id as id', 'image_id', 'image_latitude', 'image_longitude', 'image_location'), $where);
+ }
+
+ /**
+ * Get image attribtues from ansel_image_attributes table
+ *
+ * @param $image_id
+ * @return unknown_type
+ */
+ function getImageAttributes($image_id)
+ {
+ return $GLOBALS['ansel_db']->queryAll('SELECT attr_name, attr_value FROM ansel_image_attributes WHERE image_id = ' . (int)$image_id, null, MDB2_FETCHMODE_ASSOC, true);
+ }
+
+ /**
+ * Like getRecentImages, but returns geotag data for the most recently added
+ * images from the current user. Useful for providing images to help locate
+ * images at the same place.
+ */
+ function getRecentImagesGeodata($user = null, $start = 0, $count = 8)
+ {
+ $galleries = $this->listGalleries('PERMS_EDIT', $user);
+ $where = 'gallery_id IN(' . implode(',', array_keys($galleries)) . ') AND LENGTH(image_latitude) > 0 GROUP BY image_latitude, image_longitude';
+ return $this->listImages(0, $start, $count, array('image_id as id', 'image_id', 'gallery_id', 'image_latitude', 'image_longitude', 'image_location'), $where, 'image_geotag_date DESC');
+ }
+
+ function searchLocations($search = '')
+ {
+ $sql = 'SELECT DISTINCT image_location, image_latitude, image_longitude'
+ . ' FROM ansel_images WHERE image_location LIKE "' . $search . '%"';
+ $results = $this->_db->query($sql);
+ if (is_a($results, 'PEAR_Error')) {
+ return $results;
+ }
+
+ return $results->fetchAll(MDB2_FETCHMODE_ASSOC, true, true, false);
+ }
+
+ /**
+ * Helper function to get a string of field names
+ *
+ * @return string
+ */
+ function _getImageFields($alias = '')
+ {
+ $fields = array('image_id', 'gallery_id', 'image_filename', 'image_type',
+ 'image_caption', 'image_uploaded_date', 'image_sort',
+ 'image_faces', 'image_original_date', 'image_latitude',
+ 'image_longitude', 'image_location', 'image_geotag_date');
+ if (!empty($alias)) {
+ foreach ($fields as $field) {
+ $new[] = $alias . '.' . $field;
+ }
+ return implode(', ', $new);
+ }
+
+ return implode(', ', $fields);
+ }
+
+}