diff options
author | Nathan Kinkade <nath@nkinka.de> | 2013-02-14 14:28:46 +0000 |
---|---|---|
committer | Nathan Kinkade <nath@nkinka.de> | 2013-02-14 14:28:46 +0000 |
commit | 711651f727e093cc7357a6bbff6bd992fd6dfd80 (patch) | |
tree | 2dadc1c06acf1ab3d42d3ed5415568535db54416 /modules/gallery/helpers | |
parent | 0047af90bf4db08b22838e6ded22a7fa70cee98a (diff) | |
parent | e5ed05004f005bdccdbf68e199ae2324ad97e895 (diff) |
Merge branch 'master' of git://github.com/gallery/gallery3
Diffstat (limited to 'modules/gallery/helpers')
48 files changed, 993 insertions, 376 deletions
diff --git a/modules/gallery/helpers/MY_html.php b/modules/gallery/helpers/MY_html.php index edb16ec9..767fe3f7 100644 --- a/modules/gallery/helpers/MY_html.php +++ b/modules/gallery/helpers/MY_html.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/MY_num.php b/modules/gallery/helpers/MY_num.php index 88810bda..a550a1a0 100644 --- a/modules/gallery/helpers/MY_num.php +++ b/modules/gallery/helpers/MY_num.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/MY_remote.php b/modules/gallery/helpers/MY_remote.php index 5bed2437..59804b95 100644 --- a/modules/gallery/helpers/MY_remote.php +++ b/modules/gallery/helpers/MY_remote.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/MY_url.php b/modules/gallery/helpers/MY_url.php index aa42ca49..eba08b2b 100644 --- a/modules/gallery/helpers/MY_url.php +++ b/modules/gallery/helpers/MY_url.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/MY_valid.php b/modules/gallery/helpers/MY_valid.php index ee17267a..f1dd9c34 100644 --- a/modules/gallery/helpers/MY_valid.php +++ b/modules/gallery/helpers/MY_valid.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/access.php b/modules/gallery/helpers/access.php index fbde36c2..a7dca57d 100644 --- a/modules/gallery/helpers/access.php +++ b/modules/gallery/helpers/access.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/ajax.php b/modules/gallery/helpers/ajax.php index 6d59b6e4..0c69fe7f 100644 --- a/modules/gallery/helpers/ajax.php +++ b/modules/gallery/helpers/ajax.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/album.php b/modules/gallery/helpers/album.php index 0945e4d9..23aed8ac 100644 --- a/modules/gallery/helpers/album.php +++ b/modules/gallery/helpers/album.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/auth.php b/modules/gallery/helpers/auth.php index c86eaa5e..2eb3c252 100644 --- a/modules/gallery/helpers/auth.php +++ b/modules/gallery/helpers/auth.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/batch.php b/modules/gallery/helpers/batch.php index 991cad43..bf2425e7 100644 --- a/modules/gallery/helpers/batch.php +++ b/modules/gallery/helpers/batch.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/block_manager.php b/modules/gallery/helpers/block_manager.php index f59e6d58..bd6ca1c8 100644 --- a/modules/gallery/helpers/block_manager.php +++ b/modules/gallery/helpers/block_manager.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/data_rest.php b/modules/gallery/helpers/data_rest.php index 343975f6..d4f456d7 100644 --- a/modules/gallery/helpers/data_rest.php +++ b/modules/gallery/helpers/data_rest.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,51 +32,51 @@ class data_rest_Core { throw new Rest_Exception("Bad Request", 400, array("errors" => array("size" => "invalid"))); } - switch ($p->size) { - case "thumb": - $file = $item->thumb_path(); - break; - - case "resize": - $file = $item->resize_path(); - break; + // Note: this code is roughly duplicated in file_proxy, so if you modify this, please look to + // see if you should make the same change there as well. - case "full": + if ($p->size == "full") { $file = $item->file_path(); - break; + } else if ($p->size == "resize") { + $file = $item->resize_path(); + } else { + $file = $item->thumb_path(); } if (!file_exists($file)) { throw new Kohana_404_Exception(); } - // Note: this code is roughly duplicated in data_rest, so if you modify this, please look to - // see if you should make the same change there as well. - // - // We don't have a cache buster in the url, so don't set cache headers here. - // We don't need to save the session for this request - Session::instance()->abort_save(); + header("Content-Length: " . filesize($file)); - if ($item->is_album() && !$item->album_cover_item_id) { - // No thumbnail. Return nothing. - // @todo: what should we do here? - return; + if (isset($p->m)) { + header("Pragma:"); + // Check that the content hasn't expired or it wasn't changed since cached + expires::check(2592000, $item->updated); + + expires::set(2592000, $item->updated); // 30 days } - // Dump out the image. If the item is a movie, then its thumbnail will be a JPG. - if ($item->is_movie() && $p->size == "thumb") { + // We don't need to save the session for this request + Session::instance()->abort_save(); + + // Dump out the image. If the item is a movie or album, then its thumbnail will be a JPG. + if (($item->is_movie() || $item->is_album()) && $p->size == "thumb") { header("Content-Type: image/jpeg"); - } else if ($item->is_album()) { - header("Content-Type: " . $item->album_cover()->mime_type); } else { - header("Content-Type: {$item->mime_type}"); + header("Content-Type: $item->mime_type"); } - Kohana::close_buffers(false); - if (isset($p->encoding) && $p->encoding == "base64") { - print base64_encode(file_get_contents($file)); + if (TEST_MODE) { + return $file; } else { - readfile($file); + Kohana::close_buffers(false); + + if (isset($p->encoding) && $p->encoding == "base64") { + print base64_encode(file_get_contents($file)); + } else { + readfile($file); + } } // We must exit here to keep the regular REST framework reply code from adding more bytes on @@ -93,6 +93,18 @@ class data_rest_Core { } static function url($item, $size) { - return url::abs_site("rest/data/{$item->id}?size=$size"); + if ($size == "full") { + $file = $item->file_path(); + } else if ($size == "resize") { + $file = $item->resize_path(); + } else { + $file = $item->thumb_path(); + } + if (!file_exists($file)) { + throw new Kohana_404_Exception(); + } + + return url::abs_site("rest/data/{$item->id}?size=$size&m=" . filemtime($file)); } } + diff --git a/modules/gallery/helpers/dir.php b/modules/gallery/helpers/dir.php index 44a142be..807f7bd3 100644 --- a/modules/gallery/helpers/dir.php +++ b/modules/gallery/helpers/dir.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/encoding.php b/modules/gallery/helpers/encoding.php index ce30aa1c..073aef9a 100644 --- a/modules/gallery/helpers/encoding.php +++ b/modules/gallery/helpers/encoding.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/gallery.php b/modules/gallery/helpers/gallery.php index 8b118a80..f1f7190c 100644 --- a/modules/gallery/helpers/gallery.php +++ b/modules/gallery/helpers/gallery.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -214,4 +214,20 @@ class gallery_Core { } return null; } + + /** + * Return true if we should show the profiler at the bottom of the page. Note that this + * function is called at database setup time so it cannot rely on the database. + */ + static function show_profiler() { + return file_exists(VARPATH . "PROFILE"); + } + + /** + * Return true if we should allow Javascript and CSS combining for performance reasons. + * Typically we want this, but it's convenient for developers to be able to disable it. + */ + static function allow_css_and_js_combining() { + return !file_exists(VARPATH . "DONT_COMBINE"); + } }
\ No newline at end of file diff --git a/modules/gallery/helpers/gallery_block.php b/modules/gallery/helpers/gallery_block.php index fe9173c0..5ac4d74d 100644 --- a/modules/gallery/helpers/gallery_block.php +++ b/modules/gallery/helpers/gallery_block.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,7 +78,7 @@ class gallery_block_Core { $block->css_id = "g-project-news"; $block->title = t("Gallery project news"); $block->content = new View("admin_block_news.html"); - $block->content->feed = feed::parse("http://gallery.menalto.com/node/feed", 3); + $block->content->feed = feed::parse("http://galleryproject.org/node/feed", 3); break; case "block_adder": diff --git a/modules/gallery/helpers/gallery_error.php b/modules/gallery/helpers/gallery_error.php index b2515f44..76c8ca99 100644 --- a/modules/gallery/helpers/gallery_error.php +++ b/modules/gallery/helpers/gallery_error.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/gallery_event.php b/modules/gallery/helpers/gallery_event.php index 6225633f..aeb1c7eb 100644 --- a/modules/gallery/helpers/gallery_event.php +++ b/modules/gallery/helpers/gallery_event.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -86,17 +86,17 @@ class gallery_event_Core { static function item_created($item) { access::add_item($item); - if ($item->is_photo() || $item->is_movie()) { - // Build our thumbnail/resizes. - try { - graphics::generate($item); - } catch (Exception $e) { - log::error("graphics", t("Couldn't create a thumbnail or resize for %item_title", - array("item_title" => $item->title)), - html::anchor($item->abs_url(), t("details"))); - Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); - } + // Build our thumbnail/resizes. + try { + graphics::generate($item); + } catch (Exception $e) { + log::error("graphics", t("Couldn't create a thumbnail or resize for %item_title", + array("item_title" => $item->title)), + html::anchor($item->abs_url(), t("details"))); + Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString()); + } + if ($item->is_photo() || $item->is_movie()) { // If the parent has no cover item, make this it. $parent = $item->parent(); if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { @@ -141,10 +141,9 @@ class gallery_event_Core { foreach (ORM::factory("item") ->where("album_cover_item_id", "=", $item->id) ->find_all() as $target) { - copy($item->thumb_path(), $target->thumb_path()); - $target->thumb_width = $item->thumb_width; - $target->thumb_height = $item->thumb_height; + $target->thumb_dirty = 1; $target->save(); + graphics::generate($target); } } @@ -347,9 +346,9 @@ class gallery_event_Core { if (($item->type == "album" && empty($item->album_cover_item_id)) || ($item->type == "album" && $parent->album_cover_item_id == $item->album_cover_item_id) || $parent->album_cover_item_id == $item->id) { - $disabledState = " ui-state-disabled"; + $disabledState = "ui-state-disabled"; } else { - $disabledState = " "; + $disabledState = ""; } if ($item->parent()->id != 1) { @@ -358,7 +357,7 @@ class gallery_event_Core { Menu::factory("ajax_link") ->id("make_album_cover") ->label(t("Choose as the album cover")) - ->css_class("ui-icon-star") + ->css_class("ui-icon-star $disabledState") ->ajax_handler("function(data) { window.location.reload() }") ->url(url::site("quick/make_album_cover/$item->id?csrf=$csrf"))); } @@ -501,16 +500,16 @@ class gallery_event_Core { if (($item->type == "album" && empty($item->album_cover_item_id)) || ($item->type == "album" && $parent->album_cover_item_id == $item->album_cover_item_id) || $parent->album_cover_item_id == $item->id) { - $disabledState = " ui-state-disabled"; + $disabledState = "ui-state-disabled"; } else { - $disabledState = " "; + $disabledState = ""; } if ($item->parent()->id != 1) { $options_menu ->append(Menu::factory("ajax_link") ->id("make_album_cover") ->label($cover_title) - ->css_class("ui-icon-star") + ->css_class("ui-icon-star $disabledState") ->ajax_handler("function(data) { window.location.reload() }") ->url(url::site("quick/make_album_cover/$item->id?csrf=$csrf"))); } @@ -519,7 +518,8 @@ class gallery_event_Core { ->id("delete") ->label($delete_title) ->css_class("ui-icon-trash") - ->url(url::site("quick/form_delete/$item->id?csrf=$csrf&from_id={$theme_item->id}&page_type=$page_type"))); + ->url(url::site("quick/form_delete/$item->id?csrf=$csrf&" . + "from_id={$theme_item->id}&page_type=$page_type"))); } if ($item->is_album()) { diff --git a/modules/gallery/helpers/gallery_graphics.php b/modules/gallery/helpers/gallery_graphics.php index d2b92c87..eb76353f 100644 --- a/modules/gallery/helpers/gallery_graphics.php +++ b/modules/gallery/helpers/gallery_graphics.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,12 +29,28 @@ class gallery_graphics_Core { static function rotate($input_file, $output_file, $options, $item=null) { graphics::init_toolkit(); - module::event("graphics_rotate", $input_file, $output_file, $options, $item); + $temp_file = system::temp_filename("rotate_", pathinfo($output_file, PATHINFO_EXTENSION)); + module::event("graphics_rotate", $input_file, $temp_file, $options, $item); - Image::factory($input_file) - ->quality(module::get_var("gallery", "image_quality")) - ->rotate($options["degrees"]) - ->save($output_file); + if (@filesize($temp_file) > 0) { + // A graphics_rotate event made an image - move it to output_file and use it. + @rename($temp_file, $output_file); + } else { + // No events made an image - proceed with standard process. + if (@filesize($input_file) == 0) { + throw new Exception("@todo EMPTY_INPUT_FILE"); + } + + if (!isset($options["degrees"])) { + $options["degrees"] = 0; + } + + // Rotate the image. This also implicitly converts its format if needed. + Image::factory($input_file) + ->quality(module::get_var("gallery", "image_quality")) + ->rotate($options["degrees"]) + ->save($output_file); + } module::event("graphics_rotate_completed", $input_file, $output_file, $options, $item); } @@ -51,24 +67,46 @@ class gallery_graphics_Core { static function resize($input_file, $output_file, $options, $item=null) { graphics::init_toolkit(); - module::event("graphics_resize", $input_file, $output_file, $options, $item); - - if (@filesize($input_file) == 0) { - throw new Exception("@todo EMPTY_INPUT_FILE"); - } + $temp_file = system::temp_filename("resize_", pathinfo($output_file, PATHINFO_EXTENSION)); + module::event("graphics_resize", $input_file, $temp_file, $options, $item); - $dims = getimagesize($input_file); - if (max($dims[0], $dims[1]) <= min($options["width"], $options["height"])) { - // Image would get upscaled; do nothing - copy($input_file, $output_file); + if (@filesize($temp_file) > 0) { + // A graphics_resize event made an image - move it to output_file and use it. + @rename($temp_file, $output_file); } else { - $image = Image::factory($input_file) - ->resize($options["width"], $options["height"], $options["master"]) - ->quality(module::get_var("gallery", "image_quality")); - if (graphics::can("sharpen")) { - $image->sharpen(module::get_var("gallery", "image_sharpen")); + // No events made an image - proceed with standard process. + if (@filesize($input_file) == 0) { + throw new Exception("@todo EMPTY_INPUT_FILE"); + } + + list ($input_width, $input_height, $input_mime, $input_extension) = + photo::get_file_metadata($input_file); + if ($input_width && $input_height && + (empty($options["width"]) || empty($options["height"]) || empty($options["master"]) || + (max($input_width, $input_height) <= min($options["width"], $options["height"])))) { + // Photo dimensions well-defined, but options not well-defined or would upscale the image. + // Do not resize. Check mimes to see if we can copy the file or if we need to convert it. + // (checking mimes avoids needlessly converting jpg to jpeg, etc.) + $output_mime = legal_file::get_photo_types_by_extension(pathinfo($output_file, PATHINFO_EXTENSION)); + if ($input_mime && $output_mime && ($input_mime == $output_mime)) { + // Mimes well-defined and identical - copy input to output + copy($input_file, $output_file); + } else { + // Mimes not well-defined or not the same - convert input to output + $image = Image::factory($input_file) + ->quality(module::get_var("gallery", "image_quality")) + ->save($output_file); + } + } else { + // Resize the image. This also implicitly converts its format if needed. + $image = Image::factory($input_file) + ->resize($options["width"], $options["height"], $options["master"]) + ->quality(module::get_var("gallery", "image_quality")); + if (graphics::can("sharpen")) { + $image->sharpen(module::get_var("gallery", "image_sharpen")); + } + $image->save($output_file); } - $image->save($output_file); } module::event("graphics_resize_completed", $input_file, $output_file, $options, $item); @@ -94,38 +132,51 @@ class gallery_graphics_Core { try { graphics::init_toolkit(); - module::event("graphics_composite", $input_file, $output_file, $options, $item); - - list ($width, $height) = getimagesize($input_file); - list ($w_width, $w_height) = getimagesize($options["file"]); - - $pad = isset($options["padding"]) ? $options["padding"] : 10; - $top = $pad; - $left = $pad; - $y_center = max($height / 2 - $w_height / 2, $pad); - $x_center = max($width / 2 - $w_width / 2, $pad); - $bottom = max($height - $w_height - $pad, $pad); - $right = max($width - $w_width - $pad, $pad); - - switch ($options["position"]) { - case "northwest": $x = $left; $y = $top; break; - case "north": $x = $x_center; $y = $top; break; - case "northeast": $x = $right; $y = $top; break; - case "west": $x = $left; $y = $y_center; break; - case "center": $x = $x_center; $y = $y_center; break; - case "east": $x = $right; $y = $y_center; break; - case "southwest": $x = $left; $y = $bottom; break; - case "south": $x = $x_center; $y = $bottom; break; - case "southeast": $x = $right; $y = $bottom; break; - } + $temp_file = system::temp_filename("composite_", pathinfo($output_file, PATHINFO_EXTENSION)); + module::event("graphics_composite", $input_file, $temp_file, $options, $item); - Image::factory($input_file) - ->composite($options["file"], $x, $y, $options["transparency"]) - ->quality(module::get_var("gallery", "image_quality")) - ->save($output_file); + if (@filesize($temp_file) > 0) { + // A graphics_composite event made an image - move it to output_file and use it. + @rename($temp_file, $output_file); + } else { + // No events made an image - proceed with standard process. + + list ($width, $height) = photo::get_file_metadata($input_file); + list ($w_width, $w_height) = photo::get_file_metadata($options["file"]); + + $pad = isset($options["padding"]) ? $options["padding"] : 10; + $top = $pad; + $left = $pad; + $y_center = max($height / 2 - $w_height / 2, $pad); + $x_center = max($width / 2 - $w_width / 2, $pad); + $bottom = max($height - $w_height - $pad, $pad); + $right = max($width - $w_width - $pad, $pad); + + switch ($options["position"]) { + case "northwest": $x = $left; $y = $top; break; + case "north": $x = $x_center; $y = $top; break; + case "northeast": $x = $right; $y = $top; break; + case "west": $x = $left; $y = $y_center; break; + case "center": $x = $x_center; $y = $y_center; break; + case "east": $x = $right; $y = $y_center; break; + case "southwest": $x = $left; $y = $bottom; break; + case "south": $x = $x_center; $y = $bottom; break; + case "southeast": $x = $right; $y = $bottom; break; + } + + Image::factory($input_file) + ->composite($options["file"], $x, $y, $options["transparency"]) + ->quality(module::get_var("gallery", "image_quality")) + ->save($output_file); + } 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 597771f3..051a66cf 100644 --- a/modules/gallery/helpers/gallery_installer.php +++ b/modules/gallery/helpers/gallery_installer.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -116,7 +116,8 @@ class gallery_installer { KEY `type` (`type`), KEY `random` (`rand_key`), KEY `weight` (`weight` DESC), - KEY `left_ptr` (`left_ptr`)) + KEY `left_ptr` (`left_ptr`), + KEY `relative_path_cache` (`relative_path_cache`)) DEFAULT CHARSET=utf8;"); $db->query("CREATE TABLE {logs} ( @@ -314,8 +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_version("gallery", 53); + module::set_var("gallery", "movie_allow_uploads", "autodetect"); } static function upgrade($version) { @@ -718,14 +718,14 @@ class gallery_installer { if ($version == 50) { // In v51, we added a lock_timeout variable so that administrators could edit the time out - // from 1 second to a higher variable if their system runs concurrent parallel uploads for + // from 1 second to a higher variable if their system runs concurrent parallel uploads for // instance. module::set_var("gallery", "lock_timeout", 1); module::set_version("gallery", $version = 51); } if ($version == 51) { - // In v52, we added functions to the legal_file helper that map photo and movie file + // In v52, we added functions to the legal_file helper that map photo and movie file // extensions to their mime types (and allow extension of the list by other modules). During // this process, we correctly mapped m4v files to video/x-m4v, correcting a previous error // where they were mapped to video/mp4. This corrects the existing items. @@ -736,13 +736,67 @@ class gallery_installer { ->execute(); module::set_version("gallery", $version = 52); } - + if ($version == 52) { // In v53, we added the ability to change the default time used when extracting frames from // movies. Previously we hard-coded this at 3 seconds, so we use that as the default. module::set_var("gallery", "movie_extract_frame_time", 3); module::set_version("gallery", $version = 53); } + + if ($version == 53) { + // In v54, we changed how we check for name and slug conflicts in Item_Model. Previously, + // we checked the whole filename. As a result, "foo.jpg" and "foo.png" were not considered + // conflicting if their slugs were different (a rare case in practice since server_add and + // uploader would give them both the same slug "foo"). Now, we check the filename without its + // extension. This upgrade stanza fixes any conflicts where they were previously allowed. + + // This might be slow, but if it times out it can just pick up where it left off. + + // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one + // conflict; "bar.jpg", "bar.png", and "bar.flv" are another) + foreach (db::build() + ->select_distinct(array("parent_base_name" => + db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))"))) + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->where("type", "<>", "album") + ->having("C", ">", 1) + ->group_by("parent_base_name") + ->execute() as $conflict) { + list ($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2); + $base_name_escaped = Database::escape_for_like($base_name); + // Loop through the items for each conflict + foreach (db::build() + ->from("items") + ->select("id") + ->where("type", "<>", "album") + ->where("parent_id", "=", $parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->limit(1000000) // required to satisfy SQL syntax (no offset without limit) + ->offset(1) // skips the 0th item + ->execute() as $row) { + set_time_limit(30); + $item = ORM::factory("item", $row->id); + $item->name = $item->name; // this will force Item_Model to check for conflicts on save + $item->save(); + } + } + module::set_version("gallery", $version = 54); + } + + if ($version == 54) { + $db->query("ALTER TABLE {items} ADD KEY `relative_path_cache` (`relative_path_cache`)"); + 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/gallery_rss.php b/modules/gallery/helpers/gallery_rss.php index c952f4b6..d6b33022 100644 --- a/modules/gallery/helpers/gallery_rss.php +++ b/modules/gallery/helpers/gallery_rss.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/gallery_task.php b/modules/gallery/helpers/gallery_task.php index 65a72884..856d2639 100644 --- a/modules/gallery/helpers/gallery_task.php +++ b/modules/gallery/helpers/gallery_task.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,11 +26,13 @@ class gallery_task_Core { const FIX_STATE_RUN_DUPE_SLUGS = 5; const FIX_STATE_START_DUPE_NAMES = 6; const FIX_STATE_RUN_DUPE_NAMES = 7; - const FIX_STATE_START_REBUILD_ITEM_CACHES = 8; - const FIX_STATE_RUN_REBUILD_ITEM_CACHES = 9; - const FIX_STATE_START_MISSING_ACCESS_CACHES = 10; - const FIX_STATE_RUN_MISSING_ACCESS_CACHES = 11; - const FIX_STATE_DONE = 12; + const FIX_STATE_START_DUPE_BASE_NAMES = 8; + const FIX_STATE_RUN_DUPE_BASE_NAMES = 9; + const FIX_STATE_START_REBUILD_ITEM_CACHES = 10; + const FIX_STATE_RUN_REBUILD_ITEM_CACHES = 11; + const FIX_STATE_START_MISSING_ACCESS_CACHES = 12; + const FIX_STATE_RUN_MISSING_ACCESS_CACHES = 13; + const FIX_STATE_DONE = 14; static function available_tasks() { $dirty_count = graphics::find_dirty_images_query()->count_records(); @@ -348,8 +350,9 @@ class gallery_task_Core { // album audit (permissions and bogus album covers): 1 operation for every album $total += db::build()->where("type", "=", "album")->count_records("items"); - // one operation for each missing slug, name and access cache - foreach (array("find_dupe_slugs", "find_dupe_names", "find_missing_access_caches") as $func) { + // one operation for each dupe slug, dupe name, dupe base name, and missing access cache + foreach (array("find_dupe_slugs", "find_dupe_names", "find_dupe_base_names", + "find_missing_access_caches") as $func) { foreach (self::$func() as $row) { $total++; } @@ -489,11 +492,12 @@ class gallery_task_Core { $task->set("stack", implode(" ", $stack)); $state = self::FIX_STATE_RUN_DUPE_NAMES; } else { - $state = self::FIX_STATE_START_ALBUMS; + $state = self::FIX_STATE_START_DUPE_BASE_NAMES; } break; case self::FIX_STATE_RUN_DUPE_NAMES: + // NOTE: This does *not* attempt to fix the file system! $stack = explode(" ", $task->get("stack")); list ($parent_id, $name) = explode(":", array_pop($stack)); @@ -505,9 +509,16 @@ class gallery_task_Core { ->find_all(1, 1); if ($conflicts->count() && $conflict = $conflicts->current()) { $task->log("Fixing conflicting name for item id {$conflict->id}"); + if (!$conflict->is_album() && preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) { + $item_base_name = $matches[1]; + $item_extension = $matches[2]; // includes a leading dot + } else { + $item_base_name = $conflict->name; + $item_extension = ""; + } db::build() ->update("items") - ->set("name", $name . "-" . (string)rand(1000, 9999)) + ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension) ->where("id", "=", $conflict->id) ->execute(); @@ -522,6 +533,74 @@ class gallery_task_Core { $completed++; if (empty($stack)) { + $state = self::FIX_STATE_START_DUPE_BASE_NAMES; + } + break; + + case self::FIX_STATE_START_DUPE_BASE_NAMES: + $stack = array(); + foreach (self::find_dupe_base_names() as $row) { + list ($parent_id, $base_name) = explode(":", $row->parent_base_name, 2); + $stack[] = join(":", array($parent_id, $base_name)); + } + if ($stack) { + $task->set("stack", implode(" ", $stack)); + $state = self::FIX_STATE_RUN_DUPE_BASE_NAMES; + } else { + $state = self::FIX_STATE_START_ALBUMS; + } + break; + + case self::FIX_STATE_RUN_DUPE_BASE_NAMES: + // NOTE: This *does* attempt to fix the file system! So, it must go *after* run_dupe_names. + $stack = explode(" ", $task->get("stack")); + list ($parent_id, $base_name) = explode(":", array_pop($stack)); + $base_name_escaped = Database::escape_for_like($base_name); + + $fixed = 0; + // We want to leave the first one alone and update all conflicts to be random values. + $conflicts = ORM::factory("item") + ->where("parent_id", "=", $parent_id) + ->where("name", "LIKE", "{$base_name_escaped}.%") + ->where("type", "<>", "album") + ->find_all(1, 1); + if ($conflicts->count() && $conflict = $conflicts->current()) { + $task->log("Fixing conflicting name for item id {$conflict->id}"); + if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) { + $item_base_name = $matches[1]; // unlike $base_name, this always maintains capitalization + $item_extension = $matches[2]; // includes a leading dot + } else { + $item_base_name = $conflict->name; + $item_extension = ""; + } + // Unlike conflicts found in run_dupe_names, these items are likely to have an intact + // file system. Let's use the item save logic to rebuild the paths and rename the files + // if possible. + try { + $conflict->name = $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension; + $conflict->validate(); + // If we get here, we're safe to proceed with save + $conflict->save(); + } catch (Exception $e) { + // Didn't work. Edit database directly without fixing file system. + db::build() + ->update("items") + ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension) + ->where("id", "=", $conflict->id) + ->execute(); + } + + // We fixed one conflict, but there might be more so put this parent back on the stack + // and try again. We won't consider it completed when we don't fix a conflict. This + // guarantees that we won't spend too long fixing one set of conflicts, and that we + // won't stop before all are fixed. + $stack[] = "$parent_id:$base_name"; + break; + } + $task->set("stack", implode(" ", $stack)); + $completed++; + + if (empty($stack)) { $state = self::FIX_STATE_START_ALBUMS; } break; @@ -612,7 +691,7 @@ class gallery_task_Core { break; case self::FIX_STATE_RUN_MISSING_ACCESS_CACHES: - $stack = explode(" ", $task->get("stack")); + $stack = array_filter(explode(" ", $task->get("stack"))); // filter removes empty/zero ids if (!empty($stack)) { $id = array_pop($stack); $access_cache = ORM::factory("access_cache"); @@ -669,18 +748,32 @@ class gallery_task_Core { } static function find_dupe_names() { + // looking for photos, movies, and albums return db::build() ->select_distinct( array("parent_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(`name`))"))) ->select("id") ->select(array("C" => "COUNT(\"*\")")) ->from("items") - ->where("type", "<>", "album") ->having("C", ">", 1) ->group_by("parent_name") ->execute(); } + static function find_dupe_base_names() { + // looking for photos or movies, not albums + return db::build() + ->select_distinct( + array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))"))) + ->select("id") + ->select(array("C" => "COUNT(\"*\")")) + ->from("items") + ->where("type", "<>", "album") + ->having("C", ">", 1) + ->group_by("parent_base_name") + ->execute(); + } + static function find_empty_item_caches($limit) { return db::build() ->select("items.id") diff --git a/modules/gallery/helpers/gallery_theme.php b/modules/gallery/helpers/gallery_theme.php index 2263d8d7..3c6d71e9 100644 --- a/modules/gallery/helpers/gallery_theme.php +++ b/modules/gallery/helpers/gallery_theme.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,7 +71,7 @@ class gallery_theme_Core { static function page_bottom($theme) { $session = Session::instance(); - if ($session->get("profiler", false)) { + if (gallery::show_profiler()) { Profiler::enable(); $profiler = new Profiler(); $profiler->render(); @@ -96,7 +96,7 @@ class gallery_theme_Core { static function admin_page_bottom($theme) { $session = Session::instance(); - if ($session->get("profiler", false)) { + if (gallery::show_profiler()) { Profiler::enable(); $profiler = new Profiler(); $profiler->render(); @@ -134,7 +134,7 @@ class gallery_theme_Core { '<bdo dir="ltr">Gallery ' . gallery::version_string() . '</bdo>'); return "<li class=\"g-first\">" . t(module::get_var("gallery", "credits"), - array("url" => "http://gallery.menalto.com", + array("url" => "http://galleryproject.org", "gallery_version" => $version_string)) . "</li>"; } diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php index e7c5da68..e66908c4 100644 --- a/modules/gallery/helpers/graphics.php +++ b/modules/gallery/helpers/graphics.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -115,36 +115,12 @@ class graphics_Core { * @param Item_Model $item */ static function generate($item) { - if ($item->is_album()) { - if (!$cover = $item->album_cover()) { - // This album has no cover; there's nothing to generate. Because of an old bug, it's - // possible that there's an album cover item id that points to an invalid item. In that - // case, just null out the album cover item id. It's not optimal to do that at this low - // level, but it's not trivial to find these cases quickly in an upgrade script and if we - // don't do this, the album may be permanently marked as "needs rebuilding" - // - // ref: http://sourceforge.net/apps/trac/gallery/ticket/1172 - // http://gallery.menalto.com/node/96926 - if ($item->album_cover_item_id) { - $item->album_cover_item_id = null; - $item->save(); - } - return; - } - $input_file = $cover->file_path(); - $input_item = $cover; - } else { - $input_file = $item->file_path(); - $input_item = $item; - } - if ($item->thumb_dirty) { $ops["thumb"] = $item->thumb_path(); } - if ($item->resize_dirty && !$item->is_album() && !$item->is_movie()) { + if ($item->resize_dirty && $item->is_photo()) { $ops["resize"] = $item->resize_path(); } - if (empty($ops)) { $item->thumb_dirty = 0; $item->resize_dirty = 0; @@ -154,10 +130,11 @@ class graphics_Core { try { foreach ($ops as $target => $output_file) { - if ($input_item->is_movie()) { - // Convert the movie filename to a JPG first, delete anything that might already be there - $output_file = legal_file::change_extension($output_file, "jpg"); - unlink($output_file); + $working_file = $item->file_path(); + // Delete anything that might already be there + @unlink($output_file); + switch ($item->type) { + case "movie": // Run movie_extract_frame events, which can either: // - generate an output file, bypassing the ffmpeg-based movie::extract_frame // - add to the options sent to movie::extract_frame (e.g. change frame extract time, @@ -165,27 +142,70 @@ class graphics_Core { // Note that the args are similar to those of the events in gallery_graphics $movie_options_wrapper = new stdClass(); $movie_options_wrapper->movie_options = array(); - module::event("movie_extract_frame", $input_file, $output_file, - $movie_options_wrapper, $input_item); + module::event("movie_extract_frame", $working_file, $output_file, + $movie_options_wrapper, $item); // If no output_file generated by events, run movie::extract_frame with movie_options clearstatcache(); if (@filesize($output_file) == 0) { try { - movie::extract_frame($input_file, $output_file, $movie_options_wrapper->movie_options); + 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 - copy missing_movie instead - copy(MODPATH . "gallery/images/missing_movie.jpg", $output_file); + // Didn't work, likely because of MISSING_FFMPEG - use placeholder + graphics::_replace_image_with_placeholder($item, $target); + break; } } $working_file = $output_file; - } else { - $working_file = $input_file; - } - foreach (self::_get_rules($target) as $rule) { - $args = array($working_file, $output_file, unserialize($rule->args), $item); - call_user_func_array($rule->operation, $args); - $working_file = $output_file; + case "photo": + // Run the graphics rules (for both movies and photos) + foreach (self::_get_rules($target) as $rule) { + $args = array($working_file, $output_file, unserialize($rule->args), $item); + call_user_func_array($rule->operation, $args); + $working_file = $output_file; + } + break; + + case "album": + if (!$cover = $item->album_cover()) { + // This album has no cover; copy its placeholder image. Because of an old bug, it's + // possible that there's an album cover item id that points to an invalid item. In that + // case, just null out the album cover item id. It's not optimal to do that at this low + // level, but it's not trivial to find these cases quickly in an upgrade script and if we + // don't do this, the album may be permanently marked as "needs rebuilding" + // + // ref: http://sourceforge.net/apps/trac/gallery/ticket/1172 + // http://galleryproject.org/node/96926 + if ($item->album_cover_item_id) { + $item->album_cover_item_id = null; + $item->save(); + } + graphics::_replace_image_with_placeholder($item, $target); + break; + } + if ($cover->thumb_dirty) { + graphics::generate($cover); + } + if (!$cover->thumb_dirty) { + // Make the album cover from the cover item's thumb. Run gallery_graphics::resize with + // null options and it will figure out if this is a direct copy or conversion to jpg. + $working_file = $cover->thumb_path(); + gallery_graphics::resize($working_file, $output_file, null, $item); + } + break; } } @@ -193,33 +213,93 @@ class graphics_Core { if (file_exists($item->thumb_path())) { $item->thumb_dirty = 0; } else { - copy(MODPATH . "gallery/images/missing_photo.png", $item->thumb_path()); + Kohana_Log::add("error", "Failed to rebuild thumb image: $item->title"); + graphics::_replace_image_with_placeholder($item, "thumb"); } - $dims = getimagesize($item->thumb_path()); - $item->thumb_width = $dims[0]; - $item->thumb_height = $dims[1]; } if (!empty($ops["resize"])) { if (file_exists($item->resize_path())) { $item->resize_dirty = 0; } else { - copy(MODPATH . "gallery/images/missing_photo.png", $item->resize_path()); + Kohana_Log::add("error", "Failed to rebuild resize image: $item->title"); + graphics::_replace_image_with_placeholder($item, "resize"); } - $dims = getimagesize($item->resize_path()); - $item->resize_width = $dims[0]; - $item->resize_height = $dims[1]; } + graphics::_update_item_dimensions($item); $item->save(); } catch (Exception $e) { - // Something went wrong rebuilding the image. Leave it dirty and move on. - // @todo we should handle this better. - Kohana_Log::add("error", "Caught exception rebuilding image: {$item->title}\n" . + // Something went wrong rebuilding the image. Replace with the placeholder images, + // leave it dirty and move on. + Kohana_Log::add("error", "Caught exception rebuilding images: {$item->title}\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); + if ($item->is_photo()) { + graphics::_replace_image_with_placeholder($item, "resize"); + } + graphics::_replace_image_with_placeholder($item, "thumb"); + 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; } } + private static function _update_item_dimensions($item) { + if ($item->is_photo()) { + list ($item->resize_width, $item->resize_height) = + photo::get_file_metadata($item->resize_path()); + } + list ($item->thumb_width, $item->thumb_height) = + photo::get_file_metadata($item->thumb_path()); + } + + private static function _replace_image_with_placeholder($item, $target) { + if ($item->is_album() && !$item->album_cover_item_id) { + $input_path = MODPATH . "gallery/images/missing_album_cover.jpg"; + } else if ($item->is_movie() || ($item->is_album() && $item->album_cover()->is_movie())) { + $input_path = MODPATH . "gallery/images/missing_movie.jpg"; + } else { + $input_path = MODPATH . "gallery/images/missing_photo.jpg"; + } + + if ($target == "thumb") { + $output_path = $item->thumb_path(); + $size = module::get_var("gallery", "thumb_size", 200); + } else { + $output_path = $item->resize_path(); + $size = module::get_var("gallery", "resize_size", 640); + } + $options = array("width" => $size, "height" => $size, "master" => Image::AUTO); + + try { + // Copy/convert/resize placeholder as needed. + gallery_graphics::resize($input_path, $output_path, $options, null); + } catch (Exception $e) { + // Copy/convert/resize didn't work. Add to the log and copy the jpg version (which could have + // a non-jpg extension). This is less than ideal, but it's better than putting nothing + // there and causing theme views to act strangely because a file is missing. + // @todo we should handle this better. + Kohana_Log::add("error", "Caught exception converting placeholder for missing image: " . + $item->title . "\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); + copy($input_path, $output_path); + } + + if (!file_exists($output_path)) { + // Copy/convert/resize didn't throw an exception, but still didn't work - do the same as above. + // @todo we should handle this better. + Kohana_Log::add("error", "Failed to convert placeholder for missing image: $item->title"); + copy($input_path, $output_path); + } + } + private static function _get_rules($target) { if (empty(self::$_rules_cache[$target])) { $rules = array(); @@ -256,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/identity.php b/modules/gallery/helpers/identity.php index fcc9c6e8..5fc0f2f6 100644 --- a/modules/gallery/helpers/identity.php +++ b/modules/gallery/helpers/identity.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php index b739e8bd..9882a9c5 100644 --- a/modules/gallery/helpers/item.php +++ b/modules/gallery/helpers/item.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,55 +39,28 @@ class item_Core { } } - $source->parent_id = $target->id; - - // Moving may result in name or slug conflicts. If that happens, try up to 5 times to pick a - // random name (or slug) to avoid the conflict. $orig_name = $source->name; - $orig_name_filename = pathinfo($source->name, PATHINFO_FILENAME); - $orig_name_extension = pathinfo($source->name, PATHINFO_EXTENSION); - $orig_slug = $source->slug; - for ($i = 0; $i < 5; $i++) { - try { - $source->save(); - if ($orig_name != $source->name) { - switch ($source->type) { - case "album": - message::info( - t("Album <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict", - array("old_name" => $orig_name, "new_name" => $source->name))); - break; - - case "photo": - message::info( - t("Photo <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict", - array("old_name" => $orig_name, "new_name" => $source->name))); - break; + $source->parent_id = $target->id; + $source->save(); + if ($orig_name != $source->name) { + switch ($source->type) { + case "album": + message::info( + t("Album <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; - case "movie": - message::info( - t("Movie <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict", - array("old_name" => $orig_name, "new_name" => $source->name))); - break; - } - } + case "photo": + message::info( + t("Photo <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); break; - } catch (ORM_Validation_Exception $e) { - $rand = rand(10, 99); - $errors = $e->validation->errors(); - if (isset($errors["name"])) { - $source->name = $orig_name_filename . "-{$rand}." . $orig_name_extension; - unset($errors["name"]); - } - if (isset($errors["slug"])) { - $source->slug = $orig_slug . "-{$rand}"; - unset($errors["slug"]); - } - if ($errors) { - // There were other validation issues-- we don't know how to handle those - throw $e; - } + case "movie": + message::info( + t("Movie <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict", + array("old_name" => $orig_name, "new_name" => $source->name))); + break; } } @@ -103,34 +76,42 @@ class item_Core { access::required("view", $parent); access::required("edit", $parent); + $old_album_cover_id = $parent->album_cover_item_id; + model_cache::clear(); $parent->album_cover_item_id = $item->is_album() ? $item->album_cover_item_id : $item->id; - if ($item->thumb_dirty) { - $parent->thumb_dirty = 1; - graphics::generate($parent); - } else { - copy($item->thumb_path(), $parent->thumb_path()); - $parent->thumb_width = $item->thumb_width; - $parent->thumb_height = $item->thumb_height; - } $parent->save(); + graphics::generate($parent); + + // Walk up the parent hierarchy and set album covers if necessary $grand_parent = $parent->parent(); if ($grand_parent && access::can("edit", $grand_parent) && $grand_parent->album_cover_item_id == null) { item::make_album_cover($parent); } + + // When albums are album covers themselves, we hotlink directly to the target item. This + // means that when we change an album cover, the grandparent may have a deep link to the old + // album cover. So find any parent albums that had the old item as their album cover and + // switch them over to the new item. + if ($old_album_cover_id) { + foreach ($item->parents(array(array("album_cover_item_id", "=", $old_album_cover_id))) + as $ancestor) { + if (access::can("edit", $ancestor)) { + $ancestor->album_cover_item_id = $parent->album_cover_item_id; + $ancestor->save(); + graphics::generate($ancestor); + } + } + } } static function remove_album_cover($album) { access::required("view", $album); access::required("edit", $album); - @unlink($album->thumb_path()); model_cache::clear(); $album->album_cover_item_id = null; - $album->thumb_width = 0; - $album->thumb_height = 0; - $album->thumb_dirty = 1; $album->save(); graphics::generate($album); } @@ -437,4 +418,16 @@ class item_Core { } return call_user_func_array($callback, $args); } + + /** + * Reset all child weights of a given album to a monotonically increasing sequence based on the + * current sort order of the album. + */ + static function resequence_child_weights($album) { + $weight = 0; + foreach ($album->children() as $child) { + $child->weight = ++$weight; + $child->save(); + } + } }
\ No newline at end of file diff --git a/modules/gallery/helpers/item_rest.php b/modules/gallery/helpers/item_rest.php index 43fec3ab..efeba2ef 100644 --- a/modules/gallery/helpers/item_rest.php +++ b/modules/gallery/helpers/item_rest.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,7 +64,7 @@ class item_rest_Core { } if (isset($p->name)) { - $orm->where("name", "LIKE", "%{$p->name}%"); + $orm->where("name", "LIKE", "%" . Database::escape_for_like($p->name) . "%"); } if (isset($p->type)) { diff --git a/modules/gallery/helpers/items_rest.php b/modules/gallery/helpers/items_rest.php index 71d7a59a..7622c534 100644 --- a/modules/gallery/helpers/items_rest.php +++ b/modules/gallery/helpers/items_rest.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/json.php b/modules/gallery/helpers/json.php index 0cd78581..b56f2690 100644 --- a/modules/gallery/helpers/json.php +++ b/modules/gallery/helpers/json.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/l10n_client.php b/modules/gallery/helpers/l10n_client.php index a8c36d29..2a1be2f9 100644 --- a/modules/gallery/helpers/l10n_client.php +++ b/modules/gallery/helpers/l10n_client.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,7 +21,7 @@ class l10n_client_Core { private static function _server_url($path) { - return "http://gallery.menalto.com/translations/$path"; + return "http://galleryproject.org/translations/$path"; } static function server_api_key_url() { diff --git a/modules/gallery/helpers/l10n_scanner.php b/modules/gallery/helpers/l10n_scanner.php index 1f184da5..5980ebe0 100644 --- a/modules/gallery/helpers/l10n_scanner.php +++ b/modules/gallery/helpers/l10n_scanner.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/legal_file.php b/modules/gallery/helpers/legal_file.php index b3622764..eb9c25de 100644 --- a/modules/gallery/helpers/legal_file.php +++ b/modules/gallery/helpers/legal_file.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,24 +18,44 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class legal_file_Core { + private static $photo_types_by_extension; + private static $movie_types_by_extension; + private static $photo_extensions; + 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 + * Create a default list of allowed photo MIME types paired with their extensions and then let * modules modify it. This is an ordered map, mapping extensions to their MIME types. * Extensions cannot be duplicated, but MIMEs can (e.g. jpeg and jpg both map to image/jpeg). * * @param string $extension (opt.) - return MIME of extension; if not given, return complete array */ - static function get_photo_types_by_extension($extension=NULL) { - $types_by_extension_wrapper = new stdClass(); - $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); + static function get_photo_types_by_extension($extension=null) { + if (empty(self::$photo_types_by_extension)) { + $types_by_extension_wrapper = new stdClass(); + $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) { // return matching MIME type - return $types_by_extension_wrapper->types_by_extension[$extension]; + $extension = strtolower($extension); + if (isset(self::$photo_types_by_extension[$extension])) { + return self::$photo_types_by_extension[$extension]; + } else { + return null; + } } else { // return complete array - return $types_by_extension_wrapper->types_by_extension; + return self::$photo_types_by_extension; } } @@ -46,49 +66,115 @@ class legal_file_Core { * * @param string $extension (opt.) - return MIME of extension; if not given, return complete array */ - static function get_movie_types_by_extension($extension=NULL) { - $types_by_extension_wrapper = new stdClass(); - $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); + static function get_movie_types_by_extension($extension=null) { + if (empty(self::$movie_types_by_extension)) { + $types_by_extension_wrapper = new stdClass(); + $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) { // return matching MIME type - return $types_by_extension_wrapper->types_by_extension[$extension]; + $extension = strtolower($extension); + if (isset(self::$movie_types_by_extension[$extension])) { + return self::$movie_types_by_extension[$extension]; + } else { + return null; + } } else { // return complete array - return $types_by_extension_wrapper->types_by_extension; + return self::$movie_types_by_extension; + } + } + + /** + * Create a merged list of all allowed photo and movie MIME types paired with their extensions. + * + * @param string $extension (opt.) - return MIME of extension; if not given, return complete array + */ + static function get_types_by_extension($extension=null) { + $types_by_extension = legal_file::get_photo_types_by_extension(); + if (movie::allow_uploads()) { + $types_by_extension = array_merge($types_by_extension, + legal_file::get_movie_types_by_extension()); + } + if ($extension) { + // return matching MIME type + $extension = strtolower($extension); + if (isset($types_by_extension[$extension])) { + return $types_by_extension[$extension]; + } else { + return null; + } + } else { + // return complete array + return $types_by_extension; } } /** * Create a default list of allowed photo extensions and then let modules modify it. + * + * @param string $extension (opt.) - return true if allowed; if not given, return complete array */ - static function get_photo_extensions() { - $extensions_wrapper = new stdClass(); - $extensions_wrapper->extensions = array_keys(legal_file::get_photo_types_by_extension()); - module::event("legal_photo_extensions", $extensions_wrapper); - return $extensions_wrapper->extensions; + static function get_photo_extensions($extension=null) { + if (empty(self::$photo_extensions)) { + $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 = array_diff($extensions_wrapper->extensions, self::$blacklist); + } + if ($extension) { + // return true if in array, false if not + return in_array(strtolower($extension), self::$photo_extensions); + } else { + // return complete array + return self::$photo_extensions; + } } /** * Create a default list of allowed movie extensions and then let modules modify it. + * + * @param string $extension (opt.) - return true if allowed; if not given, return complete array */ - static function get_movie_extensions() { - $extensions_wrapper = new stdClass(); - $extensions_wrapper->extensions = array_keys(legal_file::get_movie_types_by_extension()); - module::event("legal_movie_extensions", $extensions_wrapper); - return $extensions_wrapper->extensions; + static function get_movie_extensions($extension=null) { + if (empty(self::$movie_extensions)) { + $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 = array_diff($extensions_wrapper->extensions, self::$blacklist); + } + if ($extension) { + // return true if in array, false if not + return in_array(strtolower($extension), self::$movie_extensions); + } else { + // return complete array + return self::$movie_extensions; + } } /** * Create a merged list of all allowed photo and movie extensions. + * + * @param string $extension (opt.) - return true if allowed; if not given, return complete array */ - static function get_extensions() { + 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()); } - return $extensions; + if ($extension) { + // return true if in array, false if not + return in_array(strtolower($extension), $extensions); + } else { + // return complete array + return $extensions; + } } /** @@ -109,10 +195,14 @@ class legal_file_Core { * (e.g. flv maps to video/x-flv by default, but video/flv is still legal). */ static function get_photo_types() { - $types_wrapper = new stdClass(); - $types_wrapper->types = array_values(legal_file::get_photo_types_by_extension()); - module::event("legal_photo_types", $types_wrapper); - return $types_wrapper->types; + if (empty(self::$photo_types)) { + $types_wrapper = new stdClass(); + // Need array_unique since types_by_extension can be many-to-one (e.g. jpeg and jpg). + $types_wrapper->types = array_unique(array_values(legal_file::get_photo_types_by_extension())); + module::event("legal_photo_types", $types_wrapper); + self::$photo_types = $types_wrapper->types; + } + return self::$photo_types; } /** @@ -121,29 +211,34 @@ class legal_file_Core { * (e.g. flv maps to video/x-flv by default, but video/flv is still legal). */ static function get_movie_types() { - $types_wrapper = new stdClass(); - $types_wrapper->types = array_values(legal_file::get_movie_types_by_extension()); - $types_wrapper->types[] = "video/flv"; - module::event("legal_movie_types", $types_wrapper); - return $types_wrapper->types; + if (empty(self::$movie_types)) { + $types_wrapper = new stdClass(); + // Need array_unique since types_by_extension can be many-to-one (e.g. jpeg and jpg). + $types_wrapper->types = array_unique(array_values(legal_file::get_movie_types_by_extension())); + $types_wrapper->types[] = "video/flv"; + module::event("legal_movie_types", $types_wrapper); + self::$movie_types = $types_wrapper->types; + } + return self::$movie_types; } /** - * Convert the extension of a filename. If the original filename has no + * Change the extension of a filename. If the original filename has no * extension, add the new one to the end. */ static function change_extension($filename, $new_ext) { - if (strpos($filename, ".") === false) { - return "{$filename}.{$new_ext}"; - } else { - return preg_replace("/\.[^\.]*?$/", ".{$new_ext}", $filename); - } + $filename_no_ext = preg_replace("/\.[^\.\/]*?$/", "", $filename); + return "{$filename_no_ext}.{$new_ext}"; } /** * 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"] != ".") { @@ -152,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/locales.php b/modules/gallery/helpers/locales.php index f4837e01..8ca25c39 100644 --- a/modules/gallery/helpers/locales.php +++ b/modules/gallery/helpers/locales.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/log.php b/modules/gallery/helpers/log.php index 98a5c7d0..cd554b5c 100644 --- a/modules/gallery/helpers/log.php +++ b/modules/gallery/helpers/log.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/message.php b/modules/gallery/helpers/message.php index 277a0fee..d1099953 100644 --- a/modules/gallery/helpers/message.php +++ b/modules/gallery/helpers/message.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php index fd56f131..7cff68d7 100644 --- a/modules/gallery/helpers/model_cache.php +++ b/modules/gallery/helpers/model_cache.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/module.php b/modules/gallery/helpers/module.php index e4f41d3d..df258e87 100644 --- a/modules/gallery/helpers/module.php +++ b/modules/gallery/helpers/module.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -175,9 +175,8 @@ class module_Core { $installer_class = "{$module_name}_installer"; if (method_exists($installer_class, "install")) { call_user_func_array(array($installer_class, "install"), array()); - } else { - module::set_version($module_name, module::available()->$module_name->code_version); } + module::set_version($module_name, module::available()->$module_name->code_version); // Set the weight of the new module, which controls the order in which the modules are // loaded. By default, new modules are installed at the end of the priority list. Since the diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php index 6d70ab2d..eda478c7 100644 --- a/modules/gallery/helpers/movie.php +++ b/modules/gallery/helpers/movie.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -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); @@ -66,7 +68,7 @@ class movie_Core { * @param string $output_file * @param array $movie_options (optional) */ - static function extract_frame($input_file, $output_file, $movie_options=NULL) { + static function extract_frame($input_file, $output_file, $movie_options=null) { $ffmpeg = movie::find_ffmpeg(); if (empty($ffmpeg)) { throw new Exception("@todo MISSING_FFMPEG"); @@ -74,17 +76,17 @@ class movie_Core { list($width, $height, $mime_type, $extension, $duration) = movie::get_file_metadata($input_file); - if (is_numeric($movie_options["start_time"])) { + if (isset($movie_options["start_time"]) && is_numeric($movie_options["start_time"])) { $start_time = max(0, $movie_options["start_time"]); // ensure it's non-negative } else { $start_time = module::get_var("gallery", "movie_extract_frame_time", 3); // use default } // extract frame at start_time, unless movie is too short $start_time_arg = ($duration >= $start_time + 0.1) ? - "-ss " . date("H:i:s", mktime(0,0,$start_time,0,0,0,0)) : ""; - - $input_args = $movie_options["input_args"] ? $movie_options["input_args"] : ""; - $output_args = $movie_options["output_args"] ? $movie_options["output_args"] : ""; + "-ss " . movie::seconds_to_hhmmssdd($start_time) : ""; + + $input_args = isset($movie_options["input_args"]) ? $movie_options["input_args"] : ""; + $output_args = isset($movie_options["output_args"]) ? $movie_options["output_args"] : ""; $cmd = escapeshellcmd($ffmpeg) . " $input_args -i " . escapeshellarg($input_file) . " -an $start_time_arg -an -r 1 -vframes 1" . @@ -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() { @@ -123,41 +148,108 @@ class movie_Core { /** * 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. + * + * This function and its use cases are symmetric to those of photo::get_file_metadata. + * + * @param string $file_path + * @return array array($width, $height, $mime_type, $extension, $duration) + * + * Use cases in detail: + * Input is standard movie type (flv/mp4/m4v) + * -> return metadata from ffmpeg + * Input is *not* standard movie type that is supported by ffmpeg (e.g. avi, mts...) + * -> 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 + * -> throw exception + * Note: movie_get_file_metadata events can change any of the above cases (except the last one). */ static function get_file_metadata($file_path) { - $ffmpeg = movie::find_ffmpeg(); - if (empty($ffmpeg)) { - throw new Exception("@todo MISSING_FFMPEG"); + if (!is_readable($file_path)) { + throw new Exception("@todo UNREADABLE_FILE"); } - $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($file_path) . " 2>&1"; - $result = `$cmd`; - if (preg_match("/Stream.*?Video:.*?, (\d+)x(\d+)/", $result, $matches_res)) { - if (preg_match("/Stream.*?Video:.*? \[.*?DAR (\d+):(\d+).*?\]/", $result, $matches_dar) && - $matches_dar[1] >= 1 && $matches_dar[2] >= 1) { - // DAR is defined - determine width based on height and DAR - // (should always be int, but adding round to be sure) - $matches_res[1] = round($matches_res[2] * $matches_dar[1] / $matches_dar[2]); + $metadata = new stdClass(); + $ffmpeg = movie::find_ffmpeg(); + if (!empty($ffmpeg)) { + // ffmpeg found - use it to get width, height, and duration. + $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($file_path) . " 2>&1"; + $result = `$cmd`; + if (preg_match("/Stream.*?Video:.*?, (\d+)x(\d+)/", $result, $matches_res)) { + if (preg_match("/Stream.*?Video:.*? \[.*?DAR (\d+):(\d+).*?\]/", $result, $matches_dar) && + $matches_dar[1] >= 1 && $matches_dar[2] >= 1) { + // DAR is defined - determine width based on height and DAR + // (should always be int, but adding round to be sure) + $matches_res[1] = round($matches_res[2] * $matches_dar[1] / $matches_dar[2]); + } + list ($metadata->width, $metadata->height) = array($matches_res[1], $matches_res[2]); + } else { + list ($metadata->width, $metadata->height) = array(0, 0); + } + + if (preg_match("/Duration: (\d+:\d+:\d+\.\d+)/", $result, $matches)) { + $metadata->duration = movie::hhmmssdd_to_seconds($matches[1]); + } else if (preg_match("/duration.*?:.*?(\d+)/", $result, $matches)) { + $metadata->duration = $matches[1]; + } else { + $metadata->duration = 0; } - list ($width, $height) = array($matches_res[1], $matches_res[2]); } else { - list ($width, $height) = array(0, 0); + // ffmpeg not found - set width, height, and duration to zero. + $metadata->width = 0; + $metadata->height = 0; + $metadata->duration = 0; } - $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); - $extension = $extension ? $extension : "flv"; // No extension? Assume FLV. - $mime_type = legal_file::get_movie_types_by_extension($extension); - $mime_type = $mime_type ? $mime_type : "video/x-flv"; // No MIME found? Default to video/x-flv. - - if (preg_match("/Duration: (\d+):(\d+):(\d+\.\d+)/", $result, $matches)) { - $duration = 3600 * $matches[1] + 60 * $matches[2] + $matches[3]; - } else if (preg_match("/duration.*?:.*?(\d+)/", $result, $matches)) { - $duration = $matches[1]; + $extension = pathinfo($file_path, PATHINFO_EXTENSION); + if (!$extension || + (!$metadata->mime_type = legal_file::get_movie_types_by_extension($extension))) { + // Extension is empty or illegal. + $metadata->extension = null; + $metadata->mime_type = null; } else { - $duration = 0; + // Extension is legal (and mime is already set above). + $metadata->extension = strtolower($extension); } - return array($width, $height, $mime_type, $extension, $duration); + // 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); + } + + /** + * Return the time/duration formatted in hh:mm:ss.dd from a number of seconds. + * Useful for inputs to ffmpeg. + * + * Note that this is similar to date("H:i:s", mktime(0,0,$seconds,0,0,0,0)), but unlike this + * approach avoids potential issues with time zone and DST mismatch and/or using deprecated + * features (the last argument of mkdate above, which disables DST, is deprecated as of PHP 5.3). + */ + static function seconds_to_hhmmssdd($seconds) { + return sprintf("%02d:%02d:%05.2f", floor($seconds / 3600), floor(($seconds % 3600) / 60), + floor(100 * $seconds % 6000) / 100); } + /** + * Return the number of seconds from a time/duration formatted in hh:mm:ss.dd. + * Useful for outputs from ffmpeg. + */ + static function hhmmssdd_to_seconds($hhmmssdd) { + preg_match("/(\d+):(\d+):(\d+\.\d+)/", $hhmmssdd, $matches); + return 3600 * $matches[1] + 60 * $matches[2] + $matches[3]; + } } diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php index c4001bd5..2d32f0d3 100644 --- a/modules/gallery/helpers/photo.php +++ b/modules/gallery/helpers/photo.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -80,20 +80,68 @@ class photo_Core { /** * Return the width, height, mime_type and extension of the given image file. + * Metadata is first generated using getimagesize (or the legal_file mapping if it fails), + * then can be modified by other modules using photo_get_file_metadata events. + * + * This function and its use cases are symmetric to those of photo::get_file_metadata. + * + * @param string $file_path + * @return array array($width, $height, $mime_type, $extension) + * + * Use cases in detail: + * Input is standard photo type (jpg/png/gif) + * -> return metadata from getimagesize() + * 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 + * -> throw exception + * Note: photo_get_file_metadata events can change any of the above cases (except the last one). */ static function get_file_metadata($file_path) { - $image_info = getimagesize($file_path); - if ($image_info) { - $width = $image_info[0]; - $height = $image_info[1]; - $mime_type = $image_info["mime"]; - $extension = image_type_to_extension($image_info[2], false); - return array($width, $height, $mime_type, $extension); + if (!is_readable($file_path)) { + throw new Exception("@todo UNREADABLE_FILE"); + } + + $metadata = new stdClass(); + if ($image_info = getimagesize($file_path)) { + // getimagesize worked - use its results. + $metadata->width = $image_info[0]; + $metadata->height = $image_info[1]; + $metadata->mime_type = $image_info["mime"]; + $metadata->extension = image_type_to_extension($image_info[2], false); + // We prefer jpg instead of jpeg (which is returned by image_type_to_extension). + if ($metadata->extension == "jpeg") { + $metadata->extension = "jpg"; + } } else { - // getimagesize failed - use legal_file mapping instead. - $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); - $mime_type = legal_file::get_photo_types_by_extension($extension); - return array(0, 0, $mime_type, $extension); + // getimagesize failed - try to use legal_file mapping instead. + $extension = pathinfo($file_path, PATHINFO_EXTENSION); + if (!$extension || + (!$metadata->mime_type = legal_file::get_photo_types_by_extension($extension))) { + // Extension is empty or illegal. + $metadata->extension = null; + $metadata->mime_type = null; + } else { + // Extension is legal (and mime is already set above). + $metadata->extension = strtolower($extension); + } + $metadata->width = 0; + $metadata->height = 0; } + + // 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/random.php b/modules/gallery/helpers/random.php index ea08815a..d40bfb52 100644 --- a/modules/gallery/helpers/random.php +++ b/modules/gallery/helpers/random.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/site_status.php b/modules/gallery/helpers/site_status.php index dc8a6c23..e1a81637 100644 --- a/modules/gallery/helpers/site_status.php +++ b/modules/gallery/helpers/site_status.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/system.php b/modules/gallery/helpers/system.php index 9293e422..e1398103 100644 --- a/modules/gallery/helpers/system.php +++ b/modules/gallery/helpers/system.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -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; diff --git a/modules/gallery/helpers/task.php b/modules/gallery/helpers/task.php index 6fd6618f..32fd9739 100644 --- a/modules/gallery/helpers/task.php +++ b/modules/gallery/helpers/task.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/theme.php b/modules/gallery/helpers/theme.php index ea93a44f..072f98a1 100644 --- a/modules/gallery/helpers/theme.php +++ b/modules/gallery/helpers/theme.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/tree_rest.php b/modules/gallery/helpers/tree_rest.php index c2f81d3a..5186cf09 100644 --- a/modules/gallery/helpers/tree_rest.php +++ b/modules/gallery/helpers/tree_rest.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/upgrade_checker.php b/modules/gallery/helpers/upgrade_checker.php index d92da6e5..492f72e9 100644 --- a/modules/gallery/helpers/upgrade_checker.php +++ b/modules/gallery/helpers/upgrade_checker.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ class upgrade_checker_Core { - const CHECK_URL = "http://gallery.menalto.com/versioncheck/gallery3"; + const CHECK_URL = "http://galleryproject.org/versioncheck/gallery3"; const AUTO_CHECK_INTERVAL = 604800; // 7 days in seconds /** diff --git a/modules/gallery/helpers/user_profile.php b/modules/gallery/helpers/user_profile.php index be79d9a8..222d2f57 100644 --- a/modules/gallery/helpers/user_profile.php +++ b/modules/gallery/helpers/user_profile.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/modules/gallery/helpers/xml.php b/modules/gallery/helpers/xml.php index b57ecafa..e20beb19 100644 --- a/modules/gallery/helpers/xml.php +++ b/modules/gallery/helpers/xml.php @@ -1,7 +1,7 @@ <?php defined("SYSPATH") or die("No direct script access."); /** * Gallery - a web based photo album viewer and editor - * Copyright (C) 2000-2012 Bharat Mediratta + * Copyright (C) 2000-2013 Bharat Mediratta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by |