Lots of H4 chagnes and improvements
authorMichael J. Rubinsky <mrubinsk@horde.org>
Wed, 17 Feb 2010 23:20:22 +0000 (18:20 -0500)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Wed, 17 Feb 2010 23:22:00 +0000 (18:22 -0500)
CS changes, PHP5-ify Ansel_Image and Ansel_ImageView classes.
Start moving all SQL queries into Horde_Storage.

13 files changed:
ansel/img/prettythumb.php
ansel/lib/Gallery.php
ansel/lib/Image.php
ansel/lib/ImageView/Mini.php
ansel/lib/ImageView/PlainStack.php
ansel/lib/ImageView/PolaroidStack.php
ansel/lib/ImageView/PolaroidThumb.php
ansel/lib/ImageView/PrettyThumb.php
ansel/lib/ImageView/RoundedStack.php
ansel/lib/ImageView/Screen.php
ansel/lib/ImageView/ShadowSharpThumb.php
ansel/lib/ImageView/Thumb.php
ansel/lib/Storage.php

index dc28e25..c85e4f0 100644 (file)
@@ -41,6 +41,4 @@ if ($conf['vfs']['src'] == 'sendfile') {
     exit;
 }
 
-if (is_a($result = $image->display('prettythumb', $style), 'PEAR_Error')) {
-    Horde::fatal($result, __FILE__, __LINE__);
-}
+$image->display('prettythumb', $style);
index d3f8dcd..1716a6c 100644 (file)
@@ -627,21 +627,19 @@ class Ansel_Gallery extends Horde_Share_Object_sql_hierarchical
             try {
                 $iview = Ansel_ImageView::factory($gal_style['default_galleryimage_type'], $params);
                 $img = $iview->create();
-                if ($img) {
-                    // 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);
-                }
+
+                // 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;
 
             } catch (Horde_Exception $e) {
                 // Might not support the requested style...try ansel_default
index a4f0a73..6bae5e0 100644 (file)
 class Ansel_Image Implements Iterator
 {
     /**
-     * @var integer  The gallery id of this image's parent gallery
+     * The gallery id of this image's parent gallery
+     *
+     * @var integer
      */
     public $gallery;
 
     /**
-     * @var Horde_Image_Base  Horde_Image object for this image.
+     * Image Id
+     *
+     * @var integer
      */
-    protected $_image;
-    protected $_dirty;
-    protected $_loaded = array();
-    protected $_data = array();
+    public $id = null;
+
     /**
-     * Holds an array of tags for this image
-     * @var array
+     * The filename for this image
+     *
+     * @var string
      */
-    protected $_tags = array();
+    public $filename = 'Untitled';
 
     /**
-     * Cache the raw EXIF data locally
+     * Image caption
      *
-     * @var array
+     * @var string
      */
-    protected $_exif = array();
-
-    public $id = null;
-    public $filename = 'Untitled';
     public $caption = '';
+
+    /**
+     * The image's mime type
+     * 
+     * @var string
+     */
     public $type = 'image/jpeg';
 
     /**
-     * timestamp of uploaded date
+     * Timestamp of uploaded datetime
      *
      * @var integer
      */
     public $uploaded;
 
+    /**
+     * Sort count for this image
+     *
+     * @var integer
+     */
     public $sort;
+
+    /**
+     * The number of comments for this image, if available.
+     *
+     * @var integer
+     */
     public $commentCount;
+
+    /**
+     * Number of faces in this image
+     * @var integer
+     */
     public $facesCount;
+
+    /**
+     * Latitude
+     *
+     * @var string
+     */
     public $lat;
+
+    /**
+     * Longitude
+     *
+     * @var string
+     */
     public $lng;
+
+    /**
+     * Textual location
+     *
+     * @var string
+     */
     public $location;
+
+    /**
+     * Timestamp for when image was geotagged
+     *
+     * @var integer
+     */
     public $geotag_timestamp;
 
     /**
@@ -66,10 +111,51 @@ class Ansel_Image Implements Iterator
     public $originalDate;
 
     /**
-     * TODO: refactor Ansel_Image to use a ::get() method like Ansel_Gallery
-     * instead of direct instance variable access and all the nonsense below.
+     * Horde_Image object for this image.
+     *
+     * @var Horde_Image_Base
+     */
+    protected $_image;
+
+    /**
+     * Dirty flag
+     *
+     * @var boolean
+     */
+    protected $_dirty;
+
+    /**
+     * Flags for loaded views
+     *
+     * @var array
+     */
+    protected $_loaded = array();
+
+    /**
+     * Binary image data for loaded views
+     *
+     * @var array
+     */
+    protected $_data = array();
+    /**
+     * Holds an array of tags for this image
+     *
+     * @var array
+     */
+    protected $_tags = array();
+
+    /**
+     * Cache the raw EXIF data locally
+     *
+     * @var array
+     */
+    protected $_exif = array();
+
+    /**
+     * Const'r
+     *
+     * @param array $image
      *
-     * @param unknown_type $image
      * @return Ansel_Image
      */
     public function __construct($image = array())
@@ -89,7 +175,6 @@ class Ansel_Image Implements Iterator
                 $this->sort = $image['image_sort'];
             }
 
-            // New image?
             if (!empty($image['image_id'])) {
                 $this->id = $image['image_id'];
             }
@@ -153,7 +238,7 @@ class Ansel_Image Implements Iterator
      *
      * @return string  The vfs path for this image.
      */
-    function getVFSPath($view = 'full', $style = null)
+    public function getVFSPath($view = 'full', $style = null)
     {
         $view = $this->getViewHash($view, $style);
         return '.horde/ansel/'
@@ -166,7 +251,7 @@ class Ansel_Image Implements Iterator
      *
      * @return string  This image's VFS file name.
      */
-    function getVFSName($view)
+    public function getVFSName($view)
     {
         $vfsname = $this->id;
 
@@ -191,9 +276,9 @@ class Ansel_Image Implements Iterator
      * @param string $style  The named gallery style.
      *
      * @return boolean
-     * @throws Horde_Exception
+     * @throws Ansel_Exception
      */
-    function load($view = 'full', $style = null)
+    public 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
@@ -203,8 +288,8 @@ class Ansel_Image Implements Iterator
             $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;
@@ -227,7 +312,7 @@ class Ansel_Image Implements Iterator
         $data = $GLOBALS['ansel_vfs']->read($vfspath, $this->getVFSName($view));
         if (is_a($data, 'PEAR_Error')) {
             Horde::logMessage($date, __FILE__, __LINE__, PEAR_LOG_ERR);
-            throw new Horde_Exception($data->getMessage());
+            throw new Ansel_Exception($data);
         }
 
         $this->_data[$viewHash] = $data;
@@ -247,7 +332,7 @@ class Ansel_Image Implements Iterator
      *
      * @static
      */
-    function viewExists($id, $view, $style)
+    public function viewExists($id, $view, $style)
     {
         /* We cannot check empty styles since we cannot get the hash */
         if (empty($style)) {
@@ -283,9 +368,9 @@ class Ansel_Image Implements Iterator
      * @param string $style  A named gallery style
      *
      * @return boolean
-     * @throws Horde_Exception
+     * @throws Ansel_Exception
      */
-    function createView($view, $style = null)
+    public 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
@@ -302,63 +387,54 @@ class Ansel_Image Implements Iterator
         if ($GLOBALS['ansel_vfs']->exists($vfspath, $this->getVFSName($view))) {
             return true;
         }
-
-        $data = $GLOBALS['ansel_vfs']->read($this->getVFSPath('full'),
-                                            $this->getVFSName('full'));
+        $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);
-            throw new Horde_Exception($data->getMessage());
+            throw new Ansel_Exception($data);
         }
         $this->_image->loadString($this->getVFSPath('full') . '/' . $this->id, $data);
         $styleDef = Ansel::getStyleDefinition($style);
         if ($view == 'prettythumb') {
             $viewType = $styleDef['thumbstyle'];
         } else {
-            $viewType = $view;
+            // Screen, Mini, Thumb
+            $viewType = ucfirst($view);
         }
-        $iview = Ansel_ImageView::factory($viewType, array('image' => $this,
-                                                           'style' => $style));
 
-        if (is_a($iview, 'PEAR_Error')) {
+        try {
+           $iview = Ansel_ImageView::factory($viewType, array('image' => $this, 'style' => $style));
+        } catch (Ansel_Exception $e) {
             // 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;
-                }
+                // If we still fail, the exception gets thrown up the chain.
+                $iview = Ansel_ImageView::factory('Thumb', array('image' => $this, 'style' => 'ansel_default'));
             }
         }
 
-        $res = $iview->create();
-        if (is_a($res, 'PEAR_Error')) {
-            return $res;
-        }
+        /* Create the ImageView */
+        $iview->create();
 
+        /* Cache the data from the new imageview */
         $view = $this->getViewHash($view, $style);
-
         try {
             $this->_data[$view] = $this->_image->raw();
         } catch (Horde_Image_Exception $e) {
-            throw new Horde_Exception_Prior($e);
+            throw new Ansel_Exception($e);
         }
-        $this->_image->loadString($vfspath . '/' . $this->id,
-                                  $this->_data[$view]);
+
+        /* ...and put it in Horde_Image obejct, then save */
+        $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);
+        $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view), $this->_data[$view], true);
 
