diff options
Diffstat (limited to 'modules/gallery')
29 files changed, 484 insertions, 134 deletions
diff --git a/modules/gallery/controllers/admin.php b/modules/gallery/controllers/admin.php index e4216991..7706e9fc 100644 --- a/modules/gallery/controllers/admin.php +++ b/modules/gallery/controllers/admin.php @@ -21,7 +21,7 @@ class Admin_Controller extends Controller { private $theme; public function __construct($theme=null) { - if (!(identity::active_user()->admin)) { + if (!identity::active_user()->admin) { access::forbidden(); } @@ -29,6 +29,10 @@ class Admin_Controller extends Controller { } public function __call($controller_name, $args) { + if (auth::must_reauth_for_admin_area()) { + return self::_prompt_for_reauth($controller_name, $args); + } + if (request::method() == "post") { access::verify_csrf(); } @@ -49,5 +53,13 @@ class Admin_Controller extends Controller { call_user_func_array(array(new $controller_name, $method), $args); } + + private static function _prompt_for_reauth($controller_name, $args) { + if (request::method() == "get" && !request::is_ajax()) { + // Avoid anti-phishing protection by passing the url as session variable. + Session::instance()->set("continue_url", url::current(true)); + } + url::redirect("reauthenticate"); + } } diff --git a/modules/gallery/controllers/admin_maintenance.php b/modules/gallery/controllers/admin_maintenance.php index 8e4845a9..d90fe0ea 100644 --- a/modules/gallery/controllers/admin_maintenance.php +++ b/modules/gallery/controllers/admin_maintenance.php @@ -46,15 +46,6 @@ class Admin_Maintenance_Controller extends Admin_Controller { ->where("done", "=", 0)->order_by("updated", "DESC")->find_all(); $view->content->finished_tasks = ORM::factory("task") ->where("done", "=", 1)->order_by("updated", "DESC")->find_all(); - $task_buttons = - new ArrayObject(array((object)array("text" => t("run"), - "url" =>url::site("admin/maintenance/start")))); - module::event("admin_maintenance_task_buttons", $task_buttons); - $view->content->task_buttons = $task_buttons; - - $maintenance_content = new ArrayObject(); - module::event("admin_maintenance_content", $maintenance_content); - $view->content->task_maintenance_content = $maintenance_content; print $view; } diff --git a/modules/gallery/controllers/albums.php b/modules/gallery/controllers/albums.php index a378f3ee..e1985cfb 100644 --- a/modules/gallery/controllers/albums.php +++ b/modules/gallery/controllers/albums.php @@ -28,20 +28,13 @@ class Albums_Controller extends Items_Controller { // sure that we're actually receiving an object Kohana::show_404(); } - $page_size = module::get_var("gallery", "page_size", 9); + if (!access::can("view", $album)) { - if ($album->id == 1) { - $view = new Theme_View("page.html", "other", "login"); - $view->page_title = t("Log in to Gallery"); - $view->content = new View("login_ajax.html"); - $view->content->form = auth::get_login_form("login/auth_html"); - print $view; - return; - } else { - access::forbidden(); - } + print auth::require_login(); + return; } + $page_size = module::get_var("gallery", "page_size", 9); $input = Input::instance(); $show = $input->get("show"); diff --git a/modules/gallery/controllers/login.php b/modules/gallery/controllers/login.php index 1426f0d8..093c15da 100644 --- a/modules/gallery/controllers/login.php +++ b/modules/gallery/controllers/login.php @@ -44,9 +44,10 @@ class Login_Controller extends Controller { public function auth_html() { access::verify_csrf(); + $continue_url = Session::instance()->get("continue_url", null); list ($valid, $form) = $this->_auth("login/auth_html"); if ($valid) { - url::redirect(item::root()->abs_url()); + url::redirect($continue_url ? $continue_url : item::root()->abs_url()); } else { $view = new Theme_View("page.html", "other", "login"); $view->page_title = t("Log in to Gallery"); @@ -65,7 +66,7 @@ class Login_Controller extends Controller { $form->login->inputs["name"]->add_error("invalid_login", 1); $name = $form->login->inputs["name"]->value; log::warning("user", t("Failed login for %name", array("name" => $name))); - module::event("user_login_failed", $name); + module::event("user_auth_failed", $name); $valid = false; } } diff --git a/modules/gallery/controllers/movies.php b/modules/gallery/controllers/movies.php index b51282b3..8041066e 100644 --- a/modules/gallery/controllers/movies.php +++ b/modules/gallery/controllers/movies.php @@ -24,7 +24,11 @@ class Movies_Controller extends Items_Controller { // sure that we're actually receiving an object Kohana::show_404(); } - access::required("view", $movie); + + if (!access::can("view", $movie)) { + print auth::require_login(); + return; + } $where = array(array("type", "!=", "album")); $position = $movie->parent()->get_position($movie, $where); diff --git a/modules/gallery/controllers/photos.php b/modules/gallery/controllers/photos.php index b5da3884..778e9ae7 100644 --- a/modules/gallery/controllers/photos.php +++ b/modules/gallery/controllers/photos.php @@ -24,7 +24,11 @@ class Photos_Controller extends Items_Controller { // sure that we're actually receiving an object Kohana::show_404(); } - access::required("view", $photo); + + if (!access::can("view", $photo)) { + print auth::require_login(); + return; + } $where = array(array("type", "!=", "album")); $position = $photo->parent()->get_position($photo, $where); diff --git a/modules/gallery/controllers/reauthenticate.php b/modules/gallery/controllers/reauthenticate.php new file mode 100644 index 00000000..dbd1cd21 --- /dev/null +++ b/modules/gallery/controllers/reauthenticate.php @@ -0,0 +1,73 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2009 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 + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Reauthenticate_Controller extends Controller { + public function index($share_translations_form=null) { + if (!identity::active_user()->admin) { + access::forbidden(); + } + return self::_show_form(self::_form()); + } + + public function auth() { + if (!identity::active_user()->admin) { + access::forbidden(); + } + access::verify_csrf(); + + $form = self::_form(); + $valid = $form->validate(); + $user = identity::active_user(); + if ($valid) { + message::success(t("Successfully re-authenticated!")); + module::event("user_auth", $user); + $continue_url = Session::instance()->get_once("continue_url", "admin"); + url::redirect($continue_url); + } else { + $name = $user->name; + log::warning("user", t("Failed re-authentication for %name", array("name" => $name))); + module::event("user_auth_failed", $name); + return self::_show_form($form); + } + } + + private static function _show_form($form) { + $view = new Theme_View("page.html", "other", "reauthenticate"); + $view->page_title = t("Re-authenticate"); + $view->content = new View("reauthenticate.html"); + $view->content->form = $form; + $view->content->user_name = identity::active_user()->name; + print $view; + } + + private static function _form() { + $form = new Forge("reauthenticate/auth", "", "post", array("id" => "g-reauthenticate-form")); + $form->set_attr('class', "g-narrow"); + $group = $form->group("reauthenticate")->label(t("Re-authenticate")); + $group->password("password")->label(t("Password"))->id("g-password")->class(null) + ->callback("auth::validate_too_many_failed_auth_attempts") + ->callback("user::valid_password") + ->error_messages("invalid", t("Incorrect password")) + ->error_messages( + "too_many_failed_auth_attempts", + t("Too many incorrect passwords. Try again later")); + $group->submit("")->value(t("Submit")); + return $form; + } +} diff --git a/modules/gallery/css/upgrader.css b/modules/gallery/css/upgrader.css index 73da0ff4..2b3b5afd 100644 --- a/modules/gallery/css/upgrader.css +++ b/modules/gallery/css/upgrader.css @@ -26,6 +26,12 @@ div#footer { margin: 1em; } +table { + width: 600px; + margin-bottom: 10px; +} + +th.name, td.name { text-align: left; padding-left: 30px; @@ -53,11 +59,6 @@ tr.upgradeable td.gallery { color: #00d; } -table { - width: 600px; - margin-bottom: 10px; -} - p { font-size: .9em; } @@ -125,3 +126,31 @@ pre { margin: 0px; padding: 0px; } + +.rtl { + direction: rtl; +} + +.rtl th.name, +.rtl td.name { + text-align: right; + padding-right: 30px; +} + + +.rtl li:before { + content: ""; +} + +.rtl li:after { + content: "\00BB \0020"; +} + +.rtl ul { + margin-right: 0; + padding-right: 0; +} + +.rtl div#dialog a.close { + float: left; +} diff --git a/modules/gallery/helpers/auth.php b/modules/gallery/helpers/auth.php index 717cf40a..f5454f85 100644 --- a/modules/gallery/helpers/auth.php +++ b/modules/gallery/helpers/auth.php @@ -20,7 +20,7 @@ class auth_Core { static function get_login_form($url) { $form = new Forge($url, "", "post", array("id" => "g-login-form")); - $form->set_attr('class', "g-narrow"); + $form->set_attr("class", "g-narrow"); $group = $form->group("login")->label(t("Login")); $group->input("name")->label(t("Username"))->id("g-username")->class(null) ->callback("auth::validate_too_many_failed_logins") @@ -60,52 +60,87 @@ class auth_Core { } /** - * After there have been 5 failed login attempts, any failure leads to getting locked out for a + * After there have been 5 failed auth attempts, any failure leads to getting locked out for a * minute. */ - static function too_many_failed_logins($name) { - $failed_login = ORM::factory("failed_login") + static function too_many_failures($name) { + $failed = ORM::factory("failed_auth") ->where("name", "=", $name) ->find(); - return ($failed_login->loaded() && - $failed_login->count > 5 && - (time() - $failed_login->time < 60)); + return ($failed->loaded() && + $failed->count > 5 && + (time() - $failed->time < 60)); } static function validate_too_many_failed_logins($name_input) { - if (self::too_many_failed_logins($name_input->value)) { + if (self::too_many_failures($name_input->value)) { $name_input->add_error("too_many_failed_logins", 1); } } - static function validate_too_many_failed_password_changes($password_input) { - if (self::too_many_failed_logins(identity::active_user()->name)) { - $password_input->add_error("too_many_failed_password_changes", 1); + static function validate_too_many_failed_auth_attempts($form_input) { + if (self::too_many_failures(identity::active_user()->name)) { + $form_input->add_error("too_many_failed_auth_attempts", 1); } } /** - * Record a failed login for this user + * Record a failed authentication for this user */ - static function record_failed_auth_attempts($name) { - $failed_login = ORM::factory("failed_login") + static function record_failed_attempt($name) { + $failed = ORM::factory("failed_auth") ->where("name", "=", $name) ->find(); - if (!$failed_login->loaded()) { - $failed_login->name = $name; + if (!$failed->loaded()) { + $failed->name = $name; } - $failed_login->time = time(); - $failed_login->count++; - $failed_login->save(); + $failed->time = time(); + $failed->count++; + $failed->save(); } /** * Clear any failed logins for this user */ - static function clear_failed_logins($user) { - db::build() - ->delete("failed_logins") + static function clear_failed_attempts($user) { + ORM::factory("failed_auth") ->where("name", "=", $user->name) - ->execute(); + ->delete_all(); + } + + /** + * Checks whether the current user (= admin) must + * actively re-authenticate before access is given + * to the admin area. + */ + static function must_reauth_for_admin_area() { + if (!identity::active_user()->admin) { + access::forbidden(); + } + + $session = Session::instance(); + $last_active_auth = $session->get("active_auth_timestamp", 0); + $last_admin_area_activity = $session->get("admin_area_activity_timestamp", 0); + $admin_area_timeout = module::get_var("gallery", "admin_area_timeout"); + + if (max($last_active_auth, $last_admin_area_activity) + $admin_area_timeout < time()) { + return true; + } + + $session->set("admin_area_activity_timestamp", time()); + return false; + } + + /** + * Redirect to the login page. + */ + static function require_login() { + $view = new Theme_View("page.html", "other", "login"); + $view->page_title = t("Log in to Gallery"); + $view->content = new View("login_ajax.html"); + $view->content->form = auth::get_login_form("login/auth_html"); + // Avoid anti-phishing protection by passing the url as session variable. + Session::instance()->set("continue_url", url::current(true)); + return $view; } }
\ No newline at end of file diff --git a/modules/gallery/helpers/gallery_block.php b/modules/gallery/helpers/gallery_block.php index be0f11b8..46742743 100644 --- a/modules/gallery/helpers/gallery_block.php +++ b/modules/gallery/helpers/gallery_block.php @@ -70,7 +70,7 @@ class gallery_block_Core { $block->css_id = "g-platform"; $block->title = t("Platform information"); $block->content = new View("admin_block_platform.html"); - if (is_readable("/proc/loadavg")) { + if (@is_readable("/proc/loadavg")) { $block->content->load_average = join(" ", array_slice(explode(" ", current(file("/proc/loadavg"))), 0, 3)); } else { diff --git a/modules/gallery/helpers/gallery_event.php b/modules/gallery/helpers/gallery_event.php index 7b538c49..63f33c12 100644 --- a/modules/gallery/helpers/gallery_event.php +++ b/modules/gallery/helpers/gallery_event.php @@ -110,19 +110,17 @@ class gallery_event_Core { graphics::choose_default_toolkit(); module::clear_var("gallery", "choose_default_tookit"); } - auth::clear_failed_auth_attempts($user); + Session::instance()->set("active_auth_timestamp", time()); + auth::clear_failed_attempts($user); } - static function user_login_failed($name) { - auth::record_failed_auth_attempts($name); + static function user_auth_failed($name) { + auth::record_failed_attempt($name); } - static function user_password_changed($user) { - auth::clear_failed_auth_attempts($user); - } - - static function user_password_change_failed($name) { - auth::record_failed_auth_attempts($name); + static function user_auth($user) { + auth::clear_failed_attempts($user); + Session::instance()->set("active_auth_timestamp", time()); } static function item_index_data($item, $data) { diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php index 761843b0..dd53cf43 100644 --- a/modules/gallery/helpers/gallery_installer.php +++ b/modules/gallery/helpers/gallery_installer.php @@ -42,7 +42,7 @@ class gallery_installer { KEY (`tags`)) DEFAULT CHARSET=utf8;"); - $db->query("CREATE TABLE {failed_logins} ( + $db->query("CREATE TABLE {failed_auths} ( `id` int(9) NOT NULL auto_increment, `count` int(9) NOT NULL, `name` varchar(255) NOT NULL, @@ -287,7 +287,8 @@ class gallery_installer { // @todo this string needs to be picked up by l10n_scanner module::set_var("gallery", "credits", "Powered by <a href=\"%url\">Gallery %version</a>"); module::set_var("gallery", "simultaneous_upload_limit", 5); - module::set_version("gallery", 26); + module::set_var("gallery", "admin_area_timeout", 90 * 60); + module::set_version("gallery", 28); } static function upgrade($version) { @@ -526,6 +527,17 @@ class gallery_installer { ->execute(); module::set_version("gallery", $version = 26); } + + if ($version == 26) { + $db->query("RENAME TABLE {failed_logins} TO {failed_auths}"); + module::set_version("gallery", $version = 27); + } + + if ($version == 27) { + // Set the admin area timeout to 90 minutes + module::set_var("gallery", "admin_area_timeout", 90 * 60); + module::set_version("gallery", $version = 28); + } } static function uninstall() { @@ -534,7 +546,7 @@ class gallery_installer { $db->query("DROP TABLE IF EXISTS {access_intents}"); $db->query("DROP TABLE IF EXISTS {graphics_rules}"); $db->query("DROP TABLE IF EXISTS {incoming_translations}"); - $db->query("DROP TABLE IF EXISTS {failed_logins}"); + $db->query("DROP TABLE IF EXISTS {failed_auths}"); $db->query("DROP TABLE IF EXISTS {items}"); $db->query("DROP TABLE IF EXISTS {logs}"); $db->query("DROP TABLE IF EXISTS {modules}"); diff --git a/modules/gallery/helpers/gallery_theme.php b/modules/gallery/helpers/gallery_theme.php index 0018fd9a..9ffeb911 100644 --- a/modules/gallery/helpers/gallery_theme.php +++ b/modules/gallery/helpers/gallery_theme.php @@ -90,6 +90,17 @@ class gallery_theme_Core { $profiler = new Profiler(); $profiler->render(); } + + // Redirect to the root album when the admin session expires. + $redirect_url = url::abs_site(""); + $admin_area_timeout = 1000 * module::get_var("gallery", "admin_area_timeout"); + $admin_session_redirect_check = '<script type="text/javascript"> + var page_loaded_timestamp = new Date(); + setInterval("if (new Date() - page_loaded_timestamp > ' . $admin_area_timeout . + ') document.location = \'' . $redirect_url . '\';", 60 * 1000); + </script>'; + print $admin_session_redirect_check; + if ($session->get("l10n_mode", false)) { return L10n_Client_Controller::l10n_form(); } diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php index 41d49ce9..36193071 100644 --- a/modules/gallery/helpers/item.php +++ b/modules/gallery/helpers/item.php @@ -40,7 +40,56 @@ class item_Core { } $source->parent_id = $target->id; - $source->save(); + + // 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; + + 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; + } + } + 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; + } + } + } // If the target has no cover item, make this it. if ($target->album_cover_item_id == null) { diff --git a/modules/gallery/helpers/locales.php b/modules/gallery/helpers/locales.php index dc32b12f..e72d7ed9 100644 --- a/modules/gallery/helpers/locales.php +++ b/modules/gallery/helpers/locales.php @@ -63,50 +63,50 @@ class locales_Core { // @todo Might want to add a localizable language name as well. private static function _init_language_data() { - $l["af_ZA"] = "Afrikaans"; // Afrikaans - $l["ar_SA"] = "العربية"; // Arabic - $l["be_BY"] = "Беларускі"; // Belarusian - $l["bg_BG"] = "Български"; // Bulgarian - $l["ca_ES"] = "Catalan"; // Catalan - $l["cs_CZ"] = "Česky"; // Czech - $l["da_DK"] = "Dansk"; // Danish - $l["de_DE"] = "Deutsch"; // German - $l["el_GR"] = "Greek"; // Greek - $l["en_GB"] = "English (UK)"; // English (UK) - $l["en_US"] = "English (US)"; // English (US) - $l["es_AR"] = "Español (AR)"; // Spanish (AR) - $l["es_ES"] = "Español"; // Spanish (ES) - $l["es_MX"] = "Español (MX)"; // Spanish (MX) - $l["et_EE"] = "Eesti"; // Estonian - $l["eu_ES"] = "Euskara"; // Basque - $l["fa_IR"] = "فارسي"; // Farsi - $l["fi_FI"] = "Suomi"; // Finnish - $l["fr_FR"] = "Français"; // French - $l["ga_IE"] = "Gaeilge"; // Irish - $l["he_IL"] = "עברית"; // Hebrew - $l["hu_HU"] = "Magyar"; // Hungarian - $l["is_IS"] = "Icelandic"; // Icelandic - $l["it_IT"] = "Italiano"; // Italian - $l["ja_JP"] = "日本語"; // Japanese - $l["ko_KR"] = "한국말"; // Korean - $l["lt_LT"] = "Lietuvių"; // Lithuanian - $l["lv_LV"] = "Latviešu"; // Latvian - $l["nl_NL"] = "Nederlands"; // Dutch - $l["no_NO"] = "Norsk bokmål"; // Norwegian - $l["pl_PL"] = "Polski"; // Polish - $l["pt_BR"] = "Português Brasileiro"; // Portuguese (BR) - $l["pt_PT"] = "Português"; // Portuguese (PT) - $l["ro_RO"] = "Română"; // Romanian - $l["ru_RU"] = "Русский"; // Russian - $l["sk_SK"] = "Slovenčina"; // Slovak - $l["sl_SI"] = "Slovenščina"; // Slovenian - $l["sr_CS"] = "Srpski"; // Serbian - $l["sv_SE"] = "Svenska"; // Swedish - $l["tr_TR"] = "Türkçe"; // Turkish - $l["uk_UA"] = "УкÑаÑнÑÑка"; // Ukrainian - $l["vi_VN"] = "Tiếng Việt"; // Vietnamese - $l["zh_CN"] = "简体中文"; // Chinese (CN) - $l["zh_TW"] = "繁體中文"; // Chinese (TW) + $l["af_ZA"] = "Afrikaans"; // Afrikaans + $l["ar_SA"] = "العربية"; // Arabic + $l["be_BY"] = "Беларускі"; // Belarusian + $l["bg_BG"] = "български"; // Bulgarian + $l["ca_ES"] = "Catalan"; // Catalan + $l["cs_CZ"] = "čeština"; // Czech + $l["da_DK"] = "Dansk"; // Danish + $l["de_DE"] = "Deutsch"; // German + $l["el_GR"] = "Greek"; // Greek + $l["en_GB"] = "English (UK)"; // English (UK) + $l["en_US"] = "English (US)"; // English (US) + $l["es_AR"] = "Español (AR)"; // Spanish (AR) + $l["es_ES"] = "Español"; // Spanish (ES) + $l["es_MX"] = "Español (MX)"; // Spanish (MX) + $l["et_EE"] = "Eesti"; // Estonian + $l["eu_ES"] = "Euskara"; // Basque + $l["fa_IR"] = "فارس"; // Farsi + $l["fi_FI"] = "Suomi"; // Finnish + $l["fr_FR"] = "Français"; // French + $l["ga_IE"] = "Gaeilge"; // Irish + $l["he_IL"] = "עברית"; // Hebrew + $l["hu_HU"] = "Magyar"; // Hungarian + $l["is_IS"] = "Icelandic"; // Icelandic + $l["it_IT"] = "Italiano"; // Italian + $l["ja_JP"] = "日本語"; // Japanese + $l["ko_KR"] = "한국어"; // Korean + $l["lt_LT"] = "Lietuvių"; // Lithuanian + $l["lv_LV"] = "Latviešu"; // Latvian + $l["nl_NL"] = "Nederlands"; // Dutch + $l["no_NO"] = "Norsk bokmål"; // Norwegian + $l["pl_PL"] = "Polski"; // Polish + $l["pt_BR"] = "Português do Brasil"; // Portuguese (BR) + $l["pt_PT"] = "Português ibérico"; // Portuguese (PT) + $l["ro_RO"] = "Română"; // Romanian + $l["ru_RU"] = "Русский"; // Russian + $l["sk_SK"] = "Slovenčina"; // Slovak + $l["sl_SI"] = "Slovenščina"; // Slovenian + $l["sr_CS"] = "Srpski"; // Serbian + $l["sv_SE"] = "Svenska"; // Swedish + $l["tr_TR"] = "Türkçe"; // Turkish + $l["uk_UA"] = "українська"; // Ukrainian + $l["vi_VN"] = "Tiếng Việt"; // Vietnamese + $l["zh_CN"] = "简体中文"; // Chinese (CN) + $l["zh_TW"] = "繁體中文"; // Chinese (TW) asort($l, SORT_LOCALE_STRING); self::$locales = $l; @@ -131,9 +131,7 @@ class locales_Core { } static function is_rtl($locale=null) { - $locale or $locale = Gallery_I18n::instance()->locale(); - list ($language, $territory) = explode('_', $locale . "_"); - return in_array($language, array("he", "fa", "ar")); + return Gallery_I18n::instance()->is_rtl($locale); } /** diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php index 7033b7da..3c494e96 100644 --- a/modules/gallery/helpers/movie.php +++ b/modules/gallery/helpers/movie.php @@ -84,6 +84,20 @@ class movie_Core { " -an -ss 00:00:03 -an -r 1 -vframes 1" . " -y -f mjpeg " . escapeshellarg($output_file) . " 2>&1"; exec($cmd); + + clearstatcache(); // use $filename parameter when PHP_version is 5.3+ + if (filesize($output_file) == 0) { + // Maybe the movie is shorter, fall back to the first frame. + $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($input_file) . + " -an -an -r 1 -vframes 1" . + " -y -f mjpeg " . escapeshellarg($output_file) . " 2>&1"; + exec($cmd); + + clearstatcache(); + if (filesize($output_file) == 0) { + throw new Exception("@todo FFMPEG_FAILED"); + } + } } static function find_ffmpeg() { diff --git a/modules/gallery/libraries/Gallery_I18n.php b/modules/gallery/libraries/Gallery_I18n.php index 4e0c1f82..cfed046a 100644 --- a/modules/gallery/libraries/Gallery_I18n.php +++ b/modules/gallery/libraries/Gallery_I18n.php @@ -87,6 +87,16 @@ class Gallery_I18n_Core { return $this->_config['default_locale']; } + public function is_rtl($locale=null) { + $is_rtl = !empty($this->_config["force_rtl"]); + if (empty($is_rtl)) { + $locale or $locale = $this->locale(); + list ($language, $territory) = explode('_', $locale . "_"); + $is_rtl = in_array($language, array("he", "fa", "ar")); + } + return $is_rtl; + } + /** * Translates a localizable message. * diff --git a/modules/gallery/libraries/MY_Database.php b/modules/gallery/libraries/MY_Database.php index e2ef68cd..cb70104a 100644 --- a/modules/gallery/libraries/MY_Database.php +++ b/modules/gallery/libraries/MY_Database.php @@ -54,11 +54,18 @@ abstract class Database extends Database_Core { */ return $sql; } else if (strpos($sql, "CREATE TABLE") === 0) { - // Creating a new table add it to the table cache. + // Creating a new table; add it to the table cache. $open_brace = strpos($sql, "{") + 1; $close_brace = strpos($sql, "}", $open_brace); $name = substr($sql, $open_brace, $close_brace - $open_brace); $this->_table_names["{{$name}}"] = "{$prefix}$name"; + } else if (strpos($sql, "RENAME TABLE") === 0) { + // Renaming a table; add it to the table cache. + // You must use the form "TO {new_table_name}" exactly for this to work. + $open_brace = strpos($sql, "TO {") + 4; + $close_brace = strpos($sql, "}", $open_brace); + $name = substr($sql, $open_brace, $close_brace - $open_brace); + $this->_table_names["{{$name}}"] = "{$prefix}$name"; } if (!isset($this->_table_names)) { diff --git a/modules/gallery/libraries/MY_Input.php b/modules/gallery/libraries/MY_Input.php new file mode 100644 index 00000000..dce569fd --- /dev/null +++ b/modules/gallery/libraries/MY_Input.php @@ -0,0 +1,31 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2009 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 + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Input extends Input_Core { + /** + * Modified form of Input::clean_input_keys() that replaces malformed values + * instead of dying on bad input. + * + * @param string string to clean + * @return string + */ + public function clean_input_keys($str) { + return preg_replace('#^[\pL0-9:_.-]++$#uD', '_', $str); + } +} diff --git a/modules/gallery/models/failed_login.php b/modules/gallery/models/failed_auth.php index 0b84c295..3c25f9d8 100644 --- a/modules/gallery/models/failed_login.php +++ b/modules/gallery/models/failed_auth.php @@ -17,4 +17,4 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ -class Failed_Login_Model extends ORM {} +class Failed_Auth_Model extends ORM {} diff --git a/modules/gallery/module.info b/modules/gallery/module.info index fd241066..ae300399 100644 --- a/modules/gallery/module.info +++ b/modules/gallery/module.info @@ -1,3 +1,3 @@ name = "Gallery 3" description = "Gallery core application" -version = 26 +version = 28 diff --git a/modules/gallery/tests/Database_Test.php b/modules/gallery/tests/Database_Test.php index 861f7bba..730785e2 100644 --- a/modules/gallery/tests/Database_Test.php +++ b/modules/gallery/tests/Database_Test.php @@ -130,6 +130,13 @@ class Database_Test extends Gallery_Unit_Test_Case { $this->assert_same($expected, $sql); } + function prefix_replacement_for_rename_table_test() { + $db = Database::instance("mock"); + $this->assert_same( + "RENAME TABLE g_test TO g_new_test", + $db->add_table_prefixes("RENAME TABLE {test} TO {new_test}")); + } + function prefix_no_replacement_test() { $sql = db::build("mock") ->from("test_tables") diff --git a/modules/gallery/tests/File_Structure_Test.php b/modules/gallery/tests/File_Structure_Test.php index 4590e95d..9b2b1480 100644 --- a/modules/gallery/tests/File_Structure_Test.php +++ b/modules/gallery/tests/File_Structure_Test.php @@ -195,7 +195,7 @@ class File_Structure_Test extends Gallery_Unit_Test_Case { foreach ($dir as $file) { $file_as_string = file_get_contents($file); if (preg_match('/\t/', $file_as_string)) { - foreach (split("\n", $file_as_string) as $l => $line) { + foreach (explode("\n", $file_as_string) as $l => $line) { if (preg_match('/\t/', $line)) { $errors[] = "$file:$l has tab(s) ($line)"; } diff --git a/modules/gallery/tests/Item_Helper_Test.php b/modules/gallery/tests/Item_Helper_Test.php index cdbdd324..50587702 100644 --- a/modules/gallery/tests/Item_Helper_Test.php +++ b/modules/gallery/tests/Item_Helper_Test.php @@ -19,6 +19,10 @@ */ class Item_Helper_Test extends Gallery_Unit_Test_Case { + public function setup() { + identity::set_active_user(identity::admin_user()); + } + public function viewable_test() { $album = test::random_album(); $item = test::random_photo($album); @@ -42,4 +46,65 @@ class Item_Helper_Test extends Gallery_Unit_Test_Case { $this->assert_equal("foo", item::convert_filename_to_slug("{[foo]}")); $this->assert_equal("foo-bar", item::convert_filename_to_slug("{[foo!@#!$@#^$@($!(@bar]}")); } + + public function move_test() { + $photo = test::random_photo(item::root()); + $dst_album = test::random_album(); + + item::move($photo, $dst_album); + $this->assert_same($dst_album->id, $photo->parent_id); + } + + + public function move_updates_album_covers_test() { + // 2 photos in the source album + $src_album = test::random_album(); + $photo1 = test::random_photo($src_album); + $photo2 = test::random_photo($src_album); + $src_album->reload(); + + // destination album + $dst_album = test::random_album(); + + item::move($photo1, $dst_album); + + // Refresh cached copies + $src_album->reload(); + $dst_album->reload(); + + // photo 2 becomes the album cover for the source album and photo 1 + // becomes the album cover for the destination + $this->assert_same($photo1->id, $dst_album->album_cover_item_id); + $this->assert_same($photo2->id, $src_album->album_cover_item_id); + } + + public function move_leaves_empty_album_with_no_album_cover_test() { + $src_album = test::random_album(); + $photo = test::random_photo($src_album); + + item::move($photo, item::root()); + + $src_album->reload(); + $this->assert_false($src_album->album_cover_item_id); + } + + public function move_conflicts_result_in_a_rename_test() { + $rand = rand(); + $photo1 = test::random_photo_unsaved(item::root()); + $photo1->name = "{$rand}.jpg"; + $photo1->slug = (string)$rand; + $photo1->save(); + + $src_album = test::random_album(); + $photo2 = test::random_photo_unsaved($src_album); + $photo2->name = "{$rand}.jpg"; + $photo2->slug = (string)$rand; + $photo2->save(); + + item::move($photo2, item::root()); + + $this->assert_same(item::root()->id, $photo2->parent_id); + $this->assert_not_same("{$rand}.jpg", $photo2->name); + $this->assert_not_same($rand, $photo2->slug); + } } diff --git a/modules/gallery/views/admin_maintenance.html.php b/modules/gallery/views/admin_maintenance.html.php index 19375670..ac597715 100644 --- a/modules/gallery/views/admin_maintenance.html.php +++ b/modules/gallery/views/admin_maintenance.html.php @@ -29,22 +29,16 @@ <?= $task->description ?> </td> <td> - <? foreach ($task_buttons as $button): ?> - <a href="<?= "{$button->url}/$task->callback?csrf=$csrf" ?>" + <a href="<?= url::site("admin/maintenance/start/$task->callback?csrf=$csrf") ?>" class="g-dialog-link g-button ui-icon-left ui-state-default ui-corner-all"> - <?= html::clean($button->text) ?> + <?= t("run") ?> </a> - <? endforeach ?> </td> </tr> <? endforeach ?> </table> </div> - <? foreach ($task_maintenance_content as $content): ?> - <?= html::purify($content) ?> - <? endforeach ?> - <? if ($running_tasks->count()): ?> <div id="g-running-tasks"> <h2> <?= t("Running tasks") ?> </h2> diff --git a/modules/gallery/views/reauthenticate.html.php b/modules/gallery/views/reauthenticate.html.php new file mode 100644 index 00000000..8611d0f7 --- /dev/null +++ b/modules/gallery/views/reauthenticate.html.php @@ -0,0 +1,10 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div> + <p> + <?= t("The administration session has expired, please re-authenticate to access the administration area.") ?> + </p> + <p> + <?= t("You are currently logged in as %user_name.", array("user_name" => $user_name)) ?> + </p> + <?= $form ?> +</div>
\ No newline at end of file diff --git a/modules/gallery/views/upgrader.html.php b/modules/gallery/views/upgrader.html.php index 6cf0068d..55731440 100644 --- a/modules/gallery/views/upgrader.html.php +++ b/modules/gallery/views/upgrader.html.php @@ -6,7 +6,7 @@ media="screen,print,projection" /> <script src="<?= url::file("lib/jquery.js") ?>" type="text/javascript"></script> </head> - <body> + <body<? if (locales::is_rtl()) { echo ' class="rtl"'; } ?>> <div id="outer"> <img src="<?= url::file("modules/gallery/images/gallery.png") ?>" /> <div id="inner"> @@ -59,7 +59,7 @@ </p> <table> <tr class="<?= $done ? "muted" : "" ?>"> - <th> <?= t("Module name") ?> </th> + <th class="name"> <?= t("Module name") ?> </th> <th> <?= t("Installed version") ?> </th> <th> <?= t("Available version") ?> </th> </tr> @@ -112,7 +112,9 @@ <? else: // can_upgrade ?> <h1> <?= t("Who are you?") ?> </h1> <p> - <?= t("You're not logged in as an administrator, so we have to verify you to make sure it's ok for you to do an upgrade. To prove you can run an upgrade, create a file called <br/><b>%name</b> in your <b>gallery3/var/tmp</b> directory.", array("name" => "$upgrade_token")) ?> + <?= t("You're not logged in as an administrator, so we have to verify you to make sure it's ok for you to do an upgrade. To prove you can run an upgrade, create a file called <b>%name</b> in your <b>%tmp_dir_path</b> directory.", + array("name" => "$upgrade_token", + "tmp_dir_path" => "gallery3/var/tmp")) ?> </p> <a href="<?= url::site("upgrader?") ?>"><?= t("Ok, I've done that") ?></a> <? endif // can_upgrade ?> diff --git a/modules/gallery/views/user_profile.html.php b/modules/gallery/views/user_profile.html.php index 78e1c579..1c3e4ea2 100644 --- a/modules/gallery/views/user_profile.html.php +++ b/modules/gallery/views/user_profile.html.php @@ -63,6 +63,9 @@ <a class="g-button ui-icon-right ui-state-default ui-corner-all g-dialog-link" href="<?= url::site("users/form_change_password/{$user->id}") ?>"> <?= t("Change password") ?> </a> + <a class="g-button ui-icon-right ui-state-default ui-corner-all g-dialog-link" href="<?= url::site("users/form_change_email/{$user->id}") ?>"> + <?= t("Change email") ?> + </a> <? endif ?> <a id="g-profile-return" class="g-button ui-icon-right ui-state-default ui-corner-all" href="#"> diff --git a/modules/gallery/views/welcome_message.html.php b/modules/gallery/views/welcome_message.html.php index 24d01bab..caeeff66 100644 --- a/modules/gallery/views/welcome_message.html.php +++ b/modules/gallery/views/welcome_message.html.php @@ -15,15 +15,12 @@ </p> <p> - <a href="<?= url::site("form/edit/users/{$user->id}") ?>" + <a href="<?= url::site("user_profile/show/{$user->id}") ?>" title="<?= t("Edit your profile")->for_html_attr() ?>" id="g-after-install-change-password-link" class="g-button ui-state-default ui-corners-all"> - <?= t("Change password now") ?> + <?= t("Change password and email now") ?> </a> - <script type="text/javascript"> - $("#g-after-install-change-password-link").gallery_dialog(); - </script> </p> <p> |