3 class E_UploadException extends E_NggErrorException
5 function __construct($message='', $code=NULL, $previous=NULL)
7 if (!$message) $message = "There was a problem uploading the file.";
8 parent::__construct($message, $code, $previous);
12 class E_InsufficientWriteAccessException extends E_NggErrorException
14 function __construct($message=FALSE, $filename=NULL, $code=NULL, $previous=NULL)
16 if (!$message) $message = "Could not write to file. Please check filesystem permissions.";
17 if ($filename) $message .= " Filename: {$filename}";
18 if (PHP_VERSION_ID >= 50300)
19 parent::__construct($message, $code, $previous);
21 parent::__construct($message, $code);
25 class E_NoSpaceAvailableException extends E_NggErrorException
27 function __construct($message='', $code=NULL, $previous=NULL)
29 if (!$message) $message = "You have exceeded your storage capacity. Please remove some files and try again.";
30 parent::__construct($message, $code, $previous);
34 class E_No_Image_Library_Exception extends E_NggErrorException
36 function __construct($message='', $code=NULL, $previous=NULL)
38 if (!$message) $message = "The site does not support the GD Image library. Please ask your hosting provider to enable it.";
39 parent::__construct($message, $code, $previous);
44 class Mixin_GalleryStorage_Driver_Base extends Mixin
47 * Set correct file permissions (taken from wp core). Should be called
48 * after writing any file
51 * @param string $filename
52 * @return bool $result
54 function _chmod($filename = '')
56 $stat = @ stat( dirname($filename) );
57 $perms = $stat['mode'] & 0000666; // Remove execute bits for files
58 if ( @chmod($filename, $perms) )
65 * Gets the id of a gallery, regardless of whether an integer
66 * or object was passed as an argument
67 * @param mixed $gallery_obj_or_id
69 function _get_gallery_id($gallery_obj_or_id)
72 $gallery_key = $this->object->_gallery_mapper->get_primary_key_column();
73 if (is_object($gallery_obj_or_id)) {
74 if (isset($gallery_obj_or_id->$gallery_key)) {
75 $retval = $gallery_obj_or_id->$gallery_key;
78 elseif(is_numeric($gallery_obj_or_id)) {
79 $retval = $gallery_obj_or_id;
86 * Gets the id of an image, regardless of whether an integer
87 * or object was passed as an argument
88 * @param type $image_obj_or_id
90 function _get_image_id($image_obj_or_id)
94 $image_key = $this->object->_image_mapper->get_primary_key_column();
95 if (is_object($image_obj_or_id)) {
96 if (isset($image_obj_or_id->$image_key)) {
97 $retval = $image_obj_or_id->$image_key;
100 elseif (is_numeric($image_obj_or_id)) {
101 $retval = $image_obj_or_id;
107 function convert_slashes($path)
109 $search = array('/', "\\");
110 $replace = array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR);
112 return str_replace($search, $replace, $path);
116 function delete_directory($abspath)
120 if (@file_exists($abspath)) {
121 $files = scandir($abspath);
124 foreach ($files as $file) {
125 $file_abspath = implode(DIRECTORY_SEPARATOR, array(rtrim($abspath, "/\\"), $file));
126 if (is_dir($file_abspath)) $this->object->delete_directory($file_abspath);
127 else unlink($file_abspath);
130 $retval = @file_exists($abspath);
137 * Backs up an image file
138 * @param int|object $image
140 function backup_image($image)
144 if (($image_path = $this->object->get_image_abspath($image))) {
145 $retval = copy($image_path, $this->object->get_backup_abspath($image));
152 * Copies images into another gallery
153 * @param array $images
154 * @param int|object $gallery
155 * @param boolean $db optionally only copy the image files
156 * @param boolean $move move the image instead of copying
158 function copy_images($images, $gallery, $db=TRUE, $move=FALSE)
162 // Ensure we have a valid gallery
163 if (($gallery = $this->object->_get_gallery_id($gallery))) {
164 $gallery_path = $this->object->get_gallery_abspath($gallery);
165 $image_key = $this->object->_image_mapper->get_primary_key_column();
168 // Iterate through each image to copy...
169 foreach ($images as $image) {
171 // Copy each image size
172 foreach ($this->object->get_image_sizes() as $size) {
173 $image_path = $this->object->get_image_abspath($image, $size);
174 $dst = implode(DIRECTORY_SEPARATOR, array($gallery_path, basename($image_path)));
175 $success = $move ? move($image_path, $dst) : copy($image_path, $dst);
176 if (!$success) $retval = FALSE;
181 if (is_numeric($image)) $this->object->_image_mapper($image);
182 unset($image->$image_key);
183 $image->galleryid = $gallery;
192 * Empties the gallery cache directory of content
194 function flush_cache($gallery)
196 $cache = $this->object->get_registry()->get_utility('I_Cache');
197 $cache->flush_directory($this->object->get_cache_abspath($gallery));
201 * Gets the absolute path of the backup of an original image
202 * @param string $image
204 function get_backup_abspath($image)
208 if (($image_path = $this->object->get_image_abspath($image))) {
209 $retval = $image_path.'_backup';
216 * Returns the absolute path to the cache directory of a gallery.
218 * Without the gallery parameter the legacy (pre 2.0) shared directory is returned.
220 * @param int|stdClass|C_Gallery $gallery (optional)
221 * @return string Absolute path to cache directory
223 function get_cache_abspath($gallery = FALSE)
227 if (FALSE == $gallery)
229 $gallerypath = C_NextGen_Settings::get_instance()->gallerypath;
230 $retval = implode(DIRECTORY_SEPARATOR, array(
231 rtrim(C_Fs::get_instance()->get_document_root('gallery'), "/\\"),
232 rtrim($gallerypath, "/\\"),
237 if (is_numeric($gallery))
239 $gallery = $this->object->_gallery_mapper->find($gallery);
241 $retval = rtrim(implode(DIRECTORY_SEPARATOR, array($this->object->get_gallery_abspath($gallery), 'dynamic')), "/\\");
248 * Gets the absolute path where the full-sized image is stored
249 * @param int|object $image
251 function get_full_abspath($image)
253 return $this->object->get_image_abspath($image, 'full');
257 * Alias to get_image_dimensions()
258 * @param int|object $image
261 function get_full_dimensions($image)
263 return $this->object->get_image_dimensions($image, 'full');
267 * Alias to get_image_html()
268 * @param int|object $image
271 function get_full_html($image)
273 return $this->object->get_image_html($image, 'full');
277 * Alias for get_original_url()
279 * @param int|stdClass|C_Image $image
282 function get_full_url($image, $check_existance=FALSE)
284 return $this->object->get_image_url($image, 'full', $check_existance);
288 * Gets the dimensions for a particular-sized image
290 * @param int|object $image
291 * @param string $size
294 function get_image_dimensions($image, $size='full')
298 // If an image id was provided, get the entity
299 if (is_numeric($image)) $image = $this->object->_image_mapper->find($image);
301 // Ensure we have a valid image
304 // Adjust size parameter
317 // Image dimensions are stored in the $image->meta_data
318 // property for all implementations
319 if (isset($image->meta_data) && isset($image->meta_data[$size])) {
320 $retval = $image->meta_data[$size];
323 // Didn't exist for meta data. We'll have to compute
324 // dimensions in the meta_data after computing? This is most likely
325 // due to a dynamic image size being calculated for the first time
328 $abspath = $this->object->get_image_abspath($image, $size);
330 if (@file_exists($abspath))
332 $dims = getimagesize($abspath);
335 $retval['width'] = $dims[0];
336 $retval['height'] = $dims[1];
346 * Gets the HTML for an image
347 * @param int|object $image
348 * @param string $size
351 function get_image_html($image, $size='full', $attributes=array())
355 if (is_numeric($image)) $image = $this->object->_image_mapper->find($image);
359 // Set alt text if not already specified
360 if (!isset($attributes['alttext'])) {
361 $attributes['alt'] = esc_attr($image->alttext);
364 // Set the title if not already set
365 if (!isset($attributes['title'])) {
366 $attributes['title'] = esc_attr($image->alttext);
369 // Set the dimensions if not set already
370 if (!isset($attributes['width']) OR !isset($attributes['height'])) {
371 $dimensions = $this->object->get_image_dimensions($image, $size);
372 if (!isset($attributes['width'])) {
373 $attributes['width'] = $dimensions['width'];
375 if (!isset($attributes['height'])) {
376 $attributes['height'] = $dimensions['height'];
380 // Set the url if not already specified
381 if (!isset($attributes['src'])) {
382 $attributes['src'] = $this->object->get_image_url($image, $size);
387 foreach ($attributes as $attrib => $value) $attribs[] = "{$attrib}=\"{$value}\"";
388 $attribs = implode(" ", $attribs);
390 // Return HTML string
391 $retval = "<img {$attribs} />";
398 * An alias for get_full_abspath()
399 * @param int|object $image
401 function get_original_abspath($image, $check_existance=FALSE)
403 return $this->object->get_image_abspath($image, 'full', $check_existance);
407 * Alias to get_image_dimensions()
408 * @param int|object $image
411 function get_original_dimensions($image)
413 return $this->object->get_image_dimensions($image, 'full');
417 * Alias to get_image_html()
418 * @param int|object $image
421 function get_original_html($image)
423 return $this->object->get_image_html($image, 'full');
427 * Gets the url to the original-sized image
428 * @param int|stdClass|C_Image $image
431 function get_original_url($image, $check_existance=FALSE)
433 return $this->object->get_image_url($image, 'full', $check_existance);
437 * Gets the upload path, optionally for a particular gallery
438 * @param int|C_Gallery|stdClass $gallery
440 function get_upload_relpath($gallery=FALSE)
442 $fs = C_Fs::get_instance();
444 $retval = str_replace(
445 $fs->get_document_root('gallery'),
447 $this->object->get_upload_abspath($gallery)
450 return DIRECTORY_SEPARATOR.ltrim($retval, "/\\");
454 * Moves images from to another gallery
455 * @param array $images
456 * @param int|object $gallery
457 * @param boolean $db optionally only move the image files, not the db entries
460 function move_images($images, $gallery, $db=TRUE)
462 return $this->object->copy_images($images, $gallery, $db, TRUE);
465 function is_image_file()
469 if ((isset($_FILES['file']) && $_FILES['file']['error'] == 0)) {
470 $file_info = $_FILES['file'];
471 $filename = $_FILES['file']['tmp_name'];
473 if (isset($file_info['type'])) {
474 $type = strtolower($file_info['type']);;
475 $valid_types = array(
482 $valid_regex = '/\.(jpg|jpeg|gif|png)$/';
484 // Is this a valid type?
485 if (in_array($type, $valid_types)) {
487 // If we can, we'll verify the mime type
488 if (function_exists('exif_imagetype')) {
489 if (($image_type = @exif_imagetype($filename)) !== FALSE) {
490 $retval = in_array(image_type_to_mime_type($image_type), $valid_types);
495 $file_info = @getimagesize($filename);
496 if (isset($file_info[2])) {
497 $retval = in_array(image_type_to_mime_type($file_info[2]), $valid_types);
500 // We'll assume things are ok as there isn't much else we can do
505 // Is this a valid extension?
506 // TODO: Should we remove this?
507 else if (strpos($type, 'octet-stream') !== FALSE && preg_match($valid_regex, $type)) {
521 if ((isset($_FILES['file']) && $_FILES['file']['error'] == 0)) {
522 $file_info = $_FILES['file'];
524 if (isset($file_info['type'])) {
525 $type = $file_info['type'];
526 $type_parts = explode('/', $type);
528 if (strtolower($type_parts[0]) == 'application') {
529 $spec = $type_parts[1];
530 $spec_parts = explode('-', $spec);
531 $spec_parts = array_map('strtolower', $spec_parts);
533 if (in_array($spec, array('zip', 'octet-stream')) || in_array('zip', $spec_parts)) {
543 function upload_zip($gallery_id)
545 $memory_limit = intval(ini_get('memory_limit'));
546 if (!extension_loaded('suhosin') && $memory_limit < 256) @ini_set('memory_limit', '256M');
550 if ($this->object->is_zip()) {
551 $fs = $this->get_registry()->get_utility('I_Fs');
553 // Uses the WordPress ZIP abstraction API
554 include_once($fs->join_paths(ABSPATH, 'wp-admin', 'includes', 'file.php'));
557 // Ensure that we truly have the gallery id
558 $gallery_id = $this->_get_gallery_id($gallery_id);
560 $zipfile = $_FILES['file']['tmp_name'];
561 $dest_path = implode(DIRECTORY_SEPARATOR, array(
562 rtrim(get_temp_dir(), "/\\"),
563 'unpacked-'.basename($zipfile)
566 wp_mkdir_p($dest_path);
568 if ((unzip_file($zipfile, $dest_path) === TRUE)) {
569 $dest_dir = $dest_path . DIRECTORY_SEPARATOR;
570 $files = glob($dest_dir . '*');
573 foreach ($files as $file) {
574 if (is_file($dest_dir . $file)) {
575 $size += filesize($dest_dir . $file);
580 $this->object->delete_directory($dest_path);
582 $destination = wp_upload_dir();
583 $destination_path = $destination['basedir'];
584 $dest_path = implode(DIRECTORY_SEPARATOR, array(
585 rtrim($destination_path, "/\\"),
586 'unpacked-' . basename($zipfile)
589 wp_mkdir_p($dest_path);
591 if ((unzip_file($zipfile, $dest_path) === TRUE)) {
592 $retval = $this->object->import_gallery_from_fs($dest_path, $gallery_id);
596 $retval = $this->object->import_gallery_from_fs($dest_path, $gallery_id);
600 $this->object->delete_directory($dest_path);
603 if (!extension_loaded('suhosin')) @ini_set('memory_limit', $memory_limit.'M');
608 function is_current_user_over_quota()
611 $settings = C_NextGen_Settings::get_instance();
613 if ((is_multisite()) && $settings->get('wpmuQuotaCheck')) {
614 require_once(ABSPATH . 'wp-admin/includes/ms.php');
615 $retval = upload_is_user_over_quota(FALSE);
623 * Uploads base64 file to a gallery
624 * @param int|stdClass|C_Gallery $gallery
625 * @param $data base64-encoded string of data representing the image
626 * @param type $filename specifies the name of the file
629 function upload_base64_image($gallery, $data, $filename=FALSE, $image_id=FALSE)
631 $settings = C_NextGen_Settings::get_instance();
632 $memory_limit = intval(ini_get('memory_limit'));
633 if (!extension_loaded('suhosin') && $memory_limit < 256) @ini_set('memory_limit', '256M');
636 if (($gallery_id = $this->object->_get_gallery_id($gallery))) {
638 if ($this->object->is_current_user_over_quota()) {
639 $message = sprintf(__('Sorry, you have used your space allocation. Please delete some files to upload more files.', 'nggallery'));
640 throw new E_NoSpaceAvailableException($message);
643 // Get path information. The use of get_upload_abspath() might
644 // not be the best for some drivers. For example, if using the
645 // WordPress Media Library for uploading, then the wp_upload_bits()
646 // function should perhaps be used
647 $upload_dir = $this->object->get_upload_abspath($gallery);
649 // Perhaps a filename was given instead of base64 data?
650 if (preg_match("#/\\\\#", $data[0]) && @file_exists($data)) {
651 if (!$filename) $filename = basename($data);
652 $data = file_get_contents($data);
655 // Determine filenames
656 $original_filename = $filename;
657 $filename = $filename ? sanitize_file_name($original_filename) : uniqid('nextgen-gallery');
658 if (preg_match("/\-(png|jpg|gif|jpeg)$/i", $filename, $match)) {
659 $filename = str_replace($match[0], '.'.$match[1], $filename);
662 $abs_filename = implode(DIRECTORY_SEPARATOR, array($upload_dir, $filename));
664 // Prevent duplicate filenames: check if the filename exists and
665 // begin appending '-i' until we find an open slot
666 if (!ini_get('safe_mode') && @file_exists($abs_filename))
672 $parts = explode('.', $filename);
673 $extension = array_pop($parts);
674 $new_filename = implode('.', $parts) . '-' . $i . '.' . $extension;
675 $new_abs_filename = implode(DIRECTORY_SEPARATOR, array($upload_dir, $new_filename));
676 if (!@file_exists($new_abs_filename))
678 $file_exists = FALSE;
679 $filename = $new_filename;
680 $abs_filename = $new_abs_filename;
682 } while ($file_exists == TRUE);
685 // Create or retrieve the image object
688 $image = $this->object->_image_mapper->find($image_id, TRUE);
689 unset($image->meta_data['saved']);
691 if (!$image) $image = $this->object->_image_mapper->create();
694 // Create or update the database record
695 $image->alttext = basename($original_filename, '.' . pathinfo($original_filename, PATHINFO_EXTENSION));
696 $image->galleryid = $this->object->_get_gallery_id($gallery);
697 $image->filename = $filename;
698 $image->image_slug = nggdb::get_unique_slug( sanitize_title_with_dashes( $image->alttext ), 'image' );
699 $image_key = $this->object->_image_mapper->get_primary_key_column();
701 // If we can't write to the directory, then there's no point in continuing
702 if (!@file_exists($upload_dir)) @wp_mkdir_p($upload_dir);
703 if (!is_writable($upload_dir)) {
704 throw new E_InsufficientWriteAccessException(
705 FALSE, $upload_dir, FALSE
710 if (($image_id = $this->object->_image_mapper->save($image))) {
712 // Try writing the image
713 $fp = fopen($abs_filename, 'w');
717 if ($settings->imgBackup)
718 $this->object->backup_image($image);
720 if ($settings->imgAutoResize)
721 $this->object->generate_image_clone(
724 $this->object->get_image_size_params($image_id, 'full')
727 // Ensure that fullsize dimensions are added to metadata array
728 $dimensions = getimagesize($abs_filename);
730 'width' => $dimensions[0],
731 'height' => $dimensions[1]
733 if (!isset($image->meta_data) OR (is_string($image->meta_data) && strlen($image->meta_data) == 0)) {
734 $image->meta_data = array();
736 $image->meta_data = array_merge($image->meta_data, $full_meta);
737 $image->meta_data['full'] = $full_meta;
739 // Generate a thumbnail for the image
740 $this->object->generate_thumbnail($image);
742 // Set gallery preview image if missing
743 $this->object->get_registry()->get_utility('I_Gallery_Mapper')->set_preview_image($gallery, $image_id, TRUE);
745 // Notify other plugins that an image has been added
746 do_action('ngg_added_new_image', $image);
748 // delete dirsize after adding new images
749 delete_transient( 'dirsize_cache' );
751 // Seems redundant to above hook. Maintaining for legacy purposes
753 'ngg_after_new_images_added',
755 array($image->$image_key)
758 catch(E_No_Image_Library_Exception $ex) {
761 catch(E_Clean_Exit $ex) {
764 catch(Exception $ex) {
765 throw new E_InsufficientWriteAccessException(
766 FALSE, $abs_filename, FALSE, $ex
770 else throw new E_InvalidEntityException();
772 else throw new E_EntityNotFoundException();
774 if (!extension_loaded('suhosin')) @ini_set('memory_limit', $memory_limit.'M');
779 function import_gallery_from_fs($abspath, $gallery_id=FALSE, $move_files=TRUE)
782 if (@file_exists($abspath)) {
784 // Ensure that this folder has images
785 $files_all = scandir($abspath);
788 // first perform some filtering on file list
789 foreach ($files_all as $file)
791 if ($file == '.' || $file == '..')
797 if (!empty($files)) {
799 // Get needed utilities
800 $fs = $this->get_registry()->get_utility('I_Fs');
801 $gallery_mapper = $this->get_registry()->get_utility('I_Gallery_Mapper');
803 // Sometimes users try importing a directory, which actually has all images under another directory
804 $first_file_abspath = $fs->join_paths($abspath, $files[0]);
805 if (is_dir($first_file_abspath) && count($files) == 1) return $this->import_gallery_from_fs($first_file_abspath, $gallery_id, $move_files);
807 // If no gallery has been specified, then use the directory name as the gallery name
809 // Create the gallery
810 $gallery = $gallery_mapper->create(array(
811 'title' => basename($abspath),
815 $gallery->path = str_ireplace(ABSPATH, '', $abspath);
819 if ($gallery->save()) $gallery_id = $gallery->id();
822 // Ensure that we have a gallery id
824 $retval = array('gallery_id' => $gallery_id, 'image_ids' => array());
825 foreach ($files as $file) {
826 if (!preg_match("/\.(jpg|jpeg|gif|png)/i", $file)) continue;
827 $file_abspath = $fs->join_paths($abspath, $file);
831 $image = $this->object->upload_base64_image(
833 file_get_contents($file_abspath),
834 str_replace(' ', '_', $file)
838 // Create the database record ... TODO cleanup, some duplication here from upload_base64_image
839 $factory = $this->object->get_registry()->get_utility('I_Component_Factory');
840 $image = $factory->create('image');
841 $image->alttext = sanitize_title_with_dashes(basename($file_abspath, '.' . pathinfo($file_abspath, PATHINFO_EXTENSION)));
842 $image->galleryid = $this->object->_get_gallery_id($gallery_id);
843 $image->filename = basename($file_abspath);
844 $image->image_slug = nggdb::get_unique_slug( sanitize_title_with_dashes( $image->alttext ), 'image' );
845 $image_key = $this->object->_image_mapper->get_primary_key_column();
846 $abs_filename = $file_abspath;
848 if (($image_id = $this->object->_image_mapper->save($image))) {
850 // backup and image resizing should have already been performed, better to avoid
851 # if ($settings->imgBackup)
852 # $this->object->backup_image($image);
854 # if ($settings->imgAutoResize)
855 # $this->object->generate_image_clone(
858 # $this->object->get_image_size_params($image_id, 'full')
861 // Ensure that fullsize dimensions are added to metadata array
862 $dimensions = getimagesize($abs_filename);
864 'width' => $dimensions[0],
865 'height' => $dimensions[1]
867 if (!isset($image->meta_data) OR (is_string($image->meta_data) && strlen($image->meta_data) == 0)) {
868 $image->meta_data = array();
870 $image->meta_data = array_merge($image->meta_data, $full_meta);
871 $image->meta_data['full'] = $full_meta;
873 // Generate a thumbnail for the image
874 $this->object->generate_thumbnail($image);
876 // Set gallery preview image if missing
877 $this->object->get_registry()->get_utility('I_Gallery_Mapper')->set_preview_image($gallery, $image_id, TRUE);
879 // Notify other plugins that an image has been added
880 do_action('ngg_added_new_image', $image);
882 // delete dirsize after adding new images
883 delete_transient( 'dirsize_cache' );
885 // Seems redundant to above hook. Maintaining for legacy purposes
887 'ngg_after_new_images_added',
889 array($image->$image_key)
892 catch(Exception $ex) {
893 throw new E_InsufficientWriteAccessException(
894 FALSE, $abs_filename, FALSE, $ex
898 else throw new E_InvalidEntityException();
901 $retval['image_ids'][] = $image->{$image->id_field};
904 // Add the gallery name to the result
905 $gallery = $gallery_mapper->find($gallery_id);
906 $retval['gallery_name'] = $gallery->title;
915 function get_image_format_list()
917 $format_list = array(IMAGETYPE_GIF => 'gif', IMAGETYPE_JPEG => 'jpg', IMAGETYPE_PNG => 'png');
923 * Returns an array of properties of a resulting clone image if and when generated
924 * @param string $image_path
925 * @param string $clone_path
926 * @param array $params
929 function calculate_image_clone_result($image_path, $clone_path, $params)
931 $width = isset($params['width']) ? $params['width'] : NULL;
932 $height = isset($params['height']) ? $params['height'] : NULL;
933 $quality = isset($params['quality']) ? $params['quality'] : NULL;
934 $type = isset($params['type']) ? $params['type'] : NULL;
935 $crop = isset($params['crop']) ? $params['crop'] : NULL;
936 $watermark = isset($params['watermark']) ? $params['watermark'] : NULL;
937 $rotation = isset($params['rotation']) ? $params['rotation'] : NULL;
938 $reflection = isset($params['reflection']) ? $params['reflection'] : NULL;
939 $crop_frame = isset($params['crop_frame']) ? $params['crop_frame'] : NULL;
942 // Ensure we have a valid image
943 if ($image_path && @file_exists($image_path))
945 // Ensure target directory exists, but only create 1 subdirectory
946 $image_dir = dirname($image_path);
947 $clone_dir = dirname($clone_path);
948 $image_extension = pathinfo($image_path, PATHINFO_EXTENSION);
949 $image_extension_str = null;
950 $clone_extension = pathinfo($clone_path, PATHINFO_EXTENSION);
951 $clone_extension_str = null;
953 if ($image_extension != null)
955 $image_extension_str = '.' . $image_extension;
958 if ($clone_extension != null)
960 $clone_extension_str = '.' . $clone_extension;
963 $image_basename = basename($image_path, $image_extension_str);
964 $clone_basename = basename($clone_path, $clone_extension_str);
965 // We use a default suffix as passing in null as the suffix will make WordPress use a default
966 $clone_suffix = null;
967 $format_list = $this->object->get_image_format_list();
968 $clone_format = null; // format is determined below and based on $type otherwise left to null
970 // suffix is only used to reconstruct paths for image_resize function
971 if (strpos($clone_basename, $image_basename) === 0)
973 $clone_suffix = substr($clone_basename, strlen($image_basename));
976 if ($clone_suffix != null && $clone_suffix[0] == '-')
978 // WordPress adds '-' on its own
979 $clone_suffix = substr($clone_suffix, 1);
982 // Get original image dimensions
983 $dimensions = getimagesize($image_path);
985 if ($width == null && $height == null) {
986 if ($dimensions != null) {
988 if ($width == null) {
989 $width = $dimensions[0];
992 if ($height == null) {
993 $height = $dimensions[1];
997 // XXX Don't think there's any other option here but to fail miserably...use some hard-coded defaults maybe?
1002 if ($dimensions != null) {
1003 $dimensions_ratio = $dimensions[0] / $dimensions[1];
1005 if ($width == null) {
1006 $width = (int) round($height * $dimensions_ratio);
1008 if ($width == ($dimensions[0] - 1))
1010 $width = $dimensions[0];
1013 else if ($height == null) {
1014 $height = (int) round($width / $dimensions_ratio);
1016 if ($height == ($dimensions[1] - 1))
1018 $height = $dimensions[1];
1022 if ($width > $dimensions[0]) {
1023 $width = $dimensions[0];
1026 if ($height > $dimensions[1]) {
1027 $height = $dimensions[1];
1030 $image_format = $dimensions[2];
1034 if (is_string($type))
1036 $type = strtolower($type);
1038 // Indexes in the $format_list array correspond to IMAGETYPE_XXX values appropriately
1039 if (($index = array_search($type, $format_list)) !== false)
1043 if ($type != $image_format)
1045 // Note: this only changes the FORMAT of the image but not the extension
1046 $clone_format = $type;
1053 if ($width == null || $height == null) {
1054 // Something went wrong...
1058 $result['clone_path'] = $clone_path;
1059 $result['clone_directory'] = $clone_dir;
1060 $result['clone_suffix'] = $clone_suffix;
1061 $result['clone_format'] = $clone_format;
1062 $result['base_width'] = $dimensions[0];
1063 $result['base_height'] = $dimensions[1];
1065 // image_resize() has limitations:
1066 // - no easy crop frame support
1067 // - fails if the dimensions are unchanged
1068 // - doesn't support filename prefix, only suffix so names like thumbs_original_name.jpg for $clone_path are not supported
1069 // also suffix cannot be null as that will make WordPress use a default suffix...we could use an object that returns empty string from __toString() but for now just fallback to ngg generator
1070 if (FALSE) { // disabling the WordPress method for Iteration #6
1071 // if (($crop_frame == null || !$crop) && ($dimensions[0] != $width && $dimensions[1] != $height) && $clone_suffix != null)
1072 $result['method'] = 'wordpress';
1074 $new_dims = image_resize_dimensions($dimensions[0], $dimensions[1], $width, $height, $crop);
1077 list($dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) = $new_dims;
1083 $result['error'] = new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
1088 $result['method'] = 'nextgen';
1089 $original_width = $dimensions[0];
1090 $original_height = $dimensions[1];
1091 $original_ratio = $original_width / $original_height;
1093 $aspect_ratio = $width / $height;
1095 $orig_ratio_x = $original_width / $width;
1096 $orig_ratio_y = $original_height / $height;
1100 $algo = 'shrink'; // either 'adapt' or 'shrink'
1102 if ($crop_frame != null)
1104 $crop_x = (int) round($crop_frame['x']);
1105 $crop_y = (int) round($crop_frame['y']);
1106 $crop_width = (int) round($crop_frame['width']);
1107 $crop_height = (int) round($crop_frame['height']);
1108 $crop_final_width = (int) round($crop_frame['final_width']);
1109 $crop_final_height = (int) round($crop_frame['final_height']);
1111 $crop_width_orig = $crop_width;
1112 $crop_height_orig = $crop_height;
1114 $crop_factor_x = $crop_width / $crop_final_width;
1115 $crop_factor_y = $crop_height / $crop_final_height;
1117 $crop_ratio_x = $crop_width / $width;
1118 $crop_ratio_y = $crop_height / $height;
1120 if ($algo == 'adapt')
1122 // XXX not sure about this...don't use for now
1123 # $crop_width = (int) round($width * $crop_factor_x);
1124 # $crop_height = (int) round($height * $crop_factor_y);
1126 else if ($algo == 'shrink')
1128 if ($crop_ratio_x < $crop_ratio_y)
1130 $crop_width = max($crop_width, $width);
1131 $crop_height = (int) round($crop_width / $aspect_ratio);
1135 $crop_height = max($crop_height, $height);
1136 $crop_width = (int) round($crop_height * $aspect_ratio);
1139 if ($crop_width == ($crop_width_orig - 1))
1141 $crop_width = $crop_width_orig;
1144 if ($crop_height == ($crop_height_orig - 1))
1146 $crop_height = $crop_height_orig;
1150 $crop_diff_x = (int) round(($crop_width_orig - $crop_width) / 2);
1151 $crop_diff_y = (int) round(($crop_height_orig - $crop_height) / 2);
1153 $crop_x += $crop_diff_x;
1154 $crop_y += $crop_diff_y;
1156 $crop_max_x = ($crop_x + $crop_width);
1157 $crop_max_y = ($crop_y + $crop_height);
1159 // Check if we're overflowing borders
1165 else if ($crop_max_x > $original_width)
1167 $crop_x -= ($crop_max_x - $original_width);
1174 else if ($crop_max_y > $original_height)
1176 $crop_y -= ($crop_max_y - $original_height);
1181 if ($orig_ratio_x < $orig_ratio_y)
1183 $crop_width = $original_width;
1184 $crop_height = (int) round($height * $orig_ratio_x);
1189 $crop_height = $original_height;
1190 $crop_width = (int) round($width * $orig_ratio_y);
1193 if ($crop_width == ($width - 1))
1195 $crop_width = $width;
1198 if ($crop_height == ($height - 1))
1200 $crop_height = $height;
1203 $crop_x = (int) round(($original_width - $crop_width) / 2);
1204 $crop_y = (int) round(($original_height - $crop_height) / 2);
1207 $result['crop_area'] = array('x' => $crop_x, 'y' => $crop_y, 'width' => $crop_width, 'height' => $crop_height);
1210 // Just constraint dimensions to ensure there's no stretching or deformations
1211 list($width, $height) = wp_constrain_dimensions($original_width, $original_height, $width, $height);
1215 $result['width'] = $width;
1216 $result['height'] = $height;
1217 $result['quality'] = $quality;
1219 $real_width = $width;
1220 $real_height = $height;
1222 if ($rotation && in_array(abs($rotation), array(90, 270)))
1224 $real_width = $height;
1225 $real_height = $width;
1230 // default for nextgen was 40%, this is used in generate_image_clone as well
1231 $reflection_amount = 40;
1232 // Note, round() would probably be best here but using the same code that C_NggLegacy_Thumbnail uses for compatibility
1233 $reflection_height = intval($real_height * ($reflection_amount / 100));
1234 $real_height = $real_height + $reflection_height;
1237 $result['real_width'] = $real_width;
1238 $result['real_height'] = $real_height;
1245 * Returns an array of dimensional properties (width, height, real_width, real_height) of a resulting clone image if and when generated
1246 * @param string $image_path
1247 * @param string $clone_path
1248 * @param array $params
1251 function calculate_image_clone_dimensions($image_path, $clone_path, $params)
1254 $result = $this->object->calculate_image_clone_result($image_path, $clone_path, $params);
1256 if ($result != null) {
1258 'width' => $result['width'],
1259 'height' => $result['height'],
1260 'real_width' => $result['real_width'],
1261 'real_height' => $result['real_height']
1269 * Generates a "clone" for an existing image, the clone can be altered using the $params array
1270 * @param string $image_path
1271 * @param string $clone_path
1272 * @param array $params
1275 function generate_image_clone($image_path, $clone_path, $params)
1277 $width = isset($params['width']) ? $params['width'] : NULL;
1278 $height = isset($params['height']) ? $params['height'] : NULL;
1279 $quality = isset($params['quality']) ? $params['quality'] : NULL;
1280 $type = isset($params['type']) ? $params['type'] : NULL;
1281 $crop = isset($params['crop']) ? $params['crop'] : NULL;
1282 $watermark = isset($params['watermark']) ? $params['watermark'] : NULL;
1283 $reflection = isset($params['reflection']) ? $params['reflection'] : NULL;
1284 $rotation = isset($params['rotation']) ? $params['rotation'] : NULL;
1285 $flip = isset($params['flip']) ? $params['flip'] : NULL;
1286 $crop_frame = isset($params['crop_frame']) ? $params['crop_frame'] : NULL;
1291 // Do this before anything else can modify the original -- $detailed_size
1292 // may hold IPTC metadata we need to write to our clone
1293 $size = getimagesize($image_path, $detailed_size);
1295 $result = $this->object->calculate_image_clone_result($image_path, $clone_path, $params);
1297 // XXX this should maybe be removed and extra settings go into $params?
1298 $settings = C_NextGen_Settings::get_instance();
1300 // Ensure we have a valid image
1301 if ($image_path && @file_exists($image_path) && $result != null && !isset($result['error']))
1303 $image_dir = dirname($image_path);
1304 $clone_path = $result['clone_path'];
1305 $clone_dir = $result['clone_directory'];
1306 $clone_suffix = $result['clone_suffix'];
1307 $clone_format = $result['clone_format'];
1308 $format_list = $this->object->get_image_format_list();
1310 // Ensure target directory exists, but only create 1 subdirectory
1311 if (!@file_exists($clone_dir))
1313 if (strtolower(realpath($image_dir)) != strtolower(realpath($clone_dir)))
1315 if (strtolower(realpath($image_dir)) == strtolower(realpath(dirname($clone_dir))))
1317 wp_mkdir_p($clone_dir);
1322 $method = $result['method'];
1323 $width = $result['width'];
1324 $height = $result['height'];
1325 $quality = $result['quality'];
1327 if ($quality == null)
1332 if ($method == 'wordpress')
1334 $original = wp_get_image_editor($image_path);
1335 $destpath = $clone_path;
1336 if (!is_wp_error($original))
1338 $original->resize($width, $height, $crop);
1339 $original->set_quality($quality);
1340 $original->save($clone_path);
1343 else if ($method == 'nextgen')
1345 $destpath = $clone_path;
1346 $thumbnail = new C_NggLegacy_Thumbnail($image_path, true);
1347 if (!$thumbnail->error) {
1349 $crop_area = $result['crop_area'];
1350 $crop_x = $crop_area['x'];
1351 $crop_y = $crop_area['y'];
1352 $crop_width = $crop_area['width'];
1353 $crop_height = $crop_area['height'];
1355 $thumbnail->crop($crop_x, $crop_y, $crop_width, $crop_height);
1358 $thumbnail->resize($width, $height);
1360 else $thumbnail = NULL;
1363 // We successfully generated the thumbnail
1364 if (is_string($destpath) && (@file_exists($destpath) || $thumbnail != null))
1366 if ($clone_format != null)
1368 if (isset($format_list[$clone_format]))
1370 $clone_format_extension = $format_list[$clone_format];
1371 $clone_format_extension_str = null;
1373 if ($clone_format_extension != null)
1375 $clone_format_extension_str = '.' . $clone_format_extension;
1378 $destpath_info = pathinfo($destpath);
1379 $destpath_extension = $destpath_info['extension'];
1380 $destpath_extension_str = null;
1382 if ($destpath_extension != null)
1384 $destpath_extension_str = '.' . $destpath_extension;
1387 if (strtolower($destpath_extension) != strtolower($clone_format_extension))
1389 $destpath_dir = $destpath_info['dirname'];
1390 $destpath_basename = $destpath_info['filename'];
1391 $destpath_new = $destpath_dir . DIRECTORY_SEPARATOR . $destpath_basename . $clone_format_extension_str;
1393 if ((@file_exists($destpath) && rename($destpath, $destpath_new)) || $thumbnail != null)
1395 $destpath = $destpath_new;
1401 if (is_null($thumbnail))
1403 $thumbnail = new C_NggLegacy_Thumbnail($destpath, true);
1407 $thumbnail->fileName = $destpath;
1410 // This is quite odd, when watermark equals int(0) it seems all statements below ($watermark == 'image') and ($watermark == 'text') both evaluate as true
1411 // so we set it at null if it evaluates to any null-like value
1412 if ($watermark == null)
1417 if ($watermark == 1 || $watermark === true)
1419 if (in_array(strval($settings->wmType), array('image', 'text')))
1421 $watermark = $settings->wmType;
1425 $watermark = 'text';
1429 $watermark = strval($watermark);
1431 if ($watermark == 'image')
1433 $thumbnail->watermarkImgPath = $settings['wmPath'];
1434 $thumbnail->watermarkImage($settings['wmPos'], $settings['wmXpos'], $settings['wmYpos']);
1436 else if ($watermark == 'text')
1438 $thumbnail->watermarkText = $settings['wmText'];
1439 $thumbnail->watermarkCreateText($settings['wmColor'], $settings['wmFont'], $settings['wmSize'], $settings['wmOpaque']);
1440 $thumbnail->watermarkImage($settings['wmPos'], $settings['wmXpos'], $settings['wmYpos']);
1443 if ($rotation && in_array(abs($rotation), array(90, 180, 270)))
1445 $thumbnail->rotateImageAngle($rotation);
1448 $flip = strtolower($flip);
1450 if ($flip && in_array($flip, array('h', 'v', 'hv')))
1452 $flip_h = in_array($flip, array('h', 'hv'));
1453 $flip_v = in_array($flip, array('v', 'hv'));
1455 $thumbnail->flipImage($flip_h, $flip_v);
1460 $thumbnail->createReflection(40, 40, 50, FALSE, '#a4a4a4');
1463 if ($clone_format != null && isset($format_list[$clone_format]))
1466 $thumbnail->format = strtoupper($format_list[$clone_format]);
1469 $thumbnail->save($destpath, $quality);
1471 // IF the original contained IPTC metadata we should attempt to copy it
1472 if (isset($detailed_size['APP13']) && function_exists('iptcembed'))
1474 $metadata = @iptcembed($detailed_size['APP13'], $destpath);
1475 $fp = @fopen($destpath, 'wb');
1476 @fwrite($fp, $metadata);
1486 class C_GalleryStorage_Driver_Base extends C_GalleryStorage_Base
1488 public static $_instances = array();
1490 function define($context)
1492 parent::define($context);
1493 $this->add_mixin('Mixin_GalleryStorage_Driver_Base');
1494 $this->implement('I_GalleryStorage_Driver');
1497 function initialize()
1499 parent::initialize();
1500 $this->_gallery_mapper = $this->get_registry()->get_utility('I_Gallery_Mapper');
1501 $this->_image_mapper = $this->get_registry()->get_utility('I_Image_Mapper');
1504 public static function get_instance($context = False)
1506 if (!isset(self::$_instances[$context]))
1508 self::$_instances[$context] = new C_GalleryStorage_Driver_Base($context);
1510 return self::$_instances[$context];
1515 * Gets the class name of the driver used
1518 function get_driver_class_name()
1520 return get_called_class();