-        // Autowatermark the screen view
+        /* 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);
+            $GLOBALS['ansel_vfs']->writeData($vfspath, $this->getVFSName($view), $this->_image->_data);
         }
 
         return true;
@@ -366,13 +442,23 @@ class Ansel_Image Implements Iterator
 
     /**
      * Writes the current data to vfs, used when creating a new image
+     *
+     * @return boolean
+     * @throws Ansel_Exception
      */
-    function _writeData()
+    protected function _writeData()
     {
         $this->_dirty = false;
-        return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath('full'),
+
+        $results = $GLOBALS['ansel_vfs']->writeData($this->getVFSPath('full'),
                                                 $this->getVFSName('full'),
                                                 $this->_data['full'], true);
+
+        if ($results instanceof PEAR_Error) {
+            throw new Ansel_Exception($results);
+        }
+
+        return true;
     }
 
     /**
@@ -382,11 +468,15 @@ class Ansel_Image Implements Iterator
      * @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.
+     *
+     * @return boolean
+     * @throws Ansel_Exception
      */
-    function updateData($data, $view = 'full')
+    public function updateData($data, $view = 'full')
     {
+        // TODO: Get rid of this, $data should only be valid data.
         if (is_a($data, 'PEAR_Error')) {
-            return $data;
+            throw new Ansel_Exception($data);
         }
 
         /* Delete old cached data if we are replacing the full image */
@@ -394,15 +484,26 @@ class Ansel_Image Implements Iterator
             $this->deleteCache();
         }
 
