From 4ae8bb6c1c6e4cd0b9e744f955e92caf95a051cd Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Sun, 17 Apr 2011 17:11:18 -0700 Subject: [PATCH 1/7] A few fixes for the captionator: - Escape all text fields to avoid XSS, etc. - Automatically create the slug from the title - Fix a bug that caused autocompleting tags not to work unless your Gallery is in the documentroot. --- .../views/captionator_dialog.html.php | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/3.0/modules/captionator/views/captionator_dialog.html.php b/3.0/modules/captionator/views/captionator_dialog.html.php index fe77c2ab..a5310c5c 100644 --- a/3.0/modules/captionator/views/captionator_dialog.html.php +++ b/3.0/modules/captionator/views/captionator_dialog.html.php @@ -1,9 +1,20 @@
id}") ?>" method="post" id="g-captionator-form"> @@ -23,7 +34,7 @@
  • - +
  • @@ -32,16 +43,16 @@
  • - +
  • - +
  • - +
From 899aaec37ca887e9b2cbc1d18108a214f3165d0e Mon Sep 17 00:00:00 2001 From: Tim Almdal Date: Thu, 21 Apr 2011 11:55:21 -0700 Subject: [PATCH 2/7] Change the name of the name in the module.info to the actual module name --- modules/themeroller/module.info | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/themeroller/module.info b/modules/themeroller/module.info index 0e50286e..74512420 100755 --- a/modules/themeroller/module.info +++ b/modules/themeroller/module.info @@ -1,3 +1,3 @@ -name = "Theme generator" +name = "Theme Roller" description = "Use a JQuery UI theme to create a Gallery3 Theme" version = 1 From 5e5a9558ee039b4e0a7e6899626eadfd9d0b700d Mon Sep 17 00:00:00 2001 From: rWatcher Date: Thu, 21 Apr 2011 16:44:21 -0400 Subject: [PATCH 3/7] Bugfix for displaying a year with no photos. --- 3.0/modules/calendarview/views/calendarview_year.html.php | 2 +- 3.1/modules/calendarview/views/calendarview_year.html.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/3.0/modules/calendarview/views/calendarview_year.html.php b/3.0/modules/calendarview/views/calendarview_year.html.php index 880e0267..b40d7d58 100644 --- a/3.0/modules/calendarview/views/calendarview_year.html.php +++ b/3.0/modules/calendarview/views/calendarview_year.html.php @@ -38,7 +38,7 @@ // Check and see if any photos were taken in January, // If so, make the month title into a clickable link. print "
"; - if (date("n", $items_for_year[$counter]->captured) == 1) { + if ((count($items_for_year) > 0) && (date("n", $items_for_year[$counter]->captured) == 1)) { $month_url = url::site("calendarview/month/" . $calendar_year . "/" . $calendar_user . "/" . $counter_months . "/"); } else { $month_url = ""; diff --git a/3.1/modules/calendarview/views/calendarview_year.html.php b/3.1/modules/calendarview/views/calendarview_year.html.php index 880e0267..b40d7d58 100644 --- a/3.1/modules/calendarview/views/calendarview_year.html.php +++ b/3.1/modules/calendarview/views/calendarview_year.html.php @@ -38,7 +38,7 @@ // Check and see if any photos were taken in January, // If so, make the month title into a clickable link. print "
"; - if (date("n", $items_for_year[$counter]->captured) == 1) { + if ((count($items_for_year) > 0) && (date("n", $items_for_year[$counter]->captured) == 1)) { $month_url = url::site("calendarview/month/" . $calendar_year . "/" . $calendar_user . "/" . $counter_months . "/"); } else { $month_url = ""; From e36b3f5b75321699baa63695285f09465fa51af1 Mon Sep 17 00:00:00 2001 From: rWatcher Date: Thu, 21 Apr 2011 18:36:31 -0400 Subject: [PATCH 4/7] Added an option for limiting the number of tags displayed. --- .../tagsinalbum/helpers/tagsinalbum_installer.php | 10 +++++++++- 3.0/modules/tagsinalbum/module.info | 2 +- .../tagsinalbum/views/tagsinalbum_sidebar.html.php | 5 +++++ .../tagsinalbum/helpers/tagsinalbum_installer.php | 10 +++++++++- 3.1/modules/tagsinalbum/module.info | 2 +- .../tagsinalbum/views/tagsinalbum_sidebar.html.php | 5 +++++ 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/3.0/modules/tagsinalbum/helpers/tagsinalbum_installer.php b/3.0/modules/tagsinalbum/helpers/tagsinalbum_installer.php index 4357ab87..09f82108 100644 --- a/3.0/modules/tagsinalbum/helpers/tagsinalbum_installer.php +++ b/3.0/modules/tagsinalbum/helpers/tagsinalbum_installer.php @@ -19,7 +19,15 @@ */ class tagsinalbum_installer { static function install() { - module::set_version("tagsinalbum", 1); + module::set_var("tagsinalbum", "max_display_tags", 0); + module::set_version("tagsinalbum", 2); + } + + static function upgrade($version) { + if ($version == 1) { + module::set_var("tagsinalbum", "max_display_tags", 0); + module::set_version("tagsinalbum", $version = 2); + } } static function deactivate() { diff --git a/3.0/modules/tagsinalbum/module.info b/3.0/modules/tagsinalbum/module.info index 94b7699b..5d9b91f1 100644 --- a/3.0/modules/tagsinalbum/module.info +++ b/3.0/modules/tagsinalbum/module.info @@ -1,3 +1,3 @@ name = "Tags In Album" description = "Creates a sidebar block to display tags used by photos and videos in the current album." -version = 1 +version = 2 diff --git a/3.0/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php b/3.0/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php index 803e46ee..6c8b1dff 100644 --- a/3.0/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php +++ b/3.0/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php @@ -12,6 +12,11 @@ $display_tags[] = array(html::clean($tag->name), $tag->url()); $last_tagid = $one_tag->id; } + if (module::get_var("tagsinalbum", "max_display_tags") > 0) { + if (count($display_tags) == module::get_var("tagsinalbum", "max_display_tags")) { + break; + } + } } // Sort the array. diff --git a/3.1/modules/tagsinalbum/helpers/tagsinalbum_installer.php b/3.1/modules/tagsinalbum/helpers/tagsinalbum_installer.php index 4357ab87..09f82108 100644 --- a/3.1/modules/tagsinalbum/helpers/tagsinalbum_installer.php +++ b/3.1/modules/tagsinalbum/helpers/tagsinalbum_installer.php @@ -19,7 +19,15 @@ */ class tagsinalbum_installer { static function install() { - module::set_version("tagsinalbum", 1); + module::set_var("tagsinalbum", "max_display_tags", 0); + module::set_version("tagsinalbum", 2); + } + + static function upgrade($version) { + if ($version == 1) { + module::set_var("tagsinalbum", "max_display_tags", 0); + module::set_version("tagsinalbum", $version = 2); + } } static function deactivate() { diff --git a/3.1/modules/tagsinalbum/module.info b/3.1/modules/tagsinalbum/module.info index 94b7699b..5d9b91f1 100644 --- a/3.1/modules/tagsinalbum/module.info +++ b/3.1/modules/tagsinalbum/module.info @@ -1,3 +1,3 @@ name = "Tags In Album" description = "Creates a sidebar block to display tags used by photos and videos in the current album." -version = 1 +version = 2 diff --git a/3.1/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php b/3.1/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php index 803e46ee..6c8b1dff 100644 --- a/3.1/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php +++ b/3.1/modules/tagsinalbum/views/tagsinalbum_sidebar.html.php @@ -12,6 +12,11 @@ $display_tags[] = array(html::clean($tag->name), $tag->url()); $last_tagid = $one_tag->id; } + if (module::get_var("tagsinalbum", "max_display_tags") > 0) { + if (count($display_tags) == module::get_var("tagsinalbum", "max_display_tags")) { + break; + } + } } // Sort the array. From 30d9551dabfdf4c7eafc780cc37778cff302cf2d Mon Sep 17 00:00:00 2001 From: rWatcher Date: Thu, 21 Apr 2011 19:08:57 -0400 Subject: [PATCH 5/7] Synced movie.php with changes in Gallery, modified uploadify html to accept videos. --- 3.0/modules/noffmpeg/helpers/movie.php | 21 +-- .../noffmpeg/views/form_uploadify.html.php | 164 ++++++++++++++++++ 3.1/modules/noffmpeg/helpers/movie.php | 21 +-- .../noffmpeg/views/form_uploadify.html.php | 164 ++++++++++++++++++ 4 files changed, 346 insertions(+), 24 deletions(-) create mode 100644 3.0/modules/noffmpeg/views/form_uploadify.html.php create mode 100644 3.1/modules/noffmpeg/views/form_uploadify.html.php diff --git a/3.0/modules/noffmpeg/helpers/movie.php b/3.0/modules/noffmpeg/helpers/movie.php index a42804d2..99308994 100644 --- a/3.0/modules/noffmpeg/helpers/movie.php +++ b/3.0/modules/noffmpeg/helpers/movie.php @@ -24,6 +24,7 @@ * Note: by design, this class does not do any permission checking. */ +// rWatcher edit: include MP4Info.php library. include MODPATH . "noffmpeg/libraries/MP4Info.php"; class movie_Core { @@ -61,7 +62,7 @@ class movie_Core { } static function extract_frame($input_file, $output_file) { - $ffmpeg = self::find_ffmpeg(); + $ffmpeg = movie::find_ffmpeg(); if (empty($ffmpeg)) { // BEGIN rWatcher Edit. copy(MODPATH . "noffmpeg/images/missing_movie.png", $output_file); @@ -89,27 +90,23 @@ class movie_Core { } } + /** + * Return the path to the ffmpeg binary if one exists and is executable, or null. + */ static function find_ffmpeg() { if (!($ffmpeg_path = module::get_var("gallery", "ffmpeg_path")) || !file_exists($ffmpeg_path)) { - $graphics_path = module::get_var("gallery", "graphics_toolkit_path", null); - - putenv("PATH=" . getenv("PATH") . (empty($graphics_path) ? "" : ":$graphics_path") . - ":/usr/local/bin:/opt/local/bin:/opt/bin"); - if (function_exists("exec")) { - $ffmpeg_path = exec("which ffmpeg"); - } - + $ffmpeg_path = system::find_binary( + "ffmpeg", module::get_var("gallery", "graphics_toolkit_path")); module::set_var("gallery", "ffmpeg_path", $ffmpeg_path); } return $ffmpeg_path; } - /** * Return the width, height, mime_type and extension of the given movie file. */ static function get_file_metadata($file_path) { - $ffmpeg = self::find_ffmpeg(); + $ffmpeg = movie::find_ffmpeg(); if (empty($ffmpeg)) { // BEGIN rWatcher Edit. $pi = pathinfo($file_path); @@ -147,4 +144,4 @@ class movie_Core { return array($width, $height, $mime_type, $extension); } -} +} \ No newline at end of file diff --git a/3.0/modules/noffmpeg/views/form_uploadify.html.php b/3.0/modules/noffmpeg/views/form_uploadify.html.php new file mode 100644 index 00000000..911e02d5 --- /dev/null +++ b/3.0/modules/noffmpeg/views/form_uploadify.html.php @@ -0,0 +1,164 @@ + + + + + +
+ admin && !$movies_allowed)): ?> +
+ +

