diff options
author | Bharat Mediratta <bharat@menalto.com> | 2013-02-15 13:00:26 -0500 |
---|---|---|
committer | Bharat Mediratta <bharat@menalto.com> | 2013-02-15 13:00:26 -0500 |
commit | 96a01c86d9bd15f9dc91c60be6f236deab2ffad9 (patch) | |
tree | db06b9a5ab2b03a0475f536b9792435059f999e9 /modules/gallery/helpers | |
parent | 16b8b8d16b8b46fe1e60b2a86f9ee5883dbc892f (diff) | |
parent | 5b6c138da1e53e93e4de8079885fcef29d12e673 (diff) |
Merge branch 'master' into jquery_190
Diffstat (limited to 'modules/gallery/helpers')
-rw-r--r-- | modules/gallery/helpers/gallery_event.php | 4 | ||||
-rw-r--r-- | modules/gallery/helpers/gallery_graphics.php | 5 | ||||
-rw-r--r-- | modules/gallery/helpers/gallery_installer.php | 8 | ||||
-rw-r--r-- | modules/gallery/helpers/graphics.php | 35 | ||||
-rw-r--r-- | modules/gallery/helpers/legal_file.php | 79 | ||||
-rw-r--r-- | modules/gallery/helpers/movie.php | 67 | ||||
-rw-r--r-- | modules/gallery/helpers/photo.php | 15 | ||||
-rw-r--r-- | modules/gallery/helpers/system.php | 36 |
8 files changed, 224 insertions, 25 deletions
diff --git a/modules/gallery/helpers/gallery_event.php b/modules/gallery/helpers/gallery_event.php index aeb1c7eb..54c60296 100644 --- a/modules/gallery/helpers/gallery_event.php +++ b/modules/gallery/helpers/gallery_event.php @@ -399,6 +399,10 @@ class gallery_event_Core { ->label(t("Graphics")) ->url(url::site("admin/graphics"))) ->append(Menu::factory("link") + ->id("movies_settings") + ->label(t("Movies")) + ->url(url::site("admin/movies"))) + ->append(Menu::factory("link") ->id("languages") ->label(t("Languages")) ->url(url::site("admin/languages"))) diff --git a/modules/gallery/helpers/gallery_graphics.php b/modules/gallery/helpers/gallery_graphics.php index b78bd9a7..eb76353f 100644 --- a/modules/gallery/helpers/gallery_graphics.php +++ b/modules/gallery/helpers/gallery_graphics.php @@ -172,6 +172,11 @@ class gallery_graphics_Core { module::event("graphics_composite_completed", $input_file, $output_file, $options, $item); } catch (ErrorException $e) { + // Unlike rotate and resize, composite catches its exceptions here. This is because + // composite is typically called for watermarks. If during thumb/resize generation + // the watermark fails, we'd still like the image resized, just without its watermark. + // If the exception isn't caught here, graphics::generate will replace it with a + // placeholder. Kohana_Log::add("error", $e->getMessage()); } } diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php index 7f10cdee..051a66cf 100644 --- a/modules/gallery/helpers/gallery_installer.php +++ b/modules/gallery/helpers/gallery_installer.php @@ -315,6 +315,7 @@ class gallery_installer { module::set_var("gallery", "timezone", null); module::set_var("gallery", "lock_timeout", 1); module::set_var("gallery", "movie_extract_frame_time", 3); + module::set_var("gallery", "movie_allow_uploads", "autodetect"); } static function upgrade($version) { @@ -789,6 +790,13 @@ class gallery_installer { module::set_version("gallery", $version = 55); } + if ($version == 55) { + // In v56, we added the ability to change the default behavior regarding movie uploads. It + // can be set to "always", "never", or "autodetect" to match the previous behavior where they + // are allowed only if FFmpeg is found. + module::set_var("gallery", "movie_allow_uploads", "autodetect"); + module::set_version("gallery", $version = 56); + } } static function uninstall() { diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php index 7c8e89d5..e66908c4 100644 --- a/modules/gallery/helpers/graphics.php +++ b/modules/gallery/helpers/graphics.php @@ -149,6 +149,19 @@ class graphics_Core { if (@filesize($output_file) == 0) { try { movie::extract_frame($working_file, $output_file, $movie_options_wrapper->movie_options); + // If we're here, we know ffmpeg is installed and the movie is valid. Because the + // user may not always have had ffmpeg installed, the movie's width, height, and + // mime type may need updating. Let's use this opportunity to make sure they're + // correct. It's not optimal to do it at this low level, but it's not trivial to find + // these cases quickly in an upgrade script. + list ($width, $height, $mime_type) = movie::get_file_metadata($working_file); + // Only set them if they need updating to avoid marking them as "changed" + if (($item->width != $width) || ($item->height != $height) || + ($item->mime_type != $mime_type)) { + $item->width = $width; + $item->height = $height; + $item->mime_type = $mime_type; + } } catch (Exception $e) { // Didn't work, likely because of MISSING_FFMPEG - use placeholder graphics::_replace_image_with_placeholder($item, $target); @@ -224,7 +237,16 @@ class graphics_Core { graphics::_replace_image_with_placeholder($item, "resize"); } graphics::_replace_image_with_placeholder($item, "thumb"); - graphics::_update_item_dimensions($item); + try { + graphics::_update_item_dimensions($item); + } catch (Exception $e) { + // Looks like get_file_metadata couldn't identify our placeholders. We should never get + // here, but in the odd case we do, we need to do something. Let's put in hardcoded values. + if ($item->is_photo()) { + list ($item->resize_width, $item->resize_height) = array(200, 200); + } + list ($item->thumb_width, $item->thumb_height) = array(200, 200); + } $item->save(); throw $e; } @@ -314,12 +336,19 @@ class graphics_Core { } /** - * Mark thumbnails and resizes as dirty. They will have to be rebuilt. + * Mark thumbnails and resizes as dirty. They will have to be rebuilt. Optionally, only those of + * a specified type and/or mime type can be marked (e.g. $type="movie" to rebuild movies only). */ - static function mark_dirty($thumbs, $resizes) { + static function mark_dirty($thumbs, $resizes, $type=null, $mime_type=null) { if ($thumbs || $resizes) { $db = db::build() ->update("items"); + if ($type) { + $db->where("type", "=", $type); + } + if ($mime_type) { + $db->where("mime_type", "=", $mime_type); + } if ($thumbs) { $db->set("thumb_dirty", 1); } diff --git a/modules/gallery/helpers/legal_file.php b/modules/gallery/helpers/legal_file.php index ab9047c8..eb9c25de 100644 --- a/modules/gallery/helpers/legal_file.php +++ b/modules/gallery/helpers/legal_file.php @@ -24,6 +24,8 @@ class legal_file_Core { private static $movie_extensions; private static $photo_types; private static $movie_types; + private static $blacklist = array("php", "php3", "php4", "php5", "phtml", "phtm", "shtml", "shtm", + "pl", "cgi", "asp", "sh", "py", "c", "js"); /** * Create a default list of allowed photo MIME types paired with their extensions and then let @@ -38,6 +40,9 @@ class legal_file_Core { $types_by_extension_wrapper->types_by_extension = array( "jpg" => "image/jpeg", "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png"); module::event("photo_types_by_extension", $types_by_extension_wrapper); + foreach (self::$blacklist as $key) { + unset($types_by_extension_wrapper->types_by_extension[$key]); + } self::$photo_types_by_extension = $types_by_extension_wrapper->types_by_extension; } if ($extension) { @@ -67,6 +72,9 @@ class legal_file_Core { $types_by_extension_wrapper->types_by_extension = array( "flv" => "video/x-flv", "mp4" => "video/mp4", "m4v" => "video/x-m4v"); module::event("movie_types_by_extension", $types_by_extension_wrapper); + foreach (self::$blacklist as $key) { + unset($types_by_extension_wrapper->types_by_extension[$key]); + } self::$movie_types_by_extension = $types_by_extension_wrapper->types_by_extension; } if ($extension) { @@ -90,7 +98,7 @@ class legal_file_Core { */ static function get_types_by_extension($extension=null) { $types_by_extension = legal_file::get_photo_types_by_extension(); - if (movie::find_ffmpeg()) { + if (movie::allow_uploads()) { $types_by_extension = array_merge($types_by_extension, legal_file::get_movie_types_by_extension()); } @@ -118,7 +126,7 @@ class legal_file_Core { $extensions_wrapper = new stdClass(); $extensions_wrapper->extensions = array_keys(legal_file::get_photo_types_by_extension()); module::event("legal_photo_extensions", $extensions_wrapper); - self::$photo_extensions = $extensions_wrapper->extensions; + self::$photo_extensions = array_diff($extensions_wrapper->extensions, self::$blacklist); } if ($extension) { // return true if in array, false if not @@ -139,7 +147,7 @@ class legal_file_Core { $extensions_wrapper = new stdClass(); $extensions_wrapper->extensions = array_keys(legal_file::get_movie_types_by_extension()); module::event("legal_movie_extensions", $extensions_wrapper); - self::$movie_extensions = $extensions_wrapper->extensions; + self::$movie_extensions = array_diff($extensions_wrapper->extensions, self::$blacklist); } if ($extension) { // return true if in array, false if not @@ -157,7 +165,7 @@ class legal_file_Core { */ static function get_extensions($extension=null) { $extensions = legal_file::get_photo_extensions(); - if (movie::find_ffmpeg()) { + if (movie::allow_uploads()) { $extensions = array_merge($extensions, legal_file::get_movie_extensions()); } if ($extension) { @@ -227,6 +235,10 @@ class legal_file_Core { * Reduce the given file to having a single extension. */ static function smash_extensions($filename) { + if (!$filename) { + // It's harmless, so return it before it causes issues with pathinfo. + return $filename; + } $parts = pathinfo($filename); $result = ""; if ($parts["dirname"] != ".") { @@ -235,7 +247,64 @@ class legal_file_Core { $parts["filename"] = str_replace(".", "_", $parts["filename"]); $parts["filename"] = preg_replace("/[_]+/", "_", $parts["filename"]); $parts["filename"] = trim($parts["filename"], "_"); - $result .= "{$parts['filename']}.{$parts['extension']}"; + $result .= isset($parts["extension"]) ? "{$parts['filename']}.{$parts['extension']}" : $parts["filename"]; return $result; } + + /** + * Sanitize a filename for a given type (given as "photo" or "movie") and a target file format + * (given as an extension). This returns a completely legal and valid filename, + * or throws an exception if the type or extension given is invalid or illegal. It tries to + * maintain the filename's original extension even if it's not identical to the given extension + * (e.g. don't change "JPG" or "jpeg" to "jpg"). + * + * Note: it is not okay if the extension given is legal but does not match the type (e.g. if + * extension is "mp4" and type is "photo", it will throw an exception) + * + * @param string $filename (with no directory) + * @param string $extension (can be uppercase or lowercase) + * @param string $type (as "photo" or "movie") + * @return string sanitized filename (or null if bad extension argument) + */ + static function sanitize_filename($filename, $extension, $type) { + // Check if the type is valid - if so, get the mime types of the + // original and target extensions; if not, throw an exception. + $original_extension = pathinfo($filename, PATHINFO_EXTENSION); + switch ($type) { + case "photo": + $mime_type = legal_file::get_photo_types_by_extension($extension); + $original_mime_type = legal_file::get_photo_types_by_extension($original_extension); + break; + case "movie": + $mime_type = legal_file::get_movie_types_by_extension($extension); + $original_mime_type = legal_file::get_movie_types_by_extension($original_extension); + break; + default: + throw new Exception("@todo INVALID_TYPE"); + } + + // Check if the target extension is blank or invalid - if so, throw an exception. + if (!$extension || !$mime_type) { + throw new Exception("@todo ILLEGAL_EXTENSION"); + } + + // Check if the mime types of the original and target extensions match - if not, fix it. + if (!$original_extension || ($mime_type != $original_mime_type)) { + $filename = legal_file::change_extension($filename, $extension); + } + + // It should be a filename without a directory - remove all slashes (and backslashes). + $filename = str_replace("/", "_", $filename); + $filename = str_replace("\\", "_", $filename); + + // Remove extra dots from the filename. This will also remove extraneous underscores. + $filename = legal_file::smash_extensions($filename); + + // It's possible that the filename has no base (e.g. ".jpg") - if so, give it a generic one. + if (empty($filename) || (substr($filename, 0, 1) == ".")) { + $filename = $type . $filename; // e.g. "photo.jpg" or "movie.mp4" + } + + return $filename; + } } diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php index 6844771b..aff2acc1 100644 --- a/modules/gallery/helpers/movie.php +++ b/modules/gallery/helpers/movie.php @@ -24,6 +24,8 @@ * Note: by design, this class does not do any permission checking. */ class movie_Core { + private static $allow_uploads; + static function get_edit_form($movie) { $form = new Forge("movies/update/$movie->id", "", "post", array("id" => "g-edit-movie-form")); $form->hidden("from_id")->value($movie->id); @@ -110,6 +112,29 @@ class movie_Core { } /** + * Return true if movie uploads are allowed, false if not. This is based on the + * "movie_allow_uploads" Gallery variable as well as whether or not ffmpeg is found. + */ + static function allow_uploads() { + if (empty(self::$allow_uploads)) { + // Refresh ffmpeg settings + $ffmpeg = movie::find_ffmpeg(); + switch (module::get_var("gallery", "movie_allow_uploads", "autodetect")) { + case "always": + self::$allow_uploads = true; + break; + case "never": + self::$allow_uploads = false; + break; + default: + self::$allow_uploads = !empty($ffmpeg); + break; + } + } + return self::$allow_uploads; + } + + /** * Return the path to the ffmpeg binary if one exists and is executable, or null. */ static function find_ffmpeg() { @@ -122,6 +147,34 @@ class movie_Core { } /** + * Return version number and build date of ffmpeg if found, empty string(s) if not. When using + * static builds that aren't official releases, the version numbers are strange, hence why the + * date can be useful. + */ + static function get_ffmpeg_version() { + $ffmpeg = movie::find_ffmpeg(); + if (empty($ffmpeg)) { + return array("", ""); + } + + // Find version using -h argument since -version wasn't available in early versions. + // To keep the preg_match searches quick, we'll trim the (otherwise long) result. + $cmd = escapeshellcmd($ffmpeg) . " -h 2>&1"; + $result = substr(`$cmd`, 0, 1000); + if (preg_match("/ffmpeg version (\S+)/i", $result, $matches_version)) { + // Version number found - see if we can get the build date or copyright year as well. + if (preg_match("/built on (\S+\s\S+\s\S+)/i", $result, $matches_build_date)) { + return array(trim($matches_version[1], ","), trim($matches_build_date[1], ",")); + } else if (preg_match("/copyright \S*\s?2000-(\d{4})/i", $result, $matches_copyright_date)) { + return array(trim($matches_version[1], ","), $matches_copyright_date[1]); + } else { + return array(trim($matches_version[1], ","), ""); + } + } + return array("", ""); + } + + /** * Return the width, height, mime_type, extension and duration of the given movie file. * Metadata is first generated using ffmpeg (or set to defaults if it fails), * then can be modified by other modules using movie_get_file_metadata events. @@ -138,9 +191,7 @@ class movie_Core { * -> return metadata from ffmpeg * Input is *not* standard movie type that is *not* supported by ffmpeg but is legal * -> return zero width, height, and duration; mime type and extension according to legal_file - * Input is *not* standard movie type that is *not* supported by ffmpeg and is *not* legal - * -> return zero width, height, and duration; null mime type and extension - * Input is not readable or does not exist + * Input is illegal, unidentifiable, unreadable, or does not exist * -> throw exception * Note: movie_get_file_metadata events can change any of the above cases (except the last one). */ @@ -192,8 +243,16 @@ class movie_Core { $metadata->extension = strtolower($extension); } - // Run movie_get_file_metadata events which can modify the class, then return results. + // Run movie_get_file_metadata events which can modify the class. module::event("movie_get_file_metadata", $file_path, $metadata); + + // If the post-events results are invalid, throw an exception. Note that, unlike photos, having + // zero width and height isn't considered invalid (as is the case when FFmpeg isn't installed). + if (!$metadata->mime_type || !$metadata->extension || + ($metadata->mime_type != legal_file::get_movie_types_by_extension($metadata->extension))) { + throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE"); + } + return array($metadata->width, $metadata->height, $metadata->mime_type, $metadata->extension, $metadata->duration); } diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php index 51e51507..004cc7c4 100644 --- a/modules/gallery/helpers/photo.php +++ b/modules/gallery/helpers/photo.php @@ -94,10 +94,8 @@ class photo_Core { * Input is *not* standard photo type that is supported by getimagesize (e.g. tif, bmp...) * -> return metadata from getimagesize() * Input is *not* standard photo type that is *not* supported by getimagesize but is legal - * -> return zero width and height, mime type and extension according to legal_file - * Input is *not* standard photo type that is *not* supported by getimagesize and is *not* legal - * -> return zero width and height, null mime type and extension - * Input is not readable or does not exist + * -> return metadata if found by photo_get_file_metadata events + * Input is illegal, unidentifiable, unreadable, or does not exist * -> throw exception * Note: photo_get_file_metadata events can change any of the above cases (except the last one). */ @@ -133,8 +131,15 @@ class photo_Core { $metadata->height = 0; } - // Run photo_get_file_metadata events which can modify the class, then return results. + // Run photo_get_file_metadata events which can modify the class. module::event("photo_get_file_metadata", $file_path, $metadata); + + // If the post-events results are invalid, throw an exception. + if (!$metadata->width || !$metadata->height || !$metadata->mime_type || !$metadata->extension || + ($metadata->mime_type != legal_file::get_photo_types_by_extension($metadata->extension))) { + throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE"); + } + return array($metadata->width, $metadata->height, $metadata->mime_type, $metadata->extension); } } diff --git a/modules/gallery/helpers/system.php b/modules/gallery/helpers/system.php index fa834824..e1398103 100644 --- a/modules/gallery/helpers/system.php +++ b/modules/gallery/helpers/system.php @@ -20,22 +20,42 @@ class system_Core { /** * Return the path to an executable version of the named binary, or null. - * Traverse the PATH environment variable looking for the given file. If - * the $priority_path variable is set, check that path first. + * The paths are traversed in the following order: + * 1. $priority_path (if specified) + * 2. Gallery's own bin directory (DOCROOT . "bin") + * 3. PATH environment variable + * 4. extra_binary_paths Gallery variable (if specified) + * In addition, if the file is found inside Gallery's bin directory but + * it's not executable, we try to change its permissions to 0755. + * + * @param string $binary + * @param string $priority_path (optional) + * @return string path to binary if found; null if not found */ static function find_binary($binary, $priority_path=null) { - $paths = array_merge( - explode(":", getenv("PATH")), - explode(":", module::get_var("gallery", "extra_binary_paths"))); + $bin_path = DOCROOT . "bin"; + if ($priority_path) { - array_unshift($paths, $priority_path); + $paths = array($priority_path, $bin_path); + } else { + $paths = array($bin_path); } + $paths = array_merge($paths, + explode(":", getenv("PATH")), + explode(":", module::get_var("gallery", "extra_binary_paths"))); foreach ($paths as $path) { $candidate = "$path/$binary"; // @suppress errors below to avoid open_basedir issues - if (@file_exists($candidate) && @is_executable($candidate)) { - return $candidate; + if (@file_exists($candidate)) { + if (!@is_executable($candidate) && + (substr_compare($bin_path, $candidate, 0, strlen($bin_path)) == 0)) { + // Binary isn't executable but is in Gallery's bin directory - try fixing permissions. + @chmod($candidate, 0755); + } + if (@is_executable($candidate)) { + return $candidate; + } } } return null; |