-        return $GLOBALS['ansel_vfs']->writeData($this->getVFSPath($view),
+        $results = $GLOBALS['ansel_vfs']->writeData($this->getVFSPath($view),
                                                 $this->getVFSName($view),
                                                 $data, true);
+
+        if ($results instanceof PEAR_Error) {
+            throw new Ansel_Exception($results);
+        }
     }
 
     /**
-     * Update the geotag data
+     * Update the image's geotag data. Saves to backend storage as well, so no
+     * need to call self::save()
+     *
+     * @param string $lat       Latitude
+     * @param string $lng       Longitude
+     * @param string $location  Textual location
+     *
+     * @return void
      */
-    function geotag($lat, $lng, $location = '')
+    public function geotag($lat, $lng, $location = '')
     {
         $this->lat = $lat;
         $this->lng = $lng;
@@ -412,77 +513,23 @@ class Ansel_Image Implements Iterator
     }
 
     /**
-     * Save basic image details
+     * Save image details to storage.
      *
-     * @TODO: Move all SQL queries to Ansel_Storage::?
+     * @TODO: Move all SQL queries to Ansel_Storage::
+     *
+     * @return The image id
+     * @throws Ansel_Exception
      */
-    function save()
+    public function save()
     {
-        /* If we have an id, then it's an existing image.*/
+        /* Existing image, just save and exit */
         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;
+            /* Save image details */
+            return $GLOBALS['ansel_storage']->saveImage($this);
         }
 