+ suhosin.session.encrypt setting from Suhosin. You must disable this setting to upload photos.", + array("encrypt_url" => "http://www.hardened-php.net/suhosin/configuration.html#suhosin.session.encrypt", + "suhosin_url" => "http://www.hardened-php.net/suhosin/")) ?> +

+ + + admin && !$movies_allowed): ?> +

+ ffmpeg on your system. Movie uploading disabled. Help!", array("help_url" => "http://codex.gallery2.org/Gallery3:FAQ#Why_does_it_say_I.27m_missing_ffmpeg.3F")) ?> +

+ +
+ + +
+

+ +

+
    + parents() as $i => $parent): ?> + > title) ?> + +
  • title) ?>
  • +
+
+ +
+ + +
+
+
    +
+
+
+ + \ No newline at end of file diff --git a/3.1/modules/noffmpeg/helpers/movie.php b/3.1/modules/noffmpeg/helpers/movie.php index a42804d2..99308994 100644 --- a/3.1/modules/noffmpeg/helpers/movie.php +++ b/3.1/modules/noffmpeg/helpers/movie.php @@ -24,6 +24,7 @@ * Note: by design, this class does not do any permission checking. */ +// rWatcher edit: include MP4Info.php library. include MODPATH . "noffmpeg/libraries/MP4Info.php"; class movie_Core { @@ -61,7 +62,7 @@ class movie_Core { } static function extract_frame($input_file, $output_file) { - $ffmpeg = self::find_ffmpeg(); + $ffmpeg = movie::find_ffmpeg(); if (empty($ffmpeg)) { // BEGIN rWatcher Edit. copy(MODPATH . "noffmpeg/images/missing_movie.png", $output_file); @@ -89,27 +90,23 @@ class movie_Core { } } + /** + * Return the path to the ffmpeg binary if one exists and is executable, or null. + */ static function find_ffmpeg() { if (!($ffmpeg_path = module::get_var("gallery", "ffmpeg_path")) || !file_exists($ffmpeg_path)) { - $graphics_path = module::get_var("gallery", "graphics_toolkit_path", null); - - putenv("PATH=" . getenv("PATH") . (empty($graphics_path) ? "" : ":$graphics_path") . - ":/usr/local/bin:/opt/local/bin:/opt/bin"); - if (function_exists("exec")) { - $ffmpeg_path = exec("which ffmpeg"); - } - + $ffmpeg_path = system::find_binary( + "ffmpeg", module::get_var("gallery", "graphics_toolkit_path")); module::set_var("gallery", "ffmpeg_path", $ffmpeg_path); } return $ffmpeg_path; } - /** * Return the width, height, mime_type and extension of the given movie file. */ static function get_file_metadata($file_path) { - $ffmpeg = self::find_ffmpeg(); + $ffmpeg = movie::find_ffmpeg(); if (empty($ffmpeg)) { // BEGIN rWatcher Edit. $pi = pathinfo($file_path); @@ -147,4 +144,4 @@ class movie_Core { return array($width, $height, $mime_type, $extension); } -} +} \ No newline at end of file diff --git a/3.1/modules/noffmpeg/views/form_uploadify.html.php b/3.1/modules/noffmpeg/views/form_uploadify.html.php new file mode 100644 index 00000000..911e02d5 --- /dev/null +++ b/3.1/modules/noffmpeg/views/form_uploadify.html.php @@ -0,0 +1,164 @@ + + + + + +
+ admin && !$movies_allowed)): ?> +
+ +

+ suhosin.session.encrypt setting from Suhosin. You must disable this setting to upload photos.", + array("encrypt_url" => "http://www.hardened-php.net/suhosin/configuration.html#suhosin.session.encrypt", + "suhosin_url" => "http://www.hardened-php.net/suhosin/")) ?> +

+ + + admin && !$movies_allowed): ?> +

+ ffmpeg on your system. Movie uploading disabled. Help!", array("help_url" => "http://codex.gallery2.org/Gallery3:FAQ#Why_does_it_say_I.27m_missing_ffmpeg.3F")) ?> +

+ +
+ + +
+

+ +

+
    + parents() as $i => $parent): ?> + > title) ?> + +
  • title) ?>
  • +
+
+ +
+ + +
+
+
    +
