diff options
author | Bharat Mediratta <bharat@menalto.com> | 2009-05-27 15:07:27 -0700 |
---|---|---|
committer | Bharat Mediratta <bharat@menalto.com> | 2009-05-27 15:07:27 -0700 |
commit | 28b41056e3ea962dce1ad017a3c0a60252195e7a (patch) | |
tree | 82c11956bb13969e6c8ddeb39ccfce7ae70786ca /modules | |
parent | 2e285cf3ecac742193457347ecb5c2d1121a1052 (diff) |
Restructure things so that the application is now just another module.
Kohana makes this type of transition fairly straightforward in that
all controllers/helpers/etc are still located in the cascading
filesystem without any extra effort, except that I've temporarily
added a hack to force modules/gallery into the module path.
Rename what's left of "core" to be "application" so that it conforms
more closely to the Kohana standard (basically, just
application/config/config.php which is the minimal thing that you need
in the application directory)
There's still considerable work left to be done here.
Diffstat (limited to 'modules')
165 files changed, 14533 insertions, 0 deletions
diff --git a/modules/gallery/config/cookie.php b/modules/gallery/config/cookie.php new file mode 100644 index 00000000..692ef548 --- /dev/null +++ b/modules/gallery/config/cookie.php @@ -0,0 +1,49 @@ +<?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. + */ + +/** + * Domain, to restrict the cookie to a specific website domain. For security, + * you are encouraged to set this option. An empty setting allows the cookie + * to be read by any website domain. + */ +$config['domain'] = ''; + +/** + * Restrict cookies to a specific path, typically the installation directory. + */ +$config['path'] = '/'; + +/** + * Lifetime of the cookie. A setting of 0 makes the cookie active until the + * users browser is closed or the cookie is deleted. + */ +$config['expire'] = 0; + +/** + * Enable this option to only allow the cookie to be read when using the a + * secure protocol. + */ +$config['secure'] = false; + +/** + * Enable this option to disable the cookie from being accessed when using a + * secure protocol. This option is only available in PHP 5.2 and above. + */ +$config['httponly'] = true;
\ No newline at end of file diff --git a/modules/gallery/config/database.php b/modules/gallery/config/database.php new file mode 100644 index 00000000..c20ccdb0 --- /dev/null +++ b/modules/gallery/config/database.php @@ -0,0 +1,23 @@ +<?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. + */ + +if (file_exists(VARPATH . "database.php")) { + include(VARPATH . "database.php"); +} diff --git a/modules/gallery/config/locale.php b/modules/gallery/config/locale.php new file mode 100644 index 00000000..4789dc2b --- /dev/null +++ b/modules/gallery/config/locale.php @@ -0,0 +1,46 @@ +<?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. + */ + +/** + * @package Core + * + * Default language locale name(s). + * First item must be a valid i18n directory name, subsequent items are alternative locales + * for OS's that don't support the first (e.g. Windows). The first valid locale in the array will be used. + * @see http://php.net/setlocale + */ +$config['language'] = array('en_US', 'English_United States'); + +/** + * Locale timezone. Defaults to use the server timezone. + * @see http://php.net/timezones + */ +$config['timezone'] = ''; + +// i18n settings + +/** + * The locale of the built-in localization messages (locale of strings in translate() calls). + * This can't be changed easily, unless all localization strings are replaced in all source files + * as well. + * Although the actual root is "en_US", the configured root is "en" that all en locales inherit the + * built-in strings. + */ +$config['root_locale'] = 'en';
\ No newline at end of file diff --git a/modules/gallery/config/routes.php b/modules/gallery/config/routes.php new file mode 100644 index 00000000..0272ca15 --- /dev/null +++ b/modules/gallery/config/routes.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. + */ + +// The abstract REST_Controller is not directly routable. +$config["^rest\b.*"] = null; + +// Admin controllers are not available, except via /admin +$config["^admin_.*"] = null; + +// Redirect /form/add and /form/edit to REST_Controller. +$config["^form/(edit|add)/(\w+)/(.*)$"] = "$2/form_$1/$3"; + +// Default page is the root album +$config["_default"] = "albums/1"; diff --git a/modules/gallery/config/sendmail.php b/modules/gallery/config/sendmail.php new file mode 100644 index 00000000..a4fa76ef --- /dev/null +++ b/modules/gallery/config/sendmail.php @@ -0,0 +1,29 @@ +<?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. + */ +/** + * PHP Mail Configuration parameters + * from => email address that appears as the from address + * line-length => word wrap length (PHP documentations suggest no larger tha 70 characters + * reply-to => what goes into the reply to header + */ +$config["from"] = "admin@gallery3.com"; +$config["line_length"] = 70; +$config["reply_to"] = "public@gallery3.com"; +$config["header_separator"] = "\n"; diff --git a/modules/gallery/config/session.php b/modules/gallery/config/session.php new file mode 100644 index 00000000..990fa31f --- /dev/null +++ b/modules/gallery/config/session.php @@ -0,0 +1,66 @@ +<?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. + */ + +/** + * @package Session + * + * Session driver name. + */ +$config['driver'] = 'database'; + +/** + * Session storage parameter, used by drivers. + */ +$config['storage'] = ''; + +/** + * Session name. + * It must contain only alphanumeric characters and underscores. At least one letter must be present. + */ +$config['name'] = 'g3sid'; + +/** + * Session parameters to validate: user_agent, ip_address, expiration. + */ +$config['validate'] = array('user_agent'); + +/** + * Enable or disable session encryption. + * Note: this has no effect on the native session driver. + * Note: the cookie driver always encrypts session data. Set to TRUE for stronger encryption. + */ +$config['encryption'] = FALSE; + +/** + * Session lifetime. Number of seconds that each session will last. + * A value of 0 will keep the session active until the browser is closed (with a limit of 24h). + */ +$config['expiration'] = 604800; // 7 days + +/** + * Number of page loads before the session id is regenerated. + * A value of 0 will disable automatic session id regeneration. + */ +$config['regenerate'] = 0; + +/** + * Percentage probability that the gc (garbage collection) routine is started. + */ +$config['gc_probability'] = 2;
\ No newline at end of file diff --git a/modules/gallery/config/upload.php b/modules/gallery/config/upload.php new file mode 100644 index 00000000..897ecacf --- /dev/null +++ b/modules/gallery/config/upload.php @@ -0,0 +1,36 @@ +<?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. + */ + +/** + * @package Core + * + * This path is relative to your index file. Absolute paths are also supported. + */ +$config['directory'] = VARPATH.'uploads'; + +/** + * Enable or disable directory creation. + */ +$config['create_directories'] = FALSE; + +/** + * Remove spaces from uploaded filenames. + */ +$config['remove_spaces'] = TRUE;
\ No newline at end of file diff --git a/modules/gallery/controllers/admin.php b/modules/gallery/controllers/admin.php new file mode 100644 index 00000000..af0f387a --- /dev/null +++ b/modules/gallery/controllers/admin.php @@ -0,0 +1,52 @@ +<?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 Admin_Controller extends Controller { + private $theme; + + public function __construct($theme=null) { + if (!(user::active()->admin)) { + throw new Exception("@todo UNAUTHORIZED", 401); + } + parent::__construct(); + } + + public function __call($controller_name, $args) { + if (request::method() == "post") { + access::verify_csrf(); + } + + if ($controller_name == "index") { + $controller_name = "dashboard"; + } + $controller_name = "Admin_{$controller_name}_Controller"; + if ($args) { + $method = array_shift($args); + } else { + $method = "index"; + } + + if (!method_exists($controller_name, $method)) { + return kohana::show_404(); + } + + call_user_func_array(array(new $controller_name, $method), $args); + } +} + diff --git a/modules/gallery/controllers/admin_advanced_settings.php b/modules/gallery/controllers/admin_advanced_settings.php new file mode 100644 index 00000000..79bc1183 --- /dev/null +++ b/modules/gallery/controllers/admin_advanced_settings.php @@ -0,0 +1,53 @@ +<?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 Admin_Advanced_Settings_Controller extends Admin_Controller { + public function index() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_advanced_settings.html"); + $view->content->vars = ORM::factory("var") + ->orderby("module_name", "name") + ->find_all(); + print $view; + } + + public function edit($module_name, $var_name) { + $value = module::get_var($module_name, $var_name); + $form = new Forge("admin/advanced_settings/save/$module_name/$var_name", "", "post"); + $group = $form->group("edit_var")->label( + t("Edit %var (%module_name)", + array("module_name" => $module_name, "var" => $var_name))); + $group->input("module_name")->label(t("Module"))->value($module_name)->disabled(1); + $group->input("var_name")->label(t("Setting"))->value($var_name)->disabled(1); + $group->textarea("value")->label(t("Value"))->value($value); + $group->submit("")->value(t("Save")); + print $form; + } + + public function save($module_name, $var_name) { + access::verify_csrf(); + + module::set_var($module_name, $var_name, Input::instance()->post("value")); + message::success( + t("Saved value for %var (%module_name)", + array("var" => $var_name, "module_name" => $module_name))); + + print json_encode(array("result" => "success")); + } +} diff --git a/modules/gallery/controllers/admin_dashboard.php b/modules/gallery/controllers/admin_dashboard.php new file mode 100644 index 00000000..d2d2f79b --- /dev/null +++ b/modules/gallery/controllers/admin_dashboard.php @@ -0,0 +1,93 @@ +<?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 Admin_Dashboard_Controller extends Admin_Controller { + public function index() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_dashboard.html"); + $view->content->blocks = block_manager::get_html("dashboard_center"); + $view->sidebar = "<div id=\"gAdminDashboardSidebar\">" . + block_manager::get_html("dashboard_sidebar") . + "</div>"; + print $view; + } + + public function add_block() { + $form = core_block::get_add_block_form(); + if ($form->validate()) { + list ($module_name, $id) = explode(":", $form->add_block->id->value); + $available = block_manager::get_available(); + + if ($form->add_block->center->value) { + block_manager::add("dashboard_center", $module_name, $id); + message::success( + t("Added <b>%title</b> block to the dashboard center", + array("title" => $available["$module_name:$id"]))); + } else { + block_manager::add("dashboard_sidebar", $module_name, $id); + message::success( + t("Added <b>%title</b> to the dashboard sidebar", + array("title" => $available["$module_name:$id"]))); + } + } + url::redirect("admin/dashboard"); + } + + public function remove_block($id) { + access::verify_csrf(); + $blocks_center = block_manager::get_active("dashboard_center"); + $blocks_sidebar = block_manager::get_active("dashboard_sidebar"); + + if (array_key_exists($id, $blocks_sidebar)) { + $deleted = $blocks_sidebar[$id]; + block_manager::remove("dashboard_sidebar", $id); + } else if (array_key_exists($id, $blocks_center)) { + $deleted = $blocks_center[$id]; + block_manager::remove("dashboard_center", $id); + } + + if (!empty($deleted)) { + $available = block_manager::get_available(); + $title = $available[join(":", $deleted)]; + message::success(t("Removed <b>%title</b> block", array("title" => $title))); + } + + url::redirect("admin"); + } + + public function reorder() { + access::verify_csrf(); + $active_set = array(); + foreach (array("dashboard_sidebar", "dashboard_center") as $location) { + foreach (block_manager::get_active($location) as $id => $info) { + $active_set[$id] = $info; + } + } + + foreach (array("dashboard_sidebar", "dashboard_center") as $location) { + $new_blocks = array(); + foreach ($this->input->get($location, array()) as $id) { + $new_blocks[$id] = $active_set[$id]; + } + block_manager::set_active($location, $new_blocks); + } + + $this->_force_block_adder(); + } +} diff --git a/modules/gallery/controllers/admin_graphics.php b/modules/gallery/controllers/admin_graphics.php new file mode 100644 index 00000000..0b3014f0 --- /dev/null +++ b/modules/gallery/controllers/admin_graphics.php @@ -0,0 +1,63 @@ +<?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 Admin_Graphics_Controller extends Admin_Controller { + public function index() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_graphics.html"); + $view->content->available = ""; + + $tk = new ArrayObject(graphics::detect_toolkits(), ArrayObject::ARRAY_AS_PROPS); + $active = module::get_var("core", "graphics_toolkit", "none"); + foreach (array("gd", "imagemagick", "graphicsmagick", "none") as $id) { + if ($id == $active) { + $view->content->active = new View("admin_graphics_$id.html"); + $view->content->active->tk = $tk; + $view->content->active->is_active = true; + } else if ($id != "none") { + $v = new View("admin_graphics_$id.html"); + $v->tk = $tk; + $v->is_active = false; + $view->content->available .= $v; + } + } + + print $view; + } + + public function choose($toolkit) { + access::verify_csrf(); + if ($toolkit != module::get_var("core", "graphics_toolkit")) { + module::set_var("core", "graphics_toolkit", $toolkit); + + $toolkit_info = graphics::detect_toolkits(); + if ($toolkit == "graphicsmagick" || $toolkit == "imagemagick") { + module::set_var("core", "graphics_toolkit_path", $toolkit_info[$toolkit]); + } + + site_status::clear("missing_graphics_toolkit"); + message::success(t("Updated Graphics Toolkit")); + log::success("graphics", t("Changed graphics toolkit to: %toolkit", + array("toolkit" => $toolkit))); + } + + url::redirect("admin/graphics"); + } +} + diff --git a/modules/gallery/controllers/admin_languages.php b/modules/gallery/controllers/admin_languages.php new file mode 100644 index 00000000..37d335a3 --- /dev/null +++ b/modules/gallery/controllers/admin_languages.php @@ -0,0 +1,136 @@ +<?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 Admin_Languages_Controller extends Admin_Controller { + public function index($share_translations_form=null) { + $v = new Admin_View("admin.html"); + $v->content = new View("admin_languages.html"); + $v->content->settings_form = $this->_languages_form(); + if (empty($share_translations_form)) { + $share_translations_form = $this->_share_translations_form(); + } + $v->content->share_translations_form = $share_translations_form; + $this->_outgoing_translations_count(); + print $v; + } + + public function save() { + $form = $this->_languages_form(); + if ($form->validate()) { + module::set_var("core", "default_locale", $form->choose_language->locale->value); + locale::update_installed($form->choose_language->installed_locales->value); + message::success(t("Settings saved")); + } + url::redirect("admin/languages"); + } + + public function share() { + $form = $this->_share_translations_form(); + if (!$form->validate()) { + // Show the page with form errors + return $this->index($form); + } + + if ($form->sharing->share) { + l10n_client::submit_translations(); + message::success(t("Translations submitted")); + } else { + return $this->_save_api_key($form); + } + url::redirect("admin/languages"); + } + + private function _save_api_key($form) { + $new_key = $form->sharing->api_key->value; + if ($new_key && !l10n_client::validate_api_key($new_key)) { + $form->sharing->api_key->add_error("invalid", 1); + $valid = false; + } else { + $valid = true; + } + + if ($valid) { + $old_key = l10n_client::api_key(); + l10n_client::api_key($new_key); + if ($old_key && !$new_key) { + message::success(t("Your API key has been cleared.")); + } else if ($old_key && $new_key && $old_key != $new_key) { + message::success(t("Your API key has been changed.")); + } else if (!$old_key && $new_key) { + message::success(t("Your API key has been saved.")); + } + + log::success(t("core"), t("l10n_client API key changed.")); + url::redirect("admin/languages"); + } else { + // Show the page with form errors + $this->index($form); + } + } + + private function _languages_form() { + $all_locales = locale::available(); + $installed_locales = locale::installed(); + $form = new Forge("admin/languages/save", "", "post", array("id" => "gLanguageSettingsForm")); + $group = $form->group("choose_language") + ->label(t("Language settings")); + $group->dropdown("locale") + ->options($installed_locales) + ->selected(module::get_var("core", "default_locale")) + ->label(t("Default language")) + ->rules('required'); + + $installation_options = array(); + foreach ($all_locales as $code => $display_name) { + $installation_options[$code] = array($display_name, isset($installed_locales->$code)); + } + $group->checklist("installed_locales") + ->label(t("Installed Languages")) + ->options($installation_options) + ->rules("required"); + $group->submit("save")->value(t("Save settings")); + return $form; + } + + private function _outgoing_translations_count() { + return ORM::factory("outgoing_translation")->count_all(); + } + + private function _share_translations_form() { + $form = new Forge("admin/languages/share", "", "post", array("id" => "gShareTranslationsForm")); + $group = $form->group("sharing") + ->label(t("Sharing you own translations with the Gallery community is easy. Please do!")); + $api_key = l10n_client::api_key(); + $server_link = l10n_client::server_api_key_url(); + $group->input("api_key") + ->label(empty($api_key) + ? t("This is a unique key that will allow you to send translations to the remote server. To get your API key go to %server-link.", + array("server-link" => html::anchor($server_link))) + : t("API Key")) + ->value($api_key) + ->error_messages("invalid", t("The API key you provided is invalid.")); + $group->submit("save")->value(t("Save settings")); + if ($api_key && $this->_outgoing_translations_count()) { + // TODO: UI improvement: hide API key / save button when API key is set. + $group->submit("share")->value(t("Submit translations")); + } + return $form; + } +} + diff --git a/modules/gallery/controllers/admin_maintenance.php b/modules/gallery/controllers/admin_maintenance.php new file mode 100644 index 00000000..c169de75 --- /dev/null +++ b/modules/gallery/controllers/admin_maintenance.php @@ -0,0 +1,181 @@ +<?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 Admin_Maintenance_Controller extends Admin_Controller { + /** + * Show a list of all available, running and finished tasks. + */ + public function index() { + $query = Database::instance()->query( + "UPDATE {tasks} SET `state` = 'stalled' " . + "WHERE done = 0 " . + "AND state <> 'stalled' " . + "AND unix_timestamp(now()) - updated > 15"); + $stalled_count = $query->count(); + if ($stalled_count) { + log::warning("tasks", + t2("One task is stalled", + "%count tasks are stalled", + $stalled_count), + t('<a href="%url">view</a>', + array("url" => url::site("admin/maintenance")))); + } + + $view = new Admin_View("admin.html"); + $view->content = new View("admin_maintenance.html"); + $view->content->task_definitions = task::get_definitions(); + $view->content->running_tasks = ORM::factory("task") + ->where("done", 0)->orderby("updated", "DESC")->find_all(); + $view->content->finished_tasks = ORM::factory("task") + ->where("done", 1)->orderby("updated", "DESC")->find_all(); + print $view; + } + + /** + * Start a new task + * @param string $task_callback + */ + public function start($task_callback) { + access::verify_csrf(); + + $tasks = task::get_definitions(); + $task = task::create($tasks[$task_callback], array()); + $view = new View("admin_maintenance_task.html"); + $view->task = $task; + + log::info("tasks", t("Task %task_name started (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor(url::site("admin/maintenance"), t("maintenance"))); + print $view; + } + + /** + * Resume a stalled task + * @param string $task_id + */ + public function resume($task_id) { + access::verify_csrf(); + + $task = ORM::factory("task", $task_id); + if (!$task->loaded) { + throw new Exception("@todo MISSING_TASK"); + } + $view = new View("admin_maintenance_task.html"); + $view->task = $task; + + log::info("tasks", t("Task %task_name resumed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor(url::site("admin/maintenance"), t("maintenance"))); + print $view; + } + + /** + * Cancel a task. + * @param string $task_id + */ + public function cancel($task_id) { + access::verify_csrf(); + + task::cancel($task_id); + + message::success(t("Task cancelled")); + url::redirect("admin/maintenance"); + } + + public function cancel_running_tasks() { + access::verify_csrf(); + Database::instance()->update( + "tasks", + array("done" => 1, "state" => "cancelled"), + array("done" => 0)); + message::success(t("All running tasks cancelled")); + url::redirect("admin/maintenance"); + } + + /** + * Remove a task. + * @param string $task_id + */ + public function remove($task_id) { + access::verify_csrf(); + + task::remove($task_id); + + message::success(t("Task removed")); + url::redirect("admin/maintenance"); + } + + public function remove_finished_tasks() { + access::verify_csrf(); + Database::instance()->delete("tasks", array("done" => 1)); + message::success(t("All finished tasks removed")); + url::redirect("admin/maintenance"); + } + + /** + * Run a task. This will trigger the task to do a small amount of work, then it will report + * back with status on the task. + * @param string $task_id + */ + public function run($task_id) { + access::verify_csrf(); + + try { + $task = task::run($task_id); + } catch (Exception $e) { + Kohana::log( + "error", + sprintf( + "%s in %s at line %s:\n%s", $e->getMessage(), $e->getFile(), + $e->getLine(), $e->getTraceAsString())); + throw $e; + } + + if ($task->done) { + switch ($task->state) { + case "success": + log::success("tasks", t("Task %task_name completed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor(url::site("admin/maintenance"), t("maintenance"))); + message::success(t("Task completed successfully")); + break; + + case "error": + log::error("tasks", t("Task %task_name failed (task id %task_id)", + array("task_name" => $task->name, "task_id" => $task->id)), + html::anchor(url::site("admin/maintenance"), t("maintenance"))); + message::success(t("Task failed")); + break; + } + print json_encode(array("result" => "success", + "task" => array( + "percent_complete" => $task->percent_complete, + "status" => $task->status, + "done" => $task->done), + "location" => url::site("admin/maintenance"))); + + } else { + print json_encode(array("result" => "in_progress", + "task" => array( + "percent_complete" => $task->percent_complete, + "status" => $task->status, + "done" => $task->done))); + } + } +} diff --git a/modules/gallery/controllers/admin_modules.php b/modules/gallery/controllers/admin_modules.php new file mode 100644 index 00000000..f7dd909d --- /dev/null +++ b/modules/gallery/controllers/admin_modules.php @@ -0,0 +1,65 @@ +<?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 Admin_Modules_Controller extends Admin_Controller { + public function index() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_modules.html"); + $view->content->available = module::available(); + print $view; + } + + public function save() { + access::verify_csrf(); + + $changes->activate = array(); + $changes->deactivate = array(); + $activated_names = array(); + $deactivated_names = array(); + foreach (module::available() as $module_name => $info) { + if ($info->locked) { + continue; + } + + $desired = $this->input->post($module_name) == 1; + if ($info->active && !$desired && module::is_active($module_name)) { + $changes->deactivate[] = $module_name; + $deactivated_names[] = $info->name; + module::deactivate($module_name); + } else if (!$info->active && $desired && !module::is_active($module_name)) { + $changes->activate[] = $module_name; + $activated_names[] = $info->name; + module::install($module_name); + module::activate($module_name); + } + } + + module::event("module_change", $changes); + + // @todo this type of collation is questionable from a i18n perspective + if ($activated_names) { + message::success(t("Activated: %names", array("names" => join(", ", $activated_names)))); + } + if ($deactivated_names) { + message::success(t("Deactivated: %names", array("names" => join(", ", $deactivated_names)))); + } + url::redirect("admin/modules"); + } +} + diff --git a/modules/gallery/controllers/admin_theme_details.php b/modules/gallery/controllers/admin_theme_details.php new file mode 100644 index 00000000..542ec31c --- /dev/null +++ b/modules/gallery/controllers/admin_theme_details.php @@ -0,0 +1,67 @@ +<?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 Admin_Theme_Details_Controller extends Admin_Controller { + public function index() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_theme_details.html"); + $view->content->form = theme::get_edit_form_admin(); + print $view; + } + + public function save() { + $form = theme::get_edit_form_admin(); + if ($form->validate()) { + module::set_var("core", "page_size", $form->edit_theme->page_size->value); + + $thumb_size = $form->edit_theme->thumb_size->value; + $thumb_dirty = false; + if (module::get_var("core", "thumb_size") != $thumb_size) { + graphics::remove_rule("core", "thumb", "resize"); + graphics::add_rule( + "core", "thumb", "resize", + array("width" => $thumb_size, "height" => $thumb_size, "master" => Image::AUTO), + 100); + module::set_var("core", "thumb_size", $thumb_size); + } + + $resize_size = $form->edit_theme->resize_size->value; + $resize_dirty = false; + if (module::get_var("core", "resize_size") != $resize_size) { + graphics::remove_rule("core", "resize", "resize"); + graphics::add_rule( + "core", "resize", "resize", + array("width" => $resize_size, "height" => $resize_size, "master" => Image::AUTO), + 100); + module::set_var("core", "resize_size", $resize_size); + } + + module::set_var("core", "header_text", $form->edit_theme->header_text->value); + module::set_var("core", "footer_text", $form->edit_theme->footer_text->value); + + message::success(t("Updated theme details")); + url::redirect("admin/theme_details"); + } else { + $view = new Admin_View("admin.html"); + $view->content = $form; + print $view; + } + } +} + diff --git a/modules/gallery/controllers/admin_themes.php b/modules/gallery/controllers/admin_themes.php new file mode 100644 index 00000000..05c134d1 --- /dev/null +++ b/modules/gallery/controllers/admin_themes.php @@ -0,0 +1,79 @@ +<?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 Admin_Themes_Controller extends Admin_Controller { + public function index() { + $view = new Admin_View("admin.html"); + $view->content = new View("admin_themes.html"); + $view->content->admin = module::get_var("core", "active_admin_theme"); + $view->content->site = module::get_var("core", "active_site_theme"); + $view->content->themes = $this->_get_themes(); + print $view; + } + + private function _get_themes() { + $themes = array(); + foreach (scandir(THEMEPATH) as $theme_name) { + if ($theme_name[0] == ".") { + continue; + } + + $file = THEMEPATH . "$theme_name/theme.info"; + $theme_info = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS); + $themes[$theme_name] = $theme_info; + } + return $themes; + } + + public function preview($type, $theme_name) { + $view = new View("admin_themes_preview.html"); + $theme_name = preg_replace("/[^\w]/", "", $theme_name); + $view->info = new ArrayObject( + parse_ini_file(THEMEPATH . "$theme_name/theme.info"), ArrayObject::ARRAY_AS_PROPS); + $view->theme_name = $theme_name; + $view->type = $type; + if ($type == "admin") { + $view->url = url::site("admin?theme=$theme_name"); + } else { + $view->url = url::site("albums/1?theme=$theme_name"); + } + print $view; + } + + public function choose($type, $theme_name) { + access::verify_csrf(); + + $theme_name = preg_replace("/[^\w]/", "", $theme_name); + $info = new ArrayObject( + parse_ini_file(THEMEPATH . "$theme_name/theme.info"), ArrayObject::ARRAY_AS_PROPS); + + if ($type == "admin" && $info->admin) { + module::set_var("core", "active_admin_theme", $theme_name); + message::success(t("Successfully changed your admin theme to <b>%theme_name</b>", + array("theme_name" => $info->name))); + } else if ($type == "site" && $info->site) { + module::set_var("core", "active_site_theme", $theme_name); + message::success(t("Successfully changed your Gallery theme to <b>%theme_name</b>", + array("theme_name" => $info->name))); + } + + url::redirect("admin/themes"); + } +} + diff --git a/modules/gallery/controllers/after_install.php b/modules/gallery/controllers/after_install.php new file mode 100644 index 00000000..f066afe4 --- /dev/null +++ b/modules/gallery/controllers/after_install.php @@ -0,0 +1,30 @@ +<?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 After_Install_Controller extends Controller { + public function index() { + if (!user::active()->admin) { + url::redirect("albums/1"); + } + + $v = new View("after_install.html"); + $v->user = user::active(); + print $v; + } +} diff --git a/modules/gallery/controllers/albums.php b/modules/gallery/controllers/albums.php new file mode 100644 index 00000000..5b4d5979 --- /dev/null +++ b/modules/gallery/controllers/albums.php @@ -0,0 +1,229 @@ +<?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 Albums_Controller extends Items_Controller { + + /** + * @see REST_Controller::_show($resource) + */ + public function _show($album) { + if (!access::can("view", $album)) { + if ($album->id != 1) { + access::forbidden(); + } else { + print new Theme_View("login_page.html", "album"); + return; + } + } + + $page_size = module::get_var("core", "page_size", 9); + $show = $this->input->get("show"); + + if ($show) { + $index = $album->get_position($show); + $page = ceil($index / $page_size); + if ($page == 1) { + url::redirect("albums/$album->id"); + } else { + url::redirect("albums/$album->id?page=$page"); + } + } + + $page = $this->input->get("page", "1"); + $children_count = $album->viewable()->children_count(); + $offset = ($page - 1) * $page_size; + $max_pages = max(ceil($children_count / $page_size), 1); + + // Make sure that the page references a valid offset + if ($page < 1) { + url::redirect("albums/$album->id"); + } else if ($page > $max_pages) { + url::redirect("albums/$album->id?page=$max_pages"); + } + + $template = new Theme_View("page.html", "album"); + $template->set_global("page_size", $page_size); + $template->set_global("item", $album); + $template->set_global("children", $album->viewable()->children($page_size, $offset)); + $template->set_global("children_count", $children_count); + $template->set_global("parents", $album->parents()); + $template->content = new View("album.html"); + + // We can't use math in ORM or the query builder, so do this by hand. It's important + // that we do this with math, otherwise concurrent accesses will damage accuracy. + Database::instance()->query( + "UPDATE {items} SET `view_count` = `view_count` + 1 WHERE `id` = $album->id"); + + print $template; + } + + /** + * @see REST_Controller::_create($resource) + */ + public function _create($album) { + access::required("add", $album); + + switch ($this->input->post("type")) { + case "album": + return $this->_create_album($album); + + case "photo": + return $this->_create_photo($album); + + default: + access::forbidden(); + } + } + + private function _create_album($album) { + access::required("add", $album); + + $form = album::get_add_form($album); + if ($form->validate()) { + $new_album = album::create( + $album, + $this->input->post("name"), + $this->input->post("title", $this->input->post("name")), + $this->input->post("description"), + user::active()->id); + + log::success("content", "Created an album", + html::anchor("albums/$new_album->id", "view album")); + message::success(t("Created album %album_title", array("album_title" => $new_album->title))); + + print json_encode( + array("result" => "success", + "location" => url::site("albums/$new_album->id"), + "resource" => url::site("albums/$new_album->id"))); + } else { + print json_encode( + array("result" => "error", + "form" => $form->__toString() . html::script("core/js/albums_form_add.js"))); + } + } + + private function _create_photo($album) { + access::required("add", $album); + + // If we set the content type as JSON, it triggers saving the result as + // a document in the browser (well, in Chrome at least). + // @todo figure out why and fix this. + $form = photo::get_add_form($album); + if ($form->validate()) { + $photo = photo::create( + $album, + $this->input->post("file"), + $_FILES["file"]["name"], + $this->input->post("title", $this->input->post("name")), + $this->input->post("description"), + user::active()->id); + + log::success("content", "Added a photo", html::anchor("photos/$photo->id", "view photo")); + message::success(t("Added photo %photo_title", array("photo_title" => $photo->title))); + + print json_encode( + array("result" => "success", + "resource" => url::site("photos/$photo->id"), + "location" => url::site("photos/$photo->id"))); + } else { + print json_encode( + array("result" => "error", + "form" => $form->__toString())); + } + } + + /** + * @see REST_Controller::_update($resource) + */ + public function _update($album) { + access::required("edit", $album); + + $form = album::get_edit_form($album); + if ($valid = $form->validate()) { + // Make sure that there's not a conflict + if (Database::instance() + ->from("items") + ->where("parent_id", $album->parent_id) + ->where("id <>", $album->id) + ->where("name", $form->edit_album->dirname->value) + ->count_records()) { + $form->edit_album->dirname->add_error("conflict", 1); + $valid = false; + } + } + + // @todo + // @todo we need to make sure that filename / dirname components can't contain a / + // @todo + + if ($valid) { + $orig = clone $album; + $album->title = $form->edit_album->title->value; + $album->description = $form->edit_album->description->value; + $album->sort_column = $form->edit_album->sort_order->column->value; + $album->sort_order = $form->edit_album->sort_order->direction->value; + $album->rename($form->edit_album->dirname->value); + $album->save(); + + module::event("item_updated", $orig, $album); + + log::success("content", "Updated album", "<a href=\"albums/$album->id\">view</a>"); + message::success(t("Saved album %album_title", array("album_title" => $album->title))); + + print json_encode( + array("result" => "success", + "location" => url::site("albums/$album->id"))); + } else { + print json_encode( + array("result" => "error", + "form" => $form->__toString())); + } + } + + /** + * @see REST_Controller::_form_add($parameters) + */ + public function _form_add($album_id) { + $album = ORM::factory("item", $album_id); + access::required("add", $album); + + switch ($this->input->get("type")) { + case "album": + print album::get_add_form($album) . + html::script("core/js/albums_form_add.js"); + break; + + case "photo": + print photo::get_add_form($album); + break; + + default: + kohana::show_404(); + } + } + + /** + * @see REST_Controller::_form_add($parameters) + */ + public function _form_edit($album) { + access::required("edit", $album); + + print album::get_edit_form($album); + } +} diff --git a/modules/gallery/controllers/file_proxy.php b/modules/gallery/controllers/file_proxy.php new file mode 100644 index 00000000..f3c5f109 --- /dev/null +++ b/modules/gallery/controllers/file_proxy.php @@ -0,0 +1,120 @@ +<?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. + */ +/** + * Proxy access to files in var/albums and var/resizes, making sure that the session user has + * access to view these files. + * + * Security Philosophy: we do not use the information provided to find if the file exists on + * disk. We use this information only to locate the correct item in the database and then we + * *only* use information from the database to find and proxy the correct file. This way all user + * input is sanitized against the database before we perform any file I/O. + */ +class File_Proxy_Controller extends Controller { + public function __call($function, $args) { + // request_uri: http://example.com/gallery3/var/trunk/albums/foo/bar.jpg + $request_uri = $this->input->server("REQUEST_URI"); + $request_uri = preg_replace("/\?.*/", "", $request_uri); + + // var_uri: http://example.com/gallery3/var/ + $var_uri = url::file("var/"); + + // Make sure that the request is for a file inside var + $offset = strpos($request_uri, $var_uri); + if ($offset === false) { + kohana::show_404(); + } + + $file = substr($request_uri, strlen($var_uri)); + + // Make sure that we don't leave the var dir + if (strpos($file, "..") !== false) { + kohana::show_404(); + } + + // We only handle var/resizes and var/albums + $paths = explode("/", $file); + $type = $paths[0]; + if ($type != "resizes" && $type != "albums" && $type != "thumbs") { + kohana::show_404(); + } + + // If the last element is .album.jpg, pop that off since it's not a real item + if ($paths[count($paths)-1] == ".album.jpg") { + array_pop($paths); + } + if ($paths[count($paths)-1] == "") { + array_pop($paths); + } + + // Find all items that match the level and name, then iterate over those to find a match. + // In most cases we'll get it in one. Note that for the level calculation, we just count the + // size of $paths. $paths includes the type ("thumbs", etc) but it doesn't include the root, + // so it's a wash. + $count = count($paths); + $compare_file = VARPATH . $file; + $item = null; + foreach (ORM::factory("item") + ->where("name", $paths[$count - 1]) + ->where("level", $count) + ->find_all() as $match) { + if ($type == "albums") { + $match_file = $match->file_path(); + } else if ($type == "resizes") { + $match_file = $match->resize_path(); + } else { + $match_file = $match->thumb_path(); + } + if ($match_file == $compare_file) { + $item = $match; + break; + } + } + + if (!$item) { + kohana::show_404(); + } + + // Make sure we have access to the item + if (!access::can("view", $item)) { + kohana::show_404(); + } + + // Make sure we have view_full access to the original + if ($type == "albums" && !access::can("view_full", $item)) { + kohana::show_404(); + } + + // Don't try to load a directory + if ($type == "albums" && $item->is_album()) { + kohana::show_404(); + } + + if (!file_exists($match_file)) { + kohana::show_404(); + } + + // Dump out the image + header("Content-Type: $item->mime_type"); + Kohana::close_buffers(false); + $fd = fopen($match_file, "rb"); + fpassthru($fd); + fclose($fd); + } +} diff --git a/modules/gallery/controllers/items.php b/modules/gallery/controllers/items.php new file mode 100644 index 00000000..13891726 --- /dev/null +++ b/modules/gallery/controllers/items.php @@ -0,0 +1,30 @@ +<?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 Items_Controller extends REST_Controller { + protected $resource_type = "item"; + + public function _show($item) { + // Redirect to the more specific resource type, since it will render + // differently. We could also just delegate here, but it feels more appropriate + // to have a single canonical resource mapping. + access::required("view", $item); + return url::redirect($item->url(array(), true)); + } +} diff --git a/modules/gallery/controllers/l10n_client.php b/modules/gallery/controllers/l10n_client.php new file mode 100644 index 00000000..17520051 --- /dev/null +++ b/modules/gallery/controllers/l10n_client.php @@ -0,0 +1,128 @@ +<?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 L10n_Client_Controller extends Controller { + public function save() { + access::verify_csrf(); + user::active()->admin or access::forbidden(); + + $input = Input::instance(); + $message = $input->post("l10n-message-source"); + $translation = $input->post("l10n-edit-target"); + $key = I18n::get_message_key($message); + $locale = I18n::instance()->locale(); + + $entry = ORM::factory("outgoing_translation") + ->where(array("key" => $key, + "locale" => $locale)) + ->find(); + + if (!$entry->loaded) { + $entry->key = $key; + $entry->locale = $locale; + $entry->message = serialize($message); + $entry->base_revision = null; + } + + $entry->translation = serialize($translation); + + $entry_from_incoming = ORM::factory("incoming_translation") + ->where(array("key" => $key, + "locale" => $locale)) + ->find(); + + if (!$entry_from_incoming->loaded) { + $entry->base_revision = $entry_from_incoming->revision; + } + + $entry->save(); + + print json_encode(new stdClass()); + } + + public function toggle_l10n_mode() { + access::verify_csrf(); + + $session = Session::instance(); + $session->set("l10n_mode", + !$session->get("l10n_mode", false)); + + url::redirect("albums/1"); + } + + private static function _l10n_client_form() { + $form = new Forge("l10n_client/save", "", "post", array("id" => "gL10nClientSaveForm")); + $group = $form->group("l10n_message"); + $group->hidden("l10n-message-source")->value(""); + $group->textarea("l10n-edit-target"); + $group->submit("l10n-edit-save")->value(t("Save translation")); + // TODO(andy_st): Avoiding multiple submit buttons for now (hassle with jQuery form plugin). + // $group->submit("l10n-edit-copy")->value(t("Copy source")); + // $group->submit("l10n-edit-clear")->value(t("Clear")); + + return $form; + } + + private static function _l10n_client_search_form() { + $form = new Forge("l10n_client/search", "", "post", array("id" => "gL10nSearchForm")); + $group = $form->group("l10n_search"); + $group->input("l10n-search")->id("gL10nSearch"); + $group->submit("l10n-search-filter-clear")->value(t("X")); + + return $form; + } + + public static function l10n_form() { + $calls = I18n::instance()->call_log(); + + if ($calls) { + $string_list = array(); + foreach ($calls as $call) { + list ($message, $options) = $call; + // Note: Don't interpolate placeholders for the actual translation input field. + // TODO: Use $options to generate a preview. + if (is_array($message)) { + // TODO: Handle plural forms. + // Translate each message. If it has a plural form, get + // the current locale's plural rules and all plural translations. + continue; + } + $source = $message; + $translation = ''; + $options_for_raw_translation = array(); + if (isset($options['count'])) { + $options_for_raw_translation['count'] = $options['count']; + } + if (I18n::instance()->has_translation($message, $options_for_raw_translation)) { + $translation = I18n::instance()->translate($message, $options_for_raw_translation); + } + $string_list[] = array('source' => $source, + 'translation' => $translation); + } + + $v = new View('l10n_client.html'); + $v->string_list = $string_list; + $v->l10n_form = self::_l10n_client_form(); + $v->l10n_search_form = self::_l10n_client_search_form(); + return $v; + } + + return ''; + } +} diff --git a/modules/gallery/controllers/maintenance.php b/modules/gallery/controllers/maintenance.php new file mode 100644 index 00000000..b5f39bed --- /dev/null +++ b/modules/gallery/controllers/maintenance.php @@ -0,0 +1,24 @@ +<?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 Maintenance_Controller extends Controller { + function index() { + print new View("maintenance.html"); + } +}
\ No newline at end of file diff --git a/modules/gallery/controllers/move.php b/modules/gallery/controllers/move.php new file mode 100644 index 00000000..130c247f --- /dev/null +++ b/modules/gallery/controllers/move.php @@ -0,0 +1,64 @@ +<?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 Move_Controller extends Controller { + public function browse($source_id) { + $source = ORM::factory("item", $source_id); + access::required("edit", $source); + + $view = new View("move_browse.html"); + $view->source = $source; + $view->tree = $this->_get_tree_html($source, ORM::factory("item", 1)); + print $view; + } + + public function save($source_id) { + access::verify_csrf(); + $source = ORM::factory("item", $source_id); + $target = ORM::factory("item", $this->input->post("target_id")); + + item::move($source, $target); + + print json_encode( + array("result" => "success", + "location" => url::site("albums/{$target->id}"))); + } + + public function show_sub_tree($source_id, $target_id) { + $source = ORM::factory("item", $source_id); + $target = ORM::factory("item", $target_id); + access::required("edit", $source); + access::required("view", $target); + + print $this->_get_tree_html($source, $target); + } + + private function _get_tree_html($source, $target) { + $view = new View("move_tree.html"); + $view->source = $source; + $view->parent = $target; + $view->children = ORM::factory("item") + ->viewable() + ->where("type", "album") + ->where("parent_id", $target->id) + ->find_all(); + return $view; + } + +} diff --git a/modules/gallery/controllers/movies.php b/modules/gallery/controllers/movies.php new file mode 100644 index 00000000..55bbb0e5 --- /dev/null +++ b/modules/gallery/controllers/movies.php @@ -0,0 +1,114 @@ +<?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 Movies_Controller extends Items_Controller { + + /** + * @see REST_Controller::_show($resource) + */ + public function _show($photo) { + access::required("view", $photo); + + // We sort by id ascending so for now, find sibling info by doing id based queries. + $next_item = ORM::factory("item") + ->viewable() + ->where("parent_id", $photo->parent_id) + ->where("id >", $photo->id) + ->orderby("id", "ASC") + ->find(); + $previous_item = ORM::factory("item") + ->viewable() + ->where("parent_id", $photo->parent_id) + ->where("id <", $photo->id) + ->orderby("id", "DESC") + ->find(); + $position = ORM::factory("item") + ->viewable() + ->where("parent_id", $photo->parent_id) + ->where("id <=", $photo->id) + ->count_all(); + + $template = new Theme_View("page.html", "photo"); + $template->set_global("item", $photo); + $template->set_global("children", array()); + $template->set_global("children_count", $photo->children_count()); + $template->set_global("parents", $photo->parents()); + $template->set_global("next_item", $next_item->loaded ? $next_item : null); + $template->set_global("previous_item", $previous_item->loaded ? $previous_item : null); + $template->set_global("sibling_count", $photo->parent()->children_count()); + $template->set_global("position", $position); + + $template->content = new View("movie.html"); + + $photo->view_count++; + $photo->save(); + + print $template; + } + + /** + * @see REST_Controller::_update($resource) + */ + public function _update($photo) { + access::required("edit", $photo); + + $form = photo::get_edit_form($photo); + if ($valid = $form->validate()) { + // Make sure that there's not a conflict + if (Database::instance() + ->from("items") + ->where("parent_id", $photo->parent_id) + ->where("id <>", $photo->id) + ->where("name", $form->edit_photo->filename->value) + ->count_records()) { + $form->edit_photo->filename->add_error("conflict", 1); + $valid = false; + } + } + + if ($valid) { + $orig = clone $photo; + $photo->title = $form->edit_photo->title->value; + $photo->description = $form->edit_photo->description->value; + $photo->rename($form->edit_photo->filename->value); + $photo->save(); + + module::event("item_updated", $orig, $photo); + + log::success("content", "Updated photo", "<a href=\"photos/$photo->id\">view</a>"); + message::success(t("Saved photo %photo_title", array("photo_title" => $photo->title))); + + print json_encode( + array("result" => "success", + "location" => url::site("photos/$photo->id"))); + } else { + print json_encode( + array("result" => "error", + "form" => $form->__toString())); + } + } + + /** + * @see REST_Controller::_form_edit($resource) + */ + public function _form_edit($photo) { + access::required("edit", $photo); + print photo::get_edit_form($photo); + } +} diff --git a/modules/gallery/controllers/permissions.php b/modules/gallery/controllers/permissions.php new file mode 100644 index 00000000..b0cee303 --- /dev/null +++ b/modules/gallery/controllers/permissions.php @@ -0,0 +1,80 @@ +<?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 Permissions_Controller extends Controller { + function browse($id) { + $item = ORM::factory("item", $id); + access::required("edit", $item); + + if (!$item->is_album()) { + access::forbidden(); + } + + $view = new View("permissions_browse.html"); + $view->htaccess_works = access::htaccess_works(); + $view->item = $item; + $view->parents = $item->parents(); + $view->form = $this->_get_form($item); + + print $view; + } + + function form($id) { + $item = ORM::factory("item", $id); + access::required("edit", $item); + + if (!$item->is_album()) { + access::forbidden(); + } + + print $this->_get_form($item); + } + + function change($command, $group_id, $perm_id, $item_id) { + access::verify_csrf(); + $group = ORM::factory("group", $group_id); + $perm = ORM::factory("permission", $perm_id); + $item = ORM::factory("item", $item_id); + access::required("edit", $item); + + if ($group->loaded && $perm->loaded && $item->loaded) { + switch($command) { + case "allow": + access::allow($group, $perm->name, $item); + break; + + case "deny": + access::deny($group, $perm->name, $item); + break; + + case "reset": + access::reset($group, $perm->name, $item); + break; + } + } + } + + function _get_form($item) { + $view = new View("permissions_form.html"); + $view->item = $item; + $view->groups = ORM::factory("group")->find_all(); + $view->permissions = ORM::factory("permission")->find_all(); + return $view; + } +} diff --git a/modules/gallery/controllers/photos.php b/modules/gallery/controllers/photos.php new file mode 100644 index 00000000..5d4040cf --- /dev/null +++ b/modules/gallery/controllers/photos.php @@ -0,0 +1,116 @@ +<?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 Photos_Controller extends Items_Controller { + + /** + * @see REST_Controller::_show($resource) + */ + public function _show($photo) { + access::required("view", $photo); + + // We sort by id ascending so for now, find sibling info by doing id based queries. + $next_item = ORM::factory("item") + ->viewable() + ->where("parent_id", $photo->parent_id) + ->where("id >", $photo->id) + ->orderby("id", "ASC") + ->find(); + $previous_item = ORM::factory("item") + ->viewable() + ->where("parent_id", $photo->parent_id) + ->where("id <", $photo->id) + ->orderby("id", "DESC") + ->find(); + $position = ORM::factory("item") + ->viewable() + ->where("parent_id", $photo->parent_id) + ->where("id <=", $photo->id) + ->count_all(); + + $template = new Theme_View("page.html", "photo"); + $template->set_global("item", $photo); + $template->set_global("children", array()); + $template->set_global("children_count", $photo->children_count()); + $template->set_global("parents", $photo->parents()); + $template->set_global("next_item", $next_item->loaded ? $next_item : null); + $template->set_global("previous_item", $previous_item->loaded ? $previous_item : null); + $template->set_global("sibling_count", $photo->parent()->children_count()); + $template->set_global("position", $position); + + $template->content = new View("photo.html"); + + $photo->view_count++; + $photo->save(); + + print $template; + } + + /** + * @see REST_Controller::_update($resource) + */ + public function _update($photo) { + access::required("edit", $photo); + + $form = photo::get_edit_form($photo); + if ($valid = $form->validate()) { + if ($form->edit_photo->filename->value != $photo->name) { + // Make sure that there's not a conflict + if (Database::instance() + ->from("items") + ->where("parent_id", $photo->parent_id) + ->where("id <>", $photo->id) + ->where("name", $form->edit_photo->filename->value) + ->count_records()) { + $form->edit_photo->filename->add_error("conflict", 1); + $valid = false; + } + } + } + + if ($valid) { + $orig = clone $photo; + $photo->title = $form->edit_photo->title->value; + $photo->description = $form->edit_photo->description->value; + $photo->rename($form->edit_photo->filename->value); + $photo->save(); + + module::event("item_updated", $orig, $photo); + + log::success("content", "Updated photo", "<a href=\"photos/$photo->id\">view</a>"); + message::success(t("Saved photo %photo_title", array("photo_title" => $photo->title))); + + print json_encode( + array("result" => "success", + "location" => url::site("photos/$photo->id"))); + } else { + print json_encode( + array("result" => "error", + "form" => $form->__toString())); + } + } + + /** + * @see REST_Controller::_form_edit($resource) + */ + public function _form_edit($photo) { + access::required("edit", $photo); + print photo::get_edit_form($photo); + } +} diff --git a/modules/gallery/controllers/quick.php b/modules/gallery/controllers/quick.php new file mode 100644 index 00000000..643dce30 --- /dev/null +++ b/modules/gallery/controllers/quick.php @@ -0,0 +1,122 @@ +<?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 Quick_Controller extends Controller { + public function pane($id) { + $item = ORM::factory("item", $id); + if (!$item->loaded) { + return ""; + } + + $view = new View("quick_pane.html"); + $view->item = $item; + $view->page_type = Input::instance()->get("page_type"); + print $view; + } + + public function rotate($id, $dir) { + access::verify_csrf(); + $item = ORM::factory("item", $id); + if (!$item->loaded) { + return ""; + } + + $degrees = 0; + switch($dir) { + case "ccw": + $degrees = -90; + break; + + case "cw": + $degrees = 90; + break; + } + + if ($degrees) { + graphics::rotate($item->file_path(), $item->file_path(), array("degrees" => $degrees)); + + list($item->width, $item->height) = getimagesize($item->file_path()); + $item->resize_dirty= 1; + $item->thumb_dirty= 1; + $item->save(); + + graphics::generate($item); + + $parent = $item->parent(); + if ($parent->album_cover_item_id == $item->id) { + copy($item->thumb_path(), $parent->thumb_path()); + $parent->thumb_width = $item->thumb_width; + $parent->thumb_height = $item->thumb_height; + $parent->save(); + } + } + + if (Input::instance()->get("page_type") == "album") { + print json_encode( + array("src" => $item->thumb_url() . "?rnd=" . rand(), + "width" => $item->thumb_width, + "height" => $item->thumb_height)); + } else { + print json_encode( + array("src" => $item->resize_url() . "?rnd=" . rand(), + "width" => $item->resize_width, + "height" => $item->resize_height)); + } + } + + public function make_album_cover($id) { + access::verify_csrf(); + item::make_album_cover(ORM::factory("item", $id)); + + print json_encode(array("result" => "success")); + } + + public function delete($id) { + access::verify_csrf(); + $item = ORM::factory("item", $id); + access::required("edit", $item); + + if ($item->is_album()) { + $msg = t("Deleted album <b>%title</b>", array("title" => $item->title)); + } else { + $msg = t("Deleted photo <b>%title</b>", array("title" => $item->title)); + } + + $item->delete(); + message::success($msg); + + if (Input::instance()->get("page_type") == "album") { + print json_encode(array("result" => "success", "reload" => 1)); + } else { + print json_encode(array("result" => "success", + "location" => url::site("albums/$parent->id"))); + } + } + + public function form_edit($id) { + $item = ORM::factory("item", $id); + access::required("edit", $item); + if ($item->is_album()) { + $form = album::get_edit_form($item); + } else { + $form = photo::get_edit_form($item); + } + print $form; + } +} diff --git a/modules/gallery/controllers/rest.php b/modules/gallery/controllers/rest.php new file mode 100644 index 00000000..11a6bbac --- /dev/null +++ b/modules/gallery/controllers/rest.php @@ -0,0 +1,183 @@ +<?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. + */ +/** + * This abstract controller makes it easy to create a RESTful controller. To use it, create a + * subclass which defines the resource type and implements get/post/put/delete methods, like this: + * + * class Comment_Controller extends REST_Controller { + * protected $resource_type = "comment"; // this tells REST which model to use + * + * public function _index() { + * // Handle GET request to /controller + * } + * + * public function _show(ORM $comment) { + * // Handle GET request to /comments/{comment_id} + * } + * + * public function _update(ORM $comment) { + * // Handle PUT request to /comments/{comment_id} + * } + * + * public function _create(ORM $comment) { + * // Handle POST request to /comments + * } + * + * public function _delete(ORM $comment) { + * // Handle DELETE request to /comments/{comments_id} + * } + * + * public function _form_add($parameters) { + * // Handle GET request to /form/add/comments + * // Show a form for creating a new comment + * } + * + * public function _form_edit(ORM $comment) { + * // Handle GET request to /form/edit/comments + * // Show a form for editing an existing comment + * } + * + * A request to http://example.com/gallery3/comments/3 will result in a call to + * REST_Controller::__call(3) which will load up the comment associated with id 3. If there's + * no such comment, it returns a 404. Otherwise, it will then delegate to + * Comment_Controller::get() with the ORM instance as an argument. + */ +class REST_Controller extends Controller { + protected $resource_type = null; + + public function __construct() { + if ($this->resource_type == null) { + throw new Exception("@todo ERROR_MISSING_RESOURCE_TYPE"); + } + parent::__construct(); + } + + /** + * Handle dispatching for all REST controllers. + */ + public function __call($function, $args) { + // If no parameter was provided after the controller name (eg "/albums") then $function will + // be set to "index". Otherwise, $function is the first parameter, and $args are all + // subsequent parameters. + $request_method = rest::request_method(); + if ($function == "index" && $request_method == "get") { + return $this->_index(); + } + + $resource = ORM::factory($this->resource_type, (int)$function); + if (!$resource->loaded && $request_method != "post") { + return Kohana::show_404(); + } + + if ($request_method != "get") { + access::verify_csrf(); + } + + switch ($request_method) { + case "get": + return $this->_show($resource); + + case "put": + return $this->_update($resource); + + case "delete": + return $this->_delete($resource); + + case "post": + return $this->_create($resource); + } + } + + /* We're editing an existing item, load it from the database. */ + public function form_edit($resource_id) { + if ($this->resource_type == null) { + throw new Exception("@todo ERROR_MISSING_RESOURCE_TYPE"); + } + + // @todo this needs security checks + $resource = ORM::factory($this->resource_type, $resource_id); + if (!$resource->loaded) { + return Kohana::show_404(); + } + + return $this->_form_edit($resource); + } + + /* We're adding a new item, pass along any additional parameters. */ + public function form_add($parameters) { + return $this->_form_add($parameters); + } + + /** + * Perform a GET request on the controller root + * (e.g. http://www.example.com/gallery3/comments) + */ + public function _index() { + throw new Exception("@todo _create NOT IMPLEMENTED"); + } + + /** + * Perform a POST request on this resource + * @param ORM $resource the instance of this resource type + */ + public function _create($resource) { + throw new Exception("@todo _create NOT IMPLEMENTED"); + } + + /** + * Perform a GET request on this resource + * @param ORM $resource the instance of this resource type + */ + public function _show($resource) { + throw new Exception("@todo _show NOT IMPLEMENTED"); + } + + /** + * Perform a PUT request on this resource + * @param ORM $resource the instance of this resource type + */ + public function _update($resource) { + throw new Exception("@todo _update NOT IMPLEMENTED"); + } + + /** + * Perform a DELETE request on this resource + * @param ORM $resource the instance of this resource type + */ + public function _delete($resource) { + throw new Exception("@todo _delete NOT IMPLEMENTED"); + } + + /** + * Present a form for adding a new resource + * @param string part of the URI after the controller name + */ + public function _form_add($parameter) { + throw new Exception("@todo _form_add NOT IMPLEMENTED"); + } + + /** + * Present a form for editing an existing resource + * @param ORM $resource the resource container for instances of this resource type + */ + public function _form_edit($resource) { + throw new Exception("@todo _form_edit NOT IMPLEMENTED"); + } +} diff --git a/modules/gallery/controllers/scaffold.php b/modules/gallery/controllers/scaffold.php new file mode 100644 index 00000000..f0063725 --- /dev/null +++ b/modules/gallery/controllers/scaffold.php @@ -0,0 +1,437 @@ +<?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 Scaffold_Controller extends Template_Controller { + public $template = "scaffold.html"; + + function index() { + $session = Session::instance(); + + set_error_handler(array("Scaffold_Controller", "_error_handler")); + try { + $this->template->album_count = ORM::factory("item")->where("type", "album")->count_all(); + $this->template->photo_count = ORM::factory("item")->where("type", "photo")->count_all(); + $this->template->album_tree = $this->_load_album_tree(); + $this->template->add_photo_html = $this->_get_add_photo_html(); + } catch (Exception $e) { + $this->template->album_count = 0; + $this->template->photo_count = 0; + $this->template->deepest_photo = null; + $this->template->album_tree = array(); + $this->template->add_photo_html = ""; + } + + $this->_load_comment_info(); + $this->_load_tag_info(); + + restore_error_handler(); + + if (!empty($session) && $session->get("profiler", false)) { + $profiler = new Profiler(); + $profiler->render(); + } + } + + + function add_photos() { + $path = trim($this->input->post("path")); + $parent_id = (int)$this->input->post("parent_id"); + $parent = ORM::factory("item", $parent_id); + if (!$parent->loaded) { + throw new Exception("@todo BAD_ALBUM"); + } + + batch::start(); + cookie::set("add_photos_path", $path); + $photo_count = 0; + foreach (glob("$path/*.[Jj][Pp][Gg]") as $file) { + set_time_limit(30); + photo::create($parent, $file, basename($file), basename($file)); + $photo_count++; + } + batch::stop(); + + if ($photo_count > 0) { + log::success("content", "(scaffold) Added $photo_count photos", + html::anchor("albums/$parent_id", "View album")); + } + + url::redirect("scaffold"); + } + + function add_albums_and_photos($count, $desired_type=null) { + srand(time()); + $parents = ORM::factory("item")->where("type", "album")->find_all()->as_array(); + $owner_id = user::active()->id; + + $test_images = glob(APPPATH . "tests/images/*.[Jj][Pp][Gg]"); + + batch::start(); + $album_count = $photo_count = 0; + for ($i = 0; $i < $count; $i++) { + set_time_limit(30); + + $parent = $parents[array_rand($parents)]; + $parent->reload(); + $type = $desired_type; + if (!$type) { + $type = rand(0, 10) ? "photo" : "album"; + } + if ($type == "album") { + $thumb_size = module::get_var("core", "thumb_size"); + $parents[] = album::create( + $parent, "rnd_" . rand(), "Rnd $i", "random album $i", $owner_id) + ->save(); + $album_count++; + } else { + $photo_index = rand(0, count($test_images) - 1); + photo::create($parent, $test_images[$photo_index], basename($test_images[$photo_index]), + "rnd_" . rand(), "sample thumb", $owner_id); + $photo_count++; + } + } + batch::stop(); + + if ($photo_count > 0) { + log::success("content", "(scaffold) Added $photo_count photos"); + } + + if ($album_count > 0) { + log::success("content", "(scaffold) Added $album_count albums"); + } + url::redirect("scaffold"); + } + + function random_phrase($count) { + static $words; + if (empty($words)) { + $sample_text = "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium + laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi + architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas + sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione + voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, + amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut + labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis + nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi + consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam + nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla + pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis + praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi + sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt + mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et + expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque + nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas + assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis + debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et + molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut + reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores + repellat."; + $words = preg_split('/\s+/', $sample_text); + } + + $chosen = array(); + for ($i = 0; $i < $count; $i++) { + $chosen[] = $words[array_rand($words)]; + } + + return implode(' ', $chosen); + } + + function add_comments($count) { + srand(time()); + $photos = ORM::factory("item")->where("type", "photo")->find_all()->as_array(); + $users = ORM::factory("user")->find_all()->as_array(); + + if (empty($photos)) { + url::redirect("scaffold"); + } + + if (module::is_active("akismet")) { + akismet::$test_mode = 1; + } + for ($i = 0; $i < $count; $i++) { + $photo = $photos[array_rand($photos)]; + $author = $users[array_rand($users)]; + $guest_name = ucfirst($this->random_phrase(rand(1, 3))); + $guest_email = sprintf("%s@%s.com", $this->random_phrase(1), $this->random_phrase(1)); + $guest_url = sprintf("http://www.%s.com", $this->random_phrase(1)); + comment::create($photo, $author, $this->random_phrase(rand(8, 500)), + $guest_name, $guest_email, $guest_url); + } + + url::redirect("scaffold"); + } + + function add_tags($count) { + $items = ORM::factory("item")->find_all()->as_array(); + + if (!empty($items)) { + $tags = $this->_generateTags($count); + + while ($count-- > 0) { + $tag_name = $tags[array_rand($tags)]; + $item = $items[array_rand($items)]; + + tag::add($item, $tag_name); + } + } + + url::redirect("scaffold"); + } + + private function _generateTags($number){ + // Words from lorem2.com + $words = explode( + " ", + "Lorem ipsum dolor sit amet consectetuer adipiscing elit Donec odio Quisque volutpat " . + "mattis eros Nullam malesuada erat ut turpis Suspendisse urna nibh viverra non " . + "semper suscipit posuere a pede Donec nec justo eget felis facilisis " . + "fermentum Aliquam porttitor mauris sit amet orci Aenean dignissim pellentesque " . + "felis Morbi in sem quis dui placerat ornare Pellentesque odio nisi euismod in " . + "pharetra a ultricies in diam Sed arcu Cras consequat Praesent dapibus neque " . + "id cursus faucibus tortor neque egestas augue eu vulputate magna eros eu " . + "erat Aliquam erat volutpat Nam dui mi tincidunt quis accumsan porttitor " . + "facilisis luctus metus Phasellus ultrices nulla quis nibh Quisque a " . + "lectus Donec consectetuer ligula vulputate sem tristique cursus Nam nulla quam " . + "gravida non commodo a sodales sit amet nisi Pellentesque fermentum " . + "dolor Aliquam quam lectus facilisis auctor ultrices ut elementum vulputate " . + "nunc Sed adipiscing ornare risus Morbi est est blandit sit amet sagittis vel " . + "euismod vel velit Pellentesque egestas sem Suspendisse commodo ullamcorper " . + "magna"); + + while ($number--) { + $results[] = $words[array_rand($words, 1)]; + } + return $results; + } + + function _error_handler($x) { + } + + private function _load_comment_info() { + if (class_exists("Comment_Model")) { + $this->template->comment_count = ORM::factory("comment")->count_all(); + } else { + $this->template->comment_count = 0; + } + } + + private function _load_tag_info() { + if (class_exists("Tag_Model")) { + $this->template->tag_count = ORM::factory("tag")->count_all(); + $this->template->most_tagged = Database::instance() + ->select("item_id AS id", "COUNT(tag_id) AS count") + ->from("items_tags") + ->groupby("item_id") + ->orderby("count", "DESC") + ->limit(1) + ->get() + ->current(); + } else { + $this->template->tag_count = 0; + $this->template->most_tagged = 0; + } + } + + function install($module_name, $redirect=true) { + $to_install = array(); + if ($module_name == "*") { + foreach (module::available() as $module_name => $info) { + if (empty($info->installed)) { + $to_install[] = $module_name; + } + } + } else { + $to_install[] = $module_name; + } + + foreach ($to_install as $module_name) { + if ($module_name != "core") { + require_once(DOCROOT . "modules/${module_name}/helpers/${module_name}_installer.php"); + } + module::install($module_name); + } + + if ($redirect) { + url::redirect("scaffold"); + } + } + + + public function package() { + $this->auto_render = false; + $db = Database::instance(); + + // Drop all tables + foreach ($db->list_tables() as $table) { + $db->query("DROP TABLE IF EXISTS `$table`"); + } + + // Clean out data + dir::unlink(VARPATH . "uploads"); + dir::unlink(VARPATH . "albums"); + dir::unlink(VARPATH . "resizes"); + dir::unlink(VARPATH . "thumbs"); + dir::unlink(VARPATH . "modules"); + dir::unlink(VARPATH . "tmp"); + + $db->clear_cache(); + module::$modules = array(); + module::$active = array(); + + // Use a known random seed so that subsequent packaging runs will reuse the same random + // numbers, keeping our install.sql file more stable. + srand(0); + + try { + core_installer::install(true); + module::load_modules(); + + foreach (array("user", "comment", "organize", "info", "rss", + "search", "slideshow", "tag") as $module_name) { + module::install($module_name); + module::activate($module_name); + } + } catch (Exception $e) { + Kohana::log("error", $e->getTraceAsString()); + print $e->getTrace(); + throw $e; + } + + url::redirect("scaffold/dump_database"); + } + + public function dump_database() { + $this->auto_render = false; + + // We now have a clean install with just the packages that we want. Make sure that the + // database is clean too. + $db = Database::instance(); + $db->query("TRUNCATE {sessions}"); + $db->query("TRUNCATE {logs}"); + $db->query("DELETE FROM {vars} WHERE `module_name` = 'core' AND `name` = '_cache'"); + $db->update("users", array("password" => ""), array("id" => 1)); + $db->update("users", array("password" => ""), array("id" => 2)); + + $dbconfig = Kohana::config('database.default'); + $conn = $dbconfig["connection"]; + $pass = $conn["pass"] ? "-p{$conn['pass']}" : ""; + $sql_file = DOCROOT . "installer/install.sql"; + if (!is_writable($sql_file)) { + print "$sql_file is not writeable"; + return; + } + $command = "mysqldump --compact --add-drop-table -h{$conn['host']} " . + "-u{$conn['user']} $pass {$conn['database']} > $sql_file"; + exec($command, $output, $status); + if ($status) { + print "<pre>"; + print "$command\n"; + print "Failed to dump database\n"; + print implode("\n", $output); + return; + } + + // Post-process the sql file + $buf = ""; + $root_timestamp = ORM::factory("item", 1)->created; + foreach (file($sql_file) as $line) { + // Prefix tables + $line = preg_replace( + "/(CREATE TABLE|IF EXISTS|INSERT INTO) `{$dbconfig['table_prefix']}(\w+)`/", "\\1 {\\2}", + $line); + + // Normalize dates + $line = preg_replace("/,$root_timestamp,/", ",UNIX_TIMESTAMP(),", $line); + $buf .= $line; + } + $fd = fopen($sql_file, "wb"); + fwrite($fd, $buf); + fclose($fd); + + url::redirect("scaffold/dump_var"); + } + + public function dump_var() { + $this->auto_render = false; + + $objects = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(VARPATH), + RecursiveIteratorIterator::SELF_FIRST); + + $var_file = DOCROOT . "installer/init_var.php"; + if (!is_writable($var_file)) { + print "$var_file is not writeable"; + return; + } + + $paths = array(); + foreach($objects as $name => $file){ + if ($file->getBasename() == "database.php") { + continue; + } else if (basename($file->getPath()) == "logs") { + continue; + } + + if ($file->isDir()) { + $paths[] = "VARPATH . \"" . substr($name, strlen(VARPATH)) . "\""; + } else { + // @todo: serialize non-directories + print "Unknown file: $name"; + return; + } + } + // Sort the paths so that the var file is stable + sort($paths); + + $fd = fopen($var_file, "w"); + fwrite($fd, "<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>\n"); + fwrite($fd, "<?php\n"); + foreach ($paths as $path) { + fwrite($fd, "!file_exists($path) && mkdir($path);\n"); + } + fclose($fd); + url::redirect("scaffold"); + } + + private function _load_album_tree() { + $tree = array(); + foreach (ORM::factory("item")->where("type", "album")->find_all() as $album) { + if ($album->parent_id) { + $tree[$album->parent_id]->children[] = $album->id; + } + $tree[$album->id]->album = $album; + $tree[$album->id]->children = array(); + } + + return $tree; + } + + public function form($arg1, $arg2) { + if ($arg1 == "add" && $arg2 == "photos") { + print $this->_get_add_photo_html(); + } + $this->auto_render = false; + } + + public function _get_add_photo_html($parent_id=1) { + $parent = ORM::factory("item", $parent_id); + return photo::get_add_form($parent); + } +} diff --git a/modules/gallery/controllers/simple_uploader.php b/modules/gallery/controllers/simple_uploader.php new file mode 100644 index 00000000..bdf9582f --- /dev/null +++ b/modules/gallery/controllers/simple_uploader.php @@ -0,0 +1,86 @@ +<?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 Simple_Uploader_Controller extends Controller { + public function app($id) { + $item = ORM::factory("item", $id); + access::required("edit", $item); + + $v = new View("simple_uploader.html"); + $v->item = $item; + print $v; + } + + public function start() { + batch::start(); + } + + public function add_photo($id) { + $album = ORM::factory("item", $id); + access::required("add", $album); + access::verify_csrf(); + + $file_validation = new Validation($_FILES); + $file_validation->add_rules("Filedata", "upload::valid", "upload::type[gif,jpg,png,flv,mp4]"); + if ($file_validation->validate()) { + + // SimpleUploader.swf does not yet call /start directly, so simulate it here for now. + if (!batch::in_progress()) { + batch::start(); + } + + $temp_filename = upload::save("Filedata"); + try { + $name = substr(basename($temp_filename), 10); // Skip unique identifier Kohana adds + $title = $this->convert_filename_to_title($name); + $path_info = pathinfo($temp_filename); + if (array_key_exists("extension", $path_info) && + in_array(strtolower($path_info["extension"]), array("flv", "mp4"))) { + $movie = movie::create($album, $temp_filename, $name, $title); + log::success("content", t("Added a movie"), + html::anchor("movies/$movie->id", t("view movie"))); + } else { + $photo = photo::create($album, $temp_filename, $name, $title); + log::success("content", t("Added a photo"), + html::anchor("photos/$photo->id", t("view photo"))); + } + } catch (Exception $e) { + unlink($temp_filename); + throw $e; + } + unlink($temp_filename); + } + print "File Received"; + } + + /** + * We should move this into a helper somewhere.. but where is appropriate? + */ + private function convert_filename_to_title($filename) { + $title = strtr($filename, "_", " "); + $title = preg_replace("/\..*?$/", "", $title); + $title = preg_replace("/ +/", " ", $title); + return $title; + } + + public function finish() { + batch::stop(); + print json_encode(array("result" => "success")); + } +} diff --git a/modules/gallery/css/debug.css b/modules/gallery/css/debug.css new file mode 100644 index 00000000..fe5665ad --- /dev/null +++ b/modules/gallery/css/debug.css @@ -0,0 +1,28 @@ +.gAnnotatedThemeBlock { + border: 1px solid #C00; + clear: both; + margin: 1em; + padding: 1em; + position: relative; +} + +.gAnnotatedThemeBlock_album_top { + float: right; +} + +.gAnnotatedThemeBlock_header_bottom { + float: right; +} + +.gAnnotatedThemeBlock div.title { + background: #C00; + border: 1px solid black; + color: white; + font-size: 110%; + padding: 4px; + position: absolute; + right: -1em; + top: -1em; + text-align: left; + -moz-border-radius: 5%; +} diff --git a/modules/gallery/css/l10n_client.css b/modules/gallery/css/l10n_client.css new file mode 100644 index 00000000..8973715f --- /dev/null +++ b/modules/gallery/css/l10n_client.css @@ -0,0 +1,185 @@ +// TODO(andy_st): Add original copyright notice from Drupal l10_client. +// TODO(andy_st): Add G3 copyright notice. +// TODO(andy_st): clean up formatting to match our other CSS files. + +/* $Id: l10n_client.css,v 1.6 2008/09/09 10:48:20 goba Exp $ */ + +/* width percentages add to 99% rather than 100% to prevent float +overflows from occurring in an unnamed browser that can't decide +how it wants to round. */ + +/* l10n_client container */ +#l10n-client { + text-align:left; + z-index:99; + line-height:1em; + color:#000; background:#fff; + position:fixed; + width:100%; height: 2em; + bottom:0px; left:0px; + overflow:hidden;} + + * html #l10n-client { + position:static;} + +#l10n-client-string-select .string-list, +#l10n-client-string-editor .source, +#l10n-client-string-editor .editor { + height:20em;} + +#l10n-client .labels { + overflow:hidden; + position:relative; + height:2em; + color:#fff; + background:#37a;} + + #l10n-client .labels .label { + display:none;} + + /* Panel toggle button (span) */ + #l10n-client .labels .toggle { + cursor:pointer; + display:block; + position:absolute; right:0em; + padding: 0em .75em; height:2em; line-height:2em; + text-transform:uppercase; + text-align:center; background:#000;} + + /* Panel labels */ + #l10n-client h2 { + border-left:1px solid #fff; + height:1em; line-height:1em; + padding: .5em; margin:0px; + font-size:1em; + text-transform:uppercase;} + + #l10n-client .strings h2 { + border:0px;} + + /* 25 + 37 + 37 = 99 */ + #l10n-client .strings { + width:25%; float:left;} + + #l10n-client .source { + width:37%; float:left;} + + #l10n-client .translation { + width:37%; float:left;} + +/* Translatable string list */ +#l10n-client-string-select { + display:none; + float:left; + width:25%;} + + #l10n-client .string-list { + height:17em; + overflow:auto; + list-style:none; list-style-image:none; + margin:0em; padding:0em;} + + #l10n-client .string-list li { + font-size:.9em; + line-height:1.5em; + cursor:default; + background:transparent; + list-style:none; list-style-image:none; + border-bottom:1px solid #ddd; + padding:.25em .5em; + margin:0em;} + + /* Green for translated */ + #l10n-client .string-list li.translated { + border-bottom-color:#9c3; + background:#cf6; color:#360;} + + #l10n-client .string-list li.translated:hover { + background: #df8;} + + #l10n-client .string-list li.translated:active { + background: #9c3;} + + #l10n-client .string-list li.hidden { + display:none;} + + /* Gray + Blue hover for untranslated */ + #l10n-client .string-list li.untranslated {} + + #l10n-client .string-list li.untranslated:hover { + background: #ace;} + + #l10n-client .string-list li.untranslated:active { + background: #8ac;} + + /* Selected string is indicated by bold text */ + #l10n-client .string-list li.active { + font-weight:bold;} + + #l10n-client #gL10nSearchForm { + background:#eee; + text-align:center; + height:2em; line-height:2em; + margin:0em; padding:.5em .5em; + } + + #l10n-client #gL10nSearchForm .form-item, + #l10n-client #gL10nSearchForm input.form-text, + #l10n-client #gL10nSearchForm #search-filter-go, + #l10n-client #gL10nSearchForm #search-filter-clear { + display:inline; + vertical-align:middle; + } + + #l10n-client #gL10nSearchForm .form-item { + margin:0em; + padding:0em; + } + + #l10n-client #gL10nSearchForm input.form-text { + width:80%; + } + + #l10n-client #gL10nSearchForm #search-filter-clear { + width:10%; + margin:0em; + } + + +#l10n-client-string-editor { + display:none; + float:left; + width:74%;} + + #l10n-client-string-editor .source { + overflow:hidden; + width:50%; float:left;} + + #l10n-client-string-editor .source .source-text { + line-height:1.5em; + background:#eee; + height:16em; margin:1em; padding:1em; + overflow:auto;} + + #l10n-client-string-editor .translation { + overflow:hidden; + width:49%; float:right;} + +#gL10nClientSaveForm { + padding:0em;} + + #gL10nClientSaveForm .form-textarea { + height:13em; + font-size:1em; line-height:1.25em; + width:95%;} + + #gL10nClientSaveForm .form-submit { + margin-top: 0em;} + + +#l10n-client form ul, +#l10n-client form li, +#l10n-client form input[type=submit], +#l10n-client form input[type=text] { + display: inline ! important ; +} diff --git a/modules/gallery/css/quick.css b/modules/gallery/css/quick.css new file mode 100644 index 00000000..02f9953e --- /dev/null +++ b/modules/gallery/css/quick.css @@ -0,0 +1,40 @@ +.gItem:hover { + background-color: #cfdeff; +} + +.gQuick { + border: none !important; + margin: 0 !important; + padding: 0 !important; +} + +#gQuickPane { + background: #000; + border-bottom: 1px solid #ccc; + opacity: 0.9; +} + +#gQuickPane a { + cursor: pointer; + float: left; + margin: 4px; +} + +#gQuickPaneOptions { + background: #000; + float: left; + width: 100%; +} + +#gQuickPaneOptions li a { + display: block; + float: none; + width: auto; + margin: 0; + padding: .5em .5em .5em .8em; + text-align: left; +} + +#gQuickPaneOptions li a:hover { + background-color: #4d4d4d; +} diff --git a/modules/gallery/helpers/MY_remote.php b/modules/gallery/helpers/MY_remote.php new file mode 100644 index 00000000..4abf5bf1 --- /dev/null +++ b/modules/gallery/helpers/MY_remote.php @@ -0,0 +1,163 @@ +<?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 remote extends remote_Core { + + static function post($url, $post_data_array, $extra_headers=array()) { + $post_data_raw = self::_encode_post_data($post_data_array, $extra_headers); + + /* Read the web page into a buffer */ + list ($response_status, $response_headers, $response_body) = + self::do_request($url, 'POST', $extra_headers, $post_data_raw); + + return array($response_body, $response_status, $response_headers); + } + + static function success($response_status) { + return preg_match("/^HTTP\/\d+\.\d+\s2\d{2}(\s|$)/", trim($response_status)); + } + + /** + * Encode the post data. For each key/value pair, urlencode both the key and the value and then + * concatenate together. As per the specification, each key/value pair is separated with an + * ampersand (&) + * @param array $post_data_array the key/value post data + * @param array $extra_headers extra headers to pass to the server + * @return string the encoded post data + */ + private static function _encode_post_data($post_data_array, &$extra_headers) { + $post_data_raw = ''; + foreach ($post_data_array as $key => $value) { + if (!empty($post_data_raw)) { + $post_data_raw .= '&'; + } + $post_data_raw .= urlencode($key) . '=' . urlencode($value); + } + + $extra_headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $extra_headers['Content-Length'] = strlen($post_data_raw); + + return $post_data_raw; + } + + /** + * A single request, without following redirects + * + * @todo: Handle redirects? If so, only for GET (i.e. not for POST), and use G2's WebHelper_simple::_parseLocation logic. + */ + static function do_request($url, $method='GET', $headers=array(), $body='') { + /* Convert illegal characters */ + $url = str_replace(' ', '%20', $url); + + $url_components = self::_parse_url_for_fsockopen($url); + $handle = fsockopen( + $url_components['fsockhost'], $url_components['port'], $errno, $errstr, 5); + if (empty($handle)) { + // log "Error $errno: '$errstr' requesting $url"; + return array(null, null, null); + } + + $header_lines = array('Host: ' . $url_components['host']); + foreach ($headers as $key => $value) { + $header_lines[] = $key . ': ' . $value; + } + + $success = fwrite($handle, sprintf("%s %s HTTP/1.0\r\n%s\r\n\r\n%s", + $method, + $url_components['uri'], + implode("\r\n", $header_lines), + $body)); + if (!$success) { + // Zero bytes written or false was returned + // log "fwrite failed in requestWebPage($url)" . ($success === false ? ' - false' : '' + return array(null, null, null); + } + fflush($handle); + + /* + * Read the status line. fgets stops after newlines. The first line is the protocol + * version followed by a numeric status code and its associated textual phrase. + */ + $response_status = trim(fgets($handle, 4096)); + if (empty($response_status)) { + // 'Empty http response code, maybe timeout' + return array(null, null, null); + } + + /* Read the headers */ + $response_headers = array(); + while (!feof($handle)) { + $line = trim(fgets($handle, 4096)); + if (empty($line)) { + break; + } + + /* Normalize the line endings */ + $line = str_replace("\r", '', $line); + + list ($key, $value) = explode(':', $line, 2); + if (isset($response_headers[$key])) { + if (!is_array($response_headers[$key])) { + $response_headers[$key] = array($response_headers[$key]); + } + $response_headers[$key][] = trim($value); + } else { + $response_headers[$key] = trim($value); + } + } + + /* Read the body */ + $response_body = ''; + while (!feof($handle)) { + $response_body .= fread($handle, 4096); + } + fclose($handle); + + return array($response_status, $response_headers, $response_body); + } + + /** + * Prepare for fsockopen call. + * @param string $url + * @return array url components + * @access private + */ + private static function _parse_url_for_fsockopen($url) { + $url_components = parse_url($url); + if (strtolower($url_components['scheme']) == 'https') { + $url_components['fsockhost'] = 'ssl://' . $url_components['host']; + $default_port = 443; + } else { + $url_components['fsockhost'] = $url_components['host']; + $default_port = 80; + } + if (empty($url_components['port'])) { + $url_components['port'] = $default_port; + } + if (empty($url_components['path'])) { + $url_components['path'] = '/'; + } + $uri = $url_components['path'] + . (empty($url_components['query']) ? '' : '?' . $url_components['query']); + /* Unescape ampersands, since if the url comes from form input it will be escaped */ + $url_components['uri'] = str_replace('&', '&', $uri); + return $url_components; + } +} + diff --git a/modules/gallery/helpers/MY_url.php b/modules/gallery/helpers/MY_url.php new file mode 100644 index 00000000..81dcbe1e --- /dev/null +++ b/modules/gallery/helpers/MY_url.php @@ -0,0 +1,81 @@ +<?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 url extends url_Core { + static function site($uri, $protocol=false) { + if (($pos = strpos($uri, "?")) !== false) { + list ($uri, $query) = explode("?", $uri, 2); + $query = "?$query"; + } else { + $query = ""; + } + + $parts = explode("/", $uri, 3); + if ($parts[0] == "albums" || $parts[0] == "photos") { + $uri = model_cache::get("item", $parts[1])->relative_path(); + } + return parent::site($uri . $query, $protocol); + } + + static function parse_url() { + if (Router::$controller) { + return; + } + + $count = count(Router::$segments); + foreach (ORM::factory("item") + ->where("name", html_entity_decode(Router::$segments[$count - 1], ENT_QUOTES)) + ->where("level", $count + 1) + ->find_all() as $match) { + if ($match->relative_path() == html_entity_decode(Router::$current_uri, ENT_QUOTES)) { + $item = $match; + } + } + + if (!empty($item)) { + Router::$controller = "{$item->type}s"; + Router::$controller_path = APPPATH . "controllers/{$item->type}s.php"; + Router::$method = $item->id; + } + } + + /** + * Just like url::file() except that it returns an absolute URI + */ + static function abs_file($path) { + return url::base( + false, (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') ? 'http' : 'https') . $path; + } + + /** + * Just like url::site() except that it returns an absolute URI and + * doesn't take a protocol parameter. + */ + static function abs_site($path) { + return url::site( + $path, (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') ? 'http' : 'https'); + } + + /** + * Just like url::current except that it returns an absolute URI + */ + static function abs_current($qs=false) { + return self::abs_site(url::current($qs)); + } +} diff --git a/modules/gallery/helpers/access.php b/modules/gallery/helpers/access.php new file mode 100644 index 00000000..64ce91fa --- /dev/null +++ b/modules/gallery/helpers/access.php @@ -0,0 +1,628 @@ +<?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. + */ +/** + * API for Gallery Access control. + * + * Permissions are hierarchical, and apply only to groups and albums. They cascade down from the + * top of the Gallery to the bottom, so if you set a permission in the root album, that permission + * applies for any sub-album unless the sub-album overrides it. Likewise, any permission applied + * to an album applies to any photos inside the album. Overrides can be applied at any level of + * the hierarchy for any permission other than View permissions. + * + * View permissions are an exceptional case. In the case of viewability, we want to ensure that + * if an album's parent is inaccessible, then this album must be inaccessible also. So while view + * permissions cascade downwards and you're free to set the ALLOW permission on any album, that + * ALLOW permission will be ignored unless all that album's parents are marked ALLOW also. + * + * Implementatation Notes: + * + * Notes refer to this example album hierarchy: + * A1 + * / \ + * A2 A3 + * / \ + * A4 A5 + * + * o We have the concept of "intents". A user can specify that he intends for A3 to be + * inaccessible (ie: a DENY on the "view" permission to the EVERYBODY group). Once A3 is + * inaccessible, A5 can never be displayed to that group. If A1 is made inaccessible, then the + * entire tree is hidden. If subsequently A1 is made accessible, then the whole tree is + * available again *except* A3 and below since the user's "intent" for A3 is maintained. + * + * o Intents are specified as <group_id, perm, item_id> tuples. It would be inefficient to check + * these tuples every time we want to do a lookup, so we use these intents to create an entire + * table of permissions for easy lookup in the Access_Cache_Model. There's a 1:1 mapping + * between Item_Model and Access_Cache_Model entries. + * + * o For efficiency, we create columns in Access_Intent_Model and Access_Cache_Model for each of + * the possible Group_Model and Permission_Model combinations. This may lead to performance + * issues for very large Gallery installs, but for small to medium sized ones (5-10 groups, 5-10 + * permissions) it's especially efficient because there's a single field value for each + * group/permission/item combination. + * + * o For efficiency, we store the cache columns for view permissions directly in the Item_Model. + * This means that we can filter items by group/permission combination without doing any table + * joins making for an especially efficient permission check at the expense of having to + * maintain extra columns for each item. + * + * o If at any time the Access_Cache_Model becomes invalid, we can rebuild the entire table from + * the Access_Intent_Model + */ +class access_Core { + const DENY = 0; + const ALLOW = 1; + const UNKNOWN = 2; + + /** + * Does the active user have this permission on this item? + * + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function can($perm_name, $item) { + if (!$item->loaded) { + return false; + } + + if (user::active()->admin) { + return true; + } + + $resource = $perm_name == "view" ? + $item : model_cache::get("access_cache", $item->id, "item_id"); + foreach (user::group_ids() as $id) { + if ($resource->__get("{$perm_name}_$id") === self::ALLOW) { + return true; + } + } + return false; + } + + /** + * If the active user does not have this permission, failed with an access::forbidden(). + * + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function required($perm_name, $item) { + if (!self::can($perm_name, $item)) { + self::forbidden(); + } + } + + /** + * Does this group have this permission on this item? + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return boolean + */ + static function group_can($group, $perm_name, $item) { + $resource = $perm_name == "view" ? + $item : model_cache::get("access_cache", $item->id, "item_id"); + return $resource->__get("{$perm_name}_{$group->id}") === self::ALLOW; + } + + /** + * Return this group's intent for this permission on this item. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return integer access::ALLOW, access::DENY or null for no intent + */ + static function group_intent($group, $perm_name, $item) { + $intent = model_cache::get("access_intent", $item->id, "item_id"); + return $intent->__get("{$perm_name}_{$group->id}"); + } + + /** + * Is the permission on this item locked by a parent? If so return the nearest parent that + * locks it. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return ORM_Model item that locks this one + */ + static function locked_by($group, $perm_name, $item) { + if ($perm_name != "view") { + return null; + } + + // For view permissions, if any parent is self::DENY, then those parents lock this one. + // Return + $lock = ORM::factory("item") + ->where("`left` <= $item->left") + ->where("`right` >= $item->right") + ->where("items.id <> $item->id") + ->join("access_intents", "items.id", "access_intents.item_id") + ->where("access_intents.view_$group->id", 0) + ->orderby("level", "DESC") + ->limit(1) + ->find(); + + if ($lock->loaded) { + return $lock; + } else { + return null; + } + } + + /** + * Terminate immediately with an HTTP 503 Forbidden response. + */ + static function forbidden() { + throw new Exception("@todo FORBIDDEN", 503); + } + + /** + * Internal method to set a permission + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @param boolean $value + */ + private static function _set(Group_Model $group, $perm_name, $album, $value) { + if (get_class($group) != "Group_Model") { + throw new Exception("@todo PERMISSIONS_ONLY_WORK_ON_GROUPS"); + } + if (!$album->loaded) { + throw new Exception("@todo INVALID_ALBUM $album->id"); + } + if (!$album->is_album()) { + throw new Exception("@todo INVALID_ALBUM_TYPE not an album"); + } + $access = model_cache::get("access_intent", $album->id, "item_id"); + $access->__set("{$perm_name}_{$group->id}", $value); + $access->save(); + + if ($perm_name == "view") { + self::_update_access_view_cache($group, $album); + } else { + self::_update_access_non_view_cache($group, $perm_name, $album); + } + + self::_update_htaccess_files($album, $group, $perm_name, $value); + } + + /** + * Allow a group to have a permission on an item. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + */ + static function allow($group, $perm_name, $item) { + self::_set($group, $perm_name, $item, self::ALLOW); + } + + /** + * Deny a group the given permission on an item. + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + */ + static function deny($group, $perm_name, $item) { + self::_set($group, $perm_name, $item, self::DENY); + } + + /** + * Unset the given permission for this item and use inherited values + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + */ + static function reset($group, $perm_name, $item) { + if ($item->id == 1) { + throw new Exception("@todo CANT_RESET_ROOT_PERMISSION"); + } + self::_set($group, $perm_name, $item, null); + } + + /** + * Register a permission so that modules can use it. + * + * @param string $name The internal name for for this permission + * @param string $display_name The internationalized version of the displayable name + * @return void + */ + static function register_permission($name, $display_name) { + $permission = ORM::factory("permission", $name); + if ($permission->loaded) { + throw new Exception("@todo PERMISSION_ALREADY_EXISTS $name"); + } + $permission->name = $name; + $permission->display_name = $display_name; + $permission->save(); + + foreach (self::_get_all_groups() as $group) { + self::_add_columns($name, $group); + } + } + + /** + * Delete a permission. + * + * @param string $perm_name + * @return void + */ + static function delete_permission($name) { + foreach (self::_get_all_groups() as $group) { + self::_drop_columns($name, $group); + } + $permission = ORM::factory("permission")->where("name", $name)->find(); + if ($permission->loaded) { + $permission->delete(); + } + } + + /** + * Add the appropriate columns for a new group + * + * @param Group_Model $group + * @return void + */ + static function add_group($group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + self::_add_columns($perm->name, $group); + } + } + + /** + * Remove a group's permission columns (usually when it's deleted) + * + * @param Group_Model $group + * @return void + */ + static function delete_group($group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + self::_drop_columns($perm->name, $group); + } + } + + /** + * Add new access rows when a new item is added. + * + * @param Item_Model $item + * @return void + */ + static function add_item($item) { + $access_intent = ORM::factory("access_intent", $item->id); + if ($access_intent->loaded) { + throw new Exception("@todo ITEM_ALREADY_ADDED $item->id"); + } + $access_intent = ORM::factory("access_intent"); + $access_intent->item_id = $item->id; + $access_intent->save(); + + // Create a new access cache entry and copy the parents values. + $access_cache = ORM::factory("access_cache"); + $access_cache->item_id = $item->id; + if ($item->id != 1) { + $parent_access_cache = + ORM::factory("access_cache")->where("item_id", $item->parent()->id)->find(); + foreach (self::_get_all_groups() as $group) { + foreach (ORM::factory("permission")->find_all() as $perm) { + $field = "{$perm->name}_{$group->id}"; + if ($perm->name == "view") { + $item->$field = $item->parent()->$field; + } else { + $access_cache->$field = $parent_access_cache->$field; + } + } + } + } + $item->save(); + $access_cache->save(); + } + + /** + * Delete appropriate access rows when an item is deleted. + * + * @param Item_Model $item + * @return void + */ + static function delete_item($item) { + ORM::factory("access_intent")->where("item_id", $item->id)->find()->delete(); + ORM::factory("access_cache")->where("item_id", $item->id)->find()->delete(); + } + + /** + * Verify our Cross Site Request Forgery token is valid, else throw an exception. + */ + static function verify_csrf() { + $input = Input::instance(); + if ($input->post("csrf", $input->get("csrf", null)) !== Session::instance()->get("csrf")) { + self::forbidden(); + } + } + + /** + * Get the Cross Site Request Forgery token for this session. + * @return string + */ + static function csrf_token() { + $session = Session::instance(); + $csrf = $session->get("csrf"); + if (empty($csrf)) { + $csrf = md5(rand()); + $session->set("csrf", $csrf); + } + return $csrf; + } + + /** + * Generate an <input> element containing the Cross Site Request Forgery token for this session. + * @return string + */ + static function csrf_form_field() { + return "<input type=\"hidden\" name=\"csrf\" value=\"" . self::csrf_token() . "\"/>"; + } + + /** + * Internal method to get all available groups. + * + * @return ORM_Iterator + */ + private static function _get_all_groups() { + // When we build the core package, it's possible that the user module is not installed yet. + // This is ok at packaging time, so work around it. + if (module::is_active("user")) { + return ORM::factory("group")->find_all(); + } else { + return array(); + } + } + + /** + * Internal method to remove Permission/Group columns + * + * @param Group_Model $group + * @param string $perm_name + * @return void + */ + private static function _drop_columns($perm_name, $group) { + $db = Database::instance(); + $field = "{$perm_name}_{$group->id}"; + $cache_table = $perm_name == "view" ? "items" : "access_caches"; + $db->query("ALTER TABLE {{$cache_table}} DROP `$field`"); + $db->query("ALTER TABLE {access_intents} DROP `$field`"); + ORM::factory("access_intent")->clear_cache(); + } + + /** + * Internal method to add Permission/Group columns + * + * @param Group_Model $group + * @param string $perm_name + * @return void + */ + private static function _add_columns($perm_name, $group) { + $db = Database::instance(); + $field = "{$perm_name}_{$group->id}"; + $cache_table = $perm_name == "view" ? "items" : "access_caches"; + $db->query("ALTER TABLE {{$cache_table}} ADD `$field` SMALLINT NOT NULL DEFAULT 0"); + $db->query("ALTER TABLE {access_intents} ADD `$field` BOOLEAN DEFAULT NULL"); + $db->update("access_intents", array($field => 0), array("item_id" => 1)); + ORM::factory("access_intent")->clear_cache(); + } + + /** + * Update the Access_Cache model based on information from the Access_Intent model for view + * permissions only. + * + * @todo: use database locking + * + * @param Group_Model $group + * @param Item_Model $item + * @return void + */ + private static function _update_access_view_cache($group, $item) { + $access = ORM::factory("access_intent")->where("item_id", $item->id)->find(); + + $db = Database::instance(); + $field = "view_{$group->id}"; + + // With view permissions, deny values in the parent can override allow values in the child, + // so start from the bottom of the tree and work upwards overlaying negative on top of + // positive. + // + // If the item's intent is ALLOW or DEFAULT, it's possible that some ancestor has specified + // DENY and this ALLOW cannot be obeyed. So in that case, back up the tree and find any + // non-DEFAULT and non-ALLOW parent and propagate from there. If we can't find a matching + // item, then its safe to propagate from here. + if ($access->$field !== self::DENY) { + $tmp_item = ORM::factory("item") + ->where("left <", $item->left) + ->where("right >", $item->right) + ->join("access_intents", "access_intents.item_id", "items.id") + ->where("access_intents.$field", self::DENY) + ->orderby("left", "DESC") + ->limit(1) + ->find(); + if ($tmp_item->loaded) { + $item = $tmp_item; + } + } + + // We will have a problem if we're trying to change a DENY to an ALLOW because the + // access_caches table will already contain DENY values and we won't be able to overwrite + // them according the rule above. So mark every permission below this level as UNKNOWN so + // that we can tell which permissions have been changed, and which ones need to be updated. + $db->update("items", array($field => self::UNKNOWN), + array("left >=" => $item->left, "right <=" => $item->right)); + + $query = ORM::factory("access_intent") + ->select(array("access_intents.$field", "items.left", "items.right", "items.id")) + ->join("items", "items.id", "access_intents.item_id") + ->where("left >=", $item->left) + ->where("right <=", $item->right) + ->where("type", "album") + ->where("access_intents.$field IS NOT", null) + ->orderby("level", "DESC") + ->find_all(); + foreach ($query as $row) { + if ($row->$field == self::ALLOW) { + // Propagate ALLOW for any row that is still UNKNOWN. + $db->update("items", array($field => $row->$field), + array($field => self::UNKNOWN, "left >=" => $row->left, "right <=" => $row->right)); + } else if ($row->$field == self::DENY) { + // DENY overwrites everything below it + $db->update("items", array($field => $row->$field), + array("left >=" => $row->left, "right <=" => $row->right)); + } + } + + // Finally, if our intent is DEFAULT at this point it means that we were unable to find a + // DENY parent in the hierarchy to propagate from. So we'll still have a UNKNOWN values in + // the hierarchy, and all of those are safe to change to ALLOW. + $db->update("items", array($field => self::ALLOW), + array($field => self::UNKNOWN, "left >=" => $item->left, "right <=" => $item->right)); + } + + /** + * Update the Access_Cache model based on information from the Access_Intent model for non-view + * permissions. + * + * @todo: use database locking + * + * @param Group_Model $group + * @param string $perm_name + * @param Item_Model $item + * @return void + */ + private static function _update_access_non_view_cache($group, $perm_name, $item) { + $access = ORM::factory("access_intent")->where("item_id", $item->id)->find(); + + $db = Database::instance(); + $field = "{$perm_name}_{$group->id}"; + + // If the item's intent is DEFAULT, then we need to back up the chain to find the nearest + // parent with an intent and propagate from there. + // + // @todo To optimize this, we wouldn't need to propagate from the parent, we could just + // propagate from here with the parent's intent. + if ($access->$field === null) { + $tmp_item = ORM::factory("item") + ->join("access_intents", "items.id", "access_intents.item_id") + ->where("left <", $item->left) + ->where("right >", $item->right) + ->where("$field IS NOT", null) + ->orderby("left", "DESC") + ->limit(1) + ->find(); + if ($tmp_item->loaded) { + $item = $tmp_item; + } + } + + // With non-view permissions, each level can override any permissions that came above it + // so start at the top and work downwards, overlaying permissions as we go. + $query = ORM::factory("access_intent") + ->select(array("access_intents.$field", "items.left", "items.right")) + ->join("items", "items.id", "access_intents.item_id") + ->where("left >=", $item->left) + ->where("right <=", $item->right) + ->where("$field IS NOT", null) + ->orderby("level", "ASC") + ->find_all(); + foreach ($query as $row) { + $db->query( + "UPDATE {access_caches} SET `$field` = {$row->$field} " . + "WHERE `item_id` IN " . + " (SELECT `id` FROM {items} " . + " WHERE `left` >= $row->left " . + " AND `right` <= $row->right)"); + } + } + + /** + * Maintain .htacccess files to prevent direct access to albums, resizes and thumbnails when we + * apply the view and view_full permissions to guest users. + */ + private static function _update_htaccess_files($album, $group, $perm_name, $value) { + if ($group->id != 1 || !($perm_name == "view" || $perm_name == "view_full")) { + return; + } + + $dirs = array($album->file_path()); + if ($perm_name == "view") { + $dirs[] = dirname($album->resize_path()); + $dirs[] = dirname($album->thumb_path()); + } + + $base_url = url::site("file_proxy"); + foreach ($dirs as $dir) { + if ($value === self::DENY) { + $fp = fopen("$dir/.htaccess", "w+"); + fwrite($fp, "<IfModule mod_rewrite.c>\n"); + fwrite($fp, " RewriteEngine On\n"); + fwrite($fp, " RewriteRule (.*) $base_url/\$1 [L]\n"); + fwrite($fp, "</IfModule>\n"); + fwrite($fp, "<IfModule !mod_rewrite.c>\n"); + fwrite($fp, " Order Deny,Allow\n"); + fwrite($fp, " Deny from All\n"); + fwrite($fp, "</IfModule>\n"); + fclose($fp); + } else { + @unlink($dir . "/.htaccess"); + } + } + } + + static function private_key() { + return module::get_var("core", "private_key"); + } + + /** + * Verify that our htaccess based permission system actually works. Create a temporary + * directory containing an .htaccess file that uses mod_rewrite to redirect /verify to + * /success. Then request that url. If we retrieve it successfully, then our redirects are + * working and our permission system works. + */ + static function htaccess_works() { + $success_url = url::file("var/tmp/security_test/success"); + + @mkdir(VARPATH . "tmp/security_test"); + if ($fp = @fopen(VARPATH . "tmp/security_test/.htaccess", "w+")) { + fwrite($fp, "RewriteEngine On\n"); + fwrite($fp, "RewriteRule verify $success_url [L]\n"); + fclose($fp); + } + + if ($fp = @fopen(VARPATH . "tmp/security_test/success", "w+")) { + fwrite($fp, "success"); + fclose($fp); + } + + list ($response) = remote::do_request(url::abs_file("var/tmp/security_test/verify")); + $works = $response == "HTTP/1.1 200 OK"; + @dir::unlink(VARPATH . "tmp/security_test"); + + return $works; + } +} diff --git a/modules/gallery/helpers/album.php b/modules/gallery/helpers/album.php new file mode 100644 index 00000000..362b93d0 --- /dev/null +++ b/modules/gallery/helpers/album.php @@ -0,0 +1,132 @@ +<?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. + */ + +/** + * This is the API for handling albums. + * + * Note: by design, this class does not do any permission checking. + */ +class album_Core { + /** + * Create a new album. + * @param integer $parent_id id of parent album + * @param string $name the name of this new album (it will become the directory name on disk) + * @param integer $title the title of the new album + * @param string $description (optional) the longer description of this album + * @return Item_Model + */ + static function create($parent, $name, $title, $description=null, $owner_id=null) { + if (!$parent->loaded || !$parent->is_album()) { + throw new Exception("@todo INVALID_PARENT"); + } + + if (strpos($name, "/")) { + throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); + } + + // We don't allow trailing periods as a security measure + // ref: http://dev.kohanaphp.com/issues/684 + if (rtrim($name, ".") != $name) { + throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD"); + } + + $album = ORM::factory("item"); + $album->type = "album"; + $album->title = $title; + $album->description = $description; + $album->name = $name; + $album->owner_id = $owner_id; + $album->thumb_dirty = 1; + $album->resize_dirty = 1; + $album->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + $album->sort_column = "weight"; + $album->sort_order = "ASC"; + + while (ORM::factory("item") + ->where("parent_id", $parent->id) + ->where("name", $album->name) + ->find()->id) { + $album->name = "{$name}-" . rand(); + } + + $album = $album->add_to_parent($parent); + mkdir($album->file_path()); + mkdir(dirname($album->thumb_path())); + mkdir(dirname($album->resize_path())); + + module::event("item_created", $album); + + return $album; + } + + static function get_add_form($parent) { + $form = new Forge("albums/{$parent->id}", "", "post", array("id" => "gAddAlbumForm")); + $group = $form->group("add_album") + ->label(t("Add an album to %album_title", array("album_title" => $parent->title))); + $group->input("title")->label(t("Title")); + $group->textarea("description")->label(t("Description")); + $group->input("name")->label(t("Directory Name")) + ->callback("item::validate_no_slashes") + ->error_messages("no_slashes", t("The directory name can't contain the \"/\" character")); + $group->hidden("type")->value("album"); + $group->submit("")->value(t("Create")); + $form->add_rules_from(ORM::factory("item")); + return $form; + } + + static function get_edit_form($parent) { + $form = new Forge("albums/{$parent->id}", "", "post", array("id" => "gEditAlbumForm")); + $form->hidden("_method")->value("put"); + $group = $form->group("edit_album")->label(t("Edit Album")); + + $group->input("title")->label(t("Title"))->value($parent->title); + $group->textarea("description")->label(t("Description"))->value($parent->description); + if ($parent->id != 1) { + $group->input("dirname")->label(t("Directory Name"))->value($parent->name) + ->callback("item::validate_no_slashes") + ->error_messages("no_slashes", t("The directory name can't contain a \"/\"")) + ->callback("item::validate_no_trailing_period") + ->error_messages("no_trailing_period", t("The directory name can't end in \".\"")); + } + + $sort_order = $group->group("sort_order", array("id" => "gAlbumSortOrder")) + ->label(t("Sort Order")); + + $sort_order->dropdown("column", array("id" => "gAlbumSortColumn")) + ->label(t("Sort by")) + ->options(array("weight" => t("Default"), + "captured" => t("Capture Date"), + "created" => t("Creation Date"), + "title" => t("Title"), + "updated" => t("Updated Date"), + "view_count" => t("Number of views"), + "rand_key" => t("Random"))) + ->selected($parent->sort_column); + $sort_order->dropdown("direction", array("id" => "gAlbumSortDirection")) + ->label(t("Order")) + ->options(array("ASC" => t("Ascending"), + "DESC" => t("Descending"))) + ->selected($parent->sort_order); + $group->hidden("type")->value("album"); + $group->submit("")->value(t("Modify")); + $form->add_rules_from(ORM::factory("item")); + return $form; + } +} diff --git a/modules/gallery/helpers/batch.php b/modules/gallery/helpers/batch.php new file mode 100644 index 00000000..0faa3369 --- /dev/null +++ b/modules/gallery/helpers/batch.php @@ -0,0 +1,40 @@ +<?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 batch_Core { + static function start() { + $session = Session::instance(); + $session->set("batch_level", $session->get("batch_level", 0) + 1); + } + + static function stop() { + $session = Session::instance(); + $batch_level = $session->get("batch_level", 0) - 1; + if ($batch_level > 0) { + $session->set("batch_level", $batch_level); + } else { + $session->delete("batch_level"); + module::event("batch_complete"); + } + } + + static function in_progress() { + return Session::instance()->get("batch_level", 0) > 0; + } +} diff --git a/modules/gallery/helpers/block_manager.php b/modules/gallery/helpers/block_manager.php new file mode 100644 index 00000000..022626e5 --- /dev/null +++ b/modules/gallery/helpers/block_manager.php @@ -0,0 +1,67 @@ +<?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 block_manager_Core { + static function get_active($location) { + return unserialize(module::get_var("core", "blocks_$location", "a:0:{}")); + } + + static function set_active($location, $blocks) { + module::set_var("core", "blocks_$location", serialize($blocks)); + } + + static function add($location, $module_name, $block_id) { + $blocks = self::get_active($location); + $blocks[rand()] = array($module_name, $block_id); + self::set_active($location, $blocks); + } + + static function remove($location, $block_id) { + $blocks = self::get_active($location); + unset($blocks[$block_id]); + self::set_active($location, $blocks); + } + + static function get_available() { + $blocks = array(); + + foreach (module::active() as $module) { + $class_name = "{$module->name}_block"; + if (method_exists($class_name, "get_list")) { + foreach (call_user_func(array($class_name, "get_list")) as $id => $title) { + $blocks["{$module->name}:$id"] = $title; + } + } + } + return $blocks; + } + + static function get_html($location) { + $active = self::get_active($location); + $result = ""; + foreach ($active as $id => $desc) { + if (method_exists("$desc[0]_block", "get")) { + $block = call_user_func(array("$desc[0]_block", "get"), $desc[1]); + $block->id = $id; + $result .= $block; + } + } + return $result; + } +} diff --git a/modules/gallery/helpers/core.php b/modules/gallery/helpers/core.php new file mode 100644 index 00000000..63f51f86 --- /dev/null +++ b/modules/gallery/helpers/core.php @@ -0,0 +1,52 @@ +<?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 core_Core { + /** + * If Gallery is in maintenance mode, then force all non-admins to get routed to a "This site is + * down for maintenance" page. + */ + static function maintenance_mode() { + $maintenance_mode = Kohana::config("core.maintenance_mode", false, false); + + if (Router::$controller != "login" && !empty($maintenance_mode) && !user::active()->admin) { + Router::$controller = "maintenance"; + Router::$controller_path = APPPATH . "controllers/maintenance.php"; + Router::$method = "index"; + } + } + + /** + * This function is called when the Gallery is fully initialized. We relay it to modules as the + * "gallery_ready" event. Any module that wants to perform an action at the start of every + * request should implement the <module>_event::gallery_ready() handler. + */ + static function ready() { + module::event("gallery_ready"); + } + + /** + * This function is called right before the Kohana framework shuts down. We relay it to modules + * as the "gallery_shutdown" event. Any module that wants to perform an action at the start of + * every request should implement the <module>_event::gallery_shutdown() handler. + */ + static function shutdown() { + module::event("gallery_shutdown"); + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/core_block.php b/modules/gallery/helpers/core_block.php new file mode 100644 index 00000000..0e2e9c54 --- /dev/null +++ b/modules/gallery/helpers/core_block.php @@ -0,0 +1,100 @@ +<?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 core_block_Core { + static function get_list() { + return array( + "welcome" => t("Welcome to Gallery 3!"), + "photo_stream" => t("Photo Stream"), + "log_entries" => t("Log Entries"), + "stats" => t("Gallery Stats"), + "platform_info" => t("Platform Information"), + "project_news" => t("Gallery Project News")); + } + + static function get($block_id) { + $block = new Block(); + switch($block_id) { + case "welcome": + $block->css_id = "gWelcome"; + $block->title = t("Welcome to Gallery3"); + $block->content = new View("admin_block_welcome.html"); + break; + + case "photo_stream": + $block->css_id = "gPhotoStream"; + $block->title = t("Photo Stream"); + $block->content = new View("admin_block_photo_stream.html"); + $block->content->photos = + ORM::factory("item")->where("type", "photo")->orderby("created", "DESC")->find_all(10); + break; + + case "log_entries": + $block->css_id = "gLogEntries"; + $block->title = t("Log Entries"); + $block->content = new View("admin_block_log_entries.html"); + $block->content->entries = ORM::factory("log")->orderby("timestamp", "DESC")->find_all(5); + break; + + case "stats": + $block->css_id = "gStats"; + $block->title = t("Gallery Stats"); + $block->content = new View("admin_block_stats.html"); + $block->content->album_count = ORM::factory("item")->where("type", "album")->count_all(); + $block->content->photo_count = ORM::factory("item")->where("type", "photo")->count_all(); + break; + + case "platform_info": + $block->css_id = "gPlatform"; + $block->title = t("Platform Information"); + $block->content = new View("admin_block_platform.html"); + if (is_readable("/proc/loadavg")) { + $block->content->load_average = + join(" ", array_slice(split(" ", array_shift(file("/proc/loadavg"))), 0, 3)); + } else { + $block->content->load_average = t("Unavailable"); + } + break; + + case "project_news": + $block->css_id = "gProjectNews"; + $block->title = t("Gallery Project News"); + $block->content = new View("admin_block_news.html"); + $block->content->feed = feed::parse("http://gallery.menalto.com/node/feed", 3); + break; + + case "block_adder": + $block->css_id = "gBlockAdder"; + $block->title = t("Dashboard Content"); + $block->content = self::get_add_block_form(); + } + + return $block; + } + + static function get_add_block_form() { + $form = new Forge("admin/dashboard/add_block", "", "post", + array("id" => "gAddDashboardBlockForm")); + $group = $form->group("add_block")->label(t("Add Block")); + $group->dropdown("id")->label("Available Blocks")->options(block_manager::get_available()); + $group->submit("center")->value(t("Add to center")); + $group->submit("sidebar")->value(t("Add to sidebar")); + return $form; + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/core_event.php b/modules/gallery/helpers/core_event.php new file mode 100644 index 00000000..bbb53cc9 --- /dev/null +++ b/modules/gallery/helpers/core_event.php @@ -0,0 +1,46 @@ +<?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 core_event_Core { + static function group_created($group) { + access::add_group($group); + } + + static function group_before_delete($group) { + access::delete_group($group); + } + + static function item_created($item) { + access::add_item($item); + } + + static function item_before_delete($item) { + access::delete_item($item); + } + + static function user_login($user) { + // If this user is an admin, check to see if there are any post-install tasks that we need + // to run and take care of those now. + if ($user->admin && module::get_var("core", "choose_default_tookit", null)) { + graphics::choose_default_toolkit(); + module::clear_var("core", "choose_default_tookit"); + } + } +} diff --git a/modules/gallery/helpers/core_installer.php b/modules/gallery/helpers/core_installer.php new file mode 100644 index 00000000..cffcbedb --- /dev/null +++ b/modules/gallery/helpers/core_installer.php @@ -0,0 +1,278 @@ +<?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 core_installer { + static function install($initial_install=false) { + $db = Database::instance(); + if ($initial_install) { + $version = 0; + } else { + $version = module::get_version("core"); + } + + if ($version == 0) { + $db->query("CREATE TABLE {access_caches} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {access_intents} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {graphics_rules} ( + `id` int(9) NOT NULL auto_increment, + `active` BOOLEAN default 0, + `args` varchar(255) default NULL, + `module_name` varchar(64) NOT NULL, + `operation` varchar(64) NOT NULL, + `priority` int(9) NOT NULL, + `target` varchar(32) NOT NULL, + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {items} ( + `id` int(9) NOT NULL auto_increment, + `album_cover_item_id` int(9) default NULL, + `captured` int(9) default NULL, + `created` int(9) default NULL, + `description` varchar(2048) default NULL, + `height` int(9) default NULL, + `left` int(9) NOT NULL, + `level` int(9) NOT NULL, + `mime_type` varchar(64) default NULL, + `name` varchar(255) default NULL, + `owner_id` int(9) default NULL, + `parent_id` int(9) NOT NULL, + `rand_key` float default NULL, + `relative_path_cache` varchar(255) default NULL, + `resize_dirty` boolean default 1, + `resize_height` int(9) default NULL, + `resize_width` int(9) default NULL, + `right` int(9) NOT NULL, + `sort_column` varchar(64) default NULL, + `sort_order` char(4) default 'ASC', + `thumb_dirty` boolean default 1, + `thumb_height` int(9) default NULL, + `thumb_width` int(9) default NULL, + `title` varchar(255) default NULL, + `type` varchar(32) NOT NULL, + `updated` int(9) default NULL, + `view_count` int(9) default 0, + `weight` int(9) NOT NULL default 0, + `width` int(9) default NULL, + PRIMARY KEY (`id`), + KEY `parent_id` (`parent_id`), + KEY `type` (`type`), + KEY `random` (`rand_key`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {logs} ( + `id` int(9) NOT NULL auto_increment, + `category` varchar(64) default NULL, + `html` varchar(255) default NULL, + `message` text default NULL, + `referer` varchar(255) default NULL, + `severity` int(9) default 0, + `timestamp` int(9) default 0, + `url` varchar(255) default NULL, + `user_id` int(9) default 0, + PRIMARY KEY (`id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {messages} ( + `id` int(9) NOT NULL auto_increment, + `key` varchar(255) default NULL, + `severity` varchar(32) default NULL, + `value` varchar(255) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {modules} ( + `id` int(9) NOT NULL auto_increment, + `active` BOOLEAN default 0, + `name` varchar(64) default NULL, + `version` int(9) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {themes} ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(64) default NULL, + `version` int(9) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {permissions} ( + `id` int(9) NOT NULL auto_increment, + `display_name` varchar(64) default NULL, + `name` varchar(64) default NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {incoming_translations} ( + `id` int(9) NOT NULL auto_increment, + `key` char(32) NOT NULL, + `locale` char(10) NOT NULL, + `message` text NOT NULL, + `revision` int(9) DEFAULT NULL, + `translation` text, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`, `locale`), + KEY `locale_key` (`locale`, `key`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {outgoing_translations} ( + `id` int(9) NOT NULL auto_increment, + `base_revision` int(9) DEFAULT NULL, + `key` char(32) NOT NULL, + `locale` char(10) NOT NULL, + `message` text NOT NULL, + `translation` text, + PRIMARY KEY (`id`), + UNIQUE KEY(`key`, `locale`), + KEY `locale_key` (`locale`, `key`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {sessions} ( + `session_id` varchar(127) NOT NULL, + `data` text NOT NULL, + `last_activity` int(10) UNSIGNED NOT NULL, + PRIMARY KEY (`session_id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {tasks} ( + `id` int(9) NOT NULL auto_increment, + `callback` varchar(128) default NULL, + `context` text NOT NULL, + `done` boolean default 0, + `name` varchar(128) default NULL, + `owner_id` int(9) default NULL, + `percent_complete` int(9) default 0, + `state` varchar(32) default NULL, + `status` varchar(255) default NULL, + `updated` int(9) default NULL, + PRIMARY KEY (`id`), + KEY (`owner_id`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + $db->query("CREATE TABLE {vars} ( + `id` int(9) NOT NULL auto_increment, + `module_name` varchar(64) NOT NULL, + `name` varchar(64) NOT NULL, + `value` text, + PRIMARY KEY (`id`), + UNIQUE KEY(`module_name`, `name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + + foreach (array("albums", "logs", "modules", "resizes", "thumbs", "tmp", "uploads") as $dir) { + @mkdir(VARPATH . $dir); + } + + access::register_permission("view", "View"); + access::register_permission("view_full", "View Full Size"); + access::register_permission("edit", "Edit"); + access::register_permission("add", "Add"); + + $root = ORM::factory("item"); + $root->type = "album"; + $root->title = "Gallery"; + $root->description = ""; + $root->left = 1; + $root->right = 2; + $root->parent_id = 0; + $root->level = 1; + $root->thumb_dirty = 1; + $root->resize_dirty = 1; + $root->sort_column = "weight"; + $root->sort_order = "ASC"; + $root->save(); + access::add_item($root); + + module::set_var("core", "active_site_theme", "default"); + module::set_var("core", "active_admin_theme", "admin_default"); + module::set_var("core", "page_size", 9); + module::set_var("core", "thumb_size", 200); + module::set_var("core", "resize_size", 640); + module::set_var("core", "default_locale", "en_US"); + module::set_var("core", "image_quality", 75); + + // Add rules for generating our thumbnails and resizes + graphics::add_rule( + "core", "thumb", "resize", + array("width" => 200, "height" => 200, "master" => Image::AUTO), + 100); + graphics::add_rule( + "core", "resize", "resize", + array("width" => 640, "height" => 480, "master" => Image::AUTO), + 100); + + // Instantiate default themes (site and admin) + foreach (array("default", "admin_default") as $theme_name) { + $theme_info = new ArrayObject(parse_ini_file(THEMEPATH . $theme_name . "/theme.info"), + ArrayObject::ARRAY_AS_PROPS); + $theme = ORM::factory("theme"); + $theme->name = $theme_name; + $theme->version = $theme_info->version; + $theme->save(); + } + + block_manager::add("dashboard_sidebar", "core", "block_adder"); + block_manager::add("dashboard_sidebar", "core", "stats"); + block_manager::add("dashboard_sidebar", "core", "platform_info"); + block_manager::add("dashboard_sidebar", "core", "project_news"); + block_manager::add("dashboard_center", "core", "welcome"); + block_manager::add("dashboard_center", "core", "photo_stream"); + block_manager::add("dashboard_center", "core", "log_entries"); + + module::set_version("core", 1); + module::set_var("core", "version", "3.0 pre-beta svn"); + module::set_var("core", "choose_default_tookit", 1); + } + } + + static function uninstall() { + $db = Database::instance(); + $db->query("DROP TABLE IF EXISTS {access_caches}"); + $db->query("DROP TABLE IF EXISTS {access_intents}"); + $db->query("DROP TABLE IF EXISTS {graphics_rules}"); + $db->query("DROP TABLE IF EXISTS {items}"); + $db->query("DROP TABLE IF EXISTS {logs}"); + $db->query("DROP TABLE IF EXISTS {messages}"); + $db->query("DROP TABLE IF EXISTS {modules}"); + $db->query("DROP TABLE IF EXISTS {themes}"); + $db->query("DROP TABLE IF EXISTS {incoming_translations}"); + $db->query("DROP TABLE IF EXISTS {outgoing_translations}"); + $db->query("DROP TABLE IF EXISTS {permissions}"); + $db->query("DROP TABLE IF EXISTS {sessions}"); + $db->query("DROP TABLE IF EXISTS {tasks}"); + $db->query("DROP TABLE IF EXISTS {vars}"); + foreach (array("albums", "resizes", "thumbs", "uploads", + "modules", "logs", "database.php") as $entry) { + system("/bin/rm -rf " . VARPATH . $entry); + } + } +} diff --git a/modules/gallery/helpers/core_menu.php b/modules/gallery/helpers/core_menu.php new file mode 100644 index 00000000..eb208560 --- /dev/null +++ b/modules/gallery/helpers/core_menu.php @@ -0,0 +1,162 @@ +<?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 core_menu_Core { + static function site($menu, $theme) { + if (file_exists(APPPATH . "controllers/scaffold.php") && user::active()->admin) { + $menu->append($scaffold_menu = Menu::factory("submenu") + ->id("scaffold") + ->label("Scaffold")); + $scaffold_menu->append(Menu::factory("link") + ->id("scaffold_home") + ->label("Dashboard") + ->url(url::site("scaffold"))); + } + + $menu->append(Menu::factory("link") + ->id("home") + ->label(t("Home")) + ->url(url::site("albums/1"))); + + $item = $theme->item(); + + if (user::active()->admin || ($item && access::can("edit", $item))) { + $menu->append($options_menu = Menu::factory("submenu") + ->id("options_menu") + ->label(t("Options"))); + + if ($item && access::can("edit", $item)) { + $options_menu + ->append(Menu::factory("dialog") + ->id("edit_item") + ->label($item->is_album() ? t("Edit album") : t("Edit photo")) + ->url(url::site("form/edit/{$item->type}s/$item->id"))); + + // @todo Move album options menu to the album quick edit pane + // @todo Create resized item quick edit pane menu + if ($item->is_album()) { + $options_menu + ->append(Menu::factory("dialog") + ->id("add_item") + ->label(t("Add a photo")) + ->url(url::site("simple_uploader/app/$item->id"))) + ->append(Menu::factory("dialog") + ->id("add_album") + ->label(t("Add an album")) + ->url(url::site("form/add/albums/$item->id?type=album"))) + ->append(Menu::factory("dialog") + ->id("edit_permissions") + ->label(t("Edit permissions")) + ->url(url::site("permissions/browse/$item->id"))); + } + } + } + + if (user::active()->admin) { + $menu->append($admin_menu = Menu::factory("submenu") + ->id("admin_menu") + ->label(t("Admin"))); + self::admin($admin_menu, $theme); + foreach (module::active() as $module) { + if ($module->name == "core") { + continue; + } + $class = "{$module->name}_menu"; + if (method_exists($class, "admin")) { + call_user_func_array(array($class, "admin"), array(&$admin_menu, $theme)); + } + } + } + } + + static function album($menu, $theme) { + } + + static function photo($menu, $theme) { + if (access::can("view_full", $theme->item())) { + $menu + ->append(Menu::factory("link") + ->id("fullsize") + ->label(t("View full size")) + ->url("#") + ->css_class("gFullSizeLink")); + } + $menu + ->append(Menu::factory("link") + ->id("album") + ->label(t("Return to album")) + ->url($theme->item()->parent()->url("show={$theme->item->id}")) + ->css_id("gAlbumLink")); + } + + static function admin($menu, $theme) { + $menu + ->append(Menu::factory("link") + ->id("dashboard") + ->label(t("Dashboard")) + ->url(url::site("admin"))) + ->append(Menu::factory("submenu") + ->id("settings_menu") + ->label(t("Settings")) + ->append(Menu::factory("link") + ->id("graphics_toolkits") + ->label(t("Graphics")) + ->url(url::site("admin/graphics"))) + ->append(Menu::factory("link") + ->id("languages") + ->label(t("Languages")) + ->url(url::site("admin/languages"))) + ->append(Menu::factory("link") + ->id("l10n_mode") + ->label(Session::instance()->get("l10n_mode", false) + ? t("Stop translating") : t("Start translating")) + ->url(url::site("l10n_client/toggle_l10n_mode?csrf=" . + access::csrf_token()))) + ->append(Menu::factory("link") + ->id("advanced") + ->label("Advanced") + ->url(url::site("admin/advanced_settings")))) + ->append(Menu::factory("link") + ->id("modules") + ->label(t("Modules")) + ->url(url::site("admin/modules"))) + ->append(Menu::factory("submenu") + ->id("content_menu") + ->label(t("Content"))) + ->append(Menu::factory("submenu") + ->id("appearance_menu") + ->label(t("Appearance")) + ->append(Menu::factory("link") + ->id("themes") + ->label(t("Theme Choice")) + ->url(url::site("admin/themes"))) + ->append(Menu::factory("link") + ->id("theme_details") + ->label(t("Theme Options")) + ->url(url::site("admin/theme_details")))) + ->append(Menu::factory("link") + ->id("maintenance") + ->label(t("Maintenance")) + ->url(url::site("admin/maintenance"))) + ->append(Menu::factory("submenu") + ->id("statistics_menu") + ->label(t("Statistics")) + ->url("#")); + } +} diff --git a/modules/gallery/helpers/core_search.php b/modules/gallery/helpers/core_search.php new file mode 100644 index 00000000..9957a493 --- /dev/null +++ b/modules/gallery/helpers/core_search.php @@ -0,0 +1,24 @@ +<?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 Core_Search_Core { + static function item_index_data($item) { + return join(" ", array($item->description, $item->name, $item->title)); + } +} diff --git a/modules/gallery/helpers/core_task.php b/modules/gallery/helpers/core_task.php new file mode 100644 index 00000000..e078192c --- /dev/null +++ b/modules/gallery/helpers/core_task.php @@ -0,0 +1,166 @@ +<?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 core_task_Core { + static function available_tasks() { + $dirty_count = graphics::find_dirty_images_query()->count(); + $tasks = array(); + $tasks[] = Task_Definition::factory() + ->callback("core_task::rebuild_dirty_images") + ->name(t("Rebuild Images")) + ->description($dirty_count ? + t2("You have one out of date photo", + "You have %count out of date photos", + $dirty_count) + : t("All your photos are up to date")) + ->severity($dirty_count ? log::WARNING : log::SUCCESS); + + $tasks[] = Task_Definition::factory() + ->callback("core_task::update_l10n") + ->name(t("Update translations")) + ->description(t("Download new and updated translated strings")) + ->severity(log::SUCCESS); + + return $tasks; + } + + /** + * Task that rebuilds all dirty images. + * @param Task_Model the task + */ + static function rebuild_dirty_images($task) { + $result = graphics::find_dirty_images_query(); + $remaining = $result->count(); + $completed = $task->get("completed", 0); + + $i = 0; + foreach ($result as $row) { + $item = ORM::factory("item", $row->id); + if ($item->loaded) { + graphics::generate($item); + } + + $completed++; + $remaining--; + + if (++$i == 2) { + break; + } + } + + $task->status = t2("Updated: 1 image. Total: %total_count.", + "Updated: %count images. Total: %total_count.", + $completed, + array("total_count" => ($remaining + $completed))); + + if ($completed + $remaining > 0) { + $task->percent_complete = (int)(100 * $completed / ($completed + $remaining)); + } else { + $task->percent_complete = 100; + } + + $task->set("completed", $completed); + if ($remaining == 0) { + $task->done = true; + $task->state = "success"; + site_status::clear("graphics_dirty"); + } + } + + static function update_l10n(&$task) { + $start = microtime(true); + $dirs = $task->get("dirs"); + $files = $task->get("files"); + $cache = $task->get("cache", array()); + $i = 0; + + switch ($task->get("mode", "init")) { + case "init": // 0% + $dirs = array("core", "modules", "themes", "installer"); + $files = array(); + $task->set("mode", "find_files"); + $task->status = t("Finding files"); + break; + + case "find_files": // 0% - 10% + while (($dir = array_pop($dirs)) && microtime(true) - $start < 0.5) { + if (basename($dir) == "tests") { + continue; + } + + foreach (glob(DOCROOT . "$dir/*") as $path) { + $relative_path = str_replace(DOCROOT, "", $path); + if (is_dir($path)) { + $dirs[] = $relative_path; + } else { + $files[] = $relative_path; + } + } + } + + $task->status = t2("Finding files: found 1 file", + "Finding files: found %count files", count($files)); + + if (!$dirs) { + $task->set("mode", "scan_files"); + $task->set("total_files", count($files)); + $task->status = t("Scanning files"); + $task->percent_complete = 10; + } + break; + + case "scan_files": // 10% - 90% + while (($file = array_pop($files)) && microtime(true) - $start < 0.5) { + $file = DOCROOT . $file; + switch (pathinfo($file, PATHINFO_EXTENSION)) { + case "php": + l10n_scanner::scan_php_file($file, $cache); + break; + + case "info": + l10n_scanner::scan_info_file($file, $cache); + break; + } + } + + $total_files = $task->get("total_files"); + $task->status = t2("Scanning files: scanned 1 file", + "Scanning files: scanned %count files", $total_files - count($files)); + + $task->percent_complete = 10 + 80 * ($total_files - count($files)) / $total_files; + if (empty($files)) { + $task->set("mode", "fetch_updates"); + $task->status = t("Fetching updates"); + $task->percent_complete = 90; + } + break; + + case "fetch_updates": // 90% - 100% + l10n_client::fetch_updates(); + $task->done = true; + $task->state = "success"; + $task->status = t("Translations installed/updated"); + $task->percent_complete = 100; + } + + $task->set("files", $files); + $task->set("dirs", $dirs); + $task->set("cache", $cache); + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/core_theme.php b/modules/gallery/helpers/core_theme.php new file mode 100644 index 00000000..28f544a1 --- /dev/null +++ b/modules/gallery/helpers/core_theme.php @@ -0,0 +1,137 @@ +<?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 core_theme_Core { + static function head($theme) { + $session = Session::instance(); + $buf = ""; + if ($session->get("debug")) { + $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . + url::file("core/css/debug.css") . "\" />"; + } + if (($theme->page_type == "album" || $theme->page_type == "photo") + && access::can("edit", $theme->item())) { + $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . + url::file("core/css/quick.css") . "\" />"; + $buf .= html::script("core/js/quick.js"); + } + if ($theme->page_type == "photo" && access::can("view_full", $theme->item())) { + $buf .= "<script type=\"text/javascript\" >" . + " var fullsize_detail = { " . + " close: \"" . url::file("core/images/ico-close.png") . "\", " . + " url: \"" . $theme->item()->file_url() . "\", " . + " width: " . $theme->item()->width . ", " . + " height: " . $theme->item()->height . "};" . + "</script>"; + $buf .= html::script("core/js/fullsize.js"); + } + + if ($session->get("l10n_mode", false)) { + $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . + url::file("core/css/l10n_client.css") . "\" />"; + $buf .= html::script("lib/jquery.cookie.js"); + $buf .= html::script("core/js/l10n_client.js"); + } + + return $buf; + } + + static function resize_top($theme, $item) { + if (access::can("edit", $item)) { + $edit_link = url::site("quick/pane/$item->id?page_type=photo"); + return "<div class=\"gQuick\" href=\"$edit_link\">"; + } + } + + static function resize_bottom($theme, $item) { + if (access::can("edit", $item)) { + return "</div>"; + } + } + + static function thumb_top($theme, $child) { + if (access::can("edit", $child)) { + $edit_link = url::site("quick/pane/$child->id?page_type=album"); + return "<div class=\"gQuick\" href=\"$edit_link\">"; + } + } + + static function thumb_bottom($theme, $child) { + if (access::can("edit", $child)) { + return "</div>"; + } + } + + static function admin_head($theme) { + $session = Session::instance(); + $buf = ""; + if ($session->get("debug")) { + $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . + url::file("core/css/debug.css") . "\" />"; + } + + if ($session->get("l10n_mode", false)) { + $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . + url::file("core/css/l10n_client.css") . "\" />"; + $buf .= html::script("lib/jquery.cookie.js"); + $buf .= html::script("core/js/l10n_client.js"); + } + + return $buf; + } + + static function page_bottom($theme) { + $session = Session::instance(); + if ($session->get("profiler", false)) { + $profiler = new Profiler(); + $profiler->render(); + } + if ($session->get("l10n_mode", false)) { + return L10n_Client_Controller::l10n_form(); + } + + if ($session->get("after_install")) { + $session->delete("after_install"); + return new View("after_install_loader.html"); + } + } + + static function admin_page_bottom($theme) { + $session = Session::instance(); + if ($session->get("profiler", false)) { + $profiler = new Profiler(); + $profiler->render(); + } + if ($session->get("l10n_mode", false)) { + return L10n_Client_Controller::l10n_form(); + } + } + + static function credits() { + return "<li class=\"first\">" . + t("Powered by <a href=\"%url\">Gallery %version</a>", + array("url" => "http://gallery.menalto.com", + "version" => module::get_var("core", "version"))) . + "</li>"; + } + + static function admin_credits() { + return core_theme::credits(); + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/dir.php b/modules/gallery/helpers/dir.php new file mode 100644 index 00000000..1717bdd9 --- /dev/null +++ b/modules/gallery/helpers/dir.php @@ -0,0 +1,40 @@ +<?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 dir_Core { + static function unlink($path) { + if (is_dir($path) && is_writable($path)) { + foreach (new DirectoryIterator($path) as $resource) { + if ($resource->isDot()) { + unset($resource); + continue; + } else if ($resource->isFile()) { + unlink($resource->getPathName()); + } else if ($resource->isDir()) { + dir::unlink($resource->getRealPath()); + } + unset($resource); + } + return @rmdir($path); + } + return false; + } + + +} diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php new file mode 100644 index 00000000..805a95c0 --- /dev/null +++ b/modules/gallery/helpers/graphics.php @@ -0,0 +1,387 @@ +<?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 graphics_Core { + private static $init; + + /** + * Add a new graphics rule. + * + * Rules are applied to targets (thumbnails and resizes) in priority order. Rules are functions + * in the graphics class. So for example, the following rule: + * + * graphics::add_rule("core", "thumb", "resize", + * array("width" => 200, "height" => 200, "master" => Image::AUTO), 100); + * + * Specifies that "core" is adding a rule to resize thumbnails down to a max of 200px on + * the longest side. The core module adds default rules at a priority of 100. You can set + * higher and lower priorities to perform operations before or after this fires. + * + * @param string $module_name the module that added the rule + * @param string $target the target for this operation ("thumb" or "resize") + * @param string $operation the name of the operation + * @param array $args arguments to the operation + * @param integer $priority the priority for this rule (lower priorities are run first) + */ + static function add_rule($module_name, $target, $operation, $args, $priority) { + $rule = ORM::factory("graphics_rule"); + $rule->module_name = $module_name; + $rule->target = $target; + $rule->operation = $operation; + $rule->priority = $priority; + $rule->args = serialize($args); + $rule->active = true; + $rule->save(); + + self::mark_dirty($target == "thumb", $target == "resize"); + } + + /** + * Remove any matching graphics rules + * @param string $module_name the module that added the rule + * @param string $target the target for this operation ("thumb" or "resize") + * @param string $operation the name of the operation + */ + static function remove_rule($module_name, $target, $operation) { + ORM::factory("graphics_rule") + ->where("module_name", $module_name) + ->where("target", $target) + ->where("operation", $operation) + ->delete_all(); + + self::mark_dirty($target == "thumb", $target == "resize"); + } + + /** + * Remove all rules for this module + * @param string $module_name + */ + static function remove_rules($module_name) { + $status = Database::instance()->delete("graphics_rules", array("module_name" => $module_name)); + if (count($status)) { + self::mark_dirty(true, true); + } + } + + /** + * Activate the rules for this module, typically done when the module itself is deactivated. + * Note that this does not mark images as dirty so that if you deactivate and reactivate a + * module it won't cause all of your images to suddenly require a rebuild. + */ + static function activate_rules($module_name) { + Database::instance() + ->update("graphics_rules",array("active" => true), array("module_name" => $module_name)); + } + + /** + * Deactivate the rules for this module, typically done when the module itself is deactivated. + * Note that this does not mark images as dirty so that if you deactivate and reactivate a + * module it won't cause all of your images to suddenly require a rebuild. + */ + static function deactivate_rules($module_name) { + Database::instance() + ->update("graphics_rules",array("active" => false), array("module_name" => $module_name)); + } + + /** + * Rebuild the thumb and resize for the given item. + * @param Item_Model $item + */ + static function generate($item) { + if ($item->is_album()) { + if (!$cover = $item->album_cover()) { + return; + } + $input_file = $cover->file_path(); + $input_item = $cover; + } else { + $input_file = $item->file_path(); + $input_item = $item; + } + + if ($item->thumb_dirty) { + $ops["thumb"] = $item->thumb_path(); + } + if ($item->resize_dirty && !$item->is_album() && !$item->is_movie()) { + $ops["resize"] = $item->resize_path(); + } + + if (empty($ops)) { + return; + } + + try { + foreach ($ops as $target => $output_file) { + if ($input_item->is_movie()) { + // Convert the movie to a JPG first + $output_file = preg_replace("/...$/", "jpg", $output_file); + movie::extract_frame($input_file, $output_file); + $working_file = $output_file; + } else { + $working_file = $input_file; + } + + foreach (ORM::factory("graphics_rule") + ->where("target", $target) + ->where("active", true) + ->orderby("priority", "asc") + ->find_all() as $rule) { + $args = array($working_file, $output_file, unserialize($rule->args)); + call_user_func_array(array("graphics", $rule->operation), $args); + $working_file = $output_file; + } + } + + if (!empty($ops["thumb"])) { + $dims = getimagesize($item->thumb_path()); + $item->thumb_width = $dims[0]; + $item->thumb_height = $dims[1]; + $item->thumb_dirty = 0; + } + + if (!empty($ops["resize"])) { + $dims = getimagesize($item->resize_path()); + $item->resize_width = $dims[0]; + $item->resize_height = $dims[1]; + $item->resize_dirty = 0; + } + $item->save(); + } catch (Kohana_Exception $e) { + // Something went wrong rebuilding the image. Leave it dirty and move on. + // @todo we should handle this better. + Kohana::log("error", "Caught exception rebuilding image: {$item->title}\n" . + $e->getTraceAsString()); + } + } + + /** + * Resize an image. Valid options are width, height and master. Master is one of the Image + * master dimension constants. + * + * @param string $input_file + * @param string $output_file + * @param array $options + */ + static function resize($input_file, $output_file, $options) { + if (!self::$init) { + self::init_toolkit(); + } + + if (filesize($input_file) == 0) { + throw new Exception("@todo MALFORMED_INPUT_FILE"); + } + + $dims = getimagesize($input_file); + if (max($dims[0], $dims[1]) < min($options["width"], $options["height"])) { + // Image would get upscaled; do nothing + copy($input_file, $output_file); + } else { + Image::factory($input_file) + ->resize($options["width"], $options["height"], $options["master"]) + ->quality(module::get_var("core", "image_quality")) + ->save($output_file); + } + } + + /** + * Rotate an image. Valid options are degrees + * + * @param string $input_file + * @param string $output_file + * @param array $options + */ + static function rotate($input_file, $output_file, $options) { + if (!self::$init) { + self::init_toolkit(); + } + + Image::factory($input_file) + ->quality(module::get_var("core", "image_quality")) + ->rotate($options["degrees"]) + ->save($output_file); + } + + /** + * Overlay an image on top of the input file. + * + * Valid options are: file, mime_type, position, transparency_percent, padding + * + * Valid positions: northwest, north, northeast, + * west, center, east, + * southwest, south, southeast + * + * padding is in pixels + * + * @param string $input_file + * @param string $output_file + * @param array $options + */ + static function composite($input_file, $output_file, $options) { + if (!self::$init) { + self::init_toolkit(); + } + + list ($width, $height) = getimagesize($input_file); + list ($w_width, $w_height) = getimagesize($options["file"]); + + $pad = isset($options["padding"]) ? $options["padding"] : 10; + $top = $pad; + $left = $pad; + $y_center = max($height / 2 - $w_height / 2, $pad); + $x_center = max($width / 2 - $w_width / 2, $pad); + $bottom = max($height - $w_height - $pad, $pad); + $right = max($width - $w_width - $pad, $pad); + + switch ($options["position"]) { + case "northwest": $x = $left; $y = $top; break; + case "north": $x = $x_center; $y = $top; break; + case "northeast": $x = $right; $y = $top; break; + case "west": $x = $left; $y = $y_center; break; + case "center": $x = $x_center; $y = $y_center; break; + case "east": $x = $right; $y = $y_center; break; + case "southwest": $x = $left; $y = $bottom; break; + case "south": $x = $x_center; $y = $bottom; break; + case "southeast": $x = $right; $y = $bottom; break; + } + + Image::factory($input_file) + ->composite($options["file"], $x, $y, $options["transparency"]) + ->quality(module::get_var("core", "image_quality")) + ->save($output_file); + } + + /** + * Return a query result that locates all items with dirty images. + * @return Database_Result Query result + */ + static function find_dirty_images_query() { + return Database::instance()->query( + "SELECT `id` FROM {items} " . + "WHERE ((`thumb_dirty` = 1 AND (`type` <> 'album' OR `album_cover_item_id` IS NOT NULL))" . + " OR (`resize_dirty` = 1 AND `type` = 'photo')) " . + " AND `id` != 1"); + } + + /** + * Mark thumbnails and resizes as dirty. They will have to be rebuilt. + */ + static function mark_dirty($thumbs, $resizes) { + if ($thumbs || $resizes) { + $db = Database::instance(); + $fields = array(); + if ($thumbs) { + $fields["thumb_dirty"] = 1; + } + if ($resizes) { + $fields["resize_dirty"] = 1; + } + $db->update("items", $fields, true); + } + + $count = self::find_dirty_images_query()->count(); + if ($count) { + site_status::warning( + t2("One of your photos is out of date. <a %attrs>Click here to fix it</a>", + "%count of your photos are out of date. <a %attrs>Click here to fix them</a>", + $count, + array("attrs" => sprintf( + 'href="%s" class="gDialogLink"', + url::site("admin/maintenance/start/core_task::rebuild_dirty_images?csrf=__CSRF__")))), + "graphics_dirty"); + } + } + + /** + * Detect which graphics toolkits are available on this system. Return an array of key value + * pairs where the key is one of gd, imagemagick, graphicsmagick and the value is information + * about that toolkit. For GD we return the version string, and for ImageMagick and + * GraphicsMagick we return the path to the directory containing the appropriate binaries. + */ + static function detect_toolkits() { + $gd = function_exists("gd_info") ? gd_info() : array(); + $exec = function_exists("exec"); + if (!isset($gd["GD Version"])) { + $gd["GD Version"] = false; + } + return array("gd" => $gd, + "imagemagick" => $exec ? dirname(exec("which convert")) : false, + "graphicsmagick" => $exec ? dirname(exec("which gm")) : false); + } + + /** + * This needs to be run once, after the initial install, to choose a graphics toolkit. + */ + static function choose_default_toolkit() { + // Detect a graphics toolkit + $toolkits = graphics::detect_toolkits(); + foreach (array("imagemagick", "graphicsmagick", "gd") as $tk) { + if ($toolkits[$tk]) { + module::set_var("core", "graphics_toolkit", $tk); + module::set_var("core", "graphics_toolkit_path", $tk == "gd" ? "" : $toolkits[$tk]); + break; + } + } + if (!module::get_var("core", "graphics_toolkit")) { + site_status::warning( + t("Graphics toolkit missing! Please <a href=\"%url\">choose a toolkit</a>", + array("url" => url::site("admin/graphics"))), + "missing_graphics_toolkit"); + } + } + + /** + * Choose which driver the Kohana Image library uses. + */ + static function init_toolkit() { + switch(module::get_var("core", "graphics_toolkit")) { + case "gd": + Kohana::config_set("image.driver", "GD"); + break; + + case "imagemagick": + Kohana::config_set("image.driver", "ImageMagick"); + Kohana::config_set( + "image.params.directory", module::get_var("core", "graphics_toolkit_path")); + break; + + case "graphicsmagick": + Kohana::config_set("image.driver", "GraphicsMagick"); + Kohana::config_set( + "image.params.directory", module::get_var("core", "graphics_toolkit_path")); + break; + } + + self::$init = 1; + } + + /** + * Verify that a specific graphics function is available with the active toolkit. + * @param string $func (eg rotate, resize) + * @return boolean + */ + static function can($func) { + if (module::get_var("core", "graphics_toolkit") == "gd" && + $func == "rotate" && + !function_exists("imagerotate")) { + return false; + } + + return true; + } +} diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php new file mode 100644 index 00000000..7daaf1e1 --- /dev/null +++ b/modules/gallery/helpers/item.php @@ -0,0 +1,105 @@ +<?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 item_Core { + static function move($source, $target) { + access::required("edit", $source); + access::required("edit", $target); + + $parent = $source->parent(); + if ($parent->album_cover_item_id == $source->id) { + if ($parent->children_count() > 1) { + foreach ($parent->children(2) as $child) { + if ($child->id != $source->id) { + $new_cover_item = $child; + break; + } + } + item::make_album_cover($new_cover_item); + } else { + item::remove_album_cover($parent); + } + } + + $source->move_to($target); + + // If the target has no cover item, make this it. + if ($target->album_cover_item_id == null) { + item::make_album_cover($source); + } + } + + static function make_album_cover($item) { + $parent = $item->parent(); + access::required("edit", $parent); + + model_cache::clear("item", $parent->album_cover_item_id); + $parent->album_cover_item_id = $item->is_album() ? $item->album_cover_item_id : $item->id; + $parent->thumb_dirty = 1; + $parent->save(); + graphics::generate($parent); + $grand_parent = $parent->parent(); + if ($grand_parent && $grand_parent->album_cover_item_id == null) { + item::make_album_cover($parent); + } + } + + static function remove_album_cover($album) { + access::required("edit", $album); + @unlink($album->thumb_path()); + + model_cache::clear("item", $album->album_cover_item_id) ; + $album->album_cover_item_id = null; + $album->thumb_width = 0; + $album->thumb_height = 0; + $album->thumb_dirty = 1; + $album->save(); + graphics::generate($album); + } + + static function validate_no_slashes($input) { + if (strpos($input->value, "/") !== false) { + $input->add_error("no_slashes", 1); + } + } + + static function validate_no_trailing_period($input) { + if (rtrim($input->value, ".") !== $input->value) { + $input->add_error("no_trailing_period", 1); + } + } + + static function validate_no_name_conflict($input) { + $itemid = Input::instance()->post("item"); + if (is_array($itemid)) { + $itemid = $itemid[0]; + } + $item = ORM::factory("item") + ->in("id", $itemid) + ->find(); + if (Database::instance() + ->from("items") + ->where("parent_id", $item->parent_id) + ->where("id <>", $item->id) + ->where("name", $input->value) + ->count_records()) { + $input->add_error("conflict", 1); + } + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/l10n_client.php b/modules/gallery/helpers/l10n_client.php new file mode 100644 index 00000000..ec4c5429 --- /dev/null +++ b/modules/gallery/helpers/l10n_client.php @@ -0,0 +1,203 @@ +<?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 l10n_client_Core { + + private static function _server_url() { + return "http://gallery.menalto.com/index.php"; + } + + static function server_api_key_url() { + return self::_server_url() . "?q=translations/userkey/" . + self::client_token(); + } + + static function client_token() { + return md5("l10n_client_client_token" . access::private_key()); + } + + static function api_key($api_key=null) { + if ($api_key !== null) { + module::set_var("core", "l10n_client_key", $api_key); + } + return module::get_var("core", "l10n_client_key", ""); + } + + static function server_uid($api_key=null) { + $api_key = $api_key == null ? self::api_key() : $api_key; + $parts = explode(":", $api_key); + return empty($parts) ? 0 : $parts[0]; + } + + private static function _sign($payload, $api_key=null) { + $api_key = $api_key == null ? self::api_key() : $api_key; + return md5($api_key . $payload . self::client_token()); + } + + static function validate_api_key($api_key) { + $version = "1.0"; + $url = self::_server_url() . "?q=translations/status"; + $signature = self::_sign($version, $api_key); + + list ($response_data, $response_status) = remote::post( + $url, array("version" => $version, + "client_token" => self::client_token(), + "signature" => $signature, + "uid" => self::server_uid($api_key))); + if (!remote::success($response_status)) { + return false; + } + return true; + } + + static function fetch_updates() { + $request->locales = array(); + $request->messages = new stdClass(); + + $locales = locale::installed(); + foreach ($locales as $locale => $locale_data) { + $request->locales[] = $locale; + } + + // @todo Batch requests (max request size) + foreach (Database::instance() + ->select("key", "locale", "revision", "translation") + ->from("incoming_translations") + ->get() + ->as_array() as $row) { + if (!isset($request->messages->{$row->key})) { + $request->messages->{$row->key} = 1; + } + if (!empty($row->revision) && !empty($row->translation)) { + if (!is_object($request->messages->{$row->key})) { + $request->messages->{$row->key} = new stdClass(); + } + $request->messages->{$row->key}->{$row->locale} = $row->revision; + } + } + // @todo Include messages from outgoing_translations? + + $request_data = json_encode($request); + $url = self::_server_url() . "?q=translations/fetch"; + list ($response_data, $response_status) = remote::post($url, array("data" => $request_data)); + if (!remote::success($response_status)) { + throw new Exception("@todo TRANSLATIONS_FETCH_REQUEST_FAILED " . $response_status); + } + if (empty($response_data)) { + log::info("translations", "Translations fetch request resulted in an empty response"); + return; + } + + $response = json_decode($response_data); + + // Response format (JSON payload): + // [{key:<key_1>, translation: <JSON encoded translation>, rev:<rev>, locale:<locale>}, + // {key:<key_2>, ...} + // ] + $count = count($response); + log::info("translations", "Installed $count new / updated translation messages"); + + foreach ($response as $message_data) { + // @todo Better input validation + if (empty($message_data->key) || empty($message_data->translation) || + empty($message_data->locale) || empty($message_data->rev)) { + throw new Exception("@todo TRANSLATIONS_FETCH_REQUEST_FAILED: Invalid response data"); + } + $key = $message_data->key; + $locale = $message_data->locale; + $revision = $message_data->rev; + $translation = serialize(json_decode($message_data->translation)); + + // @todo Should we normalize the incoming_translations table into messages(id, key, message) + // and incoming_translations(id, translation, locale, revision)? Or just allow + // incoming_translations.message to be NULL? + $locale = $message_data->locale; + $entry = ORM::factory("incoming_translation") + ->where(array("key" => $key, "locale" => $locale)) + ->find(); + if (!$entry->loaded) { + // @todo Load a message key -> message (text) dict into memory outside of this loop + $root_entry = ORM::factory("incoming_translation") + ->where(array("key" => $key, "locale" => "root")) + ->find(); + $entry->key = $key; + $entry->message = $root_entry->message; + $entry->locale = $locale; + } + $entry->revision = $revision; + $entry->translation = $translation; + $entry->save(); + } + } + + static function submit_translations() { + // Request format (HTTP POST): + // client_token = <client_token> + // uid = <l10n server user id> + // signature = md5(user_api_key($uid, $client_token) . $data . $client_token)) + // data = // JSON payload + // + // {<key_1>: {message: <JSON encoded message> + // translations: {<locale_1>: <JSON encoded translation>, + // <locale_2>: ...}}, + // <key_2>: {...} + // } + + // @todo Batch requests (max request size) + // @todo include base_revision in submission / how to handle resubmissions / edit fights? + foreach (Database::instance() + ->select("key", "message", "locale", "base_revision", "translation") + ->from("outgoing_translations") + ->get() as $row) { + $key = $row->key; + if (!isset($request->{$key})) { + $request->{$key}->message = json_encode(unserialize($row->message)); + } + $request->{$key}->translations->{$row->locale} = json_encode(unserialize($row->translation)); + } + + // @todo reduce memory consumpotion, e.g. free $request + $request_data = json_encode($request); + $url = self::_server_url() . "?q=translations/submit"; + $signature = self::_sign($request_data); + + list ($response_data, $response_status) = remote::post( + $url, array("data" => $request_data, + "client_token" => self::client_token(), + "signature" => $signature, + "uid" => self::server_uid())); + + if (!remote::success($response_status)) { + throw new Exception("@todo TRANSLATIONS_SUBMISSION_FAILED " . $response_status); + } + if (empty($response_data)) { + return; + } + + $response = json_decode($response_data); + // Response format (JSON payload): + // [{key:<key_1>, locale:<locale_1>, rev:<rev_1>, status:<rejected|accepted|pending>}, + // {key:<key_2>, ...} + // ] + + // @todo Move messages out of outgoing into incoming, using new rev? + // @todo show which messages have been rejected / are pending? + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/l10n_scanner.php b/modules/gallery/helpers/l10n_scanner.php new file mode 100644 index 00000000..80b6f01c --- /dev/null +++ b/modules/gallery/helpers/l10n_scanner.php @@ -0,0 +1,154 @@ +<?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. + */ + +/** + * Scans all source code for messages that need to be localized. + */ +class l10n_scanner_Core { + // Based on Drupal's potx module, originally written by: + // Gbor Hojtsy http://drupal.org/user/4166 + public static $cache; + + static function process_message($message, &$cache) { + if (empty($cache)) { + foreach (Database::instance() + ->select("key") + ->from("incoming_translations") + ->where("locale", "root") + ->get() as $row) { + $cache[$row->key] = true; + } + } + + $key = I18n::get_message_key($message); + if (array_key_exists($key, $cache)) { + return $cache[$key]; + } + + $entry = ORM::factory("incoming_translation", array("key" => $key)); + if (!$entry->loaded) { + $entry->key = $key; + $entry->message = serialize($message); + $entry->locale = "root"; + $entry->save(); + } + } + + static function scan_php_file($file, &$cache) { + $code = file_get_contents($file); + $raw_tokens = token_get_all($code); + unset($code); + + $tokens = array(); + $func_token_list = array("t" => array(), "t2" => array()); + $token_number = 0; + // Filter out HTML / whitespace, and build a lookup for global function calls. + foreach ($raw_tokens as $token) { + if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) { + if (is_array($token)) { + if ($token[0] == T_STRING && in_array($token[1], array("t", "t2"))) { + $func_token_list[$token[1]][] = $token_number; + } + } + $tokens[] = $token; + $token_number++; + } + } + unset($raw_tokens); + + if (!empty($func_token_list["t"])) { + l10n_scanner::_parse_t_calls($tokens, $func_token_list["t"], $cache); + } + if (!empty($func_token_list["t2"])) { + l10n_scanner::_parse_plural_calls($tokens, $func_token_list["t2"], $cache); + } + } + + static function scan_info_file($file, &$cache) { + $code = file_get_contents($file); + if (preg_match("#name\s*?=\s*(.*?)\ndescription\s*?=\s*(.*)\n#", $code, $matches)) { + unset($matches[0]); + foreach ($matches as $string) { + l10n_scanner::process_message($string, $cache); + } + } + } + + private static function _parse_t_calls(&$tokens, &$call_list, &$cache) { + foreach ($call_list as $index) { + $function_name = $tokens[$index++]; + $parens = $tokens[$index++]; + $first_param = $tokens[$index++]; + $next_token = $tokens[$index]; + + if ($parens == "(") { + if (in_array($next_token, array(")", ",")) + && (is_array($first_param) && ($first_param[0] == T_CONSTANT_ENCAPSED_STRING))) { + $message = self::_escape_quoted_string($first_param[1]); + l10n_scanner::process_message($message, $cache); + } else { + // t() found, but inside is something which is not a string literal. + // @todo Call status callback with error filename/line. + } + } + } + } + + private static function _parse_plural_calls(&$tokens, &$call_list, &$cache) { + foreach ($call_list as $index) { + $function_name = $tokens[$index++]; + $parens = $tokens[$index++]; + $first_param = $tokens[$index++]; + $first_separator = $tokens[$index++]; + $second_param = $tokens[$index++]; + $next_token = $tokens[$index]; + + if ($parens == "(") { + if ($first_separator == "," && $next_token == "," + && is_array($first_param) && $first_param[0] == T_CONSTANT_ENCAPSED_STRING + && is_array($second_param) && $second_param[0] == T_CONSTANT_ENCAPSED_STRING) { + $singular = self::_escape_quoted_string($first_param[1]); + $plural = self::_escape_quoted_string($first_param[1]); + l10n_scanner::process_message(array("one" => $singular, "other" => $plural), $cache); + } else { + // t2() found, but inside is something which is not a string literal. + // @todo Call status callback with error filename/line. + } + } + } + } + + /** + * Escape quotes in a strings depending on the surrounding + * quote type used. + * + * @param $str The strings to escape + */ + private static function _escape_quoted_string($str) { + $quo = substr($str, 0, 1); + $str = substr($str, 1, -1); + if ($quo == '"') { + $str = stripcslashes($str); + } else { + $str = strtr($str, array("\\'" => "'", "\\\\" => "\\")); + } + return addcslashes($str, "\0..\37\\\""); + } +} diff --git a/modules/gallery/helpers/locale.php b/modules/gallery/helpers/locale.php new file mode 100644 index 00000000..b707637f --- /dev/null +++ b/modules/gallery/helpers/locale.php @@ -0,0 +1,119 @@ +<?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. + */ + +/** + * This is the API for handling locales. + */ +class locale_Core { + private static $locales; + + /** + * Return the list of available locales. + */ + static function available() { + if (empty(self::$locales)) { + self::_init_language_data(); + } + + return self::$locales; + } + + static function installed() { + $available = self::available(); + $default = module::get_var("core", "default_locale"); + $codes = explode("|", module::get_var("core", "installed_locales", $default)); + foreach ($codes as $code) { + if (isset($available->$code)) { + $installed[$code] = $available[$code]; + } + } + return $installed; + } + + static function update_installed($locales) { + // Ensure that the default is included... + $default = module::get_var("core", "default_locale"); + $locales = array_merge($locales, array($default)); + + module::set_var("core", "installed_locales", join("|", $locales)); + } + + // @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["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) + asort($l, SORT_LOCALE_STRING); + self::$locales = $l; + } + + static function display_name($locale=null) { + if (empty(self::$locales)) { + self::_init_language_data(); + } + $locale or $locale = I18n::instance()->locale(); + + return self::$locales["$locale"]; + } + + static function is_rtl($locale) { + return in_array($locale, array("he_IL", "fa_IR", "ar_SA")); + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/log.php b/modules/gallery/helpers/log.php new file mode 100644 index 00000000..451f985a --- /dev/null +++ b/modules/gallery/helpers/log.php @@ -0,0 +1,108 @@ +<?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 log_Core { + const SUCCESS = 1; + const INFO = 2; + const WARNING = 3; + const ERROR = 4; + + /** + * Report a successful event. + * @param string $category an arbitrary category we can use to filter log messages + * @param string $message a detailed log message + * @param string $html an html snippet presented alongside the log message to aid the admin + */ + static function success($category, $message, $html="") { + self::_add($category, $message, $html, self::SUCCESS); + } + + /** + * Report an informational event. + * @param string $category an arbitrary category we can use to filter log messages + * @param string $message a detailed log message + * @param string $html an html snippet presented alongside the log message to aid the admin + */ + static function info($category, $message, $html="") { + self::_add($category, $message, $html, self::INFO); + } + + /** + * Report that something went wrong, not fatal, but worth investigation. + * @param string $category an arbitrary category we can use to filter log messages + * @param string $message a detailed log message + * @param string $html an html snippet presented alongside the log message to aid the admin + */ + static function warning($category, $message, $html="") { + self::_add($category, $message, $html, self::WARNING); + } + + /** + * Report that something went wrong that should be fixed. + * @param string $category an arbitrary category we can use to filter log messages + * @param string $message a detailed log message + * @param string $html an html snippet presented alongside the log message to aid the admin + */ + static function error($category, $message, $html="") { + self::_add($category, $message, $html, self::ERROR); + } + + /** + * Add a log entry. + * + * @param string $category an arbitrary category we can use to filter log messages + * @param string $message a detailed log message + * @param integer $severity INFO, WARNING or ERROR + * @param string $html an html snippet presented alongside the log message to aid the admin + */ + private static function _add($category, $message, $html, $severity) { + $log = ORM::factory("log"); + $log->category = $category; + $log->message = $message; + $log->severity = $severity; + $log->html = $html; + $log->url = substr(url::abs_current(true), 0, 255); + $log->referer = request::referrer(null); + $log->timestamp = time(); + $log->user_id = user::active()->id; + $log->save(); + } + + + /** + * Convert a message severity to a CSS class + * @param integer $severity + * @return string + */ + static function severity_class($severity) { + switch($severity) { + case self::SUCCESS: + return "gSuccess"; + + case self::INFO: + return "gInfo"; + + case self::WARNING: + return "gWarning"; + + case self::ERROR: + return "gError"; + } + } +} diff --git a/modules/gallery/helpers/message.php b/modules/gallery/helpers/message.php new file mode 100644 index 00000000..af3b96cc --- /dev/null +++ b/modules/gallery/helpers/message.php @@ -0,0 +1,108 @@ +<?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 message_Core { + const SUCCESS = 1; + const INFO = 2; + const WARNING = 3; + const ERROR = 4; + + /** + * Report a successful event. + * @param string $msg a detailed message + */ + static function success($msg) { + self::_add($msg, self::SUCCESS); + } + + /** + * Report an informational event. + * @param string $msg a detailed message + */ + static function info($msg) { + self::_add($msg, self::INFO); + } + + /** + * Report that something went wrong, not fatal, but worth investigation. + * @param string $msg a detailed message + */ + static function warning($msg) { + self::_add($msg, self::WARNING); + } + + /** + * Report that something went wrong that should be fixed. + * @param string $msg a detailed message + */ + static function error($msg) { + self::_add($msg, self::ERROR); + } + + /** + * Save a message in the session for our next page load. + * @param string $msg a detailed message + * @param integer $severity one of the severity constants + */ + private static function _add($msg, $severity) { + $session = Session::instance(); + $status = $session->get("messages"); + $status[] = array($msg, $severity); + $session->set("messages", $status); + } + + /** + * Get any pending messages. There are two types of messages, transient and permanent. + * Permanent messages are used to let the admin know that there are pending administrative + * issues that need to be resolved. Transient ones are only displayed once. + * @return html text + */ + static function get() { + $buf = array(); + + $messages = Session::instance()->get_once("messages", array()); + foreach ($messages as $msg) { + $buf[] = "<li class=\"" . self::severity_class($msg[1]) . "\">$msg[0]</li>"; + } + if ($buf) { + return "<ul id=\"gMessage\">" . implode("", $buf) . "</ul>"; + } + } + + /** + * Convert a message severity to a CSS class + * @param integer $severity + * @return string + */ + static function severity_class($severity) { + switch($severity) { + case self::SUCCESS: + return "gSuccess"; + + case self::INFO: + return "gInfo"; + + case self::WARNING: + return "gWarning"; + + case self::ERROR: + return "gError"; + } + } +} diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php new file mode 100644 index 00000000..2649fdbd --- /dev/null +++ b/modules/gallery/helpers/model_cache.php @@ -0,0 +1,46 @@ +<?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 model_cache_Core { + private static $cache; + + static function get($model_name, $id, $field_name="id") { + if (TEST_MODE || empty(self::$cache->$model_name->$field_name->$id)) { + $model = ORM::factory($model_name)->where($field_name, $id)->find(); + if (!$model->loaded) { + throw new Exception("@todo MISSING_MODEL $model_name:$id"); + } + self::$cache->$model_name->$field_name->$id = $model; + } + + return self::$cache->$model_name->$field_name->$id; + } + + static function clear($model_name, $id, $field_name="id") { + if (!empty(self::$cache->$model_name->$field_name->$id)) { + unset(self::$cache->$model_name->$field_name->$id); + } + } + + static function set($model) { + self::$cache->{$model->object_name} + ->{$model->primary_key} + ->{$model->{$model->primary_key}} = $model; + } +} diff --git a/modules/gallery/helpers/module.php b/modules/gallery/helpers/module.php new file mode 100644 index 00000000..a48c89ed --- /dev/null +++ b/modules/gallery/helpers/module.php @@ -0,0 +1,357 @@ +<?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. + */ + +/** + * This is the API for handling modules. + * + * Note: by design, this class does not do any permission checking. + */ +class module_Core { + public static $active = array(); + public static $modules = array(); + public static $var_cache = null; + + /** + * Set the version of the corresponding Module_Model + * @param string $module_name + * @param integer $version + */ + static function set_version($module_name, $version) { + $module = self::get($module_name); + if (!$module->loaded) { + $module->name = $module_name; + $module->active = $module_name == "core"; // only core is active by default + } + $module->version = 1; + $module->save(); + Kohana::log("debug", "$module_name: version is now $version"); + } + + /** + * Load the corresponding Module_Model + * @param string $module_name + */ + static function get($module_name) { + // @todo can't easily use model_cache here because it throw an exception on missing models. + return ORM::factory("module", array("name" => $module_name)); + } + + /** + * Check to see if a module is installed + * @param string $module_name + */ + static function is_installed($module_name) { + return array_key_exists($module_name, self::$modules); + } + + /** + * Check to see if a module is active + * @param string $module_name + */ + static function is_active($module_name) { + return array_key_exists($module_name, self::$modules) && + self::$modules[$module_name]->active; + } + + /** + * Return the list of available modules, including uninstalled modules. + */ + static function available() { + $modules = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS); + foreach (array_merge(array("core/module.info"), glob(MODPATH . "*/module.info")) as $file) { + $module_name = basename(dirname($file)); + $modules->$module_name = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS); + $modules->$module_name->installed = self::is_installed($module_name); + $modules->$module_name->active = self::is_active($module_name); + $modules->$module_name->version = self::get_version($module_name); + $modules->$module_name->locked = false; + } + + // Lock certain modules + $modules->core->locked = true; + $modules->user->locked = true; + $modules->ksort(); + + return $modules; + } + + /** + * Return a list of all the active modules in no particular order. + */ + static function active() { + return self::$active; + } + + /** + * Install a module. This will call <module>_installer::install(), which is responsible for + * creating database tables, setting module variables and and calling module::set_version(). + * Note that after installing, the module must be activated before it is available for use. + * @param string $module_name + */ + static function install($module_name) { + $kohana_modules = Kohana::config("core.modules"); + $kohana_modules[] = MODPATH . $module_name; + Kohana::config_set("core.modules", $kohana_modules); + + $installer_class = "{$module_name}_installer"; + if (method_exists($installer_class, "install")) { + call_user_func_array(array($installer_class, "install"), array()); + } + + // Now the module is installed but inactive, so don't leave it in the active path + array_pop($kohana_modules); + Kohana::config_set("core.modules", $kohana_modules); + + log::success( + "module", t("Installed module %module_name", array("module_name" => $module_name))); + } + + /** + * Activate an installed module. This will call <module>_installer::activate() which should take + * any steps to make sure that the module is ready for use. This will also activate any + * existing graphics rules for this module. + * @param string $module_name + */ + static function activate($module_name) { + $kohana_modules = Kohana::config("core.modules"); + $kohana_modules[] = MODPATH . $module_name; + Kohana::config_set("core.modules", $kohana_modules); + + $installer_class = "{$module_name}_installer"; + if (method_exists($installer_class, "activate")) { + call_user_func_array(array($installer_class, "activate"), array()); + } + + $module = self::get($module_name); + if ($module->loaded) { + $module->active = true; + $module->save(); + } + + self::load_modules(); + graphics::activate_rules($module_name); + log::success( + "module", t("Activated module %module_name", array("module_name" => $module_name))); + } + + /** + * Deactivate an installed module. This will call <module>_installer::deactivate() which + * should take any cleanup steps to make sure that the module isn't visible in any way. + * @param string $module_name + */ + static function deactivate($module_name) { + $installer_class = "{$module_name}_installer"; + if (method_exists($installer_class, "deactivate")) { + call_user_func_array(array($installer_class, "deactivate"), array()); + } + + $module = self::get($module_name); + if ($module->loaded) { + $module->active = false; + $module->save(); + } + + self::load_modules(); + graphics::deactivate_rules($module_name); + log::success( + "module", t("Deactivated module %module_name", array("module_name" => $module_name))); + } + + /** + * Uninstall a deactivated module. This will call <module>_installer::uninstall() which should + * take whatever steps necessary to make sure that all traces of a module are gone. + * @param string $module_name + */ + static function uninstall($module_name) { + $installer_class = "{$module_name}_installer"; + if (method_exists($installer_class, "uninstall")) { + call_user_func(array($installer_class, "uninstall")); + } + + graphics::remove_rule($module_name); + $module = self::get($module_name); + if ($module->loaded) { + $module->delete(); + } + + // We could delete the module vars here too, but it's nice to leave them around + // in case the module gets reinstalled. + + self::load_modules(); + log::success( + "module", t("Uninstalled module %module_name", array("module_name" => $module_name))); + } + + /** + * Load the active modules. This is called at bootstrap time. + */ + static function load_modules() { + // Reload module list from the config file since we'll do a refresh after calling install() + $core = Kohana::config_load("core"); + $kohana_modules = $core["modules"]; + $modules = ORM::factory("module")->find_all(); + + self::$modules = array(); + self::$active = array(); + foreach ($modules as $module) { + self::$modules[$module->name] = $module; + if ($module->active) { + self::$active[] = $module; + } + if ($module->name != "core") { + $kohana_modules[] = MODPATH . $module->name; + } + } + Kohana::config_set("core.modules", $kohana_modules); + } + + /** + * Run a specific event on all active modules. + * @param string $name the event name + * @param mixed $data data to pass to each event handler + */ + static function event($name, &$data=null) { + $args = func_get_args(); + array_shift($args); + $function = str_replace(".", "_", $name); + + foreach (self::$modules as $module) { + if (!$module->active) { + continue; + } + + $class = "{$module->name}_event"; + if (method_exists($class, $function)) { + call_user_func_array(array($class, $function), $args); + } + } + } + + /** + * Get a variable from this module + * @param string $module_name + * @param string $name + * @param string $default_value + * @return the value + */ + static function get_var($module_name, $name, $default_value=null) { + // We cache all vars in core._cache so that we can load all vars at once for + // performance. + if (empty(self::$var_cache)) { + $row = Database::instance() + ->select("value") + ->from("vars") + ->where(array("module_name" => "core", "name" => "_cache")) + ->get() + ->current(); + if ($row) { + self::$var_cache = unserialize($row->value); + } else { + // core._cache doesn't exist. Create it now. + foreach (Database::instance() + ->select("module_name", "name", "value") + ->from("vars") + ->orderby("module_name", "name") + ->get() as $row) { + if ($row->module_name == "core" && $row->name == "_cache") { + // This could happen if there's a race condition + continue; + } + self::$var_cache->{$row->module_name}->{$row->name} = $row->value; + } + $cache = ORM::factory("var"); + $cache->module_name = "core"; + $cache->name = "_cache"; + $cache->value = serialize(self::$var_cache); + $cache->save(); + } + } + + if (isset(self::$var_cache->$module_name->$name)) { + return self::$var_cache->$module_name->$name; + } else { + return $default_value; + } + } + + /** + * Store a variable for this module + * @param string $module_name + * @param string $name + * @param string $value + */ + static function set_var($module_name, $name, $value) { + $var = ORM::factory("var") + ->where("module_name", $module_name) + ->where("name", $name) + ->find(); + if (!$var->loaded) { + $var->module_name = $module_name; + $var->name = $name; + } + $var->value = $value; + $var->save(); + + Database::instance()->delete("vars", array("module_name" => "core", "name" => "_cache")); + self::$var_cache = null; + } + + /** + * Increment the value of a variable for this module + * @param string $module_name + * @param string $name + * @param string $increment (optional, default is 1) + */ + static function incr_var($module_name, $name, $increment=1) { + Database::instance()->query( + "UPDATE {vars} SET `value` = `value` + $increment " . + "WHERE `module_name` = '$module_name' " . + "AND `name` = '$name'"); + + Database::instance()->delete("vars", array("module_name" => "core", "name" => "_cache")); + self::$var_cache = null; + } + + /** + * Remove a variable for this module. + * @param string $module_name + * @param string $name + */ + static function clear_var($module_name, $name) { + $var = ORM::factory("var") + ->where("module_name", $module_name) + ->where("name", $name) + ->find(); + if ($var->loaded) { + $var->delete(); + } + + Database::instance()->delete("vars", array("module_name" => "core", "name" => "_cache")); + self::$var_cache = null; + } + + /** + * Return the version of the installed module. + * @param string $module_name + */ + static function get_version($module_name) { + return self::get($module_name)->version; + } +} diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php new file mode 100644 index 00000000..3293d4ac --- /dev/null +++ b/modules/gallery/helpers/movie.php @@ -0,0 +1,153 @@ +<?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. + */ + +/** + * This is the API for handling movies. + * + * Note: by design, this class does not do any permission checking. + */ +class movie_Core { + /** + * Create a new movie. + * @param integer $parent_id id of parent album + * @param string $filename path to the photo file on disk + * @param string $name the filename to use for this photo in the album + * @param integer $title the title of the new photo + * @param string $description (optional) the longer description of this photo + * @return Item_Model + */ + static function create($parent, $filename, $name, $title, + $description=null, $owner_id=null) { + if (!$parent->loaded || !$parent->is_album()) { + throw new Exception("@todo INVALID_PARENT"); + } + + if (!is_file($filename)) { + throw new Exception("@todo MISSING_MOVIE_FILE"); + } + + if (strpos($name, "/")) { + throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); + } + + // We don't allow trailing periods as a security measure + // ref: http://dev.kohanaphp.com/issues/684 + if (rtrim($name, ".") != $name) { + throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD"); + } + + $movie_info = movie::getmoviesize($filename); + + // Force an extension onto the name + $pi = pathinfo($filename); + if (empty($pi["extension"])) { + $pi["extension"] = image_type_to_extension($movie_info[2], false); + $name .= "." . $pi["extension"]; + } + + $movie = ORM::factory("item"); + $movie->type = "movie"; + $movie->title = $title; + $movie->description = $description; + $movie->name = $name; + $movie->owner_id = $owner_id ? $owner_id : user::active(); + $movie->width = $movie_info[0]; + $movie->height = $movie_info[1]; + $movie->mime_type = strtolower($pi["extension"]) == "mp4" ? "video/mp4" : "video/x-flv"; + $movie->thumb_dirty = 1; + $movie->resize_dirty = 1; + $movie->sort_column = "weight"; + $movie->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + + // Randomize the name if there's a conflict + while (ORM::Factory("item") + ->where("parent_id", $parent->id) + ->where("name", $movie->name) + ->find()->id) { + // @todo Improve this. Random numbers are not user friendly + $movie->name = rand() . "." . $pi["extension"]; + } + + // This saves the photo + $movie->add_to_parent($parent); + + // If the thumb or resize already exists then rename it + if (file_exists($movie->resize_path()) || + file_exists($movie->thumb_path())) { + $movie->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"]; + $movie->save(); + } + + copy($filename, $movie->file_path()); + + module::event("item_created", $movie); + + // Build our thumbnail + graphics::generate($movie); + + // If the parent has no cover item, make this it. + if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { + item::make_album_cover($movie); + } + + return $movie; + } + + static function getmoviesize($filename) { + $ffmpeg = self::find_ffmpeg(); + if (empty($ffmpeg)) { + throw new Exception("@todo MISSING_FFMPEG"); + } + + $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($filename) . " 2>&1"; + $result = `$cmd`; + if (preg_match("/Stream.*?Video:.*?(\d+)x(\d+).*\ +([0-9\.]+) (fps|tb).*/", + $result, $regs)) { + list ($width, $height) = array($regs[1], $regs[2]); + } else { + list ($width, $height) = array(0, 0); + } + return array($width, $height); + } + + static function extract_frame($input_file, $output_file) { + $ffmpeg = self::find_ffmpeg(); + if (empty($ffmpeg)) { + throw new Exception("@todo MISSING_FFMPEG"); + } + + $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($input_file) . + " -an -ss 00:00:03 -an -r 1 -vframes 1" . + " -y -f mjpeg " . escapeshellarg($output_file); + exec($cmd); + } + + static function find_ffmpeg() { + if (!$ffmpeg_path = module::get_var("core", "ffmpeg_path")) { + if (function_exists("exec")) { + $ffmpeg_path = exec("which ffmpeg"); + if ($ffmpeg_path) { + module::set_var("core", "ffmpeg_path", $ffmpeg_path); + } + } + } + return $ffmpeg_path; + } +} diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php new file mode 100644 index 00000000..c1c005f5 --- /dev/null +++ b/modules/gallery/helpers/photo.php @@ -0,0 +1,171 @@ +<?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. + */ + +/** + * This is the API for handling photos. + * + * Note: by design, this class does not do any permission checking. + */ +class photo_Core { + /** + * Create a new photo. + * @param integer $parent_id id of parent album + * @param string $filename path to the photo file on disk + * @param string $name the filename to use for this photo in the album + * @param integer $title the title of the new photo + * @param string $description (optional) the longer description of this photo + * @return Item_Model + */ + static function create($parent, $filename, $name, $title, + $description=null, $owner_id=null) { + if (!$parent->loaded || !$parent->is_album()) { + throw new Exception("@todo INVALID_PARENT"); + } + + if (!is_file($filename)) { + throw new Exception("@todo MISSING_IMAGE_FILE"); + } + + if (strpos($name, "/")) { + throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); + } + + // We don't allow trailing periods as a security measure + // ref: http://dev.kohanaphp.com/issues/684 + if (rtrim($name, ".") != $name) { + throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD"); + } + + $image_info = getimagesize($filename); + + // Force an extension onto the name + $pi = pathinfo($filename); + if (empty($pi["extension"])) { + $pi["extension"] = image_type_to_extension($image_info[2], false); + $name .= "." . $pi["extension"]; + } + + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->title = $title; + $photo->description = $description; + $photo->name = $name; + $photo->owner_id = $owner_id ? $owner_id : user::active(); + $photo->width = $image_info[0]; + $photo->height = $image_info[1]; + $photo->mime_type = empty($image_info['mime']) ? "application/unknown" : $image_info['mime']; + $photo->thumb_dirty = 1; + $photo->resize_dirty = 1; + $photo->sort_column = "weight"; + $photo->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + + // Randomize the name if there's a conflict + while (ORM::Factory("item") + ->where("parent_id", $parent->id) + ->where("name", $photo->name) + ->find()->id) { + // @todo Improve this. Random numbers are not user friendly + $photo->name = rand() . "." . $pi["extension"]; + } + + // This saves the photo + $photo->add_to_parent($parent); + + /* + * If the thumb or resize already exists then rename it. We need to do this after the save + * because the resize_path and thumb_path both call relative_path which caches the + * path. Before add_to_parent the relative path will be incorrect. + */ + if (file_exists($photo->resize_path()) || + file_exists($photo->thumb_path())) { + $photo->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"]; + $photo->save(); + } + + copy($filename, $photo->file_path()); + + module::event("item_created", $photo); + + // Build our thumbnail/resizes + graphics::generate($photo); + + // If the parent has no cover item, make this it. + if (access::can("edit", $parent) && $parent->album_cover_item_id == null) { + item::make_album_cover($photo); + } + + return $photo; + } + + static function get_add_form($parent) { + $form = new Forge("albums/{$parent->id}", "", "post", array("id" => "gAddPhotoForm")); + $group = $form->group("add_photo")->label( + t("Add Photo to %album_title", array("album_title" =>$parent->title))); + $group->input("title")->label(t("Title")); + $group->textarea("description")->label(t("Description")); + $group->input("name")->label(t("Filename")); + $group->upload("file")->label(t("File"))->rules("required|allow[jpg,png,gif,flv,mp4]"); + $group->hidden("type")->value("photo"); + $group->submit("")->value(t("Upload")); + $form->add_rules_from(ORM::factory("item")); + return $form; + } + + static function get_edit_form($photo) { + $form = new Forge("photos/$photo->id", "", "post", array("id" => "gEditPhotoForm")); + $form->hidden("_method")->value("put"); + $group = $form->group("edit_photo")->label(t("Edit Photo")); + $group->input("title")->label(t("Title"))->value($photo->title); + $group->textarea("description")->label(t("Description"))->value($photo->description); + $group->input("filename")->label(t("Filename"))->value($photo->name) + ->error_messages("conflict", t("There is already a file with this name")) + ->callback("item::validate_no_slashes") + ->error_messages("no_slashes", t("The photo name can't contain a \"/\"")) + ->callback("item::validate_no_trailing_period") + ->error_messages("no_trailing_period", t("The photo name can't end in \".\"")); + + $group->submit("")->value(t("Modify")); + $form->add_rules_from(ORM::factory("item")); + return $form; + } + + /** + * Return scaled width and height. + * + * @param integer $width + * @param integer $height + * @param integer $max the target size for the largest dimension + * @param string $format the output format using %d placeholders for width and height + */ + static function img_dimensions($width, $height, $max, $format="width=\"%d\" height=\"%d\"") { + if (!$width || !$height) { + return ""; + } + + if ($width > $height) { + $new_width = $max; + $new_height = (int)$max * ($height / $width); + } else { + $new_height = $max; + $new_width = (int)$max * ($width / $height); + } + return sprintf($format, $new_width, $new_height); + } +} diff --git a/modules/gallery/helpers/rest.php b/modules/gallery/helpers/rest.php new file mode 100644 index 00000000..a63b94c8 --- /dev/null +++ b/modules/gallery/helpers/rest.php @@ -0,0 +1,116 @@ +<?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 rest_Core { + const OK = "200 OK"; + const CREATED = "201 Created"; + const ACCEPTED = "202 Accepted"; + const NO_CONTENT = "204 No Content"; + const RESET_CONTENT = "205 Reset Content"; + const PARTIAL_CONTENT = "206 Partial Content"; + const MOVED_PERMANENTLY = "301 Moved Permanently"; + const FOUND = "302 Found"; + const SEE_OTHER = "303 See Other"; + const NOT_MODIFIED = "304 Not Modified"; + const TEMPORARY_REDIRECT = "307 Temporary Redirect"; + const BAD_REQUEST = "400 Bad Request"; + const UNAUTHORIZED = "401 Unauthorized"; + const FORBIDDEN = "403 Forbidden"; + const NOT_FOUND = "404 Not Found"; + const METHOD_NOT_ALLOWED = "405 Method Not Allowed"; + const NOT_ACCEPTABLE = "406 Not Acceptable"; + const CONFLICT = "409 Conflict"; + const GONE = "410 Gone"; + const LENGTH_REQUIRED = "411 Length Required"; + const PRECONDITION_FAILED = "412 Precondition Failed"; + const UNSUPPORTED_MEDIA_TYPE = "415 Unsupported Media Type"; + const EXPECTATION_FAILED = "417 Expectation Failed"; + const INTERNAL_SERVER_ERROR = "500 Internal Server Error"; + const SERVICE_UNAVAILABLE = "503 Service Unavailable"; + + const XML = "application/xml"; + const ATOM = "application/atom+xml"; + const RSS = "application/rss+xml"; + const JSON = "application/json"; + const HTML = "text/html"; + + /** + * We're expecting to run in an environment that only supports GET/POST, so expect to tunnel + * PUT and DELETE through POST. + * + * Returns the HTTP request method taking into consideration PUT/DELETE tunneling. + * @return string HTTP request method + */ + static function request_method() { + if (request::method() == "get") { + return "get"; + } else { + $input = Input::instance(); + switch (strtolower($input->post("_method", $input->get("_method", request::method())))) { + case "put": return "put"; + case "delete": return "delete"; + default: return "post"; + } + } + } + + /** + * Choose an output format based on what the client prefers to accept. + * @return string "html", "xml" or "json" + */ + static function output_format() { + // Pick a format, but let it be overridden. + $input = Input::instance(); + $fmt = $input->get( + "_format", $input->post( + "_format", request::preferred_accept( + array("xhtml", "html", "xml", "json")))); + + // Some browsers (Chrome!) prefer xhtml over html, but we'll normalize this to html for now. + if ($fmt == "xhtml") { + $fmt = "html"; + } + return $fmt; + } + + /** + * Set HTTP response code. + * @param string Use one of the status code constants defined in this class. + */ + static function http_status($status_code) { + header("HTTP/1.1 " . $status_code); + } + + /** + * Set HTTP Location header. + * @param string URL + */ + static function http_location($url) { + header("Location: " . $url); + } + + /** + * Set HTTP Content-Type header. + * @param string content type + */ + static function http_content_type($type) { + header("Content-Type: " . $type); + } +} diff --git a/modules/gallery/helpers/site_status.php b/modules/gallery/helpers/site_status.php new file mode 100644 index 00000000..6d47e565 --- /dev/null +++ b/modules/gallery/helpers/site_status.php @@ -0,0 +1,132 @@ +<?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 site_status_Core { + const SUCCESS = 1; + const INFO = 2; + const WARNING = 3; + const ERROR = 4; + + /** + * Report a successful event. + * @param string $msg a detailed message + * @param string $permanent_key make this message permanent and store it under this key + */ + static function success($msg, $permanent_key) { + self::_add($msg, self::SUCCESS, $permanent_key); + } + + /** + * Report an informational event. + * @param string $msg a detailed message + * @param string $permanent_key make this message permanent and store it under this key + */ + static function info($msg, $permanent_key) { + self::_add($msg, self::INFO, $permanent_key); + } + + /** + * Report that something went wrong, not fatal, but worth investigation. + * @param string $msg a detailed message + * @param string $permanent_key make this message permanent and store it under this key + */ + static function warning($msg, $permanent_key) { + self::_add($msg, self::WARNING, $permanent_key); + } + + /** + * Report that something went wrong that should be fixed. + * @param string $msg a detailed message + * @param string $permanent_key make this message permanent and store it under this key + */ + static function error($msg, $permanent_key) { + self::_add($msg, self::ERROR, $permanent_key); + } + + /** + * Save a message in the session for our next page load. + * @param string $msg a detailed message + * @param integer $severity one of the severity constants + * @param string $permanent_key make this message permanent and store it under this key + */ + private static function _add($msg, $severity, $permanent_key) { + $message = ORM::factory("message") + ->where("key", $permanent_key) + ->find(); + if (!$message->loaded) { + $message->key = $permanent_key; + } + $message->severity = $severity; + $message->value = $msg; + $message->save(); + } + + /** + * Remove any permanent message by key. + * @param string $permanent_key + */ + static function clear($permanent_key) { + $message = ORM::factory("message")->where("key", $permanent_key)->find(); + if ($message->loaded) { + $message->delete(); + } + } + + /** + * Get any pending messages. There are two types of messages, transient and permanent. + * Permanent messages are used to let the admin know that there are pending administrative + * issues that need to be resolved. Transient ones are only displayed once. + * @return html text + */ + static function get() { + if (!user::active()->admin) { + return; + } + $buf = array(); + foreach (ORM::factory("message")->find_all() as $msg) { + $value = str_replace('__CSRF__', access::csrf_token(), $msg->value); + $buf[] = "<li class=\"" . self::severity_class($msg->severity) . "\">$value</li>"; + } + + if ($buf) { + return "<ul id=\"gSiteStatus\">" . implode("", $buf) . "</ul>"; + } + } + + /** + * Convert a message severity to a CSS class + * @param integer $severity + * @return string + */ + static function severity_class($severity) { + switch($severity) { + case self::SUCCESS: + return "gSuccess"; + + case self::INFO: + return "gInfo"; + + case self::WARNING: + return "gWarning"; + + case self::ERROR: + return "gError"; + } + } +} diff --git a/modules/gallery/helpers/task.php b/modules/gallery/helpers/task.php new file mode 100644 index 00000000..a8a004ab --- /dev/null +++ b/modules/gallery/helpers/task.php @@ -0,0 +1,83 @@ +<?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 task_Core { + /** + * Get all available tasks + */ + static function get_definitions() { + $tasks = array(); + foreach (module::active() as $module) { + $class_name = "{$module->name}_task"; + if (method_exists($class_name, "available_tasks")) { + foreach (call_user_func(array($class_name, "available_tasks")) as $task) { + $tasks[$task->callback] = $task; + } + } + } + + return $tasks; + } + + static function create($task_def, $context) { + $task = ORM::factory("task"); + $task->callback = $task_def->callback; + $task->name = $task_def->name; + $task->percent_complete = 0; + $task->status = ""; + $task->state = "started"; + $task->owner_id = user::active()->id; + $task->context = serialize($context); + $task->save(); + + return $task; + } + + static function cancel($task_id) { + $task = ORM::factory("task", $task_id); + if (!$task->loaded) { + throw new Exception("@todo MISSING_TASK"); + } + $task->done = 1; + $task->state = "cancelled"; + $task->save(); + + return $task; + } + + static function remove($task_id) { + $task = ORM::factory("task", $task_id); + if ($task->loaded) { + $task->delete(); + } + } + + static function run($task_id) { + $task = ORM::factory("task", $task_id); + if (!$task->loaded) { + throw new Exception("@todo MISSING_TASK"); + } + + $task->state = "running"; + call_user_func_array($task->callback, array(&$task)); + $task->save(); + + return $task; + } +}
\ No newline at end of file diff --git a/modules/gallery/helpers/theme.php b/modules/gallery/helpers/theme.php new file mode 100644 index 00000000..cbe224db --- /dev/null +++ b/modules/gallery/helpers/theme.php @@ -0,0 +1,61 @@ +<?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. + */ + +/** + * This is the API for handling themes. + * + * Note: by design, this class does not do any permission checking. + */ +class theme_Core { + /** + * Load the active theme. This is called at bootstrap time. We will only ever have one theme + * active for any given request. + */ + static function load_themes() { + $modules = Kohana::config("core.modules"); + if (Router::$controller == "admin") { + array_unshift($modules, THEMEPATH . module::get_var("core", "active_admin_theme")); + } else { + array_unshift($modules, THEMEPATH . module::get_var("core", "active_site_theme")); + } + Kohana::config_set("core.modules", $modules); + } + + static function get_edit_form_admin() { + $form = new Forge("admin/theme_details/save/", "", null, array("id" =>"gThemeDetailsForm")); + $group = $form->group("edit_theme"); + $group->input("page_size")->label(t("Items per page"))->id("gPageSize") + ->rules("required|valid_digit") + ->value(module::get_var("core", "page_size")); + $group->input("thumb_size")->label(t("Thumbnail size (in pixels)"))->id("gThumbSize") + ->rules("required|valid_digit") + ->value(module::get_var("core", "thumb_size")); + $group->input("resize_size")->label(t("Resized image size (in pixels)"))->id("gResizeSize") + ->rules("required|valid_digit") + ->value(module::get_var("core", "resize_size")); + $group->textarea("header_text")->label(t("Header text"))->id("gHeaderText") + ->value(module::get_var("core", "header_text")); + $group->textarea("footer_text")->label(t("Footer text"))->id("gFooterText") + ->value(module::get_var("core", "footer_text")); + $group->submit("")->value(t("Save")); + return $form; + } +} + diff --git a/modules/gallery/helpers/xml.php b/modules/gallery/helpers/xml.php new file mode 100644 index 00000000..e734e90c --- /dev/null +++ b/modules/gallery/helpers/xml.php @@ -0,0 +1,35 @@ +<?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 xml_Core { + static function to_xml($array, $element_names) { + $xml = "<$element_names[0]>\n"; + foreach ($array as $key => $value) { + if (is_array($value)) { + $xml .= xml::to_xml($value, array_slice($element_names, 1)); + } else if (is_object($value)) { + $xml .= xml::to_xml($value->as_array(), array_slice($element_names, 1)); + } else { + $xml .= "<$key>$value</$key>\n"; + } + } + $xml .= "</$element_names[0]>\n"; + return $xml; + } +} diff --git a/modules/gallery/hooks/init_gallery.php b/modules/gallery/hooks/init_gallery.php new file mode 100644 index 00000000..2c36795a --- /dev/null +++ b/modules/gallery/hooks/init_gallery.php @@ -0,0 +1,44 @@ +<?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. + */ + +// If var/database.php doesn't exist, then we assume that the Gallery is not properly installed +// and send users to the installer. +if (!file_exists(VARPATH . "database.php")) { + url::redirect(url::abs_file("installer")); +} + +Event::add("system.ready", array("I18n", "instance")); +Event::add("system.ready", array("module", "load_modules")); +Event::add("system.ready", array("core", "ready")); +Event::add("system.post_routing", array("theme", "load_themes")); +Event::add("system.post_routing", array("url", "parse_url")); +Event::add("system.post_routing", array("core", "maintenance_mode")); +Event::add("system.shutdown", array("core", "shutdown")); + +// Override the cookie if we have a session id in the URL. +// @todo This should probably be an event callback +$input = Input::instance(); +if ($g3sid = $input->post("g3sid", $input->get("g3sid"))) { + $_COOKIE["g3sid"] = $g3sid; +} + +if ($user_agent = $input->post("user_agent", $input->get("user_agent"))) { + Kohana::$user_agent = $user_agent; +} diff --git a/modules/gallery/images/gallery.png b/modules/gallery/images/gallery.png Binary files differnew file mode 100644 index 00000000..ca8e0e95 --- /dev/null +++ b/modules/gallery/images/gallery.png diff --git a/modules/gallery/images/gd.png b/modules/gallery/images/gd.png Binary files differnew file mode 100644 index 00000000..b341d71c --- /dev/null +++ b/modules/gallery/images/gd.png diff --git a/modules/gallery/images/graphicsmagick.png b/modules/gallery/images/graphicsmagick.png Binary files differnew file mode 100644 index 00000000..3d1d77e9 --- /dev/null +++ b/modules/gallery/images/graphicsmagick.png diff --git a/modules/gallery/images/imagemagick.jpg b/modules/gallery/images/imagemagick.jpg Binary files differnew file mode 100644 index 00000000..d83c4509 --- /dev/null +++ b/modules/gallery/images/imagemagick.jpg diff --git a/modules/gallery/js/albums_form_add.js b/modules/gallery/js/albums_form_add.js new file mode 100644 index 00000000..06a364f3 --- /dev/null +++ b/modules/gallery/js/albums_form_add.js @@ -0,0 +1,12 @@ +$("#gAddAlbumForm input[name=title]").change( + function() { + $("#gAddAlbumForm input[name=name]").attr( + "value", $("#gAddAlbumForm input[name=title]").attr("value"). + replace(/\s+/g, "_").replace(/\.+$/, "")); + }); +$("#gAddAlbumForm input[name=title]").keyup( + function() { + $("#gAddAlbumForm input[name=name]").attr( + "value", $("#gAddAlbumForm input[name=title]").attr("value"). + replace(/\s+/g, "_").replace(/\.+$/, "")); + }); diff --git a/modules/gallery/js/fullsize.js b/modules/gallery/js/fullsize.js new file mode 100644 index 00000000..7428adb5 --- /dev/null +++ b/modules/gallery/js/fullsize.js @@ -0,0 +1,78 @@ +/** + * @todo Move inline CSS out to external style sheet (theme style sheet) + */ +$(document).ready(function() { + $(".gFullSizeLink").click(function() { + var width = $(document).width(); + var height = $(document).height(); + + $("body").append('<div id="gFullsizeOverlay" class="ui-dialog-overlay" ' + + 'style="border: none; margin: 0; padding: 0; background: #000 ' + + 'none repeat scroll 0% 0%; position: absolute; top: 0px; left: 0px; ' + + 'width: ' + width + 'px; height: ' + height + 'px; opacity: 0.7; ' + + '-moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; ' + + '-moz-background-inline-policy: -moz-initial; z-index: 1001;"> </div>'); + + var image_size = _auto_fit(fullsize_detail.width, fullsize_detail.height); + + $("body").append('<div id="gFullsize" class="ui-dialog ui-widget" ' + + 'style="overflow: hidden; display: block; ' + + 'position: absolute; z-index: 1002; outline-color: -moz-use-text-color; ' + + 'outline-style: none; outline-width: 0px; ' + + 'height: ' + image_size.height + 'px; ' + + 'width: ' + image_size.width + 'px; ' + + 'top: ' + image_size.top + 'px; left: ' + image_size.left + 'px;">' + + '<img id="gFullSizeImage" src="' + fullsize_detail.url + '"' + + 'height="' + image_size.height + '" width="' + image_size.width + '"/></div>'); + + $("#gFullsize").append('<span id="gFullsizeClose" class="fg-button ui-icon ui-state-default ' + + 'ui-icon-closethick ui-corner-all" style="z-index: 1003; position: absolute; ' + + 'right: 1em; top: 1em;"></span>'); + $("#gFullsizeClose").click(function() { + $("#gFullsizeOverlay*").remove(); + $("#gFullsize").remove(); + }); + $(window).resize(function() { + $("#gFullsizeOverlay").width($(document).width()); + $("#gFullsizeOverlay").height($(document).height()); + image_size = _auto_fit(fullsize_detail.width, fullsize_detail.height); + $("#gFullsize").height(image_size.height); + $("#gFullsize").width(image_size.width); + $("#gFullsize").css("top", image_size.top); + $("#gFullsize").css("left", image_size.left); + $("#gFullSizeImage").height(image_size.height); + $("#gFullSizeImage").width(image_size.width); + }); + }); +}); + +/* + * Calculate the size of the image panel based on the size of the image and the size of the + * window. Scale the image so the entire panel fits in the view port. + */ +function _auto_fit(imageWidth, imageHeight) { + // ui-dialog gives a padding of 2 pixels + var windowWidth = $(window).width() - 10; + var windowHeight = $(window).height() - 10; + + /* If the width is greater then scale the image width first */ + if (imageWidth > windowWidth) { + var ratio = windowWidth / imageWidth; + imageWidth *= ratio; + imageHeight *= ratio; + } + /* after scaling the width, check that the height fits */ + if (imageHeight > windowHeight) { + var ratio = windowHeight / imageHeight; + imageWidth *= ratio; + imageHeight *= ratio; + } + + // handle the case where the calculation is almost zero (2.14e-14) + return { + top: ((windowHeight - imageHeight) / 2).toFixed(2), + left: ((windowWidth - imageWidth) / 2).toFixed(2), + width: imageWidth.toFixed(2), + height: imageHeight.toFixed(2) + }; +} diff --git a/modules/gallery/js/l10n_client.js b/modules/gallery/js/l10n_client.js new file mode 100644 index 00000000..f43671f1 --- /dev/null +++ b/modules/gallery/js/l10n_client.js @@ -0,0 +1,195 @@ +// Fork from Drupal's l10n_client module, originally written by: +// Gbor Hojtsy http://drupal.org/user/4166 (original author) +// Young Hahn / Development Seed - http://developmentseed.org/ (friendly user interface) + +var Gallery = Gallery || { 'behaviors': {} }; + +Gallery.attachBehaviors = function(context) { + context = context || document; + // Execute all of them. + jQuery.each(Gallery.behaviors, + function() { + this(context); + }); +}; + +$(document).ready(function() { + Gallery.attachBehaviors(this); +}); + + +// Store all l10n_client related data + methods in its own object +jQuery.extend(Gallery, { + l10nClient: new (function() { + // Set "selected" string to unselected, i.e. -1 + this.selected = -1; + // Keybindings + this.keys = {'toggle':'ctrl+shift+s', 'clear': 'esc'}; // Keybindings + // Keybinding functions + this.key = function(pressed) { + switch(pressed) { + case 'toggle': + // Grab user-hilighted text & send it into the search filter + userSelection = window.getSelection ? window.getSelection() : document.getSelection ? document.getSelection() : document.selection.createRange().text; + userSelection = String(userSelection); + if(userSelection.length > 0) { + Gallery.l10nClient.filter(userSelection); + Gallery.l10nClient.toggle(1); + $('#l10n-client #gL10nSearch').focus(); + } else { + if($('#l10n-client').is('.hidden')) { + Gallery.l10nClient.toggle(1); + if(!$.browser.safari) { + $('#l10n-client #gL10nSearch').focus(); + } + } else { + Gallery.l10nClient.toggle(0); + } + } + break; + case 'clear': + this.filter(false); + break; + } + } + // Toggle the l10nclient + this.toggle = function(state) { + switch(state) { + case 1: + $('#l10n-client-string-select, #l10n-client-string-editor, #l10n-client .labels .label').show(); + $('#l10n-client').height('22em').removeClass('hidden'); + $('#l10n-client .labels .toggle').text('X'); + /* + * This CSS clashes with Gallery's CSS, probably due to + * YUI's grid / floats. + if(!$.browser.msie) { + $('body').css('border-bottom', '22em solid #fff'); + } + */ + $.cookie('Gallery_l10n_client', '1', {expires: 7, path: '/'}); + break; + case 0: + $('#l10n-client-string-select, #l10n-client-string-editor, #l10n-client .labels .label').hide(); + $('#l10n-client').height('2em').addClass('hidden'); + // TODO: Localize this message + $('#l10n-client .labels .toggle').text('Translate Text'); + /* + if(!$.browser.msie) { + $('body').css('border-bottom', '0px'); + } + */ + $.cookie('Gallery_l10n_client', '0', {expires: 7, path: '/'}); + break; + } + } + // Get a string from the DOM tree + this.getString = function(index, type) { + return l10n_client_data[index][type]; + } + // Set a string in the DOM tree + this.setString = function(index, data) { + l10n_client_data[index]['translation'] = data; + } + // Filter the the string list by a search string + this.filter = function(search) { + if(search == false || search == '') { + $('#l10n-client #l10n-search-filter-clear').focus(); + $('#l10n-client-string-select li').show(); + $('#l10n-client #gL10nSearch').val(''); + $('#l10n-client #gL10nSearch').focus(); + } else { + if(search.length > 0) { + $('#l10n-client-string-select li').hide(); + $('#l10n-client-string-select li:contains('+search+')').show(); + $('#l10n-client #gL10nSearch').val(search); + } + } + } + }) +}); + +// Attaches the localization editor behavior to all required fields. +Gallery.behaviors.l10nClient = function(context) { + + switch($.cookie('Gallery_l10n_client')) { + case '1': + Gallery.l10nClient.toggle(1); + break; + default: + Gallery.l10nClient.toggle(0); + break; + } + + // If the selection changes, copy string values to the source and target fields. + // Add class to indicate selected string in list widget. + $('#l10n-client-string-select li').click(function() { + $('#l10n-client-string-select li').removeClass('active'); + $(this).addClass('active'); + var index = $('#l10n-client-string-select li').index(this); + + $('#l10n-client-string-editor .source-text').text(Gallery.l10nClient.getString(index, 'source')); + $("#gL10nClientSaveForm input[name='l10n-message-source']").val(Gallery.l10nClient.getString(index, 'source')); + $('#gL10nClientSaveForm #l10n-edit-target').val(Gallery.l10nClient.getString(index, 'translation')); + + Gallery.l10nClient.selected = index; + }); + + // When l10n_client window is clicked, toggle based on current state. + $('#l10n-client .labels .toggle').click(function() { + if($('#l10n-client').is('.hidden')) { + Gallery.l10nClient.toggle(1); + } else { + Gallery.l10nClient.toggle(0); + } + }); + + // Register keybindings using jQuery hotkeys + // TODO: Either remove hotkeys code or add query.hotkeys.js. + if($.hotkeys) { + $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle')}); + $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #gL10nSearch', type:'keyup'}, function(){Gallery.l10nClient.key('clear')}); + } + + // Custom listener for l10n_client livesearch + $('#l10n-client #gL10nSearch').keyup(function(key) { + Gallery.l10nClient.filter($('#l10n-client #gL10nSearch').val()); + }); + + // Clear search + $('#l10n-client #l10n-search-filter-clear').click(function() { + Gallery.l10nClient.filter(false); + return false; + }); + + // Send AJAX POST data on form submit. + $('#gL10nClientSaveForm').ajaxForm({ + dataType: "json", + success: function(data) { + // Store string in local js + Gallery.l10nClient.setString(Gallery.l10nClient.selected, $('#gL10nClientSaveForm #l10n-edit-target').val()); + + // Mark string as translated. + $('#l10n-client-string-select li').eq(Gallery.l10nClient.selected).removeClass('untranslated').removeClass('active').addClass('translated').text($('#gL10nClientSaveForm #l10n-edit-target').val()); + + // Empty input fields. + $('#l10n-client-string-editor .source-text').html(''); + $('#gL10nClientSaveForm #l10n-edit-target').val(''); + $("#gL10nClientSaveForm input[name='l10n-message-source']").val(''); + }, + error: function(xmlhttp) { + // TODO: Localize this message + alert('An HTTP error @status occured (or empty response).'.replace('@status', xmlhttp.status)); + } + }); + + + // Copy source text to translation field on button click. + $('#gL10nClientSaveForm #l10n-edit-copy').click(function() { + $('#gL10nClientSaveForm #l10n-edit-target').val($('#l10n-client-string-editor .source-text').text()); + }); + + // Clear translation field on button click. + $('#gL10nClientSaveForm #l10n-edit-clear').click(function() { + $('#gL10nClientSaveForm #l10n-edit-target').val(''); + }); +}; diff --git a/modules/gallery/js/quick.js b/modules/gallery/js/quick.js new file mode 100644 index 00000000..e7f35cea --- /dev/null +++ b/modules/gallery/js/quick.js @@ -0,0 +1,95 @@ +$(document).ready(function() { + if ($("#gAlbumGrid").length) { + // @todo Add quick edit pane for album (meta, move, permissions, delete) + $(".gItem").hover(show_quick, function() {}); + } + if ($("#gPhoto").length) { + $("#gPhoto").hover(show_quick, function() {}); + } +}); + +var show_quick = function() { + var cont = $(this); + var quick = $(this).find(".gQuick"); + $("#gQuickPane").remove(); + cont.append("<div id=\"gQuickPane\"></div>"); + var img = cont.find(".gThumbnail,.gResize"); + var pos = cont.position(); + $("#gQuickPane").css({ + "position": "absolute", + "top": pos.top, + "left": pos.left, + "text-align": "center", + "width": cont.innerWidth() + 1, + "height": "auto" + }).hide(); + cont.hover(function() {}, hide_quick); + $.get( + quick.attr("href"), + {}, + function(data, textStatus) { + $("#gQuickPane").html(data).slideDown("fast"); + $(".ui-state-default").hover( + function(){ + $(this).addClass("ui-state-hover"); + }, + function(){ + $(this).removeClass("ui-state-hover"); + } + ); + $("#gQuickPane a:not(.options)").click(function(e) { + e.preventDefault(); + if ($(this).attr("id") == "gQuickDelete" && + !confirm($(this).attr("ref"))) { + return; + } + quick_do(cont, $(this), img); + }); + $("#gQuickPane a.options").click(function(e) { + e.preventDefault(); + $("#gQuickPaneOptions").slideToggle("fast"); + }); + } + ); +}; + +var quick_do = function(cont, pane, img) { + if (pane.hasClass("ui-state-disabled")) { + return false; + } + if (pane.hasClass("gDialogLink")) { + openDialog(pane, function() { window.location.reload(); }); + } else { + img.css("opacity", "0.1"); + cont.addClass("gLoadingLarge"); + $.ajax({ + type: "GET", + url: pane.attr("href"), + dataType: "json", + success: function(data) { + img.css("opacity", "1"); + cont.removeClass("gLoadingLarge"); + if (data.src) { + img.attr("width", data.width); + img.attr("height", data.height); + img.attr("src", data.src); + if (data.height > data.width) { + img.css("margin-top", -32); + } else { + img.css("margin-top", 0); + } + } else if (data.location) { + window.location = data.location; + } else if (data.reload) { + window.location.reload(); + } + } + }); + } + return false; +}; + +var hide_quick = function() { + $("#gQuickPane").remove(); +}; + diff --git a/modules/gallery/libraries/Admin_View.php b/modules/gallery/libraries/Admin_View.php new file mode 100644 index 00000000..acc3f8ec --- /dev/null +++ b/modules/gallery/libraries/Admin_View.php @@ -0,0 +1,126 @@ +<?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 Admin_View_Core extends View { + private $theme_name = null; + + /** + * Attempts to load a view and pre-load view data. + * + * @throws Kohana_Exception if the requested view cannot be found + * @param string $name view name + * @param string $theme_name view name + * @return void + */ + public function __construct($name) { + $theme_name = module::get_var("core", "active_site_theme"); + if (!file_exists("themes/$theme_name")) { + module::set_var("core", "active_site_theme", "admin_default"); + theme::load_themes(); + Kohana::log("error", "Unable to locate theme '$theme_name', switching to default theme."); + } + parent::__construct($name); + + $this->theme_name = module::get_var("core", "active_admin_theme"); + if (user::active()->admin) { + $this->theme_name = Input::instance()->get("theme", $this->theme_name); + } + $this->sidebar = ""; + $this->set_global("theme", $this); + $this->set_global("user", user::active()); + } + + public function url($path, $absolute_url=false) { + $arg = "themes/{$this->theme_name}/$path"; + return $absolute_url ? url::abs_file($arg) : url::file($arg); + } + + public function display($page_name, $view_class="View") { + return new $view_class($page_name); + } + + public function admin_menu() { + $menu = Menu::factory("root"); + core_menu::admin($menu, $this); + + foreach (module::active() as $module) { + if ($module->name == "core") { + continue; + } + $class = "{$module->name}_menu"; + if (method_exists($class, "admin")) { + call_user_func_array(array($class, "admin"), array(&$menu, $this)); + } + } + + print $menu; + } + + /** + * Print out any site wide status information. + */ + public function site_status() { + return site_status::get(); + } + + /** + * Print out any messages waiting for this user. + */ + public function messages() { + return message::get(); + } + + /** + * Handle all theme functions that insert module content. + */ + public function __call($function, $args) { + switch ($function) { + case "admin_credits"; + case "admin_footer": + case "admin_header_top": + case "admin_header_bottom": + case "admin_page_bottom": + case "admin_page_top": + case "admin_head": + $blocks = array(); + foreach (module::active() as $module) { + $helper_class = "{$module->name}_theme"; + if (method_exists($helper_class, $function)) { + $blocks[] = call_user_func_array( + array($helper_class, $function), + array_merge(array($this), $args)); + } + } + + if (Session::instance()->get("debug")) { + if ($function != "admin_head") { + array_unshift( + $blocks, "<div class=\"gAnnotatedThemeBlock gAnnotatedThemeBlock_$function\">" . + "<div class=\"title\">$function</div>"); + $blocks[] = "</div>"; + } + } + + return implode("\n", $blocks); + + default: + throw new Exception("@todo UNKNOWN_THEME_FUNCTION: $function"); + } + } +}
\ No newline at end of file diff --git a/modules/gallery/libraries/Block.php b/modules/gallery/libraries/Block.php new file mode 100644 index 00000000..6fe679f1 --- /dev/null +++ b/modules/gallery/libraries/Block.php @@ -0,0 +1,30 @@ +<?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 Block_Core { + public $content = null; + public $css_id = null; + public $id = null; + public $title = null; + public $anchor = null; + + public function __toString() { + return View::factory("block.html", get_object_vars($this))->__toString(); + } +} diff --git a/modules/gallery/libraries/I18n.php b/modules/gallery/libraries/I18n.php new file mode 100644 index 00000000..c936be88 --- /dev/null +++ b/modules/gallery/libraries/I18n.php @@ -0,0 +1,410 @@ +<?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. + */ + +/** + * Translates a localizable message. + * @param $message String The message to be translated. E.g. "Hello world" + * @param $options array (optional) Options array for key value pairs which are used + * for pluralization and interpolation. Special key: "locale" to override the + * currently configured locale. + * @return String The translated message string. + */ +function t($message, $options=array()) { + return I18n::instance()->translate($message, $options); +} + +/** + * Translates a localizable message with plural forms. + * @param $singular String The message to be translated. E.g. "There is one album." + * @param $plural String The plural message to be translated. E.g. + * "There are %count albums." + * @param $count Number The number which is inserted for the %count placeholder and + * which is used to select the proper plural form ($singular or $plural). + * @param $options array (optional) Options array for key value pairs which are used + * for pluralization and interpolation. Special key: "locale" to override the + * currently configured locale. + * @return String The translated message string. + */ +function t2($singular, $plural, $count, $options=array()) { + return I18n::instance()->translate(array("one" => $singular, "other" => $plural), + array_merge($options, array("count" => $count))); +} + +class I18n_Core { + private static $_instance; + private $_config = array(); + private $_call_log = array(); + private $_cache = array(); + + private function __construct($config) { + $this->_config = $config; + $this->locale($config['default_locale']); + } + + public static function instance($config=null) { + if (self::$_instance == NULL || isset($config)) { + $config = isset($config) ? $config : Kohana::config('locale'); + if (empty($config['default_locale'])) { + $config['default_locale'] = module::get_var('core', 'default_locale'); + } + self::$_instance = new I18n_Core($config); + } + + return self::$_instance; + } + + public function locale($locale=null) { + if ($locale) { + $this->_config['default_locale'] = $locale; + // Attempt to set PHP's locale as well (for number formatting, collation, etc.) + // TODO: See G2 for better fallack code. + $locale_prefs = array($locale); + $locale_prefs[] = 'en_US'; + setlocale(LC_ALL, $locale_prefs); + } + return $this->_config['default_locale']; + } + + /** + * Translates a localizable message. + * @param $message String|array The message to be translated. E.g. "Hello world" + * or array("one" => "One album", "other" => "%count albums") + * @param $options array (optional) Options array for key value pairs which are used + * for pluralization and interpolation. Special keys are "count" and "locale", + * the latter to override the currently configured locale. + * @return String The translated message string. + */ + public function translate($message, $options=array()) { + $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale']; + $count = isset($options['count']) ? $options['count'] : null; + $values = $options; + unset($values['locale']); + $this->log($message, $options); + + $entry = $this->lookup($locale, $message); + + if (null === $entry) { + // Default to the root locale. + $entry = $message; + $locale = $this->_config['root_locale']; + } + + $entry = $this->pluralize($locale, $entry, $count); + + $entry = $this->interpolate($locale, $entry, $values); + + return $entry; + } + + private function lookup($locale, $message) { + if (!isset($this->_cache[$locale])) { + $this->_cache[$locale] = array(); + // TODO: Load data from locale file instead of the DB. + foreach (Database::instance() + ->select("key", "translation") + ->from("incoming_translations") + ->where(array("locale" => $locale)) + ->get() + ->as_array() as $row) { + $this->_cache[$locale][$row->key] = unserialize($row->translation); + } + + // Override incoming with outgoing... + foreach (Database::instance() + ->select("key", "translation") + ->from("outgoing_translations") + ->where(array("locale" => $locale)) + ->get() + ->as_array() as $row) { + $this->_cache[$locale][$row->key] = unserialize($row->translation); + } + } + + $key = self::get_message_key($message); + + if (isset($this->_cache[$locale][$key])) { + return $this->_cache[$locale][$key]; + } else { + return null; + } + } + + public function has_translation($message, $options=null) { + $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale']; + $count = empty($options['count']) ? null : $options['count']; + $values = $options; + unset($values['locale']); + $this->log($message, $options); + + $entry = $this->lookup($locale, $message); + + if (null === $entry) { + return false; + } else if (!is_array($entry)) { + return $entry !== ''; + } else { + $plural_key = self::get_plural_key($locale, $count); + return isset($entry[$plural_key]) + && $entry[$plural_key] !== null + && $entry[$plural_key] !== ''; + } + } + + public static function get_message_key($message) { + $as_string = is_array($message) ? implode('|', $message) : $message; + return md5($as_string); + } + + private function interpolate($locale, $string, $values) { + // TODO: Handle locale specific number formatting. + + // Replace x_y before replacing x. + krsort($values, SORT_STRING); + + $keys = array(); + foreach (array_keys($values) as $key) { + $keys[] = "%$key"; + } + return str_replace($keys, array_values($values), $string); + } + + private function pluralize($locale, $entry, $count) { + if (!is_array($entry)) { + return $entry; + } + + $plural_key = self::get_plural_key($locale, $count); + if (!isset($entry[$plural_key])) { + // Fallback to the default plural form. + $plural_key = 'other'; + } + + if (isset($entry[$plural_key])) { + return $entry[$plural_key]; + } else { + // Fallback to just any plural form. + list ($plural_key, $string) = each($entry); + return $string; + } + } + + private function log($message, $options) { + $key = self::get_message_key($message); + isset($this->_call_log[$key]) or $this->_call_log[$key] = array($message, $options); + } + + public function call_log() { + return $this->_call_log; + } + + private static function get_plural_key($locale, $count) { + $parts = explode('_', $locale); + $language = $parts[0]; + + // Data from CLDR 1.6 (http://unicode.org/cldr/data/common/supplemental/plurals.xml). + // Docs: http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html + switch ($language) { + case 'az': + case 'fa': + case 'hu': + case 'ja': + case 'ko': + case 'my': + case 'to': + case 'tr': + case 'vi': + case 'yo': + case 'zh': + case 'bo': + case 'dz': + case 'id': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ms': + case 'th': + return 'other'; + + case 'ar': + if ($count == 0) { + return 'zero'; + } else if ($count == 1) { + return 'one'; + } else if ($count == 2) { + return 'two'; + } else if (is_int($count) && ($i = $count % 100) >= 3 && $i <= 10) { + return 'few'; + } else if (is_int($count) && ($i = $count % 100) >= 11 && $i <= 99) { + return 'many'; + } else { + return 'other'; + } + + case 'pt': + case 'am': + case 'bh': + case 'fil': + case 'tl': + case 'guw': + case 'hi': + case 'ln': + case 'mg': + case 'nso': + case 'ti': + case 'wa': + if ($count == 0 || $count == 1) { + return 'one'; + } else { + return 'other'; + } + + case 'fr': + if ($count >= 0 and $count < 2) { + return 'one'; + } else { + return 'other'; + } + + case 'lv': + if ($count == 0) { + return 'zero'; + } else if ($count % 10 == 1 && $count % 100 != 11) { + return 'one'; + } else { + return 'other'; + } + + case 'ga': + case 'se': + case 'sma': + case 'smi': + case 'smj': + case 'smn': + case 'sms': + if ($count == 1) { + return 'one'; + } else if ($count == 2) { + return 'two'; + } else { + return 'other'; + } + + case 'ro': + case 'mo': + if ($count == 1) { + return 'one'; + } else if (is_int($count) && $count == 0 && ($i = $count % 100) >= 1 && $i <= 19) { + return 'few'; + } else { + return 'other'; + } + + case 'lt': + if (is_int($count) && $count % 10 == 1 && $count % 100 != 11) { + return 'one'; + } else if (is_int($count) && ($i = $count % 10) >= 2 && $i <= 9 && ($i = $count % 100) < 11 && $i > 19) { + return 'few'; + } else { + return 'other'; + } + + case 'hr': + case 'ru': + case 'sr': + case 'uk': + case 'be': + case 'bs': + case 'sh': + if (is_int($count) && $count % 10 == 1 && $count % 100 != 11) { + return 'one'; + } else if (is_int($count) && ($i = $count % 10) >= 2 && $i <= 4 && ($i = $count % 100) < 12 && $i > 14) { + return 'few'; + } else if (is_int($count) && ($count % 10 == 0 || (($i = $count % 10) >= 5 && $i <= 9) || (($i = $count % 100) >= 11 && $i <= 14))) { + return 'many'; + } else { + return 'other'; + } + + case 'cs': + case 'sk': + if ($count == 1) { + return 'one'; + } else if (is_int($count) && $count >= 2 && $count <= 4) { + return 'few'; + } else { + return 'other'; + } + + case 'pl': + if ($count == 1) { + return 'one'; + } else if (is_int($count) && ($i = $count % 10) >= 2 && $i <= 4 && + ($i = $count % 100) < 12 && $i > 14 && ($i = $count % 100) < 22 && $i > 24) { + return 'few'; + } else { + return 'other'; + } + + case 'sl': + if ($count % 100 == 1) { + return 'one'; + } else if ($count % 100 == 2) { + return 'two'; + } else if (is_int($count) && ($i = $count % 100) >= 3 && $i <= 4) { + return 'few'; + } else { + return 'other'; + } + + case 'mt': + if ($count == 1) { + return 'one'; + } else if ($count == 0 || is_int($count) && ($i = $count % 100) >= 2 && $i <= 10) { + return 'few'; + } else if (is_int($count) && ($i = $count % 100) >= 11 && $i <= 19) { + return 'many'; + } else { + return 'other'; + } + + case 'mk': + if ($count % 10 == 1) { + return 'one'; + } else { + return 'other'; + } + + case 'cy': + if ($count == 1) { + return 'one'; + } else if ($count == 2) { + return 'two'; + } else if ($count == 8 || $count == 11) { + return 'many'; + } else { + return 'other'; + } + + default: // en, de, etc. + return $count == 1 ? 'one' : 'other'; + } + } +}
\ No newline at end of file diff --git a/modules/gallery/libraries/MY_Database.php b/modules/gallery/libraries/MY_Database.php new file mode 100644 index 00000000..c56f16e8 --- /dev/null +++ b/modules/gallery/libraries/MY_Database.php @@ -0,0 +1,92 @@ +<?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 Database extends Database_Core { + protected $_table_names; + + public function open_paren() { + $this->where[] = "("; + return $this; + } + + public function close_paren() { + // Search backwards for the last opening paren and resolve it + $i = count($this->where) - 1; + $this->where[$i] .= ")"; + while (--$i >= 0) { + if ($this->where[$i] == "(") { + // Remove the paren from the where clauses, and add it to the right of the operator of the + // next where clause. If removing the paren makes the next where clause the first element + // in the where list, then the operator shouldn't be there. It's there because we + // calculate whether or not we need an operator based on the number of where clauses, and + // the open paren seems like a where clause even though it isn't. + array_splice($this->where, $i, 1); + $this->where[$i] = preg_replace("/^(AND|OR) /", $i ? "\\1 (" : "(", $this->where[$i]); + return $this; + } + } + + throw new Kohana_Database_Exception('database.missing_open_paren'); + } + + /** + * Parse the query string and convert any strings of the form `\([a-zA-Z0-9_]*?)\] + * table prefix . $1 + */ + public function query($sql = '') { + if (!empty($sql)) { + $sql = $this->add_table_prefixes($sql); + } + return parent::query($sql); + } + + public function add_table_prefixes($sql) { + $prefix = $this->config["table_prefix"]; + if (strpos($sql, "SHOW TABLES") === 0) { + /* + * Don't ignore "show tables", otherwise we could have a infinite + * @todo this may have to be changed if we support more than mysql + */ + return $sql; + } else if (strpos($sql, "CREATE TABLE") === 0) { + // 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"; + } + + if (!isset($this->_table_names)) { + // This should only run once on the first query + $this->_table_names =array(); + $len = strlen($prefix); + foreach($this->list_tables() as $table_name) { + if ($len > 0) { + $naked_name = strpos($table_name, $prefix) !== 0 ? + $table_name : substr($table_name, $len); + } else { + $naked_name = $table_name; + } + $this->_table_names["{{$naked_name}}"] = $table_name; + } + } + + return empty($this->_table_names) ? $sql : strtr($sql, $this->_table_names); + } +}
\ No newline at end of file diff --git a/modules/gallery/libraries/MY_Forge.php b/modules/gallery/libraries/MY_Forge.php new file mode 100644 index 00000000..17d0465b --- /dev/null +++ b/modules/gallery/libraries/MY_Forge.php @@ -0,0 +1,59 @@ +<?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 Forge extends Forge_Core { + /** + * Force a CSRF element into every form. + */ + public function __construct($action=null, $title='', $method=null, $attr=array()) { + parent::__construct($action, $title, $method, $attr); + $this->hidden("csrf")->value(""); + } + /** + * Use our own template + */ + public function render($template="form.html", $custom=false) { + $this->hidden["csrf"]->value(access::csrf_token()); + return parent::render($template, $custom); + } + + /** + * Associate validation rules defined in the model with this form. + */ + public function add_rules_from($model) { + foreach ($this->inputs as $name => $input) { + if (isset($input->inputs)) { + $input->add_rules_from($model); + } + if (isset($model->rules[$name])) { + $input->rules($model->rules[$name]); + } + } + } + + /** + * Validate our CSRF value as a mandatory part of all form validation. + */ + public function validate() { + $status = parent::validate(); + access::verify_csrf(); + return $status; + } +}
\ No newline at end of file diff --git a/modules/gallery/libraries/MY_ORM.php b/modules/gallery/libraries/MY_ORM.php new file mode 100644 index 00000000..fb2f80a7 --- /dev/null +++ b/modules/gallery/libraries/MY_ORM.php @@ -0,0 +1,46 @@ +<?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 ORM extends ORM_Core { + public function open_paren() { + $this->db->open_paren(); + return $this; + } + + public function close_paren() { + $this->db->close_paren(); + return $this; + } +} + +/** + * Slide this in here for convenience. We won't ever be overloading ORM_Iterator without ORM. + */ +class ORM_Iterator extends ORM_Iterator_Core { + /** + * Cache the result row + */ + public function current() { + $row = parent::current(); + if (is_object($row)) { + model_cache::set($row); + } + return $row; + } +}
\ No newline at end of file diff --git a/modules/gallery/libraries/MY_Pagination.php b/modules/gallery/libraries/MY_Pagination.php new file mode 100644 index 00000000..d06a974f --- /dev/null +++ b/modules/gallery/libraries/MY_Pagination.php @@ -0,0 +1,35 @@ +<?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 Pagination extends Pagination_Core { + public function render($style=NULL) { + // Hide single page pagination + if ($this->auto_hide === TRUE AND $this->total_pages <= 1) { + return ""; + } + + if ($style === NULL) { + // Use default style + $style = $this->style; + } + + // Return rendered pagination view + return View::factory("pager.html", get_object_vars($this))->render(); + } +} diff --git a/modules/gallery/libraries/MY_View.php b/modules/gallery/libraries/MY_View.php new file mode 100644 index 00000000..836d1087 --- /dev/null +++ b/modules/gallery/libraries/MY_View.php @@ -0,0 +1,46 @@ +<?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 View extends View_Core { + /** + * Override View_Core::__construct so that we can set the csrf value into all views. + * + * @see View_Core::__construct + */ + public function __construct($name = NULL, $data = NULL, $type = NULL) { + parent::__construct($name, $data, $type); + $this->set_global("csrf", access::csrf_token()); + } + + /** + * Override View_Core::render so that we trap errors stemming from bad PHP includes and show a + * visible stack trace to help developers. + * + * @see View_Core::render + */ + public function render($print=false, $renderer=false) { + try { + return parent::render($print, $renderer); + } catch (Exception $e) { + Kohana::Log('error', $e->getTraceAsString()); + Kohana::Log('debug', $e->getMessage()); + return ""; + } + } +} diff --git a/modules/gallery/libraries/Menu.php b/modules/gallery/libraries/Menu.php new file mode 100644 index 00000000..d19d8b1e --- /dev/null +++ b/modules/gallery/libraries/Menu.php @@ -0,0 +1,187 @@ +<?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 Menu_Element { + public $label; + public $url; + public $css_id; + public $css_class; + public $id; + + /** + * Set the id + * @chainable + */ + public function id($id) { + $this->id = $id; + return $this; + } + + /** + * Set the label + * @chainable + */ + public function label($label) { + $this->label = $label; + return $this; + } + + /** + * Set the url + * @chainable + */ + public function url($url) { + $this->url = $url; + return $this; + } + + /** + * Set the css id + * @chainable + */ + public function css_id($css_id) { + $this->css_id = $css_id; + return $this; + } + + /** + * Set the css class + * @chainable + */ + public function css_class($css_class) { + $this->css_class = $css_class; + return $this; + } + +} + +/** + * Menu element that provides a link to a new page. + */ +class Menu_Element_Link extends Menu_Element { + public function __toString() { + if (isset($this->css_id) && !empty($this->css_id)) { + $css_id = " id=\"$this->css_id\""; + } else { + $css_id = ""; + } + if (isset($this->css_class) && !empty($this->css_class)) { + $css_class = " $this->css_class"; + } else { + $css_class = ""; + } + return "<li><a$css_id class=\"gMenuElement$css_class\" href=\"$this->url\" " . + "title=\"$this->label\">$this->label</a></li>"; + } +} + +/** + * Menu element that provides a pop-up dialog + */ +class Menu_Element_Dialog extends Menu_Element { + public function __toString() { + if (isset($this->css_id) && !empty($this->css_id)) { + $css_id = " id=\"$this->css_id\""; + } else { + $css_id = ""; + } + if (isset($this->css_class) && !empty($this->css_class)) { + $css_class = " $this->css_class"; + } else { + $css_class = ""; + } + return "<li><a$css_id class=\"gMenuLink$css_class\" href=\"$this->url\" " . + "title=\"$this->label\">$this->label</a></li>"; + } +} + +/** + * Root menu or submenu + */ +class Menu_Core extends Menu_Element { + public $elements; + public $is_root = false; + + /** + * Return an instance of a Menu_Element + * @chainable + */ + public static function factory($type) { + switch($type) { + case "link": + return new Menu_Element_Link(); + + case "dialog": + return new Menu_Element_Dialog(); + + case "root": + $menu = new Menu(); + $menu->is_root = true; + return $menu; + + case "submenu": + return new Menu(); + + default: + throw Exception("@todo UNKNOWN_MENU_TYPE"); + } + } + + public function __construct() { + $this->elements = array(); + } + + /** + * Add a new element to this menu + */ + public function append($menu_element) { + $this->elements[$menu_element->id] = $menu_element; + return $this; + } + + /** + * Add a new element to this menu + */ + public function add_after($target_id, $new_menu_element) { + $copy = array(); + foreach ($this->elements as $id => $menu_element) { + $copy[$id] = $menu_element; + if ($id == $target_id) { + $copy[$new_menu_element->id] = $new_menu_element; + } + } + $this->elements = $copy; + return $this; + } + + /** + * Retrieve a Menu_Element by id + */ + public function get($id) { + return $this->elements[$id]; + } + + public function __toString() { + $html = $this->is_root ? "<ul class=\"gMenu\">" : + "<li><a href=#>$this->label</a><ul class=\"gMenu\">"; + $html .= implode("\n", $this->elements); + $html .= $this->is_root ? "</ul>" : "</ul></li>"; + return $html; + } +} diff --git a/modules/gallery/libraries/ORM_MPTT.php b/modules/gallery/libraries/ORM_MPTT.php new file mode 100644 index 00000000..46280d95 --- /dev/null +++ b/modules/gallery/libraries/ORM_MPTT.php @@ -0,0 +1,307 @@ +<?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. + */ +/** + * Implement Modified Preorder Tree Traversal on top of ORM. + * + * MPTT is an efficient way to store and retrieve hierarchical data in a single database table. + * For a good description, read http://www.sitepoint.com/article/hierarchical-data-database/3/ + * + * This code was heavily influenced by code from: + * - http://code.google.com/p/kohana-mptt/ + * - http://code.google.com/p/kohana-mptt/wiki/Documentation + * - http://code.google.com/p/s7ncms/source/browse/trunk/modules/s7ncms/libraries/ORM_MPTT.php + * + * Unfortunately that code was not ready for production and I did not want to absorb their code + * and licensing issues so I've reimplemented just the features that we need. + */ +class ORM_MPTT_Core extends ORM { + private $model_name = null; + + function __construct($id=null) { + parent::__construct($id); + $this->model_name = inflector::singular($this->table_name); + } + + /** + * Add this node as a child of the parent provided. + * + * @chainable + * @param integer $parent_id the id of the parent node + * @return ORM + */ + function add_to_parent($parent) { + $this->lock(); + + try { + // Make a hole in the parent for this new item + $this->db->query( + "UPDATE {{$this->table_name}} SET `left` = `left` + 2 WHERE `left` >= {$parent->right}"); + $this->db->query( + "UPDATE {{$this->table_name}} SET `right` = `right` + 2 WHERE `right` >= {$parent->right}"); + $parent->right += 2; + + // Insert this item into the hole + $this->left = $parent->right - 2; + $this->right = $parent->right - 1; + $this->parent_id = $parent->id; + $this->level = $parent->level + 1; + $this->save(); + $parent->reload(); + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + + $this->unlock(); + return $this; + } + + /** + * Delete this node and all of its children. + */ + public function delete() { + $children = $this->children(); + if ($children) { + foreach ($this->children() as $item) { + // Deleting children affects the MPTT tree, so we have to reload each child before we + // delete it so that we have current left/right pointers. This is inefficient. + // @todo load each child once, not twice. + $item->reload()->delete(); + } + + // Deleting children has affected this item + $this->reload(); + } + + $this->lock(); + try { + $this->db->query( + "UPDATE {{$this->table_name}} SET `left` = `left` - 2 WHERE `left` > {$this->right}"); + $this->db->query( + "UPDATE {{$this->table_name}} SET `right` = `right` - 2 WHERE `right` > {$this->right}"); + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + + $this->unlock(); + parent::delete(); + } + + /** + * Return true if the target is descendant of this item. + * @param ORM $target + * @return boolean + */ + function is_descendant($target) { + return ($this->left <= $target->left && $this->right >= $target->right); + } + + /** + * Return the parent of this node + * + * @return ORM + */ + function parent() { + if (!$this->parent_id) { + return null; + } + return model_cache::get($this->model_name, $this->parent_id); + } + + /** + * Return all the parents of this node, in order from root to this node's immediate parent. + * + * @return array ORM + */ + function parents() { + return $this + ->where("`left` <= {$this->left}") + ->where("`right` >= {$this->right}") + ->where("id <> {$this->id}") + ->orderby("left", "ASC") + ->find_all(); + } + + /** + * Return all of the children of this node, ordered by id. + * + * @chainable + * @param integer SQL limit + * @param integer SQL offset + * @param array orderby + * @return array ORM + */ + function children($limit=null, $offset=0, $orderby=null) { + $this->where("parent_id", $this->id); + if (empty($orderby)) { + $this->orderby("id", "ASC"); + } else { + $this->orderby($orderby); + } + return $this->find_all($limit, $offset); + } + + /** + * Return all of the children of this node, ordered by id. + * + * @chainable + * @param integer SQL limit + * @param integer SQL offset + * @return array ORM + */ + function children_count() { + return $this->where("parent_id", $this->id)->count_all(); + } + + /** + * Return all of the children of the specified type, ordered by id. + * + * @param integer SQL limit + * @param integer SQL offset + * @param string type to return + * @param array orderby + * @return object ORM_Iterator + */ + function descendants($limit=null, $offset=0, $type=null, $orderby=null) { + $this->where("left >", $this->left) + ->where("right <=", $this->right); + if ($type) { + $this->where("type", $type); + } + + if (empty($orderby)) { + $this->orderby("id", "ASC"); + } else { + $this->orderby($orderby); + } + + return $this->find_all($limit, $offset); + } + + /** + * Return the count of all the children of the specified type. + * + * @param string type to count + * @return integer child count + */ + function descendants_count($type=null) { + $this->where("left >", $this->left) + ->where("right <=", $this->right); + if ($type) { + $this->where("type", $type); + } + return $this->count_all(); + } + + /** + * Move this item to the specified target. + * + * @chainable + * @param Item_Model $target Target node + * @return ORM_MTPP + */ + function move_to($target) { + if ($this->left <= $target->left && + $this->right >= $target->right) { + throw new Exception("@todo INVALID_TARGET can't move item inside itself"); + } + + $number_to_move = (int)(($this->right - $this->left) / 2 + 1); + $size_of_hole = $number_to_move * 2; + $original_left = $this->left; + $original_right = $this->right; + $target_right = $target->right; + $level_delta = ($target->level + 1) - $this->level; + + $this->lock(); + try { + if ($level_delta) { + // Update the levels for the to-be-moved items + $this->db->query( + "UPDATE {{$this->table_name}} SET `level` = `level` + $level_delta" . + " WHERE `left` >= $original_left AND `right` <= $original_right"); + } + + // Make a hole in the target for the move + $target->db->query( + "UPDATE {{$this->table_name}} SET `left` = `left` + $size_of_hole" . + " WHERE `left` >= $target_right"); + $target->db->query( + "UPDATE {{$this->table_name}} SET `right` = `right` + $size_of_hole" . + " WHERE `right` >= $target_right"); + + // Change the parent. + $this->db->query( + "UPDATE {{$this->table_name}} SET `parent_id` = {$target->id}" . + " WHERE `id` = {$this->id}"); + + // If the source is to the right of the target then we just adjusted its left and right above. + $left = $original_left; + $right = $original_right; + if ($original_left > $target_right) { + $left += $size_of_hole; + $right += $size_of_hole; + } + + $new_offset = $target->right - $left; + $this->db->query( + "UPDATE {{$this->table_name}}" . + " SET `left` = `left` + $new_offset," . + " `right` = `right` + $new_offset" . + " WHERE `left` >= $left" . + " AND `right` <= $right"); + + // Close the hole in the source's parent after the move + $this->db->query( + "UPDATE {{$this->table_name}} SET `left` = `left` - $size_of_hole" . + " WHERE `left` > $right"); + $this->db->query( + "UPDATE {{$this->table_name}} SET `right` = `right` - $size_of_hole" . + " WHERE `right` > $right"); + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + + $this->unlock(); + + // Lets reload to get the changes. + $this->reload(); + return $this; + } + + /** + * Lock the tree to prevent concurrent modification. + */ + protected function lock() { + $result = $this->db->query("SELECT GET_LOCK('{$this->table_name}', 1) AS l")->current(); + if (empty($result->l)) { + throw new Exception("@todo UNABLE_TO_LOCK_EXCEPTION"); + } + } + + /** + * Unlock the tree. + */ + protected function unlock() { + $this->db->query("SELECT RELEASE_LOCK('{$this->table_name}')"); + } +} diff --git a/modules/gallery/libraries/Sendmail.php b/modules/gallery/libraries/Sendmail.php new file mode 100644 index 00000000..90998457 --- /dev/null +++ b/modules/gallery/libraries/Sendmail.php @@ -0,0 +1,97 @@ +<?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 Sendmail_Core { + protected $to; + protected $subject; + protected $message; + protected $headers; + protected $line_length = 70; + protected $header_separator = "\r\n"; + + /** + * Return an instance of Sendmail + * @chainable + */ + static function factory() { + return new Sendmail(); + } + + public function __construct() { + $this->headers = array(); + $config = Kohana::config("sendmail"); + foreach ($config as $key => $value) { + $this->$key($value); + } + } + + public function __get($key) { + return null; + } + + public function __call($key, $value) { + switch ($key) { + case "to": + $this->to = is_array($value[0]) ? $value[0] : array($value[0]); + break; + case "header": + if (count($value) != 2) { + throw new Exception("@todo INVALID_HEADER_PARAMETERS"); + } + $this->headers[$value[0]] = $value[1]; + break; + case "from": + $this->headers["From"] = $value[0]; + break; + case "reply_to": + $this->headers["Reply-To"] = $value[0]; + break; + default: + $this->$key = $value[0]; + } + return $this; + } + + public function send() { + if (empty($this->to)) { + throw new Exception("@todo TO_IS_REQUIRED_FOR_MAIL"); + } + $to = implode(", ", $this->to); + $headers = array(); + foreach ($this->headers as $key => $value) { + $key = ucfirst($key); + $headers[] = "$key: $value"; + } + + // The docs say headers should be separated by \r\n, but occasionaly that doesn't work and you + // need to use a single \n. This can be set in config/sendmail.php + $headers = implode($this->header_separator, $headers); + $message = wordwrap($this->message, $this->line_length, "\n"); + if (!$this->mail($to, $this->subject, $message, $headers)) { + Kohana::log("error", wordwrap("Sending mail failed:\nTo: $to\n $this->subject\n" . + "Headers: $headers\n $this->message")); + throw new Exception("@todo SEND_MAIL_FAILED"); + } + return $this; + } + + public function mail($to, $subject, $message, $headers) { + return mail($to, $subject, $message, $headers); + } +} diff --git a/modules/gallery/libraries/Task_Definition.php b/modules/gallery/libraries/Task_Definition.php new file mode 100644 index 00000000..8d9c5922 --- /dev/null +++ b/modules/gallery/libraries/Task_Definition.php @@ -0,0 +1,50 @@ +<?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 Task_Definition_Core { + public $callback; + public $description; + public $name; + public $severity; + + static function factory() { + return new Task_Definition(); + } + + function callback($callback) { + $this->callback = $callback; + return $this; + } + + function description($description) { + $this->description = $description; + return $this; + } + + function name($name) { + $this->name = $name; + return $this; + } + + function severity($severity) { + $this->severity = $severity; + return $this; + } +} diff --git a/modules/gallery/libraries/Theme_View.php b/modules/gallery/libraries/Theme_View.php new file mode 100644 index 00000000..b5b97666 --- /dev/null +++ b/modules/gallery/libraries/Theme_View.php @@ -0,0 +1,221 @@ +<?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 Theme_View_Core extends View { + private $theme_name = null; + + /** + * Attempts to load a view and pre-load view data. + * + * @throws Kohana_Exception if the requested view cannot be found + * @param string $name view name + * @param string $page_type page type: album, photo, tags, etc + * @param string $theme_name view name + * @return void + */ + public function __construct($name, $page_type) { + $theme_name = module::get_var("core", "active_site_theme"); + if (!file_exists("themes/$theme_name")) { + module::set_var("core", "active_site_theme", "default"); + theme::load_themes(); + Kohana::log("error", "Unable to locate theme '$theme_name', switching to default theme."); + } + parent::__construct($name); + + $this->theme_name = module::get_var("core", "active_site_theme"); + if (user::active()->admin) { + $this->theme_name = Input::instance()->get("theme", $this->theme_name); + } + $this->item = null; + $this->tag = null; + $this->set_global("theme", $this); + $this->set_global("user", user::active()); + $this->set_global("page_type", $page_type); + if ($page_type == "album") { + $this->set_global("thumb_proportion", $this->thumb_proportion()); + } + + $maintenance_mode = Kohana::config("core.maintenance_mode", false, false); + if ($maintenance_mode) { + message::warning(t("This site is currently in maintenance mode")); + } + } + + /** + * Proportion of the current thumb_size's to default + * @return int + */ + public function thumb_proportion() { + // @TODO change the 200 to a theme supplied value when and if we come up with an + // API to allow the theme to set defaults. + return module::get_var("core", "thumb_size", 200) / 200; + } + + public function url($path, $absolute_url=false) { + $arg = "themes/{$this->theme_name}/$path"; + return $absolute_url ? url::abs_file($arg) : url::file($arg); + } + + public function item() { + return $this->item; + } + + public function tag() { + return $this->tag; + } + + public function page_type() { + return $this->page_type; + } + + public function display($page_name, $view_class="View") { + return new $view_class($page_name); + } + + public function site_menu() { + $menu = Menu::factory("root"); + if ($this->page_type != "login") { + core_menu::site($menu, $this); + + foreach (module::active() as $module) { + if ($module->name == "core") { + continue; + } + $class = "{$module->name}_menu"; + if (method_exists($class, "site")) { + call_user_func_array(array($class, "site"), array(&$menu, $this)); + } + } + } + + print $menu; + } + + public function album_menu() { + $menu = Menu::factory("root"); + core_menu::album($menu, $this); + + foreach (module::active() as $module) { + if ($module->name == "core") { + continue; + } + $class = "{$module->name}_menu"; + if (method_exists($class, "album")) { + call_user_func_array(array($class, "album"), array(&$menu, $this)); + } + } + + print $menu; + } + + public function photo_menu() { + $menu = Menu::factory("root"); + core_menu::photo($menu, $this); + + foreach (module::active() as $module) { + if ($module->name == "core") { + continue; + } + $class = "{$module->name}_menu"; + if (method_exists($class, "photo")) { + call_user_func_array(array($class, "photo"), array(&$menu, $this)); + } + } + + print $menu; + } + + public function pager() { + if ($this->children_count) { + $this->pagination = new Pagination(); + $this->pagination->initialize( + array('query_string' => 'page', + 'total_items' => $this->children_count, + 'items_per_page' => $this->page_size, + 'style' => 'classic')); + return $this->pagination->render(); + } + } + + /** + * Print out any site wide status information. + */ + public function site_status() { + return site_status::get(); + } + + /** + * Print out any messages waiting for this user. + */ + public function messages() { + return message::get(); + } + + /** + * Handle all theme functions that insert module content. + */ + public function __call($function, $args) { + switch ($function) { + case "album_blocks": + case "album_bottom": + case "album_top": + case "credits"; + case "dynamic_bottom": + case "dynamic_top": + case "footer": + case "head": + case "header_bottom": + case "header_top": + case "page_bottom": + case "page_top": + case "photo_blocks": + case "photo_bottom": + case "photo_top": + case "resize_bottom": + case "resize_top": + case "sidebar_blocks": + case "sidebar_bottom": + case "sidebar_top": + case "thumb_bottom": + case "thumb_info": + case "thumb_top": + $blocks = array(); + foreach (module::active() as $module) { + $helper_class = "{$module->name}_theme"; + if (method_exists($helper_class, $function)) { + $blocks[] = call_user_func_array( + array($helper_class, $function), + array_merge(array($this), $args)); + } + } + if (Session::instance()->get("debug")) { + if ($function != "head") { + array_unshift( + $blocks, "<div class=\"gAnnotatedThemeBlock gAnnotatedThemeBlock_$function gClearFix\">" . + "<div class=\"title\">$function</div>"); + $blocks[] = "</div>"; + } + } + return implode("\n", $blocks); + + default: + throw new Exception("@todo UNKNOWN_THEME_FUNCTION: $function"); + } + } +}
\ No newline at end of file diff --git a/modules/gallery/models/access_cache.php b/modules/gallery/models/access_cache.php new file mode 100644 index 00000000..10d05df7 --- /dev/null +++ b/modules/gallery/models/access_cache.php @@ -0,0 +1,21 @@ +<?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 Access_Cache_Model extends ORM { +} diff --git a/modules/gallery/models/access_intent.php b/modules/gallery/models/access_intent.php new file mode 100644 index 00000000..86cda7b3 --- /dev/null +++ b/modules/gallery/models/access_intent.php @@ -0,0 +1,21 @@ +<?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 Access_Intent_Model extends ORM { +} diff --git a/modules/gallery/models/graphics_rule.php b/modules/gallery/models/graphics_rule.php new file mode 100644 index 00000000..2b5a8968 --- /dev/null +++ b/modules/gallery/models/graphics_rule.php @@ -0,0 +1,21 @@ +<?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 Graphics_Rule_Model extends ORM { +} diff --git a/modules/gallery/models/incoming_translation.php b/modules/gallery/models/incoming_translation.php new file mode 100644 index 00000000..0c9a343f --- /dev/null +++ b/modules/gallery/models/incoming_translation.php @@ -0,0 +1,21 @@ +<?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 Incoming_Translation_Model extends ORM { +} diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php new file mode 100644 index 00000000..4b8cac8e --- /dev/null +++ b/modules/gallery/models/item.php @@ -0,0 +1,497 @@ +<?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 Item_Model extends ORM_MPTT { + protected $children = 'items'; + private $view_restrictions = null; + protected $sorting = array(); + + var $rules = array( + "name" => "required|length[0,255]", + "title" => "required|length[0,255]", + "description" => "length[0,65535]" + ); + + /** + * Add a set of restrictions to any following queries to restrict access only to items + * viewable by the active user. + * @chainable + */ + public function viewable() { + if (is_null($this->view_restrictions)) { + if (user::active()->admin) { + $this->view_restrictions = array(); + } else { + foreach (user::group_ids() as $id) { + // Separate the first restriction from the rest to make it easier for us to formulate + // our where clause below + if (empty($this->view_restrictions)) { + $this->view_restrictions[0] = "view_$id"; + } else { + $this->view_restrictions[1]["view_$id"] = access::ALLOW; + } + } + } + } + switch (count($this->view_restrictions)) { + case 0: + break; + + case 1: + $this->where($this->view_restrictions[0], access::ALLOW); + break; + + default: + $this->open_paren(); + $this->where($this->view_restrictions[0], access::ALLOW); + $this->orwhere($this->view_restrictions[1]); + $this->close_paren(); + break; + } + + return $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() { + 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)) { + @dir::unlink($path); + @dir::unlink(dirname($resize_path)); + @dir::unlink(dirname($thumb_path)); + } else { + @unlink($path); + @unlink($resize_path); + @unlink($thumb_path); + } + } + + /** + * Move this item to the specified target. + * @chainable + * @param Item_Model $target Target item (must be an album + * @return ORM_MTPP + */ + function move_to($target) { + if (!$target->is_album()) { + throw new Exception("@todo INVALID_MOVE_TYPE $target->type"); + } + + if ($this->id == 1) { + throw new Exception("@todo INVALID_SOURCE root album"); + } + + $original_path = $this->file_path(); + $original_resize_path = $this->resize_path(); + $original_thumb_path = $this->thumb_path(); + + parent::move_to($target, true); + $this->relative_path_cache = null; + + rename($original_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())); + Database::instance() + ->update("items", + array("relative_path_cache" => null), + array("left >" => $this->left, "right <" => $this->right)); + } else { + @rename($original_resize_path, $this->resize_path()); + @rename($original_thumb_path, $this->thumb_path()); + } + + return $this; + } + + /** + * Rename the underlying file for this item to a new name. Move all the files. This requires a + * save. + * + * @chainable + */ + public function rename($new_name) { + if ($new_name == $this->name) { + return; + } + + if (strpos($new_name, "/")) { + throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH"); + } + + $old_relative_path = $this->relative_path(); + $new_relative_path = dirname($old_relative_path) . "/" . $new_name; + @rename(VARPATH . "albums/$old_relative_path", VARPATH . "albums/$new_relative_path"); + @rename(VARPATH . "resizes/$old_relative_path", VARPATH . "resizes/$new_relative_path"); + @rename(VARPATH . "thumbs/$old_relative_path", VARPATH . "thumbs/$new_relative_path"); + $this->name = $new_name; + + if ($this->is_album()) { + Database::instance() + ->update("items", + array("relative_path_cache" => null), + array("left >" => $this->left, "right <" => $this->right)); + } + + return $this; + } + + /** + * album: url::site("albums/2") + * photo: url::site("photos/3") + * + * @param string $query the query string (eg "show=3") + */ + public function url($query=array(), $full_uri=false) { + $url = ($full_uri ? url::abs_site("{$this->type}s/$this->id") + : url::site("{$this->type}s/$this->id")); + 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/" . $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) { + return $full_uri ? + url::abs_file("var/albums/" . $this->relative_path()) : + url::file("var/albums/" . $this->relative_path()); + } + + /** + * album: /var/resizes/album1/.thumb.jpg + * photo: /var/albums/album1/photo.thumb.jpg + */ + public function thumb_path() { + $base = VARPATH . "thumbs/" . $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 = "?m=" . $this->updated; + $base = ($full_uri ? + url::abs_file("var/thumbs/" . $this->relative_path()) : + url::file("var/thumbs/" . $this->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/" . $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) { + return ($full_uri ? + url::abs_file("var/resizes/" . $this->relative_path()) : + url::file("var/resizes/" . $this->relative_path())) . + ($this->is_album() ? "/.album.jpg" : ""); + } + + /** + * Return the relative path to this item's file. + * @return string + */ + public function relative_path() { + if (!isset($this->relative_path_cache)) { + $paths = array(); + foreach (Database::instance() + ->select("name") + ->from("items") + ->where("left <=", $this->left) + ->where("right >=", $this->right) + ->where("id <>", 1) + ->orderby("left", "ASC") + ->get() as $row) { + $paths[] = $row->name; + } + $this->relative_path_cache = implode($paths, "/"); + $this->save(); + } + return $this->relative_path_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 model_cache::get("user", $this->owner_id); + } catch (Exception $e) { + return null; + } + } else { + return parent::__get($column); + } + } + + /** + * @see ORM::__set() + */ + public function __set($column, $value) { + if ($column == "name") { + // Clear the relative path as it is no longer valid. + $this->relative_path_cache = null; + } + parent::__set($column, $value); + } + + /** + * @see ORM::save() + */ + public function save() { + if (!empty($this->changed) && $this->changed != array("view_count" => "view_count")) { + $this->updated = time(); + if (!$this->loaded) { + $this->created = $this->updated; + $r = ORM::factory("item")->select("MAX(weight) as max_weight")->find(); + $this->weight = $r->max_weight + 1; + } + } + return parent::save(); + } + + /** + * 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. + */ + public function get_position($child_id) { + $result = Database::instance()->query(" + SELECT COUNT(*) AS position FROM {items} + WHERE parent_id = {$this->parent_id} + AND {$this->sort_column} <= (SELECT {$this->sort_column} + FROM {items} WHERE id = $child_id) + ORDER BY {$this->sort_column} {$this->sort_order}"); + + return $result->current()->position; + } + + /** + * Return an <img> 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_tag($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 = ($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 "<img" . html::attributes($attrs) . "/>"; + } + + /** + * Calculate the largest width/height that fits inside the given maximum, while preserving the + * aspect ratio. + * @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 ($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 <img> tag for the resize. + * @param array $extra_attrs Extra attributes to add to the img tag + * @return string + */ + public function resize_tag($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 "<img" . html::attributes($attrs) . "/>"; + } + + /** + * Return a flowplayer <script> tag for movies + * @param array $extra_attrs + * @return string + */ + public function movie_tag($extra_attrs) { + $attrs = array_merge($extra_attrs, + array("id" => "player", + "style" => "display:block;width:400px;height:300px") + ); + return html::anchor($this->file_url(true), "", $attrs) . + "<script>flowplayer('player', '" . + url::abs_file("lib/flowplayer-3.0.5.swf") . + "'); </script>"; + } + + /** + * Return all of the children of this node, ordered by the defined sort order. + * + * @chainable + * @param integer SQL limit + * @param integer SQL offset + * @return array ORM + */ + function children($limit=null, $offset=0) { + return parent::children($limit, $offset, array($this->sort_column => $this->sort_order)); + } + + /** + * Return all of the children of the specified type, ordered by the defined sort order. + * @param integer SQL limit + * @param integer SQL offset + * @param string type to return + * @return object ORM_Iterator + */ + function descendants($limit=null, $offset=0, $type=null) { + return parent::descendants($limit, $offset, $type, + array($this->sort_column => $this->sort_order)); + } +} diff --git a/modules/gallery/models/log.php b/modules/gallery/models/log.php new file mode 100644 index 00000000..6734afb8 --- /dev/null +++ b/modules/gallery/models/log.php @@ -0,0 +1,22 @@ +<?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 Log_Model extends ORM { + protected $has_one = array("user"); +} diff --git a/modules/gallery/models/message.php b/modules/gallery/models/message.php new file mode 100644 index 00000000..851ececa --- /dev/null +++ b/modules/gallery/models/message.php @@ -0,0 +1,21 @@ +<?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 Message_Model extends ORM { +} diff --git a/modules/gallery/models/module.php b/modules/gallery/models/module.php new file mode 100644 index 00000000..5a9ea5bd --- /dev/null +++ b/modules/gallery/models/module.php @@ -0,0 +1,21 @@ +<?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 Module_Model extends ORM { +} diff --git a/modules/gallery/models/outgoing_translation.php b/modules/gallery/models/outgoing_translation.php new file mode 100644 index 00000000..bf617f42 --- /dev/null +++ b/modules/gallery/models/outgoing_translation.php @@ -0,0 +1,21 @@ +<?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 Outgoing_Translation_Model extends ORM { +} diff --git a/modules/gallery/models/permission.php b/modules/gallery/models/permission.php new file mode 100644 index 00000000..e77fa5bd --- /dev/null +++ b/modules/gallery/models/permission.php @@ -0,0 +1,21 @@ +<?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 Permission_Model extends ORM { +} diff --git a/modules/gallery/models/task.php b/modules/gallery/models/task.php new file mode 100644 index 00000000..9e3ae5c6 --- /dev/null +++ b/modules/gallery/models/task.php @@ -0,0 +1,46 @@ +<?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 Task_Model extends ORM { + public function get($key, $default=null) { + $context = unserialize($this->context); + if (array_key_exists($key, $context)) { + return $context[$key]; + } else { + return $default; + } + } + + public function set($key, $value) { + $context = unserialize($this->context); + $context[$key] = $value; + $this->context = serialize($context); + } + + public function save() { + if (!empty($this->changed)) { + $this->updated = time(); + } + return parent::save(); + } + + public function owner() { + return user::lookup($this->owner_id); + } +}
\ No newline at end of file diff --git a/modules/gallery/models/theme.php b/modules/gallery/models/theme.php new file mode 100644 index 00000000..f479fd5a --- /dev/null +++ b/modules/gallery/models/theme.php @@ -0,0 +1,21 @@ +<?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 Theme_Model extends ORM { +}
\ No newline at end of file diff --git a/modules/gallery/models/var.php b/modules/gallery/models/var.php new file mode 100644 index 00000000..434370e9 --- /dev/null +++ b/modules/gallery/models/var.php @@ -0,0 +1,21 @@ +<?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 Var_Model extends ORM { +} diff --git a/modules/gallery/module.info b/modules/gallery/module.info new file mode 100644 index 00000000..ff7da82d --- /dev/null +++ b/modules/gallery/module.info @@ -0,0 +1,3 @@ +name = Gallery 3 +description = Gallery core application +version = 1 diff --git a/modules/gallery/tests/Access_Helper_Test.php b/modules/gallery/tests/Access_Helper_Test.php new file mode 100644 index 00000000..7012a487 --- /dev/null +++ b/modules/gallery/tests/Access_Helper_Test.php @@ -0,0 +1,323 @@ +<?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 Access_Helper_Test extends Unit_Test_Case { + private $_group; + + public function teardown() { + try { + $group = ORM::factory("group")->where("name", "access_test")->find(); + if ($group->loaded) { + $group->delete(); + } + } catch (Exception $e) { } + + try { + access::delete_permission("access_test"); + } catch (Exception $e) { } + + try { + $user = ORM::factory("user")->where("name", "access_test")->find(); + if ($user->loaded) { + $user->delete(); + } + } catch (Exception $e) { } + } + + public function setup() { + user::set_active(user::guest()); + } + + public function groups_and_permissions_are_bound_to_columns_test() { + access::register_permission("access_test", "Access Test"); + $group = group::create("access_test"); + + // We have a new column for this perm / group combo + $fields = Database::instance()->list_fields("access_caches"); + $this->assert_true(array_key_exists("access_test_{$group->id}", $fields)); + + access::delete_permission("access_test"); + $group->delete(); + + // Now the column has gone away + $fields = Database::instance()->list_fields("access_caches"); + $this->assert_false(array_key_exists("access_test_{$group->id}", $fields)); + } + + public function adding_and_removing_items_adds_ands_removes_rows_test() { + $root = ORM::factory("item", 1); + $item = album::create($root, rand(), "test album"); + + // New rows exist + $this->assert_true(ORM::factory("access_cache")->where("item_id", $item->id)->find()->loaded); + $this->assert_true(ORM::factory("access_intent")->where("item_id", $item->id)->find()->loaded); + + // Delete the item + $item->delete(); + + // Rows are gone + $this->assert_false(ORM::factory("access_cache")->where("item_id", $item->id)->find()->loaded); + $this->assert_false(ORM::factory("access_intent")->where("item_id", $item->id)->find()->loaded); + } + + public function new_photos_inherit_parent_permissions_test() { + $root = ORM::factory("item", 1); + + $album = album::create($root, rand(), "test album"); + access::allow(group::everybody(), "view", $album); + + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->add_to_parent($album); + access::add_item($photo); + + $this->assert_true($photo->__get("view_" . group::everybody()->id)); + } + + public function can_allow_deny_and_reset_intent_test() { + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), "test album"); + $intent = ORM::factory("access_intent")->where("item_id", $album)->find(); + + // Allow + access::allow(group::everybody(), "view", $album); + $this->assert_same(access::ALLOW, $intent->reload()->view_1); + + // Deny + access::deny(group::everybody(), "view", $album); + $this->assert_same( + access::DENY, + ORM::factory("access_intent")->where("item_id", $album)->find()->view_1); + + // Allow again. If the initial value was allow, then the first Allow clause above may not + // have actually changed any values. + access::allow(group::everybody(), "view", $album); + $this->assert_same( + access::ALLOW, + ORM::factory("access_intent")->where("item_id", $album)->find()->view_1); + + access::reset(group::everybody(), "view", $album); + $this->assert_same( + null, + ORM::factory("access_intent")->where("item_id", $album)->find()->view_1); + } + + public function cant_reset_root_item_test() { + try { + access::reset(group::everybody(), "view", ORM::factory("item", 1)); + } catch (Exception $e) { + return; + } + $this->assert_true(false, "Should not be able to reset root intent"); + } + + public function can_view_item_test() { + $root = ORM::factory("item", 1); + access::allow(group::everybody(), "view", $root); + $this->assert_true(access::group_can(group::everybody(), "view", $root)); + } + + public function can_always_fails_on_unloaded_items_test() { + $root = ORM::factory("item", 1); + access::allow(group::everybody(), "view", $root); + $this->assert_true(access::group_can(group::everybody(), "view", $root)); + + $bogus = ORM::factory("item", -1); + $this->assert_false(access::group_can(group::everybody(), "view", $bogus)); + } + + public function cant_view_child_of_hidden_parent_test() { + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), "test album"); + + $root->reload(); + access::deny(group::everybody(), "view", $root); + access::reset(group::everybody(), "view", $album); + + $album->reload(); + $this->assert_false(access::group_can(group::everybody(), "view", $album)); + } + + public function view_permissions_propagate_down_test() { + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), "test album"); + + access::allow(group::everybody(), "view", $root); + access::reset(group::everybody(), "view", $album); + $album->reload(); + $this->assert_true(access::group_can(group::everybody(), "view", $album)); + } + + public function can_toggle_view_permissions_propagate_down_test() { + $root = ORM::factory("item", 1); + $album1 = album::create($root, rand(), "test album"); + $album2 = album::create($album1, rand(), "test album"); + $album3 = album::create($album2, rand(), "test album"); + $album4 = album::create($album3, rand(), "test album"); + + $album1->reload(); + $album2->reload(); + $album3->reload(); + $album4->reload(); + + access::allow(group::everybody(), "view", $root); + access::deny(group::everybody(), "view", $album1); + access::reset(group::everybody(), "view", $album2); + access::reset(group::everybody(), "view", $album3); + access::reset(group::everybody(), "view", $album4); + + $album4->reload(); + $this->assert_false(access::group_can(group::everybody(), "view", $album4)); + + access::allow(group::everybody(), "view", $album1); + $album4->reload(); + $this->assert_true(access::group_can(group::everybody(), "view", $album4)); + } + + public function revoked_view_permissions_cant_be_allowed_lower_down_test() { + $root = ORM::factory("item", 1); + $album1 = album::create($root, rand(), "test album"); + $album2 = album::create($album1, rand(), "test album"); + + $root->reload(); + access::deny(group::everybody(), "view", $root); + access::allow(group::everybody(), "view", $album2); + + $album1->reload(); + $this->assert_false(access::group_can(group::everybody(), "view", $album1)); + + $album2->reload(); + $this->assert_false(access::group_can(group::everybody(), "view", $album2)); + } + + public function can_edit_item_test() { + $root = ORM::factory("item", 1); + access::allow(group::everybody(), "edit", $root); + $this->assert_true(access::group_can(group::everybody(), "edit", $root)); + } + + public function non_view_permissions_propagate_down_test() { + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), "test album"); + + access::allow(group::everybody(), "edit", $root); + access::reset(group::everybody(), "edit", $album); + $this->assert_true(access::group_can(group::everybody(), "edit", $album)); + } + + public function non_view_permissions_can_be_revoked_lower_down_test() { + $root = ORM::factory("item", 1); + $outer = album::create($root, rand(), "test album"); + $outer_photo = ORM::factory("item"); + $outer_photo->type = "photo"; + $outer_photo->add_to_parent($outer); + access::add_item($outer_photo); + + $inner = album::create($outer, rand(), "test album"); + $inner_photo = ORM::factory("item"); + $inner_photo->type = "photo"; + $inner_photo->add_to_parent($inner); + access::add_item($inner_photo); + + $outer->reload(); + $inner->reload(); + + access::allow(group::everybody(), "edit", $root); + access::deny(group::everybody(), "edit", $outer); + access::allow(group::everybody(), "edit", $inner); + + // Outer album is not editable, inner one is. + $this->assert_false(access::group_can(group::everybody(), "edit", $outer_photo)); + $this->assert_true(access::group_can(group::everybody(), "edit", $inner_photo)); + } + + public function i_can_edit_test() { + // Create a new user that belongs to no groups + $user = user::create("access_test", "Access Test", ""); + foreach ($user->groups as $group) { + $user->remove($group); + } + $user->save(); + user::set_active($user); + + // This user can't edit anything + $root = ORM::factory("item", 1); + $this->assert_false(access::can("edit", $root)); + + // Now add them to a group that has edit permission + $group = group::create("access_test"); + $group->add($user); + $group->save(); + access::allow($group, "edit", $root); + + $user = ORM::factory("user", $user->id); // reload() does not flush related columns + user::set_active($user); + + // And verify that the user can edit. + $this->assert_true(access::can("edit", $root)); + } + + public function everybody_view_permission_maintains_htaccess_files_test() { + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), "test album"); + + $this->assert_false(file_exists($album->file_path() . "/.htaccess")); + + access::deny(group::everybody(), "view", $album); + $this->assert_true(file_exists($album->file_path() . "/.htaccess")); + + access::allow(group::everybody(), "view", $album); + $this->assert_false(file_exists($album->file_path() . "/.htaccess")); + + access::deny(group::everybody(), "view", $album); + $this->assert_true(file_exists($album->file_path() . "/.htaccess")); + + access::reset(group::everybody(), "view", $album); + $this->assert_false(file_exists($album->file_path() . "/.htaccess")); + } + + public function everybody_view_full_permission_maintains_htaccess_files_test() { + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), "test album"); + + $this->assert_false(file_exists($album->file_path() . "/.htaccess")); + $this->assert_false(file_exists($album->resize_path() . "/.htaccess")); + $this->assert_false(file_exists($album->thumb_path() . "/.htaccess")); + + access::deny(group::everybody(), "view_full", $album); + $this->assert_true(file_exists($album->file_path() . "/.htaccess")); + $this->assert_false(file_exists($album->resize_path() . "/.htaccess")); + $this->assert_false(file_exists($album->thumb_path() . "/.htaccess")); + + access::allow(group::everybody(), "view_full", $album); + $this->assert_false(file_exists($album->file_path() . "/.htaccess")); + $this->assert_false(file_exists($album->resize_path() . "/.htaccess")); + $this->assert_false(file_exists($album->thumb_path() . "/.htaccess")); + + access::deny(group::everybody(), "view_full", $album); + $this->assert_true(file_exists($album->file_path() . "/.htaccess")); + $this->assert_false(file_exists($album->resize_path() . "/.htaccess")); + $this->assert_false(file_exists($album->thumb_path() . "/.htaccess")); + + access::reset(group::everybody(), "view_full", $album); + $this->assert_false(file_exists($album->file_path() . "/.htaccess")); + $this->assert_false(file_exists($album->resize_path() . "/.htaccess")); + $this->assert_false(file_exists($album->thumb_path() . "/.htaccess")); + } +} diff --git a/modules/gallery/tests/Album_Helper_Test.php b/modules/gallery/tests/Album_Helper_Test.php new file mode 100644 index 00000000..80afa8d1 --- /dev/null +++ b/modules/gallery/tests/Album_Helper_Test.php @@ -0,0 +1,87 @@ +<?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 Album_Helper_Test extends Unit_Test_Case { + public function create_album_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $album = album::create($root, $rand, $rand, $rand); + + $this->assert_equal(VARPATH . "albums/$rand", $album->file_path()); + $this->assert_equal(VARPATH . "thumbs/$rand/.album.jpg", $album->thumb_path()); + $this->assert_true(is_dir(VARPATH . "thumbs/$rand"), "missing thumb dir"); + + // It's unclear that a resize makes sense for an album. But we have one. + $this->assert_equal(VARPATH . "resizes/$rand/.album.jpg", $album->resize_path()); + $this->assert_true(is_dir(VARPATH . "resizes/$rand"), "missing resizes dir"); + + $this->assert_equal(1, $album->parent_id); // MPTT tests will cover other hierarchy checks + $this->assert_equal($rand, $album->name); + $this->assert_equal($rand, $album->title); + $this->assert_equal($rand, $album->description); + } + + public function create_conflicting_album_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $album1 = album::create($root, $rand, $rand, $rand); + $album2 = album::create($root, $rand, $rand, $rand); + $this->assert_true($album1->name != $album2->name); + } + + public function thumb_url_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $album = album::create($root, $rand, $rand, $rand); + $this->assert_equal("http://./var/thumbs/$rand/.album.jpg", $album->thumb_url()); + } + + public function resize_url_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $album = album::create($root, $rand, $rand, $rand); + $this->assert_equal("http://./var/resizes/$rand/.album.jpg", $album->resize_url()); + } + + public function create_album_shouldnt_allow_names_with_slash_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + try { + $album = album::create($root, $rand . "/", $rand, $rand); + } catch (Exception $e) { + // pass + return; + } + + $this->assert_true(false, "Shouldn't create an album with / in the name"); + } + + public function create_album_silently_trims_trailing_periods_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + try { + $album = album::create($root, $rand . "..", $rand, $rand); + } catch (Exception $e) { + $this->assert_equal("@todo NAME_CANNOT_END_IN_PERIOD", $e->getMessage()); + return; + } + + $this->assert_true(false, "Shouldn't create an album with trailing . in the name"); + } +} diff --git a/modules/gallery/tests/Albums_Controller_Test.php b/modules/gallery/tests/Albums_Controller_Test.php new file mode 100644 index 00000000..ef1fac77 --- /dev/null +++ b/modules/gallery/tests/Albums_Controller_Test.php @@ -0,0 +1,76 @@ +<?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 Albums_Controller_Test extends Unit_Test_Case { + public function setup() { + $this->_post = $_POST; + } + + public function teardown() { + $_POST = $this->_post; + } + + public function change_album_test() { + $controller = new Albums_Controller(); + $root = ORM::factory("item", 1); + $album = album::create($root, "test", "test", "test"); + $orig_name = $album->name; + + $_POST["dirname"] = "test"; + $_POST["name"] = "new name"; + $_POST["title"] = "new title"; + $_POST["description"] = "new description"; + $_POST["column"] = "weight"; + $_POST["direction"] = "ASC"; + $_POST["csrf"] = access::csrf_token(); + $_POST["_method"] = "put"; + access::allow(group::everybody(), "edit", $root); + + ob_start(); + $controller->_update($album); + $results = ob_get_contents(); + ob_end_clean(); + + $this->assert_equal( + json_encode(array("result" => "success", "location" => "http://./index.php/test")), + $results); + $this->assert_equal("new title", $album->title); + $this->assert_equal("new description", $album->description); + + // We don't change the name, yet. + $this->assert_equal($orig_name, $album->name); + } + + public function change_album_no_csrf_fails_test() { + $controller = new Albums_Controller(); + $root = ORM::factory("item", 1); + $album = album::create($root, "test", "test", "test"); + $_POST["name"] = "new name"; + $_POST["title"] = "new title"; + $_POST["description"] = "new description"; + access::allow(group::everybody(), "edit", $root); + + try { + $controller->_update($album); + $this->assert_true(false, "This should fail"); + } catch (Exception $e) { + // pass + } + } +} diff --git a/modules/gallery/tests/Core_Installer_Test.php b/modules/gallery/tests/Core_Installer_Test.php new file mode 100644 index 00000000..f7036286 --- /dev/null +++ b/modules/gallery/tests/Core_Installer_Test.php @@ -0,0 +1,50 @@ +<?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. + */ + +/** + * This test case operates under the assumption that core_installer::install() is called by the + * test controller before it starts. + */ +class Core_Installer_Test extends Unit_Test_Case { + public function install_creates_dirs_test() { + $this->assert_true(file_exists(VARPATH . "albums")); + $this->assert_true(file_exists(VARPATH . "resizes")); + } + + public function install_registers_core_module_test() { + $core = ORM::factory("module")->where("name", "core")->find(); + $this->assert_equal("core", $core->name); + + // This is probably too volatile to keep for long + $this->assert_equal(1, $core->version); + } + + public function install_creates_root_item_test() { + $max_right = ORM::factory("item") + ->select("MAX(`right`) AS `right`") + ->find()->right; + $root = ORM::factory('item')->find(1); + $this->assert_equal("Gallery", $root->title); + $this->assert_equal(1, $root->left); + $this->assert_equal($max_right, $root->right); + $this->assert_equal(null, $root->parent_id); + $this->assert_equal(1, $root->level); + } +} diff --git a/modules/gallery/tests/Database_Test.php b/modules/gallery/tests/Database_Test.php new file mode 100644 index 00000000..bd3d2f53 --- /dev/null +++ b/modules/gallery/tests/Database_Test.php @@ -0,0 +1,134 @@ +<?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 Database_Test extends Unit_Test_Case { + function simple_where_test() { + $sql = Database::instance() + ->where("a", 1) + ->where("b", 2) + ->compile(); + $sql = str_replace("\n", " ", $sql); + $this->assert_same("SELECT * WHERE `a` = 1 AND `b` = 2", $sql); + } + + function compound_where_test() { + $sql = Database::instance() + ->where("outer1", 1) + ->open_paren() + ->where("inner1", 1) + ->orwhere("inner2", 2) + ->close_paren() + ->where("outer2", 2) + ->compile(); + $sql = str_replace("\n", " ", $sql); + $this->assert_same( + "SELECT * WHERE `outer1` = 1 AND (`inner1` = 1 OR `inner2` = 2) AND `outer2` = 2", + $sql); + } + + function group_first_test() { + $sql = Database::instance() + ->open_paren() + ->where("inner1", 1) + ->orwhere("inner2", 2) + ->close_paren() + ->where("outer1", 1) + ->where("outer2", 2) + ->compile(); + $sql = str_replace("\n", " ", $sql); + $this->assert_same( + "SELECT * WHERE (`inner1` = 1 OR `inner2` = 2) AND `outer1` = 1 AND `outer2` = 2", + $sql); + } + + function where_array_test() { + $sql = Database::instance() + ->where("outer1", 1) + ->open_paren() + ->where("inner1", 1) + ->orwhere(array("inner2" => 2, "inner3" => 3)) + ->close_paren() + ->compile(); + $sql = str_replace("\n", " ", $sql); + $this->assert_same( + "SELECT * WHERE `outer1` = 1 AND (`inner1` = 1 OR `inner2` = 2 OR `inner3` = 3)", + $sql); + } + + function notlike_test() { + $sql = Database::instance() + ->where("outer1", 1) + ->open_paren() + ->ornotlike("inner1", 1) + ->close_paren() + ->compile(); + $sql = str_replace("\n", " ", $sql); + $this->assert_same( + "SELECT * WHERE `outer1` = 1 OR ( `inner1` NOT LIKE '%1%')", + $sql); + } + + function prefix_replacement_test() { + $db = Database_For_Test::instance(); + $converted = $db->add_table_prefixes("CREATE TABLE IF NOT EXISTS {test_tables} ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(32) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8"); + $expected = "CREATE TABLE IF NOT EXISTS g3test_test_tables ( + `id` int(9) NOT NULL auto_increment, + `name` varchar(32) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY(`name`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8"; + $this->assert_same($expected, $converted); + + $sql = "UPDATE {test_tables} SET `name` = '{test string}' " . + "WHERE `item_id` IN " . + " (SELECT `id` FROM {items} " . + " WHERE `left` >= 1 " . + " AND `right` <= 6)"; + $sql = $db->add_table_prefixes($sql); + + $expected = "UPDATE g3test_test_tables SET `name` = '{test string}' " . + "WHERE `item_id` IN " . + " (SELECT `id` FROM g3test_items " . + " WHERE `left` >= 1 " . + " AND `right` <= 6)"; + + $this->assert_same($expected, $sql); + } + + public function setup() { + } + + public function teardown() { + } + +} + +class Database_For_Test extends Database { + static function instance() { + $db = new Database_For_Test(); + $db->_table_names["{items}"] = "g3test_items"; + $db->config["table_prefix"] = "g3test_"; + return $db; + } +} diff --git a/modules/gallery/tests/Dir_Helper_Test.php b/modules/gallery/tests/Dir_Helper_Test.php new file mode 100644 index 00000000..46bb871c --- /dev/null +++ b/modules/gallery/tests/Dir_Helper_Test.php @@ -0,0 +1,32 @@ +<?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 Dir_Helper_Test extends Unit_Test_Case { + public function remove_album_test() { + $dirname = (VARPATH . "albums/testdir"); + mkdir($dirname, 0777, true); + + $filename = tempnam($dirname, "file"); + touch($filename); + + dir::unlink($dirname); + $this->assert_boolean(!file_exists($filename), "File not deleted"); + $this->assert_boolean(!file_exists($dirname), "Directory not deleted"); + } +} diff --git a/modules/gallery/tests/DrawForm_Test.php b/modules/gallery/tests/DrawForm_Test.php new file mode 100644 index 00000000..2c5aaba4 --- /dev/null +++ b/modules/gallery/tests/DrawForm_Test.php @@ -0,0 +1,84 @@ +<?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 DrawForm_Test extends Unit_Test_Case { + function no_group_test() { + $form = new Forge("test/controller", "", "post", array("id" => "gTestGroupForm")); + $form->input("title")->label(t("Title")); + $form->textarea("description")->label(t("Text Area")); + $form->submit("")->value(t("Submit")); + $rendered = $form->__toString(); + + $expected = "<form action=\"http://./index.php/test/controller\" method=\"post\" " . + "id=\"gTestGroupForm\">\n" . + "<input type=\"hidden\" name=\"csrf\" value=\"" . access::csrf_token() . "\" />\n" . + " <ul>\n" . + " <li>\n" . + " <label for=\"title\" >Title</label>\n" . + " <input type=\"text\" id=\"title\" name=\"title\" value=\"\" " . + "class=\"textbox\" />\n" . + " </li>\n" . + " <li>\n" . + " <label for=\"description\" >Text Area</label>\n" . + " <textarea id=\"description\" name=\"description\" " . + "class=\"textarea\" ></textarea>\n" . + " </li>\n" . + " <li>\n" . + " <input type=\"submit\" value=\"Submit\" class=\"submit\" />\n" . + " </li>\n" . + " </ul>\n" . + "</form>\n"; + $this->assert_same($expected, $rendered); + } + + function group_test() { + $form = new Forge("test/controller", "", "post", array("id" => "gTestGroupForm")); + $group = $form->group("test_group")->label(t("Test Group")); + $group->input("title")->label(t("Title")); + $group->textarea("description")->label(t("Text Area")); + $group->submit("")->value(t("Submit")); + $rendered = $form->__toString(); + + $expected = "<form action=\"http://./index.php/test/controller\" method=\"post\" " . + "id=\"gTestGroupForm\">\n" . + "<input type=\"hidden\" name=\"csrf\" value=\"" . access::csrf_token() . "\" />\n" . + " <fieldset>\n" . + " <legend>Test Group</legend>\n" . + " <ul>\n" . + " <li>\n" . + " <label for=\"title\" >Title</label>\n" . + " <input type=\"text\" id=\"title\" name=\"title\" value=\"\" " . + "class=\"textbox\" />\n" . + " </li>\n" . + " <li>\n" . + " <label for=\"description\" >Text Area</label>\n" . + " <textarea id=\"description\" name=\"description\" " . + "class=\"textarea\" ></textarea>\n" . + " </li>\n" . + " <li>\n" . + " <input type=\"submit\" value=\"Submit\" class=\"submit\" />\n" . + " </li>\n" . + " </ul>\n" . + " </fieldset>\n" . + "</form>\n"; + $this->assert_same($expected, $rendered); + } + +} + diff --git a/modules/gallery/tests/File_Structure_Test.php b/modules/gallery/tests/File_Structure_Test.php new file mode 100644 index 00000000..1caa82ba --- /dev/null +++ b/modules/gallery/tests/File_Structure_Test.php @@ -0,0 +1,235 @@ +<?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 File_Structure_Test extends Unit_Test_Case { + public function no_trailing_closing_php_tag_test() { + $dir = new GalleryCodeFilterIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator(DOCROOT))); + foreach ($dir as $file) { + if (!preg_match("|\.html\.php$|", $file->getPathname())) { + $this->assert_false( + preg_match('/\?\>\s*$/', file_get_contents($file)), + "{$file->getPathname()} ends in ?>"); + } + } + } + + public function view_files_correct_suffix_test() { + $dir = new GalleryCodeFilterIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator(DOCROOT))); + foreach ($dir as $file) { + if (strpos($file, "views")) { + $this->assert_true( + preg_match("#/views/.*?(\.html|mrss|txt)\.php$#", $file->getPathname()), + "{$file->getPathname()} should end in .{html,mrss,txt}.php"); + } + } + } + + public function no_windows_line_endings_test() { + $dir = new GalleryCodeFilterIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator(DOCROOT))); + foreach ($dir as $file) { + if (preg_match("/\.(php|css|html|js)$/", $file)) { + foreach (file($file) as $line) { + $this->assert_true(substr($line, -2) != "\r\n", "$file has windows style line endings"); + } + } + } + } + + private function _check_view_preamble($path, &$errors) { + // The preamble for views is a single line that prevents direct script access + if (strpos($path, SYSPATH) === 0) { + // Kohana preamble + $expected = "<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>\n"; + } else { + // Gallery preamble + // @todo use the same preamble for both! + $expected = "<?php defined(\"SYSPATH\") or die(\"No direct script access.\") ?>\n"; + } + + $fp = fopen($path, "r"); + $actual = fgets($fp); + fclose($fp); + + if ($expected != $actual) { + $errors[] = "$path:1\n expected:\n\t$expected\n actual:\n\t$actual"; + } + } + + private function _check_php_preamble($path, &$errors) { + if (strpos($path, SYSPATH) === 0 || + strpos($path, MODPATH . "unit_test") === 0) { + // Kohana: we only care about the first line + $fp = fopen($path, "r"); + $actual = array(fgets($fp)); + fclose($fp); + $expected = array("<?php defined('SYSPATH') OR die('No direct access allowed.');\n"); + } else if (strpos($path, MODPATH . "forge") === 0 || + strpos($path, MODPATH . "exif/lib") === 0 || + $path == MODPATH . "user/lib/PasswordHash.php" || + $path == DOCROOT . "var/database.php") { + // 3rd party module security-only preambles, similar to Gallery's + $expected = array("<?php defined(\"SYSPATH\") or die(\"No direct script access.\");\n"); + $fp = fopen($path, "r"); + $actual = array(fgets($fp)); + fclose($fp); + } else { + // Gallery: we care about the entire copyright + $actual = $this->_get_preamble($path); + $expected = array( + "<?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.", + " */", + ); + } + if ($expected != $actual) { + $errors[] = "$path:1\n expected\n\t" . join("\n\t", $expected) . + "\n actual:\n\t" . join("\n\t", $actual); + } + } + + public function code_files_start_with_preamble_test() { + $dir = new PhpCodeFilterIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator(DOCROOT))); + + $errors = array(); + foreach ($dir as $file) { + $path = $file->getPathname(); + switch ($path) { + case DOCROOT . "installer/database_config.php": + case DOCROOT . "installer/init_var.php": + // Special case views + $this->_check_view_preamble($path, $errors); + break; + + case DOCROOT . "index.php": + case DOCROOT . "installer/index.php": + // Front controllers + break; + + case DOCROOT . "index.local.php": + // Special case optional file, not part of the codebase + break; + + default: + if (strpos($path, DOCROOT . "var/logs") === 0) { + continue; + } else if (preg_match("/views/", $path)) { + $this->_check_view_preamble($path, $errors); + } else { + $this->_check_php_preamble($path, $errors); + } + } + } + + if ($errors) { + $this->assert_false(true, "Preamble errors:\n" . join("\n", $errors)); + } + } + + public function no_tabs_in_our_code_test() { + $dir = new PhpCodeFilterIterator( + new GalleryCodeFilterIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(DOCROOT)))); + foreach ($dir as $file) { + $this->assert_false( + preg_match('/\t/', file_get_contents($file)), + "{$file->getPathname()} has tabs in it"); + } + } + + private function _get_preamble($file) { + $lines = file($file); + $copy = array(); + for ($i = 0; $i < count($lines); $i++) { + $copy[] = rtrim($lines[$i]); + if (!strncmp($lines[$i], ' */', 3)) { + return $copy; + } + } + return $copy; + } + + public function helpers_are_static_test() { + $dir = new PhpCodeFilterIterator( + new GalleryCodeFilterIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(DOCROOT)))); + foreach ($dir as $file) { + if (basename(dirname($file)) == "helpers") { + foreach (file($file) as $line) { + $this->assert_true( + !preg_match("/\sfunction\s.*\(/", $line) || + preg_match("/^\s*(private static function _|static function)/", $line), + "should be \"static function foo\" or \"private static function _foo\":\n" . + "$file\n$line\n"); + } + } + } + } +} + +class PhpCodeFilterIterator extends FilterIterator { + public function accept() { + $path_name = $this->getInnerIterator()->getPathName(); + return (substr($path_name, -4) == ".php" && + !(strpos($path_name, VARPATH) === 0)); + } +} + +class GalleryCodeFilterIterator extends FilterIterator { + public function accept() { + // Skip anything that we didn"t write + $path_name = $this->getInnerIterator()->getPathName(); + return !( + strpos($path_name, ".svn") || + strpos($path_name, "core/views/kohana_profiler.php") !== false || + strpos($path_name, DOCROOT . "test") !== false || + strpos($path_name, DOCROOT . "var") !== false || + strpos($path_name, MODPATH . "forge") !== false || + strpos($path_name, APPPATH . "views/kohana_error_page.php") !== false || + strpos($path_name, MODPATH . "gallery_unit_test/views/kohana_error_page.php") !== false || + strpos($path_name, MODPATH . "gallery_unit_test/views/kohana_unit_test_cli.php") !== false || + strpos($path_name, MODPATH . "unit_test") !== false || + strpos($path_name, MODPATH . "exif/lib") !== false || + strpos($path_name, MODPATH . "user/lib/PasswordHash") !== false || + strpos($path_name, DOCROOT . "lib/swfupload") !== false || + strpos($path_name, SYSPATH) !== false || + substr($path_name, -1, 1) == "~"); + } +} diff --git a/modules/gallery/tests/I18n_Test.php b/modules/gallery/tests/I18n_Test.php new file mode 100644 index 00000000..9010606a --- /dev/null +++ b/modules/gallery/tests/I18n_Test.php @@ -0,0 +1,108 @@ +<?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 I18n_Test extends Unit_Test_Case { + private $i18n; + + public function setup() { + $config = array( + 'root_locale' => 'en', + 'default_locale' => 'te_ST', + 'locale_dir' => VARPATH . 'locale/'); + $this->i18n = I18n::instance($config); + + ORM::factory("incoming_translation") + ->where("locale", "te_ST") + ->delete_all(); + + $messages_te_ST = array( + array('Hello world', 'Hallo Welt'), + array(array('one' => 'One item has been added', + 'other' => '%count elements have been added'), + array('one' => 'Ein Element wurde hinzugefuegt.', + 'other' => '%count Elemente wurden hinzugefuegt.')), + array('Hello %name, how are you today?', 'Hallo %name, wie geht es Dir heute?')); + + foreach ($messages_te_ST as $data) { + list ($message, $translation) = $data; + $entry = ORM::factory("incoming_translation"); + $entry->key = I18n::get_message_key($message); + $entry->message = serialize($message); + $entry->translation = serialize($translation); + $entry->locale = 'te_ST'; + $entry->revision = null; + $entry->save(); + } + } + + public function get_locale_test() { + $locale = $this->i18n->locale(); + $this->assert_equal("te_ST", $locale); + } + + public function set_locale_test() { + $this->i18n->locale("de_DE"); + $locale = $this->i18n->locale(); + $this->assert_equal("de_DE", $locale); + } + + public function translate_simple_test() { + $result = $this->i18n->translate('Hello world'); + $this->assert_equal('Hallo Welt', $result); + } + + public function translate_simple_root_fallback_test() { + $result = $this->i18n->translate('Hello world zzz'); + $this->assert_equal('Hello world zzz', $result); + } + + public function translate_plural_other_test() { + $result = $this->i18n->translate(array('one' => 'One item has been added', + 'other' => '%count elements have been added'), + array('count' => 5)); + $this->assert_equal('5 Elemente wurden hinzugefuegt.', $result); + } + + public function translate_plural_one_test() { + $result = $this->i18n->translate(array('one' => 'One item has been added', + 'other' => '%count elements have been added'), + array('count' => 1)); + $this->assert_equal('Ein Element wurde hinzugefuegt.', $result); + } + + public function translate_interpolate_test() { + $result = $this->i18n->translate('Hello %name, how are you today?', array('name' => 'John')); + $this->assert_equal('Hallo John, wie geht es Dir heute?', $result); + } + + public function translate_interpolate_missing_value_test() { + $result = $this->i18n->translate('Hello %name, how are you today?', array('foo' => 'bar')); + $this->assert_equal('Hallo %name, wie geht es Dir heute?', $result); + } + + public function translate_plural_zero_test() { + // te_ST has the same plural rules as en and de. + // For count 0, plural form "other" should be used. + $result = $this->i18n->translate(array('one' => 'One item has been added', + 'other' => '%count elements have been added'), + array('count' => 0)); + $this->assert_equal('0 Elemente wurden hinzugefuegt.', $result); + } +}
\ No newline at end of file diff --git a/modules/gallery/tests/Item_Model_Test.php b/modules/gallery/tests/Item_Model_Test.php new file mode 100644 index 00000000..615b8997 --- /dev/null +++ b/modules/gallery/tests/Item_Model_Test.php @@ -0,0 +1,143 @@ +<?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 Item_Model_Test extends Unit_Test_Case { + public function saving_sets_created_and_updated_dates_test() { + $item = self::create_random_item(); + $this->assert_true(!empty($item->created)); + $this->assert_true(!empty($item->updated)); + } + + private function create_random_item() { + $item = ORM::factory("item"); + /* Set all required fields (values are irrelevant) */ + $item->name = rand(); + $item->type = "photo"; + return $item->add_to_parent(ORM::factory("item", 1)); + } + + public function updating_doesnt_change_created_date_test() { + $item = self::create_random_item(); + + // Force the creation date to something well known + $db = Database::instance(); + $db->update("items", array("created" => 0, "updated" => 0), array("id" => $item->id)); + $item->reload(); + $item->title = "foo"; // force a change + $item->save(); + + $this->assert_true(empty($item->created)); + $this->assert_true(!empty($item->updated)); + } + + public function updating_view_count_only_doesnt_change_updated_date_test() { + $item = self::create_random_item(); + $item->reload(); + $this->assert_same(0, $item->view_count); + + // Force the updated date to something well known + $db = Database::instance(); + $db->update("items", array("updated" => 0), array("id" => $item->id)); + $item->reload(); + $item->view_count++; + $item->save(); + + $this->assert_same(1, $item->view_count); + $this->assert_true(empty($item->updated)); + } + + public function move_photo_test() { + // Create a test photo + $item = self::create_random_item(); + + file_put_contents($item->thumb_path(), "thumb"); + file_put_contents($item->resize_path(), "resize"); + file_put_contents($item->file_path(), "file"); + + $original_name = $item->name; + $new_name = rand(); + + // Now rename it + $item->rename($new_name)->save(); + + // Expected: the name changed, the name is now baked into all paths, and all files were moved. + $this->assert_equal($new_name, $item->name); + $this->assert_equal($new_name, basename($item->file_path())); + $this->assert_equal($new_name, basename($item->thumb_path())); + $this->assert_equal($new_name, basename($item->resize_path())); + $this->assert_equal("thumb", file_get_contents($item->thumb_path())); + $this->assert_equal("resize", file_get_contents($item->resize_path())); + $this->assert_equal("file", file_get_contents($item->file_path())); + } + + public function move_album_test() { + // Create an album with a photo in it + $root = ORM::factory("item", 1); + $album = album::create($root, rand(), rand(), rand()); + $photo = ORM::factory("item"); + $photo->name = rand(); + $photo->type = "photo"; + $photo->add_to_parent($album); + + file_put_contents($photo->thumb_path(), "thumb"); + file_put_contents($photo->resize_path(), "resize"); + file_put_contents($photo->file_path(), "file"); + + $original_album_name = $album->name; + $original_photo_name = $photo->name; + $new_album_name = rand(); + + // Now rename the album + $album->rename($new_album_name)->save(); + $photo->reload(); + + // Expected: + // * the album name changed. + // * the album dirs are all moved + // * the photo's paths are all inside the albums paths + // * the photo files are all still intact and accessible + $this->assert_equal($new_album_name, $album->name); + $this->assert_equal($new_album_name, basename($album->file_path())); + $this->assert_equal($new_album_name, basename(dirname($album->thumb_path()))); + $this->assert_equal($new_album_name, basename(dirname($album->resize_path()))); + + $this->assert_same(0, strpos($photo->file_path(), $album->file_path())); + $this->assert_same(0, strpos($photo->thumb_path(), dirname($album->thumb_path()))); + $this->assert_same(0, strpos($photo->resize_path(), dirname($album->resize_path()))); + + $this->assert_equal("thumb", file_get_contents($photo->thumb_path())); + $this->assert_equal("resize", file_get_contents($photo->resize_path())); + $this->assert_equal("file", file_get_contents($photo->file_path())); + } + + public function item_rename_wont_accept_slash_test() { + // Create a test photo + $item = self::create_random_item(); + + $new_name = rand() . "/"; + + try { + $item->rename($new_name)->save(); + } catch (Exception $e) { + // pass + return; + } + $this->assert_false(true, "Item_Model::rename should not accept / characters"); + } +} diff --git a/modules/gallery/tests/Menu_Test.php b/modules/gallery/tests/Menu_Test.php new file mode 100644 index 00000000..c91aee0b --- /dev/null +++ b/modules/gallery/tests/Menu_Test.php @@ -0,0 +1,32 @@ +<?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 Menu_Test extends Unit_Test_Case { + public function find_menu_item_test() { + $menu = new Menu(true); + $menu + ->append(Menu::factory("link")->id("element_1")) + ->append(Menu::factory("dialog")->id("element_2")) + ->append(Menu::factory("submenu")->id("element_3") + ->append(Menu::factory("link")->id("element_3_1"))); + + $this->assert_equal("element_2", $menu->get("element_2")->id); + $this->assert_equal("element_3_1", $menu->get("element_3")->get("element_3_1")->id); + } +}
\ No newline at end of file diff --git a/modules/gallery/tests/Movie_Helper_Test.php b/modules/gallery/tests/Movie_Helper_Test.php new file mode 100644 index 00000000..b92ef3f8 --- /dev/null +++ b/modules/gallery/tests/Movie_Helper_Test.php @@ -0,0 +1,46 @@ +<?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 Movie_Helper_Test extends Unit_Test_Case { + public function create_movie_shouldnt_allow_names_with_slash_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + try { + $movie = movie::create($root, DOCROOT . "core/tests/test.jpg", "$rand/.jpg", $rand, $rand); + } catch (Exception $e) { + // pass + return; + } + + $this->assert_true(false, "Shouldn't create a movie with / in the name"); + } + + public function create_movie_shouldnt_allow_names_with_trailing_periods_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + try { + $movie = movie::create($root, DOCROOT . "core/tests/test.jpg", "$rand.jpg.", $rand, $rand); + } catch (Exception $e) { + $this->assert_equal("@todo NAME_CANNOT_END_IN_PERIOD", $e->getMessage()); + return; + } + + $this->assert_true(false, "Shouldn't create a movie with trailing . in the name"); + } +} diff --git a/modules/gallery/tests/ORM_MPTT_Test.php b/modules/gallery/tests/ORM_MPTT_Test.php new file mode 100644 index 00000000..200c8a74 --- /dev/null +++ b/modules/gallery/tests/ORM_MPTT_Test.php @@ -0,0 +1,221 @@ +<?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 ORM_MPTT_Test extends Unit_Test_Case { + + private function create_item_and_add_to_parent($parent) { + $album = album::create($parent, rand(), "test album"); + return $album; + } + + public function add_to_parent_test() { + $root = ORM::factory("item", 1); + $album = ORM::factory("item"); + $album->type = "album"; + $album->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + $album->sort_column = "weight"; + $album->sort_order = "ASC"; + $album->add_to_parent($root); + + $this->assert_equal($album->parent()->right - 2, $album->left); + $this->assert_equal($album->parent()->right - 1, $album->right); + $this->assert_equal($album->parent()->level + 1, $album->level); + $this->assert_equal($album->parent()->id, $album->parent_id); + } + + public function add_hierarchy_test() { + $root = ORM::factory("item", 1); + $album1 = self::create_item_and_add_to_parent($root); + $album1_1 = self::create_item_and_add_to_parent($album1); + $album1_2 = self::create_item_and_add_to_parent($album1); + $album1_1_1 = self::create_item_and_add_to_parent($album1_1); + $album1_1_2 = self::create_item_and_add_to_parent($album1_1); + + $album1->reload(); + $this->assert_equal(9, $album1->right - $album1->left); + + $album1_1->reload(); + $this->assert_equal(5, $album1_1->right - $album1_1->left); + } + + public function delete_hierarchy_test() { + $root = ORM::factory("item", 1); + $album1 = self::create_item_and_add_to_parent($root); + $album1_1 = self::create_item_and_add_to_parent($album1); + $album1_2 = self::create_item_and_add_to_parent($album1); + $album1_1_1 = self::create_item_and_add_to_parent($album1_1); + $album1_1_2 = self::create_item_and_add_to_parent($album1_1); + + $album1_1->delete(); + $album1->reload(); + + // Now album1 contains only album1_2 + $this->assert_equal(3, $album1->right - $album1->left); + } + + public function move_to_test() { + $root = ORM::factory("item", 1); + $album1 = album::create($root, "move_to_test_1", "move_to_test_1"); + $album1_1 = album::create($album1, "move_to_test_1_1", "move_to_test_1_1"); + $album1_2 = album::create($album1, "move_to_test_1_2", "move_to_test_1_2"); + $album1_1_1 = album::create($album1_1, "move_to_test_1_1_1", "move_to_test_1_1_1"); + $album1_1_2 = album::create($album1_1, "move_to_test_1_1_2", "move_to_test_1_1_2"); + + $album1_2->reload(); + $album1_1_1->reload(); + + $album1_1_1->move_to($album1_2); + + $album1_1->reload(); + $album1_2->reload(); + + $this->assert_equal(3, $album1_1->right - $album1_1->left); + $this->assert_equal(3, $album1_2->right - $album1_2->left); + + $this->assert_equal( + array($album1_1_2->id => "move_to_test_1_1_2"), + $album1_1->children()->select_list()); + + $this->assert_equal( + array($album1_1_1->id => "move_to_test_1_1_1"), + $album1_2->children()->select_list()); + } + + public function parent_test() { + $root = ORM::factory("item", 1); + $album = self::create_item_and_add_to_parent($root); + + $parent = ORM::factory("item", 1); + $this->assert_equal($parent->id, $album->parent()->id); + } + + public function parents_test() { + $root = ORM::factory("item", 1); + $outer = self::create_item_and_add_to_parent($root); + $inner = self::create_item_and_add_to_parent($outer); + + $parent_ids = array(); + foreach ($inner->parents() as $parent) { + $parent_ids[] = $parent->id; + } + $this->assert_equal(array(1, $outer->id), $parent_ids); + } + + public function children_test() { + $root = ORM::factory("item", 1); + $outer = self::create_item_and_add_to_parent($root); + $inner1 = self::create_item_and_add_to_parent($outer); + $inner2 = self::create_item_and_add_to_parent($outer); + + $child_ids = array(); + foreach ($outer->children() as $child) { + $child_ids[] = $child->id; + } + $this->assert_equal(array($inner1->id, $inner2->id), $child_ids); + } + + public function children_limit_test() { + $root = ORM::factory("item", 1); + $outer = self::create_item_and_add_to_parent($root); + $inner1 = self::create_item_and_add_to_parent($outer); + $inner2 = self::create_item_and_add_to_parent($outer); + + $this->assert_equal(array($inner2->id => $inner2->name), + $outer->children(1, 1)->select_list('id')); + } + + public function children_count_test() { + $root = ORM::factory("item", 1); + $outer = self::create_item_and_add_to_parent($root); + $inner1 = self::create_item_and_add_to_parent($outer); + $inner2 = self::create_item_and_add_to_parent($outer); + + $this->assert_equal(2, $outer->children_count()); + } + + public function descendant_test() { + $root = ORM::factory("item", 1); + + $parent = ORM::factory("item"); + $parent->type = "album"; + $parent->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + $parent->sort_column = "weight"; + $parent->sort_order = "ASC"; + $parent->add_to_parent($root); + + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->add_to_parent($parent); + + $album1 = ORM::factory("item"); + $album1->type = "album"; + $album1->rand_key = ((float)mt_rand()) / (float)mt_getrandmax(); + $album1->sort_column = "weight"; + $album1->sort_order = "ASC"; + $album1->add_to_parent($parent); + + $photo1 = ORM::factory("item"); + $photo1->type = "photo"; + $photo1->add_to_parent($album1); + + $parent->reload(); + + $this->assert_equal(3, $parent->descendants()->count()); + $this->assert_equal(2, $parent->descendants(null, 0, "photo")->count()); + $this->assert_equal(1, $parent->descendants(null, 0, "album")->count()); + } + + public function descendant_limit_test() { + $root = ORM::factory("item", 1); + + $parent = self::create_item_and_add_to_parent($root); + $album1 = self::create_item_and_add_to_parent($parent); + $album2 = self::create_item_and_add_to_parent($parent); + $album3 = self::create_item_and_add_to_parent($parent); + + $parent->reload(); + $this->assert_equal(2, $parent->descendants(2)->count()); + } + + public function descendant_count_test() { + $root = ORM::factory("item", 1); + + $parent = ORM::factory("item"); + $parent->type = "album"; + $parent->add_to_parent($root); + + $photo = ORM::factory("item"); + $photo->type = "photo"; + $photo->add_to_parent($parent); + + $album1 = ORM::factory("item"); + $album1->type = "album"; + $album1->add_to_parent($parent); + + $photo1 = ORM::factory("item"); + $photo1->type = "photo"; + $photo1->add_to_parent($album1); + + $parent->reload(); + + $this->assert_equal(3, $parent->descendants_count()); + $this->assert_equal(2, $parent->descendants_count("photo")); + $this->assert_equal(1, $parent->descendants_count("album")); + } +} diff --git a/modules/gallery/tests/Photo_Helper_Test.php b/modules/gallery/tests/Photo_Helper_Test.php new file mode 100644 index 00000000..deb11bb9 --- /dev/null +++ b/modules/gallery/tests/Photo_Helper_Test.php @@ -0,0 +1,109 @@ +<?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 Photo_Helper_Test extends Unit_Test_Case { + public function create_photo_test() { + $rand = rand(); + + $filename = DOCROOT . "core/tests/test.jpg"; + $image_info = getimagesize($filename); + + $root = ORM::factory("item", 1); + $photo = photo::create($root, $filename, "$rand.jpg", $rand, $rand); + + $this->assert_equal(VARPATH . "albums/$rand.jpg", $photo->file_path()); + $this->assert_equal(VARPATH . "thumbs/{$rand}.jpg", $photo->thumb_path()); + $this->assert_equal(VARPATH . "resizes/{$rand}.jpg", $photo->resize_path()); + + $this->assert_true(is_file($photo->file_path()), "missing: {$photo->file_path()}"); + $this->assert_true(is_file($photo->resize_path()), "missing: {$photo->resize_path()}"); + $this->assert_true(is_file($photo->thumb_path()), "missing: {$photo->thumb_path()}"); + + $this->assert_equal($root->id, $photo->parent_id); // MPTT tests cover other hierarchy checks + $this->assert_equal("$rand.jpg", $photo->name); + $this->assert_equal($rand, $photo->title); + $this->assert_equal($rand, $photo->description); + $this->assert_equal("image/jpeg", $photo->mime_type); + $this->assert_equal($image_info[0], $photo->width); + $this->assert_equal($image_info[1], $photo->height); + + $this->assert_equal($photo->parent()->right - 2, $photo->left); + $this->assert_equal($photo->parent()->right - 1, $photo->right); + } + + public function create_conflicting_photo_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $photo1 = photo::create($root, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + $photo2 = photo::create($root, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + $this->assert_true($photo1->name != $photo2->name); + } + + public function create_photo_with_no_extension_test() { + $root = ORM::factory("item", 1); + try { + photo::create($root, "/tmp", "name", "title", "description"); + $this->assert_false("should fail with an exception"); + } catch (Exception $e) { + // pass + } + } + + public function thumb_url_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $photo = photo::create($root, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + $this->assert_equal("http://./var/thumbs/{$rand}.jpg", $photo->thumb_url()); + } + + public function resize_url_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + $album = album::create($root, $rand, $rand, $rand); + $photo = photo::create($album, DOCROOT . "core/tests/test.jpg", "$rand.jpg", $rand, $rand); + + $this->assert_equal("http://./var/resizes/{$rand}/{$rand}.jpg", $photo->resize_url()); + } + + public function create_photo_shouldnt_allow_names_with_slash_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + try { + $photo = photo::create($root, DOCROOT . "core/tests/test.jpg", "$rand/.jpg", $rand, $rand); + } catch (Exception $e) { + // pass + return; + } + + $this->assert_true(false, "Shouldn't create a photo with / in the name"); + } + + public function create_photo_silently_trims_trailing_periods_test() { + $rand = rand(); + $root = ORM::factory("item", 1); + try { + $photo = photo::create($root, DOCROOT . "core/tests/test.jpg", "$rand.jpg.", $rand, $rand); + } catch (Exception $e) { + $this->assert_equal("@todo NAME_CANNOT_END_IN_PERIOD", $e->getMessage()); + return; + } + + $this->assert_true(false, "Shouldn't create a photo with trailing . in the name"); + } +} diff --git a/modules/gallery/tests/Photos_Controller_Test.php b/modules/gallery/tests/Photos_Controller_Test.php new file mode 100644 index 00000000..71319315 --- /dev/null +++ b/modules/gallery/tests/Photos_Controller_Test.php @@ -0,0 +1,74 @@ +<?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 Photos_Controller_Test extends Unit_Test_Case { + public function setup() { + $this->_post = $_POST; + } + + public function teardown() { + $_POST = $this->_post; + } + + public function change_photo_test() { + $controller = new Photos_Controller(); + $root = ORM::factory("item", 1); + $photo = photo::create($root, DOCROOT . "core/tests/test.jpg", "test.jpeg", "test", "test"); + $orig_name = $photo->name; + + $_POST["filename"] = "test.jpeg"; + $_POST["name"] = "new name"; + $_POST["title"] = "new title"; + $_POST["description"] = "new description"; + $_POST["csrf"] = access::csrf_token(); + access::allow(group::everybody(), "edit", $root); + + ob_start(); + $controller->_update($photo); + $results = ob_get_contents(); + ob_end_clean(); + + $this->assert_equal( + json_encode(array("result" => "success", + "location" => "http://./index.php/test.jpeg")), + $results); + $this->assert_equal("new title", $photo->title); + $this->assert_equal("new description", $photo->description); + + // We don't change the name, yet. + $this->assert_equal($orig_name, $photo->name); + } + + public function change_photo_no_csrf_fails_test() { + $controller = new Photos_Controller(); + $root = ORM::factory("item", 1); + $photo = photo::create($root, DOCROOT . "core/tests/test.jpg", "test", "test", "test"); + $_POST["name"] = "new name"; + $_POST["title"] = "new title"; + $_POST["description"] = "new description"; + access::allow(group::everybody(), "edit", $root); + + try { + $controller->_update($photo); + $this->assert_true(false, "This should fail"); + } catch (Exception $e) { + // pass + } + } +} diff --git a/modules/gallery/tests/REST_Controller_Test.php b/modules/gallery/tests/REST_Controller_Test.php new file mode 100644 index 00000000..8fb04d86 --- /dev/null +++ b/modules/gallery/tests/REST_Controller_Test.php @@ -0,0 +1,197 @@ +<?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 REST_Controller_Test extends Unit_Test_Case { + public function setup() { + $this->_post = $_POST; + $this->mock_controller = new Mock_RESTful_Controller("mock"); + $this->mock_not_loaded_controller = new Mock_RESTful_Controller("mock_not_loaded"); + $_POST = array(); + } + + public function teardown() { + $_POST = $this->_post; + } + + public function dispatch_index_test() { + $_SERVER["REQUEST_METHOD"] = "GET"; + $_POST["_method"] = ""; + $this->mock_controller->__call("index", ""); + $this->assert_equal("index", $this->mock_controller->method_called); + } + + public function dispatch_show_test() { + $_SERVER["REQUEST_METHOD"] = "GET"; + $_POST["_method"] = ""; + $this->mock_controller->__call("3", ""); + $this->assert_equal("show", $this->mock_controller->method_called); + $this->assert_equal("Mock_Model", get_class($this->mock_controller->resource)); + } + + public function dispatch_update_test() { + $_SERVER["REQUEST_METHOD"] = "POST"; + $_POST["_method"] = "PUT"; + $_POST["csrf"] = access::csrf_token(); + $this->mock_controller->__call("3", ""); + $this->assert_equal("update", $this->mock_controller->method_called); + $this->assert_equal("Mock_Model", get_class($this->mock_controller->resource)); + } + + public function dispatch_update_fails_without_csrf_test() { + $_SERVER["REQUEST_METHOD"] = "POST"; + $_POST["_method"] = "PUT"; + try { + $this->mock_controller->__call("3", ""); + $this->assert_false(true, "this should fail with a forbidden exception"); + } catch (Exception $e) { + // pass + } + } + + public function dispatch_delete_test() { + $_SERVER["REQUEST_METHOD"] = "POST"; + $_POST["_method"] = "DELETE"; + $_POST["csrf"] = access::csrf_token(); + $this->mock_controller->__call("3", ""); + $this->assert_equal("delete", $this->mock_controller->method_called); + $this->assert_equal("Mock_Model", get_class($this->mock_controller->resource)); + } + + public function dispatch_delete_fails_without_csrf_test() { + $_SERVER["REQUEST_METHOD"] = "POST"; + $_POST["_method"] = "DELETE"; + try { + $this->mock_controller->__call("3", ""); + $this->assert_false(true, "this should fail with a forbidden exception"); + } catch (Exception $e) { + // pass + } + } + + public function dispatch_404_test() { + /* The dispatcher should throw a 404 if the resource isn't loaded and the method isn't POST. */ + $methods = array( + array("GET", ""), + array("POST", "PUT"), + array("POST", "DELETE")); + + foreach ($methods as $method) { + $_SERVER["REQUEST_METHOD"] = $method[0]; + $_POST["_method"] = $method[1]; + $exception_caught = false; + try { + $this->mock_not_loaded_controller->__call(rand(), ""); + } catch (Kohana_404_Exception $e) { + $exception_caught = true; + } + $this->assert_true($exception_caught, "$method[0], $method[1]"); + } + } + + public function dispatch_create_test() { + $_SERVER["REQUEST_METHOD"] = "POST"; + $_POST["_method"] = ""; + $_POST["csrf"] = access::csrf_token(); + $this->mock_not_loaded_controller->__call("", ""); + $this->assert_equal("create", $this->mock_not_loaded_controller->method_called); + $this->assert_equal( + "Mock_Not_Loaded_Model", get_class($this->mock_not_loaded_controller->resource)); + } + + public function dispatch_create_fails_without_csrf_test() { + $_SERVER["REQUEST_METHOD"] = "POST"; + $_POST["_method"] = ""; + try { + $this->mock_not_loaded_controller->__call("", ""); + $this->assert_false(true, "this should fail with a forbidden exception"); + } catch (Exception $e) { + // pass + } + } + + public function dispatch_form_test_add() { + $this->mock_controller->form_add("args"); + $this->assert_equal("form_add", $this->mock_controller->method_called); + $this->assert_equal("args", $this->mock_controller->resource); + } + + public function dispatch_form_test_edit() { + $this->mock_controller->form_edit("1"); + $this->assert_equal("form_edit", $this->mock_controller->method_called); + $this->assert_equal("Mock_Model", get_class($this->mock_controller->resource)); + } + + public function routes_test() { + $this->assert_equal("mock/form_add/args", router::routed_uri("form/add/mock/args")); + $this->assert_equal("mock/form_edit/args", router::routed_uri("form/edit/mock/args")); + $this->assert_equal(null, router::routed_uri("rest/args")); + } +} + +class Mock_RESTful_Controller extends REST_Controller { + public $method_called; + public $resource; + + public function __construct($type) { + $this->resource_type = $type; + parent::__construct(); + } + + public function _index() { + $this->method_called = "index"; + } + + public function _create($resource) { + $this->method_called = "create"; + $this->resource = $resource; + } + + public function _show($resource) { + $this->method_called = "show"; + $this->resource = $resource; + } + + public function _update($resource) { + $this->method_called = "update"; + $this->resource = $resource; + } + + public function _delete($resource) { + $this->method_called = "delete"; + $this->resource = $resource; + } + + public function _form_add($args) { + $this->method_called = "form_add"; + $this->resource = $args; + } + + public function _form_edit($resource) { + $this->method_called = "form_edit"; + $this->resource = $resource; + } +} + +class Mock_Model { + public $loaded = true; +} + +class Mock_Not_Loaded_Model { + public $loaded = false; +} diff --git a/modules/gallery/tests/REST_Helper_Test.php b/modules/gallery/tests/REST_Helper_Test.php new file mode 100644 index 00000000..1bfc63ab --- /dev/null +++ b/modules/gallery/tests/REST_Helper_Test.php @@ -0,0 +1,45 @@ +<?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 REST_Helper_Test extends Unit_Test_Case { + public function setup() { + $this->_post = $_POST; + } + + public function teardown() { + $_POST = $this->_post; + } + + public function request_method_test() { + foreach (array("GET", "POST") as $method) { + foreach (array("", "PUT", "DELETE") as $tunnel) { + if ($method == "GET") { + $expected = "GET"; + } else { + $expected = $tunnel == "" ? $method : $tunnel; + } + $_SERVER["REQUEST_METHOD"] = $method; + $_POST["_method"] = $tunnel; + + $this->assert_equal(strtolower(rest::request_method()), strtolower($expected), + "Request method: {$method}, tunneled: {$tunnel}"); + } + } + } +} diff --git a/modules/gallery/tests/Sendmail_Test.php b/modules/gallery/tests/Sendmail_Test.php new file mode 100644 index 00000000..64c1fff0 --- /dev/null +++ b/modules/gallery/tests/Sendmail_Test.php @@ -0,0 +1,115 @@ +<?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 Sendmail_Test extends Unit_Test_Case { + public function setup() { + $config = Kohana::config("sendmail"); + $config["from"] = "from@gallery3.com"; + Kohana::config_set("sendmail", $config); + } + + public function sendmail_test() { + $expected = "To: receiver@someemail.com\r\n" . + "From: from@gallery3.com\n" . + "Reply-To: public@gallery3.com\r\n" . + "Subject: Test Email Unit test\r\n\r\n" . + "The mail message body"; + $result = Sendmail_For_Test::factory() + ->to("receiver@someemail.com") + /* + * @todo figure out why this test fails so badly, when the following + * line is not supplied. It doesn't seem to be set by setup method + * as you would expect. + */ + ->from("from@gallery3.com") + ->subject("Test Email Unit test") + ->message("The mail message body") + ->send() + ->send_text; + + $this->assert_equal($expected, $result); + } + + public function sendmail_reply_to_test() { + $expected = "To: receiver@someemail.com\r\n" . + "From: from@gallery3.com\n" . + "Reply-To: reply-to@gallery3.com\r\n" . + "Subject: Test Email Unit test\r\n\r\n" . + "The mail message body"; + $result = Sendmail_For_Test::factory() + ->to("receiver@someemail.com") + ->subject("Test Email Unit test") + ->reply_to("reply-to@gallery3.com") + ->message("The mail message body") + ->send() + ->send_text; + $this->assert_equal($expected, $result); + } + + public function sendmail_html_message_test() { + $expected = "To: receiver@someemail.com\r\n" . + "From: from@gallery3.com\n" . + "Reply-To: public@gallery3.com\n" . + "MIME-Version: 1.0\n" . + "Content-type: text/html; charset=iso-8859-1\r\n" . + "Subject: Test Email Unit test\r\n\r\n" . + "<html><body><p>This is an html msg</p></body></html>"; + $result = Sendmail_For_Test::factory() + ->to("receiver@someemail.com") + ->subject("Test Email Unit test") + ->header("MIME-Version", "1.0") + ->header("Content-type", "text/html; charset=iso-8859-1") + ->message("<html><body><p>This is an html msg</p></body></html>") + ->send() + ->send_text; + $this->assert_equal($expected, $result); + } + + public function sendmail_wrapped_message_test() { + $expected = "To: receiver@someemail.com\r\n" . + "From: from@gallery3.com\n" . + "Reply-To: public@gallery3.com\r\n" . + "Subject: Test Email Unit test\r\n\r\n" . + "This is a long message that needs to go\n" . + "over forty characters If we get lucky we\n" . + "might make it long enought to wrap a\n" . + "couple of times."; + $result = Sendmail_For_Test::factory() + ->to("receiver@someemail.com") + ->subject("Test Email Unit test") + ->line_length(40) + ->message("This is a long message that needs to go over forty characters " . + "If we get lucky we might make it long enought to wrap a couple " . + "of times.") + ->send() + ->send_text; + $this->assert_equal($expected, $result); + } +} + +class Sendmail_For_Test extends Sendmail { + static function factory() { + return new Sendmail_For_Test(); + } + + public function mail($to, $subject, $message, $headers) { + $this->send_text = "To: $to\r\n{$headers}\r\nSubject: $this->subject\r\n\r\n$message"; + return true; + } +}
\ No newline at end of file diff --git a/modules/gallery/tests/Var_Test.php b/modules/gallery/tests/Var_Test.php new file mode 100644 index 00000000..82370631 --- /dev/null +++ b/modules/gallery/tests/Var_Test.php @@ -0,0 +1,49 @@ +<?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 Var_Test extends Unit_Test_Case { + public function add_parameter_test() { + module::set_var("core", "Parameter", "original value"); + $this->assert_equal("original value", module::get_var("core", "Parameter")); + + module::set_var("core", "Parameter", "updated value"); + $this->assert_equal("updated value", module::get_var("core", "Parameter")); + } + + public function clear_parameter_test() { + module::set_var("core", "Parameter", "original value"); + $this->assert_equal("original value", module::get_var("core", "Parameter")); + + module::clear_var("core", "Parameter"); + $this->assert_equal(null, module::get_var("core", "Parameter")); + } + + public function incr_parameter_test() { + module::set_var("core", "Parameter", "original value"); + module::incr_var("core", "Parameter"); + $this->assert_equal("1", module::get_var("core", "Parameter")); + + module::set_var("core", "Parameter", "2"); + module::incr_var("core", "Parameter", "9"); + $this->assert_equal("11", module::get_var("core", "Parameter")); + + module::incr_var("core", "NonExistent", "9"); + $this->assert_equal(null, module::get_var("core", "NonExistent")); + } +}
\ No newline at end of file diff --git a/modules/gallery/tests/images/DSC_0003.jpg b/modules/gallery/tests/images/DSC_0003.jpg Binary files differnew file mode 100644 index 00000000..5780d9d8 --- /dev/null +++ b/modules/gallery/tests/images/DSC_0003.jpg diff --git a/modules/gallery/tests/images/DSC_0005.jpg b/modules/gallery/tests/images/DSC_0005.jpg Binary files differnew file mode 100644 index 00000000..4d2b53a9 --- /dev/null +++ b/modules/gallery/tests/images/DSC_0005.jpg diff --git a/modules/gallery/tests/images/DSC_0017.jpg b/modules/gallery/tests/images/DSC_0017.jpg Binary files differnew file mode 100644 index 00000000..b7f7bb90 --- /dev/null +++ b/modules/gallery/tests/images/DSC_0017.jpg diff --git a/modules/gallery/tests/images/DSC_0019.jpg b/modules/gallery/tests/images/DSC_0019.jpg Binary files differnew file mode 100644 index 00000000..0ce25aa4 --- /dev/null +++ b/modules/gallery/tests/images/DSC_0019.jpg diff --git a/modules/gallery/tests/images/DSC_0067.jpg b/modules/gallery/tests/images/DSC_0067.jpg Binary files differnew file mode 100644 index 00000000..84f134cb --- /dev/null +++ b/modules/gallery/tests/images/DSC_0067.jpg diff --git a/modules/gallery/tests/images/DSC_0072.jpg b/modules/gallery/tests/images/DSC_0072.jpg Binary files differnew file mode 100644 index 00000000..dfad82b0 --- /dev/null +++ b/modules/gallery/tests/images/DSC_0072.jpg diff --git a/modules/gallery/tests/images/P4050088.jpg b/modules/gallery/tests/images/P4050088.jpg Binary files differnew file mode 100644 index 00000000..62f4749d --- /dev/null +++ b/modules/gallery/tests/images/P4050088.jpg diff --git a/modules/gallery/tests/selenium/Add_Album.html b/modules/gallery/tests/selenium/Add_Album.html new file mode 100644 index 00000000..ccd4d0b7 --- /dev/null +++ b/modules/gallery/tests/selenium/Add_Album.html @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>AddAlbum</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">AddAlbum</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/index.php/albums/1</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>link=Add album</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>name</td> + <td>seleniumtest</td> +</tr> +<tr> + <td>type</td> + <td>title</td> + <td>Selenium Test Album</td> +</tr> +<tr> + <td>type</td> + <td>description</td> + <td>Test</td> +</tr> +<tr> + <td>click</td> + <td>//button[@type='button']</td> + <td></td> +</tr> +<tr> + <td>assertTextPresent</td> + <td>Selenium Test Album</td> + <td></td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/modules/gallery/tests/selenium/Add_Comment.html b/modules/gallery/tests/selenium/Add_Comment.html new file mode 100644 index 00000000..b4b96ed2 --- /dev/null +++ b/modules/gallery/tests/selenium/Add_Comment.html @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>Add comment</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">Add comment</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/index.php/albums/1</td> + <td></td> +</tr> +<tr> + <td>clickAndWait</td> + <td>gPhotoId-2</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>gAuthor</td> + <td>Test</td> +</tr> +<tr> + <td>type</td> + <td>gEmail</td> + <td>test@gmail.com</td> +</tr> +<tr> + <td>type</td> + <td>gText</td> + <td>This is a selenium test comment.</td> +</tr> +<tr> + <td>click</td> + <td>//button[@type='submit']</td> + <td></td> +</tr> +<tr> + <td>assertTextPresent</td> + <td>This is a selenium test comment.</td> + <td></td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/modules/gallery/tests/selenium/Add_Item.html b/modules/gallery/tests/selenium/Add_Item.html new file mode 100644 index 00000000..741dff65 --- /dev/null +++ b/modules/gallery/tests/selenium/Add_Item.html @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>AddItem</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">AddItem</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/index.php/albums/1</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>link=Add an item</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>name</td> + <td>seleniumitem.jpg</td> +</tr> +<tr> + <td>type</td> + <td>title</td> + <td>Selenium Item</td> +</tr> +<tr> + <td>type</td> + <td>description</td> + <td>Test item</td> +</tr> +<tr> + <td>type</td> + <td>file</td> + <td>/Users/ckieffer/Sites/gallery3.0/core/tests/images/DSC_0003.jpg</td> +</tr> +<tr> + <td>click</td> + <td>//button[@type='button']</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>link=X</td> + <td></td> +</tr> +<tr> + <td>assertTextPresent</td> + <td>Selenium Item</td> + <td></td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/modules/gallery/tests/selenium/Login.html b/modules/gallery/tests/selenium/Login.html new file mode 100644 index 00000000..5e17a3c7 --- /dev/null +++ b/modules/gallery/tests/selenium/Login.html @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>Login</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">Login</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/index.php/albums/1</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>gLoginLink</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>gName</td> + <td>admin</td> +</tr> +<tr> + <td>type</td> + <td>gPassword</td> + <td>admin</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>//button[@type='button']</td> + <td></td> +</tr> +<tr> + <td>clickAndWait</td> + <td>gUserProfileLink</td> + <td></td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/modules/gallery/tests/test.jpg b/modules/gallery/tests/test.jpg Binary files differnew file mode 100644 index 00000000..1f3525e5 --- /dev/null +++ b/modules/gallery/tests/test.jpg diff --git a/modules/gallery/views/admin_advanced_settings.html.php b/modules/gallery/views/admin_advanced_settings.html.php new file mode 100644 index 00000000..1f3825bd --- /dev/null +++ b/modules/gallery/views/admin_advanced_settings.html.php @@ -0,0 +1,34 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="gAdminAdvancedSettings"> + <h1> <?= t("Advanced Settings") ?> </h1> + <p> + <?= t("Here are internal Gallery configuration settings. Most of these settings are accessible elsewhere in the administrative console.") ?> + </p> + <ul id="gMessage"> + <li class="gWarning"> + <b><?= t("Change these values at your own risk!</b>") ?> + </li> + </ul> + + <table> + <tr> + <th> <?= t("Module") ?> </th> + <th> <?= t("Name") ?> </th> + <th> <?= t("Value") ?></th> + </tr> + <? foreach ($vars as $var): ?> + <? if ($var->module_name == "core" && $var->name == "_cache") continue ?> + <tr class="setting"> + <td> <?= $var->module_name ?> </td> + <td> <?= $var->name ?> </td> + <td> + <a href="<?= url::site("admin/advanced_settings/edit/$var->module_name/$var->name") ?>" + class="gDialogLink" + title="<?= t("Edit %var (%module_name)", array("var" => $var->name, "module_name" => $var->module_name)) ?>"> + <?= $var->value ?> + </a> + </td> + </tr> + <? endforeach ?> + </table> +</div> diff --git a/modules/gallery/views/admin_block_log_entries.html.php b/modules/gallery/views/admin_block_log_entries.html.php new file mode 100644 index 00000000..db6313e1 --- /dev/null +++ b/modules/gallery/views/admin_block_log_entries.html.php @@ -0,0 +1,11 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<ul> + <? foreach ($entries as $entry): ?> + <li class="<?= log::severity_class($entry->severity) ?>"> + <a href="<?= url::site("user/$entry->user_id") ?>"><?= $entry->user->name ?></a> + <?= date("Y-M-d H:i:s", $entry->timestamp) ?> + <?= $entry->message ?> + <?= $entry->html ?> + </li> + <? endforeach ?> +</ul> diff --git a/modules/gallery/views/admin_block_news.html.php b/modules/gallery/views/admin_block_news.html.php new file mode 100644 index 00000000..cb276ae5 --- /dev/null +++ b/modules/gallery/views/admin_block_news.html.php @@ -0,0 +1,11 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<ul> + <? foreach ($feed as $entry): ?> + <li> + <a href="<?= $entry["link"] ?>"><?= $entry["title"] ?></a> + <p> + <?= text::limit_words(strip_tags($entry["description"]), 25); ?> + </p> + </li> + <? endforeach ?> +</ul> diff --git a/modules/gallery/views/admin_block_photo_stream.html.php b/modules/gallery/views/admin_block_photo_stream.html.php new file mode 100644 index 00000000..e8a4d933 --- /dev/null +++ b/modules/gallery/views/admin_block_photo_stream.html.php @@ -0,0 +1,14 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<ul> +<? foreach ($photos as $photo): ?> + <li class="gItem gPhoto"> + <a href="<?= url::site("photos/$photo->id") ?>" title="<?= $photo->title ?>"> + <img <?= photo::img_dimensions($photo->width, $photo->height, 72) ?> + src="<?= $photo->thumb_url() ?>" alt="<?= $photo->title ?>" /> + </a> + </li> +<? endforeach ?> +</ul> +<p> + <?= t("Recent photos added to your Gallery") ?> +</p> diff --git a/modules/gallery/views/admin_block_platform.html.php b/modules/gallery/views/admin_block_platform.html.php new file mode 100644 index 00000000..6b79f047 --- /dev/null +++ b/modules/gallery/views/admin_block_platform.html.php @@ -0,0 +1,18 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<ul> + <li> + <?= t("Operating System: %operating_system", array("operating_system" => PHP_OS)) ?> + </li> + <li> + <?= t("Apache: %apache_version", array("apache_version" => function_exists("apache_get_version") ? apache_get_version() : t("Unknown"))) ?> + </li> + <li> + <?= t("PHP: %php_version", array("php_version" => phpversion())) ?> + </li> + <li> + <?= t("MySQL: %mysql_version", array("mysql_version" => Database::instance()->query("SELECT version() as v")->current()->v)) ?> + </li> + <li> + <?= t("Server load: %load_average", array("load_average" => $load_average)) ?> + </li> +</ul> diff --git a/modules/gallery/views/admin_block_stats.html.php b/modules/gallery/views/admin_block_stats.html.php new file mode 100644 index 00000000..2d975073 --- /dev/null +++ b/modules/gallery/views/admin_block_stats.html.php @@ -0,0 +1,12 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<ul> + <li> + <?= t("Version: %version", array("version" => module::get_var("core", "version"))) ?> + </li> + <li> + <?= t("Albums: %count", array("count" => $album_count)) ?> + </li> + <li> + <?= t("Photos: %count", array("count" => $photo_count)) ?> + </li> +</ul> diff --git a/modules/gallery/views/admin_block_welcome.html.php b/modules/gallery/views/admin_block_welcome.html.php new file mode 100644 index 00000000..488fa908 --- /dev/null +++ b/modules/gallery/views/admin_block_welcome.html.php @@ -0,0 +1,20 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<p> + <?= t("This is your administration dashboard and it provides a quick overview of status messages, recent updates, and frequently used options. Add or remove blocks and rearrange them to tailor to your needs. The admin menu provides quick access to all of Gallery 3's options and settings. Here are a few of the most used options to get you started.") ?> +</p> +<ul> + <li> + <?= t("General Settings - choose your <a href=\"%graphics_url\">graphics</a> and <a href=\"%language_url\">language</a> settings.", + array("graphics_url" => url::site("admin/graphics"), + "language_url" => url::site("admin/languages"))) ?> + </li> + <li> + <?= t("Appearance - <a href=\"%theme_url\">choose a theme</a>, or <a href=\"%theme_details_url\">customize the way it looks</a>.", + array("theme_url" => url::site("admin/theme"), + "theme_details_url" => url::site("admin/theme_details"))) ?> + </li> + <li> + <?= t("Customize - <a href=\"%modules_url\">install modules</a> to add cool features!", + array("modules_url" => url::site("admin/modules"))) ?> + </li> +</ul> diff --git a/modules/gallery/views/admin_dashboard.html.php b/modules/gallery/views/admin_dashboard.html.php new file mode 100644 index 00000000..c266d7e1 --- /dev/null +++ b/modules/gallery/views/admin_dashboard.html.php @@ -0,0 +1,38 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + update_blocks = function() { + $.get("<?= url::site("admin/dashboard/reorder") ?>", + {"csrf": "<?= $csrf ?>", + "dashboard_center[]": $("#gAdminDashboard").sortable( + "toArray", {attribute: "block_id"}), + "dashboard_sidebar[]": $("#gAdminDashboardSidebar").sortable( + "toArray", {attribute: "block_id"})}); + }; + + $(document).ready(function(){ + $("#gAdminDashboard .gBlock *:first").addClass("gDraggable"); + $("#gAdminDashboard").sortable({ + connectWith: ["#gAdminDashboardSidebar"], + containment: "document", + cursor: "move", + handle: $("div:first"), + opacity: 0.6, + placeholder: "gDropTarget", + stop: update_blocks + }); + + $("#gAdminDashboardSidebar .gBlock *:first").addClass("gDraggable"); + $("#gAdminDashboardSidebar").sortable({ + connectWith: ["#gAdminDashboard"], + containment: "document", + cursor: "move", + handle: $("div:first"), + opacity: 0.6, + placeholder: "gDropTarget", + stop: update_blocks + }); + }); +</script> +<div id="gAdminDashboard"> + <?= $blocks ?> +</div> diff --git a/modules/gallery/views/admin_graphics.html.php b/modules/gallery/views/admin_graphics.html.php new file mode 100644 index 00000000..08374471 --- /dev/null +++ b/modules/gallery/views/admin_graphics.html.php @@ -0,0 +1,28 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + $(document).ready(function() { + select_toolkit = function(el) { + if (!$(this).hasClass("gUnavailable")) { + window.location = '<?= url::site("admin/graphics/choose/__TK__?csrf=$csrf") ?>' + .replace("__TK__", $(this).attr("id")); + } + }; + $("#gAdminGraphics div.gAvailable .gBlock").click(select_toolkit); + }); + +</script> +<div id="gAdminGraphics"> + <h1> <?= t("Graphics Settings") ?> </h1> + <p> + <?= t("Gallery needs a graphics toolkit in order to manipulate your photos. Please choose one from the list below.") ?> + </p> + + <h2> <?= t("Active Toolkit") ?> </h2> + <?= $active ?> + + <div class="gAvailable"> + <h2> <?= t("Available Toolkits") ?> </h2> + <?= $available ?> + </div> +</div> + diff --git a/modules/gallery/views/admin_graphics_gd.html.php b/modules/gallery/views/admin_graphics_gd.html.php new file mode 100644 index 00000000..cae68b74 --- /dev/null +++ b/modules/gallery/views/admin_graphics_gd.html.php @@ -0,0 +1,29 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="gd" class="gBlock<?= $is_active ? " gSelected" : "" ?><?= $tk->gd["GD Version"] ? " gInstalledToolkit" : " gUnavailable" ?>"> + <img class="logo" width="170" height="110" src="<?= url::file("core/images/gd.png"); ?>" alt="<? t("Visit the GD lib project site") ?>" /> + <h3> <?= t("GD") ?> </h3> + <p> + <?= t("The GD graphics library is an extension to PHP commonly installed most webservers. Please refer to the <a href=\"%url\">GD website</a> for more information.", + array("url" => "http://www.boutell.com/gd")) ?> + </p> + <? if ($tk->gd["GD Version"] && function_exists('imagerotate')): ?> + <p class="gSuccess"> + <?= t("You have GD version %version.", array("version" => $tk->gd["GD Version"])) ?> + </p> + <p> + <a class="gButtonLink ui-state-default ui-corner-all"><?= t("Activate GD") ?></a> + </p> + <? elseif ($tk->gd["GD Version"]): ?> + <p class="gWarning"> + <?= t("You have GD version %version, but it lacks image rotation.", + array("version" => $tk->gd["GD Version"])) ?> + </p> + <p> + <a class="gButtonLink ui-state-default ui-corner-all"><?= t("Activate GD") ?></a> + </p> + <? else: ?> + <p class="gInfo"> + <?= t("You do not have GD installed.") ?> + </p> + <? endif ?> +</div> diff --git a/modules/gallery/views/admin_graphics_graphicsmagick.html.php b/modules/gallery/views/admin_graphics_graphicsmagick.html.php new file mode 100644 index 00000000..720a9459 --- /dev/null +++ b/modules/gallery/views/admin_graphics_graphicsmagick.html.php @@ -0,0 +1,21 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="graphicsmagick" class="gBlock<?= $is_active ? " gSelected" : "" ?><?= $tk->graphicsmagick ? " gInstalledToolkit" : " gUnavailable" ?>"> + <h3> <?= t("GraphicsMagick") ?> </h3> + <img class="logo" width="107" height="76" src="<?= url::file("core/images/graphicsmagick.png"); ?>" alt="<? t("Visit the GraphicsMagick project site") ?>" /> + <p> + <?= t("GraphicsMagick is a standalone graphics program available on most Linux systems. Please refer to the <a href=\"%url\">GraphicsMagick website</a> for more information.", + array("url" => "http://www.graphicsmagick.org")) ?> + </p> + <? if ($tk->graphicsmagick): ?> + <p class="gSuccess"> + <?= t("GraphicsMagick is available in %path", array("path" => $tk->graphicsmagick)) ?> + </p> + <p> + <a class="gButtonLink ui-state-default ui-corner-all"><?= t("Activate Graphics Magic") ?></a> + </p> + <? else: ?> + <p class="gInfo"> + <?= t("GraphicsMagick is not available on your system.") ?> + </p> + <? endif ?> +</div> diff --git a/modules/gallery/views/admin_graphics_imagemagick.html.php b/modules/gallery/views/admin_graphics_imagemagick.html.php new file mode 100644 index 00000000..c7468eed --- /dev/null +++ b/modules/gallery/views/admin_graphics_imagemagick.html.php @@ -0,0 +1,21 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="imagemagick" class="gBlock<?= $is_active ? " gSelected" : "" ?><?= $tk->imagemagick ? " gInstalledToolkit" : " gUnavailable" ?>"> + <h3> <?= t("ImageMagick") ?> </h3> + <img class="logo" width="114" height="118" src="<?= url::file("core/images/imagemagick.jpg"); ?>" alt="<? t("Visit the ImageMagick project site") ?>" /> + <p> + <?= t("ImageMagick is a standalone graphics program available on most Linux systems. Please refer to the <a href=\"%url\">ImageMagick website</a> for more information.", + array("url" => "http://www.imagemagick.org")) ?> + </p> + <? if ($tk->imagemagick): ?> + <p class="gSuccess"> + <?= t("ImageMagick is available in %path", array("path" => $tk->imagemagick)) ?> + </p> + <p> + <a class="gButtonLink ui-state-default ui-corner-all"><?= t("Activate ImageMagick") ?></a> + </p> + <? else: ?> + <p class="gInfo"> + <?= t("ImageMagick is not available on your system.") ?> + </p> + <? endif ?> +</div> diff --git a/modules/gallery/views/admin_graphics_none.html.php b/modules/gallery/views/admin_graphics_none.html.php new file mode 100644 index 00000000..5306a70d --- /dev/null +++ b/modules/gallery/views/admin_graphics_none.html.php @@ -0,0 +1,7 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="none" class="gBlock"> + <h3 class="gWarning"> <?= t("No Active Toolkit") ?> </h3> + <p> + <?= t("We were unable to detect a graphics program. You must install one of the toolkits below in order to many Gallery features.") ?> + </p> +</div> diff --git a/modules/gallery/views/admin_languages.html.php b/modules/gallery/views/admin_languages.html.php new file mode 100644 index 00000000..2b43f1b4 --- /dev/null +++ b/modules/gallery/views/admin_languages.html.php @@ -0,0 +1,15 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="gLanguages"> + <h2> <?= t("Languages") ?> </h2> + + <?= $settings_form ?> + + <h2> <?= t("Download translations") ?> </h2> + <a href="<?= url::site("admin/maintenance/start/core_task::update_l10n?csrf=$csrf") ?>" + class="gDialogLink"> + <?= t("Get updates") ?> + </a> + + <h2> <?= t("Your Own Translations") ?> </h2> + <?= $share_translations_form ?> +</div> diff --git a/modules/gallery/views/admin_maintenance.html.php b/modules/gallery/views/admin_maintenance.html.php new file mode 100644 index 00000000..bc060a7b --- /dev/null +++ b/modules/gallery/views/admin_maintenance.html.php @@ -0,0 +1,181 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="gAdminMaintenance"> + <h1> <?= t("Maintenance Tasks") ?> </h1> + <p> + <?= t("Occasionally your Gallery will require some maintenance. Here are some tasks you can use to keep it running smoothly.") ?> + </p> + + <div id="gAvailableTasks"> + <h2> <?= t("Available Tasks") ?> </h2> + <table> + <tr> + <th> + <?= t("Name") ?> + </th> + <th> + <?= t("Description") ?> + </th> + <th> + <?= t("Action") ?> + </th> + </tr> + <? foreach ($task_definitions as $task): ?> + <tr class="<?= log::severity_class($task->severity) ?>"> + <td> + <?= $task->name ?> + </td> + <td> + <?= $task->description ?> + </td> + <td> + <a href="<?= url::site("admin/maintenance/start/$task->callback?csrf=$csrf") ?>" + class="gDialogLink"> + <?= t("run") ?> + </a> + </td> + </tr> + <? endforeach ?> + </table> + </div> + + <? if ($running_tasks->count()): ?> + <div id="gRunningTasks"> + <h2> <?= t("Running Tasks") ?> </h2> + <a href="<?= url::site("admin/maintenance/cancel_running_tasks?csrf=$csrf") ?>" + class="gButtonLink ui-icon-left ui-state-default ui-corner-all right"> + <?= t("cancel all") ?></a> + + <table> + <tr> + <th> + <?= t("Last Updated") ?> + </th> + <th> + <?= t("Name") ?> + </th> + <th> + <?= t("Status") ?> + </th> + <th> + <?= t("Info") ?> + </th> + <th> + <?= t("Owner") ?> + </th> + <th> + <?= t("Action") ?> + </th> + </tr> + <? foreach ($running_tasks as $task): ?> + <tr class="<?= $task->state == "stalled" ? "gWarning" : "" ?>"> + <td> + <?= date("M j, Y H:i:s", $task->updated) ?> + </td> + <td> + <?= $task->name ?> + </td> + <td> + <? if ($task->done): ?> + <? if ($task->state == "cancelled"): ?> + <?= t("Cancelled") ?> + <? endif ?> + <?= t("Done") ?> + <? elseif ($task->state == "stalled"): ?> + <?= t("Stalled") ?> + <? else: ?> + <?= t("%percent_complete% Complete", array("percent_complete" => $task->percent_complete)) ?> + <? endif ?> + </td> + <td> + <?= $task->status ?> + </td> + <td> + <?= $task->owner()->name ?> + </td> + <td> + <? if ($task->state == "stalled"): ?> + <a class="gDialogLink" href="<?= url::site("admin/maintenance/resume/$task->id?csrf=$csrf") ?>"> + <?= t("resume") ?> + </a> + <? endif ?> + <a href="<?= url::site("admin/maintenance/cancel/$task->id?csrf=$csrf") ?>"> + <?= t("cancel") ?> + </a> + </td> + </tr> + <? endforeach ?> + </table> + </div> + <? endif ?> + + <? if ($finished_tasks->count()): ?> + <div id="gFinishedTasks"> + <a href="<?= url::site("admin/maintenance/remove_finished_tasks?csrf=$csrf") ?>" + class="gButtonLink ui-icon-left ui-state-default ui-corner-all right"> + <span class="ui-icon ui-icon-trash"></span><?= t("remove all finished") ?></a> + + <h2> <?= t("Finished Tasks") ?> </h2> + <table> + <tr> + <th> + <?= t("Last Updated") ?> + </th> + <th> + <?= t("Name") ?> + </th> + <th> + <?= t("Status") ?> + </th> + <th> + <?= t("Info") ?> + </th> + <th> + <?= t("Owner") ?> + </th> + <th> + <?= t("Action") ?> + </th> + </tr> + <? foreach ($finished_tasks as $task): ?> + <tr class="<?= $task->state == "success" ? "gSuccess" : "gError" ?>"> + <td> + <?= date("M j, Y H:i:s", $task->updated) ?> + </td> + <td> + <?= $task->name ?> + </td> + <td> + <? if ($task->state == "success"): ?> + <?= t("Success") ?> + <? elseif ($task->state == "error"): ?> + <?= t("Failed") ?> + <? elseif ($task->state == "cancelled"): ?> + <?= t("Cancelled") ?> + <? endif ?> + </td> + <td> + <?= $task->status ?> + </td> + <td> + <?= $task->owner()->name ?> + </td> + <td> + <? if ($task->done): ?> + <a href="<?= url::site("admin/maintenance/remove/$task->id?csrf=$csrf") ?>"> + <?= t("remove") ?> + </a> + <? else: ?> + <a class="gDialogLink" href="<?= url::site("admin/maintenance/resume/$task->id?csrf=$csrf") ?>"> + <?= t("resume") ?> + </a> + <a href="<?= url::site("admin/maintenance/cancel/$task->id?csrf=$csrf") ?>"> + <?= t("cancel") ?> + </a> + <? endif ?> + </td> + </tr> + <? endforeach ?> + </table> + </div> + <? endif ?> +</div> diff --git a/modules/gallery/views/admin_maintenance_task.html.php b/modules/gallery/views/admin_maintenance_task.html.php new file mode 100644 index 00000000..1ee02311 --- /dev/null +++ b/modules/gallery/views/admin_maintenance_task.html.php @@ -0,0 +1,32 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + update = function() { + $.ajax({ + url: "<?= url::site("admin/maintenance/run/$task->id?csrf=$csrf") ?>", + dataType: "json", + success: function(data) { + $(".gProgressBar").progressbar("value", data.task.percent_complete); + $("#gStatus").html("" + data.task.status); + if (data.task.done) { + $("#gPauseButton").hide(); + $("#gDoneButton").show(); + } else { + setTimeout(update, 100); + } + } + }); + } + $(".gProgressBar").progressbar({value: 0}); + update(); + dismiss = function() { + window.location.reload(); + } +</script> +<div id="gProgress"> + <div class="gProgressBar"></div> + <div id="gStatus"></div> + <div> + <button id="gPauseButton" class="ui-state-default ui-corner-all" onclick="dismiss()"><?= t("Pause") ?></button> + <button id="gDoneButton" class="ui-state-default ui-corner-all" style="display: none" onclick="dismiss()"><?= t("Done") ?></button> + </div> +</div> diff --git a/modules/gallery/views/admin_modules.html.php b/modules/gallery/views/admin_modules.html.php new file mode 100644 index 00000000..3fddd6cd --- /dev/null +++ b/modules/gallery/views/admin_modules.html.php @@ -0,0 +1,32 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="gModules"> + <h1> <?= t("Gallery Modules") ?> </h1> + <p> + <?= t("Power up your Gallery by adding more modules! Each module provides new cool features.") ?> + </p> + + <form method="post" action="<?= url::site("admin/modules/save") ?>"> + <?= access::csrf_form_field() ?> + <table> + <tr> + <th> <?= t("Installed") ?> </th> + <th> <?= t("Name") ?> </th> + <th> <?= t("Version") ?> </th> + <th> <?= t("Description") ?> </th> + </tr> + <? $i = 0 ?> + <? foreach ($available as $module_name => $module_info): ?> + <tr class="<?= ($i % 2 == 0) ? "gEvenRow" : "gOddRow" ?>"> + <? $data = array("name" => $module_name); ?> + <? if ($module_info->locked) $data["disabled"] = 1; ?> + <td> <?= form::checkbox($data, '1', module::is_active($module_name)) ?> </td> + <td> <?= t($module_info->name) ?> </td> + <td> <?= $module_info->version ?> </td> + <td> <?= t($module_info->description) ?> </td> + </tr> + <? $i++ ?> + <? endforeach ?> + </table> + <input type="submit" value="<?= t("Update") ?>"/> + </form> +</div> diff --git a/modules/gallery/views/admin_theme_details.html.php b/modules/gallery/views/admin_theme_details.html.php new file mode 100644 index 00000000..eb450b16 --- /dev/null +++ b/modules/gallery/views/admin_theme_details.html.php @@ -0,0 +1,6 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="gAdminThemeDetails"> + <h1> <?= t("Theme Details") ?> </h1> + + <?= $form ?> +</div> diff --git a/modules/gallery/views/admin_themes.html.php b/modules/gallery/views/admin_themes.html.php new file mode 100644 index 00000000..f85bce70 --- /dev/null +++ b/modules/gallery/views/admin_themes.html.php @@ -0,0 +1,89 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + var select_url = "<?= url::site("admin/themes/choose") ?>"; + select = function(type, id) { + $.post(select_url, {"type": type, "id": id, "csrf": '<?= $csrf ?>'}, + function() { load(type) }); + } +</script> + +<h1> <?= t("Theme Administration") ?> </h1> +<p> + <?= t("Gallery allows you to choose a theme for browsing your Gallery, as well as a special theme for the administration interface. Click a theme to preview and activate it.") ?> +</p> + +<div id="gSiteTheme"> + <h2> <?= t("Gallery theme") ?> </h2> + <div class="gBlock gSelected"> + <img src="<?= url::file("themes/{$site}/thumbnail.png") ?>" + alt="<?= $themes[$site]->name ?>" /> + <h3> <?= $themes[$site]->name ?> </h3> + <p> + <?= $themes[$site]->description ?> + </p> + </div> + + <h2> <?= t("Available Gallery themes") ?> </h2> + <div class="gAvailable"> + <? $count = 0 ?> + <? foreach ($themes as $id => $info): ?> + <? if (!$info->site) continue ?> + <? if ($id == $site) continue ?> + <div class="gBlock"> + <a href="<?= url::site("admin/themes/preview/site/$id") ?>" class="gDialogLink" title="<?= t("Theme Preview: %theme_name", array("theme_name" => $info->name)) ?>"> + <img src="<?= url::file("themes/{$id}/thumbnail.png") ?>" + alt="<?= $info->name ?>" /> + <h3> <?= $info->name ?> </h3> + <p> + <?= $info->description ?> + </p> + </a> + </div> + <? $count++ ?> + <? endforeach ?> + + <? if (!$count): ?> + <p> + <?= t("There are no other site themes available.") ?> + </p> + <? endif ?> + </div> +</div> + +<div id="gAdminTheme"> + <h2> <?= t("Admin theme") ?> </h2> + <div class="gBlock gSelected"> + <img src="<?= url::file("themes/{$admin}/thumbnail.png") ?>" + alt="<?= $themes[$admin]->name ?>" /> + <h3> <?= $themes[$admin]->name ?> </h3> + <p> + <?= $themes[$admin]->description ?> + </p> + </div> + + <h2> <?= t("Available admin themes") ?> </h2> + <div class="gAvailable"> + <? $count = 0 ?> + <? foreach ($themes as $id => $info): ?> + <? if (!$info->admin) continue ?> + <? if ($id == $admin) continue ?> + <div class="gBlock"> + <a href="<?= url::site("admin/themes/preview/admin/$id") ?>" class="gDialogLink" title="<?= t("Theme Preview: %theme_name", array("theme_name" => $info->name)) ?>"> + <img src="<?= url::file("themes/{$id}/thumbnail.png") ?>" + alt="<?= $info->name ?>" /> + <h3> <?= $info->name ?> </h3> + <p> + <?= $info->description ?> + </p> + </a> + </div> + <? $count++ ?> + <? endforeach ?> + + <? if (!$count): ?> + <p> + <?= t("There are no other admin themes available.") ?> + </p> + <? endif ?> + </div> +</div>
\ No newline at end of file diff --git a/modules/gallery/views/admin_themes_preview.html.php b/modules/gallery/views/admin_themes_preview.html.php new file mode 100644 index 00000000..a7aea172 --- /dev/null +++ b/modules/gallery/views/admin_themes_preview.html.php @@ -0,0 +1,7 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<p> + <a href="<?= url::site("admin/themes/choose/$type/$theme_name?csrf=$csrf") ?>"> + <?= t("Activate <strong>%theme_name</strong>", array("theme_name" => $info->name)) ?> + </a> +</p> +<iframe src="<?= $url ?>" style="width: 900px; height: 450px"></iframe> diff --git a/modules/gallery/views/after_install.html.php b/modules/gallery/views/after_install.html.php new file mode 100644 index 00000000..aa26858a --- /dev/null +++ b/modules/gallery/views/after_install.html.php @@ -0,0 +1,29 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<h1 style="display: none"> + <?= t("Welcome to Gallery 3!") ?> +</h1> + +<p> + <?= t("Congratulations on choosing Gallery to host your photos. We're confident that you're going to have a great experience.") ?> +</p> + +<p> + <?= t("You're logged in to the <b>%user_name</b> account. The very first thing you should do is to change your password to something that you'll remember.", array("user_name" => $user->name)) ?> +</p> + +<p> + <a href="<?= url::site("form/edit/users/{$user->id}") ?>" + title="<?= t("Edit Your Profile") ?>" + id="gAfterInstallChangePasswordLink" class="gButtonLink ui-state-default ui-corners-all"><?= t("Change Password Now") ?></a> + <script> + $("#gAfterInstallChangePasswordLink").bind("click", handleDialogEvent); + </script> +</p> + +<p> + <?= t("Want to learn more? The <a href=\"%url\">Gallery website</a> has news and information about Gallery Project and community.", array("url" => "http://gallery.menalto.com")) ?> +</p> + +<p> + <?= t("Having problems? There's lots of information in our <a href=\"%codex_url\">documentation site</a> or you can <a href=\"%forum_url\">ask for help in the forums!</a>", array("codex_url" => "http://codex.gallery2.org/Main_Page", "forum_url" => "http://gallery.menalto.com/forum")) ?> +</ul> diff --git a/modules/gallery/views/after_install_loader.html.php b/modules/gallery/views/after_install_loader.html.php new file mode 100644 index 00000000..baf91eed --- /dev/null +++ b/modules/gallery/views/after_install_loader.html.php @@ -0,0 +1,7 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<span id="gAfterInstall" + title="<?= t("Welcome to Gallery 3") ?>" + href="<?= url::site("after_install") ?>"/> +<script type="text/javascript"> + $(document).ready(function(){openDialog($("#gAfterInstall"));}); +</script> diff --git a/modules/gallery/views/form.html.php b/modules/gallery/views/form.html.php new file mode 100644 index 00000000..ec2a56a9 --- /dev/null +++ b/modules/gallery/views/form.html.php @@ -0,0 +1,75 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<? +print($open); + +// Not sure what to do with these, but at least show that we received them. +if ($class) { + print "<!-- unused class in form.html.php: $class -->"; +} +if ($title) { + print $title; +} + +if (!function_exists("DrawForm")) { + function DrawForm($inputs, $level=1) { + $error_messages = array(); + $prefix = str_repeat(" ", $level); + $haveGroup = false; + // On the first level, make sure we have a group if not add the <ul> tag now + if ($level == 1) { + foreach ($inputs as $input) { + $haveGroup |= $input->type == 'group'; + } + if (!$haveGroup) { + print "$prefix<ul>\n"; + } + } + + foreach ($inputs as $input) { + if ($input->type == 'group') { + print "$prefix<fieldset>\n"; + print "$prefix <legend>{$input->label}</legend>\n"; + print "$prefix <ul>\n"; + + DrawForm($input->inputs, $level + 2); + print "$prefix </ul>\n"; + + // Since hidden fields can only have name and value attributes lets just render it now + $hidden_prefix = "$prefix "; + foreach ($input->hidden as $hidden) { + print "$prefix {$hidden->render()}\n"; + } + print "$prefix</fieldset>\n"; + } else { + if ($input->error_messages()) { + print "$prefix<li class=\"gError\">\n"; + } else { + print "$prefix<li>\n"; + } + + if ($input->label()) { + print "$prefix {$input->label()}\n"; + } + print "$prefix {$input->render()}\n"; + if ($input->message()) { + print "$prefix <p>{$input->message()}</p>\n"; + } + if ($input->error_messages()) { + foreach ($input->error_messages() as $error_message) { + print "$prefix <p class=\"gError\">\n"; + print "$prefix $error_message\n"; + print "$prefix </p>\n"; + } + } + print "$prefix</li>\n"; + } + } + if ($level == 1 && !$haveGroup) { + print "$prefix</ul>\n"; + } + } +} +DrawForm($inputs); + +print($close); +?> diff --git a/modules/gallery/views/kohana_error_page.php b/modules/gallery/views/kohana_error_page.php new file mode 100644 index 00000000..d9bf9698 --- /dev/null +++ b/modules/gallery/views/kohana_error_page.php @@ -0,0 +1,118 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <style type="text/css"> + body { + background: #fff; + font-size: 14px; + line-height: 130%; + } + + div.big_box { + padding: 10px; + background: #eee; + border: solid 1px #ccc; + font-family: sans-serif; + color: #111; + width: 42em; + margin: 20px auto; + } + + div#framework_error { + text-align: center; + } + + div#error_details { + text-align: left; + } + + code { + font-family: monospace; + font-size: 12px; + margin: 20px; + color: #333; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + word-wrap: break-word; + } + + h3 { + font-family: sans-serif; + margin: 2px 0px 0px 0px; + padding: 8px 0px 0px 0px; + border-top: 1px solid #ddd; + } + + p { + padding: 0px; + margin: 0px 0px 10px 0px; + } + + li, pre { + padding: 0px; + margin: 0px; + } + </style> + <script src="<?= url::file("lib/jquery.js") ?>" type="text/javascript"></script> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + <title><?= t("Something went wrong!") ?></title> + </head> + <body> + <? try { $user = user::active(); } catch (Exception $e) { } ?> + <? $admin = isset($user) && $user->admin ?> + <div class="big_box" id="framework_error"> + <h1> + <?= t("Dang... Something went wrong!") ?> + </h1> + <h2> + <?= t("We tried really hard, but it's broken.") ?> + </h2> + <? if (!$admin): ?> + <p> + <?= t("Talk to your Gallery administrator for help fixing this!") ?> + </p> + <? endif ?> + </div> + <? if ($admin): ?> + <div class="big_box" id="error_details"> + <h2> + <?= t("Hey wait, you're an admin! We can tell you stuff.") ?> + </h2> + <a id="toggle" href="" + onclick="javascript:$('#stuff').slideDown('slow'); $('#toggle').slideUp(); return false"> + <b><?= t("Ok.. tell me stuff!") ?></b> + </a> + <div id="stuff" style="display: none"> + <? if (!empty($line) and !empty($file)): ?> + <div id="summary"> + <h3> + <?= t("Help!") ?> + </h3> + <p> + <?= t("If this stuff doesn't make any sense to you, <a href=\"%url\">ask for help in the Gallery forums</a>!", array("url" => "http://gallery.menalto.com/forum/96")) ?> + </p> + <h3> + <?= t("So here's the error:") ?> + </h3> + + <code class="block"><?= $message ?></code> + <p> + <?= t("File: <b>%file</b>, line: <b>%line</b>", array("file" => $file, "line" => $line)) ?> + </p> + </div> + <? endif ?> + + <? $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace(); ?> + <? $trace = Kohana::backtrace($trace); ?> + <? if (!empty($trace)): ?> + <div id="stack_trace"> + <h3> + <?= t("And here's how we got there:") ?> + </h3> + <?= $trace ?> + <? endif ?> + </div> + </div> + <? endif ?> + </body> +</html> diff --git a/modules/gallery/views/kohana_profiler.php b/modules/gallery/views/kohana_profiler.php new file mode 100644 index 00000000..c7534349 --- /dev/null +++ b/modules/gallery/views/kohana_profiler.php @@ -0,0 +1,35 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<style type="text/css"> + #kohana-profiler { + background-color: #F8FFF8; + border: 1px solid #E5EFF8; + clear: both; + font-family: Monaco, 'Courier New'; + margin-top: 20px; + padding: 10px 10px 0; + text-align: left; + } + #kohana-profiler pre { + font: inherit; + margin: 0; + } + #kohana-profiler .kp-meta { + background: #fff; + border: 1px solid #E5EFF8; + color: #A6B0B8; + margin: 0 0 10px; + padding: 4px; + text-align: center; + } + #kohana-profiler td { + padding-right: 1em; + } + <? echo $styles ?> +</style> + +<div id="kohana-profiler"> + <? foreach ($profiles as $profile): ?> + <?= $profile->render(); ?> + <? endforeach; ?> + <p class="kp-meta"><?= t("Profiler executed in ") . number_format($execution_time, 3) ?>s</p> +</div> diff --git a/modules/gallery/views/l10n_client.html.php b/modules/gallery/views/l10n_client.html.php new file mode 100644 index 00000000..8f4092c7 --- /dev/null +++ b/modules/gallery/views/l10n_client.html.php @@ -0,0 +1,31 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<div id="l10n-client" class="hidden"> + <div class="labels"> + <span class="toggle"><?= t("Translate Text") ?></span> + <div class="label strings"><h2><?= t("Page Text") ?></h2></div> + <div class="label source"><h2><?= t("Source") ?></div> + <div class="label translation"><h2><?= t("Translation to %language", + array("language" => locale::display_name())) ?></h2></div> + </div> + <div id="l10n-client-string-select"> + <ul class="string-list"> + <? foreach ($string_list as $string): ?> + <li class="<?= $string["translation"] === "" ? "untranslated" : "translated" ?>"> + <?= $string["source"] ?> + </li> + <? endforeach; ?> + </ul> + <?= $l10n_search_form ?> + </div> + <div id="l10n-client-string-editor"> + <div class="source"> + <div class="source-text"></div> + </div> + <div class="translation"> + <?= $l10n_form ?> + </div> + </div> + <script type="text/javascript"> + var l10n_client_data = <?= json_encode($string_list) ?>; + </script> +</div> diff --git a/modules/gallery/views/maintenance.html.php b/modules/gallery/views/maintenance.html.php new file mode 100644 index 00000000..f80b6e7a --- /dev/null +++ b/modules/gallery/views/maintenance.html.php @@ -0,0 +1,50 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<html> + <head> + <title> + <?= t("Gallery - Maintenance Mode") ?> + </title> + <style> + body { + background: #ccc; + } + form { + border: 1px solid #555; + background: #999; + width: 300px; + } + fieldset { + border: none; + } + fieldset legend { + font-size: 24px; + display: none !important; + padding-left: 0px; + } + ul { + list-style-type: none; + margin-top: 0px; + padding-left: 0px; + bullet-style: none; + } + ul li { + margin-left: 0px; + } + label { + width: 60px; + display: block; + } + </style> + </head> + <body> + <h1> + <?= t("Gallery - Maintenance Mode") ?> + </h1> + <p> + <?= t("This site is currently only accessible by site administrators.") ?> + </p> + <?= user::get_login_form("login/auth_html") ?> + </body> +</html> + + diff --git a/modules/gallery/views/move_browse.html.php b/modules/gallery/views/move_browse.html.php new file mode 100644 index 00000000..4f69c0e9 --- /dev/null +++ b/modules/gallery/views/move_browse.html.php @@ -0,0 +1,47 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + var load_tree = function(target_id, locked) { + var load_url = "<?= url::site("move/show_sub_tree/{$source->id}/__TARGETID__") ?>"; + var node = $("#node_" + target_id); + $("#gMove .node a").removeClass("selected"); + node.find("a:first").addClass("selected"); + if (locked) { + $("#gMoveButton").attr("disabled", "disabled"); + $("#gMove form input[name=target_id]").attr("value", ""); + } else { + $("#gMoveButton").removeAttr("disabled"); + $("#gMove form input[name=target_id]").attr("value", target_id); + } + var sub_tree = $("#tree_" + target_id); + if (sub_tree.length) { + sub_tree.toggle(); + } else { + $.get(load_url.replace("__TARGETID__", target_id), {}, + function(data) { + node.html(data); + node.find("a:first").addClass("selected"); + }); + } + } +</script> +<h1 style="display: none"> + <? if ($source->type == "photo"): ?> + <? t("Move this photo to a new album") ?> + <? elseif ($source->type == "movie"): ?> + <? t("Move this movie to a new album") ?> + <? elseif ($source->type == "album"): ?> + <? t("Move this album to a new album") ?> + <? endif ?> +</h1> +<div id="gMove"> + <ul id="tree_0"> + <li id="node_1" class="node"> + <?= $tree ?> + </li> + </ul> + <form method="post" action="<?= url::site("move/save/$source->id") ?>"> + <?= access::csrf_form_field() ?> + <input type="hidden" name="target_id" value="" /> + <input type="submit" id="gMoveButton" value="<?= t("Move") ?>" disabled="disabled"/> + </form> +</div> diff --git a/modules/gallery/views/move_tree.html.php b/modules/gallery/views/move_tree.html.php new file mode 100644 index 00000000..a3a4bc8f --- /dev/null +++ b/modules/gallery/views/move_tree.html.php @@ -0,0 +1,19 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<?= $parent->thumb_tag(array(), 25); ?> +<? if (!access::can("edit", $parent) || $source->is_descendant($parent)): ?> +<a href="javascript:load_tree('<?= $parent->id ?>',1)"> <?= $parent->title ?> <?= t("(locked)") ?> </a> +<? else: ?> +<a href="javascript:load_tree('<?= $parent->id ?>',0)"> <?= $parent->title ?></a> +<? endif ?> +<ul id="tree_<?= $parent->id ?>"> + <? foreach ($children as $child): ?> + <li id="node_<?= $child->id ?>" class="node"> + <?= $child->thumb_tag(array(), 25); ?> + <? if (!access::can("edit", $child) || $source->is_descendant($child)): ?> + <a href="javascript:load_tree('<?= $child->id ?>',1)"> <?= $child->title ?> <?= t("(locked)") ?></a> + <? else: ?> + <a href="javascript:load_tree('<?= $child->id ?>',0)"> <?= $child->title ?> </a> + <? endif ?> + </li> + <? endforeach ?> +</ul> diff --git a/modules/gallery/views/permissions_browse.html.php b/modules/gallery/views/permissions_browse.html.php new file mode 100644 index 00000000..afd87c2b --- /dev/null +++ b/modules/gallery/views/permissions_browse.html.php @@ -0,0 +1,56 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript"> + var form_url = "<?= url::site("permissions/form/__ITEM__") ?>"; + show = function(id) { + $.ajax({ + url: form_url.replace("__ITEM__", id), + success: function(data) { + $("div.form").slideUp(); + $("div#edit-" + id).html(data).slideDown(); + } + }); + } + + var action_url = + "<?= url::site("permissions/change/__CMD__/__GROUP__/__PERM__/__ITEM__?csrf=$csrf") ?>"; + set = function(cmd, group_id, perm_id, item_id) { + $.ajax({ + url: action_url.replace("__CMD__", cmd).replace("__GROUP__", group_id). + replace("__PERM__", perm_id).replace("__ITEM__", item_id), + success: function(data) { + $("div#edit-" + item_id).load(form_url.replace("__ITEM__", item_id)); + } + }); + } +</script> +<div id="gPermissions"> + <? if (!$htaccess_works): ?> + <ul id="gMessage"> + <li class="gError"> + <?= t("Oh no! Your server needs a configuration change in order for you to hide photos! Ask your server administrator to set <a href=\"%url\"><i>AllowOverride FileInfo Options</i></a> to fix this.", array("url" => "http://httpd.apache.org/docs/2.0/mod/core.html#allowoverride")) ?> + </li> + </ul> + <? endif ?> + <ul> + <? foreach ($parents as $parent): ?> + <li> + <a href="javascript:show(<?= $parent->id ?>)"> + <?= $parent->title ?> + </a> + <div class="form" id="edit-<?= $parent->id ?>"></div> + <ul> + <? endforeach ?> + <li> + <a href="javascript:show(<?= $item->id ?>)"> + <?= $item->title ?> + </a> + <div class="form" id="edit-<?= $item->id ?>"> + <?= $form ?> + </div> + </li> + <? foreach ($parents as $parent): ?> + </ul> + </li> + </ul> + <? endforeach ?> +</div> diff --git a/modules/gallery/views/permissions_form.html.php b/modules/gallery/views/permissions_form.html.php new file mode 100644 index 00000000..3dbd0d98 --- /dev/null +++ b/modules/gallery/views/permissions_form.html.php @@ -0,0 +1,94 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<form method="post" action="<?= url::site('permissions/edit/$item->id') ?>"> + <?= access::csrf_form_field() ?> + <fieldset> + <legend> <?= t('Edit Permissions') ?> </legend> + + <table> + <tr> + <th> </th> + <? foreach ($groups as $group): ?> + <th> <?= $group->name ?> </th> + <? endforeach ?> + </tr> + + <? foreach ($permissions as $permission): ?> + <tr> + <td> <?= t($permission->display_name) ?> </td> + <? foreach ($groups as $group): ?> + <? $intent = access::group_intent($group, $permission->name, $item) ?> + <? $allowed = access::group_can($group, $permission->name, $item) ?> + <? $lock = access::locked_by($group, $permission->name, $item) ?> + + <? if ($lock): ?> + <td class="gDenied"> + <img src="<?= url::file('themes/default/images/ico-denied.png') ?>" title="<?= t('denied and locked through parent album') ?>" alt="<?= t('denied icon') ?>" /> + <a href="javascript:show(<?= $lock->id ?>)" title="<?= t('click to go to parent album') ?>"> + <img src="<?= url::file('themes/default/images/ico-lock.png') ?>" alt="<?= t('locked icon') ?>" /> + </a> + </td> + <? else: ?> + <? if ($intent === null): ?> + <? if ($allowed): ?> + <td class="gAllowed"> + <a href="javascript:set('allow',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('allowed through parent album, click to allow explicitly') ?>"> + <img src="<?= url::file('themes/default/images/ico-success-pale.png') ?>" alt="<?= t('passive allowed icon') ?>" /> + </a> + <a href="javascript:set('deny',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('click to deny') ?>"> + <img src="<?= url::file('themes/default/images/ico-denied-gray.png') ?>" alt="<?= t('inactive denied icon') ?>" /> + </a> + </td> + <? else: ?> + <td class="gDenied"> + <a href="javascript:set('allow',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('click to allow') ?>"> + <img src="<?= url::file('themes/default/images/ico-success-gray.png') ?>" alt="<?= t('inactive allowed icon') ?>" /> + </a> + <a href="javascript:set('deny',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('denied through parent album, click to deny explicitly') ?>"> + <img src="<?= url::file('themes/default/images/ico-denied-pale.png') ?>" alt="<?= t('passive denied icon') ?>" /> + </a> + </td> + <? endif ?> + + <? elseif ($intent === access::DENY): ?> + <td class="gDenied"> + <a href="javascript:set('allow',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('click to allow') ?>"> + <img src="<?= url::file('themes/default/images/ico-success-gray.png') ?>" alt="<?= t('inactive allowed icon') ?>" /> + </a> + <? if ($item->id == 1): ?> + <img src="<?= url::file('themes/default/images/ico-denied.png') ?>" alt="<?= t('denied icon') ?>" title="<?= t('denied') ?>"/> + <? else: ?> + <a href="javascript:set('reset',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('denied, click to reset') ?>"> + <img src="<?= url::file('themes/default/images/ico-denied.png') ?>" alt="<?= t('denied icon') ?>" /> + </a> + <? endif ?> + </td> + <? elseif ($intent === access::ALLOW): ?> + <td class="gAllowed"> + <? if ($item->id == 1): ?> + <img src="<?= url::file('themes/default/images/ico-success.png') ?>" title="allowed" alt="<?= t('allowed icon') ?>" /> + <? else: ?> + <a href="javascript:set('reset',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('allowed, click to reset') ?>"> + <img src="<?= url::file('themes/default/images/ico-success.png') ?>" alt="<?= t('allowed icon') ?>" /> + </a> + <? endif ?> + <a href="javascript:set('deny',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" + title="<?= t('click to deny') ?>"> + <img src="<?= url::file('themes/default/images/ico-denied-gray.png') ?>" alt="<?= t('inactive denied icon') ?>" /> + </a> + </td> + <? endif ?> + <? endif ?> + </td> + <? endforeach ?> + </tr> + <? endforeach ?> + </table> + </fieldset> +</form> diff --git a/modules/gallery/views/quick_pane.html.php b/modules/gallery/views/quick_pane.html.php new file mode 100644 index 00000000..95de972b --- /dev/null +++ b/modules/gallery/views/quick_pane.html.php @@ -0,0 +1,108 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<? if ($item->type == "photo"): ?> +<? $title = t("Edit this photo") ?> +<? elseif ($item->type == "movie"): ?> +<? $title = t("Edit this movie") ?> +<? elseif ($item->type == "album"): ?> +<? $title = t("Edit this album") ?> +<? endif ?> +<a class="gDialogLink gButtonLink ui-corner-all ui-state-default" href="<?= url::site("quick/form_edit/$item->id?page_type=$page_type") ?>" + title="<?= $title ?>"> + <span class="ui-icon ui-icon-pencil"> + <?= $title ?> + </span> +</a> + +<? if ($item->is_photo() && graphics::can("rotate")): ?> +<a class="gButtonLink ui-corner-all ui-state-default" href="<?= url::site("quick/rotate/$item->id/ccw?csrf=$csrf&page_type=$page_type") ?>" + title="<?= t("Rotate 90 degrees counter clockwise") ?>"> + <span class="ui-icon ui-icon-rotate-ccw"> + <?= t("Rotate 90 degrees counter clockwise") ?> + </span> +</a> + +<a class="gButtonLink ui-corner-all ui-state-default" href="<?= url::site("quick/rotate/$item->id/cw?csrf=$csrf&page_type=$page_type") ?>" + title="<?= t("Rotate 90 degrees clockwise") ?>"> + <span class="ui-icon ui-icon-rotate-cw"> + <?= t("Rotate 90 degrees clockwise") ?> + </span> +</a> +<? endif ?> + +<? // Don't move photos from the photo page; we don't yet have a good way of redirecting after move ?> +<? if ($page_type == "album"): ?> +<? if ($item->type == "photo"): ?> +<? $title = t("Move this photo to another album") ?> +<? elseif ($item->type == "movie"): ?> +<? $title = t("Move this movie to another album") ?> +<? elseif ($item->type == "album"): ?> +<? $title = t("Move this album to another album") ?> +<? endif ?> +<a class="gDialogLink gButtonLink ui-corner-all ui-state-default" href="<?= url::site("move/browse/$item->id") ?>" + title="<?= $title ?>"> + <span class="ui-icon ui-icon-folder-open"> + <?= $title ?> + </span> +</a> +<? endif ?> + +<? $disabledState = "" ?> +<? if (access::can("edit", $item->parent())): ?> +<? if ($item->type == "photo"): ?> +<? $title = t("Choose this photo as the album cover") ?> +<? elseif ($item->type == "movie"): ?> +<? $title = t("Choose this movie as the album cover") ?> +<? elseif ($item->type == "album"): ?> +<? if (empty($item->album_cover_item_id)): ?> +<? $disabledState = empty($item->album_cover_item_id) ? " ui-state-disabled" : "" ?> +<? endif ?> +<? $title = t("Choose this album as the album cover") ?> +<? endif ?> +<a class="gButtonLink ui-corner-all ui-state-default<?= $disabledState ?>" href="<?= url::site("quick/make_album_cover/$item->id?csrf=$csrf&page_type=$page_type") ?>" + title="<?= $title ?>"> + <span class="ui-icon ui-icon-star"> + <?= $title ?> + </span> +</a> + +<? if ($item->type == "photo"): ?> +<? $title = t("Delete this photo") ?> +<? $message = t("Do you really want to delete this photo") ?> +<? elseif ($item->type == "movie"): ?> +<? $title = t("Delete this movie") ?> +<? $message = t("Do you really want to delete this movie") ?> +<? elseif ($item->type == "album"): ?> +<? $title = t("Delete this album") ?> +<? $message = t("Do you really want to delete this album") ?> +<? endif ?> +<a class="gButtonLink ui-corner-all ui-state-default" href="<?= url::site("quick/delete/$item->id?csrf=$csrf&page_type=$page_type") ?>" ref="<?= $message ?>" id="gQuickDelete" title="<?= $title ?>"> + <span class="ui-icon ui-icon-trash"> + <?= $title ?> + </span> +</a> +<? endif ?> + +<? if ($item->is_album()): ?> +<a class="gButtonLink ui-corner-all ui-state-default options" href="#" title="<?= t("additional options") ?>"> + <span class="ui-icon ui-icon-triangle-1-s"> + <?= t("Additional options") ?> + </span> +</a> + +<ul id="gQuickPaneOptions" style="display: none"> + <li><a class="add_item gDialogLink" href="<?= url::site("simple_uploader/app/$item->id") ?>" + title="<?= t("Add a photo") ?>"> + <?= t("Add a photo") ?> + </a></li> + + <li><a class="add_album gDialogLink" href="<?= url::site("form/add/albums/$item->id?type=album") ?>" + title="<?= t("Add an album") ?>"> + <?= t("Add an album") ?> + </a></li> + + <li><a class="permissions gDialogLink" href="<?= url::site("permissions/browse/$item->id") ?>" + title="<?= t("Edit permissions") ?>"> + <?= t("Edit permissions") ?> + </a></li> +</ul> +<? endif ?> diff --git a/modules/gallery/views/scaffold.html.php b/modules/gallery/views/scaffold.html.php new file mode 100644 index 00000000..765464b5 --- /dev/null +++ b/modules/gallery/views/scaffold.html.php @@ -0,0 +1,169 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<html> + <head> + <title>Gallery3 Scaffold</title> + <style> + body { + background: #999; + font-family: Trebuchet MS; + } + + div.outer { + width: 650px; + background: white; + border: 1px solid black; + margin: 0 auto; + padding: -10px; + } + + div.inner { + padding: 0 1em 0 1em; + margin: 0px; + } + + h1, h2, h3 { + margin-bottom: .1em; + } + + p { + margin: 0 0 0 0; + padding-left: 1em; + } + + table { + padding-left: 1em; + } + + pre { + border: 1px solid #666; + margin: 1em 0; + padding: .5em; + overflow: scroll; + } + + .error { + color: red; + } + + .success { + color: green; + } + + p.success { + font-weight: bold; + } + + div.block { + padding: 0px; + margin: 0px; + } + + ul { + margin-top: -.25em; + } + + fieldset { + margin-left: 1em; + margin-bottom: 1em; + padding-bottom: 0; + } + + a { + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + a.allowed { + color: green; + font-size: 110%; + } + + a.denied { + color: red; + font-size: 90%; + } + + .gHide { + display: none; + } + + div#browse { + border: 1px solid black; + background: #eee; + width: 450px; + padding: 2px; + margin: 5px 0px 0px 1em; + } + </style> + </head> + <body> + <div class="outer"> + <center> + <img src="<?= url::file("core/images/gallery.png") ?>"/> + </center> + <div class="inner"> + <h1>Gallery3 Scaffold</h1> + <p> + This is + a <b><a href="http://www.google.com/images?q=scaffold">scaffold</a></b>: + a <i>temporary structure built to support the developers as + they create the real product</i>. As we flesh out Gallery 3, + we'll make it possible for you to peer inside and see the + application taking shape. Eventually, this page will go + away and you'll start in the application itself. In the + meantime, here are some useful links to get you started. + </p> + + <? if ($album_count > 0): ?> + <div id="browse"> + <p> + <?= html::anchor("albums/1", "Browse Gallery") ?> + <i>(<?= $album_count ?> albums, <?= $photo_count ?> photos, <?= $comment_count ?> comments, <?= $tag_count ?> tags)</i> + </p> + </div> + <? endif ?> + + <div id="actions" class="activity"> + <fieldset> + <legend>Generate Test Data</legend> + <p> + add: [ + <? foreach (array(1, 10, 50, 100, 500, 1000) as $count): ?> + <?= html::anchor("scaffold/add_albums_and_photos/$count", "$count") ?> + <? endforeach ?> + ] photos and albums + </p> + <p> + add: [ + <? foreach (array(1, 10, 50, 100, 500, 1000) as $count): ?> + <?= html::anchor("scaffold/add_albums_and_photos/$count/album", "$count") ?> + <? endforeach ?> + ] albums only + </p> + <p> + add: [ + <? foreach (array(1, 10, 50, 100, 500, 1000) as $count): ?> + <?= html::anchor("scaffold/add_comments/$count", "$count") ?> + <? endforeach ?> + ] comments + </p> + <p> + add: [ + <? foreach (array(1, 10, 50, 100, 500, 1000) as $count): ?> + <?= html::anchor("scaffold/add_tags/$count", "$count") ?> + <? endforeach ?> + ] tags + </p> + </fieldset> + <fieldset> + <legend>Packaging</legend> + <a href="<?= url::site("scaffold/package") ?>">Make Package</a> + </fieldset> + </div> + </div> + </div> + </body> +</html> diff --git a/modules/gallery/views/simple_uploader.html.php b/modules/gallery/views/simple_uploader.html.php new file mode 100644 index 00000000..b6725c31 --- /dev/null +++ b/modules/gallery/views/simple_uploader.html.php @@ -0,0 +1,249 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<script type="text/javascript" src="<?= url::file("lib/swfupload/swfupload.js") ?>"></script> +<script type="text/javascript" src="<?= url::file("lib/swfupload/swfupload.queue.js") ?>"></script> + +<!-- hack to set the title for the dialog --> +<form id="gAddPhotosForm" action="<?= url::site("simple_uploader/finish") ?>"> + <fieldset> + <legend> <?= t("Add photos to %album_title", array("album_title" => $item->title)) ?> </legend> + </fieldset> +</form> + +<div id="gAddPhotos"> + <? if (ini_get("suhosin.session.encrypt")): ?> + <ul id="gMessage"> + <li class="gError"> + <?= t("Error: your server is configured to use the <a href=\"%encrypt_url\"><code>suhosin.session.encrypt</code></a> setting from <a href=\"%suhosin_url\">Suhosin</a>. 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/")) ?> + </li> + </ul> + <? endif ?> + + <p> + <?= t("Photos will be uploaded to album: ") ?> + </p> + <ul class="gBreadcrumbs"> + <? foreach ($item->parents() as $parent): ?> + <li> <?= $parent->title ?> </li> + <? endforeach ?> + <li class="active"> <?= $item->title ?> </li> + </ul> + + <p><?= t("Upload Queue") ?></p> + <div id="gAddPhotosCanvas" style="text-align: center;"> + <div id="gAddPhotosQueue"></div> + <div id="gEditPhotosQueue"></div> + <span id="gChooseFilesButtonPlaceholder"></span> + </div> + <button id="gUploadCancel" class="ui-state-default ui-corner-all" type="button" + onclick="swfu.cancelQueue();" + disabled="disabled"> + <?= t("Cancel all") ?> + </button> + + <!-- Proxy the done request back to our form, since its been ajaxified --> + <button class="ui-state-default ui-corner-all" onclick="$('#gAddPhotosForm').submit()"> + <?= t("Done") ?> + </button> +</div> + +<style> + #SWFUpload_0 { + margin-top: 100px; + } + #gAddPhotos .gBreadcrumbs { + border: 0; + margin: 0; + padding-left:10px; + } + #gAddPhotosCanvas { + border: 1px solid #CCCCCC; + margin: .5em 0 .5em 0; + width: 469px; + } + #gAddPhotos button { + margin-bottom: .5em; + float: right; + } + #gAddPhotos #gUploadCancel { + float: left; + } +</style> + +<script type="text/javascript"> + var swfu = new SWFUpload({ + flash_url : "<?= url::file("lib/swfupload/swfupload.swf") ?>", + upload_url: "<?= url::site("simple_uploader/add_photo/$item->id") ?>", + post_params: { + "g3sid": "<?= Session::instance()->id() ?>", + "user_agent": "<?= Input::instance()->server("HTTP_USER_AGENT") ?>", + "csrf": "<?= $csrf ?>" + }, + file_size_limit : "100 MB", + file_types : "*.gif;*.jpg;*.jpeg;*.png;*.flv;*.mp4;*.GIF;*.JPG;*.JPEG;*.PNG;*.FLV;*.MP4", + file_types_description : "<?= t("Photos and Movies") ?>", + file_upload_limit : 1000, + file_queue_limit : 0, + custom_settings : { }, + debug: false, + + // Button settings + button_image_url: "<?= url::file("themes/default/images/select-photos-backg.png") ?>", + button_width: "202", + button_height: "45", + button_placeholder_id: "gChooseFilesButtonPlaceholder", + button_text: '<span class="swfUploadFont">Select photos...</span>', + button_text_style: ".swfUploadFont { color: #2E6E9E; font-size: 16px; font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; font-weight: bold; }", + button_text_left_padding: 30, + button_text_top_padding: 10, + + // The event handler functions are defined in handlers.js + file_queued_handler : file_queued, + file_queue_error_handler : file_queue_error, + file_dialog_complete_handler : file_dialog_complete, + upload_start_handler : upload_start, + upload_progress_handler : upload_progress, + upload_error_handler : upload_error, + upload_success_handler : upload_success, + upload_complete_handler : upload_complete, + queue_complete_handler : queue_complete + }); + + // @todo add support for cancelling individual uploads + function File_Progress(file) { + this.box = $("#" + file.id); + if (!this.box.length) { + $("#gAddPhotosQueue").append( + "<div class=\"box\" id=\"" + file.id + "\">" + + "<div class=\"title\"></div>" + + "<div class=\"status\"></div>" + + "<div class=\"progressbar\"></div></div>"); + this.box = $("#" + file.id); + } + this.title = this.box.find(".title"); + this.status = this.box.find(".status"); + this.progress_bar = this.box.find(".progressbar"); + this.progress_bar.progressbar(); + this.progress_bar.css("visibility", "hidden"); + } + + File_Progress.prototype.set_status = function(status_class, msg) { + this.box.removeClass("pending error uploading complete").addClass(status_class); + this.status.html(msg); + } + + function file_queued(file) { + var fp = new File_Progress(file); + fp.title.html(file.name); + fp.set_status("pending", "<?= t("Pending...") ?>"); + // @todo add cancel button to call this.cancelUpload(file.id) + } + + function file_queue_error(file, error_code, message) { + if (error_code === SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED) { + alert("<?= t("You have attempted to queue too many files.") ?>"); + return; + } + + var fp = new File_Progress(file); + switch (error_code) { + case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT: + fp.set_status("error", "<?= t("File is too big.") ?>"); + break; + case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE: + fp.set_status("error", "<?= t("Cannot upload empty files.") ?>"); + break; + case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE: + fp.set_status("error", "<?= t("Invalid file type.") ?>"); + break; + default: + if (file !== null) { + fp.set_status("error", "<?= t("Unknown error") ?>"); + } + break; + } + } + + function file_dialog_complete(num_files_selected, num_files_queued) { + if (num_files_selected > 0) { + $("#gUploadCancel").enable(true); + } + + // Auto start the upload + this.startUpload(); + } + + function upload_start(file) { + // Do all file validation on the server side. Update the UI here because in Linux + // no uploadProgress events are called (limitation in the Linux Flash VM). + var fp = new File_Progress(file); + fp.title.html(file.name); + fp.set_status("uploading", "<?= t("Uploading...") ?>"); + return true; + // @todo add cancel button to call this.cancelUpload(file.id) + } + + function upload_progress(file, bytes_loaded, bytes_total) { + var percent = Math.ceil((bytes_loaded / bytes_total) * 100); + var fp = new File_Progress(file); + fp.set_status("uploading", "<?= t("Uploading...") ?>"); + fp.progress_bar.css("visibility", "visible"); + fp.progress_bar.progressbar("value", percent); + } + + function upload_success(file, serverData) { + var fp = new File_Progress(file); + fp.progress_bar.progressbar("value", 100); + fp.set_status("complete", "<?= t("Complete.") ?>"); + } + + function upload_error(file, error_code, message) { + var fp = new File_Progress(file); + switch (error_code) { + case SWFUpload.UPLOAD_ERROR.HTTP_ERROR: + fp.set_status("error", "<?= t("Upload error: ") ?>" + message); + break; + case SWFUpload.UPLOAD_ERROR.UPLOAD_FAILED: + fp.set_status("error", "<?= t("Upload failed") ?>"); + break; + case SWFUpload.UPLOAD_ERROR.IO_ERROR: + fp.set_status("error", "<?= t("Server error") ?>"); + break; + case SWFUpload.UPLOAD_ERROR.SECURITY_ERROR: + fp.set_status("error", "<?= t("Security error") ?>"); + break; + case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED: + fp.set_status("error", "<?= t("Upload limit exceeded") ?>"); + break; + case SWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED: + fp.set_status("error", "<?= t("Failed validation. File skipped") ?>"); + break; + case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED: + // If there aren't any files left (they were all cancelled) disable the cancel button + if (this.getStats().files_queued === 0) { + $("#gUploadCancel").enable(false); + } + fp.set_status("error", "<?= t("Cancelled") ?>"); + break; + case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED: + fp.set_status("error", "<?= t("Stopped") ?>"); + break; + default: + fp.set_status("error", "<?= t("Unknown error: ") ?>" + error_code); + break; + } + } + + function upload_complete(file) { + if (this.getStats().files_queued === 0) { + $("#gUploadCancel").enable(false); + } + } + + // This event comes from the Queue Plugin + function queue_complete(num_files_uploaded) { + var status_msg = "<?= t("Uploaded: __COUNT__") ?>"; + $("#gUploadStatus").html(status_msg.replace("__COUNT__", num_files_uploaded)); + } +</script> |