-        /* 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;
+        /* New image, need to save the image files, exif etc... */
+        $GLOBALS['ansel_storage']->saveImage($this);
 
         /* The EXIF functions require a stream, so we need to save before we read */
         $this->_writeData();
@@ -510,7 +557,7 @@ class Ansel_Image Implements Iterator
 
         /* Save again if EXIF changed any values */
         if (!empty($needUpdate)) {
-            $this->save();
+            $GLOBALS['ansel_storage']->saveImage($this);
         }
 
         return $this->id;
@@ -788,24 +835,25 @@ class Ansel_Image Implements Iterator
                 return $gallery;
             }
             if (!$gallery->canDownload()) {
-                return PEAR::RaiseError(sprintf(_("Access denied downloading photos from \"%s\"."), $gallery->get('name')));
+                throw Horde_Exception_PermissionDenied(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;
+                throw new Ansel_Exception($data);
             }
             echo $data;
             return;
         }
         try {
             $this->load($view, $style);
-            $this->_image->display();
-        } catch (Horde_Exception $e) {
-            Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+        } catch (Ansel_Exception $e) {
+            throw new Ansel_Exception($e);
         }
+
+        $this->_image->display();
     }
 
     /**
index 92486b5..c09ff57 100755 (executable)
@@ -7,13 +7,17 @@
  */
 class Ansel_ImageView_Mini extends Ansel_ImageView
 {
+    /**
+     *
+     * @return Horde_Image
+     */
     protected function _create()
     {
         $this->_image->resize(min(50, $this->_dimensions['width']),
                                       min(50, $this->_dimensions['height']),
                                       true);
 
-        return true;
+        return $this->_image->getHordeImage();
     }
 
 }
index c5cc15e..b251f46 100644 (file)
@@ -35,7 +35,7 @@ class Ansel_ImageView_PlainStack extends Ansel_ImageView
             $baseImg->resize($GLOBALS['conf']['thumbnail']['width'],
                              $GLOBALS['conf']['thumbnail']['height']);
         } catch (Horde_Image_Exception $e) {
-            return false;
+            throw new Ansel_Exception($e);
         }
 
         return $baseImg;
index b2b2ddb..34e0501 100644 (file)
@@ -35,7 +35,7 @@ class Ansel_ImageView_PolaroidStack extends Ansel_ImageView
                              $GLOBALS['conf']['thumbnail']['height']);
 
         } catch (Horde_Image_Exception $e) {
-            return false;
+            throw new Ansel_Exception($e);
         }
 
         return $baseImg;
index 8b750cc..a392d8b 100644 (file)
@@ -11,7 +11,7 @@ class Ansel_ImageView_PolaroidThumb extends Ansel_ImageView
 
     /**
      *
-     * @return boolean
+     * @return Horde_Image
      */
     protected function _create()
     {
@@ -35,7 +35,7 @@ class Ansel_ImageView_PolaroidThumb extends Ansel_ImageView
 
                 $this->_image->applyEffects();
             } catch (Horde_Image_Exception $e) {
-                return false;
+                throw new Ansel_Exception($e);
             }
 
             return true;
index 3acf67b..bbf8df9 100644 (file)
@@ -11,7 +11,7 @@ class Ansel_ImageView_PrettyThumb extends Ansel_ImageView
 
     /**
      *
-     * @return boolean
+     * @return Horde_Image
      */
     protected function _create()
     {
@@ -39,11 +39,11 @@ class Ansel_ImageView_PrettyThumb extends Ansel_ImageView
                                                              'distance' => 5,
                                                              'fade' => 3));
             } catch (Horde_Image_Exception $e) {
-                return false;
+                throw new Ansel_Exception($e);
             }
             $this->_image->applyEffects();
 
-            return true;
+            return $this->_image->getHordeImage();
         }
     }
 