+
+
+ + \ No newline at end of file From e2dc311221cca2b81221dfcbce035056a0f76946 Mon Sep 17 00:00:00 2001 From: rWatcher Date: Fri, 22 Apr 2011 00:33:11 -0400 Subject: [PATCH 6/7] Complete re-write for Gallery 3.0.1 compatability. --- .../videos/controllers/admin_videos.php | 8 +- 3.0/modules/videos/controllers/file_proxy.php | 144 +++ 3.0/modules/videos/controllers/videos.php | 173 +-- 3.0/modules/videos/css/videos.css | 16 +- 3.0/modules/videos/helpers/videos.php | 6 +- 3.0/modules/videos/helpers/videos_event.php | 8 +- .../videos/helpers/videos_installer.php | 46 +- 3.0/modules/videos/helpers/videos_theme.php | 27 +- 3.0/modules/videos/js/admin_videos.js | 5 + 3.0/modules/videos/js/videos.js | 55 +- 3.0/modules/videos/js/videos_download.js | 8 + 3.0/modules/videos/models/item.php | 1030 +++++++++++++++++ 3.0/modules/videos/models/items_video.php | 1 + .../{videos_file.php => videos_entry.php} | 6 +- 3.0/modules/videos/module.info | 2 +- .../videos/views/admin_videos.html.php | 3 +- 3.0/modules/videos/views/movieplayer.html.php | 5 + .../videos/views/videos_display_js.html.php | 28 - 3.0/modules/videos/views/videos_tree.html.php | 1 + .../videos/views/videos_tree_dialog.html.php | 23 +- .../videos/controllers/admin_videos.php | 8 +- 3.1/modules/videos/controllers/file_proxy.php | 144 +++ 3.1/modules/videos/controllers/videos.php | 173 +-- 3.1/modules/videos/css/videos.css | 16 +- 3.1/modules/videos/helpers/videos.php | 6 +- 3.1/modules/videos/helpers/videos_event.php | 8 +- .../videos/helpers/videos_installer.php | 46 +- 3.1/modules/videos/helpers/videos_theme.php | 25 +- 3.1/modules/videos/js/admin_videos.js | 5 + 3.1/modules/videos/js/videos.js | 55 +- 3.1/modules/videos/js/videos_download.js | 8 + 3.1/modules/videos/models/item.php | 1030 +++++++++++++++++ 3.1/modules/videos/models/items_video.php | 1 + .../{videos_file.php => videos_entry.php} | 6 +- 3.1/modules/videos/module.info | 2 +- .../videos/views/admin_videos.html.php | 3 +- 3.1/modules/videos/views/movieplayer.html.php | 5 + .../videos/views/videos_display_js.html.php | 28 - 3.1/modules/videos/views/videos_tree.html.php | 1 + .../videos/views/videos_tree_dialog.html.php | 23 +- 40 files changed, 2849 insertions(+), 339 deletions(-) create mode 100644 3.0/modules/videos/controllers/file_proxy.php create mode 100644 3.0/modules/videos/js/videos_download.js create mode 100644 3.0/modules/videos/models/item.php rename 3.0/modules/videos/models/{videos_file.php => videos_entry.php} (84%) delete mode 100644 3.0/modules/videos/views/videos_display_js.html.php create mode 100644 3.1/modules/videos/controllers/file_proxy.php create mode 100644 3.1/modules/videos/js/videos_download.js create mode 100644 3.1/modules/videos/models/item.php rename 3.1/modules/videos/models/{videos_file.php => videos_entry.php} (84%) delete mode 100644 3.1/modules/videos/views/videos_display_js.html.php diff --git a/3.0/modules/videos/controllers/admin_videos.php b/3.0/modules/videos/controllers/admin_videos.php index afeb4353..b5fe1cf3 100644 --- a/3.0/modules/videos/controllers/admin_videos.php +++ b/3.0/modules/videos/controllers/admin_videos.php @@ -1,7 +1,7 @@ page_title = t("Add videos from server"); + $view->page_title = t("Add from server"); $view->content = new View("admin_videos.html"); $view->content->form = $this->_get_admin_form(); $paths = unserialize(module::get_var("videos", "authorized_paths", "a:0:{}")); diff --git a/3.0/modules/videos/controllers/file_proxy.php b/3.0/modules/videos/controllers/file_proxy.php new file mode 100644 index 00000000..f69bff1b --- /dev/null +++ b/3.0/modules/videos/controllers/file_proxy.php @@ -0,0 +1,144 @@ +server("REQUEST_URI")); + + // get rid of query parameters + // request_uri: gallery3/var/albums/foo/bar.jpg + $request_uri = preg_replace("/\?.*/", "", $request_uri); + + // var_uri: gallery3/var/ + $var_uri = url::file("var/"); + + // Make sure that the request is for a file inside var + $offset = strpos(rawurldecode($request_uri), $var_uri); + if ($offset !== 0) { + throw new Kohana_404_Exception(); + } + + // file_uri: albums/foo/bar.jpg + $file_uri = substr($request_uri, strlen($var_uri)); + + // type: albums + // path: foo/bar.jpg + list ($type, $path) = explode("/", $file_uri, 2); + if ($type != "resizes" && $type != "albums" && $type != "thumbs") { + throw new Kohana_404_Exception(); + } + + // If the last element is .album.jpg, pop that off since it's not a real item + $path = preg_replace("|/.album.jpg$|", "", $path); + + $item = item::find_by_path($path); + if (!$item->loaded()) { + // We didn't turn it up. If we're looking for a .jpg then it's it's possible that we're + // requesting the thumbnail for a movie. In that case, the .flv, .mp4 or .m4v file would + // have been converted to a .jpg. So try some alternate types: + if (preg_match('/.jpg$/', $path)) { + // rWatcher Mod: look for videos with file extensions supported by the videos module in addition to flv mp4 and m4v + // Original Line: foreach (array("flv", "mp4", "m4v") as $ext) { + foreach (array_merge(array("flv", "mp4", "m4v"), unserialize(module::get_var("videos", "allowed_extensions"))) as $ext) { + $movie_path = preg_replace('/.jpg$/', ".$ext", $path); + $item = item::find_by_path($movie_path); + if ($item->loaded()) { + break; + } + } + } + // rWatcher Mod: + // If we're looking for a .flv then it's it's possible that we're requesting a flash resize + // for a movie. + if (strtolower(substr($path, strlen($path)-4)) == ".flv") { + $movie_path = str_ireplace(".flv", "", $path); + $item = ORM::factory("item")->where("relative_path_cache", "=", $movie_path)->find(); + } + // END rWatcher Mod + } + + if (!$item->loaded()) { + throw new Kohana_404_Exception(); + } + + // Make sure we have access to the item + if (!access::can("view", $item)) { + throw new Kohana_404_Exception(); + } + + // Make sure we have view_full access to the original + if ($type == "albums" && !access::can("view_full", $item)) { + throw new Kohana_404_Exception(); + } + + // Don't try to load a directory + if ($type == "albums" && $item->is_album()) { + throw new Kohana_404_Exception(); + } + + if ($type == "albums") { + $file = $item->file_path(); + } else if ($type == "resizes") { + $file = $item->resize_path(); + // rWatcher MOD + // If the resize is for a movie, assume it needs a .flv extension. + if ($item->is_movie()) { + $file = $file . ".flv"; + } + // End rWatcher MOD + } else { + $file = $item->thumb_path(); + } + + if (!file_exists($file)) { + throw new Kohana_404_Exception(); + } + + header("Content-Length: " . filesize($file)); + + header("Pragma:"); + // Check that the content hasn't expired or it wasn't changed since cached + expires::check(2592000, $item->updated); + + // We don't need to save the session for this request + Session::instance()->abort_save(); + + 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() && $type != "albums") { + header("Content-Type: image/jpeg"); + } else { + header("Content-Type: $item->mime_type"); + } + Kohana::close_buffers(false); + readfile($file); + } +} diff --git a/3.0/modules/videos/controllers/videos.php b/3.0/modules/videos/controllers/videos.php index 78121ae4..b30f4f0e 100644 --- a/3.0/modules/videos/controllers/videos.php +++ b/3.0/modules/videos/controllers/videos.php @@ -1,7 +1,7 @@ where("task_id", "NOT IN", db::build()->select("id")->from("tasks")) + ->delete("videos_entries") + ->execute(); + $item = ORM::factory("item", $id); $view = new View("videos_tree_dialog.html"); $view->item = $item; @@ -55,6 +67,7 @@ class Videos_Controller extends Admin_Controller { } if (!is_dir($file)) { $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + // rWatcher Edit //if (!in_array($ext, array("gif", "jpeg", "jpg", "png", "flv", "mp4", "m4v"))) { if (!in_array($ext, unserialize(module::get_var("videos", "allowed_extensions")))) { continue; @@ -74,23 +87,28 @@ class Videos_Controller extends Admin_Controller { } /** - * Begin the task of adding files. + * Begin the task of adding photos. */ public function start() { access::verify_csrf(); $item = ORM::factory("item", Input::instance()->get("item_id")); - foreach (Input::instance()->post("paths") as $path) { - if (videos::is_valid_path($path)) { - $paths[] = array($path, null); - } - } - $task_def = Task_Definition::factory() ->callback("Videos_Controller::add") - ->description(t("Add videos from the local server")) + ->description(t("Add photos or movies from the local server")) ->name(t("Add from server")); - $task = task::create($task_def, array("item_id" => $item->id, "queue" => $paths)); + $task = task::create($task_def, array("item_id" => $item->id)); + + foreach (Input::instance()->post("paths") as $path) { + if (videos::is_valid_path($path)) { + $entry = ORM::factory("videos_entry"); + $entry->path = $path; + $entry->is_directory = intval(is_dir($path)); + $entry->parent_id = null; + $entry->task_id = $task->id; + $entry->save(); + } + } json::reply( array("result" => "started", @@ -99,7 +117,7 @@ class Videos_Controller extends Admin_Controller { } /** - * Run the task of adding files + * Run the task of adding photos */ function run($task_id) { access::verify_csrf(); @@ -119,7 +137,7 @@ class Videos_Controller extends Admin_Controller { /** * This is the task code that adds photos and albums. It first examines all the target files - * and creates a set of Server_Add_File_Models, then runs through the list of models and adds + * and creates a set of Server_Add_Entry_Models, then runs through the list of models and adds * them one at a time. */ static function add($task) { @@ -129,6 +147,7 @@ class Videos_Controller extends Admin_Controller { switch ($mode) { case "init": $task->set("mode", "build-file-list"); + $task->set("dirs_scanned", 0); $task->percent_complete = 0; $task->status = t("Starting up"); batch::start(); @@ -137,59 +156,64 @@ class Videos_Controller extends Admin_Controller { case "build-file-list": // 0% to 10% // We can't fit an arbitrary number of paths in a task, so store them in a separate table. // Don't use an iterator here because we can't get enough control over it when we're dealing - // with a deep hierarchy and we don't want to go over our time quota. The queue is in the - // form [path, parent_id] where the parent_id refers to another Server_Add_File_Model. We - // have this extra level of abstraction because we don't know its Item_Model id yet. - $queue = $task->get("queue"); + // with a deep hierarchy and we don't want to go over our time quota. $paths = unserialize(module::get_var("videos", "authorized_paths")); + $dirs_scanned = $task->get("dirs_scanned"); + while (microtime(true) - $start < 0.5) { + // Process every directory that doesn't yet have a parent id, these are the + // paths that we're importing. + $entry = ORM::factory("videos_entry") + ->where("task_id", "=", $task->id) + ->where("is_directory", "=", 1) + ->where("checked", "=", 0) + ->order_by("id", "ASC") + ->find(); - while ($queue && microtime(true) - $start < 0.5) { - list($file, $parent_entry_id) = array_shift($queue); - // Ignore the staging directories as directories to be imported. - if (empty($paths[$file])) { - $entry = ORM::factory("videos_file"); - $entry->task_id = $task->id; - $entry->file = $file; - $entry->parent_id = $parent_entry_id; - $entry->save(); - $entry_id = $entry->id; - } else { - $entry_id = null; - } - - $file = preg_quote($file); - foreach (glob("$file/*") as $child) { - if (is_dir($child)) { - $queue[] = array($child, $entry_id); - } else { - $ext = strtolower(pathinfo($child, PATHINFO_EXTENSION)); - //if (in_array($ext, array("gif", "jpeg", "jpg", "png", "flv", "mp4", "m4v")) && - if (in_array($ext, unserialize(module::get_var("videos", "allowed_extensions"))) && - filesize($child) > 0) { - $child_entry = ORM::factory("videos_file"); - $child_entry->task_id = $task->id; - $child_entry->file = $child; - $child_entry->parent_id = $entry_id; - $child_entry->save(); - } + if ($entry->loaded()) { + $child_paths = glob(preg_quote($entry->path) . "/*"); + if (!$child_paths) { + $child_paths = glob("{$entry->path}/*"); } + foreach ($child_paths as $child_path) { + if (!is_dir($child_path)) { + $ext = strtolower(pathinfo($child_path, PATHINFO_EXTENSION)); + // rWatcher Edit. + //if (!in_array($ext, array("gif", "jpeg", "jpg", "png", "flv", "mp4", "m4v")) || + // !filesize($child_path)) { + if (!in_array($ext, unserialize(module::get_var("videos", "allowed_extensions"))) || + !filesize($child_path)) { + // Not importable, skip it. + continue; + } + } + + $child_entry = ORM::factory("videos_entry"); + $child_entry->task_id = $task->id; + $child_entry->path = $child_path; + $child_entry->parent_id = $entry->id; // null if the parent was a staging dir + $child_entry->is_directory = is_dir($child_path); + $child_entry->save(); + } + + // We've processed this entry, mark it as done. + $entry->checked = 1; + $entry->save(); + $dirs_scanned++; } } // We have no idea how long this can take because we have no idea how deep the tree // hierarchy rabbit hole goes. Leave ourselves room here for 100 iterations and don't go // over 10% in percent_complete. - $task->set("queue", $queue); + $task->set("dirs_scanned", $dirs_scanned); $task->percent_complete = min($task->percent_complete + 0.1, 10); - $task->status = t2( - "Found one file", "Found %count files", - ORM::factory("videos_file")->where("task_id", "=", $task->id)->count_all()); + $task->status = t2("Scanned one directory", "Scanned %count directories", $dirs_scanned); - if (!$queue) { + if (!$entry->loaded()) { $task->set("mode", "add-files"); $task->set( "total_files", - ORM::factory("videos_file")->where("task_id", "=", $task->id)->count_all()); + ORM::factory("videos_entry")->where("task_id", "=", $task->id)->count_all()); $task->percent_complete = 10; } break; @@ -201,7 +225,7 @@ class Videos_Controller extends Admin_Controller { // Ordering by id ensures that we add them in the order that we created the entries, which // will create albums first. Ignore entries which already have an Item_Model attached, // they're done. - $entries = ORM::factory("videos_file") + $entries = ORM::factory("videos_entry") ->where("task_id", "=", $task->id) ->where("item_id", "IS", null) ->order_by("id", "ASC") @@ -220,43 +244,59 @@ class Videos_Controller extends Admin_Controller { // Look up the parent item for this entry. By now it should exist, but if none was // specified, then this belongs as a child of the current item. - $parent_entry = ORM::factory("videos_file", $entry->parent_id); + $parent_entry = ORM::factory("videos_entry", $entry->parent_id); if (!$parent_entry->loaded()) { $parent = ORM::factory("item", $task->get("item_id")); } else { $parent = ORM::factory("item", $parent_entry->item_id); } - $name = basename($entry->file); + $name = basename($entry->path); $title = item::convert_filename_to_title($name); - if (is_dir($entry->file)) { + if ($entry->is_directory) { $album = ORM::factory("item"); $album->type = "album"; $album->parent_id = $parent->id; $album->name = $name; $album->title = $title; $album->owner_id = $owner_id; + $album->sort_order = $parent->sort_order; + $album->sort_column = $parent->sort_column; $album->save(); $entry->item_id = $album->id; } else { try { $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); - if (in_array($extension, unserialize(module::get_var("videos", "allowed_extensions")))) { + if (in_array($extension, array("gif", "png", "jpg", "jpeg"))) { + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->parent_id = $parent->id; + $photo->set_data_file($entry->path); + $photo->name = $name; + $photo->title = $title; + $photo->owner_id = $owner_id; + $photo->save(); + $entry->item_id = $photo->id; + // rWatcher EDIT + //} else if (in_array($extension, array("flv", "mp4", "m4v"))) { + } else if (in_array($extension, unserialize(module::get_var("videos", "allowed_extensions")))) { $movie = ORM::factory("item"); $movie->type = "movie"; $movie->parent_id = $parent->id; - $movie->set_data_file($entry->file); + $movie->set_data_file($entry->path); $movie->name = $name; $movie->title = $title; $movie->owner_id = $owner_id; $movie->save(); $entry->item_id = $movie->id; + // rWatcher EDIT: Add record to items_video db. $items_video = ORM::factory("items_video"); $items_video->item_id = $movie->id; $items_video->save(); - if (file_exists($entry->file . ".flv")) { - copy($entry->file . ".flv", $movie->resize_path() . ".flv"); - list ($vid_width, $vid_height, $mime_type) = movie::get_file_metadata($entry->file . ".flv"); + // rWatcher EDIT: Scan for flv resizes and copy to resize directory. + if (file_exists($entry->path . ".flv")) { + copy($entry->path . ".flv", $movie->resize_path() . ".flv"); + list ($vid_width, $vid_height, $mime_type) = movie::get_file_metadata($entry->path . ".flv"); $movie->height = $vid_height; $movie->width = $vid_width; $movie->save(); @@ -266,12 +306,12 @@ class Videos_Controller extends Admin_Controller { // process. But just in, case.. set this to a non-null value so that we skip this // entry. $entry->item_id = 0; - $task->log("Skipping unknown file type: $entry->file"); + $task->log("Skipping unknown file type: {$entry->path}"); } } catch (Exception $e) { // This can happen if a photo file is invalid, like a BMP masquerading as a .jpg $entry->item_id = 0; - $task->log("Skipping invalid file: $entry->file"); + $task->log("Skipping invalid file: {$entry->file}"); } } @@ -290,12 +330,11 @@ class Videos_Controller extends Admin_Controller { $task->done = true; $task->state = "success"; $task->percent_complete = 100; - db::build() - ->delete("videos_files") + ORM::factory("videos_entry") ->where("task_id", "=", $task->id) - ->execute(); - message::info(t2("Successfully added one file", - "Successfully added %count files", + ->delete_all(); + message::info(t2("Successfully added one photo / album", + "Successfully added %count photos / albums", $task->get("completed_files"))); } } diff --git a/3.0/modules/videos/css/videos.css b/3.0/modules/videos/css/videos.css index 36746ab5..559e5481 100644 --- a/3.0/modules/videos/css/videos.css +++ b/3.0/modules/videos/css/videos.css @@ -1,23 +1,23 @@ -#g-server-add button { +#g-videos button { margin-bottom: .5em; } -#g-server-add-tree { +#g-videos-tree { cursor: pointer; padding-left: 4px; width: 95%; } -#g-server-add-tree li { +#g-videos-tree li { padding: 0; float: none; } -#g-server-add-tree span.selected { +#g-videos-tree span.selected { background: #ddd; } -#g-server-add-tree { +#g-videos-tree { border: 1px solid #ccc; height: 20em; overflow: auto; @@ -25,14 +25,14 @@ padding: .5em; } -#g-server-add ul ul li { +#g-videos ul ul li { padding-left: 1.2em; } -#g-server-add-paths li .ui-icon { +#g-videos-paths li .ui-icon { margin-top: .4em; } -#g-server-add-admin-form .textbox { +#g-videos-admin-form .textbox { width: 400px; } diff --git a/3.0/modules/videos/helpers/videos.php b/3.0/modules/videos/helpers/videos.php index 6169e038..0bb15c31 100644 --- a/3.0/modules/videos/helpers/videos.php +++ b/3.0/modules/videos/helpers/videos.php @@ -1,7 +1,7 @@ get("settings_menu") @@ -34,7 +38,7 @@ class videos_event_Core { is_writable($item->is_album() ? $item->file_path() : $item->parent()->file_path())) { $menu->get("add_menu") ->append(Menu::factory("dialog") - ->id("videos") + ->id("Videos") ->label(t("Add videos")) ->url(url::site("videos/browse/$item->id"))); } diff --git a/3.0/modules/videos/helpers/videos_installer.php b/3.0/modules/videos/helpers/videos_installer.php index 7bfca0db..cbd4922a 100644 --- a/3.0/modules/videos/helpers/videos_installer.php +++ b/3.0/modules/videos/helpers/videos_installer.php @@ -1,7 +1,7 @@ query("CREATE TABLE {videos_files} ( + $db->query("CREATE TABLE {videos_entries} ( `id` int(9) NOT NULL auto_increment, - `file` varchar(255) NOT NULL, + `checked` boolean default 0, + `is_directory` boolean default 0, `item_id` int(9), `parent_id` int(9), + `path` varchar(255) NOT NULL, `task_id` int(9) NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8;"); + + // rWatcher Edit: My Table. $db->query("CREATE TABLE {items_videos} ( `id` int(9) NOT NULL auto_increment, `item_id` int(9) NOT NULL, PRIMARY KEY (`id`), KEY (`item_id`, `id`)) DEFAULT CHARSET=utf8;"); + // rWatcher Edit: My Variable. module::set_var("videos", "allowed_extensions", serialize(array("avi", "mpg", "mpeg", "mov", "wmv", "asf", "mts"))); - module::set_version("videos", 1); + + module::set_version("videos", 4); videos::check_config(); } + static function upgrade($version) { + $db = Database::instance(); + + if ($version < 4) { + $db->query("DROP TABLE {videos_files}"); + $db->query("CREATE TABLE {videos_entries} ( + `id` int(9) NOT NULL auto_increment, + `checked` boolean default 0, + `is_directory` boolean default 0, + `item_id` int(9), + `parent_id` int(9), + `path` varchar(255) NOT NULL, + `task_id` int(9) NOT NULL, + PRIMARY KEY (`id`)) + DEFAULT CHARSET=utf8;"); + module::set_version("videos", $version = 4); + } + } + static function deactivate() { site_status::clear("videos_configuration"); } - - static function uninstall() { - $db = Database::instance(); - $db->query("DROP TABLE IF EXISTS {videos_files};"); - $db->query("DROP TABLE IF EXISTS {items_videos};"); - module::delete("videos"); - } } diff --git a/3.0/modules/videos/helpers/videos_theme.php b/3.0/modules/videos/helpers/videos_theme.php index ea143c41..a14f69a2 100644 --- a/3.0/modules/videos/helpers/videos_theme.php +++ b/3.0/modules/videos/helpers/videos_theme.php @@ -1,7 +1,7 @@ where("item_id", "=", $item->id) ->find(); - if ($items_video->loaded()) { - $view = new View("videos_display_js.html"); - //$view->embed_code = addslashes($embedded_video->embed_code); - return $buf . $view; + if (($items_video->loaded()) && (!file_exists($item->resize_path() . ".flv"))) { + $buf .= $theme->script("videos_download.js"); } } + return $buf; } static function admin_head($theme) { $buf = ""; if (strpos(Router::$current_uri, "admin/videos") !== false) { - $buf .= $theme->css("videos.css"); - $buf .= $theme->css("jquery.autocomplete.css"); + $buf .= $theme->css("videos.css") + . $theme->css("jquery.autocomplete.css"); $base = url::site("__ARGS__"); $csrf = access::csrf_token(); $buf .= ""; - $buf .= $theme->script("jquery.autocomplete.js"); - $buf .= $theme->script("admin_videos.js"); + $buf .= $theme->script("jquery.autocomplete.js") + . $theme->script("admin_videos.js"); // rWatcher edit. } return $buf; - } -} \ No newline at end of file + } +} diff --git a/3.0/modules/videos/js/admin_videos.js b/3.0/modules/videos/js/admin_videos.js index 9bb61ed1..2a4f462c 100644 --- a/3.0/modules/videos/js/admin_videos.js +++ b/3.0/modules/videos/js/admin_videos.js @@ -2,6 +2,11 @@ * Set up autocomplete on the server path list * */ +/** + * rWatcher Edit: This file used to be admin.js from server_add module. + * All occurences of server_add have been replaced with videos + * + */ $("document").ready(function() { $("#g-path").autocomplete( base_url.replace("__ARGS__", "admin/videos/autocomplete"), {max: 256}); diff --git a/3.0/modules/videos/js/videos.js b/3.0/modules/videos/js/videos.js index 02dda4c0..27627193 100644 --- a/3.0/modules/videos/js/videos.js +++ b/3.0/modules/videos/js/videos.js @@ -1,36 +1,41 @@ +/** + * rWatcher Edit: This file used to be server_add.js from server_add module. + * All occurences of server-add have been replaced with videos + * + */ (function($) { - $.widget("ui.gallery_server_add", { + $.widget("ui.gallery_videos", { _init: function() { var self = this; - $("#g-server-add-add-button", this.element).click(function(event) { + $("#g-videos-add-button", this.element).click(function(event) { event.preventDefault(); $(".g-progress-bar", this.element). progressbar(). progressbar("value", 0); - $("#g-server-add-progress", this.element).slideDown("fast", function() { self.start_add(); }); + $("#g-videos-progress", this.element).slideDown("fast", function() { self.start_add(); }); }); - $("#g-server-add-pause-button", this.element).click(function(event) { + $("#g-videos-pause-button", this.element).click(function(event) { self.pause = true; - $("#g-server-add-pause-button", this.element).hide(); - $("#g-server-add-continue-button", this.element).show(); + $("#g-videos-pause-button", this.element).hide(); + $("#g-videos-continue-button", this.element).show(); }); - $("#g-server-add-continue-button", this.element).click(function(event) { + $("#g-videos-continue-button", this.element).click(function(event) { self.pause = false; - $("#g-server-add-pause-button", this.element).show(); - $("#g-server-add-continue-button", this.element).hide(); + $("#g-videos-pause-button", this.element).show(); + $("#g-videos-continue-button", this.element).hide(); self.run_add(); }); - $("#g-server-add-close-button", this.element).click(function(event) { + $("#g-videos-close-button", this.element).click(function(event) { $("#g-dialog").dialog("close"); window.location.reload(); }); - $("#g-server-add-tree span.g-directory", this.element).dblclick(function(event) { + $("#g-videos-tree span.g-directory", this.element).dblclick(function(event) { self.open_dir(event); }); - $("#g-server-add-tree span.g-file, #g-server-add-tree span.g-directory", this.element).click(function(event) { + $("#g-videos-tree span.g-file, #g-videos-tree span.g-directory", this.element).click(function(event) { self.select_file(event); }); - $("#g-server-add-tree span.g-directory", this.element).dblclick(function(event) { + $("#g-videos-tree span.g-directory", this.element).dblclick(function(event) { self.open_dir(event); }); $("#g-dialog").bind("dialogclose", function(event, ui) { @@ -48,8 +53,8 @@ paths.push($(this).attr("ref")); }); - $("#g-server-add-add-button", this.element).hide(); - $("#g-server-add-pause-button", this.element).show(); + $("#g-videos-add-button", this.element).hide(); + $("#g-videos-pause-button", this.element).show(); $.ajax({ url: START_URL, @@ -77,10 +82,10 @@ $("#g-status").html(data.status); $(".g-progress-bar", self.element).progressbar("value", data.percent_complete); if (data.done) { - $("#g-server-add-progress", this.element).slideUp(); - $("#g-server-add-add-button", this.element).show(); - $("#g-server-add-pause-button", this.element).hide(); - $("#g-server-add-continue-button", this.element).hide(); + $("#g-videos-progress", this.element).slideUp(); + $("#g-videos-add-button", this.element).show(); + $("#g-videos-pause-button", this.element).hide(); + $("#g-videos-continue-button", this.element).hide(); } else { if (!self.pause) { setTimeout(function() { self.run_add(); }, 25); @@ -99,11 +104,11 @@ $.ajax({ url: GET_CHILDREN_URL.replace("__PATH__", path), success: function(data, textStatus) { - $("#g-server-add-tree", self.element).html(data); - $("#g-server-add-tree span.g-directory", self.element).dblclick(function(event) { + $("#g-videos-tree", self.element).html(data); + $("#g-videos-tree span.g-directory", self.element).dblclick(function(event) { self.open_dir(event); }); - $("#g-server-add-tree span.g-file, #g-server-add-tree span.g-directory", this.element).click(function(event) { + $("#g-videos-tree span.g-file, #g-videos-tree span.g-directory", this.element).click(function(event) { self.select_file(event); }); } @@ -115,10 +120,10 @@ */ select_file: function (event) { $(event.target).toggleClass("selected"); - if ($("#g-server-add span.selected").length) { - $("#g-server-add-add-button").enable(true).removeClass("ui-state-disabled"); + if ($("#g-videos span.selected").length) { + $("#g-videos-add-button").enable(true).removeClass("ui-state-disabled"); } else { - $("#g-server-add-add-button").enable(false).addClass("ui-state-disabled"); + $("#g-videos-add-button").enable(false).addClass("ui-state-disabled"); } } }); diff --git a/3.0/modules/videos/js/videos_download.js b/3.0/modules/videos/js/videos_download.js new file mode 100644 index 00000000..b0777ec2 --- /dev/null +++ b/3.0/modules/videos/js/videos_download.js @@ -0,0 +1,8 @@ +/** + * rWatcher Edit: This file is one of mine. + * + */ +$("document").ready(function() { + var original_url = document.getElementById('g-videos-full-url'); + $("#g-movie").replaceWith(""); +}); diff --git a/3.0/modules/videos/models/item.php b/3.0/modules/videos/models/item.php new file mode 100644 index 00000000..171d664c --- /dev/null +++ b/3.0/modules/videos/models/item.php @@ -0,0 +1,1030 @@ +loaded()) { + // Set reasonable defaults + $this->created = time(); + $this->rand_key = random::percent(); + $this->thumb_dirty = 1; + $this->resize_dirty = 1; + $this->sort_column = "created"; + $this->sort_order = "ASC"; + $this->owner_id = identity::active_user()->id; + } + } + + /** + * Add a set of restrictions to any following queries to restrict access only to items + * viewable by the active user. + * @chainable + */ + public function viewable() { + return item::viewable($this); + } + + /** + * Is this item an album? + * @return true if it's an album + */ + public function is_album() { + return $this->type == 'album'; + } + + /** + * Is this item a photo? + * @return true if it's a photo + */ + public function is_photo() { + return $this->type == 'photo'; + } + + /** + * Is this item a movie? + * @return true if it's a movie + */ + public function is_movie() { + return $this->type == 'movie'; + } + + public function delete($ignored_id=null) { + if (!$this->loaded()) { + // Concurrent deletes may result in this item already being gone. Ignore it. + return; + } + + if ($this->id == 1) { + $v = new Validation(array("id")); + $v->add_error("id", "cant_delete_root_album"); + ORM_Validation_Exception::handle_validation($this->table_name, $v); + } + + $old = clone $this; + module::event("item_before_delete", $this); + + $parent = $this->parent(); + if ($parent->album_cover_item_id == $this->id) { + item::remove_album_cover($parent); + } + + $path = $this->file_path(); + $resize_path = $this->resize_path(); + $thumb_path = $this->thumb_path(); + + parent::delete(); + if (is_dir($path)) { + // Take some precautions against accidentally deleting way too much + $delete_resize_path = dirname($resize_path); + $delete_thumb_path = dirname($thumb_path); + if ($delete_resize_path == VARPATH . "resizes" || + $delete_thumb_path == VARPATH . "thumbs" || + $path == VARPATH . "albums") { + throw new Exception( + "@todo DELETING_TOO_MUCH ($delete_resize_path, $delete_thumb_path, $path)"); + } + @dir::unlink($path); + @dir::unlink($delete_resize_path); + @dir::unlink($delete_thumb_path); + } else { + @unlink($path); + @unlink($resize_path); + @unlink($thumb_path); + } + + module::event("item_deleted", $old); + } + + /** + * Specify the path to the data file associated with this item. To actually associate it, + * you still have to call save(). + * @chainable + */ + public function set_data_file($data_file) { + $this->data_file = $data_file; + return $this; + } + + /** + * Return the server-relative url to this item, eg: + * /gallery3/index.php/BobsWedding?page=2 + * /gallery3/index.php/BobsWedding/Eating-Cake.jpg + * + * @param string $query the query string (eg "show=3") + */ + public function url($query=null) { + $url = url::site($this->relative_url()); + if ($query) { + $url .= "?$query"; + } + return $url; + } + + /** + * Return the full url to this item, eg: + * http://example.com/gallery3/index.php/BobsWedding?page=2 + * http://example.com/gallery3/index.php/BobsWedding/Eating-Cake.jpg + * + * @param string $query the query string (eg "show=3") + */ + public function abs_url($query=null) { + $url = url::abs_site($this->relative_url()); + if ($query) { + $url .= "?$query"; + } + return $url; + } + + /** + * album: /var/albums/album1/album2 + * photo: /var/albums/album1/album2/photo.jpg + */ + public function file_path() { + return VARPATH . "albums/" . urldecode($this->relative_path()); + } + + /** + * album: http://example.com/gallery3/var/resizes/album1/ + * photo: http://example.com/gallery3/var/albums/album1/photo.jpg + */ + public function file_url($full_uri=false) { + $relative_path = "var/albums/" . $this->relative_path(); + $cache_buster = $this->_cache_buster($this->file_path()); + return ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)) + . $cache_buster; + } + + /** + * album: /var/resizes/album1/.thumb.jpg + * photo: /var/albums/album1/photo.thumb.jpg + */ + public function thumb_path() { + $base = VARPATH . "thumbs/" . urldecode($this->relative_path()); + if ($this->is_photo()) { + return $base; + } else if ($this->is_album()) { + return $base . "/.album.jpg"; + } else if ($this->is_movie()) { + // Replace the extension with jpg + return preg_replace("/...$/", "jpg", $base); + } + } + + /** + * Return true if there is a thumbnail for this item. + */ + public function has_thumb() { + return $this->thumb_width && $this->thumb_height; + } + + /** + * album: http://example.com/gallery3/var/resizes/album1/.thumb.jpg + * photo: http://example.com/gallery3/var/albums/album1/photo.thumb.jpg + */ + public function thumb_url($full_uri=false) { + $cache_buster = $this->_cache_buster($this->thumb_path()); + $relative_path = "var/thumbs/" . $this->relative_path(); + $base = ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)); + if ($this->is_photo()) { + return $base . $cache_buster; + } else if ($this->is_album()) { + return $base . "/.album.jpg" . $cache_buster; + } else if ($this->is_movie()) { + // Replace the extension with jpg + $base = preg_replace("/...$/", "jpg", $base); + return $base . $cache_buster; + } + } + + /** + * album: /var/resizes/album1/.resize.jpg + * photo: /var/albums/album1/photo.resize.jpg + */ + public function resize_path() { + return VARPATH . "resizes/" . urldecode($this->relative_path()) . + ($this->is_album() ? "/.album.jpg" : ""); + } + + /** + * album: http://example.com/gallery3/var/resizes/album1/.resize.jpg + * photo: http://example.com/gallery3/var/albums/album1/photo.resize.jpg + */ + public function resize_url($full_uri=false) { + $relative_path = "var/resizes/" . $this->relative_path(); + $cache_buster = $this->_cache_buster($this->resize_path()); + return ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)) . + ($this->is_album() ? "/.album.jpg" : "") . $cache_buster; + } + + /** + * Rebuild the relative_path_cache and relative_url_cache. + */ + private function _build_relative_caches() { + $names = array(); + $slugs = array(); + foreach (db::build() + ->select(array("name", "slug")) + ->from("items") + ->where("left_ptr", "<=", $this->left_ptr) + ->where("right_ptr", ">=", $this->right_ptr) + ->where("id", "<>", 1) + ->order_by("left_ptr", "ASC") + ->execute() as $row) { + // Don't encode the names segment + $names[] = rawurlencode($row->name); + $slugs[] = rawurlencode($row->slug); + } + $this->relative_path_cache = implode($names, "/"); + $this->relative_url_cache = implode($slugs, "/"); + return $this; + } + + /** + * Return the relative path to this item's file. Note that the components of the path are + * urlencoded so if you want to use this as a filesystem path, you need to call urldecode + * on it. + * @return string + */ + public function relative_path() { + if (!$this->loaded()) { + return; + } + + if (!isset($this->relative_path_cache)) { + $this->_build_relative_caches()->save(); + } + return $this->relative_path_cache; + } + + /** + * Return the relative url to this item's file. + * @return string + */ + public function relative_url() { + if (!$this->loaded()) { + return; + } + + if (!isset($this->relative_url_cache)) { + $this->_build_relative_caches()->save(); + } + return $this->relative_url_cache; + } + + /** + * @see ORM::__get() + */ + public function __get($column) { + if ($column == "owner") { + // This relationship depends on an outside module, which may not be present so handle + // failures gracefully. + try { + return identity::lookup_user($this->owner_id); + } catch (Exception $e) { + return null; + } + } else { + return parent::__get($column); + } + } + + /** + * Handle any business logic necessary to create or modify an item. + * @see ORM::save() + * + * @return ORM Item_Model + */ + public function save() { + $significant_changes = $this->changed; + unset($significant_changes["view_count"]); + unset($significant_changes["relative_url_cache"]); + unset($significant_changes["relative_path_cache"]); + + if ((!empty($this->changed) && $significant_changes) || isset($this->data_file)) { + $this->updated = time(); + if (!$this->loaded()) { + // Create a new item. + module::event("item_before_create", $this); + + // Set a weight if it's missing. We don't do this in the constructor because it's not a + // simple assignment. + if (empty($this->weight)) { + $this->weight = item::get_max_weight(); + } + + // Make an url friendly slug from the name, if necessary + if (empty($this->slug)) { + $tmp = pathinfo($this->name, PATHINFO_FILENAME); + $tmp = preg_replace("/[^A-Za-z0-9-_]+/", "-", $tmp); + $this->slug = trim($tmp, "-"); + + // If the filename is all invalid characters, then the slug may be empty here. Pick a + // random value. + if (empty($this->slug)) { + $this->slug = (string)rand(1000, 9999); + } + } + + // Get the width, height and mime type from our data file for photos and movies. + if ($this->is_photo() || $this->is_movie()) { + if ($this->is_photo()) { + list ($this->width, $this->height, $this->mime_type, $extension) = + photo::get_file_metadata($this->data_file); + } else if ($this->is_movie()) { + list ($this->width, $this->height, $this->mime_type, $extension) = + movie::get_file_metadata($this->data_file); + } + + // Force an extension onto the name if necessary + $pi = pathinfo($this->data_file); + if (empty($pi["extension"])) { + $this->name = "{$this->name}.$extension"; + } + } + + $this->_randomize_name_or_slug_on_conflict(); + + parent::save(); + + // Build our url caches, then save again. We have to do this after it's already been + // saved once because we use only information from the database to build the paths. If we + // could depend on a save happening later we could defer this 2nd save. + $this->_build_relative_caches(); + parent::save(); + + // Take any actions that we can only do once all our paths are set correctly after saving. + switch ($this->type) { + case "album": + mkdir($this->file_path()); + mkdir(dirname($this->thumb_path())); + mkdir(dirname($this->resize_path())); + break; + + case "photo": + case "movie": + // The thumb or resize may already exist in the case where a movie and a photo generate + // a thumbnail of the same name (eg, foo.flv movie and foo.jpg photo will generate + // foo.jpg thumbnail). If that happens, randomize and save again. + if (file_exists($this->resize_path()) || + file_exists($this->thumb_path())) { + $pi = pathinfo($this->name); + $this->name = $pi["filename"] . "-" . random::int() . "." . $pi["extension"]; + parent::save(); + } + + copy($this->data_file, $this->file_path()); + break; + } + + // This will almost definitely trigger another save, so put it at the end so that we're + // tail recursive. Null out the data file variable first, otherwise the next save will + // trigger an item_updated_data_file event. + $this->data_file = null; + module::event("item_created", $this); + } else { + // Update an existing item + module::event("item_before_update", $item); + + // If any significant fields have changed, load up a copy of the original item and + // keep it around. + $original = ORM::factory("item", $this->id); + if (array_intersect($this->changed, array("parent_id", "name", "slug"))) { + $original->_build_relative_caches(); + $this->relative_path_cache = null; + $this->relative_url_cache = null; + } + + $this->_randomize_name_or_slug_on_conflict(); + + parent::save(); + + // Now update the filesystem and any database caches if there were significant value + // changes. If anything past this point fails, then we'll have an inconsistent database + // so this code should be as robust as we can make it. + + // Update the MPTT pointers, if necessary. We have to do this before we generate any + // cached paths! + if ($original->parent_id != $this->parent_id) { + parent::move_to($this->parent()); + } + + if ($original->parent_id != $this->parent_id || $original->name != $this->name) { + // Move all of the items associated data files + @rename($original->file_path(), $this->file_path()); + if ($this->is_album()) { + @rename(dirname($original->resize_path()), dirname($this->resize_path())); + @rename(dirname($original->thumb_path()), dirname($this->thumb_path())); + } else { + @rename($original->resize_path(), $this->resize_path()); + @rename($original->thumb_path(), $this->thumb_path()); + } + + if ($original->parent_id != $this->parent_id) { + // This will result in 2 events since we'll still fire the item_updated event below + module::event("item_moved", $this, $original->parent()); + } + } + + // Changing the name, slug or parent ripples downwards + if ($this->is_album() && + ($original->name != $this->name || + $original->slug != $this->slug || + $original->parent_id != $this->parent_id)) { + db::build() + ->update("items") + ->set("relative_url_cache", null) + ->set("relative_path_cache", null) + ->where("left_ptr", ">", $this->left_ptr) + ->where("right_ptr", "<", $this->right_ptr) + ->execute(); + } + + // Replace the data file, if requested. + // @todo: we don't handle the case where you swap in a file of a different mime type + // should we prevent that in validation? or in set_data_file() + if ($this->data_file && ($this->is_photo() || $this->is_movie())) { + copy($this->data_file, $this->file_path()); + + // Get the width, height and mime type from our data file for photos and movies. + if ($this->is_photo()) { + list ($this->width, $this->height) = photo::get_file_metadata($this->file_path()); + } else if ($this->is_movie()) { + list ($this->width, $this->height) = movie::get_file_metadata($this->file_path()); + } + $this->thumb_dirty = 1; + $this->resize_dirty = 1; + } + + module::event("item_updated", $original, $this); + + if ($this->data_file) { + // Null out the data file variable here, otherwise this event will trigger another + // save() which will think that we're doing another file move. + $this->data_file = null; + module::event("item_updated_data_file", $this); + } + } + } else if (!empty($this->changed)) { + // Insignificant changes only. Don't fire events or do any special checking to try to keep + // this lightweight. + parent::save(); + } + + return $this; + } + + /** + * Check to see if there's another item that occupies the same name or slug that this item + * intends to use, and if so choose a new name/slug while preserving the extension. + * @todo Improve this. Random numbers are not user friendly + */ + private function _randomize_name_or_slug_on_conflict() { + $base_name = pathinfo($this->name, PATHINFO_FILENAME); + $base_ext = pathinfo($this->name, PATHINFO_EXTENSION); + $base_slug = $this->slug; + while (ORM::factory("item") + ->where("parent_id", "=", $this->parent_id) + ->where("id", $this->id ? "<>" : "IS NOT", $this->id) + ->and_open() + ->where("name", "=", $this->name) + ->or_where("slug", "=", $this->slug) + ->close() + ->find()->id) { + $rand = random::int(); + if ($base_ext) { + $this->name = "$base_name-$rand.$base_ext"; + } else { + $this->name = "$base_name-$rand"; + } + $this->slug = "$base_slug-$rand"; + } + } + + /** + * Return the Item_Model representing the cover for this album. + * @return Item_Model or null if there's no cover + */ + public function album_cover() { + if (!$this->is_album()) { + return null; + } + + if (empty($this->album_cover_item_id)) { + return null; + } + + try { + return model_cache::get("item", $this->album_cover_item_id); + } catch (Exception $e) { + // It's possible (unlikely) that the item was deleted, if so keep going. + return null; + } + } + + /** + * Find the position of the given child id in this album. The resulting value is 1-indexed, so + * the first child in the album is at position 1. + * + * This method stands as a backward compatibility for gallery 3.0, and will + * be deprecated in version 3.1. + */ + public function get_position($child, $where=array()) { + return item::get_position($child, $where); + } + + /** + * Return an tag for the thumbnail. + * @param array $extra_attrs Extra attributes to add to the img tag + * @param int (optional) $max Maximum size of the thumbnail (default: null) + * @param boolean (optional) $center_vertically Center vertically (default: false) + * @return string + */ + public function thumb_img($extra_attrs=array(), $max=null, $center_vertically=false) { + list ($height, $width) = $this->scale_dimensions($max); + if ($center_vertically && $max) { + // The constant is divide by 2 to calculate the file and 10 to convert to em + $margin_top = (int)(($max - $height) / 20); + $extra_attrs["style"] = "margin-top: {$margin_top}em"; + $extra_attrs["title"] = $this->title; + } + $attrs = array_merge($extra_attrs, + array( + "src" => $this->thumb_url(), + "alt" => $this->title, + "width" => $width, + "height" => $height) + ); + // html::image forces an absolute url which we don't want + return ""; + } + + /** + * Calculate the largest width/height that fits inside the given maximum, while preserving the + * aspect ratio. Don't upscale. + * @param int $max Maximum size of the largest dimension + * @return array + */ + public function scale_dimensions($max) { + $width = $this->thumb_width; + $height = $this->thumb_height; + + if ($width <= $max && $height <= $max) { + return array($height, $width); + } + + if ($height) { + if (isset($max)) { + if ($width > $height) { + $height = (int)($max * $height / $width); + $width = $max; + } else { + $width = (int)($max * $width / $height); + $height = $max; + } + } + } else { + // Missing thumbnail, can happen on albums with no photos yet. + // @todo we should enforce a placeholder for those albums. + $width = 0; + $height = 0; + } + return array($height, $width); + } + + /** + * Return an tag for the resize. + * @param array $extra_attrs Extra attributes to add to the img tag + * @return string + */ + public function resize_img($extra_attrs) { + $attrs = array_merge($extra_attrs, + array("src" => $this->resize_url(), + "alt" => $this->title, + "width" => $this->resize_width, + "height" => $this->resize_height) + ); + // html::image forces an absolute url which we don't want + return ""; + } + + /** + * Return a flowplayer - diff --git a/3.0/modules/videos/views/videos_tree.html.php b/3.0/modules/videos/views/videos_tree.html.php index 91354329..366d4fb4 100644 --- a/3.0/modules/videos/views/videos_tree.html.php +++ b/3.0/modules/videos/views/videos_tree.html.php @@ -1,4 +1,5 @@ +
  • diff --git a/3.0/modules/videos/views/videos_tree_dialog.html.php b/3.0/modules/videos/views/videos_tree_dialog.html.php index a235ffbf..a0c0f7b7 100644 --- a/3.0/modules/videos/views/videos_tree_dialog.html.php +++ b/3.0/modules/videos/views/videos_tree_dialog.html.php @@ -1,13 +1,14 @@ + -
    -

    html::purify($item->title))) ?>

    +
    +

    html::purify($item->title))) ?>

    -

    +

      parents() as $parent): ?> @@ -17,35 +18,35 @@
    • title) ?>
    -
      +
      -