index 5783d67..76319b9 100644 (file)
@@ -37,7 +37,7 @@ class Ansel_ImageView_RoundedStack extends Ansel_ImageView
             $baseImg->resize($GLOBALS['conf']['thumbnail']['width'],
                              $GLOBALS['conf']['thumbnail']['height']);
         } catch (Horde_Image_Exception $e) {
-            return false;
+            throw new Ansel_Exception($e);
         }
 
         return $baseImg;
index b809219..d50f111 100755 (executable)
@@ -17,7 +17,7 @@ class Ansel_ImageView_Screen extends Ansel_ImageView
                               min($GLOBALS['conf']['screen']['height'], $this->_dimensions['height']),
                               true);
 
-        return true;
+        return $this->_image->getHordeImage();
     }
 
 }
index 9153b17..ffc994e 100644 (file)
@@ -38,10 +38,10 @@ class Ansel_ImageView_ShadowSharpThumb extends Ansel_ImageView
                                                'fade' => 2));
                 $this->_image->applyEffects();
             } catch (Horde_Image_Exception $e) {
-                return false;
+                throw new Ansel_Exception($e);
             }
 
-            return true;
+            return $this->_image->getHordeImage();
         }
     }
 
index 5e650c5..62f381d 100755 (executable)
@@ -9,14 +9,14 @@ class Ansel_ImageView_Thumb extends Ansel_ImageView
 {
     /**
      *
-     * @return boolean
+     * @return Horde_Image
      */
     protected function _create()
     {
         $this->_image->resize(min($GLOBALS['conf']['thumbnail']['width'], $this->_dimensions['width']),
                               min($GLOBALS['conf']['thumbnail']['height'], $this->_dimensions['height']),
                               true);
-        return true;
+        return $this->_image->getHordeImage();
     }
 
 }
index 3c19915..998a703 100644 (file)
@@ -506,6 +506,86 @@ class Ansel_Storage
     }
 
     /**
+     * Save image details to storage. Does NOT update the cached image files.
+     *
+     * @param Ansel_Image $image
+     *
+     * @return integer The image id
+     *
+     * @throws Ansel_Exception
+     */
+    public function saveImage(Ansel_Image $image)
+    {
+        /* If we have an id, then it's an existing image.*/
+        if ($image->id) {
+            $update = $this->_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);
+                throw new Ansel_Exception($update);
+            }
+            $result = $update->execute(array(Horde_String::convertCharset($image->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+                                             $image->type,
+                                             Horde_String::convertCharset($image->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+                                             $image->sort,
+                                             $image->originalDate,
+                                             $image->lat,
+                                             $image->lng,
+                                             $image->location,
+                                             $image->geotag_timestamp,
+                                             $image->id));
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($update, __FILE__, __LINE__, PEAR_LOG_ERR);
+                throw new Ansel_Exception($result);
+            }
+            $update->free();
+
+            return $result;
+        }
+
+        /* Saving a new Image */
+        if (!$image->gallery || !strlen($image->filename) || !$image->type) {
+            throw new Ansel_Exception(_("Incomplete photo"));
+        }
+
+        /* Get the next image_id */
+        $image_id = $this->_db->nextId('ansel_images');
+        if (is_a($image_id, 'PEAR_Error')) {
+            throw new Ansel_Exception($image_id);
+        }
+
+        /* Prepare the SQL statement */
+        $insert = $this->_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);
+            throw new Ansel_Exception($insert);
+        }
+
+        /* Perform the INSERT */
+        $result = $insert->execute(array($image_id,
+                                         $image->gallery,
+                                         Horde_String::convertCharset($image->filename, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+                                         $image->type,
+                                         Horde_String::convertCharset($image->caption, Horde_Nls::getCharset(), $GLOBALS['conf']['sql']['charset']),
+                                         $image->uploaded,
+                                         $image->sort,
+                                         $image->originalDate,
+                                         $image->lat,
+                                         $image->lng,
+                                         $image->location,
+                                         (empty($image->lat) ? 0 : $image->uploaded)));
+        $insert->free();
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Ansel_Exception($result);
+        }
+
+        /* Keep the image_id */
+        $image->id = $image_id;
+
+        return $image->id;
+    }
+
+    /**
      * Returns the images corresponding to the given ids.
      *
      * @param array $params function parameters: