summaryrefslogtreecommitdiff
path: root/modules/gallery
diff options
context:
space:
mode:
authorTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
committerTristan Zur <tzur@webserver.ccwn.org>2015-06-10 20:55:53 +0200
commit406abd7c4df1ace2cd3e4e17159e8941a2e8c0c4 (patch)
treea324be16021f44f2fd6d55e609f47024e945b1db /modules/gallery
Initial import
Diffstat (limited to 'modules/gallery')
-rw-r--r--modules/gallery/config/cache.php50
-rw-r--r--modules/gallery/config/cookie.php48
-rw-r--r--modules/gallery/config/database.php23
-rw-r--r--modules/gallery/config/locale.php51
-rw-r--r--modules/gallery/config/log_file.php29
-rw-r--r--modules/gallery/config/routes.php32
-rw-r--r--modules/gallery/config/session.php66
-rw-r--r--modules/gallery/config/upload.php36
-rw-r--r--modules/gallery/config/user_agents.php25
-rw-r--r--modules/gallery/controllers/admin.php94
-rw-r--r--modules/gallery/controllers/admin_advanced_settings.php57
-rw-r--r--modules/gallery/controllers/admin_dashboard.php97
-rw-r--r--modules/gallery/controllers/admin_graphics.php50
-rw-r--r--modules/gallery/controllers/admin_languages.php135
-rw-r--r--modules/gallery/controllers/admin_maintenance.php243
-rw-r--r--modules/gallery/controllers/admin_modules.php119
-rw-r--r--modules/gallery/controllers/admin_movies.php72
-rw-r--r--modules/gallery/controllers/admin_sidebar.php69
-rw-r--r--modules/gallery/controllers/admin_theme_options.php120
-rw-r--r--modules/gallery/controllers/admin_themes.php80
-rw-r--r--modules/gallery/controllers/admin_upgrade_checker.php57
-rw-r--r--modules/gallery/controllers/albums.php207
-rw-r--r--modules/gallery/controllers/combined.php96
-rw-r--r--modules/gallery/controllers/file_proxy.php174
-rw-r--r--modules/gallery/controllers/items.php43
-rw-r--r--modules/gallery/controllers/l10n_client.php179
-rw-r--r--modules/gallery/controllers/login.php90
-rw-r--r--modules/gallery/controllers/logout.php29
-rw-r--r--modules/gallery/controllers/movies.php91
-rw-r--r--modules/gallery/controllers/packager.php191
-rw-r--r--modules/gallery/controllers/permissions.php91
-rw-r--r--modules/gallery/controllers/photos.php91
-rw-r--r--modules/gallery/controllers/quick.php144
-rw-r--r--modules/gallery/controllers/reauthenticate.php105
-rw-r--r--modules/gallery/controllers/upgrader.php118
-rw-r--r--modules/gallery/controllers/uploader.php133
-rw-r--r--modules/gallery/controllers/user_profile.php108
-rw-r--r--modules/gallery/controllers/welcome_message.php30
-rw-r--r--modules/gallery/css/debug.css28
-rw-r--r--modules/gallery/css/gallery.css213
-rw-r--r--modules/gallery/css/l10n_client.css206
-rw-r--r--modules/gallery/css/upgrader.css183
-rw-r--r--modules/gallery/helpers/MY_html.php91
-rw-r--r--modules/gallery/helpers/MY_num.php54
-rw-r--r--modules/gallery/helpers/MY_remote.php167
-rw-r--r--modules/gallery/helpers/MY_url.php92
-rw-r--r--modules/gallery/helpers/MY_valid.php26
-rw-r--r--modules/gallery/helpers/access.php758
-rw-r--r--modules/gallery/helpers/ajax.php31
-rw-r--r--modules/gallery/helpers/album.php126
-rw-r--r--modules/gallery/helpers/auth.php134
-rw-r--r--modules/gallery/helpers/batch.php40
-rw-r--r--modules/gallery/helpers/block_manager.php115
-rw-r--r--modules/gallery/helpers/data_rest.php115
-rw-r--r--modules/gallery/helpers/dir.php40
-rw-r--r--modules/gallery/helpers/encoding.php35
-rw-r--r--modules/gallery/helpers/gallery.php233
-rw-r--r--modules/gallery/helpers/gallery_block.php145
-rw-r--r--modules/gallery/helpers/gallery_error.php30
-rw-r--r--modules/gallery/helpers/gallery_event.php621
-rw-r--r--modules/gallery/helpers/gallery_graphics.php183
-rw-r--r--modules/gallery/helpers/gallery_installer.php844
-rw-r--r--modules/gallery/helpers/gallery_rss.php76
-rw-r--r--modules/gallery/helpers/gallery_task.php826
-rw-r--r--modules/gallery/helpers/gallery_theme.php151
-rw-r--r--modules/gallery/helpers/graphics.php546
-rw-r--r--modules/gallery/helpers/identity.php247
-rw-r--r--modules/gallery/helpers/item.php433
-rw-r--r--modules/gallery/helpers/item_rest.php210
-rw-r--r--modules/gallery/helpers/items_rest.php96
-rw-r--r--modules/gallery/helpers/json.php31
-rw-r--r--modules/gallery/helpers/l10n_client.php323
-rw-r--r--modules/gallery/helpers/l10n_scanner.php178
-rw-r--r--modules/gallery/helpers/legal_file.php310
-rw-r--r--modules/gallery/helpers/locales.php264
-rw-r--r--modules/gallery/helpers/log.php108
-rw-r--r--modules/gallery/helpers/message.php109
-rw-r--r--modules/gallery/helpers/model_cache.php42
-rw-r--r--modules/gallery/helpers/module.php594
-rw-r--r--modules/gallery/helpers/movie.php282
-rw-r--r--modules/gallery/helpers/photo.php145
-rw-r--r--modules/gallery/helpers/random.php48
-rw-r--r--modules/gallery/helpers/site_status.php132
-rw-r--r--modules/gallery/helpers/system.php113
-rw-r--r--modules/gallery/helpers/task.php113
-rw-r--r--modules/gallery/helpers/theme.php113
-rw-r--r--modules/gallery/helpers/tree_rest.php92
-rw-r--r--modules/gallery/helpers/upgrade_checker.php105
-rw-r--r--modules/gallery/helpers/user_profile.php55
-rw-r--r--modules/gallery/helpers/xml.php35
-rw-r--r--modules/gallery/hooks/init_gallery.php56
-rw-r--r--modules/gallery/images/ffmpeg.pngbin0 -> 2888 bytes
-rw-r--r--modules/gallery/images/gallery.pngbin0 -> 22178 bytes
-rw-r--r--modules/gallery/images/gd.pngbin0 -> 5531 bytes
-rw-r--r--modules/gallery/images/graphicsmagick.pngbin0 -> 1486 bytes
-rw-r--r--modules/gallery/images/imagemagick.jpgbin0 -> 20337 bytes
-rw-r--r--modules/gallery/images/missing_album_cover.jpgbin0 -> 4453 bytes
-rw-r--r--modules/gallery/images/missing_movie.jpgbin0 -> 3428 bytes
-rw-r--r--modules/gallery/images/missing_photo.jpgbin0 -> 2034 bytes
-rw-r--r--modules/gallery/js/albums_form_add.js25
-rw-r--r--modules/gallery/js/item_form_delete.js5
-rw-r--r--modules/gallery/js/l10n_client.js315
-rw-r--r--modules/gallery/libraries/Admin_View.php120
-rw-r--r--modules/gallery/libraries/Block.php30
-rw-r--r--modules/gallery/libraries/Breadcrumb.php70
-rw-r--r--modules/gallery/libraries/Form_Script.php66
-rw-r--r--modules/gallery/libraries/Form_Uploadify.php72
-rw-r--r--modules/gallery/libraries/Form_Uploadify_buttons.php25
-rw-r--r--modules/gallery/libraries/Gallery_I18n.php472
-rw-r--r--modules/gallery/libraries/Gallery_View.php243
-rw-r--r--modules/gallery/libraries/IdentityProvider.php283
-rw-r--r--modules/gallery/libraries/InPlaceEdit.php91
-rw-r--r--modules/gallery/libraries/MY_Database.php101
-rw-r--r--modules/gallery/libraries/MY_Forge.php45
-rw-r--r--modules/gallery/libraries/MY_Input.php31
-rw-r--r--modules/gallery/libraries/MY_Kohana_Exception.php101
-rw-r--r--modules/gallery/libraries/MY_ORM.php52
-rw-r--r--modules/gallery/libraries/MY_View.php85
-rw-r--r--modules/gallery/libraries/Menu.php257
-rw-r--r--modules/gallery/libraries/ORM_MPTT.php341
-rw-r--r--modules/gallery/libraries/SafeString.php162
-rw-r--r--modules/gallery/libraries/Sendmail.php98
-rw-r--r--modules/gallery/libraries/Task_Definition.php50
-rw-r--r--modules/gallery/libraries/Theme_View.php271
-rw-r--r--modules/gallery/libraries/drivers/Cache/Database.php166
-rw-r--r--modules/gallery/libraries/drivers/IdentityProvider.php134
-rw-r--r--modules/gallery/models/access_cache.php21
-rw-r--r--modules/gallery/models/access_intent.php21
-rw-r--r--modules/gallery/models/cache.php20
-rw-r--r--modules/gallery/models/failed_auth.php20
-rw-r--r--modules/gallery/models/graphics_rule.php21
-rw-r--r--modules/gallery/models/incoming_translation.php21
-rw-r--r--modules/gallery/models/item.php1191
-rw-r--r--modules/gallery/models/log.php38
-rw-r--r--modules/gallery/models/message.php21
-rw-r--r--modules/gallery/models/module.php21
-rw-r--r--modules/gallery/models/outgoing_translation.php21
-rw-r--r--modules/gallery/models/permission.php21
-rw-r--r--modules/gallery/models/task.php86
-rw-r--r--modules/gallery/models/theme.php21
-rw-r--r--modules/gallery/models/var.php21
-rw-r--r--modules/gallery/module.info7
-rw-r--r--modules/gallery/vendor/joomla/crypt.php151
-rw-r--r--modules/gallery/views/admin_advanced_settings.html.php57
-rw-r--r--modules/gallery/views/admin_block_log_entries.html.php15
-rw-r--r--modules/gallery/views/admin_block_news.html.php11
-rw-r--r--modules/gallery/views/admin_block_photo_stream.html.php14
-rw-r--r--modules/gallery/views/admin_block_platform.html.php24
-rw-r--r--modules/gallery/views/admin_block_stats.html.php12
-rw-r--r--modules/gallery/views/admin_block_welcome.html.php20
-rw-r--r--modules/gallery/views/admin_dashboard.html.php43
-rw-r--r--modules/gallery/views/admin_graphics.html.php40
-rw-r--r--modules/gallery/views/admin_graphics_gd.html.php30
-rw-r--r--modules/gallery/views/admin_graphics_graphicsmagick.html.php21
-rw-r--r--modules/gallery/views/admin_graphics_imagemagick.html.php21
-rw-r--r--modules/gallery/views/admin_graphics_none.html.php8
-rw-r--r--modules/gallery/views/admin_languages.html.php118
-rw-r--r--modules/gallery/views/admin_maintenance.html.php212
-rw-r--r--modules/gallery/views/admin_maintenance_show_log.html.php19
-rw-r--r--modules/gallery/views/admin_maintenance_task.html.php84
-rw-r--r--modules/gallery/views/admin_modules.html.php129
-rw-r--r--modules/gallery/views/admin_modules_confirm.html.php22
-rw-r--r--modules/gallery/views/admin_movies.html.php44
-rw-r--r--modules/gallery/views/admin_sidebar.html.php64
-rw-r--r--modules/gallery/views/admin_sidebar_blocks.html.php5
-rw-r--r--modules/gallery/views/admin_theme_options.html.php7
-rw-r--r--modules/gallery/views/admin_themes.html.php98
-rw-r--r--modules/gallery/views/admin_themes_buttonset.html.php48
-rw-r--r--modules/gallery/views/admin_themes_preview.html.php8
-rw-r--r--modules/gallery/views/error.html.php12
-rw-r--r--modules/gallery/views/error_404.html.php26
-rw-r--r--modules/gallery/views/error_admin.html.php307
-rw-r--r--modules/gallery/views/error_cli.txt.php3
-rw-r--r--modules/gallery/views/error_user.html.php60
-rw-r--r--modules/gallery/views/form.html.php77
-rw-r--r--modules/gallery/views/form_uploadify.html.php167
-rw-r--r--modules/gallery/views/form_uploadify_buttons.html.php11
-rw-r--r--modules/gallery/views/in_place_edit.html.php21
-rw-r--r--modules/gallery/views/kohana/error.php46
-rw-r--r--modules/gallery/views/kohana_profiler.php35
-rw-r--r--modules/gallery/views/l10n_client.html.php82
-rw-r--r--modules/gallery/views/login_ajax.html.php52
-rw-r--r--modules/gallery/views/login_current_user.html.php7
-rw-r--r--modules/gallery/views/menu.html.php24
-rw-r--r--modules/gallery/views/menu_ajax_link.html.php10
-rw-r--r--modules/gallery/views/menu_dialog.html.php9
-rw-r--r--modules/gallery/views/menu_link.html.php9
-rw-r--r--modules/gallery/views/movieplayer.html.php50
-rw-r--r--modules/gallery/views/permissions_browse.html.php62
-rw-r--r--modules/gallery/views/permissions_form.html.php92
-rw-r--r--modules/gallery/views/quick_delete_confirm.html.php12
-rw-r--r--modules/gallery/views/reauthenticate.html.php15
-rw-r--r--modules/gallery/views/upgrade_checker_block.html.php55
-rw-r--r--modules/gallery/views/upgrader.html.php163
-rw-r--r--modules/gallery/views/user_languages_block.html.php19
-rw-r--r--modules/gallery/views/user_profile.html.php47
-rw-r--r--modules/gallery/views/user_profile_info.html.php9
-rw-r--r--modules/gallery/views/welcome_message.html.php36
-rw-r--r--modules/gallery/views/welcome_message_loader.html.php7
199 files changed, 21949 insertions, 0 deletions
diff --git a/modules/gallery/config/cache.php b/modules/gallery/config/cache.php
new file mode 100644
index 0000000..746e95c
--- /dev/null
+++ b/modules/gallery/config/cache.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Cache
+ *
+ * Cache settings, defined as arrays, or "groups". If no group name is
+ * used when loading the cache library, the group named "default" will be used.
+ *
+ * Each group can be used independently, and multiple groups can be used at once.
+ *
+ * Group Options:
+ * driver - Cache backend driver. Kohana comes with file, database, and memcache drivers.
+ * > File cache is fast and reliable, but requires many filesystem lookups.
+ * > Database cache can be used to cache items remotely, but is slower.
+ * > Memcache is very high performance, but prevents cache tags from being used.
+ *
+ * params - Driver parameters, specific to each driver.
+ *
+ * lifetime - Default lifetime of caches in seconds. By default caches are stored for
+ * thirty minutes. Specific lifetime can also be set when creating a new cache.
+ * Setting this to 0 will never automatically delete caches.
+ *
+ * requests - Average number of cache requests that will processed before all expired
+ * caches are deleted. This is commonly referred to as "garbage collection".
+ * Setting this to 0 or a negative number will disable automatic garbage collection.
+ */
+$config["default"] = array (
+ "driver" => "database",
+ "params" => null,
+ "lifetime" => 84600,
+ "requests" => 1000,
+ "prefix" => null,
+);
diff --git a/modules/gallery/config/cookie.php b/modules/gallery/config/cookie.php
new file mode 100644
index 0000000..b164701
--- /dev/null
+++ b/modules/gallery/config/cookie.php
@@ -0,0 +1,48 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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;
+
+/**
+ * Set the secure bit on the cookie if we're using HTTPS.
+ */
+$config['secure'] = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
+
+/**
+ * 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 0000000..4148757
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 0000000..cf37de9
--- /dev/null
+++ b/modules/gallery/config/locale.php
@@ -0,0 +1,51 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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. Set in 'Advanced' settings, falling back to the server's zone.
+ * @see http://php.net/timezones
+ */
+if (file_exists(VARPATH . "database.php")) {
+ $config['timezone'] = module::get_var("gallery", "timezone", date_default_timezone_get());
+} else {
+ // Gallery3 is not installed yet -- don't make module::get_var() calls.
+ $config['timezone'] = date_default_timezone_get();
+}
+
+// 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/log_file.php b/modules/gallery/config/log_file.php
new file mode 100644
index 0000000..a79831e
--- /dev/null
+++ b/modules/gallery/config/log_file.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+
+/**
+ * Message logging directory.
+ */
+$config['log_directory'] = VARPATH.'logs';
+
+/**
+ * Permissions of the log file
+ */
+$config['posix_permissions'] = 0644; \ 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 0000000..d1ae8bf
--- /dev/null
+++ b/modules/gallery/config/routes.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+
+// Admin controllers are not available, except via /admin
+$config["^admin_.*"] = null;
+
+// Redirect /form/add/admin/controller and /form/edit/admin/controller to
+// admin/controller/form_(add|edit)/parms. provides the same as below for admin pages
+$config["^form/(edit|add)/admin/(\w+)/(.*)$"] = "admin/$2/form_$1/$3";
+
+// Redirect /form/add and /form/edit to the module/form_(add|edit)/parms.
+$config["^form/(edit|add)/(\w+)/(.*)$"] = "$2/form_$1/$3";
+
+// Default page is the root album
+$config["_default"] = "albums";
diff --git a/modules/gallery/config/session.php b/modules/gallery/config/session.php
new file mode 100644
index 0000000..7ecee58
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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', 'expiration');
+
+/**
+ * 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 0000000..a5e8797
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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'] = FALSE; \ No newline at end of file
diff --git a/modules/gallery/config/user_agents.php b/modules/gallery/config/user_agents.php
new file mode 100644
index 0000000..dcdbb73
--- /dev/null
+++ b/modules/gallery/config/user_agents.php
@@ -0,0 +1,25 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+include(SYSPATH . "config/user_agents.php");
+$config["robot"]["mj12bot"] = "MJ12bot";
+$config["robot"]["speedy spider"] = "Speedy Spider";
+$config["robot"]["baidu"] = "Baiduspider";
+$config["robot"]["bing"] = "Ezooms";
+$config["robot"]["yandex"] = "YandexBot";
diff --git a/modules/gallery/controllers/admin.php b/modules/gallery/controllers/admin.php
new file mode 100644
index 0000000..b35a929
--- /dev/null
+++ b/modules/gallery/controllers/admin.php
@@ -0,0 +1,94 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 (!identity::active_user()->admin) {
+ if (identity::active_user()->guest) {
+ Session::instance()->set("continue_url", url::abs_current(true));
+ url::redirect("login");
+ } else {
+ access::forbidden();
+ }
+ }
+
+ parent::__construct();
+ }
+
+ public function __call($controller_name, $args) {
+ if (Input::instance()->get("reauth_check")) {
+ return self::_reauth_check();
+ }
+ if (auth::must_reauth_for_admin_area()) {
+ return self::_prompt_for_reauth($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 (!class_exists($controller_name) || !method_exists($controller_name, $method)) {
+ throw new Kohana_404_Exception();
+ }
+
+ call_user_func_array(array(new $controller_name, $method), $args);
+ }
+
+ private static function _reauth_check() {
+ $session = Session::instance();
+ $last_active_auth = $session->get("active_auth_timestamp", 0);
+ $last_admin_area_activity = $session->get("admin_area_activity_timestamp", 0);
+ $admin_area_timeout = module::get_var("gallery", "admin_area_timeout");
+
+ $time_remaining = max($last_active_auth, $last_admin_area_activity) +
+ $admin_area_timeout - time();
+
+ $result = new stdClass();
+ $result->result = "success";
+ if ($time_remaining < 30) {
+ message::success(t("Automatically logged out of the admin area for your security"));
+ $result->location = url::abs_site("");
+ }
+
+ json::reply($result);
+ }
+
+ private static function _prompt_for_reauth($controller_name, $args) {
+ if (request::method() == "get") {
+ // Avoid anti-phishing protection by passing the url as session variable.
+ Session::instance()->set("continue_url", url::abs_current(true));
+ }
+ // Save the is_ajax value as we lose it, if set, when we redirect
+ Session::instance()->set("is_ajax_request", request::is_ajax());
+ url::redirect("reauthenticate");
+ }
+}
+
diff --git a/modules/gallery/controllers/admin_advanced_settings.php b/modules/gallery/controllers/admin_advanced_settings.php
new file mode 100644
index 0000000..267ff14
--- /dev/null
+++ b/modules/gallery/controllers/admin_advanced_settings.php
@@ -0,0 +1,57 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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->page_title = t("Advanced settings");
+ $view->content = new View("admin_advanced_settings.html");
+ $view->content->vars = ORM::factory("var")
+ ->order_by("module_name")
+ ->order_by("name")
+ ->find_all();
+ print $view;
+ }
+
+ public function edit($module_name, $var_name) {
+ if (module::is_installed($module_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 setting"));
+ $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();
+
+ if (module::is_installed($module_name)) {
+ 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)));
+
+ json::reply(array("result" => "success"));
+ }
+ }
+}
diff --git a/modules/gallery/controllers/admin_dashboard.php b/modules/gallery/controllers/admin_dashboard.php
new file mode 100644
index 0000000..5317210
--- /dev/null
+++ b/modules/gallery/controllers/admin_dashboard.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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->page_title = t("Dashboard");
+ $view->content = new View("admin_dashboard.html");
+ $view->content->blocks = block_manager::get_html("dashboard_center");
+ $view->sidebar = "<div id=\"g-admin-dashboard-sidebar\">" .
+ block_manager::get_html("dashboard_sidebar") .
+ "</div>";
+ $view->content->obsolete_modules_message = module::get_obsolete_modules_message();
+ print $view;
+ }
+
+ public function add_block() {
+ access::verify_csrf();
+
+ $form = gallery_block::get_add_block_form();
+ if ($form->validate()) {
+ list ($module_name, $id) = explode(":", $form->add_block->id->value);
+ $available = block_manager::get_available_admin_blocks();
+
+ 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_admin_blocks();
+ $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 (Input::instance()->get($location, array()) as $id) {
+ $new_blocks[$id] = $active_set[$id];
+ }
+ block_manager::set_active($location, $new_blocks);
+ }
+ }
+}
diff --git a/modules/gallery/controllers/admin_graphics.php b/modules/gallery/controllers/admin_graphics.php
new file mode 100644
index 0000000..a24486f
--- /dev/null
+++ b/modules/gallery/controllers/admin_graphics.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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->page_title = t("Graphics settings");
+ $view->content = new View("admin_graphics.html");
+ $view->content->tk = graphics::detect_toolkits();
+ $view->content->active = module::get_var("gallery", "graphics_toolkit", "none");
+ print $view;
+ }
+
+ public function choose($toolkit_id) {
+ access::verify_csrf();
+
+ if ($toolkit_id != module::get_var("gallery", "graphics_toolkit")) {
+ $tk = graphics::detect_toolkits();
+ module::set_var("gallery", "graphics_toolkit", $toolkit_id);
+ module::set_var("gallery", "graphics_toolkit_path", $tk->$toolkit_id->dir);
+
+ site_status::clear("missing_graphics_toolkit");
+
+ $msg = t("Changed graphics toolkit to: %toolkit", array("toolkit" => $tk->$toolkit_id->name));
+ message::success($msg);
+ log::success("graphics", $msg);
+
+ module::event("graphics_toolkit_change", $toolkit_id);
+ }
+
+ 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 0000000..50ddc67
--- /dev/null
+++ b/modules/gallery/controllers/admin_languages.php
@@ -0,0 +1,135 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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->page_title = t("Languages and translations");
+ $v->content = new View("admin_languages.html");
+ $v->content->available_locales = locales::available();
+ $v->content->installed_locales = locales::installed();
+ $v->content->default_locale = module::get_var("gallery", "default_locale");
+
+ 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() {
+ access::verify_csrf();
+
+ $input = Input::instance();
+ locales::update_installed($input->post("installed_locales"));
+
+ $installed_locales = array_keys(locales::installed());
+ $new_default_locale = $input->post("default_locale");
+ if (!in_array($new_default_locale, $installed_locales)) {
+ if (!empty($installed_locales)) {
+ $new_default_locale = $installed_locales[0];
+ } else {
+ $new_default_locale = "en_US";
+ }
+ }
+ module::set_var("gallery", "default_locale", $new_default_locale);
+
+ json::reply(array("result" => "success"));
+ }
+
+ public function share() {
+ access::verify_csrf();
+
+ $form = $this->_share_translations_form();
+ if (!$form->validate()) {
+ // Show the page with form errors
+ return $this->index($form);
+ }
+
+ if (Input::instance()->post("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) {
+ list($connected, $valid) = l10n_client::validate_api_key($new_key);
+ if (!$valid) {
+ $form->sharing->api_key->add_error($connected ? "invalid" : "no_connection", 1);
+ }
+ } 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."));
+ } else if ($old_key && $new_key && $old_key == $new_key) {
+ message::info(t("Your API key was not changed."));
+ }
+
+ log::success(t("gallery"), t("l10n_client API key changed."));
+ url::redirect("admin/languages");
+ } else {
+ // Show the page with form errors
+ $this->index($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" => "g-share-translations-form"));
+ $group = $form->group("sharing")
+ ->label("Translations API Key");
+ $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::mark_clean(html::anchor($server_link))))
+ : t("API key"))
+ ->value($api_key)
+ ->error_messages("invalid", t("The API key you provided is invalid."))
+ ->error_messages(
+ "no_connection", t("Could not connect to remote server to validate the API key."));
+ $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 0000000..32f2078
--- /dev/null
+++ b/modules/gallery/controllers/admin_maintenance.php
@@ -0,0 +1,243 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 = db::build()
+ ->update("tasks")
+ ->set("state", "stalled")
+ ->where("done", "=", 0)
+ ->where("state", "<>", "stalled")
+ ->where(db::expr("UNIX_TIMESTAMP(NOW()) - `updated` > 15"))
+ ->execute();
+ $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" => html::mark_clean(url::site("admin/maintenance")))));
+ }
+
+ $view = new Admin_View("admin.html");
+ $view->page_title = t("Maintenance tasks");
+ $view->content = new View("admin_maintenance.html");
+ $view->content->task_definitions = task::get_definitions();
+ $view->content->running_tasks = ORM::factory("task")
+ ->where("done", "=", 0)->order_by("updated", "DESC")->find_all();
+ $view->content->finished_tasks = ORM::factory("task")
+ ->where("done", "=", 1)->order_by("updated", "DESC")->find_all();
+ print $view;
+
+ // Do some maintenance while we're in here
+ db::build()
+ ->delete("caches")
+ ->where("expiration", "<>", 0)
+ ->where("expiration", "<=", time())
+ ->execute();
+ module::deactivate_missing_modules();
+ }
+
+ /**
+ * Start a new task
+ * @param string $task_callback
+ */
+ public function start($task_callback) {
+ access::verify_csrf();
+
+ $task = task::start($task_callback);
+ $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("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;
+
+ $task->log(t("Task %task_name resumed (task id %task_id)",
+ array("task_name" => $task->name, "task_id" => $task->id)));
+ log::info("tasks", t("Task %task_name resumed (task id %task_id)",
+ array("task_name" => $task->name, "task_id" => $task->id)),
+ html::anchor("admin/maintenance", t("maintenance")));
+ print $view;
+ }
+
+ /**
+ * Show the task log
+ * @param string $task_id
+ */
+ public function show_log($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_show_log.html");
+ $view->task = $task;
+
+ print $view;
+ }
+
+ /**
+ * Save the task log
+ * @param string $task_id
+ */
+ public function save_log($task_id) {
+ access::verify_csrf();
+
+ $task = ORM::factory("task", $task_id);
+ if (!$task->loaded()) {
+ throw new Exception("@todo MISSING_TASK");
+ }
+
+ header("Content-Type: application/text");
+ header("Content-Disposition: filename=gallery3_task_log.txt");
+ print $task->get_log();
+ }
+
+ /**
+ * 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();
+ db::build()
+ ->update("tasks")
+ ->set("done", 1)
+ ->set("state", "cancelled")
+ ->where("done", "=", 0)
+ ->execute();
+ 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();
+
+ // Do it the long way so we can call delete and remove the cache.
+ $finished = ORM::factory("task")
+ ->where("done", "=", 1)
+ ->find_all();
+ foreach ($finished as $task) {
+ task::remove($task->id);
+ }
+ 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::add(
+ "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("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("admin/maintenance", t("maintenance")));
+ message::success(t("Task failed"));
+ break;
+ }
+ // Using sprintf("%F") to avoid comma as decimal separator.
+ json::reply(array("result" => "success",
+ "task" => array(
+ "percent_complete" => sprintf("%F", $task->percent_complete),
+ "status" => (string) $task->status,
+ "done" => (bool) $task->done),
+ "location" => url::site("admin/maintenance")));
+
+ } else {
+ json::reply(array("result" => "in_progress",
+ "task" => array(
+ "percent_complete" => sprintf("%F", $task->percent_complete),
+ "status" => (string) $task->status,
+ "done" => (bool) $task->done)));
+ }
+ }
+
+ public function maintenance_mode($value) {
+ access::verify_csrf();
+ module::set_var("gallery", "maintenance_mode", $value);
+ url::redirect("admin/maintenance");
+ }
+}
diff --git a/modules/gallery/controllers/admin_modules.php b/modules/gallery/controllers/admin_modules.php
new file mode 100644
index 0000000..177a925
--- /dev/null
+++ b/modules/gallery/controllers/admin_modules.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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() {
+ // If modules need upgrading, this will get recreated in module::available()
+ site_status::clear("upgrade_now");
+
+ $view = new Admin_View("admin.html");
+ $view->page_title = t("Modules");
+ $view->content = new View("admin_modules.html");
+ $view->content->available = module::available();
+ $view->content->obsolete_modules_message = module::get_obsolete_modules_message();
+ print $view;
+ }
+
+
+ public function confirm() {
+ access::verify_csrf();
+
+ $messages = array("error" => array(), "warn" => array());
+ $desired_list = array();
+ foreach (module::available() as $module_name => $info) {
+ if ($info->locked) {
+ continue;
+ }
+
+ if ($desired = Input::instance()->post($module_name) == 1) {
+ $desired_list[] = $module_name;
+ }
+ if ($info->active && !$desired && module::is_active($module_name)) {
+ $messages = array_merge($messages, module::can_deactivate($module_name));
+ } else if (!$info->active && $desired && !module::is_active($module_name)) {
+ $messages = array_merge($messages, module::can_activate($module_name));
+ }
+ }
+
+ if (empty($messages["error"]) && empty($messages["warn"])) {
+ $this->_do_save();
+ $result["reload"] = 1;
+ } else {
+ $v = new View("admin_modules_confirm.html");
+ $v->messages = $messages;
+ $v->modules = $desired_list;
+ $result["dialog"] = (string)$v;
+ $result["allow_continue"] = empty($messages["error"]);
+ }
+ json::reply($result);
+ }
+
+ public function save() {
+ access::verify_csrf();
+
+ $this->_do_save();
+ url::redirect("admin/modules");
+ }
+
+ private function _do_save() {
+ $changes = new stdClass();
+ $changes->activate = array();
+ $changes->deactivate = array();
+ $activated_names = array();
+ $deactivated_names = array();
+ foreach (module::available() as $module_name => $info) {
+ if ($info->locked) {
+ continue;
+ }
+
+ try {
+ $desired = Input::instance()->post($module_name) == 1;
+ if ($info->active && !$desired && module::is_active($module_name)) {
+ module::deactivate($module_name);
+ $changes->deactivate[] = $module_name;
+ $deactivated_names[] = t($info->name);
+ } else if (!$info->active && $desired && !module::is_active($module_name)) {
+ if (module::is_installed($module_name)) {
+ module::upgrade($module_name);
+ } else {
+ module::install($module_name);
+ }
+ module::activate($module_name);
+ $changes->activate[] = $module_name;
+ $activated_names[] = t($info->name);
+ }
+ } catch (Exception $e) {
+ message::warning(t("An error occurred while installing the <b>%module_name</b> module",
+ array("module_name" => $info->name)));
+ Kohana_Log::add("error", (string)$e);
+ }
+ }
+
+ module::event("module_change", $changes);
+
+ // @todo this type of collation is questionable from an 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))));
+ }
+ }
+}
+
diff --git a/modules/gallery/controllers/admin_movies.php b/modules/gallery/controllers/admin_movies.php
new file mode 100644
index 0000000..38fa44a
--- /dev/null
+++ b/modules/gallery/controllers/admin_movies.php
@@ -0,0 +1,72 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Movies_Controller extends Admin_Controller {
+ public function index() {
+ // Print screen from new form.
+ $form = $this->_get_admin_form();
+ $this->_print_view($form);
+ }
+
+ public function save() {
+ access::verify_csrf();
+ $form = $this->_get_admin_form();
+ if ($form->validate()) {
+ module::set_var("gallery", "movie_allow_uploads", $form->settings->allow_uploads->value);
+ if ($form->settings->rebuild_thumbs->value) {
+ graphics::mark_dirty(true, false, "movie");
+ }
+ // All done - redirect with message.
+ message::success(t("Movies settings updated successfully"));
+ url::redirect("admin/movies");
+ }
+ // Something went wrong - print view from existing form.
+ $this->_print_view($form);
+ }
+
+ private function _print_view($form) {
+ list ($ffmpeg_version, $ffmpeg_date) = movie::get_ffmpeg_version();
+ $ffmpeg_version = $ffmpeg_date ? "{$ffmpeg_version} ({$ffmpeg_date})" : $ffmpeg_version;
+ $ffmpeg_path = movie::find_ffmpeg();
+ $ffmpeg_dir = substr($ffmpeg_path, 0, strrpos($ffmpeg_path, "/"));
+
+ $view = new Admin_View("admin.html");
+ $view->page_title = t("Movies settings");
+ $view->content = new View("admin_movies.html");
+ $view->content->form = $form;
+ $view->content->ffmpeg_dir = $ffmpeg_dir;
+ $view->content->ffmpeg_version = $ffmpeg_version;
+ print $view;
+ }
+
+ private function _get_admin_form() {
+ $form = new Forge("admin/movies/save", "", "post", array("id" => "g-movies-admin-form"));
+ $group = $form->group("settings")->label(t("Settings"));
+ $group->dropdown("allow_uploads")
+ ->label(t("Allow movie uploads into Gallery (does not affect existing movies)"))
+ ->options(array("autodetect"=>t("only if FFmpeg is detected (default)"),
+ "always"=>t("always"), "never"=>t("never")))
+ ->selected(module::get_var("gallery", "movie_allow_uploads", "autodetect"));
+ $group->checkbox("rebuild_thumbs")
+ ->label(t("Rebuild all movie thumbnails (once FFmpeg is installed, use this to update existing movie thumbnails)"))
+ ->checked(false); // always set as false
+ $form->submit("save")->value(t("Save"));
+ return $form;
+ }
+}
diff --git a/modules/gallery/controllers/admin_sidebar.php b/modules/gallery/controllers/admin_sidebar.php
new file mode 100644
index 0000000..f150f85
--- /dev/null
+++ b/modules/gallery/controllers/admin_sidebar.php
@@ -0,0 +1,69 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Sidebar_Controller extends Admin_Controller {
+ public function index() {
+ $view = new Admin_View("admin.html");
+ $view->page_title = t("Manage sidebar");
+ $view->content = new View("admin_sidebar.html");
+ $view->content->csrf = access::csrf_token();
+ $view->content->available = new View("admin_sidebar_blocks.html");
+ $view->content->active = new View("admin_sidebar_blocks.html");
+ list($view->content->available->blocks, $view->content->active->blocks) = $this->_get_blocks();
+ print $view;
+ }
+
+ public function update() {
+ access::verify_csrf();
+
+ $available_blocks = block_manager::get_available_site_blocks();
+
+ $active_blocks = array();
+ foreach (Input::instance()->get("block", array()) as $block_id) {
+ $active_blocks[md5($block_id)] = explode(":", (string) $block_id);
+ }
+ block_manager::set_active("site_sidebar", $active_blocks);
+
+ $result = array("result" => "success");
+ list($available, $active) = $this->_get_blocks();
+ $v = new View("admin_sidebar_blocks.html");
+ $v->blocks = $available;
+ $result["available"] = $v->render();
+ $v = new View("admin_sidebar_blocks.html");
+ $v->blocks = $active;
+ $result["active"] = $v->render();
+ $message = t("Updated sidebar blocks");
+ $result["message"] = (string) $message;
+ json::reply($result);
+ }
+
+ private function _get_blocks() {
+ $active_blocks = array();
+ $available_blocks = block_manager::get_available_site_blocks();
+ foreach (block_manager::get_active("site_sidebar") as $block) {
+ $id = "{$block[0]}:{$block[1]}";
+ if (!empty($available_blocks[$id])) {
+ $active_blocks[$id] = $available_blocks[$id];
+ unset($available_blocks[$id]);
+ }
+ }
+ return array($available_blocks, $active_blocks);
+ }
+}
+
diff --git a/modules/gallery/controllers/admin_theme_options.php b/modules/gallery/controllers/admin_theme_options.php
new file mode 100644
index 0000000..38d2b0a
--- /dev/null
+++ b/modules/gallery/controllers/admin_theme_options.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Options_Controller extends Admin_Controller {
+ public function index() {
+ $view = new Admin_View("admin.html");
+ $view->page_title = t("Theme options");
+ $view->content = new View("admin_theme_options.html");
+ $view->content->form = $this->_get_edit_form_admin();
+ print $view;
+ }
+
+ public function save() {
+ access::verify_csrf();
+
+ $form = $this->_get_edit_form_admin();
+ if ($form->validate()) {
+ module::set_var("gallery", "page_size", $form->edit_theme->page_size->value);
+
+ $thumb_size = $form->edit_theme->thumb_size->value;
+ if (module::get_var("gallery", "thumb_size") != $thumb_size) {
+ graphics::remove_rule("gallery", "thumb", "gallery_graphics::resize");
+ graphics::add_rule(
+ "gallery", "thumb", "gallery_graphics::resize",
+ array("width" => $thumb_size, "height" => $thumb_size, "master" => Image::AUTO),
+ 100);
+ module::set_var("gallery", "thumb_size", $thumb_size);
+ }
+
+ $resize_size = $form->edit_theme->resize_size->value;
+ if (module::get_var("gallery", "resize_size") != $resize_size) {
+ graphics::remove_rule("gallery", "resize", "gallery_graphics::resize");
+ graphics::add_rule(
+ "gallery", "resize", "gallery_graphics::resize",
+ array("width" => $resize_size, "height" => $resize_size, "master" => Image::AUTO),
+ 100);
+ module::set_var("gallery", "resize_size", $resize_size);
+ }
+
+ module::set_var("gallery", "header_text", $form->edit_theme->header_text->value);
+ module::set_var("gallery", "footer_text", $form->edit_theme->footer_text->value);
+ module::set_var("gallery", "show_credits", $form->edit_theme->show_credits->value);
+ module::set_var("gallery", "favicon_url", $form->edit_theme->favicon_url->value);
+ module::set_var("gallery", "apple_touch_icon_url", $form->edit_theme->apple_touch_icon_url->value);
+
+ module::event("theme_edit_form_completed", $form);
+
+ message::success(t("Updated theme details"));
+ url::redirect("admin/theme_options");
+ } else {
+ $view = new Admin_View("admin.html");
+ $view->content = new View("admin_theme_options.html");
+ $view->content->form = $form;
+ print $view;
+ }
+ }
+
+ private function _get_edit_form_admin() {
+ $form = new Forge("admin/theme_options/save/", "", null, array("id" =>"g-theme-options-form"));
+ $group = $form->group("edit_theme")->label(t("Theme layout"));
+ $group->input("page_size")->label(t("Items per page"))->id("g-page-size")
+ ->rules("required|valid_digit")
+ ->callback(array($this, "_valididate_page_size"))
+ ->error_messages("required", t("You must enter a number"))
+ ->error_messages("valid_digit", t("You must enter a number"))
+ ->error_messages("valid_min_value", t("The value must be greater than zero"))
+ ->value(module::get_var("gallery", "page_size"));
+ $group->input("thumb_size")->label(t("Thumbnail size (in pixels)"))->id("g-thumb-size")
+ ->rules("required|valid_digit")
+ ->error_messages("required", t("You must enter a number"))
+ ->error_messages("valid_digit", t("You must enter a number"))
+ ->value(module::get_var("gallery", "thumb_size"));
+ $group->input("resize_size")->label(t("Resized image size (in pixels)"))->id("g-resize-size")
+ ->rules("required|valid_digit")
+ ->error_messages("required", t("You must enter a number"))
+ ->error_messages("valid_digit", t("You must enter a number"))
+ ->value(module::get_var("gallery", "resize_size"));
+ $group->input("favicon_url")->label(t("URL (or relative path) to your favicon.ico"))
+ ->id("g-favicon")
+ ->value(module::get_var("gallery", "favicon_url"));
+ $group->input("apple_touch_icon_url")->label(t("URL (or relative path) to your Apple Touch icon"))
+ ->id("g-apple-touch")
+ ->value(module::get_var("gallery", "apple_touch_icon_url"));
+ $group->textarea("header_text")->label(t("Header text"))->id("g-header-text")
+ ->value(module::get_var("gallery", "header_text"));
+ $group->textarea("footer_text")->label(t("Footer text"))->id("g-footer-text")
+ ->value(module::get_var("gallery", "footer_text"));
+ $group->checkbox("show_credits")->label(t("Show site credits"))->id("g-footer-text")
+ ->checked(module::get_var("gallery", "show_credits"));
+
+ module::event("theme_edit_form", $form);
+
+ $group->submit("")->value(t("Save"));
+ return $form;
+ }
+
+ function _valididate_page_size($input) {
+ if ($input->value < 1) {
+ $input->add_error("valid_min_value", true);
+ }
+
+ }
+}
+
diff --git a/modules/gallery/controllers/admin_themes.php b/modules/gallery/controllers/admin_themes.php
new file mode 100644
index 0000000..4ab994f
--- /dev/null
+++ b/modules/gallery/controllers/admin_themes.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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->page_title = t("Theme choice");
+ $view->content = new View("admin_themes.html");
+ $view->content->admin = module::get_var("gallery", "active_admin_theme");
+ $view->content->site = module::get_var("gallery", "active_site_theme");
+ $view->content->themes = $this->_get_themes();
+
+ site_status::clear("missing_site_theme");
+ site_status::clear("missing_admin_theme");
+ print $view;
+ }
+
+ private function _get_themes() {
+ $themes = array();
+ foreach (scandir(THEMEPATH) as $theme_name) {
+ if ($theme_name[0] == ".") {
+ continue;
+ }
+ $theme_name = preg_replace("/[^a-zA-Z0-9\._-]/", "", $theme_name);
+ if (file_exists(THEMEPATH . "$theme_name/theme.info")) {
+
+ $themes[$theme_name] = theme::get_info($theme_name);
+ }
+ }
+ return $themes;
+ }
+
+ public function preview($type, $theme_name) {
+ $view = new View("admin_themes_preview.html");
+ $view->info = theme::get_info($theme_name);
+ $view->theme_name = t($theme_name);
+ $view->type = $type;
+ if ($type == "admin") {
+ $view->url = url::site("admin?theme=$theme_name");
+ } else {
+ $view->url = item::root()->url("theme=$theme_name");
+ }
+ print $view;
+ }
+
+ public function choose($type, $theme_name) {
+ access::verify_csrf();
+
+ $info = theme::get_info($theme_name);
+
+ if ($type == "admin" && $info->admin) {
+ module::set_var("gallery", "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("gallery", "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/admin_upgrade_checker.php b/modules/gallery/controllers/admin_upgrade_checker.php
new file mode 100644
index 0000000..3f0bdac
--- /dev/null
+++ b/modules/gallery/controllers/admin_upgrade_checker.php
@@ -0,0 +1,57 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Upgrade_Checker_Controller extends Admin_Controller {
+ function check_now() {
+ access::verify_csrf();
+ upgrade_checker::fetch_version_info();
+ $message = upgrade_checker::get_upgrade_message();
+ if ($message) {
+ $message .= t(
+ " <a href=\"%hide-url\"><i>(remind me later)</i></a>",
+ array("hide-url" => url::site("admin/upgrade_checker/remind_me_later?csrf=__CSRF__")));
+ site_status::info($message, "upgrade_checker");
+ } else {
+ site_status::clear("upgrade_checker");
+ }
+ url::redirect("admin/dashboard");
+ }
+
+ function remind_me_later() {
+ access::verify_csrf();
+ site_status::clear("upgrade_checker");
+ if ($referer = Input::instance()->server("HTTP_REFERER")) {
+ url::redirect($referer);
+ } else {
+ url::redirect(item::root()->abs_url());
+ }
+ }
+
+ function set_auto($val) {
+ access::verify_csrf();
+ module::set_var("gallery", "upgrade_checker_auto_enabled", (bool)$val);
+
+ if ((bool)$val) {
+ message::success(t("Automatic upgrade checking is enabled."));
+ } else {
+ message::success(t("Automatic upgrade checking is disabled."));
+ }
+ url::redirect("admin/dashboard");
+ }
+}
diff --git a/modules/gallery/controllers/albums.php b/modules/gallery/controllers/albums.php
new file mode 100644
index 0000000..0fb033a
--- /dev/null
+++ b/modules/gallery/controllers/albums.php
@@ -0,0 +1,207 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ public function index() {
+ $this->show(ORM::factory("item", 1));
+ }
+
+ public function show($album) {
+ if (!is_object($album)) {
+ // show() must be public because we route to it in url::parse_url(), so make
+ // sure that we're actually receiving an object
+ throw new Kohana_404_Exception();
+ }
+
+ access::required("view", $album);
+
+ $page_size = module::get_var("gallery", "page_size", 9);
+ $input = Input::instance();
+ $show = $input->get("show");
+
+ if ($show) {
+ $child = ORM::factory("item", $show);
+ $index = item::get_position($child);
+ if ($index) {
+ $page = ceil($index / $page_size);
+ if ($page == 1) {
+ url::redirect($album->abs_url());
+ } else {
+ url::redirect($album->abs_url("page=$page"));
+ }
+ }
+ }
+
+ $page = $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($album->abs_url());
+ } else if ($page > $max_pages) {
+ url::redirect($album->abs_url("page=$max_pages"));
+ }
+
+ $template = new Theme_View("page.html", "collection", "album");
+ $template->set_global(
+ array("page" => $page,
+ "page_title" => null,
+ "max_pages" => $max_pages,
+ "page_size" => $page_size,
+ "item" => $album,
+ "children" => $album->viewable()->children($page_size, $offset),
+ "parents" => $album->parents()->as_array(), // view calls empty() on this
+ "breadcrumbs" => Breadcrumb::array_from_item_parents($album),
+ "children_count" => $children_count));
+ $template->content = new View("album.html");
+ $album->increment_view_count();
+
+ print $template;
+ item::set_display_context_callback("Albums_Controller::get_display_context");
+ }
+
+ static function get_display_context($item) {
+ $where = array(array("type", "!=", "album"));
+ $position = item::get_position($item, $where);
+ if ($position > 1) {
+ list ($previous_item, $ignore, $next_item) =
+ $item->parent()->viewable()->children(3, $position - 2, $where);
+ } else {
+ $previous_item = null;
+ list ($next_item) = $item->parent()->viewable()->children(1, $position, $where);
+ }
+
+ return array("position" => $position,
+ "previous_item" => $previous_item,
+ "next_item" => $next_item,
+ "sibling_count" => $item->parent()->viewable()->children_count($where),
+ "siblings_callback" => array("Albums_Controller::get_siblings", array($item)),
+ "parents" => $item->parents()->as_array(),
+ "breadcrumbs" => Breadcrumb::array_from_item_parents($item));
+ }
+
+ static function get_siblings($item, $limit=null, $offset=null) {
+ // @todo consider creating Item_Model::siblings() if we use this more broadly.
+ return $item->parent()->viewable()->children($limit, $offset);
+ }
+
+ public function create($parent_id) {
+ access::verify_csrf();
+ $album = ORM::factory("item", $parent_id);
+ access::required("view", $album);
+ access::required("add", $album);
+
+ $form = album::get_add_form($album);
+ try {
+ $valid = $form->validate();
+ $album = ORM::factory("item");
+ $album->type = "album";
+ $album->parent_id = $parent_id;
+ $album->name = $form->add_album->inputs["name"]->value;
+ $album->title = $form->add_album->title->value ?
+ $form->add_album->title->value : $form->add_album->inputs["name"]->value;
+ $album->description = $form->add_album->description->value;
+ $album->slug = $form->add_album->slug->value;
+ $album->validate();
+ } catch (ORM_Validation_Exception $e) {
+ // Translate ORM validation errors into form error messages
+ foreach ($e->validation->errors() as $key => $error) {
+ $form->add_album->inputs[$key]->add_error($error, 1);
+ }
+ $valid = false;
+ }
+
+ if ($valid) {
+ $album->save();
+ module::event("album_add_form_completed", $album, $form);
+ log::success("content", "Created an album",
+ html::anchor("albums/$album->id", "view album"));
+ message::success(t("Created album %album_title",
+ array("album_title" => html::purify($album->title))));
+
+ json::reply(array("result" => "success", "location" => $album->url()));
+ } else {
+ json::reply(array("result" => "error", "html" => (string)$form));
+ }
+ }
+
+ public function update($album_id) {
+ access::verify_csrf();
+ $album = ORM::factory("item", $album_id);
+ access::required("view", $album);
+ access::required("edit", $album);
+
+ $form = album::get_edit_form($album);
+ try {
+ $valid = $form->validate();
+ $album->title = $form->edit_item->title->value;
+ $album->description = $form->edit_item->description->value;
+ $album->sort_column = $form->edit_item->sort_order->column->value;
+ $album->sort_order = $form->edit_item->sort_order->direction->value;
+ if (array_key_exists("name", $form->edit_item->inputs)) {
+ $album->name = $form->edit_item->inputs["name"]->value;
+ }
+ $album->slug = $form->edit_item->slug->value;
+ $album->validate();
+ } catch (ORM_Validation_Exception $e) {
+ // Translate ORM validation errors into form error messages
+ foreach ($e->validation->errors() as $key => $error) {
+ $form->edit_item->inputs[$key]->add_error($error, 1);
+ }
+ $valid = false;
+ }
+
+ if ($valid) {
+ $album->save();
+ module::event("item_edit_form_completed", $album, $form);
+
+ log::success("content", "Updated album", "<a href=\"albums/$album->id\">view</a>");
+ message::success(t("Saved album %album_title",
+ array("album_title" => html::purify($album->title))));
+
+ if ($form->from_id->value == $album->id) {
+ // Use the new url; it might have changed.
+ json::reply(array("result" => "success", "location" => $album->url()));
+ } else {
+ // Stay on the same page
+ json::reply(array("result" => "success"));
+ }
+ } else {
+ json::reply(array("result" => "error", "html" => (string)$form));
+ }
+ }
+
+ public function form_add($album_id) {
+ $album = ORM::factory("item", $album_id);
+ access::required("view", $album);
+ access::required("add", $album);
+
+ print album::get_add_form($album);
+ }
+
+ public function form_edit($album_id) {
+ $album = ORM::factory("item", $album_id);
+ access::required("view", $album);
+ access::required("edit", $album);
+
+ print album::get_edit_form($album);
+ }
+}
diff --git a/modules/gallery/controllers/combined.php b/modules/gallery/controllers/combined.php
new file mode 100644
index 0000000..3cf7f87
--- /dev/null
+++ b/modules/gallery/controllers/combined.php
@@ -0,0 +1,96 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Combined_Controller extends Controller {
+ const ALLOW_MAINTENANCE_MODE = true;
+ const ALLOW_PRIVATE_GALLERY = true;
+
+ /**
+ * Return the combined Javascript bundle associated with the given key.
+ */
+ public function javascript($key) {
+ return $this->_emit("javascript", $key);
+ }
+
+ /**
+ * Return the combined CSS bundle associated with the given key.
+ */
+ public function css($key) {
+ return $this->_emit("css", $key);
+ }
+
+ /**
+ * Print out a cached entry.
+ * @param string the combined entry type (either "javascript" or "css")
+ * @param string the key (typically an md5 sum)
+ */
+ private function _emit($type, $key) {
+ $input = Input::instance();
+
+ // We don't need to save the session for this request
+ Session::instance()->abort_save();
+
+ // Our data is immutable, so if they already have a copy then it needs no updating.
+ if ($input->server("HTTP_IF_MODIFIED_SINCE")) {
+ header('HTTP/1.0 304 Not Modified');
+ header("Expires: Tue, 19 Jan 2038 00:00:00 GMT");
+ header("Cache-Control: public,max-age=2678400");
+ header('Pragma: public');
+ Kohana::close_buffers(false);
+ return "";
+ }
+
+ if (empty($key)) {
+ throw new Kohana_404_Exception();
+ }
+
+ $cache = Cache::instance();
+ $use_gzip = function_exists("gzencode") &&
+ stripos($input->server("HTTP_ACCEPT_ENCODING"), "gzip") !== false &&
+ (int) ini_get("zlib.output_compression") === 0;
+
+ if ($use_gzip && $content = $cache->get("{$key}_gz")) {
+ header("Content-Encoding: gzip");
+ header("Vary: Accept-Encoding");
+ } else {
+ // Fall back to non-gzipped if we have to
+ $content = $cache->get($key);
+ }
+ if (empty($content)) {
+ throw new Kohana_404_Exception();
+ }
+
+ // $type is either 'javascript' or 'css'
+ if ($type == "javascript") {
+ header("Content-Type: application/javascript; charset=UTF-8");
+ } else {
+ header("Content-Type: text/css; charset=UTF-8");
+ }
+ header("Expires: Tue, 19 Jan 2038 00:00:00 GMT");
+ header("Cache-Control: public,max-age=2678400");
+ header("Pragma: public");
+ header("Last-Modified: " . gmdate("D, d M Y H:i:s T", time()));
+ header("Content-Length: " . strlen($content));
+
+ Kohana::close_buffers(false);
+ print $content;
+ }
+
+}
+
diff --git a/modules/gallery/controllers/file_proxy.php b/modules/gallery/controllers/file_proxy.php
new file mode 100644
index 0000000..34f6b8c
--- /dev/null
+++ b/modules/gallery/controllers/file_proxy.php
@@ -0,0 +1,174 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ const ALLOW_PRIVATE_GALLERY = true;
+ public function __call($function, $args) {
+
+ // Force zlib compression off. Image and movie files are already compressed and
+ // recompressing them is CPU intensive.
+ if (ini_get("zlib.output_compression")) {
+ ini_set("zlib.output_compression", "Off");
+ }
+
+ // request_uri: gallery3/var/albums/foo/bar.jpg?m=1234
+ $request_uri = rawurldecode(Input::instance()->server("REQUEST_URI"));
+
+ // get rid of query parameters
+ // request_uri: gallery3/var/albums/foo/bar.jpg
+ $request_uri = preg_replace("/\?.*/", "", $request_uri);
+
+ // var_uri: gallery3/var/
+ $var_uri = url::file("var/");
+
+ // Make sure that the request is for a file inside var
+ $offset = strpos(rawurldecode($request_uri), $var_uri);
+ if ($offset !== 0) {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 1;
+ throw $e;
+ }
+
+ // file_uri: albums/foo/bar.jpg
+ $file_uri = substr($request_uri, strlen($var_uri));
+
+ // type: albums
+ // path: foo/bar.jpg
+ list ($type, $path) = explode("/", $file_uri, 2);
+ if ($type != "resizes" && $type != "albums" && $type != "thumbs") {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 2;
+ throw $e;
+ }
+
+ // If the last element is .album.jpg, pop that off since it's not a real item
+ $path = preg_replace("|/.album.jpg$|", "", $path);
+
+ $item = item::find_by_path($path);
+ if (!$item->loaded()) {
+ // We didn't turn it up. If we're looking for a .jpg then it's it's possible that we're
+ // requesting the thumbnail for a movie. In that case, the movie file would
+ // have been converted to a .jpg. So try some alternate types:
+ if (preg_match('/.jpg$/', $path)) {
+ foreach (legal_file::get_movie_extensions() as $ext) {
+ $movie_path = preg_replace('/.jpg$/', ".$ext", $path);
+ $item = item::find_by_path($movie_path);
+ if ($item->loaded()) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (!$item->loaded()) {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 3;
+ throw $e;
+ }
+
+ // Make sure we have access to the item
+ if (!access::can("view", $item)) {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 4;
+ throw $e;
+ }
+
+ // Make sure we have view_full access to the original
+ if ($type == "albums" && !access::can("view_full", $item)) {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 5;
+ throw $e;
+ }
+
+ // Don't try to load a directory
+ if ($type == "albums" && $item->is_album()) {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 6;
+ throw $e;
+ }
+
+ // Note: this code is roughly duplicated in data_rest, so if you modify this, please look to
+ // see if you should make the same change there as well.
+
+ if ($type == "albums") {
+ $file = $item->file_path();
+ } else if ($type == "resizes") {
+ $file = $item->resize_path();
+ } else {
+ $file = $item->thumb_path();
+ }
+
+ if (!file_exists($file)) {
+ $e = new Kohana_404_Exception();
+ $e->test_fail_code = 7;
+ throw $e;
+ }
+
+ if (gallery::show_profiler()) {
+ Profiler::enable();
+ $profiler = new Profiler();
+ $profiler->render();
+ exit;
+ }
+
+ header("Content-Length: " . filesize($file));
+
+ header("Pragma:");
+ // Check that the content hasn't expired or it wasn't changed since cached
+ expires::check(2592000, $item->updated);
+
+ // We don't need to save the session for this request
+ Session::instance()->abort_save();
+
+ expires::set(2592000, $item->updated); // 30 days
+
+ // Dump out the image. If the item is a movie or album, then its thumbnail will be a JPG.
+ if (($item->is_movie() || $item->is_album()) && $type == "thumbs") {
+ header("Content-Type: image/jpeg");
+ } else {
+ header("Content-Type: $item->mime_type");
+ }
+
+ if (TEST_MODE) {
+ return $file;
+ } else {
+ // Don't use Kohana::close_buffers(false) here because that only closes all the buffers
+ // that Kohana started. We want to close *all* buffers at this point because otherwise we're
+ // going to buffer up whatever file we're proxying (and it may be very large). This may
+ // affect embedding or systems with PHP's output_buffering enabled.
+ while (ob_get_level()) {
+ if (!@ob_end_clean()) {
+ // ob_end_clean() can return false if the buffer can't be removed for some reason
+ // (zlib output compression buffers sometimes cause problems).
+ break;
+ }
+ }
+ readfile($file);
+ }
+ }
+}
diff --git a/modules/gallery/controllers/items.php b/modules/gallery/controllers/items.php
new file mode 100644
index 0000000..845df76
--- /dev/null
+++ b/modules/gallery/controllers/items.php
@@ -0,0 +1,43 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Controller {
+ public function __call($function, $args) {
+ $item = ORM::factory("item", (int)$function);
+ if (!$item->loaded()) {
+ throw new Kohana_404_Exception();
+ }
+
+ // Redirect to the more specific resource type, since it will render differently. We can't
+ // delegate here because we may have gotten to this page via /items/<id> which means that we
+ // don't have a type-specific controller. Also, we want to drive a single canonical resource
+ // mapping where possible.
+ access::required("view", $item);
+ url::redirect($item->abs_url());
+ }
+
+ // Return the width/height dimensions for the given item
+ public function dimensions($id) {
+ $item = ORM::factory("item", $id);
+ access::required("view", $item);
+ json::reply(array("thumb" => array((int)$item->thumb_width, (int)$item->thumb_height),
+ "resize" => array((int)$item->resize_width, (int)$item->resize_height),
+ "full" => array((int)$item->width, (int)$item->height)));
+ }
+}
diff --git a/modules/gallery/controllers/l10n_client.php b/modules/gallery/controllers/l10n_client.php
new file mode 100644
index 0000000..993dfb3
--- /dev/null
+++ b/modules/gallery/controllers/l10n_client.php
@@ -0,0 +1,179 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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();
+ if (!identity::active_user()->admin) {
+ access::forbidden();
+ }
+
+ $locale = Gallery_I18n::instance()->locale();
+ $input = Input::instance();
+ $key = $input->post("l10n-message-key");
+
+ $root_message = ORM::factory("incoming_translation")
+ ->where("key", "=", $key)
+ ->where("locale", "=", "root")
+ ->find();
+
+ if (!$root_message->loaded()) {
+ throw new Exception("@todo bad request data / illegal state");
+ }
+ $is_plural = Gallery_I18n::is_plural_message(unserialize($root_message->message));
+
+ $is_empty = true;
+ if ($is_plural) {
+ $plural_forms = l10n_client::plural_forms($locale);
+ $translation = array();
+ foreach($plural_forms as $plural_form) {
+ $value = $input->post("l10n-edit-plural-translation-$plural_form");
+ if (null === $value || !is_string($value)) {
+ throw new Exception("@todo bad request data");
+ }
+ $translation[$plural_form] = $value;
+ $is_empty = $is_empty && empty($value);
+ }
+ } else {
+ $translation = $input->post("l10n-edit-translation");
+ $is_empty = empty($translation);
+ if (null === $translation || !is_string($translation)) {
+ throw new Exception("@todo bad request data");
+ }
+ }
+
+ $entry = ORM::factory("outgoing_translation")
+ ->where("key", "=", $key)
+ ->where("locale", "=", $locale)
+ ->find();
+
+ if ($is_empty) {
+ if ($entry->loaded()) {
+ $entry->delete();
+ }
+ } else {
+ if (!$entry->loaded()) {
+ $entry->key = $key;
+ $entry->locale = $locale;
+ $entry->message = $root_message->message;
+ $entry->base_revision = null;
+ }
+
+ $entry->translation = serialize($translation);
+
+ $entry_from_incoming = ORM::factory("incoming_translation")
+ ->where("key", "=", $key)
+ ->where("locale", "=", $locale)
+ ->find();
+
+ if (!$entry_from_incoming->loaded()) {
+ $entry->base_revision = $entry_from_incoming->revision;
+ }
+
+ $entry->save();
+ }
+
+ Gallery_I18n::clear_cache($locale);
+
+ json::reply(new stdClass());
+ }
+
+ public function toggle_l10n_mode() {
+ access::verify_csrf();
+ if (!identity::active_user()->admin) {
+ access::forbidden();
+ }
+
+ $session = Session::instance();
+ $l10n_mode = $session->get("l10n_mode", false);
+ $session->set("l10n_mode", !$l10n_mode);
+
+ $redirect_url = "admin/languages";
+ if (!$l10n_mode) {
+ $redirect_url .= "#l10n-client";
+ }
+
+ url::redirect($redirect_url);
+ }
+
+ private static function _l10n_client_search_form() {
+ $form = new Forge("#", "", "post", array("id" => "g-l10n-search-form"));
+ $group = $form->group("l10n_search");
+ $group->input("l10n-search")->id("g-l10n-search");
+
+ return $form;
+ }
+
+ public static function l10n_form() {
+ if (Input::instance()->get("show_all_l10n_messages")) {
+ $calls = array();
+ foreach (db::build()
+ ->select("key", "message")
+ ->from("incoming_translations")
+ ->where("locale", "=", "root")
+ ->execute() as $row) {
+ $calls[$row->key] = array(unserialize($row->message), array());
+ }
+ } else {
+ $calls = Gallery_I18n::instance()->call_log();
+ }
+ $locale = Gallery_I18n::instance()->locale();
+
+ if ($calls) {
+ $translations = array();
+ foreach (db::build()
+ ->select("key", "translation")
+ ->from("incoming_translations")
+ ->where("locale", "=", $locale)
+ ->execute() as $row) {
+ $translations[$row->key] = unserialize($row->translation);
+ }
+ // Override incoming with outgoing...
+ foreach (db::build()
+ ->select("key", "translation")
+ ->from("outgoing_translations")
+ ->where("locale", "=", $locale)
+ ->execute() as $row) {
+ $translations[$row->key] = unserialize($row->translation);
+ }
+
+ $string_list = array();
+ $cache = array();
+ foreach ($calls as $key => $call) {
+ list ($message, $options) = $call;
+ // Ensure that the message is in the DB
+ l10n_scanner::process_message($message, $cache);
+ // Note: Not interpolating placeholders for the actual translation input field.
+ // TODO: Might show a preview w/ interpolations (using $options)
+ $translation = isset($translations[$key]) ? $translations[$key] : '';
+ $string_list[] = array('source' => $message,
+ 'key' => $key,
+ 'translation' => $translation);
+ }
+
+ $v = new View('l10n_client.html');
+ $v->string_list = $string_list;
+ $v->l10n_search_form = self::_l10n_client_search_form();
+ $v->plural_forms = l10n_client::plural_forms($locale);
+ return $v;
+ }
+
+ return '';
+ }
+}
diff --git a/modules/gallery/controllers/login.php b/modules/gallery/controllers/login.php
new file mode 100644
index 0000000..9da575b
--- /dev/null
+++ b/modules/gallery/controllers/login.php
@@ -0,0 +1,90 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Login_Controller extends Controller {
+ const ALLOW_MAINTENANCE_MODE = true;
+ const ALLOW_PRIVATE_GALLERY = true;
+
+ public function ajax() {
+ $view = new View("login_ajax.html");
+ $view->form = auth::get_login_form("login/auth_ajax");
+ print $view;
+ }
+
+ public function auth_ajax() {
+ access::verify_csrf();
+
+ list ($valid, $form) = $this->_auth("login/auth_ajax");
+ if ($valid) {
+ json::reply(array("result" => "success"));
+ } else {
+ $view = new View("login_ajax.html");
+ $view->form = $form;
+ json::reply(array("result" => "error", "html" => (string)$view));
+ }
+ }
+
+ public function html() {
+ $view = new Theme_View("page.html", "other", "login");
+ $view->page_title = t("Log in to Gallery");
+ $view->content = new View("login_ajax.html");
+ $view->content->form = auth::get_login_form("login/auth_html");
+ print $view;
+ }
+
+ public function auth_html() {
+ access::verify_csrf();
+
+ list ($valid, $form) = $this->_auth("login/auth_html");
+ if ($valid) {
+ $continue_url = $form->continue_url->value;
+ url::redirect($continue_url ? $continue_url : item::root()->abs_url());
+ } else {
+ $view = new Theme_View("page.html", "other", "login");
+ $view->page_title = t("Log in to Gallery");
+ $view->content = new View("login_ajax.html");
+ $view->content->form = $form;
+ print $view;
+ }
+ }
+
+ private function _auth($url) {
+ $form = auth::get_login_form($url);
+ $valid = $form->validate();
+ if ($valid) {
+ $user = identity::lookup_user_by_name($form->login->inputs["name"]->value);
+ if (empty($user) || !identity::is_correct_password($user, $form->login->password->value)) {
+ $form->login->inputs["name"]->add_error("invalid_login", 1);
+ $name = $form->login->inputs["name"]->value;
+ log::warning("user", t("Failed login for %name", array("name" => $name)));
+ module::event("user_auth_failed", $name);
+ $valid = false;
+ }
+ }
+
+ if ($valid) {
+ auth::login($user);
+ }
+
+ // Either way, regenerate the session id to avoid session trapping
+ Session::instance()->regenerate();
+
+ return array($valid, $form);
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/controllers/logout.php b/modules/gallery/controllers/logout.php
new file mode 100644
index 0000000..9a24d1b
--- /dev/null
+++ b/modules/gallery/controllers/logout.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Logout_Controller extends Controller {
+ public function index() {
+ access::verify_csrf();
+ auth::logout();
+ if ($continue_url = Input::instance()->get("continue_url")) {
+ url::redirect($continue_url);
+ }
+ url::redirect(item::root()->abs_url());
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/controllers/movies.php b/modules/gallery/controllers/movies.php
new file mode 100644
index 0000000..5607571
--- /dev/null
+++ b/modules/gallery/controllers/movies.php
@@ -0,0 +1,91 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ public function show($movie) {
+ if (!is_object($movie)) {
+ // show() must be public because we route to it in url::parse_url(), so make
+ // sure that we're actually receiving an object
+ throw new Kohana_404_Exception();
+ }
+
+ access::required("view", $movie);
+
+ $template = new Theme_View("page.html", "item", "movie");
+ $template->set_global(array("item" => $movie,
+ "children" => array(),
+ "children_count" => 0));
+ $template->set_global(item::get_display_context($movie));
+ $template->content = new View("movie.html");
+
+ $movie->increment_view_count();
+
+ print $template;
+ }
+
+ public function update($movie_id) {
+ access::verify_csrf();
+ $movie = ORM::factory("item", $movie_id);
+ access::required("view", $movie);
+ access::required("edit", $movie);
+
+ $form = movie::get_edit_form($movie);
+ try {
+ $valid = $form->validate();
+ $movie->title = $form->edit_item->title->value;
+ $movie->description = $form->edit_item->description->value;
+ $movie->slug = $form->edit_item->slug->value;
+ $movie->name = $form->edit_item->inputs["name"]->value;
+ $movie->validate();
+ } catch (ORM_Validation_Exception $e) {
+ // Translate ORM validation errors into form error messages
+ foreach ($e->validation->errors() as $key => $error) {
+ $form->edit_item->inputs[$key]->add_error($error, 1);
+ }
+ $valid = false;
+ }
+
+ if ($valid) {
+ $movie->save();
+ module::event("item_edit_form_completed", $movie, $form);
+
+ log::success("content", "Updated movie", "<a href=\"{$movie->url()}\">view</a>");
+ message::success(
+ t("Saved movie %movie_title", array("movie_title" => html::purify($movie->title))));
+
+ if ($form->from_id->value == $movie->id) {
+ // Use the new url; it might have changed.
+ json::reply(array("result" => "success", "location" => $movie->url()));
+ } else {
+ // Stay on the same page
+ json::reply(array("result" => "success"));
+ }
+ } else {
+ json::reply(array("result" => "error", "html" => (string) $form));
+ }
+ }
+
+ public function form_edit($movie_id) {
+ $movie = ORM::factory("item", $movie_id);
+ access::required("view", $movie);
+ access::required("edit", $movie);
+
+ print movie::get_edit_form($movie);
+ }
+}
diff --git a/modules/gallery/controllers/packager.php b/modules/gallery/controllers/packager.php
new file mode 100644
index 0000000..d7e3cf4
--- /dev/null
+++ b/modules/gallery/controllers/packager.php
@@ -0,0 +1,191 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Packager_Controller extends Controller {
+ function package() {
+ if (PHP_SAPI != "cli") {
+ access::forbidden();
+ }
+
+ $_SERVER["HTTP_HOST"] = "example.com";
+
+ try {
+ $this->_reset(); // empty and reinstall the standard modules
+ $this->_dump_database(); // Dump the database
+ $this->_dump_var(); // Dump the var directory
+ } catch (Exception $e) {
+ print $e->getMessage() . "\n" . $e->getTraceAsString();
+ return;
+ }
+
+ print "Successfully wrote install.sql and init_var.php\n";
+ }
+
+ private function _reset() {
+ // Drop all tables
+ foreach (Database::instance()->list_tables() as $table) {
+ Database::instance()->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");
+
+ Database::instance()->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);
+
+ foreach (array("gallery", "user", "comment", "organize", "info",
+ "rss", "search", "slideshow", "tag") as $module_name) {
+ module::install($module_name);
+ module::activate($module_name);
+ }
+ }
+
+ private function _dump_database() {
+ // We now have a clean install with just the packages that we want. Make sure that the
+ // database is clean too.
+ $i = 1;
+ foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $key) {
+ $blocks = array();
+ foreach (unserialize(module::get_var("gallery", "blocks_{$key}")) as $rnd => $value) {
+ $blocks[++$i] = $value;
+ }
+ module::set_var("gallery", "blocks_{$key}", serialize($blocks));
+ }
+
+ Database::instance()->query("TRUNCATE {caches}");
+ Database::instance()->query("TRUNCATE {sessions}");
+ Database::instance()->query("TRUNCATE {logs}");
+ db::build()->update("users")
+ ->set(array("password" => ""))
+ ->where("id", "in", array(1, 2))
+ ->execute();
+
+ $dbconfig = Kohana::config('database.default');
+ $conn = $dbconfig["connection"];
+ $sql_file = DOCROOT . "installer/install.sql";
+ if (!is_writable($sql_file)) {
+ print "$sql_file is not writeable";
+ return;
+ }
+ $command = sprintf(
+ "mysqldump --compact --skip-extended-insert --add-drop-table %s %s %s %s > $sql_file",
+ escapeshellarg("-h{$conn['host']}"),
+ escapeshellarg("-u{$conn['user']}"),
+ $conn['pass'] ? escapeshellarg("-p{$conn['pass']}") : "",
+ escapeshellarg($conn['database']));
+ 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 = ORM::factory("item", 1);
+ $root_created_timestamp = $root->created;
+ $root_updated_timestamp = $root->updated;
+ $table_name = "";
+ foreach (file($sql_file) as $line) {
+ // Prefix tables
+ $line = preg_replace(
+ "/(CREATE TABLE|IF EXISTS|INSERT INTO) `{$dbconfig['table_prefix']}(\w+)`/", "\\1 {\\2}",
+ $line);
+
+ if (preg_match("/CREATE TABLE {(\w+)}/", $line, $matches)) {
+ $table_name = $matches[1];
+ }
+ // Normalize dates
+ $line = preg_replace("/,$root_created_timestamp,/", ",UNIX_TIMESTAMP(),", $line);
+ $line = preg_replace("/,$root_updated_timestamp,/", ",UNIX_TIMESTAMP(),", $line);
+
+ // Remove ENGINE= specifications execpt for search records, it always needs to be MyISAM
+ if ($table_name != "search_records") {
+ $line = preg_replace("/ENGINE=\S+ /", "", $line);
+ }
+
+ // Null out ids in the vars table since it's an auto_increment table and this will result in
+ // more stable values so we'll have less churn in install.sql.
+ $line = preg_replace(
+ "/^INSERT INTO {vars} VALUES \(\d+/", "INSERT INTO {vars} VALUES (NULL", $line);
+
+ $buf .= $line;
+ }
+ $fd = fopen($sql_file, "wb");
+ fwrite($fd, $buf);
+ fclose($fd);
+ }
+
+ private function _dump_var() {
+ $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){
+ $path = $file->getPath();
+ $basename = $file->getBasename();
+ if ($basename == "database.php" || $basename == "." || $basename == "..") {
+ continue;
+ } else if (basename($path) == "logs" && $basename != ".htaccess") {
+ continue;
+ }
+
+ if ($file->isDir()) {
+ $paths[] = "VARPATH . \"" . substr($name, strlen(VARPATH)) . "\"";
+ } else {
+ // @todo: serialize non-directories
+ $files["VARPATH . \"" . substr($name, strlen(VARPATH)) . "\""] =
+ base64_encode(file_get_contents($name));
+ }
+ }
+ // 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");
+ }
+ ksort($files);
+ foreach ($files as $file => $contents) {
+ fwrite($fd, "file_put_contents($file, base64_decode(\"$contents\"));\n");
+ }
+ fclose($fd);
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/controllers/permissions.php b/modules/gallery/controllers/permissions.php
new file mode 100644
index 0000000..2513f86
--- /dev/null
+++ b/modules/gallery/controllers/permissions.php
@@ -0,0 +1,91 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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("view", $item);
+ 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("view", $item);
+ 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 = identity::lookup_group($group_id);
+ $perm = ORM::factory("permission", $perm_id);
+ $item = ORM::factory("item", $item_id);
+ access::required("view", $item);
+ access::required("edit", $item);
+
+ if (!empty($group) && $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;
+ }
+
+ // If the active user just took away their own edit permissions, give it back.
+ if ($perm->name == "edit") {
+ if (!access::user_can(identity::active_user(), "edit", $item)) {
+ access::allow($group, $perm->name, $item);
+ }
+ }
+ }
+ }
+
+ private function _get_form($item) {
+ $view = new View("permissions_form.html");
+ $view->item = $item;
+ $view->groups = identity::groups();
+ $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 0000000..96a22c5
--- /dev/null
+++ b/modules/gallery/controllers/photos.php
@@ -0,0 +1,91 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ public function show($photo) {
+ if (!is_object($photo)) {
+ // show() must be public because we route to it in url::parse_url(), so make
+ // sure that we're actually receiving an object
+ throw new Kohana_404_Exception();
+ }
+
+ access::required("view", $photo);
+
+ $template = new Theme_View("page.html", "item", "photo");
+ $template->set_global(array("item" => $photo,
+ "children" => array(),
+ "children_count" => 0));
+ $template->set_global(item::get_display_context($photo));
+ $template->content = new View("photo.html");
+
+ $photo->increment_view_count();
+
+ print $template;
+ }
+
+ public function update($photo_id) {
+ access::verify_csrf();
+ $photo = ORM::factory("item", $photo_id);
+ access::required("view", $photo);
+ access::required("edit", $photo);
+
+ $form = photo::get_edit_form($photo);
+ try {
+ $valid = $form->validate();
+ $photo->title = $form->edit_item->title->value;
+ $photo->description = $form->edit_item->description->value;
+ $photo->slug = $form->edit_item->slug->value;
+ $photo->name = $form->edit_item->inputs["name"]->value;
+ $photo->validate();
+ } catch (ORM_Validation_Exception $e) {
+ // Translate ORM validation errors into form error messages
+ foreach ($e->validation->errors() as $key => $error) {
+ $form->edit_item->inputs[$key]->add_error($error, 1);
+ }
+ $valid = false;
+ }
+
+ if ($valid) {
+ $photo->save();
+ module::event("item_edit_form_completed", $photo, $form);
+
+ log::success("content", "Updated photo", "<a href=\"{$photo->url()}\">view</a>");
+ message::success(
+ t("Saved photo %photo_title", array("photo_title" => html::purify($photo->title))));
+
+ if ($form->from_id->value == $photo->id) {
+ // Use the new url; it might have changed.
+ json::reply(array("result" => "success", "location" => $photo->url()));
+ } else {
+ // Stay on the same page
+ json::reply(array("result" => "success"));
+ }
+ } else {
+ json::reply(array("result" => "error", "html" => (string)$form));
+ }
+ }
+
+ public function form_edit($photo_id) {
+ $photo = ORM::factory("item", $photo_id);
+ access::required("view", $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 0000000..4b21d9e
--- /dev/null
+++ b/modules/gallery/controllers/quick.php
@@ -0,0 +1,144 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 rotate($id, $dir) {
+ access::verify_csrf();
+ $item = model_cache::get("item", $id);
+ access::required("view", $item);
+ access::required("edit", $item);
+
+ $degrees = 0;
+ switch($dir) {
+ case "ccw":
+ $degrees = -90;
+ break;
+
+ case "cw":
+ $degrees = 90;
+ break;
+ }
+
+ if ($degrees) {
+ $tmpfile = system::temp_filename("rotate",
+ pathinfo($item->file_path(), PATHINFO_EXTENSION));
+ gallery_graphics::rotate($item->file_path(), $tmpfile, array("degrees" => $degrees), $item);
+ $item->set_data_file($tmpfile);
+ $item->save();
+ }
+
+ if (Input::instance()->get("page_type") == "collection") {
+ json::reply(
+ array("src" => $item->thumb_url(),
+ "width" => $item->thumb_width,
+ "height" => $item->thumb_height));
+ } else {
+ json::reply(
+ array("src" => $item->resize_url(),
+ "width" => $item->resize_width,
+ "height" => $item->resize_height));
+ }
+ }
+
+ public function make_album_cover($id) {
+ access::verify_csrf();
+
+ $item = model_cache::get("item", $id);
+ access::required("view", $item);
+ access::required("view", $item->parent());
+ access::required("edit", $item->parent());
+
+ $msg = t("Made <b>%title</b> this album's cover", array("title" => html::purify($item->title)));
+
+ item::make_album_cover($item);
+ message::success($msg);
+
+ json::reply(array("result" => "success", "reload" => 1));
+ }
+
+ public function form_delete($id) {
+ $item = model_cache::get("item", $id);
+ access::required("view", $item);
+ access::required("edit", $item);
+
+ $v = new View("quick_delete_confirm.html");
+ $v->item = $item;
+ $v->form = item::get_delete_form($item);
+ print $v;
+ }
+
+ public function delete($id) {
+ access::verify_csrf();
+ $item = model_cache::get("item", $id);
+ access::required("view", $item);
+ access::required("edit", $item);
+
+ if ($item->is_album()) {
+ $msg = t("Deleted album <b>%title</b>", array("title" => html::purify($item->title)));
+ } else {
+ $msg = t("Deleted photo <b>%title</b>", array("title" => html::purify($item->title)));
+ }
+
+ $parent = $item->parent();
+
+ if ($item->is_album()) {
+ // Album delete will trigger deletes for all children. Do this in a batch so that we can be
+ // smart about notifications, album cover updates, etc.
+ batch::start();
+ $item->delete();
+ batch::stop();
+ } else {
+ $item->delete();
+ }
+ message::success($msg);
+
+ $from_id = Input::instance()->get("from_id");
+ if (Input::instance()->get("page_type") == "collection" &&
+ $from_id != $id /* deleted the item we were viewing */) {
+ json::reply(array("result" => "success", "reload" => 1));
+ } else {
+ json::reply(array("result" => "success", "location" => $parent->url()));
+ }
+ }
+
+ public function form_edit($id) {
+ $item = model_cache::get("item", $id);
+ access::required("view", $item);
+ access::required("edit", $item);
+
+ switch ($item->type) {
+ case "album":
+ $form = album::get_edit_form($item);
+ break;
+
+ case "photo":
+ $form = photo::get_edit_form($item);
+ break;
+
+ case "movie":
+ $form = movie::get_edit_form($item);
+ break;
+ }
+
+ // Pass on the source item where this form was generated, so we have an idea where to return to.
+ $form->hidden("from_id")->value((int)Input::instance()->get("from_id", 0));
+
+ print $form;
+ }
+}
diff --git a/modules/gallery/controllers/reauthenticate.php b/modules/gallery/controllers/reauthenticate.php
new file mode 100644
index 0000000..c79b76e
--- /dev/null
+++ b/modules/gallery/controllers/reauthenticate.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class Reauthenticate_Controller extends Controller {
+ public function index() {
+ $is_ajax = Session::instance()->get_once("is_ajax_request", request::is_ajax());
+ if (!identity::active_user()->admin) {
+ if ($is_ajax) {
+ // We should never be able to get here since Admin_Controller::_reauth_check() won't work
+ // for non-admins.
+ access::forbidden();
+ } else {
+ url::redirect(item::root()->abs_url());
+ }
+ }
+
+ // On redirects from the admin controller, the ajax request indicator is lost,
+ // so we store it in the session.
+ if ($is_ajax) {
+ $v = new View("reauthenticate.html");
+ $v->form = self::_form();
+ $v->user_name = identity::active_user()->name;
+ print $v;
+ } else {
+ self::_show_form(self::_form());
+ }
+ }
+
+ public function auth() {
+ if (!identity::active_user()->admin) {
+ access::forbidden();
+ }
+ access::verify_csrf();
+
+ $form = self::_form();
+ $valid = $form->validate();
+ $user = identity::active_user();
+ if ($valid) {
+ module::event("user_auth", $user);
+ if (!request::is_ajax()) {
+ message::success(t("Successfully re-authenticated!"));
+ }
+ url::redirect(Session::instance()->get_once("continue_url"));
+ } else {
+ $name = $user->name;
+ log::warning("user", t("Failed re-authentication for %name", array("name" => $name)));
+ module::event("user_auth_failed", $name);
+ if (request::is_ajax()) {
+ $v = new View("reauthenticate.html");
+ $v->form = $form;
+ $v->user_name = identity::active_user()->name;
+ json::reply(array("html" => (string)$v));
+ } else {
+ self::_show_form($form);
+ }
+ }
+ }
+
+ private static function _show_form($form) {
+ $view = new Theme_View("page.html", "other", "reauthenticate");
+ $view->page_title = t("Re-authenticate");
+ $view->content = new View("reauthenticate.html");
+ $view->content->form = $form;
+ $view->content->user_name = identity::active_user()->name;
+
+ print $view;
+ }
+
+ private static function _form() {
+ $form = new Forge("reauthenticate/auth", "", "post", array("id" => "g-reauthenticate-form"));
+ $form->set_attr("class", "g-narrow");
+ $group = $form->group("reauthenticate")->label(t("Re-authenticate"));
+ $group->password("password")->label(t("Password"))->id("g-password")->class(null)
+ ->callback("auth::validate_too_many_failed_auth_attempts")
+ ->callback("Reauthenticate_Controller::valid_password")
+ ->error_messages("invalid_password", t("Incorrect password"))
+ ->error_messages(
+ "too_many_failed_auth_attempts",
+ t("Too many incorrect passwords. Try again later"));
+ $group->submit("")->value(t("Submit"));
+ return $form;
+ }
+
+ static function valid_password($password_input) {
+ if (!identity::is_correct_password(identity::active_user(), $password_input->value)) {
+ $password_input->add_error("invalid_password", 1);
+ }
+ }
+}
diff --git a/modules/gallery/controllers/upgrader.php b/modules/gallery/controllers/upgrader.php
new file mode 100644
index 0000000..6b3a9ef
--- /dev/null
+++ b/modules/gallery/controllers/upgrader.php
@@ -0,0 +1,118 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Upgrader_Controller extends Controller {
+ public function index() {
+ $session = Session::instance();
+
+ // Make sure we have an upgrade token
+ if (!($upgrade_token = $session->get("upgrade_token", null))) {
+ $session->set("upgrade_token", $upgrade_token = random::hash());
+ }
+
+ // If the upgrade token exists, then bless this session
+ if (file_exists(TMPPATH . $upgrade_token)) {
+ $session->set("can_upgrade", true);
+ @unlink(TMPPATH . $upgrade_token);
+ }
+
+ $available_upgrades = 0;
+ foreach (module::available() as $module) {
+ if ($module->version && $module->version != $module->code_version) {
+ $available_upgrades++;
+ }
+ }
+
+ $failed = Input::instance()->get("failed");
+ $view = new View("upgrader.html");
+ $view->can_upgrade = identity::active_user()->admin || $session->get("can_upgrade");
+ $view->upgrade_token = $upgrade_token;
+ $view->available = module::available();
+ $view->failed = $failed ? explode(",", $failed) : array();
+ $view->done = $available_upgrades == 0;
+ $view->obsolete_modules_message = module::get_obsolete_modules_message();
+ print $view;
+ }
+
+ public function upgrade() {
+ if (php_sapi_name() == "cli") {
+ // @todo this may screw up some module installers, but we don't have a better answer at
+ // this time.
+ $_SERVER["HTTP_HOST"] = "example.com";
+ } else {
+ if (!identity::active_user()->admin && !Session::instance()->get("can_upgrade", false)) {
+ access::forbidden();
+ }
+
+ try {
+ access::verify_csrf();
+ } catch (Exception $e) {
+ url::redirect("upgrader");
+ }
+ }
+
+ $available = module::available();
+ // Upgrade gallery first
+ $gallery = $available["gallery"];
+ if ($gallery->code_version != $gallery->version) {
+ module::upgrade("gallery");
+ module::activate("gallery");
+ }
+
+ // Then upgrade the rest
+ $failed = array();
+ foreach (module::available() as $id => $module) {
+ if ($id == "gallery") {
+ continue;
+ }
+
+ if ($module->active && $module->code_version != $module->version) {
+ try {
+ module::upgrade($id);
+ } catch (Exception $e) {
+ // @todo assume it's MODULE_FAILED_TO_UPGRADE for now
+ $failed[] = $id;
+ }
+ }
+ }
+
+ // If the upgrade failed, this will get recreated
+ site_status::clear("upgrade_now");
+
+ // Clear any upgrade check strings, we are probably up to date.
+ site_status::clear("upgrade_checker");
+
+ if (php_sapi_name() == "cli") {
+ if ($failed) {
+ print "Upgrade completed ** WITH FAILURES **\n";
+ print "The following modules were not successfully upgraded:\n";
+ print " " . implode($failed, "\n ") . "\n";
+ print "Try getting newer versions or deactivating those modules\n";
+ } else {
+ print "Upgrade complete\n";
+ }
+ } else {
+ if ($failed) {
+ url::redirect("upgrader?failed=" . join(",", $failed));
+ } else {
+ url::redirect("upgrader");
+ }
+ }
+ }
+}
diff --git a/modules/gallery/controllers/uploader.php b/modules/gallery/controllers/uploader.php
new file mode 100644
index 0000000..8e09dbe
--- /dev/null
+++ b/modules/gallery/controllers/uploader.php
@@ -0,0 +1,133 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Uploader_Controller extends Controller {
+ public function index($id) {
+ $item = ORM::factory("item", $id);
+ access::required("view", $item);
+ access::required("add", $item);
+ if (!$item->is_album()) {
+ $item = $item->parent();
+ }
+
+ print $this->_get_add_form($item);
+ }
+
+ public function start() {
+ access::verify_csrf();
+ batch::start();
+ }
+
+ public function add_photo($id) {
+ $album = ORM::factory("item", $id);
+ access::required("view", $album);
+ access::required("add", $album);
+ access::verify_csrf();
+
+ // The Flash uploader not call /start directly, so simulate it here for now.
+ if (!batch::in_progress()) {
+ batch::start();
+ }
+
+ $form = $this->_get_add_form($album);
+
+ // Uploadify adds its own field to the form, so validate that separately.
+ $file_validation = new Validation($_FILES);
+ $file_validation->add_rules(
+ "Filedata", "upload::valid", "upload::required",
+ "upload::type[" . implode(",", legal_file::get_extensions()) . "]");
+
+ if ($form->validate() && $file_validation->validate()) {
+ $temp_filename = upload::save("Filedata");
+ system::delete_later($temp_filename);
+ try {
+ $item = ORM::factory("item");
+ $item->name = substr(basename($temp_filename), 10); // Skip unique identifier Kohana adds
+ $item->title = item::convert_filename_to_title($item->name);
+ $item->parent_id = $album->id;
+ $item->set_data_file($temp_filename);
+
+ $path_info = @pathinfo($temp_filename);
+ if (array_key_exists("extension", $path_info) &&
+ legal_file::get_movie_extensions($path_info["extension"])) {
+ $item->type = "movie";
+ $item->save();
+ log::success("content", t("Added a movie"),
+ html::anchor("movies/$item->id", t("view movie")));
+ } else {
+ $item->type = "photo";
+ $item->save();
+ log::success("content", t("Added a photo"),
+ html::anchor("photos/$item->id", t("view photo")));
+ }
+
+ module::event("add_photos_form_completed", $item, $form);
+ } catch (Exception $e) {
+ // The Flash uploader has no good way of reporting complex errors, so just keep it simple.
+ Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
+
+ // Ugh. I hate to use instanceof, But this beats catching the exception separately since
+ // we mostly want to treat it the same way as all other exceptions
+ if ($e instanceof ORM_Validation_Exception) {
+ Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1));
+ }
+
+ header("HTTP/1.1 500 Internal Server Error");
+ print "ERROR: " . $e->getMessage();
+ return;
+ }
+ print "FILEID: $item->id";
+ } else {
+ header("HTTP/1.1 400 Bad Request");
+ print "ERROR: " . t("Invalid upload");
+ }
+ }
+
+ public function status($success_count, $error_count) {
+ if ($error_count) {
+ // The "errors" won't be properly pluralized :-/
+ print t2("Uploaded %count photo (%error errors)",
+ "Uploaded %count photos (%error errors)",
+ (int)$success_count,
+ array("error" => (int)$error_count));
+ } else {
+ print t2("Uploaded %count photo", "Uploaded %count photos", $success_count);}
+ }
+
+ public function finish() {
+ access::verify_csrf();
+
+ batch::stop();
+ json::reply(array("result" => "success"));
+ }
+
+ private function _get_add_form($album) {
+ $form = new Forge("uploader/finish", "", "post", array("id" => "g-add-photos-form"));
+ $group = $form->group("add_photos")
+ ->label(t("Add photos to %album_title", array("album_title" => html::purify($album->title))));
+ $group->uploadify("uploadify")->album($album);
+
+ $group = $form->group("actions");
+ $group->uploadify_buttons("");
+
+ module::event("add_photos_form", $album, $form);
+
+ return $form;
+ }
+}
diff --git a/modules/gallery/controllers/user_profile.php b/modules/gallery/controllers/user_profile.php
new file mode 100644
index 0000000..cf589da
--- /dev/null
+++ b/modules/gallery/controllers/user_profile.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 User_Profile_Controller extends Controller {
+
+ public function show($id) {
+ // If we get here, then we should have a user id other than guest.
+ $user = identity::lookup_user($id);
+ if (!$user) {
+ throw new Kohana_404_Exception();
+ }
+
+ if (!$this->_can_view_profile_pages($user)) {
+ throw new Kohana_404_Exception();
+ }
+
+ $v = new Theme_View("page.html", "other", "profile");
+ $v->page_title = t("%name Profile", array("name" => $user->display_name()));
+ $v->content = new View("user_profile.html");
+
+ $v->content->user = $user;
+ $v->content->contactable =
+ !$user->guest && $user->id != identity::active_user()->id && $user->email;
+ $v->content->editable =
+ identity::is_writable() && !$user->guest && $user->id == identity::active_user()->id;
+
+ $event_data = (object)array("user" => $user, "content" => array());
+ module::event("show_user_profile", $event_data);
+ $v->content->info_parts = $event_data->content;
+
+ print $v;
+ }
+
+ public function contact($id) {
+ $user = identity::lookup_user($id);
+ if (!$this->_can_view_profile_pages($user)) {
+ throw new Kohana_404_Exception();
+ }
+
+ print user_profile::get_contact_form($user);
+ }
+
+ public function send($id) {
+ access::verify_csrf();
+ $user = identity::lookup_user($id);
+ if (!$this->_can_view_profile_pages($user)) {
+ throw new Kohana_404_Exception();
+ }
+
+ $form = user_profile::get_contact_form($user);
+ if ($form->validate()) {
+ Sendmail::factory()
+ ->to($user->email)
+ ->subject(html::clean($form->message->subject->value))
+ ->header("Mime-Version", "1.0")
+ ->header("Content-type", "text/html; charset=UTF-8")
+ ->reply_to($form->message->reply_to->value)
+ ->message(html::purify($form->message->message->value))
+ ->send();
+ message::success(t("Sent message to %user_name", array("user_name" => $user->display_name())));
+ json::reply(array("result" => "success"));
+ } else {
+ json::reply(array("result" => "error", "html" => (string)$form));
+ }
+ }
+
+ private function _can_view_profile_pages($user) {
+ if (!$user->loaded()) {
+ return false;
+ }
+
+ if ($user->id == identity::active_user()->id) {
+ // You can always view your own profile
+ return true;
+ }
+
+ switch (module::get_var("gallery", "show_user_profiles_to")) {
+ case "admin_users":
+ return identity::active_user()->admin;
+
+ case "registered_users":
+ return !identity::active_user()->guest;
+
+ case "everybody":
+ return true;
+
+ default:
+ // Fail in private mode on an invalid setting
+ return false;
+ }
+ }
+}
diff --git a/modules/gallery/controllers/welcome_message.php b/modules/gallery/controllers/welcome_message.php
new file mode 100644
index 0000000..bde5e94
--- /dev/null
+++ b/modules/gallery/controllers/welcome_message.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Welcome_Message_Controller extends Controller {
+ public function index() {
+ if (!identity::active_user()->admin) {
+ url::redirect(item::root()->abs_url());
+ }
+
+ $v = new View("welcome_message.html");
+ $v->user = identity::active_user();
+ print $v;
+ }
+}
diff --git a/modules/gallery/css/debug.css b/modules/gallery/css/debug.css
new file mode 100644
index 0000000..6808da0
--- /dev/null
+++ b/modules/gallery/css/debug.css
@@ -0,0 +1,28 @@
+.g-annotated-theme-block {
+ border: 1px solid #C00;
+ clear: both;
+ margin: 1em;
+ padding: 1em;
+ position: relative;
+}
+
+.g-annotated-theme-block_album_top {
+ float: right;
+}
+
+.g-annotated-theme-block_header_bottom {
+ float: right;
+}
+
+.g-annotated-theme-block 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/gallery.css b/modules/gallery/css/gallery.css
new file mode 100644
index 0000000..7e71115
--- /dev/null
+++ b/modules/gallery/css/gallery.css
@@ -0,0 +1,213 @@
+/**
+ * Gallery 3 core module styles
+ *
+ * Sheet organization:
+ * 1) End-user
+ * 2) Admin
+ * 3) Right to left language styles
+ */
+
+/** *******************************************************************
+ * 1) End-user
+ **********************************************************************/
+
+/* Uploader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+#g-add-photos-canvas,
+#g-add-photos-status {
+ width: 469px;
+}
+
+#g-add-photos-canvas {
+ border: 1px solid #ccc;
+ height: 200px;
+ margin: .5em 0;
+ padding: 2.8em 0 0 0;
+ overflow: auto;
+ position: relative;
+}
+
+#g-add-photos-canvas object,
+#g-add-photos-button {
+ left: 90px;
+ margin: .5em 0;
+ padding: .4em 1em;
+ position: absolute;
+ top: 0;
+ width: 300px;
+}
+
+#g-add-photos-canvas object {
+ z-index: 100;
+ padding: 0em;
+}
+
+#g-add-photos-canvas .uploadifyQueue {
+ margin-top: .5em;
+}
+
+#g-add-photos-canvas .uploadifyQueueItem {
+ margin: 0;
+}
+
+#g-add-photos-button {
+ z-index: 1;
+}
+
+#g-add-photos-status {
+ border: 1px solid #ccc;
+ height: 125px;
+ margin: .4em 0;
+ overflow: auto;
+}
+
+#g-add-photos-status .g-message-block {
+ border: none;
+}
+
+#g-add-photos-status #g-action-status li {
+ margin: 0 0 1px 0;
+ padding-top: .7em;
+ width: 433px;
+}
+
+#g-add-photos-form .g-breadcrumbs {
+ margin: 0;
+}
+
+#g-add-photos-form p {
+ margin-bottom: 0
+}
+
+#g-add-photos-status-message {
+ float: right;
+}
+
+/* Permissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+#g-edit-permissions-form {
+ clear: both;
+}
+
+#g-edit-permissions-form th {
+ text-align: center;
+}
+
+#g-permissions .g-denied,
+#g-permissions .g-allowed {
+ text-align: center;
+ vertical-align: middle;
+}
+
+/* Move items ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+#g-move ul {
+ padding-left: 1em;
+}
+
+#g-move .selected {
+ background: #999;
+}
+
+/* In-place edit ~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+#g-in-place-edit-form ul {
+ margin: 0;
+}
+
+/* User profile ~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+#g-user-profile h1 {
+ margin: 1em 0;
+}
+
+#g-user-profile .g-avatar {
+ margin-right: .6em;
+}
+
+#g-user-profile .g-block {
+ margin-top: 0;
+}
+
+#g-user-profile .g-block-content {
+ margin-top: 0;
+}
+
+#g-user-profile th,
+#g-user-profile td {
+ border: none;
+}
+
+#g-user-profile th {
+ white-space: nowrap;
+ width: 1%;
+}
+
+/* Unsupported movie download link ~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+.g-movie-download-link {
+ text-align: center;
+}
+
+/** *******************************************************************
+ * 2) Admin
+ **********************************************************************/
+
+.g-task-log {
+ border: 1px solid #000;
+ height: 400px;
+ margin: .6em 0;
+ overflow: auto;
+ padding: .4em
+}
+
+#g-languages-form table {
+ width: 40%;
+ margin: 0 3em 1em 0;
+}
+
+#g-languages-form input {
+ clear: both;
+}
+
+#g-translations ol {
+ margin: 0 0 1em 2em;
+}
+#g-translations ol li {
+ list-style-type: decimal;
+ line-height: 150%;
+}
+
+#g-translations .g-button {
+ padding: .5em;
+ margin-bottom: 1em;
+}
+
+/** *******************************************************************
+ * 3) Right to left language styles
+ **********************************************************************/
+
+.rtl #g-add-photos-status #g-action-status li {
+ width: 407px;
+}
+
+.rtl #g-block-admin .g-left {
+ margin-left: 1em;
+ margin-right: 0;
+}
+
+.rtl #g-user-profile .g-avatar {
+ margin-left: .6em;
+}
+
+.rtl #g-languages-form table {
+ margin: 0 0 1em 3em;
+}
+
+.rtl #g-translations ol {
+ margin: 0 2em 1em 0;
+}
+
+.rtl #g-add-photos-status-message {
+ float: left;
+}
diff --git a/modules/gallery/css/l10n_client.css b/modules/gallery/css/l10n_client.css
new file mode 100644
index 0000000..90034d0
--- /dev/null
+++ b/modules/gallery/css/l10n_client.css
@@ -0,0 +1,206 @@
+/**
+ * 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-toggler {
+ cursor:pointer;
+ display:block;
+ position:absolute; right:0em;
+ height:2em; line-height:2em;
+ text-align:center; background:#000;
+}
+#l10n-client-toggler a {
+ font-size: 1em;
+ padding: .5em;
+}
+#l10n-client-toggler #g-minimize-l10n {
+ border-right: 1px solid #ffffff;
+}
+
+/* Panel labels */
+#l10n-client h2 {
+ border-left:1px solid #fff;
+ height:1em; line-height:1em;
+ padding: .5em; margin:0px;
+ font-size:1em;
+}
+
+#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%;
+ direction: ltr;
+}
+
+#l10n-client .string-list {
+ height: 16em;
+ 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 #g-l10n-search-form {
+ background: #eee;
+ margin: 0em;
+ padding: .25em .25em;
+}
+
+#l10n-client #g-l10n-search-form .form-item,
+#l10n-client #g-l10n-search-form input.form-text,
+#l10n-client #g-l10n-search-form #search-filter-go,
+#l10n-client #g-l10n-search-form #search-filter-clear {
+ display:inline;
+ vertical-align:middle;
+}
+
+#l10n-client #g-l10n-search-form .form-item {
+ margin:0em;
+ padding:0em;
+}
+
+#l10n-client #g-l10n-search-form fieldset {
+ margin-bottom: 0;
+ padding: .25em .25em;
+}
+
+
+#l10n-client #g-l10n-search-form input {
+ width: 96.75%;
+}
+
+#l10n-client #g-l10n-search-form #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;
+ font-family: monospace;
+ text-align: left;
+ height:16em; margin:1em; padding:1em;
+ overflow:auto;
+ direction: ltr;
+}
+
+#l10n-client-string-editor .translation {
+ overflow-y:auto;
+ overflow-x: hidden;
+ height: 20em;
+ width:49%;
+ float: right;
+}
+
+#g-l10n-client-save-form {
+ padding: 0em;
+}
+
+#l10n-client form ul,
+#l10n-client form li,
+#l10n-client form input[type=submit],
+#l10n-client form input[type=text] {
+ display: inline ! important ;
+}
+
+#l10n-client form .hidden {
+ display: none;
+}
diff --git a/modules/gallery/css/upgrader.css b/modules/gallery/css/upgrader.css
new file mode 100644
index 0000000..8610016
--- /dev/null
+++ b/modules/gallery/css/upgrader.css
@@ -0,0 +1,183 @@
+body {
+ background: #eee;
+ font-family: 'Trebuchet MS';
+ font-size: 1.1em;
+}
+
+h1 {
+ font-size: 1.4em;
+}
+
+div#outer {
+ background: #fff;
+ border: 1px solid #999;
+ margin: 0 auto;
+ width: 650px;
+}
+
+div#inner {
+ margin: 0;
+ padding: 0 1em;
+}
+
+div#footer {
+ border-top: 1px solid #ccc;
+ margin: 1em;
+}
+
+table {
+ width: 600px;
+ margin-bottom: 10px;
+}
+
+th.name,
+td.name {
+ text-align: left;
+ padding-left: 30px;
+}
+
+td {
+ text-align: center;
+ border-bottom: 1px solid #eee;
+}
+
+tr.current td {
+ opacity: 0.5;
+ font-style: italic;
+}
+
+tr.current td.gallery {
+ color: #00d;
+}
+
+tr.upgradeable td {
+ font-weight: bold;
+}
+
+tr.upgradeable td.gallery {
+ color: #00d;
+}
+
+tr.failed td {
+ color: red;
+}
+
+p {
+ font-size: .9em;
+}
+
+ul {
+ font-size: .9em;
+ list-style: none;
+}
+
+li {
+ display: inline;
+}
+
+li:before {
+ content: "\00BB \0020";
+}
+
+div.button {
+ margin: 0 auto;
+ width: 120px;
+ text-align: center;
+ border: 1px solid #999;
+ background: #eee;
+}
+
+div.button a {
+ text-decoration: none;
+}
+
+div.button-active:hover {
+ background: #ccc;
+}
+
+div#dialog {
+ width: 340px;
+ height: 200px;
+ position: absolute;
+ background: blue;
+ z-index: 1000;
+ margin: 10px;
+ text-align: center;
+}
+
+div#dialog a.close {
+ float: right;
+ padding: 10px;
+ text-decoration: none;
+}
+
+div#dialog div {
+ width: 292px;
+ height: 152px;
+ margin: 2px;
+ padding: 20px;
+ border: 2px solid #999;
+ background: #eee;
+}
+
+.muted {
+ opacity: 0.5;
+}
+
+.failed {
+ color: red;
+}
+
+pre {
+ display: inline;
+ margin: 0px;
+ padding: 0px;
+}
+
+div#upgrade_button {
+ margin-bottom: 20px;
+}
+
+div#welcome_message {
+ margin-left: 30px;
+}
+
+#logo {
+ margin-left: 14px;
+}
+
+.rtl {
+ direction: rtl;
+}
+
+.rtl th.name,
+.rtl td.name {
+ text-align: right;
+ padding-right: 30px;
+}
+
+
+.rtl li:before {
+ content: "";
+}
+
+.rtl li:after {
+ content: "\00BB \0020";
+}
+
+.rtl ul {
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.rtl div#dialog a.close {
+ float: left;
+}
+
+.rtl div#welcome_message {
+ padding-right: 30px;
+}
+
+.rtl #logo {
+ padding-right: 12px;
+}
diff --git a/modules/gallery/helpers/MY_html.php b/modules/gallery/helpers/MY_html.php
new file mode 100644
index 0000000..767fe3f
--- /dev/null
+++ b/modules/gallery/helpers/MY_html.php
@@ -0,0 +1,91 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 html extends html_Core {
+ /**
+ * Returns a string that is safe to be used in HTML (XSS protection).
+ *
+ * If $html is a string, the returned string will be HTML escaped.
+ * If $html is a SafeString instance, the returned string may contain
+ * unescaped HTML which is assumed to be safe.
+ *
+ * Example:<pre>
+ * <div><?= html::clean($php_var) ?>
+ * </pre>
+ */
+ static function clean($html) {
+ return new SafeString($html);
+ }
+
+ /**
+ * Returns a string that is safe to be used in HTML (XSS protection),
+ * purifying (filtering) the given HTML to ensure that the result contains
+ * only non-malicious HTML.
+ *
+ * Example:<pre>
+ * <div><?= html::purify($item->title) ?>
+ * </pre>
+ */
+ static function purify($html) {
+ return SafeString::purify($html);
+ }
+
+ /**
+ * Flags the given string as safe to be used in HTML (free of malicious HTML/JS).
+ *
+ * Example:<pre>
+ * // Parameters to t() are automatically escaped by default.
+ * // If the parameter is marked as clean, it won't get escaped.
+ * t('Go <a href="%url">there</a>',
+ * array("url" => html::mark_clean(url::current())))
+ * </pre>
+ */
+ static function mark_clean($html) {
+ return SafeString::of_safe_html($html);
+ }
+
+ /**
+ * Escapes the given string for use in JavaScript.
+ *
+ * Example:<pre>
+ * <script type="text/javascript>"
+ * var some_js_string = <?= html::js_string($php_string) ?>;
+ * </script>
+ * </pre>
+ */
+ static function js_string($string) {
+ return SafeString::of($string)->for_js();
+ }
+
+ /**
+ * Returns a string safe for use in HTML element attributes.
+ *
+ * Assumes that the HTML element attribute is already
+ * delimited by single or double quotes
+ *
+ * Example:<pre>
+ * <a title="<?= html::clean_for_attribute($php_var) ?>">;
+ * </script>
+ * </pre>
+ * @return the string escaped for use in HTML attributes.
+ */
+ static function clean_attribute($string) {
+ return html::clean($string)->for_html_attr();
+ }
+}
diff --git a/modules/gallery/helpers/MY_num.php b/modules/gallery/helpers/MY_num.php
new file mode 100644
index 0000000..a550a1a
--- /dev/null
+++ b/modules/gallery/helpers/MY_num.php
@@ -0,0 +1,54 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 num extends num_Core {
+ /**
+ * Convert a size value as accepted by PHP's shorthand to bytes.
+ * ref: http://us2.php.net/manual/en/function.ini-get.php
+ * ref: http://us2.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
+ */
+ static function convert_to_bytes($val) {
+ $val = trim($val);
+ $last = strtolower($val[strlen($val)-1]);
+ switch($last) {
+ case 'g':
+ $val *= 1024;
+ case 'm':
+ $val *= 1024;
+ case 'k':
+ $val *= 1024;
+ }
+
+ return $val;
+ }
+
+ /**
+ * Convert a size value as accepted by PHP's shorthand to bytes.
+ * ref: http://us2.php.net/manual/en/function.ini-get.php
+ * ref: http://us2.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
+ */
+ static function convert_to_human_readable($num) {
+ foreach (array("G" => 1e9, "M" => 1e6, "K" => 1e3) as $k => $v) {
+ if ($num > $v) {
+ $num = round($num / $v) . $k;
+ }
+ }
+ return $num;
+ }
+}
diff --git a/modules/gallery/helpers/MY_remote.php b/modules/gallery/helpers/MY_remote.php
new file mode 100644
index 0000000..59804b9
--- /dev/null
+++ b/modules/gallery/helpers/MY_remote.php
@@ -0,0 +1,167 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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) =
+ remote::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='') {
+ if (!array_key_exists("User-Agent", $headers)) {
+ $headers["User-Agent"] = "Gallery3";
+ }
+ /* 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('&amp;', '&', $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 0000000..eba08b2
--- /dev/null
+++ b/modules/gallery/helpers/MY_url.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 parse_url() {
+ if (Router::$controller) {
+ return;
+ }
+
+ // Work around problems with the CGI sapi by enforcing our default path
+ if ($_SERVER["SCRIPT_NAME"] && "/" . Router::$current_uri == $_SERVER["SCRIPT_NAME"]) {
+ Router::$controller_path = MODPATH . "gallery/controllers/albums.php";
+ Router::$controller = "albums";
+ Router::$method = 1;
+ return;
+ }
+
+ $item = item::find_by_relative_url(html_entity_decode(Router::$current_uri, ENT_QUOTES));
+ if ($item && $item->loaded()) {
+ Router::$controller = "{$item->type}s";
+ Router::$controller_path = MODPATH . "gallery/controllers/{$item->type}s.php";
+ Router::$method = "show";
+ Router::$arguments = array($item);
+ }
+ }
+
+ /**
+ * Just like url::file() except that it returns an absolute URI
+ */
+ static function abs_file($path) {
+ return url::base(false, request::protocol()) . $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, request::protocol());
+ }
+
+ /**
+ * Just like url::current except that it returns an absolute URI
+ */
+ static function abs_current($qs=false) {
+ return self::abs_site(url::current($qs));
+ }
+
+ /**
+ * Just like url::merge except that it escapes any XSS in the path.
+ */
+ static function merge(array $arguments) {
+ return htmlspecialchars(parent::merge($arguments));
+ }
+
+ /**
+ * Just like url::current except that it escapes any XSS in the path.
+ */
+ static function current($qs=false, $suffix=false) {
+ return htmlspecialchars(parent::current($qs, $suffix));
+ }
+
+ /**
+ * Merge extra an query string onto a given url safely.
+ * @param string the original url
+ * @param array the query string data in key=value form
+ */
+ static function merge_querystring($url, $query_params) {
+ $qs = implode("&", $query_params);
+ if (strpos($url, "?") === false) {
+ return $url . "?$qs";
+ } else {
+ return $url . "&$qs";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/MY_valid.php b/modules/gallery/helpers/MY_valid.php
new file mode 100644
index 0000000..f1dd9c3
--- /dev/null
+++ b/modules/gallery/helpers/MY_valid.php
@@ -0,0 +1,26 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 valid extends valid_Core {
+ static function url($url) {
+ return valid_Core::url($url) &&
+ (!strncasecmp($url, "http://", strlen("http://")) ||
+ !strncasecmp($url, "https://", strlen("https://")));
+ }
+}
diff --git a/modules/gallery/helpers/access.php b/modules/gallery/helpers/access.php
new file mode 100644
index 0000000..a7dca57
--- /dev/null
+++ b/modules/gallery/helpers/access.php
@@ -0,0 +1,758 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 INHERIT = null; // access_intent
+ const UNKNOWN = null; // cache (access_cache, items)
+
+ /**
+ * 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) {
+ return access::user_can(identity::active_user(), $perm_name, $item);
+ }
+
+ /**
+ * Does the user have this permission on this item?
+ *
+ * @param User_Model $user
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return boolean
+ */
+ static function user_can($user, $perm_name, $item) {
+ if (!$item->loaded()) {
+ return false;
+ }
+
+ if ($user->admin) {
+ return true;
+ }
+
+ // Use the nearest parent album (including the current item) so that we take advantage
+ // of the cache when checking many items in a single album.
+ $id = ($item->type == "album") ? $item->id : $item->parent_id;
+ $resource = $perm_name == "view" ?
+ $item : model_cache::get("access_cache", $id, "item_id");
+
+ foreach ($user->groups() as $group) {
+ if ($resource->__get("{$perm_name}_{$group->id}") === access::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 (!access::can($perm_name, $item)) {
+ if ($perm_name == "view") {
+ // Treat as if the item didn't exist, don't leak any information.
+ throw new Kohana_404_Exception();
+ } else {
+ access::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) {
+ // Use the nearest parent album (including the current item) so that we take advantage
+ // of the cache when checking many items in a single album.
+ $id = ($item->type == "album") ? $item->id : $item->parent_id;
+ $resource = $perm_name == "view" ?
+ $item : model_cache::get("access_cache", $id, "item_id");
+
+ return $resource->__get("{$perm_name}_{$group->id}") === access::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 boolean access::ALLOW, access::DENY or access::INHERIT (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 access::DENY, then those parents lock this one.
+ // Return
+ $lock = ORM::factory("item")
+ ->where("left_ptr", "<=", $item->left_ptr)
+ ->where("right_ptr", ">=", $item->right_ptr)
+ ->where("items.id", "<>", $item->id)
+ ->join("access_intents", "items.id", "access_intents.item_id")
+ ->where("access_intents.view_$group->id", "=", access::DENY)
+ ->order_by("level", "DESC")
+ ->limit(1)
+ ->find();
+
+ if ($lock->loaded()) {
+ return $lock;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Terminate immediately with an HTTP 403 Forbidden response.
+ */
+ static function forbidden() {
+ throw new Kohana_Exception("@todo FORBIDDEN", null, 403);
+ }
+
+ /**
+ * 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_Definition $group, $perm_name, $album, $value) {
+ if (!($group instanceof Group_Definition)) {
+ 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);
+ }
+
+ access::update_htaccess_files($album, $group, $perm_name, $value);
+ model_cache::clear();
+ }
+
+ /**
+ * 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, self::INHERIT);
+ }
+
+ /**
+ * Recalculate the permissions for an album's hierarchy.
+ */
+ static function recalculate_album_permissions($album) {
+ foreach (self::_get_all_groups() as $group) {
+ foreach (ORM::factory("permission")->find_all() as $perm) {
+ if ($perm->name == "view") {
+ self::_update_access_view_cache($group, $album);
+ } else {
+ self::_update_access_non_view_cache($group, $perm->name, $album);
+ }
+ }
+ }
+ model_cache::clear();
+ }
+
+ /**
+ * Recalculate the permissions for a single photo.
+ */
+ static function recalculate_photo_permissions($photo) {
+ $parent = $photo->parent();
+ $parent_access_cache = ORM::factory("access_cache")->where("item_id", "=", $parent->id)->find();
+ $photo_access_cache = ORM::factory("access_cache")->where("item_id", "=", $photo->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") {
+ $photo->$field = $parent->$field;
+ } else {
+ $photo_access_cache->$field = $parent_access_cache->$field;
+ }
+ }
+ }
+ $photo_access_cache->save();
+ $photo->save();
+ model_cache::clear();
+ }
+
+ /**
+ * 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")) {
+ access::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 = random::hash();
+ $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=\"" . access::csrf_token() . "\"/>";
+ }
+
+ /**
+ * Internal method to get all available groups.
+ *
+ * @return ORM_Iterator
+ */
+ private static function _get_all_groups() {
+ // When we build the gallery package, it's possible that there is no identity provider
+ // installed yet. This is ok at packaging time, so work around it.
+ if (module::is_active(module::get_var("gallery", "identity_provider", "user"))) {
+ return identity::groups();
+ } 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) {
+ $field = "{$perm_name}_{$group->id}";
+ $cache_table = $perm_name == "view" ? "items" : "access_caches";
+ Database::instance()->query("ALTER TABLE {{$cache_table}} DROP `$field`");
+ Database::instance()->query("ALTER TABLE {access_intents} DROP `$field`");
+ model_cache::clear();
+ 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) {
+ $field = "{$perm_name}_{$group->id}";
+ $cache_table = $perm_name == "view" ? "items" : "access_caches";
+ $not_null = $cache_table == "items" ? "" : "NOT NULL";
+ Database::instance()->query(
+ "ALTER TABLE {{$cache_table}} ADD `$field` BINARY $not_null DEFAULT FALSE");
+ Database::instance()->query(
+ "ALTER TABLE {access_intents} ADD `$field` BINARY DEFAULT NULL");
+ db::build()
+ ->update("access_intents")
+ ->set($field, access::DENY)
+ ->where("item_id", "=", 1)
+ ->execute();
+ model_cache::clear();
+ 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();
+ $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 !== access::DENY) {
+ $tmp_item = ORM::factory("item")
+ ->where("left_ptr", "<", $item->left_ptr)
+ ->where("right_ptr", ">", $item->right_ptr)
+ ->join("access_intents", "access_intents.item_id", "items.id")
+ ->where("access_intents.$field", "=", access::DENY)
+ ->order_by("left_ptr", "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::build()
+ ->update("items")
+ ->set($field, access::UNKNOWN)
+ ->where("left_ptr", ">=", $item->left_ptr)
+ ->where("right_ptr", "<=", $item->right_ptr)
+ ->execute();
+
+ $query = ORM::factory("access_intent")
+ ->select(array("access_intents.$field", "items.left_ptr", "items.right_ptr", "items.id"))
+ ->join("items", "items.id", "access_intents.item_id")
+ ->where("left_ptr", ">=", $item->left_ptr)
+ ->where("right_ptr", "<=", $item->right_ptr)
+ ->where("type", "=", "album")
+ ->where("access_intents.$field", "IS NOT", access::INHERIT)
+ ->order_by("level", "DESC")
+ ->find_all();
+ foreach ($query as $row) {
+ if ($row->$field == access::ALLOW) {
+ // Propagate ALLOW for any row that is still UNKNOWN.
+ db::build()
+ ->update("items")
+ ->set($field, $row->$field)
+ ->where($field, "IS", access::UNKNOWN) // UNKNOWN is NULL so we have to use IS
+ ->where("left_ptr", ">=", $row->left_ptr)
+ ->where("right_ptr", "<=", $row->right_ptr)
+ ->execute();
+ } else if ($row->$field == access::DENY) {
+ // DENY overwrites everything below it
+ db::build()
+ ->update("items")
+ ->set($field, $row->$field)
+ ->where("left_ptr", ">=", $row->left_ptr)
+ ->where("right_ptr", "<=", $row->right_ptr)
+ ->execute();
+ }
+ }
+
+ // 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::build()
+ ->update("items")
+ ->set($field, access::ALLOW)
+ ->where($field, "IS", access::UNKNOWN) // UNKNOWN is NULL so we have to use IS
+ ->where("left_ptr", ">=", $item->left_ptr)
+ ->where("right_ptr", "<=", $item->right_ptr)
+ ->execute();
+ }
+
+ /**
+ * 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();
+
+ $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 === access::INHERIT) {
+ $tmp_item = ORM::factory("item")
+ ->join("access_intents", "items.id", "access_intents.item_id")
+ ->where("left_ptr", "<", $item->left_ptr)
+ ->where("right_ptr", ">", $item->right_ptr)
+ ->where($field, "IS NOT", access::UNKNOWN) // UNKNOWN is NULL so we have to use IS NOT
+ ->order_by("left_ptr", "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_ptr", "items.right_ptr"))
+ ->join("items", "items.id", "access_intents.item_id")
+ ->where("left_ptr", ">=", $item->left_ptr)
+ ->where("right_ptr", "<=", $item->right_ptr)
+ ->where($field, "IS NOT", access::INHERIT)
+ ->order_by("level", "ASC")
+ ->find_all();
+ foreach ($query as $row) {
+ $value = ($row->$field === access::ALLOW) ? true : false;
+ db::build()
+ ->update("access_caches")
+ ->set($field, $value)
+ ->where("item_id", "IN",
+ db::build()
+ ->select("id")
+ ->from("items")
+ ->where("left_ptr", ">=", $row->left_ptr)
+ ->where("right_ptr", "<=", $row->right_ptr))
+ ->execute();
+ }
+ }
+
+ /**
+ * Rebuild the .htaccess files that prevent direct access to albums, resizes and thumbnails. We
+ * call this internally any time we change the view or view_full permissions for guest users.
+ * This function is only public because we use it in maintenance tasks.
+ *
+ * @param Item_Model the album
+ * @param Group_Model the group whose permission is changing
+ * @param string the permission name
+ * @param string the new permission value (eg access::DENY)
+ */
+ static function update_htaccess_files($album, $group, $perm_name, $value) {
+ if ($group->id != identity::everybody()->id ||
+ !($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::base(true);
+ $sep = "?";
+ if (strpos($base_url, "?") !== false) {
+ $sep = "&";
+ }
+ $base_url .= $sep . "kohana_uri=/file_proxy";
+ // Replace "/index.php/?kohana..." with "/index.php?koahan..."
+ // Doesn't apply to "/?kohana..." or "/foo/?kohana..."
+ // Can't check for "index.php" since the file might be renamed, and
+ // there might be more Apache aliases / rewrites at work.
+ $url_path = parse_url($base_url, PHP_URL_PATH);
+ // Does the URL path have a file component?
+ if (preg_match("#[^/]+\.php#i", $url_path)) {
+ $base_url = str_replace("/?", "?", $base_url);
+ }
+
+ foreach ($dirs as $dir) {
+ if ($value === access::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("gallery", "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/security_test/success");
+
+ @mkdir(VARPATH . "security_test");
+ try {
+ if ($fp = @fopen(VARPATH . "security_test/.htaccess", "w+")) {
+ fwrite($fp, "Options +FollowSymLinks\n");
+ fwrite($fp, "RewriteEngine On\n");
+ fwrite($fp, "RewriteRule verify $success_url [L]\n");
+ fclose($fp);
+ }
+
+ if ($fp = @fopen(VARPATH . "security_test/success", "w+")) {
+ fwrite($fp, "success");
+ fclose($fp);
+ }
+
+ // Proxy our authorization headers so that if the entire Gallery is covered by Basic Auth
+ // this callback will still work.
+ $headers = array();
+ if (function_exists("apache_request_headers")) {
+ $arh = apache_request_headers();
+ if (!empty($arh["Authorization"])) {
+ $headers["Authorization"] = $arh["Authorization"];
+ }
+ }
+ list ($status, $headers, $body) =
+ remote::do_request(url::abs_file("var/security_test/verify"), "GET", $headers);
+ $works = ($status == "HTTP/1.1 200 OK") && ($body == "success");
+ } catch (Exception $e) {
+ @dir::unlink(VARPATH . "security_test");
+ throw $e;
+ }
+ @dir::unlink(VARPATH . "security_test");
+
+ return $works;
+ }
+}
diff --git a/modules/gallery/helpers/ajax.php b/modules/gallery/helpers/ajax.php
new file mode 100644
index 0000000..0c69fe7
--- /dev/null
+++ b/modules/gallery/helpers/ajax.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 ajax_Core {
+ /**
+ * Encode an Ajax response so that it's UTF-7 safe.
+ *
+ * @param string $message string to print
+ */
+ static function response($content) {
+ header("Content-Type: text/plain; charset=" . Kohana::CHARSET);
+ print "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n";
+ print $content;
+ }
+}
diff --git a/modules/gallery/helpers/album.php b/modules/gallery/helpers/album.php
new file mode 100644
index 0000000..23aed8a
--- /dev/null
+++ b/modules/gallery/helpers/album.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+
+ static function get_add_form($parent) {
+ $form = new Forge("albums/create/{$parent->id}", "", "post", array("id" => "g-add-album-form"));
+ $group = $form->group("add_album")
+ ->label(t("Add an album to %album_title", array("album_title" => $parent->title)));
+ $group->input("title")->label(t("Title"))
+ ->error_messages("required", t("You must provide a title"))
+ ->error_messages("length", t("Your title is too long"));
+ $group->textarea("description")->label(t("Description"));
+ $group->input("name")->label(t("Directory name"))
+ ->error_messages("no_slashes", t("The directory name can't contain the \"/\" character"))
+ ->error_messages("required", t("You must provide a directory name"))
+ ->error_messages("length", t("Your directory name is too long"))
+ ->error_messages("conflict", t("There is already a movie, photo or album with this name"));
+ $group->input("slug")->label(t("Internet Address"))
+ ->error_messages(
+ "reserved", t("This address is reserved and can't be used."))
+ ->error_messages(
+ "not_url_safe",
+ t("The internet address should contain only letters, numbers, hyphens and underscores"))
+ ->error_messages("required", t("You must provide an internet address"))
+ ->error_messages("length", t("Your internet address is too long"));
+ $group->hidden("type")->value("album");
+
+ module::event("album_add_form", $parent, $form);
+
+ $group->submit("")->value(t("Create"));
+ $form->script("")
+ ->url(url::abs_file("modules/gallery/js/albums_form_add.js"));
+
+ return $form;
+ }
+
+ static function get_edit_form($parent) {
+ $form = new Forge(
+ "albums/update/{$parent->id}", "", "post", array("id" => "g-edit-album-form"));
+ $form->hidden("from_id")->value($parent->id);
+ $group = $form->group("edit_item")->label(t("Edit Album"));
+
+ $group->input("title")->label(t("Title"))->value($parent->title)
+ ->error_messages("required", t("You must provide a title"))
+ ->error_messages("length", t("Your title is too long"));
+ $group->textarea("description")->label(t("Description"))->value($parent->description);
+ if ($parent->id != 1) {
+ $group->input("name")->label(t("Directory Name"))->value($parent->name)
+ ->error_messages("conflict", t("There is already a movie, photo or album with this name"))
+ ->error_messages("no_slashes", t("The directory name can't contain a \"/\""))
+ ->error_messages("no_trailing_period", t("The directory name can't end in \".\""))
+ ->error_messages("required", t("You must provide a directory name"))
+ ->error_messages("length", t("Your directory name is too long"));
+ $group->input("slug")->label(t("Internet Address"))->value($parent->slug)
+ ->error_messages(
+ "conflict", t("There is already a movie, photo or album with this internet address"))
+ ->error_messages(
+ "reserved", t("This address is reserved and can't be used."))
+ ->error_messages(
+ "not_url_safe",
+ t("The internet address should contain only letters, numbers, hyphens and underscores"))
+ ->error_messages("required", t("You must provide an internet address"))
+ ->error_messages("length", t("Your internet address is too long"));
+ } else {
+ $group->hidden("name")->value($parent->name);
+ $group->hidden("slug")->value($parent->slug);
+ }
+
+ $sort_order = $group->group("sort_order", array("id" => "g-album-sort-order"))
+ ->label(t("Sort Order"));
+
+ $sort_order->dropdown("column", array("id" => "g-album-sort-column"))
+ ->label(t("Sort by"))
+ ->options(album::get_sort_order_options())
+ ->selected($parent->sort_column);
+ $sort_order->dropdown("direction", array("id" => "g-album-sort-direction"))
+ ->label(t("Order"))
+ ->options(array("ASC" => t("Ascending"),
+ "DESC" => t("Descending")))
+ ->selected($parent->sort_order);
+
+ module::event("item_edit_form", $parent, $form);
+
+ $group = $form->group("buttons")->label("");
+ $group->hidden("type")->value("album");
+ $group->submit("")->value(t("Modify"));
+ return $form;
+ }
+
+ /**
+ * Return a structured set of all the possible sort orders.
+ */
+ static function get_sort_order_options() {
+ return array("weight" => t("Manual"),
+ "captured" => t("Date captured"),
+ "created" => t("Date uploaded"),
+ "title" => t("Title"),
+ "name" => t("File name"),
+ "updated" => t("Date modified"),
+ "view_count" => t("Number of views"),
+ "rand_key" => t("Random"));
+ }
+}
diff --git a/modules/gallery/helpers/auth.php b/modules/gallery/helpers/auth.php
new file mode 100644
index 0000000..2eb3c25
--- /dev/null
+++ b/modules/gallery/helpers/auth.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 auth_Core {
+ static function get_login_form($url) {
+ $form = new Forge($url, "", "post", array("id" => "g-login-form"));
+ $form->set_attr("class", "g-narrow");
+ $form->hidden("continue_url")->value(Session::instance()->get("continue_url"));
+ $group = $form->group("login")->label(t("Login"));
+ $group->input("name")->label(t("Username"))->id("g-username")->class(null)
+ ->callback("auth::validate_too_many_failed_logins")
+ ->error_messages(
+ "too_many_failed_logins", t("Too many failed login attempts. Try again later"));
+ $group->password("password")->label(t("Password"))->id("g-password")->class(null);
+ $group->inputs["name"]->error_messages("invalid_login", t("Invalid name or password"));
+ $group->submit("")->value(t("Login"));
+ return $form;
+ }
+
+ static function login($user) {
+ identity::set_active_user($user);
+ if (identity::is_writable()) {
+ $user->login_count += 1;
+ $user->last_login = time();
+ $user->save();
+ }
+ log::info("user", t("User %name logged in", array("name" => $user->name)));
+ module::event("user_login", $user);
+ }
+
+ static function logout() {
+ $user = identity::active_user();
+ if (!$user->guest) {
+ try {
+ Session::instance()->destroy();
+ } catch (Exception $e) {
+ Kohana_Log::add("error", $e);
+ }
+ module::event("user_logout", $user);
+ }
+ log::info("user", t("User %name logged out", array("name" => $user->name)),
+ t('<a href="%url">%user_name</a>',
+ array("url" => user_profile::url($user->id),
+ "user_name" => html::clean($user->name))));
+ }
+
+ /**
+ * After there have been 5 failed auth attempts, any failure leads to getting locked out for a
+ * minute.
+ */
+ static function too_many_failures($name) {
+ $failed = ORM::factory("failed_auth")
+ ->where("name", "=", $name)
+ ->find();
+ return ($failed->loaded() &&
+ $failed->count > 5 &&
+ (time() - $failed->time < 60));
+ }
+
+ static function validate_too_many_failed_logins($name_input) {
+ if (auth::too_many_failures($name_input->value)) {
+ $name_input->add_error("too_many_failed_logins", 1);
+ }
+ }
+
+ static function validate_too_many_failed_auth_attempts($form_input) {
+ if (auth::too_many_failures(identity::active_user()->name)) {
+ $form_input->add_error("too_many_failed_auth_attempts", 1);
+ }
+ }
+
+ /**
+ * Record a failed authentication for this user
+ */
+ static function record_failed_attempt($name) {
+ $failed = ORM::factory("failed_auth")
+ ->where("name", "=", $name)
+ ->find();
+ if (!$failed->loaded()) {
+ $failed->name = $name;
+ }
+ $failed->time = time();
+ $failed->count++;
+ $failed->save();
+ }
+
+ /**
+ * Clear any failed logins for this user
+ */
+ static function clear_failed_attempts($user) {
+ ORM::factory("failed_auth")
+ ->where("name", "=", $user->name)
+ ->delete_all();
+ }
+
+ /**
+ * Checks whether the current user (= admin) must
+ * actively re-authenticate before access is given
+ * to the admin area.
+ */
+ static function must_reauth_for_admin_area() {
+ if (!identity::active_user()->admin) {
+ access::forbidden();
+ }
+
+ $session = Session::instance();
+ $last_active_auth = $session->get("active_auth_timestamp", 0);
+ $last_admin_area_activity = $session->get("admin_area_activity_timestamp", 0);
+ $admin_area_timeout = module::get_var("gallery", "admin_area_timeout");
+
+ if (max($last_active_auth, $last_admin_area_activity) + $admin_area_timeout < time()) {
+ return true;
+ }
+
+ $session->set("admin_area_activity_timestamp", time());
+ return false;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/batch.php b/modules/gallery/helpers/batch.php
new file mode 100644
index 0000000..bf2425e
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 0000000..a227946
--- /dev/null
+++ b/modules/gallery/helpers/block_manager.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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("gallery", "blocks_$location", "a:0:{}"));
+ }
+
+ static function set_active($location, $blocks) {
+ module::set_var("gallery", "blocks_$location", serialize($blocks));
+ }
+
+ static function add($location, $module_name, $block_id) {
+ $blocks = block_manager::get_active($location);
+ $blocks[random::int()] = array($module_name, $block_id);
+
+ block_manager::set_active($location, $blocks);
+ }
+
+ static function activate_blocks($module_name) {
+ $block_class = "{$module_name}_block";
+ if (class_exists($block_class) && method_exists($block_class, "get_site_list")) {
+ $blocks = call_user_func(array($block_class, "get_site_list"));
+ foreach (array_keys($blocks) as $block_id) {
+ block_manager::add("site_sidebar", $module_name, $block_id);
+ }
+ }
+ }
+
+ static function remove($location, $block_id) {
+ $blocks = block_manager::get_active($location);
+ unset($blocks[$block_id]);
+ block_manager::set_active($location, $blocks);
+ }
+
+ static function remove_blocks_for_module($location, $module_name) {
+ $blocks = block_manager::get_active($location);
+ foreach ($blocks as $key => $block) {
+ if ($block[0] == $module_name) {
+ unset($blocks[$key]);
+ }
+ }
+ block_manager::set_active($location, $blocks);
+ }
+
+ static function deactivate_blocks($module_name) {
+ $block_class = "{$module_name}_block";
+ if (class_exists($block_class) && method_exists($block_class, "get_site_list")) {
+ $blocks = call_user_func(array($block_class, "get_site_list"));
+ foreach (array_keys($blocks) as $block_id) {
+ block_manager::remove_blocks_for_module("site_sidebar", $module_name);
+ }
+ }
+
+ if (class_exists($block_class) && method_exists($block_class, "get_admin_list")) {
+ $blocks = call_user_func(array($block_class, "get_admin_list"));
+ foreach (array("dashboard_sidebar", "dashboard_center") as $location) {
+ block_manager::remove_blocks_for_module($location, $module_name);
+ }
+ }
+ }
+
+ static function get_available_admin_blocks() {
+ return self::_get_blocks("get_admin_list");
+ }
+
+ static function get_available_site_blocks() {
+ return self::_get_blocks("get_site_list");
+ }
+
+ private static function _get_blocks($function) {
+ $blocks = array();
+
+ foreach (module::active() as $module) {
+ $class_name = "{$module->name}_block";
+ if (class_exists($class_name) && method_exists($class_name, $function)) {
+ foreach (call_user_func(array($class_name, $function)) as $id => $title) {
+ $blocks["{$module->name}:$id"] = $title;
+ }
+ }
+ }
+ return $blocks;
+ }
+
+ static function get_html($location, $theme=null) {
+ $active = block_manager::get_active($location);
+ $result = "";
+ foreach ($active as $id => $desc) {
+ if (class_exists("$desc[0]_block") && method_exists("$desc[0]_block", "get")) {
+ $block = call_user_func(array("$desc[0]_block", "get"), $desc[1], $theme);
+ if (!empty($block)) {
+ $block->id = $id;
+ $result .= $block;
+ }
+ }
+ }
+ return $result;
+ }
+}
diff --git a/modules/gallery/helpers/data_rest.php b/modules/gallery/helpers/data_rest.php
new file mode 100644
index 0000000..a0a225f
--- /dev/null
+++ b/modules/gallery/helpers/data_rest.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 resource returns the raw contents of Item_Model data files. It's analogous to the
+ * file_proxy controller, but it uses the REST authentication model.
+ */
+class data_rest_Core {
+ static function get($request) {
+ $item = rest::resolve($request->url);
+
+ $p = $request->params;
+ if (!isset($p->size) || !in_array($p->size, array("thumb", "resize", "full"))) {
+ throw new Rest_Exception("Bad Request", 400, array("errors" => array("size" => "invalid")));
+ }
+
+ // Note: this code is roughly duplicated in file_proxy, so if you modify this, please look to
+ // see if you should make the same change there as well.
+
+ if ($p->size == "full") {
+ if ($item->is_album()) {
+ throw new Kohana_404_Exception();
+ }
+ access::required("view_full", $item);
+ $file = $item->file_path();
+ } else if ($p->size == "resize") {
+ access::required("view", $item);
+ $file = $item->resize_path();
+ } else {
+ access::required("view", $item);
+ $file = $item->thumb_path();
+ }
+
+ if (!file_exists($file)) {
+ throw new Kohana_404_Exception();
+ }
+
+ header("Content-Length: " . filesize($file));
+
+ if (isset($p->m)) {
+ header("Pragma:");
+ // Check that the content hasn't expired or it wasn't changed since cached
+ expires::check(2592000, $item->updated);
+
+ expires::set(2592000, $item->updated); // 30 days
+ }
+
+ // We don't need to save the session for this request
+ Session::instance()->abort_save();
+
+ // Dump out the image. If the item is a movie or album, then its thumbnail will be a JPG.
+ if (($item->is_movie() || $item->is_album()) && $p->size == "thumb") {
+ header("Content-Type: image/jpeg");
+ } else {
+ header("Content-Type: $item->mime_type");
+ }
+
+ if (TEST_MODE) {
+ return $file;
+ } else {
+ Kohana::close_buffers(false);
+
+ if (isset($p->encoding) && $p->encoding == "base64") {
+ print base64_encode(file_get_contents($file));
+ } else {
+ readfile($file);
+ }
+ }
+
+ // We must exit here to keep the regular REST framework reply code from adding more bytes on
+ // at the end or tinkering with headers.
+ exit;
+ }
+
+ static function resolve($id) {
+ $item = ORM::factory("item", $id);
+ if (!access::can("view", $item)) {
+ throw new Kohana_404_Exception();
+ }
+ return $item;
+ }
+
+ static function url($item, $size) {
+ if ($size == "full") {
+ $file = $item->file_path();
+ } else if ($size == "resize") {
+ $file = $item->resize_path();
+ } else {
+ $file = $item->thumb_path();
+ }
+ if (!file_exists($file)) {
+ throw new Kohana_404_Exception();
+ }
+
+ return url::abs_site("rest/data/{$item->id}?size=$size&m=" . filemtime($file));
+ }
+}
+
diff --git a/modules/gallery/helpers/dir.php b/modules/gallery/helpers/dir.php
new file mode 100644
index 0000000..807f7bd
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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/encoding.php b/modules/gallery/helpers/encoding.php
new file mode 100644
index 0000000..073aef9
--- /dev/null
+++ b/modules/gallery/helpers/encoding.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 encoding_Core {
+ static function convert_to_utf8($value) {
+ if (function_exists("mb_detect_encoding")) {
+ // Rely on mb_detect_encoding()'s strict mode
+ $src_encoding = mb_detect_encoding($value, mb_detect_order(), true);
+ if ($src_encoding != "UTF-8") {
+ if (function_exists("mb_convert_encoding") && $src_encoding) {
+ $value = mb_convert_encoding($value, "UTF-8", $src_encoding);
+ } else {
+ $value = utf8_encode($value);
+ }
+ }
+ }
+ return $value;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery.php b/modules/gallery/helpers/gallery.php
new file mode 100644
index 0000000..0adabe4
--- /dev/null
+++ b/modules/gallery/helpers/gallery.php
@@ -0,0 +1,233 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_Core {
+ const VERSION = "3.0.9";
+ const CODE_NAME = "Chartres";
+ const RELEASE_CHANNEL = "release";
+ const RELEASE_BRANCH = "3.0.x";
+
+ /**
+ * 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() {
+ if (module::get_var("gallery", "maintenance_mode", 0) &&
+ !identity::active_user()->admin) {
+ try {
+ $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller');
+ $allowed = $class->getConstant("ALLOW_MAINTENANCE_MODE") === true;
+ } catch (ReflectionClass $e) {
+ $allowed = false;
+ }
+ if (!$allowed) {
+ if (Router::$controller == "admin") {
+ // At this point we're in the admin theme and it doesn't have a themed login page, so
+ // we can't just swap in the login controller and have it work. So redirect back to the
+ // root item where we'll run this code again with the site theme.
+ url::redirect(item::root()->abs_url());
+ } else {
+ Session::instance()->set("continue_url", url::abs_site("admin/maintenance"));
+ Router::$controller = "login";
+ Router::$controller_path = MODPATH . "gallery/controllers/login.php";
+ Router::$method = "html";
+ }
+ }
+ }
+ }
+
+ /**
+ * If the gallery is only available to registered users and the user is not logged in, present
+ * the login page.
+ */
+ static function private_gallery() {
+ if (identity::active_user()->guest &&
+ !access::user_can(identity::guest(), "view", item::root()) &&
+ php_sapi_name() != "cli") {
+ try {
+ $class = new ReflectionClass(ucfirst(Router::$controller).'_Controller');
+ $allowed = $class->getConstant("ALLOW_PRIVATE_GALLERY") === true;
+ } catch (ReflectionClass $e) {
+ $allowed = false;
+ }
+ if (!$allowed) {
+ if (Router::$controller == "admin") {
+ // At this point we're in the admin theme and it doesn't have a themed login page, so
+ // we can't just swap in the login controller and have it work. So redirect back to the
+ // root item where we'll run this code again with the site theme.
+ url::redirect(item::root()->abs_url());
+ } else {
+ Session::instance()->set("continue_url", url::abs_current());
+ Router::$controller = "login";
+ Router::$controller_path = MODPATH . "gallery/controllers/login.php";
+ Router::$method = "html";
+ }
+ }
+ }
+ }
+
+ /**
+ * 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() {
+ // Don't keep a session for robots; it's a waste of database space.
+ if (request::user_agent("robot")) {
+ Session::instance()->abort_save();
+ }
+
+ 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");
+ }
+
+ /**
+ * Return a unix timestamp in a user specified format including date and time.
+ * @param $timestamp unix timestamp
+ * @return string
+ */
+ static function date_time($timestamp) {
+ return date(module::get_var("gallery", "date_time_format"), $timestamp);
+ }
+
+ /**
+ * Return a unix timestamp in a user specified format that's just the date.
+ * @param $timestamp unix timestamp
+ * @return string
+ */
+ static function date($timestamp) {
+ return date(module::get_var("gallery", "date_format"), $timestamp);
+ }
+
+ /**
+ * Return a unix timestamp in a user specified format that's just the time.
+ * @param $timestamp unix timestamp
+ * @return string
+ */
+ static function time($timestamp) {
+ return date(module::get_var("gallery", "time_format", "H:i:s"), $timestamp);
+ }
+
+ /**
+ * Provide a wrapper function for Kohana::find_file that first strips the extension and
+ * then calls the Kohana::find_file and supplies the extension as the type.
+ * @param string directory to search in
+ * @param string filename to look for
+ * @param boolean file required (optional: default false)
+ * @return array if the extension is config, i18n or l10n
+ * @return string if the file is found (relative to the DOCROOT)
+ * @return false if the file is not found
+ */
+ static function find_file($directory, $file, $required=false) {
+ $file_name = substr($file, 0, -strlen($ext = strrchr($file, '.')));
+ $file_name = Kohana::find_file($directory, $file_name, $required, substr($ext, 1));
+ if (!$file_name) {
+ if (file_exists(DOCROOT . "lib/$directory/$file")) {
+ return "lib/$directory/$file";
+ } else if (file_exists(DOCROOT . "lib/$file")) {
+ return "lib/$file";
+ }
+ }
+
+ if (is_string($file_name)) {
+ // make relative to DOCROOT
+ $parts = explode("/", $file_name);
+ $count = count($parts);
+ foreach ($parts as $idx => $part) {
+ // If this part is "modules" or "themes" make sure that the part 2 after this
+ // is the target directory, and if it is then we're done. This check makes
+ // sure that if Gallery is installed in a directory called "modules" or "themes"
+ // We don't parse the directory structure incorrectly.
+ if (in_array($part, array("modules", "themes")) &&
+ $idx + 2 < $count &&
+ $parts[$idx + 2] == $directory) {
+ break;
+ }
+ unset($parts[$idx]);
+ }
+ $file_name = implode("/", $parts);
+ }
+ return $file_name;
+ }
+
+ /**
+ * Set the PATH environment variable to the paths specified.
+ * @param array Array of paths. Each array entry can contain a colon separated list of paths.
+ */
+ static function set_path_env($paths) {
+ $path_env = array();
+ foreach ($paths as $path) {
+ if ($path) {
+ array_push($path_env, $path);
+ }
+ }
+ putenv("PATH=" . implode(":", $path_env));
+ }
+
+ /**
+ * Return a string describing this version of Gallery and the type of release.
+ */
+ static function version_string() {
+ if (gallery::RELEASE_CHANNEL == "git") {
+ $build_number = gallery::build_number();
+ return sprintf(
+ "%s (branch %s, %s)", gallery::VERSION, gallery::RELEASE_BRANCH,
+ $build_number ? " build $build_number" : "unknown build number");
+ } else {
+ return sprintf("%s (%s)", gallery::VERSION, gallery::CODE_NAME);
+ }
+ }
+
+ /**
+ * Return the contents of the .build_number file, which should be a single integer
+ * or return null if the .build_number file is missing.
+ */
+ static function build_number() {
+ $build_file = DOCROOT . ".build_number";
+ if (file_exists($build_file)) {
+ $result = parse_ini_file(DOCROOT . ".build_number");
+ return $result["build_number"];
+ }
+ return null;
+ }
+
+ /**
+ * Return true if we should show the profiler at the bottom of the page. Note that this
+ * function is called at database setup time so it cannot rely on the database.
+ */
+ static function show_profiler() {
+ return file_exists(VARPATH . "PROFILE");
+ }
+
+ /**
+ * Return true if we should allow Javascript and CSS combining for performance reasons.
+ * Typically we want this, but it's convenient for developers to be able to disable it.
+ */
+ static function allow_css_and_js_combining() {
+ return !file_exists(VARPATH . "DONT_COMBINE");
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery_block.php b/modules/gallery/helpers/gallery_block.php
new file mode 100644
index 0000000..5ac4d74
--- /dev/null
+++ b/modules/gallery/helpers/gallery_block.php
@@ -0,0 +1,145 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_block_Core {
+ static function get_admin_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"),
+ "upgrade_checker" => t("Check for Gallery upgrades")
+ );
+ }
+
+ static function get_site_list() {
+ return array("language" => t("Language preference"));
+ }
+
+ static function get($block_id) {
+ $block = new Block();
+ switch ($block_id) {
+ case "welcome":
+ $block->css_id = "g-welcome";
+ $block->title = t("Welcome to Gallery 3");
+ $block->content = new View("admin_block_welcome.html");
+ break;
+
+ case "photo_stream":
+ $block->css_id = "g-photo-stream";
+ $block->title = t("Photo stream");
+ $block->content = new View("admin_block_photo_stream.html");
+ $block->content->photos = ORM::factory("item")
+ ->where("type", "=", "photo")->order_by("created", "DESC")->find_all(10);
+ break;
+
+ case "log_entries":
+ $block->css_id = "g-log-entries";
+ $block->title = t("Log entries");
+ $block->content = new View("admin_block_log_entries.html");
+ $block->content->entries = ORM::factory("log")
+ ->order_by(array("timestamp" => "DESC", "id" => "DESC"))->find_all(5);
+ break;
+
+ case "stats":
+ $block->css_id = "g-stats";
+ $block->title = t("Gallery stats");
+ $block->content = new View("admin_block_stats.html");
+ $block->content->album_count =
+ ORM::factory("item")->where("type", "=", "album")->where("id", "<>", 1)->count_all();
+ $block->content->photo_count = ORM::factory("item")->where("type", "=", "photo")->count_all();
+ break;
+
+ case "platform_info":
+ $block->css_id = "g-platform";
+ $block->title = t("Platform information");
+ $block->content = new View("admin_block_platform.html");
+ break;
+
+ case "project_news":
+ $block->css_id = "g-project-news";
+ $block->title = t("Gallery project news");
+ $block->content = new View("admin_block_news.html");
+ $block->content->feed = feed::parse("http://galleryproject.org/node/feed", 3);
+ break;
+
+ case "block_adder":
+ if ($form = gallery_block::get_add_block_form()) {
+ $block->css_id = "g-block-adder";
+ $block->title = t("Dashboard content");
+ $block->content = $form;
+ } else {
+ $block = "";
+ }
+ break;
+
+ case "language":
+ $locales = locales::installed();
+ if (count($locales) > 1) {
+ foreach ($locales as $locale => $display_name) {
+ $locales[$locale] = SafeString::of_safe_html($display_name);
+ }
+ $block = new Block();
+ $block->css_id = "g-user-language-block";
+ $block->title = t("Language preference");
+ $block->content = new View("user_languages_block.html");
+ $block->content->installed_locales = array_merge(array("" => t("« none »")), $locales);
+ $block->content->selected = (string) locales::cookie_locale();
+ } else {
+ $block = "";
+ }
+ break;
+
+ case "upgrade_checker":
+ $block = new Block();
+ $block->css_id = "g-upgrade-available-block";
+ $block->title = t("Check for Gallery upgrades");
+ $block->content = new View("upgrade_checker_block.html");
+ $block->content->version_info = upgrade_checker::version_info();
+ $block->content->auto_check_enabled = upgrade_checker::auto_check_enabled();
+ $block->content->new_version = upgrade_checker::get_upgrade_message();
+ $block->content->build_number = gallery::build_number();
+ }
+ return $block;
+ }
+
+ static function get_add_block_form() {
+ $available_blocks = block_manager::get_available_admin_blocks();
+
+ $active = array();
+ foreach (array_merge(block_manager::get_active("dashboard_sidebar"),
+ block_manager::get_active("dashboard_center")) as $b) {
+ unset($available_blocks[implode(":", $b)]);
+ }
+
+ if (!$available_blocks) {
+ return;
+ }
+
+ $form = new Forge("admin/dashboard/add_block", "", "post",
+ array("id" => "g-add-dashboard-block-form"));
+ $group = $form->group("add_block")->label(t("Add Block"));
+ $group->dropdown("id")->label(t("Available blocks"))->options($available_blocks);
+ $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/gallery_error.php b/modules/gallery/helpers/gallery_error.php
new file mode 100644
index 0000000..76c8ca9
--- /dev/null
+++ b/modules/gallery/helpers/gallery_error.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_error_Core {
+ static function error_handler($severity, $message, $filename, $lineno) {
+ if (error_reporting() == 0) {
+ return;
+ }
+
+ if (error_reporting() & $severity) {
+ throw new ErrorException($message, 0, $severity, $filename, $lineno);
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery_event.php b/modules/gallery/helpers/gallery_event.php
new file mode 100644
index 0000000..a319b9c
--- /dev/null
+++ b/modules/gallery/helpers/gallery_event.php
@@ -0,0 +1,621 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_event_Core {
+ /**
+ * Initialization.
+ */
+ static function gallery_ready() {
+ if (!get_cfg_var("date.timezone")) {
+ if (!(rand() % 4)) {
+ Kohana_Log::add("error", "date.timezone setting not detected in " .
+ get_cfg_var("cfg_file_path") . " falling back to UTC. " .
+ "Consult http://php.net/manual/function.get-cfg-var.php for help.");
+ }
+ }
+
+ identity::load_user();
+ theme::load_themes();
+ locales::set_request_locale();
+ }
+
+ static function gallery_shutdown() {
+ // Every 500th request, do a pass over var/logs and var/tmp and delete old files.
+ // Limit ourselves to deleting a single file so that we don't spend too much CPU
+ // time on it. As long as servers call this at least twice a day they'll eventually
+ // wind up with a clean var/logs directory because we only create 1 file a day there.
+ // var/tmp might be stickier because theoretically we could wind up spamming that
+ // dir with a lot of files. But let's start with this and refine as we go.
+ if (!(rand() % 500)) {
+ // Note that this code is roughly duplicated in gallery_task::file_cleanup
+ $threshold = time() - 1209600; // older than 2 weeks
+ foreach(array("logs", "tmp") as $dir) {
+ $dir = VARPATH . $dir;
+ if ($dh = opendir($dir)) {
+ while (($file = readdir($dh)) !== false) {
+ if ($file[0] == ".") {
+ continue;
+ }
+
+ // Ignore directories for now, but we should really address them in the long term.
+ if (is_dir("$dir/$file")) {
+ continue;
+ }
+
+ if (filemtime("$dir/$file") <= $threshold) {
+ unlink("$dir/$file");
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Delete all files marked using system::delete_later.
+ system::delete_marked_files();
+ }
+
+ static function user_deleted($user) {
+ $admin = identity::admin_user();
+ if (!empty($admin)) { // could be empty if there is not identity provider
+ db::build()
+ ->update("tasks")
+ ->set("owner_id", $admin->id)
+ ->where("owner_id", "=", $user->id)
+ ->execute();
+ db::build()
+ ->update("items")
+ ->set("owner_id", $admin->id)
+ ->where("owner_id", "=", $user->id)
+ ->execute();
+ db::build()
+ ->update("logs")
+ ->set("user_id", $admin->id)
+ ->where("user_id", "=", $user->id)
+ ->execute();
+ }
+ }
+
+ static function identity_provider_changed($old_provider, $new_provider) {
+ $admin = identity::admin_user();
+ db::build()
+ ->update("tasks")
+ ->set("owner_id", $admin->id)
+ ->execute();
+ db::build()
+ ->update("items")
+ ->set("owner_id", $admin->id)
+ ->execute();
+ db::build()
+ ->update("logs")
+ ->set("user_id", $admin->id)
+ ->execute();
+ module::set_var("gallery", "email_from", $admin->email);
+ module::set_var("gallery", "email_reply_to", $admin->email);
+ }
+
+ static function group_created($group) {
+ access::add_group($group);
+ }
+
+ static function group_deleted($group) {
+ access::delete_group($group);
+ }
+
+ static function item_created($item) {
+ access::add_item($item);
+
+ // Build our thumbnail/resizes.
+ try {
+ graphics::generate($item);
+ } catch (Exception $e) {
+ log::error("graphics", t("Couldn't create a thumbnail or resize for %item_title",
+ array("item_title" => $item->title)),
+ html::anchor($item->abs_url(), t("details")));
+ Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
+ }
+
+ if ($item->is_photo() || $item->is_movie()) {
+ // If the parent has no cover item, make this it.
+ $parent = $item->parent();
+ if (access::can("edit", $parent) && $parent->album_cover_item_id == null) {
+ item::make_album_cover($item);
+ }
+ }
+ }
+
+ static function item_deleted($item) {
+ access::delete_item($item);
+
+ // Find any other albums that had the deleted item as the album cover and null it out.
+ // In some cases this may leave us with a missing album cover up in this item's parent
+ // hierarchy, but in most cases it'll work out fine.
+ foreach (ORM::factory("item")
+ ->where("album_cover_item_id", "=", $item->id)
+ ->find_all() as $parent) {
+ item::remove_album_cover($parent);
+ }
+
+ $parent = $item->parent();
+ if (!$parent->album_cover_item_id) {
+ // Assume that we deleted the album cover
+ if (batch::in_progress()) {
+ // Remember that this parent is missing an album cover, for later.
+ $batch_missing_album_cover = Session::instance()->get("batch_missing_album_cover", array());
+ $batch_missing_album_cover[$parent->id] = 1;
+ Session::instance()->set("batch_missing_album_cover", $batch_missing_album_cover);
+ } else {
+ // Choose the first viewable child as the new cover.
+ if ($child = $parent->viewable()->children(1)->current()) {
+ item::make_album_cover($child);
+ }
+ }
+ }
+ }
+
+ static function item_updated_data_file($item) {
+ graphics::generate($item);
+
+ // Update any places where this is the album cover
+ foreach (ORM::factory("item")
+ ->where("album_cover_item_id", "=", $item->id)
+ ->find_all() as $target) {
+ $target->thumb_dirty = 1;
+ $target->save();
+ graphics::generate($target);
+ }
+ }
+
+ static function batch_complete() {
+ // Set the album covers for any items that where we probably deleted the album cover during
+ // this batch. The item may have been deleted, so don't count on it being around. Choose the
+ // first child as the new album cover.
+ // NOTE: if the first child doesn't have an album cover, then this won't work.
+ foreach (array_keys(Session::instance()->get("batch_missing_album_cover", array())) as $id) {
+ $item = ORM::factory("item", $id);
+ if ($item->loaded() && !$item->album_cover_item_id) {
+ if ($child = $item->children(1)->current()) {
+ item::make_album_cover($child);
+ }
+ }
+ }
+ Session::instance()->delete("batch_missing_album_cover");
+ }
+
+ static function item_moved($item, $old_parent) {
+ if ($item->is_album()) {
+ access::recalculate_album_permissions($item->parent());
+ } else {
+ access::recalculate_photo_permissions($item);
+ }
+
+ // If the new parent doesn't have an album cover, make this it.
+ if (!$item->parent()->album_cover_item_id) {
+ item::make_album_cover($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("gallery", "choose_default_tookit", null)) {
+ graphics::choose_default_toolkit();
+ module::clear_var("gallery", "choose_default_tookit");
+ }
+ Session::instance()->set("active_auth_timestamp", time());
+ auth::clear_failed_attempts($user);
+ }
+
+ static function user_auth_failed($name) {
+ auth::record_failed_attempt($name);
+ }
+
+ static function user_auth($user) {
+ auth::clear_failed_attempts($user);
+ Session::instance()->set("active_auth_timestamp", time());
+ }
+
+ static function item_index_data($item, $data) {
+ $data[] = $item->description;
+ $data[] = $item->name;
+ $data[] = $item->title;
+ }
+
+ static function user_menu($menu, $theme) {
+ if ($theme->page_subtype != "login") {
+ $user = identity::active_user();
+ if ($user->guest) {
+ $menu->append(Menu::factory("dialog")
+ ->id("user_menu_login")
+ ->css_id("g-login-link")
+ ->url(url::site("login/ajax"))
+ ->label(t("Login")));
+ } else {
+ $csrf = access::csrf_token();
+ $menu->append(Menu::factory("link")
+ ->id("user_menu_edit_profile")
+ ->css_id("g-user-profile-link")
+ ->view("login_current_user.html")
+ ->url(user_profile::url($user->id))
+ ->label($user->display_name()));
+
+ if (Router::$controller == "admin") {
+ $continue_url = url::abs_site("");
+ } else if ($item = $theme->item()) {
+ if (access::user_can(identity::guest(), "view", $theme->item)) {
+ $continue_url = $item->abs_url();
+ } else {
+ $continue_url = item::root()->abs_url();
+ }
+ } else {
+ $continue_url = url::abs_current();
+ }
+
+ $menu->append(Menu::factory("link")
+ ->id("user_menu_logout")
+ ->css_id("g-logout-link")
+ ->url(url::site("logout?csrf=$csrf&amp;continue_url=" . urlencode($continue_url)))
+ ->label(t("Logout")));
+ }
+ }
+ }
+
+ static function site_menu($menu, $theme, $item_css_selector) {
+ if ($theme->page_subtype != "login") {
+ $menu->append(Menu::factory("link")
+ ->id("home")
+ ->label(t("Home"))
+ ->url(item::root()->url()));
+
+
+ $item = $theme->item();
+
+ if (!empty($item)) {
+ $can_edit = $item && access::can("edit", $item);
+ $can_add = $item && access::can("add", $item);
+
+ if ($can_add) {
+ $menu->append($add_menu = Menu::factory("submenu")
+ ->id("add_menu")
+ ->label(t("Add")));
+ $is_album_writable =
+ is_writable($item->is_album() ? $item->file_path() : $item->parent()->file_path());
+ if ($is_album_writable) {
+ $add_menu->append(Menu::factory("dialog")
+ ->id("add_photos_item")
+ ->label(t("Add photos"))
+ ->url(url::site("uploader/index/$item->id")));
+ if ($item->is_album()) {
+ $add_menu->append(Menu::factory("dialog")
+ ->id("add_album_item")
+ ->label(t("Add an album"))
+ ->url(url::site("form/add/albums/$item->id?type=album")));
+ }
+ } else {
+ message::warning(t("The album '%album_name' is not writable.",
+ array("album_name" => $item->title)));
+ }
+ }
+
+ switch ($item->type) {
+ case "album":
+ $option_text = t("Album options");
+ $edit_text = t("Edit album");
+ $delete_text = t("Delete album");
+ break;
+ case "movie":
+ $option_text = t("Movie options");
+ $edit_text = t("Edit movie");
+ $delete_text = t("Delete movie");
+ break;
+ default:
+ $option_text = t("Photo options");
+ $edit_text = t("Edit photo");
+ $delete_text = t("Delete photo");
+ }
+
+ $menu->append($options_menu = Menu::factory("submenu")
+ ->id("options_menu")
+ ->label($option_text));
+ if ($item && ($can_edit || $can_add)) {
+ if ($can_edit) {
+ $options_menu->append(Menu::factory("dialog")
+ ->id("edit_item")
+ ->label($edit_text)
+ ->url(url::site("form/edit/{$item->type}s/$item->id?from_id={$item->id}")));
+ }
+
+ if ($item->is_album()) {
+ if ($can_edit) {
+ $options_menu->append(Menu::factory("dialog")
+ ->id("edit_permissions")
+ ->label(t("Edit permissions"))
+ ->url(url::site("permissions/browse/$item->id")));
+ }
+ }
+ }
+
+ $csrf = access::csrf_token();
+ $page_type = $theme->page_type();
+ if ($can_edit && $item->is_photo() && graphics::can("rotate")) {
+ $options_menu
+ ->append(
+ Menu::factory("ajax_link")
+ ->id("rotate_ccw")
+ ->label(t("Rotate 90° counter clockwise"))
+ ->css_class("ui-icon-rotate-ccw")
+ ->ajax_handler("function(data) { " .
+ "\$.gallery_replace_image(data, \$('$item_css_selector')) }")
+ ->url(url::site("quick/rotate/$item->id/ccw?csrf=$csrf&amp;from_id={$item->id}&amp;page_type=$page_type")))
+ ->append(
+ Menu::factory("ajax_link")
+ ->id("rotate_cw")
+ ->label(t("Rotate 90° clockwise"))
+ ->css_class("ui-icon-rotate-cw")
+ ->ajax_handler("function(data) { " .
+ "\$.gallery_replace_image(data, \$('$item_css_selector')) }")
+ ->url(url::site("quick/rotate/$item->id/cw?csrf=$csrf&amp;from_id={$item->id}&amp;page_type=$page_type")));
+ }
+
+ if ($item->id != item::root()->id) {
+ $parent = $item->parent();
+ if (access::can("edit", $parent)) {
+ // We can't make this item the highlight if it's an album with no album cover, or if it's
+ // already the album cover.
+ if (($item->type == "album" && empty($item->album_cover_item_id)) ||
+ ($item->type == "album" && $parent->album_cover_item_id == $item->album_cover_item_id) ||
+ $parent->album_cover_item_id == $item->id) {
+ $disabledState = "ui-state-disabled";
+ } else {
+ $disabledState = "";
+ }
+
+ if ($item->parent()->id != 1) {
+ $options_menu
+ ->append(
+ Menu::factory("ajax_link")
+ ->id("make_album_cover")
+ ->label(t("Choose as the album cover"))
+ ->css_class("ui-icon-star $disabledState")
+ ->ajax_handler("function(data) { window.location.reload() }")
+ ->url(url::site("quick/make_album_cover/$item->id?csrf=$csrf")));
+ }
+ $options_menu
+ ->append(
+ Menu::factory("dialog")
+ ->id("delete")
+ ->label($delete_text)
+ ->css_class("ui-icon-trash")
+ ->css_class("g-quick-delete")
+ ->url(url::site("quick/form_delete/$item->id?csrf=$csrf&amp;from_id={$item->id}&amp;page_type=$page_type")));
+ }
+ }
+ }
+
+ if (identity::active_user()->admin) {
+ $menu->append($admin_menu = Menu::factory("submenu")
+ ->id("admin_menu")
+ ->label(t("Admin")));
+ module::event("admin_menu", $admin_menu, $theme);
+
+ $settings_menu = $admin_menu->get("settings_menu");
+ uasort($settings_menu->elements, array("Menu", "title_comparator"));
+ }
+ }
+ }
+
+ static function admin_menu($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("movies_settings")
+ ->label(t("Movies"))
+ ->url(url::site("admin/movies")))
+ ->append(Menu::factory("link")
+ ->id("languages")
+ ->label(t("Languages"))
+ ->url(url::site("admin/languages")))
+ ->append(Menu::factory("link")
+ ->id("advanced")
+ ->label(t("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_options")
+ ->label(t("Theme options"))
+ ->url(url::site("admin/theme_options")))
+ ->append(Menu::factory("link")
+ ->id("sidebar")
+ ->label(t("Manage sidebar"))
+ ->url(url::site("admin/sidebar"))))
+ ->append(Menu::factory("submenu")
+ ->id("statistics_menu")
+ ->label(t("Statistics")))
+ ->append(Menu::factory("link")
+ ->id("maintenance")
+ ->label(t("Maintenance"))
+ ->url(url::site("admin/maintenance")));
+ return $menu;
+ }
+
+ static function context_menu($menu, $theme, $item, $thumb_css_selector) {
+ $menu->append($options_menu = Menu::factory("submenu")
+ ->id("options_menu")
+ ->label(t("Options"))
+ ->css_class("ui-icon-carat-1-n"));
+
+ $page_type = $theme->page_type();
+ if (access::can("edit", $item)) {
+ switch ($item->type) {
+ case "movie":
+ $edit_title = t("Edit this movie");
+ $delete_title = t("Delete this movie");
+ break;
+
+ case "album":
+ $edit_title = t("Edit this album");
+ $delete_title = t("Delete this album");
+ break;
+
+ default:
+ $edit_title = t("Edit this photo");
+ $delete_title = t("Delete this photo");
+ break;
+ }
+ $cover_title = t("Choose as the album cover");
+
+ $csrf = access::csrf_token();
+
+ $theme_item = $theme->item();
+ $options_menu->append(Menu::factory("dialog")
+ ->id("edit")
+ ->label($edit_title)
+ ->css_class("ui-icon-pencil")
+ ->url(url::site("quick/form_edit/$item->id?from_id={$theme_item->id}")));
+
+ if ($item->is_photo() && graphics::can("rotate")) {
+ $options_menu
+ ->append(
+ Menu::factory("ajax_link")
+ ->id("rotate_ccw")
+ ->label(t("Rotate 90° counter clockwise"))
+ ->css_class("ui-icon-rotate-ccw")
+ ->ajax_handler("function(data) { " .
+ "\$.gallery_replace_image(data, \$('$thumb_css_selector')) }")
+ ->url(url::site("quick/rotate/$item->id/ccw?csrf=$csrf&amp;from_id={$theme_item->id}&amp;page_type=$page_type")))
+ ->append(
+ Menu::factory("ajax_link")
+ ->id("rotate_cw")
+ ->label(t("Rotate 90° clockwise"))
+ ->css_class("ui-icon-rotate-cw")
+ ->ajax_handler("function(data) { " .
+ "\$.gallery_replace_image(data, \$('$thumb_css_selector')) }")
+ ->url(url::site("quick/rotate/$item->id/cw?csrf=$csrf&amp;from_id={$theme_item->id}&amp;page_type=$page_type")));
+ }
+
+ $parent = $item->parent();
+ if (access::can("edit", $parent)) {
+ // We can't make this item the highlight if it's an album with no album cover, or if it's
+ // already the album cover.
+ if (($item->type == "album" && empty($item->album_cover_item_id)) ||
+ ($item->type == "album" && $parent->album_cover_item_id == $item->album_cover_item_id) ||
+ $parent->album_cover_item_id == $item->id) {
+ $disabledState = "ui-state-disabled";
+ } else {
+ $disabledState = "";
+ }
+ if ($item->parent()->id != 1) {
+ $options_menu
+ ->append(Menu::factory("ajax_link")
+ ->id("make_album_cover")
+ ->label($cover_title)
+ ->css_class("ui-icon-star $disabledState")
+ ->ajax_handler("function(data) { window.location.reload() }")
+ ->url(url::site("quick/make_album_cover/$item->id?csrf=$csrf")));
+ }
+ $options_menu
+ ->append(Menu::factory("dialog")
+ ->id("delete")
+ ->label($delete_title)
+ ->css_class("ui-icon-trash")
+ ->url(url::site("quick/form_delete/$item->id?csrf=$csrf&amp;" .
+ "from_id={$theme_item->id}&amp;page_type=$page_type")));
+ }
+
+ if ($item->is_album()) {
+ $options_menu
+ ->append(Menu::factory("dialog")
+ ->id("add_item")
+ ->label(t("Add a photo"))
+ ->css_class("ui-icon-plus")
+ ->url(url::site("uploader/index/$item->id")))
+ ->append(Menu::factory("dialog")
+ ->id("add_album")
+ ->label(t("Add an album"))
+ ->css_class("ui-icon-note")
+ ->url(url::site("form/add/albums/$item->id?type=album")))
+ ->append(Menu::factory("dialog")
+ ->id("edit_permissions")
+ ->label(t("Edit permissions"))
+ ->css_class("ui-icon-key")
+ ->url(url::site("permissions/browse/$item->id")));
+ }
+ }
+ }
+
+ static function show_user_profile($data) {
+ $v = new View("user_profile_info.html");
+
+ $fields = array("name" => t("Name"), "locale" => t("Language Preference"),
+ "email" => t("Email"), "full_name" => t("Full name"), "url" => t("Web site"));
+ if (!$data->user->guest) {
+ $fields = array("name" => t("Name"), "full_name" => t("Full name"), "url" => t("Web site"));
+ }
+ $v->user_profile_data = array();
+ foreach ($fields as $field => $label) {
+ if (!empty($data->user->$field)) {
+ $value = $data->user->$field;
+ if ($field == "locale") {
+ $value = locales::display_name($value);
+ } else if ($field == "url") {
+ $value = html::mark_clean(html::anchor(html::clean($data->user->$field)));
+ }
+ $v->user_profile_data[(string) $label] = $value;
+ }
+ }
+ $data->content[] = (object) array("title" => t("User information"), "view" => $v);
+
+ }
+
+ static function user_updated($original_user, $updated_user) {
+ // If the default from/reply-to email address is set to the install time placeholder value
+ // of unknown@unknown.com then adopt the value from the first admin to set their own email
+ // address so that we at least have a valid address for the Gallery.
+ if ($updated_user->admin) {
+ $email = module::get_var("gallery", "email_from", "");
+ if ($email == "unknown@unknown.com") {
+ module::set_var("gallery", "email_from", $updated_user->email);
+ module::set_var("gallery", "email_reply_to", $updated_user->email);
+ }
+ }
+ }
+}
diff --git a/modules/gallery/helpers/gallery_graphics.php b/modules/gallery/helpers/gallery_graphics.php
new file mode 100644
index 0000000..eb76353
--- /dev/null
+++ b/modules/gallery/helpers/gallery_graphics.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_graphics_Core {
+ /**
+ * Rotate an image. Valid options are degrees
+ *
+ * @param string $input_file
+ * @param string $output_file
+ * @param array $options
+ * @param Item_Model $item (optional)
+ */
+ static function rotate($input_file, $output_file, $options, $item=null) {
+ graphics::init_toolkit();
+
+ $temp_file = system::temp_filename("rotate_", pathinfo($output_file, PATHINFO_EXTENSION));
+ module::event("graphics_rotate", $input_file, $temp_file, $options, $item);
+
+ if (@filesize($temp_file) > 0) {
+ // A graphics_rotate event made an image - move it to output_file and use it.
+ @rename($temp_file, $output_file);
+ } else {
+ // No events made an image - proceed with standard process.
+ if (@filesize($input_file) == 0) {
+ throw new Exception("@todo EMPTY_INPUT_FILE");
+ }
+
+ if (!isset($options["degrees"])) {
+ $options["degrees"] = 0;
+ }
+
+ // Rotate the image. This also implicitly converts its format if needed.
+ Image::factory($input_file)
+ ->quality(module::get_var("gallery", "image_quality"))
+ ->rotate($options["degrees"])
+ ->save($output_file);
+ }
+
+ module::event("graphics_rotate_completed", $input_file, $output_file, $options, $item);
+ }
+
+ /**
+ * 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
+ * @param Item_Model $item (optional)
+ */
+ static function resize($input_file, $output_file, $options, $item=null) {
+ graphics::init_toolkit();
+
+ $temp_file = system::temp_filename("resize_", pathinfo($output_file, PATHINFO_EXTENSION));
+ module::event("graphics_resize", $input_file, $temp_file, $options, $item);
+
+ if (@filesize($temp_file) > 0) {
+ // A graphics_resize event made an image - move it to output_file and use it.
+ @rename($temp_file, $output_file);
+ } else {
+ // No events made an image - proceed with standard process.
+ if (@filesize($input_file) == 0) {
+ throw new Exception("@todo EMPTY_INPUT_FILE");
+ }
+
+ list ($input_width, $input_height, $input_mime, $input_extension) =
+ photo::get_file_metadata($input_file);
+ if ($input_width && $input_height &&
+ (empty($options["width"]) || empty($options["height"]) || empty($options["master"]) ||
+ (max($input_width, $input_height) <= min($options["width"], $options["height"])))) {
+ // Photo dimensions well-defined, but options not well-defined or would upscale the image.
+ // Do not resize. Check mimes to see if we can copy the file or if we need to convert it.
+ // (checking mimes avoids needlessly converting jpg to jpeg, etc.)
+ $output_mime = legal_file::get_photo_types_by_extension(pathinfo($output_file, PATHINFO_EXTENSION));
+ if ($input_mime && $output_mime && ($input_mime == $output_mime)) {
+ // Mimes well-defined and identical - copy input to output
+ copy($input_file, $output_file);
+ } else {
+ // Mimes not well-defined or not the same - convert input to output
+ $image = Image::factory($input_file)
+ ->quality(module::get_var("gallery", "image_quality"))
+ ->save($output_file);
+ }
+ } else {
+ // Resize the image. This also implicitly converts its format if needed.
+ $image = Image::factory($input_file)
+ ->resize($options["width"], $options["height"], $options["master"])
+ ->quality(module::get_var("gallery", "image_quality"));
+ if (graphics::can("sharpen")) {
+ $image->sharpen(module::get_var("gallery", "image_sharpen"));
+ }
+ $image->save($output_file);
+ }
+ }
+
+ module::event("graphics_resize_completed", $input_file, $output_file, $options, $item);
+ }
+
+ /**
+ * Overlay an image on top of the input file.
+ *
+ * Valid options are: file, position, transparency, 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
+ * @param Item_Model $item (optional)
+ */
+ static function composite($input_file, $output_file, $options, $item=null) {
+ try {
+ graphics::init_toolkit();
+
+ $temp_file = system::temp_filename("composite_", pathinfo($output_file, PATHINFO_EXTENSION));
+ module::event("graphics_composite", $input_file, $temp_file, $options, $item);
+
+ if (@filesize($temp_file) > 0) {
+ // A graphics_composite event made an image - move it to output_file and use it.
+ @rename($temp_file, $output_file);
+ } else {
+ // No events made an image - proceed with standard process.
+
+ list ($width, $height) = photo::get_file_metadata($input_file);
+ list ($w_width, $w_height) = photo::get_file_metadata($options["file"]);
+
+ $pad = isset($options["padding"]) ? $options["padding"] : 10;
+ $top = $pad;
+ $left = $pad;
+ $y_center = max($height / 2 - $w_height / 2, $pad);
+ $x_center = max($width / 2 - $w_width / 2, $pad);
+ $bottom = max($height - $w_height - $pad, $pad);
+ $right = max($width - $w_width - $pad, $pad);
+
+ switch ($options["position"]) {
+ case "northwest": $x = $left; $y = $top; break;
+ case "north": $x = $x_center; $y = $top; break;
+ case "northeast": $x = $right; $y = $top; break;
+ case "west": $x = $left; $y = $y_center; break;
+ case "center": $x = $x_center; $y = $y_center; break;
+ case "east": $x = $right; $y = $y_center; break;
+ case "southwest": $x = $left; $y = $bottom; break;
+ case "south": $x = $x_center; $y = $bottom; break;
+ case "southeast": $x = $right; $y = $bottom; break;
+ }
+
+ Image::factory($input_file)
+ ->composite($options["file"], $x, $y, $options["transparency"])
+ ->quality(module::get_var("gallery", "image_quality"))
+ ->save($output_file);
+ }
+
+ module::event("graphics_composite_completed", $input_file, $output_file, $options, $item);
+ } catch (ErrorException $e) {
+ // Unlike rotate and resize, composite catches its exceptions here. This is because
+ // composite is typically called for watermarks. If during thumb/resize generation
+ // the watermark fails, we'd still like the image resized, just without its watermark.
+ // If the exception isn't caught here, graphics::generate will replace it with a
+ // placeholder.
+ Kohana_Log::add("error", $e->getMessage());
+ }
+ }
+}
diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php
new file mode 100644
index 0000000..d49be83
--- /dev/null
+++ b/modules/gallery/helpers/gallery_installer.php
@@ -0,0 +1,844 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_installer {
+ static function install() {
+ $db = Database::instance();
+ $db->query("CREATE TABLE {access_caches} (
+ `id` int(9) NOT NULL auto_increment,
+ `item_id` int(9),
+ PRIMARY KEY (`id`),
+ KEY (`item_id`))
+ DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {access_intents} (
+ `id` int(9) NOT NULL auto_increment,
+ `item_id` int(9),
+ PRIMARY KEY (`id`))
+ DEFAULT CHARSET=utf8;");
+
+ // Using a simple index instead of a unique key for the
+ // key column to avoid handling of concurrency issues
+ // on insert. Thus allowing concurrent inserts on the
+ // same cache key, as does Memcache / xcache.
+ $db->query("CREATE TABLE {caches} (
+ `id` int(9) NOT NULL auto_increment,
+ `key` varchar(255) NOT NULL,
+ `tags` varchar(255),
+ `expiration` int(9) NOT NULL,
+ `cache` longblob,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY (`key`),
+ KEY (`tags`))
+ DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {failed_auths} (
+ `id` int(9) NOT NULL auto_increment,
+ `count` int(9) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `time` int(9) NOT NULL,
+ PRIMARY KEY (`id`))
+ 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`))
+ 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`))
+ 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` text default NULL,
+ `height` int(9) default NULL,
+ `left_ptr` 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` decimal(11,10) default NULL,
+ `relative_path_cache` varchar(255) default NULL,
+ `relative_url_cache` varchar(255) default NULL,
+ `resize_dirty` boolean default 1,
+ `resize_height` int(9) default NULL,
+ `resize_width` int(9) default NULL,
+ `right_ptr` int(9) NOT NULL,
+ `slug` varchar(255) default 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`),
+ KEY `weight` (`weight` DESC),
+ KEY `left_ptr` (`left_ptr`),
+ KEY `relative_path_cache` (`relative_path_cache`))
+ 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`))
+ 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` text default NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`key`))
+ 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,
+ `weight` int(9) default NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`name`),
+ KEY (`weight`))
+ 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`))
+ 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`))
+ 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`))
+ 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`))
+ 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`))
+ 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`))
+ DEFAULT CHARSET=utf8;");
+
+ foreach (array("albums", "logs", "modules", "resizes", "thumbs", "tmp", "uploads") as $dir) {
+ @mkdir(VARPATH . $dir);
+ if (in_array($dir, array("logs", "tmp", "uploads"))) {
+ self::_protect_directory(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");
+
+ // Mark for translation (must be the same strings as used above)
+ t("View full size");
+ t("View");
+ t("Edit");
+ t("Add");
+
+ // Hardcode the first item to sidestep ORM validation rules
+ $now = time();
+ db::build()->insert(
+ "items",
+ array("created" => $now,
+ "description" => "",
+ "left_ptr" => 1,
+ "level" => 1,
+ "parent_id" => 0,
+ "resize_dirty" => 1,
+ "right_ptr" => 2,
+ "sort_column" => "weight",
+ "sort_order" => "ASC",
+ "thumb_dirty" => 1,
+ "title" => "Gallery",
+ "type" => "album",
+ "updated" => $now,
+ "weight" => 1))
+ ->execute();
+ $root = ORM::factory("item", 1);
+ access::add_item($root);
+
+ module::set_var("gallery", "active_site_theme", "wind");
+ module::set_var("gallery", "active_admin_theme", "admin_wind");
+ module::set_var("gallery", "page_size", 9);
+ module::set_var("gallery", "thumb_size", 200);
+ module::set_var("gallery", "resize_size", 640);
+ module::set_var("gallery", "default_locale", "en_US");
+ module::set_var("gallery", "image_quality", 75);
+ module::set_var("gallery", "image_sharpen", 15);
+ module::set_var("gallery", "upgrade_checker_auto_enabled", true);
+
+ // Add rules for generating our thumbnails and resizes
+ graphics::add_rule(
+ "gallery", "thumb", "gallery_graphics::resize",
+ array("width" => 200, "height" => 200, "master" => Image::AUTO),
+ 100);
+ graphics::add_rule(
+ "gallery", "resize", "gallery_graphics::resize",
+ array("width" => 640, "height" => 640, "master" => Image::AUTO),
+ 100);
+
+ // Instantiate default themes (site and admin)
+ foreach (array("wind", "admin_wind") 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", "gallery", "block_adder");
+ block_manager::add("dashboard_sidebar", "gallery", "stats");
+ block_manager::add("dashboard_sidebar", "gallery", "platform_info");
+ block_manager::add("dashboard_sidebar", "gallery", "project_news");
+ block_manager::add("dashboard_center", "gallery", "welcome");
+ block_manager::add("dashboard_center", "gallery", "upgrade_checker");
+ block_manager::add("dashboard_center", "gallery", "photo_stream");
+ block_manager::add("dashboard_center", "gallery", "log_entries");
+
+ module::set_var("gallery", "choose_default_tookit", 1);
+ module::set_var("gallery", "date_format", "Y-M-d");
+ module::set_var("gallery", "date_time_format", "Y-M-d H:i:s");
+ module::set_var("gallery", "time_format", "H:i:s");
+ module::set_var("gallery", "show_credits", 1);
+ // Mark string for translation
+ $powered_by_string = t("Powered by <a href=\"%url\">%gallery_version</a>",
+ array("locale" => "root"));
+ module::set_var("gallery", "credits", (string) $powered_by_string);
+ module::set_var("gallery", "simultaneous_upload_limit", 5);
+ module::set_var("gallery", "admin_area_timeout", 90 * 60);
+ module::set_var("gallery", "maintenance_mode", 0);
+ module::set_var("gallery", "visible_title_length", 15);
+ module::set_var("gallery", "favicon_url", "lib/images/favicon.ico");
+ module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png");
+ module::set_var("gallery", "email_from", "");
+ module::set_var("gallery", "email_reply_to", "");
+ module::set_var("gallery", "email_line_length", 70);
+ module::set_var("gallery", "email_header_separator", serialize("\n"));
+ module::set_var("gallery", "show_user_profiles_to", "registered_users");
+ module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin");
+ module::set_var("gallery", "timezone", null);
+ module::set_var("gallery", "lock_timeout", 1);
+ module::set_var("gallery", "movie_extract_frame_time", 3);
+ module::set_var("gallery", "movie_allow_uploads", "autodetect");
+ }
+
+ static function upgrade($version) {
+ $db = Database::instance();
+ if ($version == 1) {
+ module::set_var("gallery", "date_format", "Y-M-d");
+ module::set_var("gallery", "date_time_format", "Y-M-d H:i:s");
+ module::set_var("gallery", "time_format", "H:i:s");
+ module::set_version("gallery", $version = 2);
+ }
+
+ if ($version == 2) {
+ module::set_var("gallery", "show_credits", 1);
+ module::set_version("gallery", $version = 3);
+ }
+
+ if ($version == 3) {
+ $db->query("CREATE TABLE {caches} (
+ `id` varchar(255) NOT NULL,
+ `tags` varchar(255),
+ `expiration` int(9) NOT NULL,
+ `cache` text,
+ PRIMARY KEY (`id`),
+ KEY (`tags`))
+ DEFAULT CHARSET=utf8;");
+ module::set_version("gallery", $version = 4);
+ }
+
+ if ($version == 4) {
+ Cache::instance()->delete_all();
+ $db->query("ALTER TABLE {caches} MODIFY COLUMN `cache` LONGBLOB");
+ module::set_version("gallery", $version = 5);
+ }
+
+ if ($version == 5) {
+ Cache::instance()->delete_all();
+ $db->query("ALTER TABLE {caches} DROP COLUMN `id`");
+ $db->query("ALTER TABLE {caches} ADD COLUMN `key` varchar(255) NOT NULL");
+ $db->query("ALTER TABLE {caches} ADD COLUMN `id` int(9) NOT NULL auto_increment PRIMARY KEY");
+ module::set_version("gallery", $version = 6);
+ }
+
+ if ($version == 6) {
+ module::clear_var("gallery", "version");
+ module::set_version("gallery", $version = 7);
+ }
+
+ if ($version == 7) {
+ $groups = identity::groups();
+ $permissions = ORM::factory("permission")->find_all();
+ foreach($groups as $group) {
+ foreach($permissions as $permission) {
+ // Update access intents
+ $db->query("ALTER TABLE {access_intents} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT NULL");
+ // Update access cache
+ if ($permission->name === "view") {
+ $db->query("ALTER TABLE {items} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) DEFAULT FALSE");
+ } else {
+ $db->query("ALTER TABLE {access_caches} MODIFY COLUMN {$permission->name}_{$group->id} BINARY(1) NOT NULL DEFAULT FALSE");
+ }
+ }
+ }
+ module::set_version("gallery", $version = 8);
+ }
+
+ if ($version == 8) {
+ $db->query("ALTER TABLE {items} CHANGE COLUMN `left` `left_ptr` INT(9) NOT NULL;");
+ $db->query("ALTER TABLE {items} CHANGE COLUMN `right` `right_ptr` INT(9) NOT NULL;");
+ module::set_version("gallery", $version = 9);
+ }
+
+ if ($version == 9) {
+ $db->query("ALTER TABLE {items} ADD KEY `weight` (`weight` DESC);");
+
+ module::set_version("gallery", $version = 10);
+ }
+
+ if ($version == 10) {
+ module::set_var("gallery", "image_sharpen", 15);
+
+ module::set_version("gallery", $version = 11);
+ }
+
+ if ($version == 11) {
+ $db->query("ALTER TABLE {items} ADD COLUMN `relative_url_cache` varchar(255) DEFAULT NULL");
+ $db->query("ALTER TABLE {items} ADD COLUMN `slug` varchar(255) DEFAULT NULL");
+
+ // This is imperfect since some of the slugs may contain invalid characters, but it'll do
+ // for now because we don't want a lengthy operation here.
+ $db->query("UPDATE {items} SET `slug` = `name`");
+
+ // Flush all path caches because we're going to start urlencoding them.
+ $db->query("UPDATE {items} SET `relative_url_cache` = NULL, `relative_path_cache` = NULL");
+ module::set_version("gallery", $version = 12);
+ }
+
+ if ($version == 12) {
+ if (module::get_var("gallery", "active_site_theme") == "default") {
+ module::set_var("gallery", "active_site_theme", "wind");
+ }
+ if (module::get_var("gallery", "active_admin_theme") == "admin_default") {
+ module::set_var("gallery", "active_admin_theme", "admin_wind");
+ }
+ module::set_version("gallery", $version = 13);
+ }
+
+ if ($version == 13) {
+ // Add rules for generating our thumbnails and resizes
+ Database::instance()->query(
+ "UPDATE {graphics_rules} SET `operation` = CONCAT('gallery_graphics::', `operation`);");
+ module::set_version("gallery", $version = 14);
+ }
+
+ if ($version == 14) {
+ $sidebar_blocks = block_manager::get_active("site_sidebar");
+ if (empty($sidebar_blocks)) {
+ $available_blocks = block_manager::get_available_site_blocks();
+ foreach (array_keys(block_manager::get_available_site_blocks()) as $id) {
+ $sidebar_blocks[] = explode(":", $id);
+ }
+ block_manager::set_active("site_sidebar", $sidebar_blocks);
+ }
+ module::set_version("gallery", $version = 15);
+ }
+
+ if ($version == 15) {
+ module::set_var("gallery", "identity_provider", "user");
+ module::set_version("gallery", $version = 16);
+ }
+
+ // Convert block keys to an md5 hash of the module and block name
+ if ($version == 16) {
+ foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) {
+ $blocks = block_manager::get_active($location);
+ $new_blocks = array();
+ foreach ($blocks as $block) {
+ $new_blocks[md5("{$block[0]}:{$block[1]}")] = $block;
+ }
+ block_manager::set_active($location, $new_blocks);
+ }
+ module::set_version("gallery", $version = 17);
+ }
+
+ // We didn't like md5 hashes so convert block keys back to random keys to allow duplicates.
+ if ($version == 17) {
+ foreach (array("dashboard_sidebar", "dashboard_center", "site_sidebar") as $location) {
+ $blocks = block_manager::get_active($location);
+ $new_blocks = array();
+ foreach ($blocks as $block) {
+ $new_blocks[random::int()] = $block;
+ }
+ block_manager::set_active($location, $new_blocks);
+ }
+ module::set_version("gallery", $version = 18);
+ }
+
+ // Rename blocks_site.sidebar to blocks_site_sidebar
+ if ($version == 18) {
+ $blocks = block_manager::get_active("site.sidebar");
+ block_manager::set_active("site_sidebar", $blocks);
+ module::clear_var("gallery", "blocks_site.sidebar");
+ module::set_version("gallery", $version = 19);
+ }
+
+ // Set a default for the number of simultaneous uploads
+ // Version 20 was reverted in 57adefc5baa7a2b0dfcd3e736e80c2fa86d3bfa2, so skip it.
+ if ($version == 19 || $version == 20) {
+ module::set_var("gallery", "simultaneous_upload_limit", 5);
+ module::set_version("gallery", $version = 21);
+ }
+
+ // Update the graphics rules table so that the maximum height for resizes is 640 not 480.
+ // Fixes ticket #671
+ if ($version == 21) {
+ $resize_rule = ORM::factory("graphics_rule")
+ ->where("id", "=", "2")
+ ->find();
+ // make sure it hasn't been changed already
+ $args = unserialize($resize_rule->args);
+ if ($args["height"] == 480 && $args["width"] == 640) {
+ $args["height"] = 640;
+ $resize_rule->args = serialize($args);
+ $resize_rule->save();
+ }
+ module::set_version("gallery", $version = 22);
+ }
+
+ // Update slug values to be legal. We should have done this in the 11->12 upgrader, but I was
+ // lazy. Mea culpa!
+ if ($version == 22) {
+ foreach (db::build()
+ ->from("items")
+ ->select("id", "slug")
+ ->where(db::expr("`slug` REGEXP '[^_A-Za-z0-9-]'"), "=", 1)
+ ->execute() as $row) {
+ $new_slug = item::convert_filename_to_slug($row->slug);
+ if (empty($new_slug)) {
+ $new_slug = random::int();
+ }
+ db::build()
+ ->update("items")
+ ->set("slug", $new_slug)
+ ->set("relative_url_cache", null)
+ ->where("id", "=", $row->id)
+ ->execute();
+ }
+ module::set_version("gallery", $version = 23);
+ }
+
+ if ($version == 23) {
+ $db->query("CREATE TABLE {failed_logins} (
+ `id` int(9) NOT NULL auto_increment,
+ `count` int(9) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `time` int(9) NOT NULL,
+ PRIMARY KEY (`id`))
+ DEFAULT CHARSET=utf8;");
+ module::set_version("gallery", $version = 24);
+ }
+
+ if ($version == 24) {
+ foreach (array("logs", "tmp", "uploads") as $dir) {
+ self::_protect_directory(VARPATH . $dir);
+ }
+ module::set_version("gallery", $version = 25);
+ }
+
+ if ($version == 25) {
+ db::build()
+ ->update("items")
+ ->set("title", db::expr("`name`"))
+ ->and_open()
+ ->where("title", "IS", null)
+ ->or_where("title", "=", "")
+ ->close()
+ ->execute();
+ module::set_version("gallery", $version = 26);
+ }
+
+ if ($version == 26) {
+ if (in_array("failed_logins", Database::instance()->list_tables())) {
+ $db->query("RENAME TABLE {failed_logins} TO {failed_auths}");
+ }
+ module::set_version("gallery", $version = 27);
+ }
+
+ if ($version == 27) {
+ // Set the admin area timeout to 90 minutes
+ module::set_var("gallery", "admin_area_timeout", 90 * 60);
+ module::set_version("gallery", $version = 28);
+ }
+
+ if ($version == 28) {
+ module::set_var("gallery", "credits", "Powered by <a href=\"%url\">%gallery_version</a>");
+ module::set_version("gallery", $version = 29);
+ }
+
+ if ($version == 29) {
+ $db->query("ALTER TABLE {caches} ADD KEY (`key`);");
+ module::set_version("gallery", $version = 30);
+ }
+
+ if ($version == 30) {
+ module::set_var("gallery", "maintenance_mode", 0);
+ module::set_version("gallery", $version = 31);
+ }
+
+ if ($version == 31) {
+ $db->query("ALTER TABLE {modules} ADD COLUMN `weight` int(9) DEFAULT NULL");
+ $db->query("ALTER TABLE {modules} ADD KEY (`weight`)");
+ db::update("modules")
+ ->set("weight", db::expr("`id`"))
+ ->execute();
+ module::set_version("gallery", $version = 32);
+ }
+
+ if ($version == 32) {
+ $db->query("ALTER TABLE {items} ADD KEY (`left_ptr`)");
+ module::set_version("gallery", $version = 33);
+ }
+
+ if ($version == 33) {
+ $db->query("ALTER TABLE {access_caches} ADD KEY (`item_id`)");
+ module::set_version("gallery", $version = 34);
+ }
+
+ if ($version == 34) {
+ module::set_var("gallery", "visible_title_length", 15);
+ module::set_version("gallery", $version = 35);
+ }
+
+ if ($version == 35) {
+ module::set_var("gallery", "favicon_url", "lib/images/favicon.ico");
+ module::set_version("gallery", $version = 36);
+ }
+
+ if ($version == 36) {
+ module::set_var("gallery", "email_from", "admin@example.com");
+ module::set_var("gallery", "email_reply_to", "public@example.com");
+ module::set_var("gallery", "email_line_length", 70);
+ module::set_var("gallery", "email_header_separator", serialize("\n"));
+ module::set_version("gallery", $version = 37);
+ }
+
+ // Changed our minds and decided that the initial value should be empty
+ // But don't just reset it blindly, only do it if the value is version 37 default
+ if ($version == 37) {
+ $email = module::get_var("gallery", "email_from", "");
+ if ($email == "admin@example.com") {
+ module::set_var("gallery", "email_from", "");
+ }
+ $email = module::get_var("gallery", "email_reply_to", "");
+ if ($email == "admin@example.com") {
+ module::set_var("gallery", "email_reply_to", "");
+ }
+ module::set_version("gallery", $version = 38);
+ }
+
+ if ($version == 38) {
+ module::set_var("gallery", "show_user_profiles_to", "registered_users");
+ module::set_version("gallery", $version = 39);
+ }
+
+ if ($version == 39) {
+ module::set_var("gallery", "extra_binary_paths", "/usr/local/bin:/opt/local/bin:/opt/bin");
+ module::set_version("gallery", $version = 40);
+ }
+
+ if ($version == 40) {
+ module::clear_var("gallery", "_cache");
+ module::set_version("gallery", $version = 41);
+ }
+
+ if ($version == 41) {
+ $db->query("TRUNCATE TABLE {caches}");
+ $db->query("ALTER TABLE {caches} DROP INDEX `key`, ADD UNIQUE `key` (`key`)");
+ module::set_version("gallery", $version = 42);
+ }
+
+ if ($version == 42) {
+ $db->query("ALTER TABLE {items} CHANGE `description` `description` text DEFAULT NULL");
+ module::set_version("gallery", $version = 43);
+ }
+
+ if ($version == 43) {
+ $db->query("ALTER TABLE {items} CHANGE `rand_key` `rand_key` DECIMAL(11, 10)");
+ module::set_version("gallery", $version = 44);
+ }
+
+ if ($version == 44) {
+ $db->query("ALTER TABLE {messages} CHANGE `value` `value` text default NULL");
+ module::set_version("gallery", $version = 45);
+ }
+
+ if ($version == 45) {
+ // Splice the upgrade_checker block into the admin dashboard at the top
+ // of the page, but under the welcome block if it's in the first position.
+ $blocks = block_manager::get_active("dashboard_center");
+ $index = count($blocks) && current($blocks) == array("gallery", "welcome") ? 1 : 0;
+ array_splice($blocks, $index, 0, array(random::int() => array("gallery", "upgrade_checker")));
+ block_manager::set_active("dashboard_center", $blocks);
+
+ module::set_var("gallery", "upgrade_checker_auto_enabled", true);
+ module::set_version("gallery", $version = 46);
+ }
+
+ if ($version == 46) {
+ module::set_var("gallery", "apple_touch_icon_url", "lib/images/apple-touch-icon.png");
+ module::set_version("gallery", $version = 47);
+ }
+
+ if ($version == 47 || $version == 48) {
+ // Add configuration variable to set timezone. Defaults to the currently
+ // used timezone (from PHP configuration). Note that in v48 we were
+ // setting this value incorrectly, so we're going to stomp this value for v49.
+ module::set_var("gallery", "timezone", null);
+ module::set_version("gallery", $version = 49);
+ }
+
+ if ($version == 49) {
+ // In v49 we changed the Item_Model validation code to disallow files with two dots in them,
+ // but we didn't rename any files which fail to validate, so as soon as you do anything to
+ // change those files (eg. as a side effect of getting the url or file path) it fails to
+ // validate. Fix those here. This might be slow, but if it times out it can just pick up
+ // where it left off.
+ foreach (db::build()
+ ->from("items")
+ ->select("id")
+ ->where("type", "<>", "album")
+ ->where(db::expr("`name` REGEXP '\\\\..*\\\\.'"), "=", 1)
+ ->order_by("id", "asc")
+ ->execute() as $row) {
+ set_time_limit(30);
+ $item = ORM::factory("item", $row->id);
+ $item->name = legal_file::smash_extensions($item->name);
+ $item->save();
+ }
+ module::set_version("gallery", $version = 50);
+ }
+
+ if ($version == 50) {
+ // In v51, we added a lock_timeout variable so that administrators could edit the time out
+ // from 1 second to a higher variable if their system runs concurrent parallel uploads for
+ // instance.
+ module::set_var("gallery", "lock_timeout", 1);
+ module::set_version("gallery", $version = 51);
+ }
+
+ if ($version == 51) {
+ // In v52, we added functions to the legal_file helper that map photo and movie file
+ // extensions to their mime types (and allow extension of the list by other modules). During
+ // this process, we correctly mapped m4v files to video/x-m4v, correcting a previous error
+ // where they were mapped to video/mp4. This corrects the existing items.
+ db::build()
+ ->update("items")
+ ->set("mime_type", "video/x-m4v")
+ ->where("name", "REGEXP", "\.m4v$") // case insensitive since name column is utf8_general_ci
+ ->execute();
+ module::set_version("gallery", $version = 52);
+ }
+
+ if ($version == 52) {
+ // In v53, we added the ability to change the default time used when extracting frames from
+ // movies. Previously we hard-coded this at 3 seconds, so we use that as the default.
+ module::set_var("gallery", "movie_extract_frame_time", 3);
+ module::set_version("gallery", $version = 53);
+ }
+
+ if ($version == 53) {
+ // In v54, we changed how we check for name and slug conflicts in Item_Model. Previously,
+ // we checked the whole filename. As a result, "foo.jpg" and "foo.png" were not considered
+ // conflicting if their slugs were different (a rare case in practice since server_add and
+ // uploader would give them both the same slug "foo"). Now, we check the filename without its
+ // extension. This upgrade stanza fixes any conflicts where they were previously allowed.
+
+ // This might be slow, but if it times out it can just pick up where it left off.
+
+ // Find and loop through each conflict (e.g. "foo.jpg", "foo.png", and "foo.flv" are one
+ // conflict; "bar.jpg", "bar.png", and "bar.flv" are another)
+ foreach (db::build()
+ ->select_distinct(array("parent_base_name" =>
+ db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))")))
+ ->select(array("C" => "COUNT(\"*\")"))
+ ->from("items")
+ ->where("type", "<>", "album")
+ ->having("C", ">", 1)
+ ->group_by("parent_base_name")
+ ->execute() as $conflict) {
+ list ($parent_id, $base_name) = explode(":", $conflict->parent_base_name, 2);
+ $base_name_escaped = Database::escape_for_like($base_name);
+ // Loop through the items for each conflict
+ foreach (db::build()
+ ->from("items")
+ ->select("id")
+ ->where("type", "<>", "album")
+ ->where("parent_id", "=", $parent_id)
+ ->where("name", "LIKE", "{$base_name_escaped}.%")
+ ->limit(1000000) // required to satisfy SQL syntax (no offset without limit)
+ ->offset(1) // skips the 0th item
+ ->execute() as $row) {
+ set_time_limit(30);
+ $item = ORM::factory("item", $row->id);
+ $item->name = $item->name; // this will force Item_Model to check for conflicts on save
+ $item->save();
+ }
+ }
+ module::set_version("gallery", $version = 54);
+ }
+
+ if ($version == 54) {
+ $db->query("ALTER TABLE {items} ADD KEY `relative_path_cache` (`relative_path_cache`)");
+ module::set_version("gallery", $version = 55);
+ }
+
+ if ($version == 55) {
+ // In v56, we added the ability to change the default behavior regarding movie uploads. It
+ // can be set to "always", "never", or "autodetect" to match the previous behavior where they
+ // are allowed only if FFmpeg is found.
+ module::set_var("gallery", "movie_allow_uploads", "autodetect");
+ module::set_version("gallery", $version = 56);
+ }
+
+ if ($version == 56) {
+ // Cleanup possible instances where resize_dirty of albums or movies was set to 0. This is
+ // unlikely to have occurred, and doesn't currently matter much since albums and movies don't
+ // have resize images anyway. However, it may be useful to be consistent here going forward.
+ db::build()
+ ->update("items")
+ ->set("resize_dirty", 1)
+ ->where("type", "<>", "photo")
+ ->execute();
+ module::set_version("gallery", $version = 57);
+ }
+ }
+
+ 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 {incoming_translations}");
+ $db->query("DROP TABLE IF EXISTS {failed_auths}");
+ $db->query("DROP TABLE IF EXISTS {items}");
+ $db->query("DROP TABLE IF EXISTS {logs}");
+ $db->query("DROP TABLE IF EXISTS {modules}");
+ $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 {themes}");
+ $db->query("DROP TABLE IF EXISTS {vars}");
+ $db->query("DROP TABLE IF EXISTS {caches}");
+ foreach (array("albums", "resizes", "thumbs", "uploads",
+ "modules", "logs", "database.php") as $entry) {
+ system("/bin/rm -rf " . VARPATH . $entry);
+ }
+ }
+
+ static function _protect_directory($dir) {
+ $fp = @fopen("$dir/.htaccess", "w+");
+ fwrite($fp, "DirectoryIndex .htaccess\nSetHandler Gallery_Security_Do_Not_Remove\n" .
+ "Options None\n<IfModule mod_rewrite.c>\nRewriteEngine off\n</IfModule>\n" .
+ "Order allow,deny\nDeny from all\n");
+ fclose($fp);
+ }
+}
diff --git a/modules/gallery/helpers/gallery_rss.php b/modules/gallery/helpers/gallery_rss.php
new file mode 100644
index 0000000..d6b3302
--- /dev/null
+++ b/modules/gallery/helpers/gallery_rss.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_rss_Core {
+ static function available_feeds($item, $tag) {
+ $feeds["gallery/latest"] = t("Latest photos and movies");
+
+ if ($item) {
+ $feed_item = $item -> is_album() ? $item : $item->parent();
+
+ $feeds["gallery/album/{$feed_item->id}"] =
+ t("%title photos and movies", array("title" => $feed_item->title));
+ }
+
+ return $feeds;
+ }
+
+ static function feed($feed_id, $offset, $limit, $id) {
+ $feed = new stdClass();
+ switch ($feed_id) {
+ case "latest":
+ $feed->items = ORM::factory("item")
+ ->viewable()
+ ->where("type", "<>", "album")
+ ->order_by("created", "DESC")
+ ->find_all($limit, $offset);
+
+ $all_items = ORM::factory("item")
+ ->viewable()
+ ->where("type", "<>", "album")
+ ->order_by("created", "DESC");
+
+ $feed->max_pages = ceil($all_items->find_all()->count() / $limit);
+ $feed->title = t("%site_title - Recent updates", array("site_title" => item::root()->title));
+ $feed->description = t("Recent updates");
+ return $feed;
+
+ case "album":
+ $item = ORM::factory("item", $id);
+ access::required("view", $item);
+
+ $feed->items = $item
+ ->viewable()
+ ->descendants($limit, $offset, array(array("type", "=", "photo")));
+ $feed->max_pages = ceil(
+ $item->viewable()->descendants_count(array(array("type", "=", "photo"))) / $limit);
+ if ($item->id == item::root()->id) {
+ $feed->title = html::purify($item->title);
+ } else {
+ $feed->title = t("%site_title - %item_title",
+ array("site_title" => item::root()->title,
+ "item_title" => $item->title));
+ }
+ $feed->description = nl2br(html::purify($item->description));
+
+ return $feed;
+ }
+ }
+}
diff --git a/modules/gallery/helpers/gallery_task.php b/modules/gallery/helpers/gallery_task.php
new file mode 100644
index 0000000..618cf8f
--- /dev/null
+++ b/modules/gallery/helpers/gallery_task.php
@@ -0,0 +1,826 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_task_Core {
+ const FIX_STATE_START_MPTT = 0;
+ const FIX_STATE_RUN_MPTT = 1;
+ const FIX_STATE_START_ALBUMS = 2;
+ const FIX_STATE_RUN_ALBUMS = 3;
+ const FIX_STATE_START_DUPE_SLUGS = 4;
+ const FIX_STATE_RUN_DUPE_SLUGS = 5;
+ const FIX_STATE_START_DUPE_NAMES = 6;
+ const FIX_STATE_RUN_DUPE_NAMES = 7;
+ const FIX_STATE_START_DUPE_BASE_NAMES = 8;
+ const FIX_STATE_RUN_DUPE_BASE_NAMES = 9;
+ const FIX_STATE_START_REBUILD_ITEM_CACHES = 10;
+ const FIX_STATE_RUN_REBUILD_ITEM_CACHES = 11;
+ const FIX_STATE_START_MISSING_ACCESS_CACHES = 12;
+ const FIX_STATE_RUN_MISSING_ACCESS_CACHES = 13;
+ const FIX_STATE_DONE = 14;
+
+ static function available_tasks() {
+ $dirty_count = graphics::find_dirty_images_query()->count_records();
+ $tasks = array();
+ $tasks[] = Task_Definition::factory()
+ ->callback("gallery_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("gallery_task::update_l10n")
+ ->name(t("Update translations"))
+ ->description(t("Download new and updated translated strings"))
+ ->severity(log::SUCCESS);
+
+ $tasks[] = Task_Definition::factory()
+ ->callback("gallery_task::file_cleanup")
+ ->name(t("Remove old files"))
+ ->description(t("Remove expired files from the logs and tmp directory"))
+ ->severity(log::SUCCESS);
+
+ $tasks[] = Task_Definition::factory()
+ ->callback("gallery_task::fix")
+ ->name(t("Fix your Gallery"))
+ ->description(t("Fix a variety of problems that might cause your Gallery to act strangely. Requires maintenance mode."))
+ ->severity(log::SUCCESS);
+
+ return $tasks;
+ }
+
+ /**
+ * Task that rebuilds all dirty images.
+ * @param Task_Model the task
+ */
+ static function rebuild_dirty_images($task) {
+ $errors = array();
+ try {
+ // Choose the dirty images in a random order so that if we run this task multiple times
+ // concurrently each task is rebuilding different images simultaneously.
+ $result = graphics::find_dirty_images_query()->select("id")
+ ->select(db::expr("RAND() as r"))
+ ->order_by("r", "ASC")
+ ->execute();
+ $total_count = $task->get("total_count", $result->count());
+ $mode = $task->get("mode", "init");
+ if ($mode == "init") {
+ $task->set("total_count", $total_count);
+ $task->set("mode", "process");
+ batch::start();
+ }
+
+ $completed = $task->get("completed", 0);
+ $ignored = $task->get("ignored", array());
+
+ $i = 0;
+
+ // If there's no work left to do, skip to the end. This can happen if we resume a task long
+ // after the work got done in some other task.
+ if (!$result->count()) {
+ $completed = $total_count;
+ }
+
+ foreach ($result as $row) {
+ if (array_key_exists($row->id, $ignored)) {
+ continue;
+ }
+
+ $item = ORM::factory("item", $row->id);
+ if ($item->loaded()) {
+ try {
+ graphics::generate($item);
+ $completed++;
+
+ $errors[] = t("Successfully rebuilt images for '%title'",
+ array("title" => html::purify($item->title)));
+ } catch (Exception $e) {
+ $errors[] = t("Unable to rebuild images for '%title'",
+ array("title" => html::purify($item->title)));
+ $errors[] = (string)$e;
+ $ignored[$item->id] = 1;
+ }
+ }
+
+ if (++$i == 2) {
+ break;
+ }
+ }
+
+ $task->status = t2("Updated: 1 image. Total: %total_count.",
+ "Updated: %count images. Total: %total_count.",
+ $completed,
+ array("total_count" => $total_count));
+
+ if ($completed < $total_count) {
+ $task->percent_complete = (int)(100 * ($completed + count($ignored)) / $total_count);
+ } else {
+ $task->percent_complete = 100;
+ }
+
+ $task->set("completed", $completed);
+ $task->set("ignored", $ignored);
+ if ($task->percent_complete == 100) {
+ $task->done = true;
+ $task->state = "success";
+ batch::stop();
+ site_status::clear("graphics_dirty");
+ }
+ } catch (Exception $e) {
+ Kohana_Log::add("error",(string)$e);
+ $task->done = true;
+ $task->state = "error";
+ $task->status = $e->getMessage();
+ $errors[] = (string)$e;
+ }
+ if ($errors) {
+ $task->log($errors);
+ }
+ }
+
+ static function update_l10n($task) {
+ $errors = array();
+ try {
+ $start = microtime(true);
+ $data = Cache::instance()->get("update_l10n_cache:{$task->id}");
+ if ($data) {
+ list($dirs, $files, $cache, $num_fetched) = unserialize($data);
+ }
+ $i = 0;
+
+ switch ($task->get("mode", "init")) {
+ case "init": // 0%
+ $dirs = array("gallery", "modules", "themes", "installer");
+ $files = $cache = array();
+ $num_fetched = 0;
+ $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 (in_array(basename($dir), array("tests", "lib"))) {
+ 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% - 70%
+ 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 + 60 * ($total_files - count($files)) / $total_files;
+ if (empty($files)) {
+ $task->set("mode", "fetch_updates");
+ $task->status = t("Fetching updates");
+ $task->percent_complete = 70;
+ }
+ break;
+
+ case "fetch_updates": // 70% - 100%
+ // Send fetch requests in batches until we're done
+ $num_remaining = l10n_client::fetch_updates($num_fetched);
+ if ($num_remaining) {
+ $total = $num_fetched + $num_remaining;
+ $task->percent_complete = 70 + 30 * ((float) $num_fetched / $total);
+ } else {
+ Gallery_I18n::clear_cache();
+
+ $task->done = true;
+ $task->state = "success";
+ $task->status = t("Translations installed/updated");
+ $task->percent_complete = 100;
+ }
+ }
+
+ if (!$task->done) {
+ Cache::instance()->set("update_l10n_cache:{$task->id}",
+ serialize(array($dirs, $files, $cache, $num_fetched)),
+ array("l10n"));
+ } else {
+ Cache::instance()->delete("update_l10n_cache:{$task->id}");
+ }
+ } catch (Exception $e) {
+ Kohana_Log::add("error",(string)$e);
+ $task->done = true;
+ $task->state = "error";
+ $task->status = $e->getMessage();
+ $errors[] = (string)$e;
+ }
+ if ($errors) {
+ $task->log($errors);
+ }
+ }
+
+ /**
+ * Task that removes old files from var/logs and var/tmp.
+ * @param Task_Model the task
+ */
+ static function file_cleanup($task) {
+ $errors = array();
+ try {
+ $start = microtime(true);
+ $data = Cache::instance()->get("file_cleanup_cache:{$task->id}");
+ $files = $data ? unserialize($data) : array();
+ $i = 0;
+ $current = 0;
+ $total = 0;
+
+ switch ($task->get("mode", "init")) {
+ case "init":
+ $threshold = time() - 1209600; // older than 2 weeks
+ // Note that this code is roughly duplicated in gallery_event::gallery_shutdown
+ foreach(array("logs", "tmp") as $dir) {
+ $dir = VARPATH . $dir;
+ if ($dh = opendir($dir)) {
+ while (($file = readdir($dh)) !== false) {
+ if ($file[0] == ".") {
+ continue;
+ }
+
+ // Ignore directories for now, but we should really address them in the long term.
+ if (is_dir("$dir/$file")) {
+ continue;
+ }
+
+ if (filemtime("$dir/$file") <= $threshold) {
+ $files[] = "$dir/$file";
+ }
+ }
+ }
+ }
+ $task->set("mode", "delete_files");
+ $task->set("current", 0);
+ $task->set("total", count($files));
+ Cache::instance()->set("file_cleanup_cache:{$task->id}", serialize($files),
+ array("file_cleanup"));
+ if (count($files) == 0) {
+ break;
+ }
+
+ case "delete_files":
+ $current = $task->get("current");
+ $total = $task->get("total");
+ while ($current < $total && microtime(true) - $start < 1) {
+ @unlink($files[$current]);
+ $task->log(t("%file removed", array("file" => $files[$current++])));
+ }
+ $task->percent_complete = $current / $total * 100;
+ $task->set("current", $current);
+ }
+
+ $task->status = t2("Removed: 1 file. Total: %total_count.",
+ "Removed: %count files. Total: %total_count.",
+ $current, array("total_count" => $total));
+
+ if ($total == $current) {
+ $task->done = true;
+ $task->state = "success";
+ $task->percent_complete = 100;
+ }
+ } catch (Exception $e) {
+ Kohana_Log::add("error",(string)$e);
+ $task->done = true;
+ $task->state = "error";
+ $task->status = $e->getMessage();
+ $errors[] = (string)$e;
+ }
+ if ($errors) {
+ $task->log($errors);
+ }
+ }
+
+ static function fix($task) {
+ $start = microtime(true);
+
+ $total = $task->get("total");
+ if (empty($total)) {
+ $item_count = db::build()->count_records("items");
+ $total = 0;
+
+ // mptt: 2 operations for every item
+ $total += 2 * $item_count;
+
+ // album audit (permissions and bogus album covers): 1 operation for every album
+ $total += db::build()->where("type", "=", "album")->count_records("items");
+
+ // one operation for each dupe slug, dupe name, dupe base name, and missing access cache
+ foreach (array("find_dupe_slugs", "find_dupe_names", "find_dupe_base_names",
+ "find_missing_access_caches") as $func) {
+ foreach (self::$func() as $row) {
+ $total++;
+ }
+ }
+
+ // one operation to rebuild path and url caches;
+ $total += 1 * $item_count;
+
+ $task->set("total", $total);
+ $task->set("state", $state = self::FIX_STATE_START_MPTT);
+ $task->set("ptr", 1);
+ $task->set("completed", 0);
+ }
+
+ $completed = $task->get("completed");
+ $state = $task->get("state");
+
+ if (!module::get_var("gallery", "maintenance_mode")) {
+ module::set_var("gallery", "maintenance_mode", 1);
+ }
+
+ // This is a state machine that checks each item in the database. It verifies the following
+ // attributes for an item.
+ // 1. Left and right MPTT pointers are correct
+ // 2. The .htaccess permission files for restricted items exist and are well formed.
+ // 3. The relative_path_cache and relative_url_cache values are set to null.
+ // 4. there are no album_cover_item_ids pointing to missing items
+ //
+ // We'll do a depth-first tree walk over our hierarchy using only the adjacency data because
+ // we don't trust MPTT here (that might be what we're here to fix!). Avoid avoid using ORM
+ // calls as much as possible since they're expensive.
+ //
+ // NOTE: the MPTT check will only traverse items that have valid parents. It's possible that
+ // we have some tree corruption where there are items with parent ids to non-existent items.
+ // We should probably do something about that.
+ while ($state != self::FIX_STATE_DONE && microtime(true) - $start < 1.5) {
+ switch ($state) {
+ case self::FIX_STATE_START_MPTT:
+ $task->set("ptr", $ptr = 1);
+ $task->set("stack", item::root()->id . "L1");
+ $state = self::FIX_STATE_RUN_MPTT;
+ break;
+
+ case self::FIX_STATE_RUN_MPTT:
+ $ptr = $task->get("ptr");
+ $stack = explode(" ", $task->get("stack"));
+ preg_match("/([0-9]+)([A-Z])([0-9]+)/", array_pop($stack), $matches); // e.g. "12345L10"
+ list ( , $id, $ptr_mode, $level) = $matches; // Skip the 0th entry of matches.
+ switch ($ptr_mode) {
+ case "L":
+ // Albums could be parent nodes.
+ $stack[] = "{$id}R{$level}";
+ db::build()
+ ->update("items")
+ ->set("left_ptr", $ptr++)
+ ->where("id", "=", $id)
+ ->execute();
+
+ $level++;
+ foreach (db::build()
+ ->select(array("id", "type"))
+ ->from("items")
+ ->where("parent_id", "=", $id)
+ ->order_by("left_ptr", "DESC") // DESC since array_pop effectively reverses them
+ ->execute() as $child) {
+ $stack[] = ($child->type == "album") ? "{$child->id}L{$level}" : "{$child->id}B{$level}";
+ }
+ $completed++;
+ break;
+ case "B":
+ // Non-albums must be leaf nodes.
+ db::build()
+ ->update("items")
+ ->set("left_ptr", $ptr++)
+ ->set("right_ptr", $ptr++)
+ ->set("level", $level)
+ ->set("relative_path_cache", null)
+ ->set("relative_url_cache", null)
+ ->where("id", "=", $id)
+ ->execute();
+ $completed += 2; // we updated two pointers
+ break;
+ case "R":
+ db::build()
+ ->update("items")
+ ->set("right_ptr", $ptr++)
+ ->set("level", $level)
+ ->set("relative_path_cache", null)
+ ->set("relative_url_cache", null)
+ ->where("id", "=", $id)
+ ->execute();
+ $completed++;
+ }
+ $task->set("ptr", $ptr);
+ $task->set("stack", implode(" ", $stack));
+
+ if (empty($stack)) {
+ $state = self::FIX_STATE_START_DUPE_SLUGS;
+ }
+ break;
+
+
+ case self::FIX_STATE_START_DUPE_SLUGS:
+ $stack = array();
+ foreach (self::find_dupe_slugs() as $row) {
+ list ($parent_id, $slug) = explode(":", $row->parent_slug, 2);
+ $stack[] = join(":", array($parent_id, $slug));
+ }
+ if ($stack) {
+ $task->set("stack", implode(" ", $stack));
+ $state = self::FIX_STATE_RUN_DUPE_SLUGS;
+ } else {
+ $state = self::FIX_STATE_START_DUPE_NAMES;
+ }
+ break;
+
+ case self::FIX_STATE_RUN_DUPE_SLUGS:
+ $stack = explode(" ", $task->get("stack"));
+ list ($parent_id, $slug) = explode(":", array_pop($stack));
+
+ // We want to leave the first one alone and update all conflicts to be random values.
+ $fixed = 0;
+ $conflicts = ORM::factory("item")
+ ->where("parent_id", "=", $parent_id)
+ ->where("slug", "=", $slug)
+ ->find_all(1, 1);
+ if ($conflicts->count() && $conflict = $conflicts->current()) {
+ $task->log("Fixing conflicting slug for item id {$conflict->id}");
+ db::build()
+ ->update("items")
+ ->set("slug", $slug . "-" . (string)rand(1000, 9999))
+ ->where("id", "=", $conflict->id)
+ ->execute();
+
+ // We fixed one conflict, but there might be more so put this parent back on the stack
+ // and try again. We won't consider it completed when we don't fix a conflict. This
+ // guarantees that we won't spend too long fixing one set of conflicts, and that we
+ // won't stop before all are fixed.
+ $stack[] = "$parent_id:$slug";
+ break;
+ }
+ $task->set("stack", implode(" ", $stack));
+ $completed++;
+
+ if (empty($stack)) {
+ $state = self::FIX_STATE_START_DUPE_NAMES;
+ }
+ break;
+
+ case self::FIX_STATE_START_DUPE_NAMES:
+ $stack = array();
+ foreach (self::find_dupe_names() as $row) {
+ list ($parent_id, $name) = explode(":", $row->parent_name, 2);
+ $stack[] = join(":", array($parent_id, $name));
+ }
+ if ($stack) {
+ $task->set("stack", implode(" ", $stack));
+ $state = self::FIX_STATE_RUN_DUPE_NAMES;
+ } else {
+ $state = self::FIX_STATE_START_DUPE_BASE_NAMES;
+ }
+ break;
+
+ case self::FIX_STATE_RUN_DUPE_NAMES:
+ // NOTE: This does *not* attempt to fix the file system!
+ $stack = explode(" ", $task->get("stack"));
+ list ($parent_id, $name) = explode(":", array_pop($stack));
+
+ $fixed = 0;
+ // We want to leave the first one alone and update all conflicts to be random values.
+ $conflicts = ORM::factory("item")
+ ->where("parent_id", "=", $parent_id)
+ ->where("name", "=", $name)
+ ->find_all(1, 1);
+ if ($conflicts->count() && $conflict = $conflicts->current()) {
+ $task->log("Fixing conflicting name for item id {$conflict->id}");
+ if (!$conflict->is_album() && preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) {
+ $item_base_name = $matches[1];
+ $item_extension = $matches[2]; // includes a leading dot
+ } else {
+ $item_base_name = $conflict->name;
+ $item_extension = "";
+ }
+ db::build()
+ ->update("items")
+ ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension)
+ ->where("id", "=", $conflict->id)
+ ->execute();
+
+ // We fixed one conflict, but there might be more so put this parent back on the stack
+ // and try again. We won't consider it completed when we don't fix a conflict. This
+ // guarantees that we won't spend too long fixing one set of conflicts, and that we
+ // won't stop before all are fixed.
+ $stack[] = "$parent_id:$name";
+ break;
+ }
+ $task->set("stack", implode(" ", $stack));
+ $completed++;
+
+ if (empty($stack)) {
+ $state = self::FIX_STATE_START_DUPE_BASE_NAMES;
+ }
+ break;
+
+ case self::FIX_STATE_START_DUPE_BASE_NAMES:
+ $stack = array();
+ foreach (self::find_dupe_base_names() as $row) {
+ list ($parent_id, $base_name) = explode(":", $row->parent_base_name, 2);
+ $stack[] = join(":", array($parent_id, $base_name));
+ }
+ if ($stack) {
+ $task->set("stack", implode(" ", $stack));
+ $state = self::FIX_STATE_RUN_DUPE_BASE_NAMES;
+ } else {
+ $state = self::FIX_STATE_START_ALBUMS;
+ }
+ break;
+
+ case self::FIX_STATE_RUN_DUPE_BASE_NAMES:
+ // NOTE: This *does* attempt to fix the file system! So, it must go *after* run_dupe_names.
+ $stack = explode(" ", $task->get("stack"));
+ list ($parent_id, $base_name) = explode(":", array_pop($stack));
+ $base_name_escaped = Database::escape_for_like($base_name);
+
+ $fixed = 0;
+ // We want to leave the first one alone and update all conflicts to be random values.
+ $conflicts = ORM::factory("item")
+ ->where("parent_id", "=", $parent_id)
+ ->where("name", "LIKE", "{$base_name_escaped}.%")
+ ->where("type", "<>", "album")
+ ->find_all(1, 1);
+ if ($conflicts->count() && $conflict = $conflicts->current()) {
+ $task->log("Fixing conflicting name for item id {$conflict->id}");
+ if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $conflict->name, $matches)) {
+ $item_base_name = $matches[1]; // unlike $base_name, this always maintains capitalization
+ $item_extension = $matches[2]; // includes a leading dot
+ } else {
+ $item_base_name = $conflict->name;
+ $item_extension = "";
+ }
+ // Unlike conflicts found in run_dupe_names, these items are likely to have an intact
+ // file system. Let's use the item save logic to rebuild the paths and rename the files
+ // if possible.
+ try {
+ $conflict->name = $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension;
+ $conflict->validate();
+ // If we get here, we're safe to proceed with save
+ $conflict->save();
+ } catch (Exception $e) {
+ // Didn't work. Edit database directly without fixing file system.
+ db::build()
+ ->update("items")
+ ->set("name", $item_base_name . "-" . (string)rand(1000, 9999) . $item_extension)
+ ->where("id", "=", $conflict->id)
+ ->execute();
+ }
+
+ // We fixed one conflict, but there might be more so put this parent back on the stack
+ // and try again. We won't consider it completed when we don't fix a conflict. This
+ // guarantees that we won't spend too long fixing one set of conflicts, and that we
+ // won't stop before all are fixed.
+ $stack[] = "$parent_id:$base_name";
+ break;
+ }
+ $task->set("stack", implode(" ", $stack));
+ $completed++;
+
+ if (empty($stack)) {
+ $state = self::FIX_STATE_START_ALBUMS;
+ }
+ break;
+
+ case self::FIX_STATE_START_ALBUMS:
+ $stack = array();
+ foreach (db::build()
+ ->select("id")
+ ->from("items")
+ ->where("type", "=", "album")
+ ->execute() as $row) {
+ $stack[] = $row->id;
+ }
+ $task->set("stack", implode(" ", $stack));
+ $state = self::FIX_STATE_RUN_ALBUMS;
+ break;
+
+ case self::FIX_STATE_RUN_ALBUMS:
+ $stack = explode(" ", $task->get("stack"));
+ $id = array_pop($stack);
+
+ $item = ORM::factory("item", $id);
+ if ($item->album_cover_item_id) {
+ $album_cover_item = ORM::factory("item", $item->album_cover_item_id);
+ if (!$album_cover_item->loaded()) {
+ $item->album_cover_item_id = null;
+ $item->save();
+ }
+ }
+
+ $everybody = identity::everybody();
+ $view_col = "view_{$everybody->id}";
+ $view_full_col = "view_full_{$everybody->id}";
+ $intent = ORM::factory("access_intent")->where("item_id", "=", $id)->find();
+ if ($intent->$view_col === access::DENY) {
+ access::update_htaccess_files($item, $everybody, "view", access::DENY);
+ }
+ if ($intent->$view_full_col === access::DENY) {
+ access::update_htaccess_files($item, $everybody, "view_full", access::DENY);
+ }
+ $task->set("stack", implode(" ", $stack));
+ $completed++;
+
+ if (empty($stack)) {
+ $state = self::FIX_STATE_START_REBUILD_ITEM_CACHES;
+ }
+ break;
+
+ case self::FIX_STATE_START_REBUILD_ITEM_CACHES:
+ $stack = array();
+ foreach (self::find_empty_item_caches(500) as $row) {
+ $stack[] = $row->id;
+ }
+ $task->set("stack", implode(" ", $stack));
+ $state = self::FIX_STATE_RUN_REBUILD_ITEM_CACHES;
+ break;
+
+ case self::FIX_STATE_RUN_REBUILD_ITEM_CACHES:
+ $stack = explode(" ", $task->get("stack"));
+ if (!empty($stack)) {
+ $id = array_pop($stack);
+ $item = ORM::factory("item", $id);
+ $item->relative_path(); // this rebuilds the cache and saves the item as a side-effect
+ $task->set("stack", implode(" ", $stack));
+ $completed++;
+ }
+
+ if (empty($stack)) {
+ // Try refilling the stack
+ foreach (self::find_empty_item_caches(500) as $row) {
+ $stack[] = $row->id;
+ }
+ $task->set("stack", implode(" ", $stack));
+
+ if (empty($stack)) {
+ $state = self::FIX_STATE_START_MISSING_ACCESS_CACHES;
+ }
+ }
+ break;
+
+ case self::FIX_STATE_START_MISSING_ACCESS_CACHES:
+ $stack = array();
+ foreach (self::find_missing_access_caches_limited(500) as $row) {
+ $stack[] = $row->id;
+ }
+ $task->set("stack", implode(" ", $stack));
+ $state = self::FIX_STATE_RUN_MISSING_ACCESS_CACHES;
+ break;
+
+ case self::FIX_STATE_RUN_MISSING_ACCESS_CACHES:
+ $stack = array_filter(explode(" ", $task->get("stack"))); // filter removes empty/zero ids
+ if (!empty($stack)) {
+ $id = array_pop($stack);
+ $access_cache = ORM::factory("access_cache");
+ $access_cache->item_id = $id;
+ $access_cache->save();
+ $task->set("stack", implode(" ", $stack));
+ $completed++;
+ }
+
+ if (empty($stack)) {
+ // Try refilling the stack
+ foreach (self::find_missing_access_caches_limited(500) as $row) {
+ $stack[] = $row->id;
+ }
+ $task->set("stack", implode(" ", $stack));
+
+ if (empty($stack)) {
+ // The new cache rows are there, but they're incorrectly populated so we have to fix
+ // them. If this turns out to be too slow, we'll have to refactor
+ // access::recalculate_permissions to allow us to do it in slices.
+ access::recalculate_album_permissions(item::root());
+ $state = self::FIX_STATE_DONE;
+ }
+ }
+ break;
+ }
+ }
+
+ $task->set("state", $state);
+ $task->set("completed", $completed);
+
+ if ($state == self::FIX_STATE_DONE) {
+ $task->done = true;
+ $task->state = "success";
+ $task->percent_complete = 100;
+ module::set_var("gallery", "maintenance_mode", 0);
+ } else {
+ $task->percent_complete = round(100 * $completed / $total);
+ }
+ $task->status = t2("One operation complete", "%count / %total operations complete", $completed,
+ array("total" => $total));
+ }
+
+ static function find_dupe_slugs() {
+ return db::build()
+ ->select_distinct(
+ array("parent_slug" => db::expr("CONCAT(`parent_id`, ':', LOWER(`slug`))")))
+ ->select("id")
+ ->select(array("C" => "COUNT(\"*\")"))
+ ->from("items")
+ ->having("C", ">", 1)
+ ->group_by("parent_slug")
+ ->execute();
+ }
+
+ static function find_dupe_names() {
+ // looking for photos, movies, and albums
+ return db::build()
+ ->select_distinct(
+ array("parent_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(`name`))")))
+ ->select("id")
+ ->select(array("C" => "COUNT(\"*\")"))
+ ->from("items")
+ ->having("C", ">", 1)
+ ->group_by("parent_name")
+ ->execute();
+ }
+
+ static function find_dupe_base_names() {
+ // looking for photos or movies, not albums
+ return db::build()
+ ->select_distinct(
+ array("parent_base_name" => db::expr("CONCAT(`parent_id`, ':', LOWER(SUBSTR(`name`, 1, LOCATE('.', `name`) - 1)))")))
+ ->select("id")
+ ->select(array("C" => "COUNT(\"*\")"))
+ ->from("items")
+ ->where("type", "<>", "album")
+ ->having("C", ">", 1)
+ ->group_by("parent_base_name")
+ ->execute();
+ }
+
+ static function find_empty_item_caches($limit) {
+ return db::build()
+ ->select("items.id")
+ ->from("items")
+ ->where("relative_path_cache", "is", null)
+ ->or_where("relative_url_cache", "is", null)
+ ->limit($limit)
+ ->execute();
+ }
+
+ static function find_missing_access_caches() {
+ return self::find_missing_access_caches_limited(1 << 16);
+ }
+
+ static function find_missing_access_caches_limited($limit) {
+ return db::build()
+ ->select("items.id")
+ ->from("items")
+ ->join("access_caches", "items.id", "access_caches.item_id", "left")
+ ->where("access_caches.id", "is", null)
+ ->limit($limit)
+ ->execute();
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery_theme.php b/modules/gallery/helpers/gallery_theme.php
new file mode 100644
index 0000000..3c6d71e
--- /dev/null
+++ b/modules/gallery/helpers/gallery_theme.php
@@ -0,0 +1,151 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 gallery_theme_Core {
+ static function head($theme) {
+ $session = Session::instance();
+ $buf = "";
+ $buf .= $theme->css("gallery.css");
+ if ($session->get("debug")) {
+ $buf .= $theme->css("debug.css");
+ }
+
+ if (module::is_active("rss")) {
+ if ($item = $theme->item()) {
+ if ($item->is_album()) {
+ $buf .= rss::feed_link("gallery/album/{$item->id}");
+ } else {
+ $buf .= rss::feed_link("gallery/album/{$item->parent()->id}");
+ }
+ } else if ($tag = $theme->tag()) {
+ $buf .= rss::feed_link("tag/tag/{$tag->id}");
+ }
+ }
+
+ if (count(locales::installed())) {
+ // Needed by the languages block
+ $buf .= $theme->script("jquery.cookie.js");
+ }
+
+ if ($session->get("l10n_mode", false)) {
+ $buf .= $theme->css("l10n_client.css")
+ . $theme->script("jquery.cookie.js")
+ . $theme->script("l10n_client.js");
+ }
+
+ $buf .= $theme->css("uploadify/uploadify.css");
+ return $buf;
+ }
+
+ static function admin_head($theme) {
+ $buf = $theme->css("gallery.css");
+ $buf .= $theme->script("gallery.panel.js");
+ $session = Session::instance();
+ if ($session->get("debug")) {
+ $buf .= $theme->css("debug.css");
+ }
+
+ if ($session->get("l10n_mode", false)) {
+ $buf .= $theme->css("l10n_client.css");
+ $buf .= $theme->script("jquery.cookie.js");
+ $buf .= $theme->script("l10n_client.js");
+ }
+ return $buf;
+ }
+
+ static function page_bottom($theme) {
+ $session = Session::instance();
+ if (gallery::show_profiler()) {
+ Profiler::enable();
+ $profiler = new Profiler();
+ $profiler->render();
+ }
+ $content = "";
+ if ($session->get("l10n_mode", false)) {
+ $content .= L10n_Client_Controller::l10n_form();
+ }
+
+ if ($session->get_once("after_install")) {
+ $content .= new View("welcome_message_loader.html");
+ }
+
+ if (identity::active_user()->admin && upgrade_checker::should_auto_check()) {
+ $content .= '<script type="text/javascript">
+ $.ajax({url: "' . url::site("admin/upgrade_checker/check_now?csrf=" .
+ access::csrf_token()) . '"});
+ </script>';
+ }
+ return $content;
+ }
+
+ static function admin_page_bottom($theme) {
+ $session = Session::instance();
+ if (gallery::show_profiler()) {
+ Profiler::enable();
+ $profiler = new Profiler();
+ $profiler->render();
+ }
+
+ // Redirect to the root album when the admin session expires.
+ $content = '<script type="text/javascript">
+ var adminReauthCheck = function() {
+ $.ajax({url: "' . url::site("admin?reauth_check=1") . '",
+ dataType: "json",
+ success: function(data){
+ if ("location" in data) {
+ document.location = data.location;
+ }
+ }});
+ };
+ setInterval("adminReauthCheck();", 60 * 1000);
+ </script>';
+
+ if (upgrade_checker::should_auto_check()) {
+ $content .= '<script type="text/javascript">
+ $.ajax({url: "' . url::site("admin/upgrade_checker/check_now?csrf=" .
+ access::csrf_token()) . '"});
+ </script>';
+ }
+
+ if ($session->get("l10n_mode", false)) {
+ $content .= "\n" . L10n_Client_Controller::l10n_form();
+ }
+ return $content;
+ }
+
+ static function credits() {
+ $version_string = SafeString::of_safe_html(
+ '<bdo dir="ltr">Gallery ' . gallery::version_string() . '</bdo>');
+ return "<li class=\"g-first\">" .
+ t(module::get_var("gallery", "credits"),
+ array("url" => "http://galleryproject.org",
+ "gallery_version" => $version_string)) .
+ "</li>";
+ }
+
+ static function admin_credits() {
+ return gallery_theme::credits();
+ }
+
+ static function body_attributes() {
+ if (locales::is_rtl()) {
+ return 'class="rtl"';
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php
new file mode 100644
index 0000000..459784c
--- /dev/null
+++ b/modules/gallery/helpers/graphics.php
@@ -0,0 +1,546 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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;
+ private static $_rules_cache = array();
+
+ /**
+ * 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("gallery", "thumb", "gallery_graphics::resize",
+ * array("width" => 200, "height" => 200, "master" => Image::AUTO), 100);
+ *
+ * Specifies that "gallery" is adding a rule to resize thumbnails down to a max of 200px on
+ * the longest side. The gallery 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 (<defining class>::method)
+ * @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();
+
+ graphics::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(<defining class>::method)
+ */
+ static function remove_rule($module_name, $target, $operation) {
+ db::build()
+ ->delete("graphics_rules")
+ ->where("module_name", "=", $module_name)
+ ->where("target", "=", $target)
+ ->where("operation", "=", $operation)
+ ->execute();
+
+ graphics::mark_dirty($target == "thumb", $target == "resize");
+ }
+
+ /**
+ * Remove all rules for this module
+ * @param string $module_name
+ */
+ static function remove_rules($module_name) {
+ $status = db::build()
+ ->delete("graphics_rules")
+ ->where("module_name", "=", $module_name)
+ ->execute();
+ if (count($status)) {
+ graphics::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) {
+ db::build()
+ ->update("graphics_rules")
+ ->set("active", true)
+ ->where("module_name", "=", $module_name)
+ ->execute();
+ }
+
+ /**
+ * 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) {
+ db::build()
+ ->update("graphics_rules")
+ ->set("active", false)
+ ->where("module_name", "=", $module_name)
+ ->execute();
+ }
+
+ /**
+ * Rebuild the thumb and resize for the given item.
+ * @param Item_Model $item
+ */
+ static function generate($item) {
+ if ($item->thumb_dirty) {
+ $ops["thumb"] = $item->thumb_path();
+ }
+ if ($item->resize_dirty && $item->is_photo()) {
+ $ops["resize"] = $item->resize_path();
+ }
+
+ try {
+ foreach ($ops as $target => $output_file) {
+ $working_file = $item->file_path();
+ // Delete anything that might already be there
+ @unlink($output_file);
+ switch ($item->type) {
+ case "movie":
+ // Run movie_extract_frame events, which can either:
+ // - generate an output file, bypassing the ffmpeg-based movie::extract_frame
+ // - add to the options sent to movie::extract_frame (e.g. change frame extract time,
+ // add de-interlacing arguments to ffmpeg... see movie helper for more info)
+ // Note that the args are similar to those of the events in gallery_graphics
+ $movie_options_wrapper = new stdClass();
+ $movie_options_wrapper->movie_options = array();
+ module::event("movie_extract_frame", $working_file, $output_file,
+ $movie_options_wrapper, $item);
+ // If no output_file generated by events, run movie::extract_frame with movie_options
+ clearstatcache();
+ if (@filesize($output_file) == 0) {
+ try {
+ movie::extract_frame($working_file, $output_file, $movie_options_wrapper->movie_options);
+ // If we're here, we know ffmpeg is installed and the movie is valid. Because the
+ // user may not always have had ffmpeg installed, the movie's width, height, and
+ // mime type may need updating. Let's use this opportunity to make sure they're
+ // correct. It's not optimal to do it at this low level, but it's not trivial to find
+ // these cases quickly in an upgrade script.
+ list ($width, $height, $mime_type) = movie::get_file_metadata($working_file);
+ // Only set them if they need updating to avoid marking them as "changed"
+ if (($item->width != $width) || ($item->height != $height) ||
+ ($item->mime_type != $mime_type)) {
+ $item->width = $width;
+ $item->height = $height;
+ $item->mime_type = $mime_type;
+ }
+ } catch (Exception $e) {
+ // Didn't work, likely because of MISSING_FFMPEG - use placeholder
+ graphics::_replace_image_with_placeholder($item, $target);
+ break;
+ }
+ }
+ $working_file = $output_file;
+
+ case "photo":
+ // Run the graphics rules (for both movies and photos)
+ foreach (self::_get_rules($target) as $rule) {
+ $args = array($working_file, $output_file, unserialize($rule->args), $item);
+ call_user_func_array($rule->operation, $args);
+ $working_file = $output_file;
+ }
+ break;
+
+ case "album":
+ if (!$cover = $item->album_cover()) {
+ // This album has no cover; copy its placeholder image. Because of an old bug, it's
+ // possible that there's an album cover item id that points to an invalid item. In that
+ // case, just null out the album cover item id. It's not optimal to do that at this low
+ // level, but it's not trivial to find these cases quickly in an upgrade script and if we
+ // don't do this, the album may be permanently marked as "needs rebuilding"
+ //
+ // ref: http://sourceforge.net/apps/trac/gallery/ticket/1172
+ // http://galleryproject.org/node/96926
+ if ($item->album_cover_item_id) {
+ $item->album_cover_item_id = null;
+ $item->save();
+ }
+ graphics::_replace_image_with_placeholder($item, $target);
+ break;
+ }
+ if ($cover->thumb_dirty) {
+ graphics::generate($cover);
+ }
+ if (!$cover->thumb_dirty) {
+ // Make the album cover from the cover item's thumb. Run gallery_graphics::resize with
+ // null options and it will figure out if this is a direct copy or conversion to jpg.
+ $working_file = $cover->thumb_path();
+ gallery_graphics::resize($working_file, $output_file, null, $item);
+ }
+ break;
+ }
+ }
+
+ if (!empty($ops["thumb"])) {
+ if (file_exists($item->thumb_path())) {
+ $item->thumb_dirty = 0;
+ } else {
+ Kohana_Log::add("error", "Failed to rebuild thumb image: $item->title");
+ graphics::_replace_image_with_placeholder($item, "thumb");
+ }
+ }
+
+ if (!empty($ops["resize"])) {
+ if (file_exists($item->resize_path())) {
+ $item->resize_dirty = 0;
+ } else {
+ Kohana_Log::add("error", "Failed to rebuild resize image: $item->title");
+ graphics::_replace_image_with_placeholder($item, "resize");
+ }
+ }
+ graphics::_update_item_dimensions($item);
+ $item->save();
+ } catch (Exception $e) {
+ // Something went wrong rebuilding the image. Replace with the placeholder images,
+ // leave it dirty and move on.
+ Kohana_Log::add("error", "Caught exception rebuilding images: {$item->title}\n" .
+ $e->getMessage() . "\n" . $e->getTraceAsString());
+ if ($item->is_photo()) {
+ graphics::_replace_image_with_placeholder($item, "resize");
+ }
+ graphics::_replace_image_with_placeholder($item, "thumb");
+ try {
+ graphics::_update_item_dimensions($item);
+ } catch (Exception $e) {
+ // Looks like get_file_metadata couldn't identify our placeholders. We should never get
+ // here, but in the odd case we do, we need to do something. Let's put in hardcoded values.
+ if ($item->is_photo()) {
+ list ($item->resize_width, $item->resize_height) = array(200, 200);
+ }
+ list ($item->thumb_width, $item->thumb_height) = array(200, 200);
+ }
+ $item->save();
+ throw $e;
+ }
+ }
+
+ private static function _update_item_dimensions($item) {
+ if ($item->is_photo()) {
+ list ($item->resize_width, $item->resize_height) =
+ photo::get_file_metadata($item->resize_path());
+ }
+ list ($item->thumb_width, $item->thumb_height) =
+ photo::get_file_metadata($item->thumb_path());
+ }
+
+ private static function _replace_image_with_placeholder($item, $target) {
+ if ($item->is_album() && !$item->album_cover_item_id) {
+ $input_path = MODPATH . "gallery/images/missing_album_cover.jpg";
+ } else if ($item->is_movie() || ($item->is_album() && $item->album_cover()->is_movie())) {
+ $input_path = MODPATH . "gallery/images/missing_movie.jpg";
+ } else {
+ $input_path = MODPATH . "gallery/images/missing_photo.jpg";
+ }
+
+ if ($target == "thumb") {
+ $output_path = $item->thumb_path();
+ $size = module::get_var("gallery", "thumb_size", 200);
+ } else {
+ $output_path = $item->resize_path();
+ $size = module::get_var("gallery", "resize_size", 640);
+ }
+ $options = array("width" => $size, "height" => $size, "master" => Image::AUTO);
+
+ try {
+ // Copy/convert/resize placeholder as needed.
+ gallery_graphics::resize($input_path, $output_path, $options, null);
+ } catch (Exception $e) {
+ // Copy/convert/resize didn't work. Add to the log and copy the jpg version (which could have
+ // a non-jpg extension). This is less than ideal, but it's better than putting nothing
+ // there and causing theme views to act strangely because a file is missing.
+ // @todo we should handle this better.
+ Kohana_Log::add("error", "Caught exception converting placeholder for missing image: " .
+ $item->title . "\n" . $e->getMessage() . "\n" . $e->getTraceAsString());
+ copy($input_path, $output_path);
+ }
+
+ if (!file_exists($output_path)) {
+ // Copy/convert/resize didn't throw an exception, but still didn't work - do the same as above.
+ // @todo we should handle this better.
+ Kohana_Log::add("error", "Failed to convert placeholder for missing image: $item->title");
+ copy($input_path, $output_path);
+ }
+ }
+
+ private static function _get_rules($target) {
+ if (empty(self::$_rules_cache[$target])) {
+ $rules = array();
+ foreach (ORM::factory("graphics_rule")
+ ->where("target", "=", $target)
+ ->where("active", "=", true)
+ ->order_by("priority", "asc")
+ ->find_all() as $rule) {
+ $rules[] = (object)$rule->as_array();
+ }
+ self::$_rules_cache[$target] = $rules;
+ }
+ return self::$_rules_cache[$target];
+ }
+
+ /**
+ * Return a query result that locates all items with dirty images.
+ * @return Database_Result Query result
+ */
+ static function find_dirty_images_query() {
+ return db::build()
+ ->from("items")
+ ->and_open()
+ ->where("thumb_dirty", "=", 1)
+ ->and_open()
+ ->where("type", "<>", "album")
+ ->or_where("album_cover_item_id", "IS NOT", null)
+ ->close()
+ ->or_open()
+ ->where("resize_dirty", "=", 1)
+ ->where("type", "=", "photo")
+ ->close()
+ ->close();
+ }
+
+ /**
+ * Mark thumbnails and resizes as dirty. They will have to be rebuilt. Optionally, only those of
+ * a specified type and/or mime type can be marked (e.g. $type="movie" to rebuild movies only).
+ */
+ static function mark_dirty($thumbs, $resizes, $type=null, $mime_type=null) {
+ if ($thumbs || $resizes) {
+ $db = db::build()
+ ->update("items");
+ if ($type) {
+ $db->where("type", "=", $type);
+ }
+ if ($mime_type) {
+ $db->where("mime_type", "=", $mime_type);
+ }
+ if ($thumbs) {
+ $db->set("thumb_dirty", 1);
+ }
+ if ($resizes) {
+ $db->set("resize_dirty", 1);
+ }
+ $db->execute();
+ }
+
+ $count = graphics::find_dirty_images_query()->count_records();
+ 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" => html::mark_clean(sprintf(
+ 'href="%s" class="g-dialog-link"',
+ url::site("admin/maintenance/start/gallery_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() {
+ $toolkits = new stdClass();
+ $toolkits->gd = new stdClass();
+ $toolkits->imagemagick = new stdClass();
+ $toolkits->graphicsmagick = new stdClass();
+
+ // GD is special, it doesn't use exec()
+ $gd = function_exists("gd_info") ? gd_info() : array();
+ $toolkits->gd->name = "GD";
+ if (!isset($gd["GD Version"])) {
+ $toolkits->gd->installed = false;
+ $toolkits->gd->error = t("GD is not installed");
+ } else {
+ $toolkits->gd->installed = true;
+ $toolkits->gd->version = $gd["GD Version"];
+ $toolkits->gd->rotate = function_exists("imagerotate");
+ $toolkits->gd->sharpen = function_exists("imageconvolution");
+ $toolkits->gd->binary = "";
+ $toolkits->gd->dir = "";
+
+ if (!$toolkits->gd->rotate && !$toolkits->gd->sharpen) {
+ $toolkits->gd->error =
+ t("You have GD version %version, but it lacks image rotation and sharpening.",
+ array("version" => $gd["GD Version"]));
+ } else if (!$toolkits->gd->rotate) {
+ $toolkits->gd->error =
+ t("You have GD version %version, but it lacks image rotation.",
+ array("version" => $gd["GD Version"]));
+ } else if (!$toolkits->gd->sharpen) {
+ $toolkits->gd->error =
+ t("You have GD version %version, but it lacks image sharpening.",
+ array("version" => $gd["GD Version"]));
+ }
+ }
+
+ if (!function_exists("exec")) {
+ $toolkits->imagemagick->installed = false;
+ $toolkits->imagemagick->error = t("ImageMagick requires the <b>exec</b> function");
+
+ $toolkits->graphicsmagick->installed = false;
+ $toolkits->graphicsmagick->error = t("GraphicsMagick requires the <b>exec</b> function");
+ } else {
+ // ImageMagick & GraphicsMagick
+ $magick_kits = array(
+ "imagemagick" => array(
+ "name" => "ImageMagick", "binary" => "convert", "version_arg" => "-version",
+ "version_regex" => "/Version: \S+ (\S+)/"),
+ "graphicsmagick" => array(
+ "name" => "GraphicsMagick", "binary" => "gm", "version_arg" => "version",
+ "version_regex" => "/\S+ (\S+)/"));
+ // Loop through the kits
+ foreach ($magick_kits as $index => $settings) {
+ $path = system::find_binary(
+ $settings["binary"], module::get_var("gallery", "graphics_toolkit_path"));
+ $toolkits->$index->name = $settings["name"];
+ if ($path) {
+ if (@is_file($path) &&
+ preg_match(
+ $settings["version_regex"], shell_exec($path . " " . $settings["version_arg"]), $matches)) {
+ $version = $matches[1];
+
+ $toolkits->$index->installed = true;
+ $toolkits->$index->version = $version;
+ $toolkits->$index->binary = $path;
+ $toolkits->$index->dir = dirname($path);
+ $toolkits->$index->rotate = true;
+ $toolkits->$index->sharpen = true;
+ } else {
+ $toolkits->$index->installed = false;
+ $toolkits->$index->error =
+ t("%toolkit_name is installed, but PHP's open_basedir restriction prevents Gallery from using it.",
+ array("toolkit_name" => $settings["name"]));
+ }
+ } else {
+ $toolkits->$index->installed = false;
+ $toolkits->$index->error =
+ t("We could not locate %toolkit_name on your system.",
+ array("toolkit_name" => $settings["name"]));
+ }
+ }
+ }
+
+ return $toolkits;
+ }
+
+ /**
+ * 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->installed) {
+ module::set_var("gallery", "graphics_toolkit", $tk);
+ module::set_var("gallery", "graphics_toolkit_path", $toolkits->$tk->dir);
+ break;
+ }
+ }
+
+ if (!module::get_var("gallery", "graphics_toolkit")) {
+ site_status::warning(
+ t("Graphics toolkit missing! Please <a href=\"%url\">choose a toolkit</a>",
+ array("url" => html::mark_clean(url::site("admin/graphics")))),
+ "missing_graphics_toolkit");
+ }
+ }
+
+ /**
+ * Choose which driver the Kohana Image library uses.
+ */
+ static function init_toolkit() {
+ if (self::$init) {
+ return;
+ }
+ switch(module::get_var("gallery", "graphics_toolkit")) {
+ case "gd":
+ Kohana_Config::instance()->set("image.driver", "GD");
+ break;
+
+ case "imagemagick":
+ Kohana_Config::instance()->set("image.driver", "ImageMagick");
+ Kohana_Config::instance()->set(
+ "image.params.directory", module::get_var("gallery", "graphics_toolkit_path"));
+ break;
+
+ case "graphicsmagick":
+ Kohana_Config::instance()->set("image.driver", "GraphicsMagick");
+ Kohana_Config::instance()->set(
+ "image.params.directory", module::get_var("gallery", "graphics_toolkit_path"));
+ break;
+ }
+
+ self::$init = 1;
+ }
+
+ /**
+ * Verify that a specific graphics function is available with the active toolkit.
+ * @param string $func (eg rotate, sharpen)
+ * @return boolean
+ */
+ static function can($func) {
+ if (module::get_var("gallery", "graphics_toolkit") == "gd") {
+ switch ($func) {
+ case "rotate":
+ return function_exists("imagerotate");
+
+ case "sharpen":
+ return function_exists("imageconvolution");
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Return the max file size that this graphics toolkit can handle.
+ */
+ static function max_filesize() {
+ if (module::get_var("gallery", "graphics_toolkit") == "gd") {
+ $memory_limit = trim(ini_get("memory_limit"));
+ $memory_limit_bytes = num::convert_to_bytes($memory_limit);
+
+ // GD expands images in memory and uses 4 bytes of RAM for every byte
+ // in the file.
+ $max_filesize = $memory_limit_bytes / 4;
+ $max_filesize_human_readable = num::convert_to_human_readable($max_filesize);
+ return array($max_filesize, $max_filesize_human_readable);
+ }
+
+ // Some arbitrarily large size
+ return array(1000000000, "1G");
+ }
+}
diff --git a/modules/gallery/helpers/identity.php b/modules/gallery/helpers/identity.php
new file mode 100644
index 0000000..5fc0f2f
--- /dev/null
+++ b/modules/gallery/helpers/identity.php
@@ -0,0 +1,247 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 identity_Core {
+ protected static $available;
+
+ /**
+ * Return a list of installed Identity Drivers.
+ *
+ * @return boolean true if the driver supports updates; false if read only
+ */
+ static function providers() {
+ if (empty(self::$available)) {
+ $drivers = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
+ foreach (module::available() as $module_name => $module) {
+ if (file_exists(MODPATH . "{$module_name}/config/identity.php")) {
+ $drivers->$module_name = $module->description;
+ }
+ }
+ self::$available = $drivers;
+ }
+ return self::$available;
+ }
+
+ /**
+ * Frees the current instance of the identity provider so the next call to instance will reload
+ *
+ * @param string configuration
+ * @return Identity_Core
+ */
+ static function reset() {
+ IdentityProvider::reset();
+ }
+
+ /**
+ * Make sure that we have a session and group_ids cached in the session.
+ */
+ static function load_user() {
+ try {
+ // Call IdentityProvider::instance() now to force the load of the user interface classes.
+ // We are about to load the active user from the session and which needs the user definition
+ // class, which can't be reached by Kohana's heiracrchical lookup.
+ IdentityProvider::instance();
+
+ $session = Session::instance();
+ if (!($user = $session->get("user"))) {
+ identity::set_active_user($user = identity::guest());
+ }
+
+ // The installer cannot set a user into the session, so it just sets an id which we should
+ // upconvert into a user.
+ // @todo set the user name into the session instead of 2 and then use it to get the
+ // user object
+ if ($user === 2) {
+ $session->delete("user"); // delete it so that identity code isn't confused by the integer
+ auth::login(IdentityProvider::instance()->admin_user());
+ }
+
+ // Cache the group ids for a day to trade off performance for security updates.
+ if (!$session->get("group_ids") || $session->get("group_ids_timeout", 0) < time()) {
+ $ids = array();
+ foreach ($user->groups() as $group) {
+ $ids[] = $group->id;
+ }
+ $session->set("group_ids", $ids);
+ $session->set("group_ids_timeout", time() + 86400);
+ }
+ } catch (Exception $e) {
+ // Log it, so we at least have so notification that we swallowed the exception.
+ Kohana_Log::add("error", "load_user Exception: " .
+ $e->getMessage() . "\n" . $e->getTraceAsString());
+ try {
+ Session::instance()->destroy();
+ } catch (Exception $e) {
+ // We don't care if there was a problem destroying the session.
+ }
+ url::redirect(item::root()->abs_url());
+ }
+ }
+
+ /**
+ * Return the array of group ids this user belongs to
+ *
+ * @return array
+ */
+ static function group_ids_for_active_user() {
+ return Session::instance()->get("group_ids", array(1));
+ }
+
+ /**
+ * Return the active user. If there's no active user, return the guest user.
+ *
+ * @return User_Definition
+ */
+ static function active_user() {
+ // @todo (maybe) cache this object so we're not always doing session lookups.
+ $user = Session::instance()->get("user", null);
+ if (!isset($user)) {
+ // Don't do this as a fallback in the Session::get() call because it can trigger unnecessary
+ // work.
+ $user = identity::guest();
+ }
+ return $user;
+ }
+
+ /**
+ * Change the active user.
+ * @param User_Definition $user
+ */
+ static function set_active_user($user) {
+ $session = Session::instance();
+ $session->set("user", $user);
+ $session->delete("group_ids");
+ identity::load_user();
+ }
+
+ /**
+ * Determine if if the current driver supports updates.
+ *
+ * @return boolean true if the driver supports updates; false if read only
+ */
+ static function is_writable() {
+ return IdentityProvider::instance()->is_writable();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::guest.
+ */
+ static function guest() {
+ return IdentityProvider::instance()->guest();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::admin_user.
+ */
+ static function admin_user() {
+ return IdentityProvider::instance()->admin_user();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::create_user.
+ */
+ static function create_user($name, $full_name, $password, $email) {
+ return IdentityProvider::instance()->create_user($name, $full_name, $password, $email);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::is_correct_password.
+ */
+ static function is_correct_password($user, $password) {
+ return IdentityProvider::instance()->is_correct_password($user, $password);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_user.
+ */
+ static function lookup_user($id) {
+ return IdentityProvider::instance()->lookup_user($id);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_user_by_name.
+ */
+ static function lookup_user_by_name($name) {
+ return IdentityProvider::instance()->lookup_user_by_name($name);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::create_group.
+ */
+ static function create_group($name) {
+ return IdentityProvider::instance()->create_group($name);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::everybody.
+ */
+ static function everybody() {
+ return IdentityProvider::instance()->everybody();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::registered_users.
+ */
+ static function registered_users() {
+ return IdentityProvider::instance()->registered_users();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_group.
+ */
+ static function lookup_group($id) {
+ return IdentityProvider::instance()->lookup_group($id);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_group_by_name.
+ */
+ static function lookup_group_by_name($name) {
+ return IdentityProvider::instance()->lookup_group_by_name($name);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::get_user_list.
+ */
+ static function get_user_list($ids) {
+ return IdentityProvider::instance()->get_user_list($ids);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::groups.
+ */
+ static function groups() {
+ return IdentityProvider::instance()->groups();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::add_user_to_group.
+ */
+ static function add_user_to_group($user, $group) {
+ return IdentityProvider::instance()->add_user_to_group($user, $group);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::remove_user_to_group.
+ */
+ static function remove_user_from_group($user, $group) {
+ return IdentityProvider::instance()->remove_user_from_group($user, $group);
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php
new file mode 100644
index 0000000..9882a9c
--- /dev/null
+++ b/modules/gallery/helpers/item.php
@@ -0,0 +1,433 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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("view", $source);
+ access::required("view", $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);
+ }
+ }
+
+ $orig_name = $source->name;
+ $source->parent_id = $target->id;
+ $source->save();
+ if ($orig_name != $source->name) {
+ switch ($source->type) {
+ case "album":
+ message::info(
+ t("Album <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict",
+ array("old_name" => $orig_name, "new_name" => $source->name)));
+ break;
+
+ case "photo":
+ message::info(
+ t("Photo <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict",
+ array("old_name" => $orig_name, "new_name" => $source->name)));
+ break;
+
+ case "movie":
+ message::info(
+ t("Movie <b>%old_name</b> renamed to <b>%new_name</b> to avoid a conflict",
+ array("old_name" => $orig_name, "new_name" => $source->name)));
+ break;
+ }
+ }
+
+ // 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("view", $item);
+ access::required("view", $parent);
+ access::required("edit", $parent);
+
+ $old_album_cover_id = $parent->album_cover_item_id;
+
+ model_cache::clear();
+ $parent->album_cover_item_id = $item->is_album() ? $item->album_cover_item_id : $item->id;
+ $parent->save();
+ graphics::generate($parent);
+
+ // Walk up the parent hierarchy and set album covers if necessary
+ $grand_parent = $parent->parent();
+ if ($grand_parent && access::can("edit", $grand_parent) &&
+ $grand_parent->album_cover_item_id == null) {
+ item::make_album_cover($parent);
+ }
+
+ // When albums are album covers themselves, we hotlink directly to the target item. This
+ // means that when we change an album cover, the grandparent may have a deep link to the old
+ // album cover. So find any parent albums that had the old item as their album cover and
+ // switch them over to the new item.
+ if ($old_album_cover_id) {
+ foreach ($item->parents(array(array("album_cover_item_id", "=", $old_album_cover_id)))
+ as $ancestor) {
+ if (access::can("edit", $ancestor)) {
+ $ancestor->album_cover_item_id = $parent->album_cover_item_id;
+ $ancestor->save();
+ graphics::generate($ancestor);
+ }
+ }
+ }
+ }
+
+ static function remove_album_cover($album) {
+ access::required("view", $album);
+ access::required("edit", $album);
+
+ model_cache::clear();
+ $album->album_cover_item_id = null;
+ $album->save();
+ graphics::generate($album);
+ }
+
+ /**
+ * Sanitize a filename into something presentable as an item title
+ * @param string $filename
+ * @return string title
+ */
+ static function convert_filename_to_title($filename) {
+ $title = strtr($filename, "_", " ");
+ $title = preg_replace("/\..{3,4}$/", "", $title);
+ $title = preg_replace("/ +/", " ", $title);
+ return $title;
+ }
+
+ /**
+ * Convert a filename into something we can use as a url component.
+ * @param string $filename
+ */
+ static function convert_filename_to_slug($filename) {
+ $result = str_replace("&", "-and-", $filename);
+ $result = str_replace(" ", "-", $result);
+
+ // It's not easy to extend the text helper since it's called by the Input class which is
+ // referenced in hooks/init_gallery, so it's
+ if (class_exists("transliterate")) {
+ $result = transliterate::utf8_to_ascii($result);
+ } else {
+ $result = text::transliterate_to_ascii($result);
+ }
+ $result = preg_replace("/[^A-Za-z0-9-_]+/", "-", $result);
+ $result = preg_replace("/-+/", "-", $result);
+ return trim($result, "-");
+ }
+
+ /**
+ * Display delete confirmation message and form
+ * @param object $item
+ * @return string form
+ */
+ static function get_delete_form($item) {
+ $page_type = Input::instance()->get("page_type");
+ $from_id = Input::instance()->get("from_id");
+ $form = new Forge(
+ "quick/delete/$item->id?page_type=$page_type&from_id=$from_id", "",
+ "post", array("id" => "g-confirm-delete"));
+ $group = $form->group("confirm_delete")->label(t("Confirm Deletion"));
+ $group->submit("")->value(t("Delete"));
+ $form->script("")
+ ->url(url::abs_file("modules/gallery/js/item_form_delete.js"));
+ return $form;
+ }
+
+ /**
+ * Get the next weight value
+ */
+ static function get_max_weight() {
+ // Guard against an empty result when we create the first item. It's unfortunate that we
+ // have to check this every time.
+ // @todo: figure out a better way to bootstrap the weight.
+ $result = db::build()
+ ->select("weight")->from("items")
+ ->order_by("weight", "desc")->limit(1)
+ ->execute()->current();
+ return ($result ? $result->weight : 0) + 1;
+ }
+
+ /**
+ * Add a set of restrictions to any following queries to restrict access only to items
+ * viewable by the active user.
+ * @chainable
+ */
+ static function viewable($model) {
+ $view_restrictions = array();
+ if (!identity::active_user()->admin) {
+ foreach (identity::group_ids_for_active_user() as $id) {
+ $view_restrictions[] = array("items.view_$id", "=", access::ALLOW);
+ }
+ }
+
+ if (count($view_restrictions)) {
+ $model->and_open()->merge_or_where($view_restrictions)->close();
+ }
+
+ return $model;
+ }
+
+ /**
+ * Find an item by its path. If there's no match, return an empty Item_Model.
+ * NOTE: the caller is responsible for performing security checks on the resulting item.
+ * @param string $path
+ * @return object Item_Model
+ */
+ static function find_by_path($path) {
+ $path = trim($path, "/");
+
+ // The root path name is NULL not "", hence this workaround.
+ if ($path == "") {
+ return item::root();
+ }
+
+ // Check to see if there's an item in the database with a matching relative_path_cache value.
+ // Since that field is urlencoded, we must urlencoded the components of the path.
+ foreach (explode("/", $path) as $part) {
+ $encoded_array[] = rawurlencode($part);
+ }
+ $encoded_path = join("/", $encoded_array);
+ $item = ORM::factory("item")
+ ->where("relative_path_cache", "=", $encoded_path)
+ ->find();
+ if ($item->loaded()) {
+ return $item;
+ }
+
+ // Since the relative_path_cache field is a cache, it can be unavailable. If we don't find
+ // anything, fall back to checking the path the hard way.
+ $paths = explode("/", $path);
+ foreach (ORM::factory("item")
+ ->where("name", "=", end($paths))
+ ->where("level", "=", count($paths) + 1)
+ ->find_all() as $item) {
+ if (urldecode($item->relative_path()) == $path) {
+ return $item;
+ }
+ }
+
+ return new Item_Model();
+ }
+
+
+ /**
+ * Locate an item using the URL. We assume that the url is in the form /a/b/c where each
+ * component matches up with an item slug. If there's no match, return an empty Item_Model
+ * NOTE: the caller is responsible for performing security checks on the resulting item.
+ * @param string $url the relative url fragment
+ * @return Item_Model
+ */
+ static function find_by_relative_url($relative_url) {
+ // In most cases, we'll have an exact match in the relative_url_cache item field.
+ // but failing that, walk down the tree until we find it. The fallback code will fix caches
+ // as it goes, so it'll never be run frequently.
+ $item = ORM::factory("item")->where("relative_url_cache", "=", $relative_url)->find();
+ if (!$item->loaded()) {
+ $segments = explode("/", $relative_url);
+ foreach (ORM::factory("item")
+ ->where("slug", "=", end($segments))
+ ->where("level", "=", count($segments) + 1)
+ ->find_all() as $match) {
+ if ($match->relative_url() == $relative_url) {
+ $item = $match;
+ }
+ }
+ }
+ return $item;
+ }
+
+ /**
+ * Return the root Item_Model
+ * @return Item_Model
+ */
+ static function root() {
+ return model_cache::get("item", 1);
+ }
+
+ /**
+ * Return a query to get a random Item_Model, with optional filters.
+ * Usage: item::random_query()->execute();
+ *
+ * Note: You can add your own ->where() clauses but if your Gallery is
+ * small or your where clauses are over-constrained you may wind up with
+ * no item. You should try running this a few times in a loop if you
+ * don't get an item back.
+ */
+ static function random_query() {
+ // Pick a random number and find the item that's got nearest smaller number.
+ // This approach works best when the random numbers in the system are roughly evenly
+ // distributed so this is going to be more efficient with larger data sets.
+ return ORM::factory("item")
+ ->viewable()
+ ->where("rand_key", "<", random::percent())
+ ->order_by("rand_key", "DESC");
+ }
+
+ /**
+ * Find the position of the given item in its parent album. The resulting
+ * value is 1-indexed, so the first child in the album is at position 1.
+ *
+ * @param Item_Model $item
+ * @param array $where an array of arrays, each compatible with ORM::where()
+ */
+ static function get_position($item, $where=array()) {
+ $album = $item->parent();
+
+ if (!strcasecmp($album->sort_order, "DESC")) {
+ $comp = ">";
+ } else {
+ $comp = "<";
+ }
+ $query_model = ORM::factory("item");
+
+ // If the comparison column has NULLs in it, we can't use comparators on it
+ // and will have to deal with it the hard way.
+ $count = $query_model->viewable()
+ ->where("parent_id", "=", $album->id)
+ ->where($album->sort_column, "IS", null)
+ ->merge_where($where)
+ ->count_all();
+
+ if (empty($count)) {
+ // There are no NULLs in the sort column, so we can just use it directly.
+ $sort_column = $album->sort_column;
+
+ $position = $query_model->viewable()
+ ->where("parent_id", "=", $album->id)
+ ->where($sort_column, $comp, $item->$sort_column)
+ ->merge_where($where)
+ ->count_all();
+
+ // We stopped short of our target value in the sort (notice that we're
+ // using a inequality comparator above) because it's possible that we have
+ // duplicate values in the sort column. An equality check would just
+ // arbitrarily pick one of those multiple possible equivalent columns,
+ // which would mean that if you choose a sort order that has duplicates,
+ // it'd pick any one of them as the child's "position".
+ //
+ // Fix this by doing a 2nd query where we iterate over the equivalent
+ // columns and add them to our position count.
+ foreach ($query_model->viewable()
+ ->select("id")
+ ->where("parent_id", "=", $album->id)
+ ->where($sort_column, "=", $item->$sort_column)
+ ->merge_where($where)
+ ->order_by(array("id" => "ASC"))
+ ->find_all() as $row) {
+ $position++;
+ if ($row->id == $item->id) {
+ break;
+ }
+ }
+ } else {
+ // There are NULLs in the sort column, so we can't use MySQL comparators.
+ // Fall back to iterating over every child row to get to the current one.
+ // This can be wildly inefficient for really large albums, but it should
+ // be a rare case that the user is sorting an album with null values in
+ // the sort column.
+ //
+ // Reproduce the children() functionality here using Database directly to
+ // avoid loading the whole ORM for each row.
+ $order_by = array($album->sort_column => $album->sort_order);
+ // Use id as a tie breaker
+ if ($album->sort_column != "id") {
+ $order_by["id"] = "ASC";
+ }
+
+ $position = 0;
+ foreach ($query_model->viewable()
+ ->select("id")
+ ->where("parent_id", "=", $album->id)
+ ->merge_where($where)
+ ->order_by($order_by)
+ ->find_all() as $row) {
+ $position++;
+ if ($row->id == $item->id) {
+ break;
+ }
+ }
+ }
+
+ return $position;
+ }
+
+ /**
+ * Set the display context callback for any future item renders.
+ */
+ static function set_display_context_callback() {
+ if (!request::user_agent("robot")) {
+ $args = func_get_args();
+ Cache::instance()->set("display_context_" . $sid = Session::instance()->id(), $args,
+ array("display_context"));
+ }
+ }
+
+ /**
+ * Get rid of the display context callback
+ */
+ static function clear_display_context_callback() {
+ Cache::instance()->delete("display_context_" . $sid = Session::instance()->id());
+ }
+
+ /**
+ * Call the display context callback for the given item
+ */
+ static function get_display_context($item) {
+ if (!request::user_agent("robot")) {
+ $args = Cache::instance()->get("display_context_" . $sid = Session::instance()->id());
+ $callback = $args[0];
+ $args[0] = $item;
+ }
+
+ if (empty($callback)) {
+ $callback = "Albums_Controller::get_display_context";
+ $args = array($item);
+ }
+ return call_user_func_array($callback, $args);
+ }
+
+ /**
+ * Reset all child weights of a given album to a monotonically increasing sequence based on the
+ * current sort order of the album.
+ */
+ static function resequence_child_weights($album) {
+ $weight = 0;
+ foreach ($album->children() as $child) {
+ $child->weight = ++$weight;
+ $child->save();
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/item_rest.php b/modules/gallery/helpers/item_rest.php
new file mode 100644
index 0000000..efeba2e
--- /dev/null
+++ b/modules/gallery/helpers/item_rest.php
@@ -0,0 +1,210 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_rest_Core {
+ /**
+ * For items that are collections, you can specify the following additional query parameters to
+ * query the collection. You can specify them in any combination.
+ *
+ * scope=direct
+ * Only return items that are immediately under this one
+ * scope=all
+ * Return items anywhere under this one
+ *
+ * name=<substring>
+ * Only return items where the name contains this substring
+ *
+ * random=true
+ * Return a single random item
+ *
+ * type=<comma separate list of photo, movie or album>
+ * Limit the type to types in this list, eg: "type=photo,movie".
+ * Also limits the types returned in the member collections (same behaviour as item_rest).
+ */
+ static function get($request) {
+ $item = rest::resolve($request->url);
+ access::required("view", $item);
+
+ $p = $request->params;
+ if (isset($p->random)) {
+ $orm = item::random_query()->offset(0)->limit(1);
+ } else {
+ $orm = ORM::factory("item")->viewable();
+ }
+
+ if (empty($p->scope)) {
+ $p->scope = "direct";
+ }
+
+ if (!in_array($p->scope, array("direct", "all"))) {
+ throw new Rest_Exception("Bad Request", 400);
+ }
+
+ if ($p->scope == "direct") {
+ $orm->where("parent_id", "=", $item->id);
+ } else {
+ $orm->where("left_ptr", ">", $item->left_ptr);
+ $orm->where("right_ptr", "<", $item->right_ptr);
+ }
+
+ if (isset($p->name)) {
+ $orm->where("name", "LIKE", "%" . Database::escape_for_like($p->name) . "%");
+ }
+
+ if (isset($p->type)) {
+ $orm->where("type", "IN", explode(",", $p->type));
+ }
+
+ // Apply the item's sort order, using id as the tie breaker.
+ // See Item_Model::children()
+ $order_by = array($item->sort_column => $item->sort_order);
+ if ($item->sort_column != "id") {
+ $order_by["id"] = "ASC";
+ }
+ $orm->order_by($order_by);
+
+ $result = array(
+ "url" => $request->url,
+ "entity" => $item->as_restful_array(),
+ "relationships" => rest::relationships("item", $item));
+ if ($item->is_album()) {
+ $result["members"] = array();
+ foreach ($orm->find_all() as $child) {
+ $result["members"][] = rest::url("item", $child);
+ }
+ }
+
+ return $result;
+ }
+
+ static function put($request) {
+ $item = rest::resolve($request->url);
+ access::required("edit", $item);
+
+ if ($entity = $request->params->entity) {
+ // Only change fields from a whitelist.
+ foreach (array("album_cover", "captured", "description",
+ "height", "mime_type", "name", "parent", "rand_key", "resize_dirty",
+ "resize_height", "resize_width", "slug", "sort_column", "sort_order",
+ "thumb_dirty", "thumb_height", "thumb_width", "title", "view_count",
+ "width") as $key) {
+ switch ($key) {
+ case "album_cover":
+ if (property_exists($entity, "album_cover")) {
+ $album_cover_item = rest::resolve($entity->album_cover);
+ access::required("view", $album_cover_item);
+ $item->album_cover_item_id = $album_cover_item->id;
+ }
+ break;
+
+ case "parent":
+ if (property_exists($entity, "parent")) {
+ $parent = rest::resolve($entity->parent);
+ access::required("edit", $parent);
+ $item->parent_id = $parent->id;
+ }
+ break;
+ default:
+ if (property_exists($entity, $key)) {
+ $item->$key = $entity->$key;
+ }
+ }
+ }
+ }
+
+ // Replace the data file, if required
+ if (($item->is_photo() || $item->is_movie()) && isset($request->file)) {
+ $item->set_data_file($request->file);
+ }
+
+ $item->save();
+
+ if (isset($request->params->members) && $item->sort_column == "weight") {
+ $weight = 0;
+ foreach ($request->params->members as $url) {
+ $child = rest::resolve($url);
+ if ($child->parent_id == $item->id && $child->weight != $weight) {
+ $child->weight = $weight;
+ $child->save();
+ }
+ $weight++;
+ }
+ }
+ }
+
+ static function post($request) {
+ $parent = rest::resolve($request->url);
+ access::required("add", $parent);
+
+ $entity = $request->params->entity;
+ $item = ORM::factory("item");
+ switch ($entity->type) {
+ case "album":
+ $item->type = "album";
+ $item->parent_id = $parent->id;
+ $item->name = $entity->name;
+ $item->title = isset($entity->title) ? $entity->title : $entity->name;
+ $item->description = isset($entity->description) ? $entity->description : null;
+ $item->slug = isset($entity->slug) ? $entity->slug : null;
+ $item->save();
+ break;
+
+ case "photo":
+ case "movie":
+ if (empty($request->file)) {
+ throw new Rest_Exception(
+ "Bad Request", 400, array("errors" => array("file" => t("Upload failed"))));
+ }
+ $item->type = $entity->type;
+ $item->parent_id = $parent->id;
+ $item->set_data_file($request->file);
+ $item->name = $entity->name;
+ $item->title = isset($entity->title) ? $entity->title : $entity->name;
+ $item->description = isset($entity->description) ? $entity->description : null;
+ $item->slug = isset($entity->slug) ? $entity->slug : null;
+ $item->save();
+ break;
+
+ default:
+ throw new Rest_Exception(
+ "Bad Request", 400, array("errors" => array("type" => "invalid")));
+ }
+
+ return array("url" => rest::url("item", $item));
+ }
+
+ static function delete($request) {
+ $item = rest::resolve($request->url);
+ access::required("edit", $item);
+
+ $item->delete();
+ }
+
+ static function resolve($id) {
+ $item = ORM::factory("item", $id);
+ if (!access::can("view", $item)) {
+ throw new Kohana_404_Exception();
+ }
+ return $item;
+ }
+
+ static function url($item) {
+ return url::abs_site("rest/item/{$item->id}");
+ }
+}
diff --git a/modules/gallery/helpers/items_rest.php b/modules/gallery/helpers/items_rest.php
new file mode 100644
index 0000000..7622c53
--- /dev/null
+++ b/modules/gallery/helpers/items_rest.php
@@ -0,0 +1,96 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_rest_Core {
+ /**
+ * To retrieve a collection of items, you can specify the following query parameters to specify
+ * the type of the collection. If both are specified, then the url parameter is used and the
+ * ancestors_for is ignored. Specifying the "type" parameter with the urls parameter, will
+ * filter the results based on the specified type. Using the type parameter with the
+ * ancestors_for parameter makes no sense and will be ignored.
+ *
+ * urls=["url1","url2","url3"]
+ * Return items that match the specified urls. Typically used to return the member detail
+ *
+ * ancestors_for=url
+ * Return the ancestors of the specified item
+ *
+ * type=<comma separate list of photo, movie or album>
+ * Limit the type to types in this list, eg: "type=photo,movie".
+ * Also limits the types returned in the member collections (same behaviour as item_rest).
+ * Ignored if ancestors_for is set.
+ */
+ static function get($request) {
+ $items = array();
+ $types = array();
+
+ if (isset($request->params->urls)) {
+ if (isset($request->params->type)) {
+ $types = explode(",", $request->params->type);
+ }
+
+ foreach (json_decode($request->params->urls) as $url) {
+ $item = rest::resolve($url);
+ if (!access::can("view", $item)) {
+ continue;
+ }
+
+ if (empty($types) || in_array($item->type, $types)) {
+ $items[] = items_rest::_format_restful_item($item, $types);
+ }
+ }
+ } else if (isset($request->params->ancestors_for)) {
+ $item = rest::resolve($request->params->ancestors_for);
+ if (!access::can("view", $item)) {
+ throw new Kohana_404_Exception();
+ }
+ $items[] = items_rest::_format_restful_item($item, $types);
+ while (($item = $item->parent()) != null) {
+ array_unshift($items, items_rest::_format_restful_item($item, $types));
+ };
+ }
+
+ return $items;
+ }
+
+ static function resolve($id) {
+ $item = ORM::factory("item", $id);
+ if (!access::can("view", $item)) {
+ throw new Kohana_404_Exception();
+ }
+ return $item;
+ }
+
+ private static function _format_restful_item($item, $types) {
+ $item_rest = array("url" => rest::url("item", $item),
+ "entity" => $item->as_restful_array(),
+ "relationships" => rest::relationships("item", $item));
+ if ($item->type == "album") {
+ $members = array();
+ foreach ($item->viewable()->children() as $child) {
+ if (empty($types) || in_array($child->type, $types)) {
+ $members[] = rest::url("item", $child);
+ }
+ }
+ $item_rest["members"] = $members;
+ }
+
+ return $item_rest;
+ }
+}
diff --git a/modules/gallery/helpers/json.php b/modules/gallery/helpers/json.php
new file mode 100644
index 0000000..b56f269
--- /dev/null
+++ b/modules/gallery/helpers/json.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 json_Core {
+ /**
+ * JSON Encode a reply to the browser and set the content type to specify that it's a JSON
+ * payload.
+ *
+ * @param mixed $message string or object to json encode and print
+ */
+ static function reply($message) {
+ header("Content-Type: application/json; charset=" . Kohana::CHARSET);
+ print json_encode($message);
+ }
+} \ 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 0000000..2a1be2f
--- /dev/null
+++ b/modules/gallery/helpers/l10n_client.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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($path) {
+ return "http://galleryproject.org/translations/$path";
+ }
+
+ static function server_api_key_url() {
+ return self::_server_url("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("gallery", "l10n_client_key", $api_key);
+ }
+ return module::get_var("gallery", "l10n_client_key", "");
+ }
+
+ static function server_uid($api_key=null) {
+ $api_key = $api_key == null ? l10n_client::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 ? l10n_client::api_key() : $api_key;
+ return md5($api_key . $payload . l10n_client::client_token());
+ }
+
+ static function validate_api_key($api_key) {
+ $version = "1.0";
+ $url = self::_server_url("status");
+ $signature = self::_sign($version, $api_key);
+
+ try {
+ list ($response_data, $response_status) = remote::post(
+ $url, array("version" => $version,
+ "client_token" => l10n_client::client_token(),
+ "signature" => $signature,
+ "uid" => l10n_client::server_uid($api_key)));
+ } catch (ErrorException $e) {
+ // Log the error, but then return a "can't make connection" error
+ Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
+ }
+ if (!isset($response_data) && !isset($response_status)) {
+ return array(false, false);
+ }
+
+ if (!remote::success($response_status)) {
+ return array(true, false);
+ }
+ return array(true, true);
+ }
+
+ /**
+ * Fetches translations for l10n messages. Must be called repeatedly
+ * until 0 is returned (which is a countdown indicating progress).
+ *
+ * @param $num_fetched in/out parameter to specify which batch of
+ * messages to fetch translations for.
+ * @return The number of messages for which we didn't fetch
+ * translations for.
+ */
+ static function fetch_updates(&$num_fetched) {
+ $request = new stdClass();
+ $request->locales = array();
+ $request->messages = new stdClass();
+
+ $locales = locales::installed();
+ foreach ($locales as $locale => $locale_data) {
+ $request->locales[] = $locale;
+ }
+
+ // See the server side code for how we arrive at this
+ // number as a good limit for #locales * #messages.
+ $max_messages = 2000 / count($locales);
+ $num_messages = 0;
+ $rows = db::build()
+ ->select("key", "locale", "revision", "translation")
+ ->from("incoming_translations")
+ ->order_by("key")
+ ->limit(1000000) // ignore, just there to satisfy SQL syntax
+ ->offset($num_fetched)
+ ->execute();
+ $num_remaining = $rows->count();
+ foreach ($rows as $row) {
+ if (!isset($request->messages->{$row->key})) {
+ if ($num_messages >= $max_messages) {
+ break;
+ }
+ $request->messages->{$row->key} = 1;
+ $num_messages++;
+ }
+ if (!empty($row->revision) && !empty($row->translation) &&
+ isset($locales[$row->locale])) {
+ if (!is_object($request->messages->{$row->key})) {
+ $request->messages->{$row->key} = new stdClass();
+ }
+ $request->messages->{$row->key}->{$row->locale} = (int) $row->revision;
+ }
+ $num_fetched++;
+ $num_remaining--;
+ }
+ // @todo Include messages from outgoing_translations?
+
+ if (!$num_messages) {
+ return $num_remaining;
+ }
+
+ $request_data = json_encode($request);
+ $url = self::_server_url("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)) {
+ return $num_remaining;
+ }
+
+ $response = json_decode($response_data);
+
+ // Response format (JSON payload):
+ // [{key:<key_1>, translation: <JSON encoded translation>, rev:<rev>, locale:<locale>},
+ // {key:<key_2>, ...}
+ // ]
+ 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 = json_decode($message_data->translation);
+ if (!is_string($translation)) {
+ // Normalize stdclass to array
+ $translation = (array) $translation;
+ }
+ $translation = serialize($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("key", "=", $key)
+ ->where("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("key", "=", $key)
+ ->where("locale", "=", "root")
+ ->find();
+ $entry->key = $key;
+ $entry->message = $root_entry->message;
+ $entry->locale = $locale;
+ }
+ $entry->revision = $revision;
+ $entry->translation = $translation;
+ $entry->save();
+ }
+
+ return $num_remaining;
+ }
+
+ 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?
+ $request = new stdClass();
+ foreach (db::build()
+ ->select("key", "message", "locale", "base_revision", "translation")
+ ->from("outgoing_translations")
+ ->execute() as $row) {
+ $key = $row->key;
+ if (!isset($request->{$key})) {
+ $request->{$key} = new stdClass();
+ $request->{$key}->translations = new stdClass();
+ $request->{$key}->message = json_encode(unserialize($row->message));
+ }
+ $request->{$key}->translations->{$row->locale} = json_encode(unserialize($row->translation));
+ }
+
+ // @todo reduce memory consumption, e.g. free $request
+ $request_data = json_encode($request);
+ $url = self::_server_url("submit");
+ $signature = self::_sign($request_data);
+
+ list ($response_data, $response_status) = remote::post(
+ $url, array("data" => $request_data,
+ "client_token" => l10n_client::client_token(),
+ "signature" => $signature,
+ "uid" => l10n_client::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?
+ }
+
+ /**
+ * Plural forms.
+ */
+ static function plural_forms($locale) {
+ $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 array('other');
+
+ case 'ar':
+ return array('zero', 'one', 'two', 'few', 'many', 'other');
+
+ case 'lv':
+ return array('zero', 'one', 'other');
+
+ case 'ga':
+ case 'se':
+ case 'sma':
+ case 'smi':
+ case 'smj':
+ case 'smn':
+ case 'sms':
+ return array('one', 'two', 'other');
+
+ case 'ro':
+ case 'mo':
+ case 'lt':
+ case 'cs':
+ case 'sk':
+ case 'pl':
+ return array('one', 'few', 'other');
+
+ case 'hr':
+ case 'ru':
+ case 'sr':
+ case 'uk':
+ case 'be':
+ case 'bs':
+ case 'sh':
+ case 'mt':
+ return array('one', 'few', 'many', 'other');
+
+ case 'sl':
+ return array('one', 'two', 'few', 'other');
+
+ case 'cy':
+ return array('one', 'two', 'many', 'other');
+
+ default: // en, de, etc.
+ return array('one', 'other');
+ }
+ }
+} \ 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 0000000..5980ebe
--- /dev/null
+++ b/modules/gallery/helpers/l10n_scanner.php
@@ -0,0 +1,178 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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:
+ // G‡bor Hojtsy http://drupal.org/user/4166
+ public static $cache;
+
+ static function process_message($message, &$cache) {
+ if (empty($cache)) {
+ foreach (db::build()
+ ->select("key")
+ ->from("incoming_translations")
+ ->where("locale", "=", "root")
+ ->execute() as $row) {
+ $cache[$row->key] = true;
+ }
+ }
+
+ $key = Gallery_I18n::get_message_key($message);
+ if (array_key_exists($key, $cache)) {
+ return $cache[$key];
+ }
+
+ $entry = ORM::factory("incoming_translation")->where("key", "=", $key)->find();
+ 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"])) {
+ $errors = l10n_scanner::_parse_t_calls($tokens, $func_token_list["t"], $cache);
+ foreach ($errors as $line => $error) {
+ Kohana_Log::add(
+ "error", "Translation scanner error. " .
+ "file: " . substr($file, strlen(DOCROOT)) . ", line: $line, context: $error");
+ }
+ }
+
+ if (!empty($func_token_list["t2"])) {
+ $errors = l10n_scanner::_parse_plural_calls($tokens, $func_token_list["t2"], $cache);
+ foreach ($errors as $line => $error) {
+ Kohana_Log::add(
+ "error", "Translation scanner error. " .
+ "file: " . substr($file, strlen(DOCROOT)) . ", line: $line, context: $error");
+ }
+ }
+ }
+
+ static function scan_info_file($file, &$cache) {
+ $info = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS);
+ foreach (array('name', 'description') as $property) {
+ if (isset($info->$property)) {
+ l10n_scanner::process_message($info->$property, $cache);
+ }
+ }
+ }
+
+ private static function _parse_t_calls(&$tokens, &$call_list, &$cache) {
+ $errors = array();
+ 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 {
+ if (is_array($first_param) && ($first_param[0] == T_CONSTANT_ENCAPSED_STRING)) {
+ // Malformed string literals; escalate this
+ $errors[$first_param[2]] =
+ var_export(array($function_name, $parens, $first_param, $next_token), 1);
+ } else {
+ // t() found, but inside is something which is not a string literal. That's fine.
+ }
+ }
+ }
+ }
+ return $errors;
+ }
+
+ private static function _parse_plural_calls(&$tokens, &$call_list, &$cache) {
+ $errors = array();
+ 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($second_param[1]);
+ l10n_scanner::process_message(array("one" => $singular, "other" => $plural), $cache);
+ } else {
+ if (is_array($first_param) && $first_param[0] == T_CONSTANT_ENCAPSED_STRING) {
+ $errors[$first_param[2]] = var_export(
+ array($function_name, $parens, $first_param,
+ $first_separator, $second_param, $next_token), 1);
+ } else {
+ // t2() found, but inside is something which is not a string literal. That's fine.
+ }
+ }
+ }
+ }
+ return $errors;
+ }
+
+ /**
+ * 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 $str;
+ }
+}
diff --git a/modules/gallery/helpers/legal_file.php b/modules/gallery/helpers/legal_file.php
new file mode 100644
index 0000000..eb9c25d
--- /dev/null
+++ b/modules/gallery/helpers/legal_file.php
@@ -0,0 +1,310 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 legal_file_Core {
+ private static $photo_types_by_extension;
+ private static $movie_types_by_extension;
+ private static $photo_extensions;
+ private static $movie_extensions;
+ private static $photo_types;
+ private static $movie_types;
+ private static $blacklist = array("php", "php3", "php4", "php5", "phtml", "phtm", "shtml", "shtm",
+ "pl", "cgi", "asp", "sh", "py", "c", "js");
+
+ /**
+ * Create a default list of allowed photo MIME types paired with their extensions and then let
+ * modules modify it. This is an ordered map, mapping extensions to their MIME types.
+ * Extensions cannot be duplicated, but MIMEs can (e.g. jpeg and jpg both map to image/jpeg).
+ *
+ * @param string $extension (opt.) - return MIME of extension; if not given, return complete array
+ */
+ static function get_photo_types_by_extension($extension=null) {
+ if (empty(self::$photo_types_by_extension)) {
+ $types_by_extension_wrapper = new stdClass();
+ $types_by_extension_wrapper->types_by_extension = array(
+ "jpg" => "image/jpeg", "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png");
+ module::event("photo_types_by_extension", $types_by_extension_wrapper);
+ foreach (self::$blacklist as $key) {
+ unset($types_by_extension_wrapper->types_by_extension[$key]);
+ }
+ self::$photo_types_by_extension = $types_by_extension_wrapper->types_by_extension;
+ }
+ if ($extension) {
+ // return matching MIME type
+ $extension = strtolower($extension);
+ if (isset(self::$photo_types_by_extension[$extension])) {
+ return self::$photo_types_by_extension[$extension];
+ } else {
+ return null;
+ }
+ } else {
+ // return complete array
+ return self::$photo_types_by_extension;
+ }
+ }
+
+ /**
+ * Create a default list of allowed movie MIME types paired with their extensions and then let
+ * modules modify it. This is an ordered map, mapping extensions to their MIME types.
+ * Extensions cannot be duplicated, but MIMEs can (e.g. jpeg and jpg both map to image/jpeg).
+ *
+ * @param string $extension (opt.) - return MIME of extension; if not given, return complete array
+ */
+ static function get_movie_types_by_extension($extension=null) {
+ if (empty(self::$movie_types_by_extension)) {
+ $types_by_extension_wrapper = new stdClass();
+ $types_by_extension_wrapper->types_by_extension = array(
+ "flv" => "video/x-flv", "mp4" => "video/mp4", "m4v" => "video/x-m4v");
+ module::event("movie_types_by_extension", $types_by_extension_wrapper);
+ foreach (self::$blacklist as $key) {
+ unset($types_by_extension_wrapper->types_by_extension[$key]);
+ }
+ self::$movie_types_by_extension = $types_by_extension_wrapper->types_by_extension;
+ }
+ if ($extension) {
+ // return matching MIME type
+ $extension = strtolower($extension);
+ if (isset(self::$movie_types_by_extension[$extension])) {
+ return self::$movie_types_by_extension[$extension];
+ } else {
+ return null;
+ }
+ } else {
+ // return complete array
+ return self::$movie_types_by_extension;
+ }
+ }
+
+ /**
+ * Create a merged list of all allowed photo and movie MIME types paired with their extensions.
+ *
+ * @param string $extension (opt.) - return MIME of extension; if not given, return complete array
+ */
+ static function get_types_by_extension($extension=null) {
+ $types_by_extension = legal_file::get_photo_types_by_extension();
+ if (movie::allow_uploads()) {
+ $types_by_extension = array_merge($types_by_extension,
+ legal_file::get_movie_types_by_extension());
+ }
+ if ($extension) {
+ // return matching MIME type
+ $extension = strtolower($extension);
+ if (isset($types_by_extension[$extension])) {
+ return $types_by_extension[$extension];
+ } else {
+ return null;
+ }
+ } else {
+ // return complete array
+ return $types_by_extension;
+ }
+ }
+
+ /**
+ * Create a default list of allowed photo extensions and then let modules modify it.
+ *
+ * @param string $extension (opt.) - return true if allowed; if not given, return complete array
+ */
+ static function get_photo_extensions($extension=null) {
+ if (empty(self::$photo_extensions)) {
+ $extensions_wrapper = new stdClass();
+ $extensions_wrapper->extensions = array_keys(legal_file::get_photo_types_by_extension());
+ module::event("legal_photo_extensions", $extensions_wrapper);
+ self::$photo_extensions = array_diff($extensions_wrapper->extensions, self::$blacklist);
+ }
+ if ($extension) {
+ // return true if in array, false if not
+ return in_array(strtolower($extension), self::$photo_extensions);
+ } else {
+ // return complete array
+ return self::$photo_extensions;
+ }
+ }
+
+ /**
+ * Create a default list of allowed movie extensions and then let modules modify it.
+ *
+ * @param string $extension (opt.) - return true if allowed; if not given, return complete array
+ */
+ static function get_movie_extensions($extension=null) {
+ if (empty(self::$movie_extensions)) {
+ $extensions_wrapper = new stdClass();
+ $extensions_wrapper->extensions = array_keys(legal_file::get_movie_types_by_extension());
+ module::event("legal_movie_extensions", $extensions_wrapper);
+ self::$movie_extensions = array_diff($extensions_wrapper->extensions, self::$blacklist);
+ }
+ if ($extension) {
+ // return true if in array, false if not
+ return in_array(strtolower($extension), self::$movie_extensions);
+ } else {
+ // return complete array
+ return self::$movie_extensions;
+ }
+ }
+
+ /**
+ * Create a merged list of all allowed photo and movie extensions.
+ *
+ * @param string $extension (opt.) - return true if allowed; if not given, return complete array
+ */
+ static function get_extensions($extension=null) {
+ $extensions = legal_file::get_photo_extensions();
+ if (movie::allow_uploads()) {
+ $extensions = array_merge($extensions, legal_file::get_movie_extensions());
+ }
+ if ($extension) {
+ // return true if in array, false if not
+ return in_array(strtolower($extension), $extensions);
+ } else {
+ // return complete array
+ return $extensions;
+ }
+ }
+
+ /**
+ * Create a merged list of all photo and movie filename filters,
+ * (e.g. "*.gif"), based on allowed extensions.
+ */
+ static function get_filters() {
+ $filters = array();
+ foreach (legal_file::get_extensions() as $extension) {
+ array_push($filters, "*." . $extension, "*." . strtoupper($extension));
+ }
+ return $filters;
+ }
+
+ /**
+ * Create a default list of allowed photo MIME types and then let modules modify it.
+ * Can be used to add legal alternatives for default MIME types.
+ * (e.g. flv maps to video/x-flv by default, but video/flv is still legal).
+ */
+ static function get_photo_types() {
+ if (empty(self::$photo_types)) {
+ $types_wrapper = new stdClass();
+ // Need array_unique since types_by_extension can be many-to-one (e.g. jpeg and jpg).
+ $types_wrapper->types = array_unique(array_values(legal_file::get_photo_types_by_extension()));
+ module::event("legal_photo_types", $types_wrapper);
+ self::$photo_types = $types_wrapper->types;
+ }
+ return self::$photo_types;
+ }
+
+ /**
+ * Create a default list of allowed movie MIME types and then let modules modify it.
+ * Can be used to add legal alternatives for default MIME types.
+ * (e.g. flv maps to video/x-flv by default, but video/flv is still legal).
+ */
+ static function get_movie_types() {
+ if (empty(self::$movie_types)) {
+ $types_wrapper = new stdClass();
+ // Need array_unique since types_by_extension can be many-to-one (e.g. jpeg and jpg).
+ $types_wrapper->types = array_unique(array_values(legal_file::get_movie_types_by_extension()));
+ $types_wrapper->types[] = "video/flv";
+ module::event("legal_movie_types", $types_wrapper);
+ self::$movie_types = $types_wrapper->types;
+ }
+ return self::$movie_types;
+ }
+
+ /**
+ * Change the extension of a filename. If the original filename has no
+ * extension, add the new one to the end.
+ */
+ static function change_extension($filename, $new_ext) {
+ $filename_no_ext = preg_replace("/\.[^\.\/]*?$/", "", $filename);
+ return "{$filename_no_ext}.{$new_ext}";
+ }
+
+ /**
+ * Reduce the given file to having a single extension.
+ */
+ static function smash_extensions($filename) {
+ if (!$filename) {
+ // It's harmless, so return it before it causes issues with pathinfo.
+ return $filename;
+ }
+ $parts = pathinfo($filename);
+ $result = "";
+ if ($parts["dirname"] != ".") {
+ $result .= $parts["dirname"] . "/";
+ }
+ $parts["filename"] = str_replace(".", "_", $parts["filename"]);
+ $parts["filename"] = preg_replace("/[_]+/", "_", $parts["filename"]);
+ $parts["filename"] = trim($parts["filename"], "_");
+ $result .= isset($parts["extension"]) ? "{$parts['filename']}.{$parts['extension']}" : $parts["filename"];
+ return $result;
+ }
+
+ /**
+ * Sanitize a filename for a given type (given as "photo" or "movie") and a target file format
+ * (given as an extension). This returns a completely legal and valid filename,
+ * or throws an exception if the type or extension given is invalid or illegal. It tries to
+ * maintain the filename's original extension even if it's not identical to the given extension
+ * (e.g. don't change "JPG" or "jpeg" to "jpg").
+ *
+ * Note: it is not okay if the extension given is legal but does not match the type (e.g. if
+ * extension is "mp4" and type is "photo", it will throw an exception)
+ *
+ * @param string $filename (with no directory)
+ * @param string $extension (can be uppercase or lowercase)
+ * @param string $type (as "photo" or "movie")
+ * @return string sanitized filename (or null if bad extension argument)
+ */
+ static function sanitize_filename($filename, $extension, $type) {
+ // Check if the type is valid - if so, get the mime types of the
+ // original and target extensions; if not, throw an exception.
+ $original_extension = pathinfo($filename, PATHINFO_EXTENSION);
+ switch ($type) {
+ case "photo":
+ $mime_type = legal_file::get_photo_types_by_extension($extension);
+ $original_mime_type = legal_file::get_photo_types_by_extension($original_extension);
+ break;
+ case "movie":
+ $mime_type = legal_file::get_movie_types_by_extension($extension);
+ $original_mime_type = legal_file::get_movie_types_by_extension($original_extension);
+ break;
+ default:
+ throw new Exception("@todo INVALID_TYPE");
+ }
+
+ // Check if the target extension is blank or invalid - if so, throw an exception.
+ if (!$extension || !$mime_type) {
+ throw new Exception("@todo ILLEGAL_EXTENSION");
+ }
+
+ // Check if the mime types of the original and target extensions match - if not, fix it.
+ if (!$original_extension || ($mime_type != $original_mime_type)) {
+ $filename = legal_file::change_extension($filename, $extension);
+ }
+
+ // It should be a filename without a directory - remove all slashes (and backslashes).
+ $filename = str_replace("/", "_", $filename);
+ $filename = str_replace("\\", "_", $filename);
+
+ // Remove extra dots from the filename. This will also remove extraneous underscores.
+ $filename = legal_file::smash_extensions($filename);
+
+ // It's possible that the filename has no base (e.g. ".jpg") - if so, give it a generic one.
+ if (empty($filename) || (substr($filename, 0, 1) == ".")) {
+ $filename = $type . $filename; // e.g. "photo.jpg" or "movie.mp4"
+ }
+
+ return $filename;
+ }
+}
diff --git a/modules/gallery/helpers/locales.php b/modules/gallery/helpers/locales.php
new file mode 100644
index 0000000..8ca25c3
--- /dev/null
+++ b/modules/gallery/helpers/locales.php
@@ -0,0 +1,264 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 locales_Core {
+ private static $locales;
+ private static $language_subtag_to_locale;
+
+ /**
+ * 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("gallery", "default_locale");
+ $codes = explode("|", module::get_var("gallery", "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("gallery", "default_locale");
+ $locales = in_array($default, $locales)
+ ? $locales
+ : array_merge($locales, array($default));
+
+ module::set_var("gallery", "installed_locales", join("|", $locales));
+
+ // Clear the cache
+ self::$locales = null;
+ }
+
+ // @todo Might want to add a localizable language name as well.
+ // ref: http://cldr.unicode.org/
+ // ref: http://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code
+ // ref: http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/likely_subtags.html
+ private static function _init_language_data() {
+ $l["af_ZA"] = "Afrikaans"; // Afrikaans
+ $l["ar_SA"] = "العربية"; // Arabic
+ $l["be_BY"] = "Беларускі"; // Belarusian
+ $l["bg_BG"] = "български"; // Bulgarian
+ $l["bn_BD"] = "বাংলা"; // Bengali
+ $l["ca_ES"] = "Catalan"; // Catalan
+ $l["cs_CZ"] = "čeština"; // Czech
+ $l["da_DK"] = "Dansk"; // Danish
+ $l["de_DE"] = "Deutsch"; // German
+ $l["el_GR"] = "Greek"; // Greek
+ $l["en_GB"] = "English (UK)"; // English (UK)
+ $l["en_US"] = "English (US)"; // English (US)
+ $l["es_AR"] = "Español (AR)"; // Spanish (AR)
+ $l["es_ES"] = "Español"; // Spanish (ES)
+ $l["es_MX"] = "Español (MX)"; // Spanish (MX)
+ $l["et_EE"] = "Eesti"; // Estonian
+ $l["eu_ES"] = "Euskara"; // Basque
+ $l["fa_IR"] = "فارس"; // Farsi
+ $l["fi_FI"] = "Suomi"; // Finnish
+ $l["fo_FO"] = "Føroyskt"; // Faroese
+ $l["fr_FR"] = "Français"; // French
+ $l["ga_IE"] = "Gaeilge"; // Irish
+ $l["he_IL"] = "עברית"; // Hebrew
+ $l["hr_HR"] = "hr̀vātskī"; // Croatian
+ $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["ms_MY"] = "Bahasa Melayu"; // Malay
+ $l["mk_MK"] = "Македонски јазик"; // Macedonian
+ $l["nl_NL"] = "Nederlands"; // Dutch
+ $l["no_NO"] = "Norsk bokmål"; // Norwegian
+ $l["pl_PL"] = "Polski"; // Polish
+ $l["pt_BR"] = "Português do Brasil"; // Portuguese (BR)
+ $l["pt_PT"] = "Português ibérico"; // Portuguese (PT)
+ $l["ro_RO"] = "Română"; // Romanian
+ $l["ru_RU"] = "Русский"; // Russian
+ $l["sk_SK"] = "Slovenčina"; // Slovak
+ $l["sl_SI"] = "Slovenščina"; // Slovenian
+ $l["sr_CS"] = "Srpski"; // Serbian
+ $l["sv_SE"] = "Svenska"; // Swedish
+ $l["th_TH"] = "ภาษาไทย"; // Thai
+ $l["tn_ZA"] = "Setswana"; // Setswana
+ $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;
+
+ // Language subtag to (default) locale mapping
+ foreach ($l as $locale => $name) {
+ list ($language) = explode("_", $locale . "_");
+ // The first one mentioned is the default
+ if (!isset($d[$language])) {
+ $d[$language] = $locale;
+ }
+ }
+ self::$language_subtag_to_locale = $d;
+ }
+
+ static function display_name($locale=null) {
+ if (empty(self::$locales)) {
+ self::_init_language_data();
+ }
+ $locale or $locale = Gallery_I18n::instance()->locale();
+
+ return self::$locales[$locale];
+ }
+
+ static function is_rtl($locale=null) {
+ return Gallery_I18n::instance()->is_rtl($locale);
+ }
+
+ /**
+ * Returns the best match comparing the HTTP accept-language header
+ * with the installed locales.
+ * @todo replace this with request::accepts_language() when we upgrade to Kohana 2.4
+ */
+ static function locale_from_http_request() {
+ $http_accept_language = Input::instance()->server("HTTP_ACCEPT_LANGUAGE");
+ if ($http_accept_language) {
+ // Parse the HTTP header and build a preference list
+ // Example value: "de,en-us;q=0.7,en-uk,fr-fr;q=0.2"
+ $locale_preferences = array();
+ foreach (explode(",", $http_accept_language) as $code) {
+ list ($requested_locale, $qvalue) = explode(";", $code . ";");
+ $requested_locale = trim($requested_locale);
+ $qvalue = trim($qvalue);
+ if (preg_match("/^([a-z]{2,3})(?:[_-]([a-zA-Z]{2}))?/", $requested_locale, $matches)) {
+ $requested_locale = strtolower($matches[1]);
+ if (!empty($matches[2])) {
+ $requested_locale .= "_" . strtoupper($matches[2]);
+ }
+ $requested_locale = trim(str_replace("-", "_", $requested_locale));
+ if (!strlen($qvalue)) {
+ // If not specified, default to 1.
+ $qvalue = 1;
+ } else {
+ // qvalue is expected to be something like "q=0.7"
+ list ($ignored, $qvalue) = explode("=", $qvalue . "==");
+ $qvalue = floatval($qvalue);
+ }
+ // Group by language to boost inexact same-language matches
+ list ($language) = explode("_", $requested_locale . "_");
+ if (!isset($locale_preferences[$language])) {
+ $locale_preferences[$language] = array();
+ }
+ $locale_preferences[$language][$requested_locale] = $qvalue;
+ }
+ }
+
+ // Compare and score requested locales with installed ones
+ $scored_locales = array();
+ foreach ($locale_preferences as $language => $requested_locales) {
+ // Inexact match adjustment (same language, different region)
+ $fallback_adjustment_factor = 0.95;
+ if (count($requested_locales) > 1) {
+ // Sort by qvalue, descending
+ $qvalues = array_values($requested_locales);
+ rsort($qvalues);
+ // Ensure inexact match scores worse than 2nd preference in same language.
+ $fallback_adjustment_factor *= $qvalues[1];
+ }
+ foreach ($requested_locales as $requested_locale => $qvalue) {
+ list ($matched_locale, $match_score) =
+ self::_locale_match_score($requested_locale, $qvalue, $fallback_adjustment_factor);
+ if ($matched_locale &&
+ (!isset($scored_locales[$matched_locale]) ||
+ $match_score > $scored_locales[$matched_locale])) {
+ $scored_locales[$matched_locale] = $match_score;
+ }
+ }
+ }
+
+ arsort($scored_locales);
+
+ list ($locale) = each($scored_locales);
+ return $locale;
+ }
+
+ return null;
+ }
+
+ private static function _locale_match_score($requested_locale, $qvalue, $adjustment_factor) {
+ $installed = locales::installed();
+ if (isset($installed[$requested_locale])) {
+ return array($requested_locale, $qvalue);
+ }
+ list ($language) = explode("_", $requested_locale . "_");
+ if (isset(self::$language_subtag_to_locale[$language]) &&
+ isset($installed[self::$language_subtag_to_locale[$language]])) {
+ $score = $adjustment_factor * $qvalue;
+ return array(self::$language_subtag_to_locale[$language], $score);
+ }
+ return array(null, 0);
+ }
+
+ static function set_request_locale() {
+ // 1. Check the session specific preference (cookie)
+ $locale = locales::cookie_locale();
+ // 2. Check the user's preference
+ if (!$locale) {
+ $locale = identity::active_user()->locale;
+ }
+ // 3. Check the browser's / OS' preference
+ if (!$locale) {
+ $locale = locales::locale_from_http_request();
+ }
+ // If we have any preference, override the site's default locale
+ if ($locale) {
+ Gallery_I18n::instance()->locale($locale);
+ }
+ }
+
+ static function cookie_locale() {
+ // Can't use Input framework for client side cookies since
+ // they're not signed.
+ $cookie_data = isset($_COOKIE["g_locale"]) ? $_COOKIE["g_locale"] : null;
+ $locale = null;
+ if ($cookie_data) {
+ if (preg_match("/^([a-z]{2,3}(?:_[A-Z]{2})?)$/", trim($cookie_data), $matches)) {
+ $requested_locale = $matches[1];
+ $installed_locales = locales::installed();
+ if (isset($installed_locales[$requested_locale])) {
+ $locale = $requested_locale;
+ }
+ }
+ }
+ return $locale;
+ }
+}
diff --git a/modules/gallery/helpers/log.php b/modules/gallery/helpers/log.php
new file mode 100644
index 0000000..cd554b5
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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, log::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, log::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, log::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, log::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 = identity::active_user()->id;
+ $log->save();
+ }
+
+
+ /**
+ * Convert a message severity to a CSS class
+ * @param integer $severity
+ * @return string
+ */
+ static function severity_class($severity) {
+ switch($severity) {
+ case log::SUCCESS:
+ return "g-success";
+
+ case log::INFO:
+ return "g-info";
+
+ case log::WARNING:
+ return "g-warning";
+
+ case log::ERROR:
+ return "g-error";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/message.php b/modules/gallery/helpers/message.php
new file mode 100644
index 0000000..d109995
--- /dev/null
+++ b/modules/gallery/helpers/message.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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, message::SUCCESS);
+ }
+
+ /**
+ * Report an informational event.
+ * @param string $msg a detailed message
+ */
+ static function info($msg) {
+ self::_add($msg, message::INFO);
+ }
+
+ /**
+ * Report that something went wrong, not fatal, but worth investigation.
+ * @param string $msg a detailed message
+ */
+ static function warning($msg) {
+ self::_add($msg, message::WARNING);
+ }
+
+ /**
+ * Report that something went wrong that should be fixed.
+ * @param string $msg a detailed message
+ */
+ static function error($msg) {
+ self::_add($msg, message::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) {
+ $msg[0] = str_replace("__CSRF__", access::csrf_token(), $msg[0]);
+ $buf[] = "<li class=\"" . message::severity_class($msg[1]) . "\">$msg[0]</li>";
+ }
+ if ($buf) {
+ return "<ul id=\"g-action-status\" class=\"g-message-block\">" . implode("", $buf) . "</ul>";
+ }
+ }
+
+ /**
+ * Convert a message severity to a CSS class
+ * @param integer $severity
+ * @return string
+ */
+ static function severity_class($severity) {
+ switch($severity) {
+ case message::SUCCESS:
+ return "g-success";
+
+ case message::INFO:
+ return "g-info";
+
+ case message::WARNING:
+ return "g-warning";
+
+ case message::ERROR:
+ return "g-error";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php
new file mode 100644
index 0000000..7cff68d
--- /dev/null
+++ b/modules/gallery/helpers/model_cache.php
@@ -0,0 +1,42 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 = array();
+
+ 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() {
+ self::$cache = array();
+ }
+
+ 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 0000000..1b6c8d1
--- /dev/null
+++ b/modules/gallery/helpers/module.php
@@ -0,0 +1,594 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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;
+ public static $available = array();
+
+ /**
+ * Set the version of the corresponding Module_Model
+ * @param string $module_name
+ * @param integer $version
+ */
+ static function set_version($module_name, $version) {
+ $module = module::get($module_name);
+ if (!$module->loaded()) {
+ $module->name = $module_name;
+ $module->active = $module_name == "gallery"; // only gallery is active by default
+ }
+ $module->version = $version;
+ $module->save();
+ Kohana_Log::add("debug", "$module_name: version is now $version");
+ }
+
+ /**
+ * Load the corresponding Module_Model
+ * @param string $module_name
+ */
+ static function get($module_name) {
+ if (empty(self::$modules[$module_name])) {
+ return ORM::factory("module")->where("name", "=", $module_name)->find();
+ }
+ return self::$modules[$module_name];
+ }
+
+ /**
+ * Get the information about a module
+ * @returns ArrayObject containing the module information from the module.info file or false if
+ * not found
+ */
+ static function info($module_name) {
+ $module_list = module::available();
+ return isset($module_list->$module_name) ? $module_list->$module_name : false;
+ }
+
+ /**
+ * 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() {
+ if (empty(self::$available)) {
+ $modules = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
+ foreach (glob(MODPATH . "*/module.info") as $file) {
+ $module_name = basename(dirname($file));
+ $modules->$module_name =
+ new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS);
+ $m =& $modules->$module_name;
+ $m->installed = module::is_installed($module_name);
+ $m->active = module::is_active($module_name);
+ $m->code_version = $m->version;
+ $m->version = module::get_version($module_name);
+ $m->locked = false;
+
+ if ($m->active && $m->version != $m->code_version) {
+ site_status::warning(t("Some of your modules are out of date. <a href=\"%upgrader_url\">Upgrade now!</a>", array("upgrader_url" => url::abs_site("upgrader"))), "upgrade_now");
+ }
+ }
+
+ // Lock certain modules
+ $modules->gallery->locked = true;
+ $identity_module = module::get_var("gallery", "identity_provider", "user");
+ $modules->$identity_module->locked = true;
+
+ $modules->uasort(array("module", "module_comparator"));
+ self::$available = $modules;
+ }
+
+ return self::$available;
+ }
+
+ /**
+ * Natural name sort comparator
+ */
+ static function module_comparator($a, $b) {
+ return strnatcasecmp($a->name, $b->name);
+ }
+
+ /**
+ * Return a list of all the active modules in no particular order.
+ */
+ static function active() {
+ return self::$active;
+ }
+
+ /**
+ * Check that the module can be activated. (i.e. all the prerequistes exist)
+ * @param string $module_name
+ * @return array an array of warning or error messages to be displayed
+ */
+ static function can_activate($module_name) {
+ module::_add_to_path($module_name);
+ $messages = array();
+
+ $installer_class = "{$module_name}_installer";
+ if (class_exists($installer_class) && method_exists($installer_class, "can_activate")) {
+ $messages = call_user_func(array($installer_class, "can_activate"));
+ }
+
+ // Remove it from the active path
+ module::_remove_from_path($module_name);
+ return $messages;
+ }
+
+ /**
+ * Allow modules to indicate the impact of deactivating the specified module
+ * @param string $module_name
+ * @return array an array of warning or error messages to be displayed
+ */
+ static function can_deactivate($module_name) {
+ $data = (object)array("module" => $module_name, "messages" => array());
+
+ module::event("pre_deactivate", $data);
+
+ return $data->messages;
+ }
+
+ /**
+ * Install a module. This will call <module>_installer::install(), which is responsible for
+ * creating database tables, setting module variables 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) {
+ module::_add_to_path($module_name);
+
+ $installer_class = "{$module_name}_installer";
+ if (class_exists($installer_class) && method_exists($installer_class, "install")) {
+ call_user_func_array(array($installer_class, "install"), array());
+ }
+ module::set_version($module_name, module::available()->$module_name->code_version);
+
+ // Set the weight of the new module, which controls the order in which the modules are
+ // loaded. By default, new modules are installed at the end of the priority list. Since the
+ // id field is monotonically increasing, the easiest way to guarantee that is to set the weight
+ // the same as the id. We don't know that until we save it for the first time
+ $module = ORM::factory("module")->where("name", "=", $module_name)->find();
+ if ($module->loaded()) {
+ $module->weight = $module->id;
+ $module->save();
+ }
+ module::load_modules();
+
+ // Now the module is installed but inactive, so don't leave it in the active path
+ module::_remove_from_path($module_name);
+
+ log::success(
+ "module", t("Installed module %module_name", array("module_name" => $module_name)));
+ }
+
+ private static function _add_to_path($module_name) {
+ $config = Kohana_Config::instance();
+ $kohana_modules = $config->get("core.modules");
+ array_unshift($kohana_modules, MODPATH . $module_name);
+ $config->set("core.modules", $kohana_modules);
+ // Rebuild the include path so the module installer can benefit from auto loading
+ Kohana::include_paths(true);
+ }
+
+ private static function _remove_from_path($module_name) {
+ $config = Kohana_Config::instance();
+ $kohana_modules = $config->get("core.modules");
+ if (($key = array_search(MODPATH . $module_name, $kohana_modules)) !== false) {
+ unset($kohana_modules[$key]);
+ $kohana_modules = array_values($kohana_modules); // reindex
+ }
+ $config->set("core.modules", $kohana_modules);
+ Kohana::include_paths(true);
+ }
+
+ /**
+ * Upgrade a module. This will call <module>_installer::upgrade(), which is responsible for
+ * modifying database tables, changing module variables and calling module::set_version().
+ * Note that after upgrading, the module must be activated before it is available for use.
+ * @param string $module_name
+ */
+ static function upgrade($module_name) {
+ $version_before = module::get_version($module_name);
+ $installer_class = "{$module_name}_installer";
+ $available = module::available();
+ if (class_exists($installer_class) && method_exists($installer_class, "upgrade")) {
+ call_user_func_array(array($installer_class, "upgrade"), array($version_before));
+ } else {
+ if (isset($available->$module_name->code_version)) {
+ module::set_version($module_name, $available->$module_name->code_version);
+ } else {
+ throw new Exception("@todo UNKNOWN_MODULE");
+ }
+ }
+ module::load_modules();
+
+ $version_after = module::get_version($module_name);
+ if ($version_before != $version_after) {
+ log::success(
+ "module", t("Upgraded module %module_name from %version_before to %version_after",
+ array("module_name" => $module_name,
+ "version_before" => $version_before,
+ "version_after" => $version_after)));
+ }
+
+ if ($version_after != $available->$module_name->code_version) {
+ throw new Exception("@todo MODULE_FAILED_TO_UPGRADE");
+ }
+ }
+
+ /**
+ * 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) {
+ module::_add_to_path($module_name);
+
+ $installer_class = "{$module_name}_installer";
+ if (class_exists($installer_class) && method_exists($installer_class, "activate")) {
+ call_user_func_array(array($installer_class, "activate"), array());
+ }
+
+ $module = module::get($module_name);
+ if ($module->loaded()) {
+ $module->active = true;
+ $module->save();
+ }
+ module::load_modules();
+
+ graphics::activate_rules($module_name);
+
+ block_manager::activate_blocks($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. Note that the
+ * module remains available in Kohana's cascading file system until the end of the request!
+ * @param string $module_name
+ */
+ static function deactivate($module_name) {
+ $installer_class = "{$module_name}_installer";
+ if (class_exists($installer_class) && method_exists($installer_class, "deactivate")) {
+ call_user_func_array(array($installer_class, "deactivate"), array());
+ }
+
+ $module = module::get($module_name);
+ if ($module->loaded()) {
+ $module->active = false;
+ $module->save();
+ }
+ module::load_modules();
+
+ graphics::deactivate_rules($module_name);
+
+ block_manager::deactivate_blocks($module_name);
+
+ if (module::info($module_name)) {
+ log::success(
+ "module", t("Deactivated module %module_name", array("module_name" => $module_name)));
+ } else {
+ log::success(
+ "module", t("Deactivated missing module %module_name", array("module_name" => $module_name)));
+ }
+ }
+
+ /**
+ * Deactivate modules that are unavailable or missing, yet still active.
+ * This happens when a user deletes a module without deactivating it.
+ */
+ static function deactivate_missing_modules() {
+ foreach (self::$modules as $module_name => $module) {
+ if (module::is_active($module_name) && !module::info($module_name)) {
+ module::deactivate($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 (class_exists($installer_class) && method_exists($installer_class, "uninstall")) {
+ call_user_func(array($installer_class, "uninstall"));
+ }
+
+ graphics::remove_rules($module_name);
+ $module = module::get($module_name);
+ if ($module->loaded()) {
+ $module->delete();
+ }
+ module::load_modules();
+
+ // We could delete the module vars here too, but it's nice to leave them around
+ // in case the module gets reinstalled.
+
+ 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() {
+ self::$modules = array();
+ self::$active = array();
+ $kohana_modules = array();
+
+ // In version 32 we introduced a weight column so we can specify the module order
+ // If we try to use that blindly, we'll break earlier versions before they can even
+ // run the upgrader.
+ $modules = module::get_version("gallery") < 32 ?
+ ORM::factory("module")->find_all():
+ ORM::factory("module")->order_by("weight")->find_all();
+
+ foreach ($modules as $module) {
+ self::$modules[$module->name] = $module;
+ if (!$module->active) {
+ continue;
+ }
+
+ if ($module->name == "gallery") {
+ $gallery = $module;
+ } else {
+ self::$active[] = $module;
+ $kohana_modules[] = MODPATH . $module->name;
+ }
+ }
+ self::$active[] = $gallery; // put gallery last in the module list to match core.modules
+ $config = Kohana_Config::instance();
+ $config->set(
+ "core.modules", array_merge($kohana_modules, $config->get("core.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);
+
+ if (method_exists("gallery_event", $function)) {
+ switch (count($args)) {
+ case 0:
+ gallery_event::$function();
+ break;
+ case 1:
+ gallery_event::$function($args[0]);
+ break;
+ case 2:
+ gallery_event::$function($args[0], $args[1]);
+ break;
+ case 3:
+ gallery_event::$function($args[0], $args[1], $args[2]);
+ break;
+ case 4: // Context menu events have 4 arguments so lets optimize them
+ gallery_event::$function($args[0], $args[1], $args[2], $args[3]);
+ break;
+ default:
+ call_user_func_array(array("gallery_event", $function), $args);
+ }
+ }
+
+ foreach (self::$active as $module) {
+ if ($module->name == "gallery") {
+ continue;
+ }
+ $class = "{$module->name}_event";
+ if (class_exists($class) && method_exists($class, $function)) {
+ call_user_func_array(array($class, $function), $args);
+ }
+ }
+
+ // Give the admin theme a chance to respond, if we're in admin mode.
+ if (theme::$is_admin) {
+ $class = theme::$admin_theme_name . "_event";
+ if (class_exists($class) && method_exists($class, $function)) {
+ call_user_func_array(array($class, $function), $args);
+ }
+ }
+
+ // Give the site theme a chance to respond as well. It gets a chance even in admin mode, as
+ // long as the theme has an admin subdir.
+ $class = theme::$site_theme_name . "_event";
+ if (class_exists($class) && 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 vars so we can load them all at once for performance.
+ if (empty(self::$var_cache)) {
+ self::$var_cache = Cache::instance()->get("var_cache");
+ if (empty(self::$var_cache)) {
+ // Cache doesn't exist, create it now.
+ foreach (db::build()
+ ->select("module_name", "name", "value")
+ ->from("vars")
+ ->order_by("module_name")
+ ->order_by("name")
+ ->execute() as $row) {
+ // Mute the "Creating default object from empty value" warning below
+ @self::$var_cache->{$row->module_name}->{$row->name} = $row->value;
+ }
+ Cache::instance()->set("var_cache", self::$var_cache, array("vars"));
+ }
+ }
+
+ 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();
+
+ Cache::instance()->delete("var_cache");
+ self::$var_cache = null;
+ }
+
+ /**
+ * Increment the value of a variable for this module
+ *
+ * Note: Frequently updating counters is very inefficient because it invalidates the cache value
+ * which has to be rebuilt every time we make a change.
+ *
+ * @todo Get rid of this and find an alternate approach for all callers (currently only Akismet)
+ *
+ * @deprecated
+ * @param string $module_name
+ * @param string $name
+ * @param string $increment (optional, default is 1)
+ */
+ static function incr_var($module_name, $name, $increment=1) {
+ db::build()
+ ->update("vars")
+ ->set("value", db::expr("`value` + $increment"))
+ ->where("module_name", "=", $module_name)
+ ->where("name", "=", $name)
+ ->execute();
+
+ Cache::instance()->delete("var_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) {
+ db::build()
+ ->delete("vars")
+ ->where("module_name", "=", $module_name)
+ ->where("name", "=", $name)
+ ->execute();
+
+ Cache::instance()->delete("var_cache");
+ self::$var_cache = null;
+ }
+
+ /**
+ * Remove all variables for this module.
+ * @param string $module_name
+ */
+ static function clear_all_vars($module_name) {
+ db::build()
+ ->delete("vars")
+ ->where("module_name", "=", $module_name)
+ ->execute();
+
+ Cache::instance()->delete("var_cache");
+ self::$var_cache = null;
+ }
+
+ /**
+ * Return the version of the installed module.
+ * @param string $module_name
+ */
+ static function get_version($module_name) {
+ return module::get($module_name)->version;
+ }
+
+ /**
+ * Check if obsolete modules are active and, if so, return a warning message.
+ * If none are found, return null.
+ */
+ static function get_obsolete_modules_message() {
+ // This is the obsolete modules list. Any active module that's on the list
+ // with version number at or below the one given will be considered obsolete.
+ // It is hard-coded here, and may be updated with future releases of Gallery.
+ $obsolete_modules = array("videos" => 4, "noffmpeg" => 1, "videodimensions" => 1,
+ "digibug" => 2);
+
+ // Before we check the active modules, deactivate any that are missing.
+ module::deactivate_missing_modules();
+
+ $modules_found = array();
+ foreach ($obsolete_modules as $module => $version) {
+ if (module::is_active($module) && (module::get_version($module) <= $version)) {
+ $modules_found[] = $module;
+ }
+ }
+
+ if ($modules_found) {
+ // Need this to be on one super-long line or else the localization scanner may not work.
+ // (ref: http://sourceforge.net/apps/trac/gallery/ticket/1321)
+ return t("Recent upgrades to Gallery have made the following modules obsolete: %modules. We recommend that you <a href=\"%url_mod\">deactivate</a> the module(s). For more information, please see the <a href=\"%url_doc\">documentation page</a>.",
+ array("modules" => implode(", ", $modules_found),
+ "url_mod" => url::site("admin/modules"),
+ "url_doc" => "http://codex.galleryproject.org/Gallery3:User_guide:Obsolete_modules"));
+ }
+
+ return null;
+ }
+}
diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php
new file mode 100644
index 0000000..2f19088
--- /dev/null
+++ b/modules/gallery/helpers/movie.php
@@ -0,0 +1,282 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ private static $allow_uploads;
+
+ static function get_edit_form($movie) {
+ $form = new Forge("movies/update/$movie->id", "", "post", array("id" => "g-edit-movie-form"));
+ $form->hidden("from_id")->value($movie->id);
+ $group = $form->group("edit_item")->label(t("Edit Movie"));
+ $group->input("title")->label(t("Title"))->value($movie->title)
+ ->error_messages("required", t("You must provide a title"))
+ ->error_messages("length", t("Your title is too long"));
+ $group->textarea("description")->label(t("Description"))->value($movie->description);
+ $group->input("name")->label(t("Filename"))->value($movie->name)
+ ->error_messages(
+ "conflict", t("There is already a movie, photo or album with this name"))
+ ->error_messages("no_slashes", t("The movie name can't contain a \"/\""))
+ ->error_messages("no_trailing_period", t("The movie name can't end in \".\""))
+ ->error_messages("illegal_data_file_extension", t("You cannot change the movie file extension"))
+ ->error_messages("required", t("You must provide a movie file name"))
+ ->error_messages("length", t("Your movie file name is too long"));
+ $group->input("slug")->label(t("Internet Address"))->value($movie->slug)
+ ->error_messages(
+ "conflict", t("There is already a movie, photo or album with this internet address"))
+ ->error_messages(
+ "not_url_safe",
+ t("The internet address should contain only letters, numbers, hyphens and underscores"))
+ ->error_messages("required", t("You must provide an internet address"))
+ ->error_messages("length", t("Your internet address is too long"));
+
+ module::event("item_edit_form", $movie, $form);
+
+ $group = $form->group("buttons")->label("");
+ $group->submit("")->value(t("Modify"));
+
+ return $form;
+ }
+
+ /**
+ * Extract a frame from a movie file. Valid movie_options are start_time (in seconds),
+ * input_args (extra ffmpeg input args) and output_args (extra ffmpeg output args). Extra args
+ * are added at the end of the list, so they can override any prior args.
+ *
+ * @param string $input_file
+ * @param string $output_file
+ * @param array $movie_options (optional)
+ */
+ static function extract_frame($input_file, $output_file, $movie_options=null) {
+ $ffmpeg = movie::find_ffmpeg();
+ if (empty($ffmpeg)) {
+ throw new Exception("@todo MISSING_FFMPEG");
+ }
+
+ list($width, $height, $mime_type, $extension, $duration) = movie::get_file_metadata($input_file);
+
+ if (isset($movie_options["start_time"]) && is_numeric($movie_options["start_time"])) {
+ $start_time = max(0, $movie_options["start_time"]); // ensure it's non-negative
+ } else {
+ $start_time = module::get_var("gallery", "movie_extract_frame_time", 3); // use default
+ }
+ // extract frame at start_time, unless movie is too short
+ $start_time_arg = ($duration >= $start_time + 0.1) ?
+ "-ss " . movie::seconds_to_hhmmssdd($start_time) : "";
+
+ $input_args = isset($movie_options["input_args"]) ? $movie_options["input_args"] : "";
+ $output_args = isset($movie_options["output_args"]) ? $movie_options["output_args"] : "";
+
+ $cmd = escapeshellcmd($ffmpeg) . " $input_args -i " . escapeshellarg($input_file) .
+ " -an $start_time_arg -an -r 1 -vframes 1" .
+ " -s {$width}x{$height}" .
+ " -y -f mjpeg $output_args " . escapeshellarg($output_file) . " 2>&1";
+ exec($cmd, $exec_output, $exec_return);
+
+ clearstatcache(); // use $filename parameter when PHP_version is 5.3+
+ if (filesize($output_file) == 0 || $exec_return) {
+ // Maybe the movie needs the "-threads 1" argument added
+ // (see http://sourceforge.net/apps/trac/gallery/ticket/1924)
+ $cmd = escapeshellcmd($ffmpeg) . " -threads 1 $input_args -i " . escapeshellarg($input_file) .
+ " -an $start_time_arg -an -r 1 -vframes 1" .
+ " -s {$width}x{$height}" .
+ " -y -f mjpeg $output_args " . escapeshellarg($output_file) . " 2>&1";
+ exec($cmd, $exec_output, $exec_return);
+
+ clearstatcache();
+ if (filesize($output_file) == 0 || $exec_return) {
+ throw new Exception("@todo FFMPEG_FAILED");
+ }
+ }
+ }
+
+ /**
+ * Return true if movie uploads are allowed, false if not. This is based on the
+ * "movie_allow_uploads" Gallery variable as well as whether or not ffmpeg is found.
+ */
+ static function allow_uploads() {
+ if (empty(self::$allow_uploads)) {
+ // Refresh ffmpeg settings
+ $ffmpeg = movie::find_ffmpeg();
+ switch (module::get_var("gallery", "movie_allow_uploads", "autodetect")) {
+ case "always":
+ self::$allow_uploads = true;
+ break;
+ case "never":
+ self::$allow_uploads = false;
+ break;
+ default:
+ self::$allow_uploads = !empty($ffmpeg);
+ break;
+ }
+ }
+ return self::$allow_uploads;
+ }
+
+ /**
+ * Return the path to the ffmpeg binary if one exists and is executable, or null.
+ */
+ static function find_ffmpeg() {
+ if (!($ffmpeg_path = module::get_var("gallery", "ffmpeg_path")) ||
+ !@is_executable($ffmpeg_path)) {
+ $ffmpeg_path = system::find_binary(
+ "ffmpeg", module::get_var("gallery", "graphics_toolkit_path"));
+ module::set_var("gallery", "ffmpeg_path", $ffmpeg_path);
+ }
+ return $ffmpeg_path;
+ }
+
+ /**
+ * Return version number and build date of ffmpeg if found, empty string(s) if not. When using
+ * static builds that aren't official releases, the version numbers are strange, hence why the
+ * date can be useful.
+ */
+ static function get_ffmpeg_version() {
+ $ffmpeg = movie::find_ffmpeg();
+ if (empty($ffmpeg)) {
+ return array("", "");
+ }
+
+ // Find version using -h argument since -version wasn't available in early versions.
+ // To keep the preg_match searches quick, we'll trim the (otherwise long) result.
+ $cmd = escapeshellcmd($ffmpeg) . " -h 2>&1";
+ $result = substr(`$cmd`, 0, 1000);
+ if (preg_match("/ffmpeg version (\S+)/i", $result, $matches_version)) {
+ // Version number found - see if we can get the build date or copyright year as well.
+ if (preg_match("/built on (\S+\s\S+\s\S+)/i", $result, $matches_build_date)) {
+ return array(trim($matches_version[1], ","), trim($matches_build_date[1], ","));
+ } else if (preg_match("/copyright \S*\s?2000-(\d{4})/i", $result, $matches_copyright_date)) {
+ return array(trim($matches_version[1], ","), $matches_copyright_date[1]);
+ } else {
+ return array(trim($matches_version[1], ","), "");
+ }
+ }
+ return array("", "");
+ }
+
+ /**
+ * Return the width, height, mime_type, extension and duration of the given movie file.
+ * Metadata is first generated using ffmpeg (or set to defaults if it fails),
+ * then can be modified by other modules using movie_get_file_metadata events.
+ *
+ * This function and its use cases are symmetric to those of photo::get_file_metadata.
+ *
+ * @param string $file_path
+ * @return array array($width, $height, $mime_type, $extension, $duration)
+ *
+ * Use cases in detail:
+ * Input is standard movie type (flv/mp4/m4v)
+ * -> return metadata from ffmpeg
+ * Input is *not* standard movie type that is supported by ffmpeg (e.g. avi, mts...)
+ * -> return metadata from ffmpeg
+ * Input is *not* standard movie type that is *not* supported by ffmpeg but is legal
+ * -> return zero width, height, and duration; mime type and extension according to legal_file
+ * Input is illegal, unidentifiable, unreadable, or does not exist
+ * -> throw exception
+ * Note: movie_get_file_metadata events can change any of the above cases (except the last one).
+ */
+ static function get_file_metadata($file_path) {
+ if (!is_readable($file_path)) {
+ throw new Exception("@todo UNREADABLE_FILE");
+ }
+
+ $metadata = new stdClass();
+ $ffmpeg = movie::find_ffmpeg();
+ if (!empty($ffmpeg)) {
+ // ffmpeg found - use it to get width, height, and duration.
+ $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($file_path) . " 2>&1";
+ $result = `$cmd`;
+ if (preg_match("/Stream.*?Video:.*?, (\d+)x(\d+)/", $result, $matches_res)) {
+ if (preg_match("/Stream.*?Video:.*? \[.*?DAR (\d+):(\d+).*?\]/", $result, $matches_dar) &&
+ $matches_dar[1] >= 1 && $matches_dar[2] >= 1) {
+ // DAR is defined - determine width based on height and DAR
+ // (should always be int, but adding round to be sure)
+ $matches_res[1] = round($matches_res[2] * $matches_dar[1] / $matches_dar[2]);
+ }
+ list ($metadata->width, $metadata->height) = array($matches_res[1], $matches_res[2]);
+ } else {
+ list ($metadata->width, $metadata->height) = array(0, 0);
+ }
+
+ if (preg_match("/Duration: (\d+:\d+:\d+\.\d+)/", $result, $matches)) {
+ $metadata->duration = movie::hhmmssdd_to_seconds($matches[1]);
+ } else if (preg_match("/duration.*?:.*?(\d+)/", $result, $matches)) {
+ $metadata->duration = $matches[1];
+ } else {
+ $metadata->duration = 0;
+ }
+ } else {
+ // ffmpeg not found - set width, height, and duration to zero.
+ $metadata->width = 0;
+ $metadata->height = 0;
+ $metadata->duration = 0;
+ }
+
+ $extension = pathinfo($file_path, PATHINFO_EXTENSION);
+ if (!$extension ||
+ (!$metadata->mime_type = legal_file::get_movie_types_by_extension($extension))) {
+ // Extension is empty or illegal.
+ $metadata->extension = null;
+ $metadata->mime_type = null;
+ } else {
+ // Extension is legal (and mime is already set above).
+ $metadata->extension = strtolower($extension);
+ }
+
+ // Run movie_get_file_metadata events which can modify the class.
+ module::event("movie_get_file_metadata", $file_path, $metadata);
+
+ // If the post-events results are invalid, throw an exception. Note that, unlike photos, having
+ // zero width and height isn't considered invalid (as is the case when FFmpeg isn't installed).
+ if (!$metadata->mime_type || !$metadata->extension ||
+ ($metadata->mime_type != legal_file::get_movie_types_by_extension($metadata->extension))) {
+ throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE");
+ }
+
+ return array($metadata->width, $metadata->height, $metadata->mime_type,
+ $metadata->extension, $metadata->duration);
+ }
+
+ /**
+ * Return the time/duration formatted in hh:mm:ss.dd from a number of seconds.
+ * Useful for inputs to ffmpeg.
+ *
+ * Note that this is similar to date("H:i:s", mktime(0,0,$seconds,0,0,0,0)), but unlike this
+ * approach avoids potential issues with time zone and DST mismatch and/or using deprecated
+ * features (the last argument of mkdate above, which disables DST, is deprecated as of PHP 5.3).
+ */
+ static function seconds_to_hhmmssdd($seconds) {
+ return sprintf("%02d:%02d:%05.2f", floor($seconds / 3600), floor(($seconds % 3600) / 60),
+ floor(100 * $seconds % 6000) / 100);
+ }
+
+ /**
+ * Return the number of seconds from a time/duration formatted in hh:mm:ss.dd.
+ * Useful for outputs from ffmpeg.
+ */
+ static function hhmmssdd_to_seconds($hhmmssdd) {
+ preg_match("/(\d+):(\d+):(\d+\.\d+)/", $hhmmssdd, $matches);
+ return 3600 * $matches[1] + 60 * $matches[2] + $matches[3];
+ }
+}
diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php
new file mode 100644
index 0000000..004cc7c
--- /dev/null
+++ b/modules/gallery/helpers/photo.php
@@ -0,0 +1,145 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ static function get_edit_form($photo) {
+ $form = new Forge("photos/update/$photo->id", "", "post", array("id" => "g-edit-photo-form"));
+ $form->hidden("from_id")->value($photo->id);
+ $group = $form->group("edit_item")->label(t("Edit Photo"));
+ $group->input("title")->label(t("Title"))->value($photo->title)
+ ->error_messages("required", t("You must provide a title"))
+ ->error_messages("length", t("Your title is too long"));
+ $group->textarea("description")->label(t("Description"))->value($photo->description);
+ $group->input("name")->label(t("Filename"))->value($photo->name)
+ ->error_messages("conflict", t("There is already a movie, photo or album with this name"))
+ ->error_messages("no_slashes", t("The photo name can't contain a \"/\""))
+ ->error_messages("no_trailing_period", t("The photo name can't end in \".\""))
+ ->error_messages("illegal_data_file_extension", t("You cannot change the photo file extension"))
+ ->error_messages("required", t("You must provide a photo file name"))
+ ->error_messages("length", t("Your photo file name is too long"));
+ $group->input("slug")->label(t("Internet Address"))->value($photo->slug)
+ ->error_messages(
+ "conflict", t("There is already a movie, photo or album with this internet address"))
+ ->error_messages(
+ "not_url_safe",
+ t("The internet address should contain only letters, numbers, hyphens and underscores"))
+ ->error_messages("required", t("You must provide an internet address"))
+ ->error_messages("length", t("Your internet address is too long"));
+
+ module::event("item_edit_form", $photo, $form);
+
+ $group = $form->group("buttons")->label("");
+ $group->submit("")->value(t("Modify"));
+ 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);
+ }
+
+ /**
+ * Return the width, height, mime_type and extension of the given image file.
+ * Metadata is first generated using getimagesize (or the legal_file mapping if it fails),
+ * then can be modified by other modules using photo_get_file_metadata events.
+ *
+ * This function and its use cases are symmetric to those of photo::get_file_metadata.
+ *
+ * @param string $file_path
+ * @return array array($width, $height, $mime_type, $extension)
+ *
+ * Use cases in detail:
+ * Input is standard photo type (jpg/png/gif)
+ * -> return metadata from getimagesize()
+ * Input is *not* standard photo type that is supported by getimagesize (e.g. tif, bmp...)
+ * -> return metadata from getimagesize()
+ * Input is *not* standard photo type that is *not* supported by getimagesize but is legal
+ * -> return metadata if found by photo_get_file_metadata events
+ * Input is illegal, unidentifiable, unreadable, or does not exist
+ * -> throw exception
+ * Note: photo_get_file_metadata events can change any of the above cases (except the last one).
+ */
+ static function get_file_metadata($file_path) {
+ if (!is_readable($file_path)) {
+ throw new Exception("@todo UNREADABLE_FILE");
+ }
+
+ $metadata = new stdClass();
+ if ($image_info = getimagesize($file_path)) {
+ // getimagesize worked - use its results.
+ $metadata->width = $image_info[0];
+ $metadata->height = $image_info[1];
+ $metadata->mime_type = $image_info["mime"];
+ $metadata->extension = image_type_to_extension($image_info[2], false);
+ // We prefer jpg instead of jpeg (which is returned by image_type_to_extension).
+ if ($metadata->extension == "jpeg") {
+ $metadata->extension = "jpg";
+ }
+ } else {
+ // getimagesize failed - try to use legal_file mapping instead.
+ $extension = pathinfo($file_path, PATHINFO_EXTENSION);
+ if (!$extension ||
+ (!$metadata->mime_type = legal_file::get_photo_types_by_extension($extension))) {
+ // Extension is empty or illegal.
+ $metadata->extension = null;
+ $metadata->mime_type = null;
+ } else {
+ // Extension is legal (and mime is already set above).
+ $metadata->extension = strtolower($extension);
+ }
+ $metadata->width = 0;
+ $metadata->height = 0;
+ }
+
+ // Run photo_get_file_metadata events which can modify the class.
+ module::event("photo_get_file_metadata", $file_path, $metadata);
+
+ // If the post-events results are invalid, throw an exception.
+ if (!$metadata->width || !$metadata->height || !$metadata->mime_type || !$metadata->extension ||
+ ($metadata->mime_type != legal_file::get_photo_types_by_extension($metadata->extension))) {
+ throw new Exception("@todo ILLEGAL_OR_UNINDENTIFIABLE_FILE");
+ }
+
+ return array($metadata->width, $metadata->height, $metadata->mime_type, $metadata->extension);
+ }
+}
diff --git a/modules/gallery/helpers/random.php b/modules/gallery/helpers/random.php
new file mode 100644
index 0000000..d40bfb5
--- /dev/null
+++ b/modules/gallery/helpers/random.php
@@ -0,0 +1,48 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 random_Core {
+ /**
+ * Return a random 32 byte hash value.
+ * @param string extra entropy data
+ */
+ static function hash($length=32) {
+ require_once(MODPATH . "gallery/vendor/joomla/crypt.php");
+ return md5(JCrypt::genRandomBytes($length));
+ }
+
+ /**
+ * Return a random floating point number between 0 and 1
+ */
+ static function percent() {
+ return ((float)mt_rand()) / (float)mt_getrandmax();
+ }
+
+ /**
+ * Return a random number between $min and $max. If $min and $max are not specified,
+ * return a random number between 0 and mt_getrandmax()
+ */
+ static function int($min=null, $max=null) {
+ if ($min || $max) {
+ return mt_rand($min, $max);
+ }
+ return mt_rand();
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/site_status.php b/modules/gallery/helpers/site_status.php
new file mode 100644
index 0000000..e1a8163
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 (!identity::active_user()->admin) {
+ return;
+ }
+ $buf = array();
+ foreach (ORM::factory("message")->find_all() as $msg) {
+ $value = str_replace("__CSRF__", access::csrf_token(), $msg->value);
+ $buf[] = "<li class=\"" . site_status::severity_class($msg->severity) . "\">$value</li>";
+ }
+
+ if ($buf) {
+ return "<ul id=\"g-site-status\">" . implode("", $buf) . "</ul>";
+ }
+ }
+
+ /**
+ * Convert a message severity to a CSS class
+ * @param integer $severity
+ * @return string
+ */
+ static function severity_class($severity) {
+ switch($severity) {
+ case site_status::SUCCESS:
+ return "g-success";
+
+ case site_status::INFO:
+ return "g-info";
+
+ case site_status::WARNING:
+ return "g-warning";
+
+ case site_status::ERROR:
+ return "g-error";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/system.php b/modules/gallery/helpers/system.php
new file mode 100644
index 0000000..f0879d6
--- /dev/null
+++ b/modules/gallery/helpers/system.php
@@ -0,0 +1,113 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 system_Core {
+ private static $files_marked_for_deletion = array();
+
+ /**
+ * Return the path to an executable version of the named binary, or null.
+ * The paths are traversed in the following order:
+ * 1. $priority_path (if specified)
+ * 2. Gallery's own bin directory (DOCROOT . "bin")
+ * 3. PATH environment variable
+ * 4. extra_binary_paths Gallery variable (if specified)
+ * In addition, if the file is found inside Gallery's bin directory but
+ * it's not executable, we try to change its permissions to 0755.
+ *
+ * @param string $binary
+ * @param string $priority_path (optional)
+ * @return string path to binary if found; null if not found
+ */
+ static function find_binary($binary, $priority_path=null) {
+ $bin_path = DOCROOT . "bin";
+
+ if ($priority_path) {
+ $paths = array($priority_path, $bin_path);
+ } else {
+ $paths = array($bin_path);
+ }
+ $paths = array_merge($paths,
+ explode(":", getenv("PATH")),
+ explode(":", module::get_var("gallery", "extra_binary_paths")));
+
+ foreach ($paths as $path) {
+ $path = rtrim($path, "/");
+ $candidate = "$path/$binary";
+ // @suppress errors below to avoid open_basedir issues
+ if (@file_exists($candidate)) {
+ if (!@is_executable($candidate) &&
+ (substr_compare($bin_path, $candidate, 0, strlen($bin_path)) == 0)) {
+ // Binary isn't executable but is in Gallery's bin directory - try fixing permissions.
+ @chmod($candidate, 0755);
+ }
+ if (@is_executable($candidate)) {
+ return $candidate;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create a file with a unique file name.
+ * This helper is similar to the built-in tempnam.
+ * It allows the caller to specify a prefix and an extension.
+ * It always places the file in TMPPATH.
+ * Unless specified with the $delete_later argument, it will be marked
+ * for deletion at shutdown using system::delete_later.
+ */
+ static function temp_filename($prefix="", $extension="", $delete_later=true) {
+ do {
+ $basename = tempnam(TMPPATH, $prefix);
+ if (!$basename) {
+ return false;
+ }
+ $filename = "$basename.$extension";
+ $success = !file_exists($filename) && @rename($basename, $filename);
+ if (!$success) {
+ @unlink($basename);
+ }
+ } while (!$success);
+
+ if ($delete_later) {
+ system::delete_later($filename);
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Mark a file for deletion at shutdown time. This is useful for temp files, where we can delay
+ * the deletion time until shutdown to keep page load time quick.
+ */
+ static function delete_later($filename) {
+ self::$files_marked_for_deletion[] = $filename;
+ }
+
+ /**
+ * Delete all files marked using system::delete_later. This is called at gallery shutdown.
+ */
+ static function delete_marked_files() {
+ foreach (self::$files_marked_for_deletion as $filename) {
+ // We want to suppress all errors, as it's possible that some of these
+ // files may have been deleted/moved before we got here.
+ @unlink($filename);
+ }
+ }
+}
diff --git a/modules/gallery/helpers/task.php b/modules/gallery/helpers/task.php
new file mode 100644
index 0000000..5638faf
--- /dev/null
+++ b/modules/gallery/helpers/task.php
@@ -0,0 +1,113 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 (class_exists($class_name) && 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 start($task_callback, $context=array()) {
+ $tasks = task::get_definitions();
+ $task = task::create($tasks[$task_callback], array());
+
+ $task->log(t("Task %task_name started (task id %task_id)",
+ array("task_name" => $task->name, "task_id" => $task->id)));
+ return $task;
+ }
+
+ 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 = identity::active_user()->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->log(t("Task %task_name cancelled (task id %task_id)",
+ array("task_name" => $task->name, "task_id" => $task->id)));
+ $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");
+ }
+
+ try {
+ $task->state = "running";
+ call_user_func_array($task->callback, array(&$task));
+ if ($task->done) {
+ $task->log($task->status);
+ }
+ $task->save();
+ } catch (Exception $e) {
+ Kohana_Log::add("error", (string)$e);
+
+ // Ugh. I hate to use instanceof, But this beats catching the exception separately since
+ // we mostly want to treat it the same way as all other exceptions
+ if ($e instanceof ORM_Validation_Exception) {
+ Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1));
+ }
+
+ $task->log((string)$e);
+ $task->state = "error";
+ $task->done = true;
+ $task->status = substr($e->getMessage(), 0, 255);
+ $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 0000000..072f98a
--- /dev/null
+++ b/modules/gallery/helpers/theme.php
@@ -0,0 +1,113 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ public static $admin_theme_name;
+ public static $site_theme_name;
+ public static $is_admin;
+
+ /**
+ * 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() {
+ $input = Input::instance();
+ $path = $input->server("PATH_INFO");
+ if (empty($path)) {
+ $path = "/" . $input->get("kohana_uri");
+ }
+
+ $config = Kohana_Config::instance();
+ $modules = $config->get("core.modules");
+
+ // Normally Router::find_uri() strips off the url suffix for us, but we're working off of the
+ // PATH_INFO here so we need to strip it off manually
+ if ($suffix = Kohana::config("core.url_suffix")) {
+ $path = preg_replace("#" . preg_quote($suffix) . "$#u", "", $path);
+ }
+
+ self::$is_admin = $path == "/admin" || !strncmp($path, "/admin/", 7);
+ self::$site_theme_name = module::get_var("gallery", "active_site_theme");
+
+ // If the site theme doesn't exist, fall back to wind.
+ if (!file_exists(THEMEPATH . self::$site_theme_name . "/theme.info")) {
+ site_status::error(t("Theme '%name' is missing. Falling back to the Wind theme.",
+ array("name" => self::$site_theme_name)), "missing_site_theme");
+ module::set_var("gallery", "active_site_theme", self::$site_theme_name = "wind");
+ }
+
+ if (self::$is_admin) {
+ // Load the admin theme
+ self::$admin_theme_name = module::get_var("gallery", "active_admin_theme");
+
+ // If the admin theme doesn't exist, fall back to admin_wind.
+ if (!file_exists(THEMEPATH . self::$admin_theme_name . "/theme.info")) {
+ site_status::error(t("Admin theme '%name' is missing! Falling back to the Wind theme.",
+ array("name" => self::$admin_theme_name)), "missing_admin_theme");
+ module::set_var("gallery", "active_admin_theme", self::$admin_theme_name = "admin_wind");
+ }
+
+ array_unshift($modules, THEMEPATH . self::$admin_theme_name);
+
+ // If the site theme has an admin subdir, load that as a module so that
+ // themes can provide their own code.
+ if (file_exists(THEMEPATH . self::$site_theme_name . "/admin")) {
+ array_unshift($modules, THEMEPATH . self::$site_theme_name . "/admin");
+ }
+ // Admins can override the site theme, temporarily. This lets us preview themes.
+ if (identity::active_user()->admin && $override = $input->get("theme")) {
+ if (file_exists(THEMEPATH . $override)) {
+ self::$admin_theme_name = $override;
+ array_unshift($modules, THEMEPATH . self::$admin_theme_name);
+ } else {
+ Kohana_Log::add("error", "Missing override admin theme: '$override'");
+ }
+ }
+ } else {
+ // Admins can override the site theme, temporarily. This lets us preview themes.
+ if (identity::active_user()->admin && $override = $input->get("theme")) {
+ if (file_exists(THEMEPATH . $override)) {
+ self::$site_theme_name = $override;
+ } else {
+ Kohana_Log::add("error", "Missing override site theme: '$override'");
+ }
+ }
+ array_unshift($modules, THEMEPATH . self::$site_theme_name);
+ }
+
+ $config->set("core.modules", $modules);
+ }
+
+ static function get_info($theme_name) {
+ $theme_name = preg_replace("/[^a-zA-Z0-9\._-]/", "", $theme_name);
+ $file = THEMEPATH . "$theme_name/theme.info";
+ $theme_info = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS);
+ $theme_info->description = t($theme_info->description);
+ $theme_info->name = t($theme_info->name);
+
+ return $theme_info;
+ }
+}
+
diff --git a/modules/gallery/helpers/tree_rest.php b/modules/gallery/helpers/tree_rest.php
new file mode 100644
index 0000000..5186cf0
--- /dev/null
+++ b/modules/gallery/helpers/tree_rest.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 tree_rest_Core {
+ /**
+ * The tree is rooted in a single item and can have modifiers which adjust what data is shown
+ * for items inside the given tree, up to the depth that you want. The entity for this resource
+ * is a series of items.
+ *
+ * depth=<number>
+ * Only traverse this far down into the tree. If there are more albums
+ * below this depth, provide RESTful urls to other tree resources in
+ * the members section. Default is infinite.
+ *
+ * type=<album|photo|movie>
+ * Restrict the items displayed to the given type. Default is all types.
+ *
+ * fields=<comma separated list of field names>
+ * In the entity section only return these fields for each item.
+ * Default is all fields.
+ */
+ static function get($request) {
+ $item = rest::resolve($request->url);
+ access::required("view", $item);
+
+ $query_params = array();
+ $p = $request->params;
+ $where = array();
+ if (isset($p->type)) {
+ $where[] = array("type", "=", $p->type);
+ $query_params[] = "type={$p->type}";
+ }
+
+ if (isset($p->depth)) {
+ $lowest_depth = $item->level + $p->depth;
+ $where[] = array("level", "<=", $lowest_depth);
+ $query_params[] = "depth={$p->depth}";
+ }
+
+ $fields = array();
+ if (isset($p->fields)) {
+ $fields = explode(",", $p->fields);
+ $query_params[] = "fields={$p->fields}";
+ }
+
+ $entity = array(array("url" => rest::url("item", $item),
+ "entity" => $item->as_restful_array($fields)));
+ $members = array();
+ foreach ($item->viewable()->descendants(null, null, $where) as $child) {
+ $entity[] = array("url" => rest::url("item", $child),
+ "entity" => $child->as_restful_array($fields));
+ if (isset($lowest_depth) && $child->level == $lowest_depth) {
+ $members[] = url::merge_querystring(rest::url("tree", $child), $query_params);
+ }
+ }
+
+ $result = array(
+ "url" => $request->url,
+ "entity" => $entity,
+ "members" => $members,
+ "relationships" => rest::relationships("tree", $item));
+ return $result;
+ }
+
+ static function resolve($id) {
+ $item = ORM::factory("item", $id);
+ if (!access::can("view", $item)) {
+ throw new Kohana_404_Exception();
+ }
+ return $item;
+ }
+
+ static function url($item) {
+ return url::abs_site("rest/tree/{$item->id}");
+ }
+}
diff --git a/modules/gallery/helpers/upgrade_checker.php b/modules/gallery/helpers/upgrade_checker.php
new file mode 100644
index 0000000..492f72e
--- /dev/null
+++ b/modules/gallery/helpers/upgrade_checker.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 upgrade_checker_Core {
+ const CHECK_URL = "http://galleryproject.org/versioncheck/gallery3";
+ const AUTO_CHECK_INTERVAL = 604800; // 7 days in seconds
+
+ /**
+ * Return the last version info blob retrieved from the Gallery website or
+ * null if no checks have been performed.
+ */
+ static function version_info() {
+ return unserialize(Cache::instance()->get("upgrade_checker_version_info"));
+ }
+
+ /**
+ * Return true if auto checking is enabled.
+ */
+ static function auto_check_enabled() {
+ return (bool)module::get_var("gallery", "upgrade_checker_auto_enabled");
+ }
+
+ /**
+ * Return true if it's time to auto check.
+ */
+ static function should_auto_check() {
+ if (upgrade_checker::auto_check_enabled() && random::int(1, 100) == 1) {
+ $version_info = upgrade_checker::version_info();
+ return (!$version_info ||
+ (time() - $version_info->timestamp) > upgrade_checker::AUTO_CHECK_INTERVAL);
+ }
+ return false;
+ }
+
+ /**
+ * Fech version info from the Gallery website.
+ */
+ static function fetch_version_info() {
+ $result = new stdClass();
+ try {
+ list ($status, $headers, $body) = remote::do_request(upgrade_checker::CHECK_URL);
+ if ($status == "HTTP/1.1 200 OK") {
+ $result->status = "success";
+ foreach (explode("\n", $body) as $line) {
+ if ($line) {
+ list($key, $val) = explode("=", $line, 2);
+ $result->data[$key] = $val;
+ }
+ }
+ } else {
+ $result->status = "error";
+ }
+ } catch (Exception $e) {
+ Kohana_Log::add("error",
+ sprintf("%s in %s at line %s:\n%s", $e->getMessage(), $e->getFile(),
+ $e->getLine(), $e->getTraceAsString()));
+ }
+ $result->timestamp = time();
+ Cache::instance()->set("upgrade_checker_version_info", serialize($result),
+ array("upgrade"), 86400 * 365);
+ }
+
+ /**
+ * Check the latest version info blob to see if it's time for an upgrade.
+ */
+ static function get_upgrade_message() {
+ $version_info = upgrade_checker::version_info();
+ if ($version_info) {
+ if (gallery::RELEASE_CHANNEL == "release") {
+ if (version_compare($version_info->data["release_version"], gallery::VERSION, ">")) {
+ return t("A newer version of Gallery is available! <a href=\"%upgrade-url\">Upgrade now</a> to version %version",
+ array("version" => $version_info->data["release_version"],
+ "upgrade-url" => $version_info->data["release_upgrade_url"]));
+ }
+ } else {
+ $branch = gallery::RELEASE_BRANCH;
+ if (isset($version_info->data["branch_{$branch}_build_number"]) &&
+ version_compare($version_info->data["branch_{$branch}_build_number"],
+ gallery::build_number(), ">")) {
+ return t("A newer version of Gallery is available! <a href=\"%upgrade-url\">Upgrade now</a> to version %version (build %build on branch %branch)",
+ array("version" => $version_info->data["branch_{$branch}_version"],
+ "upgrade-url" => $version_info->data["branch_{$branch}_upgrade_url"],
+ "build" => $version_info->data["branch_{$branch}_build_number"],
+ "branch" => $branch));
+ }
+ }
+ }
+ }
+}
diff --git a/modules/gallery/helpers/user_profile.php b/modules/gallery/helpers/user_profile.php
new file mode 100644
index 0000000..222d2f5
--- /dev/null
+++ b/modules/gallery/helpers/user_profile.php
@@ -0,0 +1,55 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 user_profile_Core {
+ /**
+ * Generate the url to display the profile
+ * @return url for the profile display
+ */
+ static function url($user_id) {
+ return url::site("user_profile/show/{$user_id}");
+ }
+
+ static function get_contact_form($user) {
+ $form = new Forge("user_profile/send/{$user->id}", "", "post",
+ array("id" => "g-user-profile-contact-form"));
+ $group = $form->group("message")
+ ->label(t("Compose message to %name", array("name" => $user->display_name())));
+ $group->input("reply_to")
+ ->label(t("From:"))
+ ->rules("required|length[1, 256]|valid_email")
+ ->error_messages("required", t("You must enter a valid email address"))
+ ->error_messages("max_length", t("Your email address is too long"))
+ ->error_messages("valid_email", t("You must enter a valid email address"));
+ $group->input("subject")
+ ->label(t("Subject:"))
+ ->rules("required|length[1, 256]")
+ ->error_messages("required", t("Your message must have a subject"))
+ ->error_messages("max_length", t("Your subject is too long"));
+ $group->textarea("message")
+ ->label(t("Message:"))
+ ->rules("required")
+ ->error_messages("required", t("You must enter a message"));
+ module::event("user_profile_contact_form", $form);
+ module::event("captcha_protect_form", $form);
+ $group->submit("")->value(t("Send"));
+ return $form;
+ }
+}
diff --git a/modules/gallery/helpers/xml.php b/modules/gallery/helpers/xml.php
new file mode 100644
index 0000000..e20beb1
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 0000000..fa4f816
--- /dev/null
+++ b/modules/gallery/hooks/init_gallery.php
@@ -0,0 +1,56 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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"));
+}
+
+// Simple and cheap test to make sure that the database config is ok. Do this before we do
+// anything else database related.
+try {
+ Database::instance()->connect();
+} catch (Kohana_PHP_Exception $e) {
+ print "Database configuration error. Please check var/database.php";
+ exit;
+}
+
+Event::add("system.ready", array("Gallery_I18n", "instance"));
+Event::add("system.ready", array("module", "load_modules"));
+Event::add("system.ready", array("gallery", "ready"));
+Event::add("system.post_routing", array("url", "parse_url"));
+Event::add("system.post_routing", array("gallery", "maintenance_mode"));
+Event::add("system.post_routing", array("gallery", "private_gallery"));
+Event::add("system.shutdown", array("gallery", "shutdown"));
+
+// @todo once we convert to Kohana 2.4 this doesn't have to be here
+set_error_handler(array("gallery_error", "error_handler"));
+
+// 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"))) {
+ $_SERVER["HTTP_USER_AGENT"] = $user_agent;
+}
diff --git a/modules/gallery/images/ffmpeg.png b/modules/gallery/images/ffmpeg.png
new file mode 100644
index 0000000..6be8b62
--- /dev/null
+++ b/modules/gallery/images/ffmpeg.png
Binary files differ
diff --git a/modules/gallery/images/gallery.png b/modules/gallery/images/gallery.png
new file mode 100644
index 0000000..ca8e0e9
--- /dev/null
+++ b/modules/gallery/images/gallery.png
Binary files differ
diff --git a/modules/gallery/images/gd.png b/modules/gallery/images/gd.png
new file mode 100644
index 0000000..b341d71
--- /dev/null
+++ b/modules/gallery/images/gd.png
Binary files differ
diff --git a/modules/gallery/images/graphicsmagick.png b/modules/gallery/images/graphicsmagick.png
new file mode 100644
index 0000000..3d1d77e
--- /dev/null
+++ b/modules/gallery/images/graphicsmagick.png
Binary files differ
diff --git a/modules/gallery/images/imagemagick.jpg b/modules/gallery/images/imagemagick.jpg
new file mode 100644
index 0000000..d83c450
--- /dev/null
+++ b/modules/gallery/images/imagemagick.jpg
Binary files differ
diff --git a/modules/gallery/images/missing_album_cover.jpg b/modules/gallery/images/missing_album_cover.jpg
new file mode 100644
index 0000000..bdddeec
--- /dev/null
+++ b/modules/gallery/images/missing_album_cover.jpg
Binary files differ
diff --git a/modules/gallery/images/missing_movie.jpg b/modules/gallery/images/missing_movie.jpg
new file mode 100644
index 0000000..452db22
--- /dev/null
+++ b/modules/gallery/images/missing_movie.jpg
Binary files differ
diff --git a/modules/gallery/images/missing_photo.jpg b/modules/gallery/images/missing_photo.jpg
new file mode 100644
index 0000000..a9d176d
--- /dev/null
+++ b/modules/gallery/images/missing_photo.jpg
Binary files differ
diff --git a/modules/gallery/js/albums_form_add.js b/modules/gallery/js/albums_form_add.js
new file mode 100644
index 0000000..4e22760
--- /dev/null
+++ b/modules/gallery/js/albums_form_add.js
@@ -0,0 +1,25 @@
+$("#g-add-album-form input[name=title]").change(
+ function() {
+ console.log("changing title");
+ $("#g-add-album-form input[name=name]").attr(
+ "value", $("#g-add-album-form input[name=title]").attr("value")
+ .replace(/[\s\/]+/g, "-").replace(/\.+$/, ""));
+ $("#g-add-album-form input[name=slug]").attr(
+ "value", $("#g-add-album-form input[name=title]").attr("value")
+ .replace(/[^A-Za-z0-9-_]+/g, "-")
+ .replace(/^-+/, "")
+ .replace(/-+$/, ""));
+ });
+$("#g-add-album-form input[name=title]").keyup(
+ function() {
+ console.log("key up");
+ $("#g-add-album-form input[name=name]").attr(
+ "value", $("#g-add-album-form input[name=title]").attr("value")
+ .replace(/[\s\/]+/g, "-")
+ .replace(/\.+$/, ""));
+ $("#g-add-album-form input[name=slug]").attr(
+ "value", $("#g-add-album-form input[name=title]").attr("value")
+ .replace(/[^A-Za-z0-9-_]+/g, "-")
+ .replace(/^-+/, "")
+ .replace(/-+$/, ""));
+ });
diff --git a/modules/gallery/js/item_form_delete.js b/modules/gallery/js/item_form_delete.js
new file mode 100644
index 0000000..fa3f24a
--- /dev/null
+++ b/modules/gallery/js/item_form_delete.js
@@ -0,0 +1,5 @@
+$("#g-confirm-delete").submit(
+ function() {
+ $("#g-confirm-delete input[type=submit]").gallery_show_loading();
+ }
+);
diff --git a/modules/gallery/js/l10n_client.js b/modules/gallery/js/l10n_client.js
new file mode 100644
index 0000000..6d919c2
--- /dev/null
+++ b/modules/gallery/js/l10n_client.js
@@ -0,0 +1,315 @@
+// Fork from Drupal's l10n_client module, originally written by:
+// G‡bor 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 #g-l10n-search').focus();
+ } else {
+ if($('#l10n-client').is('.hidden')) {
+ Gallery.l10nClient.toggle(1);
+ if(!$.browser.safari) {
+ $('#l10n-client #g-l10n-search').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').slideUp();
+ $('#g-minimize-l10n').text("_");
+ // 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
+ $('#g-minimize-l10n').text(MSG_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) {
+ if (index < l10n_client_data.length) {
+ return l10n_client_data[index][type];
+ }
+ return "";
+ };
+
+ // Set a string in the DOM tree
+ this.setString = function(index, data) {
+ l10n_client_data[index]['translation'] = data;
+ };
+
+ // Display the source message
+ this.showSourceMessage = function(source, is_plural) {
+ if (is_plural) {
+ var pretty_source = $('#source-text-tmp-space').text('[one] - ' + source['one']).html();
+ pretty_source += '<br/>';
+ pretty_source += $('#source-text-tmp-space').text('[other] - ' + source['other']).html();
+ } else {
+ var pretty_source = $('#source-text-tmp-space').text(source).html();
+ }
+ $('#l10n-client-string-editor .source-text').html(pretty_source);
+ };
+ this.isPluralMessage = function(message) {
+ return typeof(message) == 'object';
+ };
+
+ this.updateTranslationForm = function(translation, is_plural) {
+ $('.translationField').addClass('hidden');
+ if (is_plural) {
+ if (typeof(translation) != 'object') {
+ translation = {};
+ }
+ var num_plural_forms = plural_forms.length;
+ for (var i = 0; i < num_plural_forms; i++) {
+ var form = plural_forms[i];
+ if (translation[form] == undefined) {
+ translation[form] = '';
+ }
+ $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']")
+ .attr('value', translation[form]);
+ $('#plural-' + form).removeClass('hidden');
+ }
+ } else {
+ $('#l10n-edit-translation').attr('value', translation);
+ $('#l10n-edit-translation').removeClass('hidden');
+ }
+ };
+
+ // Filter 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 #g-l10n-search').val('');
+ $('#l10n-client #g-l10n-search').focus();
+ } else {
+ if(search.length > 0) {
+ $('#l10n-client-string-select li').hide();
+ $('#l10n-client-string-select li').each(function() {
+ if ($(this).val().indexOf(search) != -1) {
+ $(this).show();
+ }
+ });
+ $('#l10n-client #g-l10n-search').val(search);
+ }
+ }
+ };
+
+ this.copySourceText = function() {
+ var index = Gallery.l10nClient.selected;
+ if (index >= 0) {
+ var source = Gallery.l10nClient.getString(index, 'source');
+ var is_plural = Gallery.l10nClient.isPluralMessage(source);
+ if (is_plural) {
+ if (typeof(translation) != 'object') {
+ translation = {};
+ }
+ var num_plural_forms = plural_forms.length;
+ for (var i = 0; i < num_plural_forms; i++) {
+ var form = plural_forms[i];
+ var text = source['other'];
+ if (form == 'one') {
+ text = source['one'];
+ }
+ $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']")
+ .attr('value', text);
+ }
+ } else {
+ $('#l10n-edit-translation').attr('value', source);
+ }
+
+ }
+ };
+ })
+});
+
+// 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);
+ var source = Gallery.l10nClient.getString(index, 'source');
+ var key = Gallery.l10nClient.getString(index, 'key');
+ var is_plural = Gallery.l10nClient.isPluralMessage(source);
+ Gallery.l10nClient.showSourceMessage(source, is_plural);
+ Gallery.l10nClient.updateTranslationForm(Gallery.l10nClient.getString(index, 'translation'), is_plural);
+ $("#g-l10n-client-save-form input[name='l10n-message-key']").val(key);
+ Gallery.l10nClient.selected = index;
+ });
+
+ // When l10n_client window is clicked, toggle based on current state.
+ $('#g-minimize-l10n').click(function() {
+ if($('#l10n-client').is('.hidden')) {
+ Gallery.l10nClient.toggle(1);
+ } else {
+ Gallery.l10nClient.toggle(0);
+ }
+ });
+
+ // Close the l10n client using an AJAX call and refreshing the page
+ $('#g-close-l10n').click(function(event) {
+ $.ajax({
+ type: "GET",
+ url: toggle_l10n_mode_url,
+ data: "csrf=" + csrf,
+ success: function() {
+ window.location.reload(true);
+ }
+ });
+ event.preventDefault();
+ });
+
+ // 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 #g-l10n-search', type:'keyup'}, function(){Gallery.l10nClient.key('clear');});
+ }
+
+ // never actually submit the form as the search is done in the browser
+ $('#g-l10n-search-form').submit(function() {
+ return false;
+ });
+
+ // Custom listener for l10n_client livesearch
+ $('#l10n-client #g-l10n-search').keyup(function(key) {
+ Gallery.l10nClient.filter($('#l10n-client #g-l10n-search').val());
+ });
+
+ // Clear search
+ $('#l10n-client #l10n-search-filter-clear').click(function() {
+ Gallery.l10nClient.filter(false);
+ return false;
+ });
+
+ // Send AJAX POST data on form submit.
+ $('#g-l10n-client-save-form').ajaxForm({
+ dataType: "json",
+ success: function(data) {
+ var source = Gallery.l10nClient.getString(Gallery.l10nClient.selected, 'source');
+ var is_plural = Gallery.l10nClient.isPluralMessage(source);
+ var num_plural_forms = plural_forms.length;
+
+ // Store translation in local js
+ var translation = {};
+ var is_non_empty = false;
+ if (is_plural) {
+ for (var i = 0; i < num_plural_forms; i++) {
+ var form = plural_forms[i];
+ translation[form] = $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']").attr('value');
+ is_non_empty = is_non_empty || translation[form];
+ }
+ } else {
+ translation = $('#l10n-edit-translation').attr('value');
+ is_non_empty = translation;
+ }
+ Gallery.l10nClient.setString(Gallery.l10nClient.selected, translation);
+
+ // Mark message as translated / untranslated.
+ var source_element = $('#l10n-client-string-select li').eq(Gallery.l10nClient.selected);
+ if (is_non_empty) {
+ source_element.removeClass('untranslated').removeClass('active').addClass('translated');
+ } else {
+ source_element.removeClass('active').removeClass('translated').addClass('untranslated');
+ }
+
+ // Clear the translation form fields
+ Gallery.l10nClient.showSourceMessage('', false);
+ $('#g-l10n-client-save-form #l10n-edit-translation').val('');
+
+ for (var i = 0; i < num_plural_forms; i++) {
+ var form = plural_forms[i];
+ $("#plural-" + form + " textarea[name='l10n-edit-plural-translation-" + form + "']").val('');
+ }
+ $("#g-l10n-client-save-form input[name='l10n-message-key']").val('');
+ },
+ error: function(xmlhttp) {
+ // TODO: Localize this message
+ alert('An HTTP error @status occured (or empty response).'.replace('@status', xmlhttp.status));
+ }
+ });
+
+ // TODO: Add copy/clear buttons (without ajax behavior)
+ /* <input type="submit" name="l10n-edit-copy" value="<?= t("Copy source") ?>"/>
+ <input type="submit" name="l10n-edit-clear" value="<?= t("Clear") ?>"/>
+ */
+ // TODO: Handle plurals in copy button
+
+ // Copy source text to translation field on button click.
+ $('#g-l10n-client-save-form #l10n-edit-copy').click(function() {
+ $('#g-l10n-client-save-form #l10n-edit-target').val($('#l10n-client-string-editor .source-text').text());
+ });
+
+ // Clear translation field on button click.
+ $('#g-l10n-client-save-form #l10n-edit-clear').click(function() {
+ $('#g-l10n-client-save-form #l10n-edit-target').val('');
+ });
+};
diff --git a/modules/gallery/libraries/Admin_View.php b/modules/gallery/libraries/Admin_View.php
new file mode 100644
index 0000000..62645d1
--- /dev/null
+++ b/modules/gallery/libraries/Admin_View.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Gallery_View {
+ /**
+ * 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) {
+ parent::__construct($name);
+
+ $this->theme_name = module::get_var("gallery", "active_admin_theme");
+ if (identity::active_user()->admin) {
+ $theme_name = Input::instance()->get("theme");
+ if ($theme_name &&
+ file_exists(THEMEPATH . $theme_name) &&
+ strpos(realpath(THEMEPATH . $theme_name), THEMEPATH) == 0) {
+ $this->theme_name = $theme_name;
+ }
+ }
+ $this->sidebar = "";
+ $this->set_global(array("theme" => $this,
+ "user" => identity::active_user(),
+ "page_type" => "admin",
+ "page_subtype" => $name,
+ "page_title" => null));
+ }
+
+ public function admin_menu() {
+ $menu = Menu::factory("root");
+ module::event("admin_menu", $menu, $this);
+
+ $settings_menu = $menu->get("settings_menu");
+ uasort($settings_menu->elements, array("Menu", "title_comparator"));
+
+ return $menu->render();
+ }
+
+ public function user_menu() {
+ $menu = Menu::factory("root")
+ ->css_id("g-login-menu")
+ ->css_class("g-inline ui-helper-clear-fix");
+ module::event("user_menu", $menu, $this);
+ return $menu->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 "admin_credits";
+ case "admin_footer":
+ case "admin_header_top":
+ case "admin_header_bottom":
+ case "admin_page_bottom":
+ case "admin_page_top":
+ case "admin_head":
+ case "body_attributes":
+ case "html_attributes":
+ $blocks = array();
+ foreach (module::active() as $module) {
+ $helper_class = "{$module->name}_theme";
+ if (class_exists($helper_class) && 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=\"g-annotated-theme-block g-annotated-theme-block_$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 0000000..818af9c
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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/Breadcrumb.php b/modules/gallery/libraries/Breadcrumb.php
new file mode 100644
index 0000000..3805c5d
--- /dev/null
+++ b/modules/gallery/libraries/Breadcrumb.php
@@ -0,0 +1,70 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Breadcrumb_Core {
+ public $title;
+ public $url;
+ public $first;
+ public $last;
+
+ static function instance($title, $url) {
+ return new Breadcrumb($title, $url);
+ }
+
+ public function __construct($title, $url) {
+ $this->title = $title;
+ $this->url = $url;
+ $this->first = false;
+ $this->last = false;
+ }
+
+ /**
+ * Return an array of Breadcrumb instances build from the parents of a given item.
+ * The first and last Breadcrumb instances will be marked first/last as appropriate.
+ * Each breadcrumb will have a ?show= query parameter that refers to the id of the next
+ * item in line.
+ *
+ * @return array Breadcrumb instances
+ */
+ static function array_from_item_parents($item) {
+ if ($item->id == item::root()->id) {
+ return array();
+ }
+
+ $bc = array_merge($item->parents()->as_array(), array($item));
+ for ($i = 0; $i < count($bc) - 1; $i++) {
+ $bc[$i] = new Breadcrumb($bc[$i]->title, $bc[$i]->url("show={$bc[$i+1]->id}"));
+ }
+ $bc[$i] = new Breadcrumb($item->title, $item->url());
+
+ $bc[0]->set_first();
+ end($bc)->set_last();
+ return $bc;
+ }
+
+ public function set_first() {
+ $this->first = true;
+ return $this;
+ }
+
+ public function set_last() {
+ $this->last = true;
+ return $this;
+ }
+}
diff --git a/modules/gallery/libraries/Form_Script.php b/modules/gallery/libraries/Form_Script.php
new file mode 100644
index 0000000..2b5ec3e
--- /dev/null
+++ b/modules/gallery/libraries/Form_Script.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Form_Script_Core extends Forge {
+ protected $data = array(
+ "name" => false,
+ "type" => "script",
+ "url" => "",
+ "text" => "");
+
+ public function __construct($name) {
+ // Set dummy data so we don"t get errors
+ $this->attr["action"] = "";
+ $this->attr["method"] = "post";
+ $this->data["name"] = $name;
+ }
+
+ public function __get($key) {
+ return isset($this->data[$key]) ? $this->data[$key] : null;
+ }
+
+ /**
+ * Sets url attribute
+ */
+ public function url($url) {
+ $this->data["url"] = $url;
+
+ return $this;
+ }
+
+ public function text($script_text) {
+ $this->data["text"] = $script_text;
+
+ return $this;
+ }
+
+ public function render($template="forge_template", $custom=false) {
+ $script = array();
+ if (!empty($this->data["url"])) {
+ $script[] = html::script($this->data["url"]);
+ }
+
+ if (!empty($this->data["text"])) {
+ $script[] = "<script type=\"text/javascript\">\n{$this->data['text']}\n</script>\n";
+ }
+
+ return implode("\n", $script);
+ }
+
+} \ No newline at end of file
diff --git a/modules/gallery/libraries/Form_Uploadify.php b/modules/gallery/libraries/Form_Uploadify.php
new file mode 100644
index 0000000..1e58018
--- /dev/null
+++ b/modules/gallery/libraries/Form_Uploadify.php
@@ -0,0 +1,72 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Form_Uploadify_Core extends Form_Input {
+ protected $data = array(
+ "name" => false,
+ "type" => "UNKNOWN",
+ "url" => "",
+ "text" => "");
+
+ public function __construct($name) {
+ parent::__construct($name);
+ $this->data["script_data"] = array(
+ "g3sid" => Session::instance()->id(),
+ "user_agent" => Input::instance()->server("HTTP_USER_AGENT"),
+ "csrf" => access::csrf_token());
+ }
+
+ public function album(Item_Model $album) {
+ $this->data["album"] = $album;
+ return $this;
+ }
+
+ public function script_data($key, $value) {
+ $this->data["script_data"][$key] = $value;
+ }
+
+ public function render() {
+ $v = new View("form_uploadify.html");
+ $v->album = $this->data["album"];
+ $v->script_data = $this->data["script_data"];
+ $v->simultaneous_upload_limit = module::get_var("gallery", "simultaneous_upload_limit");
+ $v->movies_allowed = movie::allow_uploads();
+ $v->extensions = legal_file::get_filters();
+ $v->suhosin_session_encrypt = (bool) ini_get("suhosin.session.encrypt");
+
+ list ($toolkit_max_filesize_bytes, $toolkit_max_filesize) = graphics::max_filesize();
+
+ $upload_max_filesize = trim(ini_get("upload_max_filesize"));
+ $upload_max_filesize_bytes = num::convert_to_bytes($upload_max_filesize);
+
+ if ($upload_max_filesize_bytes < $toolkit_max_filesize_bytes) {
+ $v->size_limit_bytes = $upload_max_filesize_bytes;
+ $v->size_limit = $upload_max_filesize;
+ } else {
+ $v->size_limit_bytes = $toolkit_max_filesize_bytes;
+ $v->size_limit = $toolkit_max_filesize;
+ }
+
+ return $v;
+ }
+
+ public function validate() {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/libraries/Form_Uploadify_buttons.php b/modules/gallery/libraries/Form_Uploadify_buttons.php
new file mode 100644
index 0000000..2e0327e
--- /dev/null
+++ b/modules/gallery/libraries/Form_Uploadify_buttons.php
@@ -0,0 +1,25 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Form_Uploadify_buttons_Core extends Form_Input {
+ public function render() {
+ $v = new View("form_uploadify_buttons.html");
+ return $v;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/libraries/Gallery_I18n.php b/modules/gallery/libraries/Gallery_I18n.php
new file mode 100644
index 0000000..6b216a2
--- /dev/null
+++ b/modules/gallery/libraries/Gallery_I18n.php
@@ -0,0 +1,472 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Gallery_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 Gallery_I18n::instance()->translate(array("one" => $singular, "other" => $plural),
+ array_merge($options, array("count" => $count)));
+}
+
+class Gallery_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('gallery', 'default_locale');
+ }
+ self::$_instance = new Gallery_I18n_Core($config);
+ }
+
+ return self::$_instance;
+ }
+
+ public function locale($locale=null) {
+ if ($locale) {
+ $this->_config['default_locale'] = $locale;
+ $php_locale = setlocale(LC_ALL, 0);
+ list ($php_locale, $unused) = explode('.', $php_locale . '.');
+ if ($php_locale != $locale) {
+ // Attempt to set PHP's locale as well (for number formatting, collation, etc.)
+ $locale_prefs = array($locale);
+ // Try appending some character set names; some systems (like FreeBSD) need this.
+ // Some systems require a format with hyphen (eg. Gentoo) and others without (eg. FreeBSD).
+ $charsets = array('utf8', 'UTF-8', 'UTF8', 'ISO8859-1', 'ISO-8859-1');
+ if (substr($locale, 0, 2) != 'en') {
+ $charsets = array_merge($charsets, array(
+ 'EUC', 'Big5', 'euc', 'ISO8859-2', 'ISO8859-5', 'ISO8859-7',
+ 'ISO8859-9', 'ISO-8859-2', 'ISO-8859-5', 'ISO-8859-7', 'ISO-8859-9'));
+ }
+ foreach ($charsets as $charset) {
+ $locale_prefs[] = $locale . '.' . $charset;
+ }
+ $locale_prefs[] = 'en_US';
+ $php_locale = setlocale(LC_ALL, $locale_prefs);
+ }
+ if (is_string($php_locale) && substr($php_locale, 0, 2) == 'tr') {
+ // Make PHP 5 work with Turkish (the localization results are mixed though).
+ // Hack for http://bugs.php.net/18556
+ setlocale(LC_CTYPE, 'C');
+ }
+ }
+ return $this->_config['default_locale'];
+ }
+
+ public function is_rtl($locale=null) {
+ $is_rtl = !empty($this->_config["force_rtl"]);
+ if (empty($is_rtl)) {
+ $locale or $locale = $this->locale();
+ list ($language, $territory) = explode('_', $locale . "_");
+ $is_rtl = in_array($language, array("he", "fa", "ar"));
+ }
+ return $is_rtl;
+ }
+
+ /**
+ * Translates a localizable message.
+ *
+ * Security:
+ * The returned string is safe for use in HTML (it contains a safe subset of HTML and
+ * interpolation parameters are converted to HTML entities).
+ * For use in JavaScript, please call ->for_js() on it.
+ *
+ * @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 SafeString::of_safe_html($entry);
+ }
+
+ private function lookup($locale, $message) {
+ if (!isset($this->_cache[$locale])) {
+ $this->_cache[$locale] = self::load_translations($locale);
+ }
+
+ $key = self::get_message_key($message);
+
+ if (isset($this->_cache[$locale][$key])) {
+ return $this->_cache[$locale][$key];
+ } else {
+ return null;
+ }
+ }
+
+ private static function load_translations($locale) {
+ $cache_key = "translation|" . $locale;
+ $cache = Cache::instance();
+ $translations = $cache->get($cache_key);
+ if (!isset($translations) || !is_array($translations)) {
+ $translations = array();
+ foreach (db::build()
+ ->select("key", "translation")
+ ->from("incoming_translations")
+ ->where("locale", "=", $locale)
+ ->execute() as $row) {
+ $translations[$row->key] = unserialize($row->translation);
+ }
+
+ // Override incoming with outgoing...
+ foreach (db::build()
+ ->select("key", "translation")
+ ->from("outgoing_translations")
+ ->where("locale", "=", $locale)
+ ->execute() as $row) {
+ $translations[$row->key] = unserialize($row->translation);
+ }
+
+ $cache->set($cache_key, $translations, array("translation"), 0);
+ }
+ return $translations;
+ }
+
+ public function has_translation($message, $options=null) {
+ $locale = empty($options['locale']) ? $this->_config['default_locale'] : $options['locale'];
+
+ $entry = $this->lookup($locale, $message);
+
+ if (null === $entry) {
+ return false;
+ } else if (!is_array($message)) {
+ return $entry !== '';
+ } else {
+ if (!is_array($entry) || empty($entry)) {
+ return false;
+ }
+ // It would be better to verify that all the locale's plural forms have a non-empty
+ // translation, but this is fine for now.
+ foreach ($entry as $value) {
+ if ($value === '') {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ static function get_message_key($message) {
+ $as_string = is_array($message) ? implode('|', $message) : $message;
+ return md5($as_string);
+ }
+
+ static function is_plural_message($message) {
+ return is_array($message);
+ }
+
+ private function interpolate($locale, $string, $key_values) {
+ // TODO: Handle locale specific number formatting.
+
+ // Replace x_y before replacing x.
+ krsort($key_values, SORT_STRING);
+
+ $keys = array();
+ $values = array();
+ foreach ($key_values as $key => $value) {
+ $keys[] = "%$key";
+ $values[] = new SafeString($value);
+ }
+ return str_replace($keys, $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;
+ }
+
+ public static function clear_cache($locale=null) {
+ $cache = Cache::instance();
+ if ($locale) {
+ $cache->delete("translation|" . $locale);
+ } else {
+ $cache->delete_tag("translation");
+ }
+ }
+
+ 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';
+ }
+ }
+}
diff --git a/modules/gallery/libraries/Gallery_View.php b/modules/gallery/libraries/Gallery_View.php
new file mode 100644
index 0000000..8f02b53
--- /dev/null
+++ b/modules/gallery/libraries/Gallery_View.php
@@ -0,0 +1,243 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Gallery_View_Core extends View {
+ protected $theme_name = null;
+ protected $combine_queue = array();
+
+ /**
+ * Provide a url to a resource within the current theme. This allows us to refer to theme
+ * resources without naming the theme itself which makes themes easier to copy.
+ */
+ public function url($path, $absolute_url=false) {
+ $arg = "themes/{$this->theme_name}/$path";
+ return $absolute_url ? url::abs_file($arg) : url::file($arg);
+ }
+
+ /**
+ * Set up the data and render a pager.
+ *
+ * See themes/wind/views/pager.html for documentation on the variables generated here.
+ */
+ public function paginator() {
+ $v = new View("paginator.html");
+ $v->page_type = $this->page_type;
+ $v->page_subtype = $this->page_subtype;
+ $v->first_page_url = null;
+ $v->previous_page_url = null;
+ $v->next_page_url = null;
+ $v->last_page_url = null;
+
+ if ($this->page_type == "collection") {
+ $v->page = $this->page;
+ $v->max_pages = $this->max_pages;
+ $v->total = $this->children_count;
+
+ if ($this->page != 1) {
+ $v->first_page_url = url::site(url::merge(array("page" => 1)));
+ $v->previous_page_url = url::site(url::merge(array("page" => $this->page - 1)));
+ }
+
+ if ($this->page != $this->max_pages) {
+ $v->next_page_url = url::site(url::merge(array("page" => $this->page + 1)));
+ $v->last_page_url = url::site(url::merge(array("page" => $this->max_pages)));
+ }
+
+ $v->first_visible_position = ($this->page - 1) * $this->page_size + 1;
+ $v->last_visible_position = min($this->page * $this->page_size, $v->total);
+ } else if ($this->page_type == "item") {
+ $v->position = $this->position;
+ $v->total = $this->sibling_count;
+ if ($this->previous_item) {
+ $v->previous_page_url = $this->previous_item->url();
+ }
+
+ if ($this->next_item) {
+ $v->next_page_url = $this->next_item->url();
+ }
+ }
+
+ return $v;
+ }
+
+ /**
+ * Begin gather up scripts or css files so that they can be combined into a single request.
+ *
+ * @param $types a comma separated list of types to combine, eg "script,css"
+ */
+ public function start_combining($types) {
+ if (gallery::allow_css_and_js_combining()) {
+ foreach (explode(",", $types) as $type) {
+ $this->combine_queue[$type] = array();
+ }
+ }
+ }
+
+ /**
+ * If script combining is enabled, add this script to the list of scripts that will be
+ * combined into a single script element. When combined, the order of scripts is preserved.
+ *
+ * @param $file the file name or path of the script to include. If a path is specified then
+ * it needs to be relative to DOCROOT. Just specifying a file name will result
+ * in searching Kohana's cascading file system.
+ * @param $group the group of scripts to combine this with. defaults to "core"
+ */
+ public function script($file, $group="core") {
+ if (($path = gallery::find_file("js", $file, false))) {
+ if (isset($this->combine_queue["script"])) {
+ $this->combine_queue["script"][$group][$path] = 1;
+ } else {
+ return html::script($path);
+ }
+ } else {
+ Kohana_Log::add("error", "Can't find script file: $file");
+ }
+ }
+
+ /**
+ * If css combining is enabled, add this css to the list of css that will be
+ * combined into a single style element. When combined, the order of style elements
+ * is preserved.
+ *
+ * @param $file the file name or path of the css to include. If a path is specified then
+ * it needs to be relative to DOCROOT. Just specifying a file name will result
+ * in searching Kohana's cascading file system.
+ * @param $group the group of css to combine this with. defaults to "core"
+ */
+ public function css($file, $group="core") {
+ if (($path = gallery::find_file("css", $file, false))) {
+ if (isset($this->combine_queue["css"])) {
+ $this->combine_queue["css"][$group][$path] = 1;
+ } else {
+ return html::stylesheet($path);
+ }
+ } else {
+ Kohana_Log::add("error", "Can't find css file: $file");
+ }
+ }
+
+ /**
+ * Combine a series of files into a single one and cache it in the database.
+ * @param $type the data type (script or css)
+ * @param $group the group of scripts or css we want
+ */
+ public function get_combined($type, $group="core") {
+ $links = array();
+
+ if (empty($this->combine_queue[$type][$group])) {
+ return;
+ }
+
+ // Include the url in the cache key so that if the Gallery moves, we don't use old cached
+ // entries.
+ $key = array(url::abs_file(""));
+
+ foreach (array_keys($this->combine_queue[$type][$group]) as $path) {
+ $stats = stat($path);
+ // 7 == size, 9 == mtime, see http://php.net/stat
+ $key[] = "$path $stats[7] $stats[9]";
+ }
+
+ $key = md5(join(" ", $key));
+ $cache = Cache::instance();
+ $contents = $cache->get($key);
+
+ if (empty($contents)) {
+ $combine_data = new stdClass();
+ $combine_data->type = $type;
+ $combine_data->contents = $this->combine_queue[$type][$group];
+ module::event("before_combine", $combine_data);
+
+ $contents = "";
+ foreach (array_keys($this->combine_queue[$type][$group]) as $path) {
+ if ($type == "css") {
+ $contents .= "/* $path */\n" . $this->process_css($path) . "\n";
+ } else {
+ $contents .= "/* $path */\n" . file_get_contents($path) . "\n";
+ }
+ }
+
+ $combine_data = new stdClass();
+ $combine_data->type = $type;
+ $combine_data->contents = $contents;
+ module::event("after_combine", $combine_data);
+
+ $cache->set($key, $combine_data->contents, array($type), 30 * 84600);
+
+ $use_gzip = function_exists("gzencode") &&
+ (int) ini_get("zlib.output_compression") === 0;
+ if ($use_gzip) {
+ $cache->set("{$key}_gz", gzencode($combine_data->contents, 9, FORCE_GZIP),
+ array($type, "gzip"), 30 * 84600);
+ }
+
+ }
+
+ unset($this->combine_queue[$type][$group]);
+ if (empty($this->combine_queue[$type])) {
+ unset($this->combine_queue[$type]);
+ }
+
+ if ($type == "css") {
+ return html::stylesheet("combined/css/$key", "screen,print,projection", true);
+ } else {
+ return html::script("combined/javascript/$key", true);
+ }
+ }
+
+ /**
+ * Convert relative references inside a CSS file to absolute ones so that when it's served from
+ * a new location as part of a combined bundle the references are still correct.
+ * @param string the path to the css file
+ */
+ private function process_css($css_file) {
+ static $PATTERN = "#url\(\s*['|\"]{0,1}(.*?)['|\"]{0,1}\s*\)#";
+ $docroot_length = strlen(DOCROOT);
+
+ $css = file_get_contents($css_file);
+ if (preg_match_all($PATTERN, $css, $matches, PREG_SET_ORDER)) {
+ $search = $replace = array();
+ foreach ($matches as $match) {
+ $relative = dirname($css_file) . "/$match[1]";
+ if (!empty($relative)) {
+ $search[] = $match[0];
+ $replace[] = "url('" . url::abs_file($relative) . "')";
+ } else {
+ Kohana_Log::add("error", "Missing URL reference '{$match[1]}' in CSS file '$css_file'");
+
+ }
+ }
+ $replace = str_replace(DIRECTORY_SEPARATOR, "/", $replace);
+ $css = str_replace($search, $replace, $css);
+ }
+ $imports = preg_match_all("#@import\s*['|\"]{0,1}(.*?)['|\"]{0,1};#",
+ $css, $matches, PREG_SET_ORDER);
+
+ if ($imports) {
+ $search = $replace = array();
+ foreach ($matches as $match) {
+ $search[] = $match[0];
+ $replace[] = $this->process_css(dirname($css_file) . "/$match[1]");
+ }
+ $css = str_replace($search, $replace, $css);
+ }
+
+ return $css;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/libraries/IdentityProvider.php b/modules/gallery/libraries/IdentityProvider.php
new file mode 100644
index 0000000..525e169
--- /dev/null
+++ b/modules/gallery/libraries/IdentityProvider.php
@@ -0,0 +1,283 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+
+/**
+ * Provides a driver-based interface for managing users and groups.
+ */
+class IdentityProvider_Core {
+ protected static $instance;
+
+ // Configuration
+ protected $config;
+
+ // Driver object
+ protected $driver;
+
+ /**
+ * Returns a singleton instance of Identity.
+ * There can only be one Identity driver configured at a given point
+ *
+ * @param string configuration
+ * @return Identity_Core
+ */
+ static function &instance() {
+ if (empty(self::$instance)) {
+ // Create a new instance
+ self::$instance = new IdentityProvider();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Frees the current instance of the identity provider so the next call to instance will reload
+ *
+ * @param string configuration
+ * @return Identity_Core
+ */
+ static function reset() {
+ self::$instance = null;
+ Kohana_Config::instance()->clear("identity");
+ }
+
+ /**
+ * Return a commen confirmation message
+ */
+ static function confirmation_message() {
+ return t("Are you sure you want to change your Identity Provider? Continuing will delete all existing users.");
+ }
+
+ static function change_provider($new_provider) {
+ if (!identity::active_user()->admin && PHP_SAPI != "cli") {
+ // Below, the active user is set to the primary admin.
+ access::forbidden();
+ }
+
+ $current_provider = module::get_var("gallery", "identity_provider");
+ if (!empty($current_provider)) {
+ module::uninstall($current_provider);
+ }
+
+ try {
+ IdentityProvider::reset();
+ $provider = new IdentityProvider($new_provider);
+
+ module::set_var("gallery", "identity_provider", $new_provider);
+
+ if (class_exists("{$new_provider}_installer") &&
+ method_exists("{$new_provider}_installer", "initialize")) {
+ call_user_func("{$new_provider}_installer::initialize");
+ }
+
+ if (!$provider->admin_user()) {
+ throw new Exception("IdentityProvider $new_provider: Couldn't find the admin user!");
+ }
+
+ module::event("identity_provider_changed", $current_provider, $new_provider);
+
+ identity::set_active_user($provider->admin_user());
+ Session::instance()->regenerate();
+ } catch (Exception $e) {
+ static $restore_already_running;
+
+ // In case of error, make an attempt to restore the old provider. Since that's calling into
+ // this function again and can fail, we should be sure not to get into an infinite recursion.
+ if (!$restore_already_running) {
+ $restore_already_running = true;
+
+ // Make sure new provider is not in the database
+ try {
+ module::uninstall($new_provider);
+ } catch (Exception $e2) {
+ Kohana_Log::add("error", "Error uninstalling failed new provider\n" .
+ $e2->getMessage() . "\n" . $e2->getTraceAsString());
+ }
+
+ try {
+ // Lets reset to the current provider so that the gallery installation is still
+ // working.
+ module::set_var("gallery", "identity_provider", null);
+ IdentityProvider::change_provider($current_provider);
+ module::activate($current_provider);
+ } catch (Exception $e2) {
+ Kohana_Log::add("error", "Error restoring original identity provider\n" .
+ $e2->getMessage() . "\n" . $e2->getTraceAsString());
+ }
+
+ message::error(
+ t("Error attempting to enable \"%new_provider\" identity provider, reverted to \"%old_provider\" identity provider",
+ array("new_provider" => $new_provider, "old_provider" => $current_provider)));
+
+ $restore_already_running = false;
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * Loads the configured driver and validates it.
+ *
+ * @return void
+ */
+ public function __construct($config=null) {
+ if (empty($config)) {
+ $config = module::get_var("gallery", "identity_provider", "user");
+ }
+
+ // Test the config group name
+ if (($this->config = Kohana::config("identity." . $config)) === NULL) {
+ throw new Exception("@todo NO_USER_LIBRARY_CONFIGURATION_FOR: $config");
+ }
+
+ // Set driver name
+ $driver = "IdentityProvider_" . ucfirst($this->config["driver"]) ."_Driver";
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver)) {
+ throw new Kohana_Exception("core.driver_not_found", $this->config["driver"],
+ get_class($this));
+ }
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config["params"]);
+
+ // Validate the driver
+ if ( !($this->driver instanceof IdentityProvider_Driver)) {
+ throw new Kohana_Exception("core.driver_implements", $this->config["driver"],
+ get_class($this), "IdentityProvider_Driver");
+ }
+
+ Kohana_Log::add("debug", "Identity Library initialized");
+ }
+
+ /**
+ * Determine if if the current driver supports updates.
+ *
+ * @return boolean true if the driver supports updates; false if read only
+ */
+ public function is_writable() {
+ return !empty($this->config["allow_updates"]);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::guest.
+ */
+ public function guest() {
+ return $this->driver->guest();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::admin_user.
+ */
+ public function admin_user() {
+ return $this->driver->admin_user();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::create_user.
+ */
+ public function create_user($name, $full_name, $password, $email) {
+ return $this->driver->create_user($name, $full_name, $password, $email);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::is_correct_password.
+ */
+ public function is_correct_password($user, $password) {
+ return $this->driver->is_correct_password($user, $password);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_user.
+ */
+ public function lookup_user($id) {
+ return $this->driver->lookup_user($id);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_user_by_name.
+ */
+ public function lookup_user_by_name($name) {
+ return $this->driver->lookup_user_by_name($name);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::create_group.
+ */
+ public function create_group($name) {
+ return $this->driver->create_group($name);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::everybody.
+ */
+ public function everybody() {
+ return $this->driver->everybody();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::registered_users.
+ */
+ public function registered_users() {
+ return $this->driver->registered_users();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_group.
+ */
+ public function lookup_group($id) {
+ return $this->driver->lookup_group($id);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::lookup_group_by_name.
+ */
+ public function lookup_group_by_name($name) {
+ return $this->driver->lookup_group_by_name($name);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::get_user_list.
+ */
+ public function get_user_list($ids) {
+ return $this->driver->get_user_list($ids);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::groups.
+ */
+ public function groups() {
+ return $this->driver->groups();
+ }
+
+ /**
+ * @see IdentityProvider_Driver::add_user_to_group.
+ */
+ public function add_user_to_group($user, $group) {
+ return $this->driver->add_user_to_group($user, $group);
+ }
+
+ /**
+ * @see IdentityProvider_Driver::remove_user_to_group.
+ */
+ public function remove_user_from_group($user, $group) {
+ return $this->driver->remove_user_from_group($user, $group);
+ }
+} // End Identity
diff --git a/modules/gallery/libraries/InPlaceEdit.php b/modules/gallery/libraries/InPlaceEdit.php
new file mode 100644
index 0000000..cd177c2
--- /dev/null
+++ b/modules/gallery/libraries/InPlaceEdit.php
@@ -0,0 +1,91 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 InPlaceEdit_Core {
+ private $rules = array();
+ private $messages = array();
+ private $callback = array();
+ private $initial_value;
+ private $action = "";
+ private $errors;
+ private $form;
+
+ static function factory($initial_value) {
+ $instance = new InPlaceEdit();
+ $instance->initial_value = $initial_value;
+ $instance->form = array("input" => $initial_value);
+ $instance->errors = array("input" => "");
+
+ return $instance;
+ }
+
+ public function action($action) {
+ $this->action = $action;
+ return $this;
+ }
+
+ public function rules($rules) {
+ $this->rules += $rules;
+ return $this;
+ }
+
+ public function messages($messages) {
+ $this->messages += $messages;
+ return $this;
+ }
+
+ public function callback($callback) {
+ $this->callback = $callback;
+ return $this;
+ }
+
+ public function validate() {
+ $post = Validation::factory($_POST);
+
+ if (!empty($this->callback)) {
+ $post->add_callbacks("input", $this->callback);
+ }
+
+ foreach ($this->rules as $rule) {
+ $post->add_rules("input", $rule);
+ }
+
+ $valid = $post->validate();
+ $this->form = array_merge($this->form, $post->as_array());
+ $this->errors = array_merge($this->errors, $post->errors());
+ return $valid;
+ }
+
+ public function render() {
+ $v = new View("in_place_edit.html");
+ $v->action = $this->action;
+ $v->form = $this->form;
+ $v->errors = $this->errors;
+ foreach ($v->errors as $key => $error) {
+ if (!empty($error)) {
+ $v->errors[$key] = $this->messages[$error];
+ }
+ }
+ return $v->render();
+ }
+
+ public function value() {
+ return $this->form["input"];
+ }
+} \ 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 0000000..33759b6
--- /dev/null
+++ b/modules/gallery/libraries/MY_Database.php
@@ -0,0 +1,101 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+abstract class Database extends Database_Core {
+ protected $_table_names;
+
+ /**
+ * Kohana 2.4 introduces a new connection parameter. If it's not specified, make sure that we
+ * define it here to avoid an error later on.
+ *
+ * @todo: add an upgrade path to modify var/database.php so that we can avoid doing this at
+ * runtime.
+ */
+ protected function __construct(array $config) {
+ if (!isset($config["connection"]["params"])) {
+ $config["connection"]["params"] = null;
+ }
+ parent::__construct($config);
+ if (gallery::show_profiler()) {
+ $this->config['benchmark'] = true;
+ }
+ }
+
+ /**
+ * 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`";
+ } else if (strpos($sql, "RENAME TABLE") === 0) {
+ // Renaming a table; add it to the table cache.
+ // You must use the form "TO {new_table_name}" exactly for this to work.
+ $open_brace = strpos($sql, "TO {") + 4;
+ $close_brace = strpos($sql, "}", $open_brace);
+ $name = substr($sql, $open_brace, $close_brace - $open_brace);
+ $this->_table_names["{{$name}}"] = "`{$prefix}$name`";
+ }
+
+ if (!isset($this->_table_names)) {
+ // This should only run once on the first query
+ $this->_table_names = array();
+ foreach($this->list_tables() as $table_name) {
+ $this->_table_names["{{$table_name}}"] = "`{$prefix}{$table_name}`";
+ }
+ }
+
+ return strtr($sql, $this->_table_names);
+ }
+
+ /**
+ * This is used by the unit test code to switch the active database connection.
+ */
+ static function set_default_instance($db) {
+ self::$instances["default"] = $db;
+ }
+
+ /**
+ * Escape LIKE queries, add wildcards. In MySQL queries using LIKE, _ and % characters are
+ * treated as wildcards similar to ? and *, respectively. Therefore, we need to escape _, %,
+ * and \ (the escape character itself).
+ */
+ static function escape_for_like($value) {
+ // backslash must go first to avoid double-escaping
+ return addcslashes($value, '\_%');
+ }
+} \ 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 0000000..635dc2d
--- /dev/null
+++ b/modules/gallery/libraries/MY_Forge.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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(access::csrf_token());
+ }
+
+ /**
+ * Use our own template
+ */
+ public function render($template="form.html", $custom=false) {
+ return parent::render($template, $custom);
+ }
+
+ /**
+ * 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_Input.php b/modules/gallery/libraries/MY_Input.php
new file mode 100644
index 0000000..2f0a727
--- /dev/null
+++ b/modules/gallery/libraries/MY_Input.php
@@ -0,0 +1,31 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class Input extends Input_Core {
+ /**
+ * Modified form of Input::clean_input_keys() that replaces malformed values
+ * instead of dying on bad input.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public function clean_input_keys($str) {
+ return preg_replace('#[^a-zA-Z0-9:_.-]+#', '_', $str);
+ }
+}
diff --git a/modules/gallery/libraries/MY_Kohana_Exception.php b/modules/gallery/libraries/MY_Kohana_Exception.php
new file mode 100644
index 0000000..51490a6
--- /dev/null
+++ b/modules/gallery/libraries/MY_Kohana_Exception.php
@@ -0,0 +1,101 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Kohana_Exception extends Kohana_Exception_Core {
+ /**
+ * Dump out the full stack trace as part of the text representation of the exception.
+ */
+ public static function text($e) {
+ if ($e instanceof Kohana_404_Exception) {
+ return "File not found: " . rawurlencode(Router::$complete_uri);
+ } else {
+ return sprintf(
+ "%s [ %s ]: %s\n%s [ %s ]\n%s",
+ get_class($e), $e->getCode(), strip_tags($e->getMessage()),
+ $e->getFile(), $e->getLine(),
+ $e->getTraceAsString());
+ }
+ }
+
+ /**
+ * @see Kohana_Exception::dump()
+ */
+ public static function dump($value, $length=128, $max_level=5) {
+ return self::safe_dump($value, null, $length, $max_level);
+ }
+
+ /**
+ * A safer version of dump(), eliding sensitive information in the dumped
+ * data, such as session ids and passwords / hashes.
+ */
+ public static function safe_dump($value, $key, $length=128, $max_level=5) {
+ return parent::dump(self::_sanitize_for_dump($value, $key, $max_level), $length, $max_level);
+ }
+
+ /**
+ * Elides sensitive data which shouldn't be echoed to the client,
+ * such as passwords, and other secrets.
+ */
+ /* Visible for testing*/ static function _sanitize_for_dump($value, $key=null, $max_level) {
+ // Better elide too much than letting something through.
+ // Note: unanchored match is intended.
+ if (!$max_level) {
+ // Too much recursion; give up. We gave it our best shot.
+ return $value;
+ }
+
+ $sensitive_info_pattern =
+ '/(password|pass|email|hash|private_key|session_id|session|g3sid|csrf|secret)/i';
+ if (preg_match($sensitive_info_pattern, $key) ||
+ (is_string($value) && preg_match('/[a-f0-9]{20,}/i', $value))) {
+ return 'removed for display';
+ } else if (is_object($value)) {
+ if ($value instanceof Database) {
+ // Elide database password, host, name, user, etc.
+ return get_class($value) . ' object - details omitted for display';
+ } else if ($value instanceof User_Model) {
+ return get_class($value) . ' object for "' . $value->name . '" - details omitted for display';
+ }
+ return self::_sanitize_for_dump((array) $value, $key, $max_level - 1);
+ } else if (is_array($value)) {
+ $result = array();
+ foreach ($value as $k => $v) {
+ $actual_key = $k;
+ $key_for_display = $k;
+ if ($k[0] === "\x00") {
+ // Remove the access level from the variable name
+ $actual_key = substr($k, strrpos($k, "\x00") + 1);
+ $access = $k[1] === '*' ? 'protected' : 'private';
+ $key_for_display = "$access: $actual_key";
+ }
+ if (is_object($v)) {
+ $key_for_display .= ' (type: ' . get_class($v) . ')';
+ }
+ $result[$key_for_display] = self::_sanitize_for_dump($v, $actual_key, $max_level - 1);
+ }
+ } else {
+ $result = $value;
+ }
+ return $result;
+ }
+
+ public static function debug_path($file) {
+ return html::clean(parent::debug_path($file));
+ }
+} \ 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 0000000..6e538d5
--- /dev/null
+++ b/modules/gallery/libraries/MY_ORM.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+
+ /**
+ * Make sure that we're only using integer ids.
+ */
+ static function factory($model, $id=null) {
+ if ($id && !is_int($id) && !is_string($id)) {
+ throw new Exception("@todo ORM::factory requires integer ids");
+ }
+ return ORM_Core::factory($model, (int) $id);
+ }
+
+ public function save() {
+ model_cache::clear();
+ return parent::save();
+ }
+}
+
+/**
+ * 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_View.php b/modules/gallery/libraries/MY_View.php
new file mode 100644
index 0000000..1e88611
--- /dev/null
+++ b/modules/gallery/libraries/MY_View.php
@@ -0,0 +1,85 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 {
+ static $global_data = array();
+
+ /**
+ * Reimplement Kohana 2.3's View::set_global() functionality.
+ */
+ public function set_global($key, $value = NULL) {
+ if (is_array($key)) {
+ foreach ($key as $key2 => $value) {
+ View::$global_data[$key2] = $value;
+ }
+ } else {
+ View::$global_data[$key] = $value;
+ }
+ }
+
+ public function is_set($key=null) {
+ return parent::is_set($key) ? true : array_key_exists($key, View::$global_data);
+ }
+
+ /**
+ * Completely replace View_Core::__get() so that local data trumps global data, trumps members.
+ * This simulates the Kohana 2.3 behavior.
+ */
+ public function &__get($key) {
+ if (isset($this->kohana_local_data[$key])) {
+ return $this->kohana_local_data[$key];
+ } else if (isset(View::$global_data[$key])) {
+ return View::$global_data[$key];
+ } else if (isset($this->$key)) {
+ return $this->$key;
+ } else {
+ throw new Kohana_Exception('Undefined view variable: :var', array(':var' => $key));
+ }
+ }
+
+ /**
+ * 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, $modifier=false) {
+ try {
+ $this->kohana_local_data = array_merge(View::$global_data, $this->kohana_local_data);
+ return parent::render($print, $renderer, $modifier);
+ } catch (ORM_Validation_Exception $e) {
+ Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
+ Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1));
+ return "";
+ } catch (Exception $e) {
+ Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
+ return "";
+ }
+ }
+}
diff --git a/modules/gallery/libraries/Menu.php b/modules/gallery/libraries/Menu.php
new file mode 100644
index 0000000..24a05cd
--- /dev/null
+++ b/modules/gallery/libraries/Menu.php
@@ -0,0 +1,257 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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;
+ public $type;
+
+ public function __construct($type) {
+ $this->type = $type;
+ }
+
+ /**
+ * Set the id
+ * @chainable
+ */
+ public function id($id) {
+ $this->id = $id;
+ return $this;
+ }
+
+ /**
+ * Set the label
+ * @chainable
+ */
+ public function label($label) {
+ // Guard against developers who forget to internationalize label strings
+ if (!($label instanceof SafeString)) {
+ $label = new SafeString($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;
+ }
+
+ /**
+ * Specifiy a view for this menu item
+ * @chainable
+ */
+ public function view($view) {
+ $this->view = $view;
+ return $this;
+ }
+
+}
+
+/**
+ * Menu element that provides a link to a new page.
+ */
+class Menu_Element_Link extends Menu_Element {
+ public function render() {
+ $view = new View(isset($this->view) ? $this->view : "menu_link.html");
+ $view->menu = $this;
+ return $view;
+ }
+}
+
+/**
+ * Menu element that provides an AJAX link.
+ */
+class Menu_Element_Ajax_Link extends Menu_Element {
+ public $ajax_handler;
+
+ /**
+ * Set the AJAX handler
+ * @chainable
+ */
+ public function ajax_handler($ajax_handler) {
+ $this->ajax_handler = $ajax_handler;
+ return $this;
+ }
+
+ public function render() {
+ $view = new View(isset($this->view) ? $this->view : "menu_ajax_link.html");
+ $view->menu = $this;
+ return $view;
+ }
+}
+
+/**
+ * Menu element that provides a pop-up dialog
+ */
+class Menu_Element_Dialog extends Menu_Element {
+ public function render() {
+ $view = new View(isset($this->view) ? $this->view : "menu_dialog.html");
+ $view->menu = $this;
+ return $view;
+ }
+}
+
+/**
+ * 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($type);
+
+ case "ajax_link":
+ return new Menu_Element_Ajax_Link($type);
+
+ case "dialog":
+ return new Menu_Element_Dialog($type);
+
+ case "root":
+ $menu = new Menu("root");
+ $menu->css_class("g-menu");
+ return $menu;
+
+ case "submenu":
+ return new Menu("submenu");
+
+ default:
+ throw Exception("@todo UNKNOWN_MENU_TYPE");
+ }
+ }
+
+ public function __construct($type) {
+ parent::__construct($type);
+ $this->elements = array();
+ $this->is_root = $type == "root";
+ }
+
+ /**
+ * 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, after the specific element
+ */
+ 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;
+ }
+
+ /**
+ * Add a new element to this menu, before the specific element
+ */
+ public function add_before($target_id, $new_menu_element) {
+ $copy = array();
+ foreach ($this->elements as $id => $menu_element) {
+ if ($id == $target_id) {
+ $copy[$new_menu_element->id] = $new_menu_element;
+ }
+ $copy[$id] = $menu_element;
+ }
+ $this->elements = $copy;
+ return $this;
+ }
+
+ /**
+ * Remove an element from the menu
+ */
+ public function remove($target_id) {
+ unset($this->elements[$target_id]);
+ }
+
+ /**
+ * Retrieve a Menu_Element by id
+ */
+ public function &get($id) {
+ if (array_key_exists($id, $this->elements)) {
+ return $this->elements[$id];
+ }
+
+ $null = null;
+ return $null;
+ }
+
+ public function is_empty() {
+ foreach ($this->elements as $element) {
+ if ($element instanceof Menu) {
+ if (!$element->is_empty()) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function render() {
+ $view = new View(isset($this->view) ? $this->view : "menu.html");
+ $view->menu = $this;
+ return $view;
+ }
+
+ static function title_comparator($a, $b) {
+ return strnatcasecmp((string)$a->label, (string)$b->label);
+ }
+}
diff --git a/modules/gallery/libraries/ORM_MPTT.php b/modules/gallery/libraries/ORM_MPTT.php
new file mode 100644
index 0000000..0ad8133
--- /dev/null
+++ b/modules/gallery/libraries/ORM_MPTT.php
@@ -0,0 +1,341 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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);
+ }
+
+ /**
+ * Overload ORM::save() to update the MPTT tree when we add new items to the hierarchy.
+ *
+ * @chainable
+ * @return ORM
+ */
+ function save() {
+ if (!$this->loaded()) {
+ $this->lock();
+ $parent = ORM::factory("item", $this->parent_id);
+
+ try {
+ // Make a hole in the parent for this new item
+ db::build()
+ ->update($this->table_name)
+ ->set("left_ptr", db::expr("`left_ptr` + 2"))
+ ->where("left_ptr", ">=", $parent->right_ptr)
+ ->execute();
+ db::build()
+ ->update($this->table_name)
+ ->set("right_ptr", db::expr("`right_ptr` + 2"))
+ ->where("right_ptr", ">=", $parent->right_ptr)
+ ->execute();
+ $parent->right_ptr += 2;
+
+ // Insert this item into the hole
+ $this->left_ptr = $parent->right_ptr - 2;
+ $this->right_ptr = $parent->right_ptr - 1;
+ $this->parent_id = $parent->id;
+ $this->level = $parent->level + 1;
+ } catch (Exception $e) {
+ $this->unlock();
+ throw $e;
+ }
+ parent::save();
+ $this->unlock();
+ } else {
+ parent::save();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Delete this node and all of its children.
+ */
+ public function delete($ignored_id=null) {
+ $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_ptr/right_ptr pointers. This is inefficient.
+ // @todo load each child once, not twice.
+ set_time_limit(30);
+ $item->reload()->delete();
+ }
+
+ // Deleting children has affected this item, but we'll reload it below.
+ }
+
+ $this->lock();
+ $this->reload(); // Assume that the prior lock holder may have changed this entry
+ if (!$this->loaded()) {
+ // Concurrent deletes may result in this item already being gone. Ignore it.
+ return;
+ }
+
+ try {
+ db::build()
+ ->update($this->table_name)
+ ->set("left_ptr", db::expr("`left_ptr` - 2"))
+ ->where("left_ptr", ">", $this->right_ptr)
+ ->execute();
+ db::build()
+ ->update($this->table_name)
+ ->set("right_ptr", db::expr("`right_ptr` - 2"))
+ ->where("right_ptr", ">", $this->right_ptr)
+ ->execute();
+ } 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 contains($target) {
+ return ($this->left_ptr <= $target->left_ptr && $this->right_ptr >= $target->right_ptr);
+ }
+
+ /**
+ * 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($where=null) {
+ return $this
+ ->merge_where($where)
+ ->where("left_ptr", "<=", $this->left_ptr)
+ ->where("right_ptr", ">=", $this->right_ptr)
+ ->where("id", "<>", $this->id)
+ ->order_by("left_ptr", "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 additional where clauses
+ * @param array order_by
+ * @return array ORM
+ */
+ function children($limit=null, $offset=null, $where=null, $order_by=array("id" => "ASC")) {
+ return $this
+ ->merge_where($where)
+ ->where("parent_id", "=", $this->id)
+ ->order_by($order_by)
+ ->find_all($limit, $offset);
+ }
+
+ /**
+ * Return the number of children of this node.
+ *
+ * @chainable
+ * @param array additional where clauses
+ * @return array ORM
+ */
+ function children_count($where=null) {
+ return $this
+ ->merge_where($where)
+ ->where("parent_id", "=", $this->id)
+ ->count_all();
+ }
+
+ /**
+ * Return all of the decendents of the specified type, ordered by id.
+ *
+ * @param integer SQL limit
+ * @param integer SQL offset
+ * @param array additional where clauses
+ * @param array order_by
+ * @return object ORM_Iterator
+ */
+ function descendants($limit=null, $offset=null, $where=null, $order_by=array("id" => "ASC")) {
+ return $this
+ ->merge_where($where)
+ ->where("left_ptr", ">", $this->left_ptr)
+ ->where("right_ptr", "<=", $this->right_ptr)
+ ->order_by($order_by)
+ ->find_all($limit, $offset);
+ }
+
+ /**
+ * Return the count of all the children of the specified type.
+ *
+ * @param array additional where clauses
+ * @return integer child count
+ */
+ function descendants_count($where=null) {
+ return $this
+ ->merge_where($where)
+ ->where("left_ptr", ">", $this->left_ptr)
+ ->where("right_ptr", "<=", $this->right_ptr)
+ ->count_all();
+ }
+
+ /**
+ * Move this item to the specified target.
+ *
+ * @chainable
+ * @param Item_Model $target Target node
+ * @return ORM_MTPP
+ */
+ protected function move_to($target) {
+ if ($this->contains($target)) {
+ throw new Exception("@todo INVALID_TARGET can't move item inside itself");
+ }
+
+ $this->lock();
+ $this->reload(); // Assume that the prior lock holder may have changed this entry
+ $target->reload();
+
+ $number_to_move = (int)(($this->right_ptr - $this->left_ptr) / 2 + 1);
+ $size_of_hole = $number_to_move * 2;
+ $original_left_ptr = $this->left_ptr;
+ $original_right_ptr = $this->right_ptr;
+ $target_right_ptr = $target->right_ptr;
+ $level_delta = ($target->level + 1) - $this->level;
+
+ try {
+ if ($level_delta) {
+ // Update the levels for the to-be-moved items
+ db::build()
+ ->update($this->table_name)
+ ->set("level", db::expr("`level` + $level_delta"))
+ ->where("left_ptr", ">=", $original_left_ptr)
+ ->where("right_ptr", "<=", $original_right_ptr)
+ ->execute();
+ }
+
+ // Make a hole in the target for the move
+ db::build()
+ ->update($this->table_name)
+ ->set("left_ptr", db::expr("`left_ptr` + $size_of_hole"))
+ ->where("left_ptr", ">=", $target_right_ptr)
+ ->execute();
+ db::build()
+ ->update($this->table_name)
+ ->set("right_ptr", db::expr("`right_ptr` + $size_of_hole"))
+ ->where("right_ptr", ">=", $target_right_ptr)
+ ->execute();
+
+ // Change the parent.
+ db::build()
+ ->update($this->table_name)
+ ->set("parent_id", $target->id)
+ ->where("id", "=", $this->id)
+ ->execute();
+
+ // If the source is to the right of the target then we just adjusted its left_ptr and
+ // right_ptr above.
+ $left_ptr = $original_left_ptr;
+ $right_ptr = $original_right_ptr;
+ if ($original_left_ptr > $target_right_ptr) {
+ $left_ptr += $size_of_hole;
+ $right_ptr += $size_of_hole;
+ }
+
+ $new_offset = $target->right_ptr - $left_ptr;
+ db::build()
+ ->update($this->table_name)
+ ->set("left_ptr", db::expr("`left_ptr` + $new_offset"))
+ ->set("right_ptr", db::expr("`right_ptr` + $new_offset"))
+ ->where("left_ptr", ">=", $left_ptr)
+ ->where("right_ptr", "<=", $right_ptr)
+ ->execute();
+
+ // Close the hole in the source's parent after the move
+ db::build()
+ ->update($this->table_name)
+ ->set("left_ptr", db::expr("`left_ptr` - $size_of_hole"))
+ ->where("left_ptr", ">", $right_ptr)
+ ->execute();
+ db::build()
+ ->update($this->table_name)
+ ->set("right_ptr", db::expr("`right_ptr` - $size_of_hole"))
+ ->where("right_ptr", ">", $right_ptr)
+ ->execute();
+ } catch (Exception $e) {
+ $this->unlock();
+ throw $e;
+ }
+
+ $this->unlock();
+
+ // Lets reload to get the changes.
+ $this->reload();
+ $target->reload();
+ return $this;
+ }
+
+ /**
+ * Lock the tree to prevent concurrent modification.
+ */
+ protected function lock() {
+ $timeout = module::get_var("gallery", "lock_timeout", 1);
+ $result = $this->db->query("SELECT GET_LOCK('{$this->table_name}', $timeout) 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/SafeString.php b/modules/gallery/libraries/SafeString.php
new file mode 100644
index 0000000..179cbd4
--- /dev/null
+++ b/modules/gallery/libraries/SafeString.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+
+/**
+ * Safe string representation (regarding security - cross site scripting).
+ */
+class SafeString_Core {
+ private $_raw_string;
+ protected $_is_safe_html = false;
+
+ /** Constructor */
+ function __construct($string) {
+ if ($string instanceof SafeString) {
+ $this->_is_safe_html = $string->_is_safe_html;
+ $string = $string->unescaped();
+ }
+ $this->_raw_string = (string) $string;
+ }
+
+ /**
+ * Factory method returning a new SafeString instance for the given string.
+ */
+ static function of($string) {
+ return new SafeString($string);
+ }
+
+ /**
+ * Factory method returning a new SafeString instance after HTML purifying
+ * the given string.
+ */
+ static function purify($string) {
+ if ($string instanceof SafeString) {
+ if ($string->_is_safe_html) {
+ return $string;
+ } else {
+ $string = $string->unescaped();
+ }
+ }
+ $safe_string = self::of_safe_html(self::_purify_for_html($string));
+ return $safe_string;
+ }
+
+ /**
+ * Factory method returning a new SafeString instance which won't HTML escape.
+ */
+ static function of_safe_html($string) {
+ $safe_string = new SafeString($string);
+ $safe_string->_is_safe_html = true;
+ return $safe_string;
+ }
+
+ /**
+ * Safe for use in HTML.
+ * @see #for_html()
+ */
+ function __toString() {
+ if ($this->_is_safe_html) {
+ return $this->_raw_string;
+ } else {
+ return self::_escape_for_html($this->_raw_string);
+ }
+ }
+
+ /**
+ * Safe for use in HTML.
+ *
+ * Example:<pre>
+ * <div><?= $php_var ?>
+ * </pre>
+ * @return the string escaped for use in HTML.
+ */
+ function for_html() {
+ return $this;
+ }
+
+ /**
+ * Safe for use as JavaScript string.
+ *
+ * Example:<pre>
+ * <script type="text/javascript>"
+ * var some_js_var = <?= $php_var->for_js() ?>;
+ * </script>
+ * </pre>
+ * @return the string escaped for use in JavaScript.
+ */
+ function for_js() {
+ return json_encode((string) $this->_raw_string);
+ }
+
+ /**
+ * Safe for use in HTML element attributes.
+ *
+ * Assumes that the HTML element attribute is already
+ * delimited by single or double quotes
+ *
+ * Example:<pre>
+ * <a title="<?= $php_var->for_html_attr() ?>">;
+ * </script>
+ * </pre>
+ * @return the string escaped for use in HTML attributes.
+ */
+ function for_html_attr() {
+ $string = (string) $this->for_html();
+ return strtr($string,
+ array("'"=>"&#039;",
+ '"'=>'&quot;'));
+ }
+
+ /**
+ * Safe for use HTML (purified HTML)
+ *
+ * Example:<pre>
+ * <div><?= $php_var->purified_html() ?>
+ * </pre>
+ * @return the string escaped for use in HTML.
+ */
+ function purified_html() {
+ return self::purify($this);
+ }
+
+ /**
+ * Returns the raw, unsafe string. Do not use lightly.
+ */
+ function unescaped() {
+ return $this->_raw_string;
+ }
+
+ /**
+ * Escape special HTML chars ("<", ">", "&", etc.) to HTML entities.
+ */
+ private static function _escape_for_html($dirty_html) {
+ return html::chars($dirty_html);
+ }
+
+ /**
+ * Purify the string, removing any potentially malicious or unsafe HTML / JavaScript.
+ */
+ private static function _purify_for_html($dirty_html) {
+ if (class_exists("purifier") && method_exists("purifier", "purify")) {
+ return purifier::purify($dirty_html);
+ } else {
+ return self::_escape_for_html($dirty_html);
+ }
+ }
+}
diff --git a/modules/gallery/libraries/Sendmail.php b/modules/gallery/libraries/Sendmail.php
new file mode 100644
index 0000000..69a7c32
--- /dev/null
+++ b/modules/gallery/libraries/Sendmail.php
@@ -0,0 +1,98 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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();
+ $this->from(module::get_var("gallery", "email_from", ""));
+ $this->reply_to(module::get_var("gallery", "email_reply_to", ""));
+ $this->line_length(module::get_var("gallery", "email_line_length", 70));
+ $separator = module::get_var("gallery", "email_header_separator", null);
+ $this->header_separator(empty($separator) ? "\n" : unserialize($separator));
+ }
+
+ 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) {
+ Kohana_Log::add("error", wordwrap("Invalid header parameters\n" . Kohana::debug($value)));
+ 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)) {
+ Kohana_Log::add("error", wordwrap("Sending mail failed:\nNo to address specified"));
+ 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)) {
+ 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 0000000..f695fe3
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 0000000..9118375
--- /dev/null
+++ b/modules/gallery/libraries/Theme_View.php
@@ -0,0 +1,271 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Gallery_View {
+ /**
+ * 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: collection, item, or other
+ * @param string $page_subtype page sub type: album, photo, tags, etc
+ * @param string $theme_name view name
+ * @return void
+ */
+ public function __construct($name, $page_type, $page_subtype) {
+ parent::__construct($name);
+
+ $this->theme_name = module::get_var("gallery", "active_site_theme");
+ if (identity::active_user()->admin) {
+ $theme_name = Input::instance()->get("theme");
+ if ($theme_name &&
+ file_exists(THEMEPATH . $theme_name) &&
+ strpos(realpath(THEMEPATH . $theme_name), THEMEPATH) == 0) {
+ $this->theme_name = $theme_name;
+ }
+ }
+ $this->item = null;
+ $this->tag = null;
+ $this->set_global(array("theme" => $this,
+ "theme_info" => theme::get_info($this->theme_name),
+ "user" => identity::active_user(),
+ "page_type" => $page_type,
+ "page_subtype" => $page_subtype,
+ "page_title" => null));
+
+ if (module::get_var("gallery", "maintenance_mode", 0)) {
+ if (identity::active_user()->admin) {
+ message::warning(t("This site is currently in maintenance mode. Visit the <a href=\"%maintenance_url\">maintenance page</a>", array("maintenance_url" => url::site("admin/maintenance"))));
+ } else
+ message::warning(t("This site is currently in maintenance mode."));
+ }
+ }
+
+ /**
+ * Proportion of the current thumb_size's to default
+ * @param object Item_Model (optional) check the proportions for this item
+ * @return int
+ */
+ public function thumb_proportion($item=null) {
+ // If the item is an album with children, grab the first item in that album instead. We're
+ // interested in the size of the thumbnails in this album, not the thumbnail of the
+ // album itself.
+ if ($item && $item->is_album() && $item->children_count()) {
+ $item = $item->children(1)->current();
+ }
+
+ // By default we have a globally fixed thumbnail size In core code, we just return a fixed
+ // proportion based on the global thumbnail size, but since modules can override that, we
+ // return the actual proportions when we have them.
+ if ($item && $item->has_thumb()) {
+ return max($item->thumb_width, $item->thumb_height) / 200;
+ } else {
+ // @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("gallery", "thumb_size", 200) / 200;
+ }
+ }
+
+ public function item() {
+ return $this->item;
+ }
+
+ public function siblings($limit=null, $offset=null) {
+ return call_user_func_array(
+ $this->siblings_callback[0],
+ array_merge($this->siblings_callback[1], array($limit, $offset)));
+ }
+
+ public function tag() {
+ return $this->tag;
+ }
+
+ public function page_type() {
+ return $this->page_type;
+ }
+
+ public function page_subtype() {
+ return $this->page_subtype;
+ }
+
+ public function user_menu() {
+ $menu = Menu::factory("root")
+ ->css_id("g-login-menu")
+ ->css_class("g-inline ui-helper-clear-fix");
+ module::event("user_menu", $menu, $this);
+ return $menu->render();
+ }
+
+ public function site_menu($item_css_selector) {
+ $menu = Menu::factory("root");
+ module::event("site_menu", $menu, $this, $item_css_selector);
+ return $menu->render();
+ }
+
+ public function album_menu() {
+ $menu = Menu::factory("root");
+ module::event("album_menu", $menu, $this);
+ return $menu->render();
+ }
+
+ public function tag_menu() {
+ $menu = Menu::factory("root");
+ module::event("tag_menu", $menu, $this);
+ return $menu->render();
+ }
+
+ public function photo_menu() {
+ $menu = Menu::factory("root");
+ if (access::can("view_full", $this->item())) {
+ $menu->append(Menu::factory("link")
+ ->id("fullsize")
+ ->label(t("View full size"))
+ ->url($this->item()->file_url())
+ ->css_class("g-fullsize-link"));
+ }
+
+ module::event("photo_menu", $menu, $this);
+ return $menu->render();
+ }
+
+ public function movie_menu() {
+ $menu = Menu::factory("root");
+ module::event("movie_menu", $menu, $this);
+ return $menu->render();
+ }
+
+ public function context_menu($item, $thumbnail_css_selector) {
+ $menu = Menu::factory("root")
+ ->append(Menu::factory("submenu")
+ ->id("context_menu")
+ ->label(t("Options")))
+ ->css_class("g-context-menu");
+
+ module::event("context_menu", $menu, $this, $item, $thumbnail_css_selector);
+ return $menu->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();
+ }
+
+ /**
+ * Print out the sidebar.
+ */
+ public function sidebar_blocks() {
+ $sidebar = block_manager::get_html("site_sidebar", $this);
+ if (empty($sidebar) && identity::active_user()->admin) {
+ $sidebar = new View("no_sidebar.html");
+ }
+ return $sidebar;
+ }
+
+ /**
+ * 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 "body_attributes":
+ case "credits";
+ case "dynamic_bottom":
+ case "dynamic_top":
+ case "footer":
+ case "head":
+ case "header_bottom":
+ case "header_top":
+ case "html_attributes":
+ case "page_bottom":
+ case "page_top":
+ case "photo_blocks":
+ case "photo_bottom":
+ case "photo_top":
+ case "resize_bottom":
+ case "resize_top":
+ case "sidebar_bottom":
+ case "sidebar_top":
+ case "thumb_bottom":
+ case "thumb_info":
+ case "thumb_top":
+ $blocks = array();
+ if (method_exists("gallery_theme", $function)) {
+ switch (count($args)) {
+ case 0:
+ $blocks[] = gallery_theme::$function($this);
+ break;
+ case 1:
+ $blocks[] = gallery_theme::$function($this, $args[0]);
+ break;
+ case 2:
+ $blocks[] = gallery_theme::$function($this, $args[0], $args[1]);
+ break;
+ default:
+ $blocks[] = call_user_func_array(
+ array("gallery_theme", $function),
+ array_merge(array($this), $args));
+ }
+ }
+
+ foreach (module::active() as $module) {
+ if ($module->name == "gallery") {
+ continue;
+ }
+ $helper_class = "{$module->name}_theme";
+ if (class_exists($helper_class) && method_exists($helper_class, $function)) {
+ $blocks[] = call_user_func_array(
+ array($helper_class, $function),
+ array_merge(array($this), $args));
+ }
+ }
+
+ $helper_class = theme::$site_theme_name . "_theme";
+ if (class_exists($helper_class) && 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" && $function != "body_attributes") {
+ array_unshift(
+ $blocks,
+ "<div class=\"g-annotated-theme-block g-annotated-theme-block_$function g-clear-fix\">" .
+ "<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/drivers/Cache/Database.php b/modules/gallery/libraries/drivers/Cache/Database.php
new file mode 100644
index 0000000..8790d0e
--- /dev/null
+++ b/modules/gallery/libraries/drivers/Cache/Database.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+/*
+ * Based on the Cache_Sqlite_Driver developed by the Kohana Team
+ */
+class Cache_Database_Driver extends Cache_Driver {
+ // Kohana database instance
+ protected $db;
+
+ /**
+ * Sets a cache item to the given data, tags, and lifetime.
+ *
+ * @param array assoc array of key => value pairs
+ * @param array cache tags
+ * @param integer lifetime
+ * @return bool
+ */
+ public function set($items, $tags=null, $lifetime=null) {
+ if (!empty($tags)) {
+ // Escape the tags, adding brackets so the tag can be explicitly matched
+ $tags = "<" . implode(">,<", $tags) . ">";
+ } else {
+ $tags = null;
+ }
+
+ // Cache Database driver expects unix timestamp
+ if ($lifetime !== 0) {
+ $lifetime += time();
+ }
+
+ $db = Database::instance();
+ $tags = $db->escape($tags);
+ foreach ($items as $id => $data) {
+ $id = $db->escape($id);
+ $data = $db->escape(serialize($data));
+ $db->query("INSERT INTO {caches} (`key`, `tags`, `expiration`, `cache`)
+ VALUES ('$id', '$tags', $lifetime, '$data')
+ ON DUPLICATE KEY UPDATE `tags` = VALUES(tags), `expiration` = VALUES(expiration),
+ `cache` = VALUES(cache)");
+ }
+
+ return true;
+ }
+
+ /**
+ * Get cache items by tag
+ * @param array cache tags
+ * @return array cached data
+ */
+ public function get_tag($tags) {
+ $db = db::build()
+ ->select()
+ ->from("caches");
+ foreach ($tags as $tag) {
+ $db->where("tags", "LIKE", "%" . Database::escape_for_like("<$tag>") . "%");
+ }
+ $db_result = $db->execute();
+
+ // An array will always be returned
+ $result = array();
+
+ // Disable notices for unserializing
+ $ER = error_reporting(~E_NOTICE);
+ if ($db_result->count() > 0) {
+ foreach ($db_result as $row) {
+ // Add each cache to the array
+ $result[$row->key] = unserialize($row->cache);
+ }
+ }
+ error_reporting($ER);
+
+ return $result;
+ }
+
+ /**
+ * Fetches a cache item. This will delete the item if it is expired or if
+ * the hash does not match the stored hash.
+ *
+ * @param string cache id
+ * @return mixed|NULL
+ */
+ public function get($keys, $single=false) {
+ $data = null;
+ $result = db::build()
+ ->select()
+ ->from("caches")
+ ->where("key", "IN", $keys)
+ ->execute();
+
+ if (count($result) > 0) {
+ $cache = $result->current();
+ // Make sure the expiration is valid and that the hash matches
+ if ($cache->expiration != 0 && $cache->expiration <= time()) {
+ // Cache is not valid, delete it now
+ $this->delete(array($cache->id));
+ } else {
+ // Disable notices for unserializing
+ $ER = error_reporting(~E_NOTICE);
+
+ // Return the valid cache data
+ $data = unserialize($cache->cache);
+
+ // Turn notices back on
+ error_reporting($ER);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Deletes a cache item by id or tag
+ *
+ * @param string cache id or tag, or true for "all items"
+ * @param bool delete a tag
+ * @return bool
+ */
+ public function delete($keys, $is_tag=false) {
+ $db = db::build()
+ ->delete("caches");
+ if ($keys === true) {
+ // Delete all caches
+ } else if ($is_tag === true) {
+ foreach ($keys as $tag) {
+ $db->where("tags", "LIKE", "%" . Database::escape_for_like("<$tag>") . "%");
+ }
+ } else {
+ $db->where("key", "IN", $keys);
+ }
+
+ $status = $db->execute();
+
+ return count($status) > 0;
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags) {
+ return $this->delete($tags, true);
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all() {
+ Database::instance()->query("TRUNCATE {caches}");
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/libraries/drivers/IdentityProvider.php b/modules/gallery/libraries/drivers/IdentityProvider.php
new file mode 100644
index 0000000..5256236
--- /dev/null
+++ b/modules/gallery/libraries/drivers/IdentityProvider.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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.
+ */
+interface IdentityProvider_Driver {
+ /**
+ * Return the guest user.
+ *
+ * @return User_Definition the user object
+ */
+ public function guest();
+
+ /**
+ * Return the primary admin user.
+ *
+ * @return User_Definition the user object
+ */
+ public function admin_user();
+
+ /**
+ * Create a new user.
+ *
+ * @param string $name
+ * @param string $full_name
+ * @param string $password
+ * @param string $email
+ * @return User_Definition the user object
+ */
+ public function create_user($name, $full_name, $password, $email);
+
+ /**
+ * Is the password provided correct?
+ *
+ * @param user User_Definition the user object
+ * @param string $password a plaintext password
+ * @return boolean true if the password is correct
+ */
+ public function is_correct_password($user, $password);
+
+ /**
+ * Look up a user by id.
+ * @param integer $id
+ * @return User_Definition the user object, or null if the name was invalid.
+ */
+ public function lookup_user($id);
+
+ /**
+ * Look up a user by name.
+ * @param string $name
+ * @return User_Definition the user object, or null if the name was invalid.
+ */
+ public function lookup_user_by_name($name);
+
+ /**
+ * Create a new group.
+ *
+ * @param string $name
+ * @return Group_Definition the group object
+ */
+ public function create_group($name);
+
+ /**
+ * The group of all possible visitors. This includes the guest user.
+ *
+ * @return Group_Definition the group object
+ */
+ public function everybody();
+
+ /**
+ * The group of all logged-in visitors. This does not include guest users.
+ *
+ * @return Group_Definition the group object
+ */
+ public function registered_users();
+
+ /**
+ * List the users
+ * @param array $ids array of ids to return the user objects for
+ * @return array the user list.
+ */
+ public function get_user_list($ids);
+
+ /**
+ * Look up a group by id.
+ * @param integer $id id
+ * @return Group_Definition the user object, or null if the name was invalid.
+ */
+ public function lookup_group($id);
+
+ /**
+ * Look up the group by name.
+ * @param string $name the name of the group to locate
+ * @return Group_Definition
+ */
+ public function lookup_group_by_name($name);
+
+ /**
+ * List the groups defined in the Identity Provider
+ */
+ public function groups();
+
+ /**
+ * Add the user to the specified group
+ * @param User_Definition the user to add
+ * @param Group_Definition the target group
+ */
+ public function add_user_to_group($user, $group);
+
+ /**
+ * Remove the user to the specified group
+ * @param User_Definition the user to remove
+ * @param Group_Definition the owning group
+ */
+ public function remove_user_from_group($user, $group);
+} // End Identity Driver Definition
+
+interface Group_Definition {}
+
+interface User_Definition {}
diff --git a/modules/gallery/models/access_cache.php b/modules/gallery/models/access_cache.php
new file mode 100644
index 0000000..b0c0843
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/access_intent.php b/modules/gallery/models/access_intent.php
new file mode 100644
index 0000000..56f9666
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/cache.php b/modules/gallery/models/cache.php
new file mode 100644
index 0000000..37d5265
--- /dev/null
+++ b/modules/gallery/models/cache.php
@@ -0,0 +1,20 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Cache_Model_Core extends ORM {}
diff --git a/modules/gallery/models/failed_auth.php b/modules/gallery/models/failed_auth.php
new file mode 100644
index 0000000..85c6492
--- /dev/null
+++ b/modules/gallery/models/failed_auth.php
@@ -0,0 +1,20 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 Failed_Auth_Model_Core extends ORM {}
diff --git a/modules/gallery/models/graphics_rule.php b/modules/gallery/models/graphics_rule.php
new file mode 100644
index 0000000..241560e
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/incoming_translation.php b/modules/gallery/models/incoming_translation.php
new file mode 100644
index 0000000..f3c9813
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php
new file mode 100644
index 0000000..c446eea
--- /dev/null
+++ b/modules/gallery/models/item.php
@@ -0,0 +1,1191 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM_MPTT {
+ protected $children = "items";
+ protected $sorting = array();
+ public $data_file = null;
+ private $data_file_error = null;
+
+ public function __construct($id=null) {
+ parent::__construct($id);
+
+ if (!$this->loaded()) {
+ // Set reasonable defaults
+ $this->created = time();
+ $this->rand_key = random::percent();
+ $this->thumb_dirty = 1;
+ $this->resize_dirty = 1;
+ $this->sort_column = "created";
+ $this->sort_order = "ASC";
+ $this->owner_id = identity::active_user()->id;
+ }
+ }
+
+ /**
+ * Add a set of restrictions to any following queries to restrict access only to items
+ * viewable by the active user.
+ * @chainable
+ */
+ public function viewable() {
+ return item::viewable($this);
+ }
+
+ /**
+ * Is this item an album?
+ * @return true if it's an album
+ */
+ public function is_album() {
+ return $this->type == 'album';
+ }
+
+ /**
+ * Is this item a photo?
+ * @return true if it's a photo
+ */
+ public function is_photo() {
+ return $this->type == 'photo';
+ }
+
+ /**
+ * Is this item a movie?
+ * @return true if it's a movie
+ */
+ public function is_movie() {
+ return $this->type == 'movie';
+ }
+
+ public function delete($ignored_id=null) {
+ if (!$this->loaded()) {
+ // Concurrent deletes may result in this item already being gone. Ignore it.
+ return;
+ }
+
+ if ($this->id == 1) {
+ $v = new Validation(array("id"));
+ $v->add_error("id", "cant_delete_root_album");
+ ORM_Validation_Exception::handle_validation($this->table_name, $v);
+ }
+
+ $old = clone $this;
+ module::event("item_before_delete", $this);
+
+ $parent = $this->parent();
+ if ($parent->album_cover_item_id == $this->id) {
+ item::remove_album_cover($parent);
+ }
+
+ $path = $this->file_path();
+ $resize_path = $this->resize_path();
+ $thumb_path = $this->thumb_path();
+
+ parent::delete();
+ if (is_dir($path)) {
+ // Take some precautions against accidentally deleting way too much
+ $delete_resize_path = dirname($resize_path);
+ $delete_thumb_path = dirname($thumb_path);
+ if ($delete_resize_path == VARPATH . "resizes" ||
+ $delete_thumb_path == VARPATH . "thumbs" ||
+ $path == VARPATH . "albums") {
+ throw new Exception(
+ "@todo DELETING_TOO_MUCH ($delete_resize_path, $delete_thumb_path, $path)");
+ }
+ @dir::unlink($path);
+ @dir::unlink($delete_resize_path);
+ @dir::unlink($delete_thumb_path);
+ } else {
+ @unlink($path);
+ @unlink($resize_path);
+ @unlink($thumb_path);
+ }
+
+ module::event("item_deleted", $old);
+ }
+
+ /**
+ * Specify the path to the data file associated with this item. To actually associate it,
+ * you still have to call save().
+ * @chainable
+ */
+ public function set_data_file($data_file) {
+ $this->data_file = $data_file;
+ return $this;
+ }
+
+ /**
+ * Return the server-relative url to this item, eg:
+ * album: /gallery3/index.php/Bobs%20Wedding?page=2
+ * photo: /gallery3/index.php/Bobs%20Wedding/Eating-Cake
+ * movie: /gallery3/index.php/Bobs%20Wedding/First-Dance
+ *
+ * @param string $query the query string (eg "page=2")
+ */
+ public function url($query=null) {
+ $url = url::site($this->relative_url());
+ if ($query) {
+ $url .= "?$query";
+ }
+ return $url;
+ }
+
+ /**
+ * Return the full url to this item, eg:
+ * album: http://example.com/gallery3/index.php/Bobs%20Wedding?page=2
+ * photo: http://example.com/gallery3/index.php/Bobs%20Wedding/Eating-Cake
+ * movie: http://example.com/gallery3/index.php/Bobs%20Wedding/First-Dance
+ *
+ * @param string $query the query string (eg "page=2")
+ */
+ public function abs_url($query=null) {
+ $url = url::abs_site($this->relative_url());
+ if ($query) {
+ $url .= "?$query";
+ }
+ return $url;
+ }
+
+ /**
+ * Return the full path to this item's file, eg:
+ * album: /usr/home/www/gallery3/var/albums/Bobs Wedding
+ * photo: /usr/home/www/gallery3/var/albums/Bobs Wedding/Eating-Cake.jpg
+ * movie: /usr/home/www/gallery3/var/albums/Bobs Wedding/First-Dance.mp4
+ */
+ public function file_path() {
+ return VARPATH . "albums/" . urldecode($this->relative_path());
+ }
+
+ /**
+ * Return the relative url to this item's file, with cache buster, eg:
+ * album: var/albums/Bobs%20Wedding?m=1234567890
+ * photo: var/albums/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890
+ * movie: var/albums/Bobs%20Wedding/First-Dance.mp4?m=1234567890
+ * If $full_uri==true, return the full url to this item's file, with cache buster, eg:
+ * album: http://example.com/gallery3/var/albums/Bobs%20Wedding?m=1234567890
+ * photo: http://example.com/gallery3/var/albums/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890
+ * movie: http://example.com/gallery3/var/albums/Bobs%20Wedding/First-Dance.mp4?m=1234567890
+ */
+ public function file_url($full_uri=false) {
+ $relative_path = "var/albums/" . $this->relative_path();
+ $cache_buster = $this->_cache_buster($this->file_path());
+ return ($full_uri ? url::abs_file($relative_path) : url::file($relative_path))
+ . $cache_buster;
+ }
+
+ /**
+ * Return the full path to this item's thumb, eg:
+ * album: /usr/home/www/gallery3/var/thumbs/Bobs Wedding/.album.jpg
+ * photo: /usr/home/www/gallery3/var/thumbs/Bobs Wedding/Eating-Cake.jpg
+ * movie: /usr/home/www/gallery3/var/thumbs/Bobs Wedding/First-Dance.jpg
+ */
+ public function thumb_path() {
+ $base = VARPATH . "thumbs/" . urldecode($this->relative_path());
+ if ($this->is_photo()) {
+ return $base;
+ } else if ($this->is_album()) {
+ return $base . "/.album.jpg";
+ } else if ($this->is_movie()) {
+ // Replace the extension with jpg
+ return legal_file::change_extension($base, "jpg");
+ }
+ }
+
+ /**
+ * Return true if there is a thumbnail for this item.
+ */
+ public function has_thumb() {
+ return $this->thumb_width && $this->thumb_height;
+ }
+
+ /**
+ * Return the relative url to this item's thumb, with cache buster, eg:
+ * album: var/thumbs/Bobs%20Wedding/.album.jpg?m=1234567890
+ * photo: var/thumbs/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890
+ * movie: var/thumbs/Bobs%20Wedding/First-Dance.mp4?m=1234567890
+ * If $full_uri==true, return the full url to this item's file, with cache buster, eg:
+ * album: http://example.com/gallery3/var/thumbs/Bobs%20Wedding/.album.jpg?m=1234567890
+ * photo: http://example.com/gallery3/var/thumbs/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890
+ * movie: http://example.com/gallery3/var/thumbs/Bobs%20Wedding/First-Dance.mp4?m=1234567890
+ */
+ public function thumb_url($full_uri=false) {
+ $cache_buster = $this->_cache_buster($this->thumb_path());
+ $relative_path = "var/thumbs/" . $this->relative_path();
+ $base = ($full_uri ? url::abs_file($relative_path) : url::file($relative_path));
+ if ($this->is_photo()) {
+ return $base . $cache_buster;
+ } else if ($this->is_album()) {
+ return $base . "/.album.jpg" . $cache_buster;
+ } else if ($this->is_movie()) {
+ // Replace the extension with jpg
+ $base = legal_file::change_extension($base, "jpg");
+ return $base . $cache_buster;
+ }
+ }
+
+ /**
+ * Return the full path to this item's resize, eg:
+ * album: /usr/home/www/gallery3/var/resizes/Bobs Wedding/.album.jpg (*)
+ * photo: /usr/home/www/gallery3/var/resizes/Bobs Wedding/Eating-Cake.jpg
+ * movie: /usr/home/www/gallery3/var/resizes/Bobs Wedding/First-Dance.mp4 (*)
+ * (*) Since only photos have resizes, album and movie paths are fictitious.
+ */
+ public function resize_path() {
+ return VARPATH . "resizes/" . urldecode($this->relative_path()) .
+ ($this->is_album() ? "/.album.jpg" : "");
+ }
+
+ /**
+ * Return the relative url to this item's resize, with cache buster, eg:
+ * album: var/resizes/Bobs%20Wedding/.album.jpg?m=1234567890 (*)
+ * photo: var/resizes/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890
+ * movie: var/resizes/Bobs%20Wedding/First-Dance.mp4?m=1234567890 (*)
+ * If $full_uri==true, return the full url to this item's file, with cache buster, eg:
+ * album: http://example.com/gallery3/var/resizes/Bobs%20Wedding/.album.jpg?m=1234567890 (*)
+ * photo: http://example.com/gallery3/var/resizes/Bobs%20Wedding/Eating-Cake.jpg?m=1234567890
+ * movie: http://example.com/gallery3/var/resizes/Bobs%20Wedding/First-Dance.mp4?m=1234567890 (*)
+ * (*) Since only photos have resizes, album and movie urls are fictitious.
+ */
+ public function resize_url($full_uri=false) {
+ $relative_path = "var/resizes/" . $this->relative_path();
+ $cache_buster = $this->_cache_buster($this->resize_path());
+ return ($full_uri ? url::abs_file($relative_path) : url::file($relative_path)) .
+ ($this->is_album() ? "/.album.jpg" : "") . $cache_buster;
+ }
+
+ /**
+ * Rebuild the relative_path_cache and relative_url_cache.
+ */
+ private function _build_relative_caches() {
+ $names = array();
+ $slugs = array();
+ foreach (db::build()
+ ->select(array("name", "slug"))
+ ->from("items")
+ ->where("left_ptr", "<=", $this->left_ptr)
+ ->where("right_ptr", ">=", $this->right_ptr)
+ ->where("id", "<>", 1)
+ ->order_by("left_ptr", "ASC")
+ ->execute() as $row) {
+ // Don't encode the names segment
+ $names[] = rawurlencode($row->name);
+ $slugs[] = rawurlencode($row->slug);
+ }
+ $this->relative_path_cache = implode($names, "/");
+ $this->relative_url_cache = implode($slugs, "/");
+ return $this;
+ }
+
+ /**
+ * Return the relative path to this item's file. Note that the components of the path are
+ * urlencoded so if you want to use this as a filesystem path, you need to call urldecode
+ * on it.
+ * @return string
+ */
+ public function relative_path() {
+ if (!$this->loaded()) {
+ return;
+ }
+
+ if (!isset($this->relative_path_cache)) {
+ $this->_build_relative_caches()->save();
+ }
+ return $this->relative_path_cache;
+ }
+
+ /**
+ * Return the relative url to this item's file.
+ * @return string
+ */
+ public function relative_url() {
+ if (!$this->loaded()) {
+ return;
+ }
+
+ if (!isset($this->relative_url_cache)) {
+ $this->_build_relative_caches()->save();
+ }
+ return $this->relative_url_cache;
+ }
+
+ /**
+ * @see ORM::__get()
+ */
+ public function __get($column) {
+ if ($column == "owner") {
+ // This relationship depends on an outside module, which may not be present so handle
+ // failures gracefully.
+ try {
+ return identity::lookup_user($this->owner_id);
+ } catch (Exception $e) {
+ return null;
+ }
+ } else {
+ return parent::__get($column);
+ }
+ }
+
+ /**
+ * Handle any business logic necessary to create or modify an item.
+ * @see ORM::save()
+ *
+ * @return ORM Item_Model
+ */
+ public function save() {
+ $significant_changes = $this->changed;
+ foreach (array("view_count", "relative_url_cache", "relative_path_cache",
+ "resize_width", "resize_height", "resize_dirty",
+ "thumb_width", "thumb_height", "thumb_dirty") as $key) {
+ unset($significant_changes[$key]);
+ }
+
+ if ((!empty($this->changed) && $significant_changes) || isset($this->data_file)) {
+ $this->updated = time();
+ if (!$this->loaded()) {
+ // Create a new item.
+ module::event("item_before_create", $this);
+
+ // Set a weight if it's missing. We don't do this in the constructor because it's not a
+ // simple assignment.
+ if (empty($this->weight)) {
+ $this->weight = item::get_max_weight();
+ }
+
+ // Process the data file info.
+ if (isset($this->data_file)) {
+ $this->_process_data_file_info();
+ } else if (!$this->is_album()) {
+ // Unless it's an album, new items must have a data file.
+ $this->data_file_error = true;
+ }
+
+ // Make an url friendly slug from the name, if necessary
+ if (empty($this->slug)) {
+ $this->slug = item::convert_filename_to_slug(pathinfo($this->name, PATHINFO_FILENAME));
+
+ // If the filename is all invalid characters, then the slug may be empty here. We set a
+ // generic name ("photo", "movie", or "album") based on its type, then rely on
+ // check_and_fix_conflicts to ensure it doesn't conflict with another name.
+ if (empty($this->slug)) {
+ $this->slug = $this->type;
+ }
+ }
+
+ $this->_check_and_fix_conflicts();
+
+ parent::save();
+
+ // Build our url caches, then save again. We have to do this after it's already been
+ // saved once because we use only information from the database to build the paths. If we
+ // could depend on a save happening later we could defer this 2nd save.
+ $this->_build_relative_caches();
+ parent::save();
+
+ // Take any actions that we can only do once all our paths are set correctly after saving.
+ switch ($this->type) {
+ case "album":
+ mkdir($this->file_path());
+ mkdir(dirname($this->thumb_path()));
+ mkdir(dirname($this->resize_path()));
+ break;
+
+ case "photo":
+ case "movie":
+ copy($this->data_file, $this->file_path());
+ break;
+ }
+
+ // This will almost definitely trigger another save, so put it at the end so that we're
+ // tail recursive. Null out the data file variable first, otherwise the next save will
+ // trigger an item_updated_data_file event.
+ $this->data_file = null;
+ module::event("item_created", $this);
+ } else {
+ // Update an existing item
+ module::event("item_before_update", $this);
+
+ // If any significant fields have changed, load up a copy of the original item and
+ // keep it around.
+ $original = ORM::factory("item", $this->id);
+
+ // If we have a new data file, process its info. This will get its metadata and
+ // preserve the extension of the data file. Many helpers, (e.g. ImageMagick), assume
+ // the MIME type from the extension. So when we adopt the new data file, it's important
+ // to adopt the new extension. That ensures that the item's extension is always
+ // appropriate for its data. We don't try to preserve the name of the data file, though,
+ // because the name is typically a temporary randomly-generated name.
+ if (isset($this->data_file)) {
+ $this->_process_data_file_info();
+ } else if (!$this->is_album() && array_key_exists("name", $this->changed)) {
+ // There's no new data file, but the name changed. If it's a photo or movie,
+ // make sure the new name still agrees with the file type.
+ $this->name = legal_file::sanitize_filename($this->name,
+ pathinfo($original->name, PATHINFO_EXTENSION), $this->type);
+ }
+
+ // If an album's cover has changed (or been removed), delete any existing album cover,
+ // reset the thumb metadata, and mark the thumb as dirty.
+ if (array_key_exists("album_cover_item_id", $this->changed) && $this->is_album()) {
+ @unlink($original->thumb_path());
+ $this->thumb_dirty = 1;
+ $this->thumb_height = 0;
+ $this->thumb_width = 0;
+ }
+
+ if (array_intersect($this->changed, array("parent_id", "name", "slug"))) {
+ $original->_build_relative_caches();
+ $this->relative_path_cache = null;
+ $this->relative_url_cache = null;
+ }
+
+ $this->_check_and_fix_conflicts();
+
+ parent::save();
+
+ // Now update the filesystem and any database caches if there were significant value
+ // changes. If anything past this point fails, then we'll have an inconsistent database
+ // so this code should be as robust as we can make it.
+
+ // Update the MPTT pointers, if necessary. We have to do this before we generate any
+ // cached paths!
+ if ($original->parent_id != $this->parent_id) {
+ parent::move_to($this->parent());
+ }
+
+ if ($original->parent_id != $this->parent_id || $original->name != $this->name) {
+ $this->_build_relative_caches();
+ // If there is a data file, then we want to preserve both the old data and the new data.
+ // (Third-party event handlers would like access to both). The old data file will be
+ // accessible via the $original item, and the new one via $this item. But in that case,
+ // we don't want to rename the original as below, because the old data would end up being
+ // clobbered by the new data file. Also, the rename isn't necessary, because the new item
+ // data is coming from the data file anyway. So we only perform the rename if there isn't
+ // a data file. Another way to solve this would be to copy the original file rather than
+ // conditionally rename it, but a copy would cost far more than the rename.
+ if (!isset($this->data_file)) {
+ @rename($original->file_path(), $this->file_path());
+ }
+ // Move all of the items associated data files
+ if ($this->is_album()) {
+ @rename(dirname($original->resize_path()), dirname($this->resize_path()));
+ @rename(dirname($original->thumb_path()), dirname($this->thumb_path()));
+ } else {
+ @rename($original->resize_path(), $this->resize_path());
+ @rename($original->thumb_path(), $this->thumb_path());
+ }
+
+ if ($original->parent_id != $this->parent_id) {
+ // This will result in 2 events since we'll still fire the item_updated event below
+ module::event("item_moved", $this, $original->parent());
+ }
+ }
+
+ // Changing the name, slug or parent ripples downwards
+ if ($this->is_album() &&
+ ($original->name != $this->name ||
+ $original->slug != $this->slug ||
+ $original->parent_id != $this->parent_id)) {
+ db::build()
+ ->update("items")
+ ->set("relative_url_cache", null)
+ ->set("relative_path_cache", null)
+ ->where("left_ptr", ">", $this->left_ptr)
+ ->where("right_ptr", "<", $this->right_ptr)
+ ->execute();
+ }
+
+ // Replace the data file, if requested.
+ if ($this->data_file && ($this->is_photo() || $this->is_movie())) {
+ copy($this->data_file, $this->file_path());
+ $this->thumb_dirty = 1;
+ $this->resize_dirty = 1;
+ }
+
+ module::event("item_updated", $original, $this);
+
+ if ($this->data_file) {
+ // Null out the data file variable here, otherwise this event will trigger another
+ // save() which will think that we're doing another file move.
+ $this->data_file = null;
+ if ($original->file_path() != $this->file_path()) {
+ @unlink($original->file_path());
+ }
+ module::event("item_updated_data_file", $this);
+ }
+ }
+ } else if (!empty($this->changed)) {
+ // Insignificant changes only. Don't fire events or do any special checking to try to keep
+ // this lightweight.
+ parent::save();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Check to see if there's another item that occupies the same name or slug that this item
+ * intends to use, and if so choose a new name/slug while preserving the extension. Since this
+ * checks the name without its extension, it covers possible collisions with thumbs and resizes
+ * as well (e.g. between the thumbs of movie "foo.flv" and photo "foo.jpg").
+ */
+ private function _check_and_fix_conflicts() {
+ $suffix_num = 1;
+ $suffix = "";
+ if ($this->is_album()) {
+ while (db::build()
+ ->from("items")
+ ->where("parent_id", "=", $this->parent_id)
+ ->where("id", $this->id ? "<>" : "IS NOT", $this->id)
+ ->and_open()
+ ->where("name", "=", "{$this->name}{$suffix}")
+ ->or_where("slug", "=", "{$this->slug}{$suffix}")
+ ->close()
+ ->count_records()) {
+ $suffix = "-" . (($suffix_num <= 99) ? sprintf("%02d", $suffix_num++) : random::int());
+ }
+ if ($suffix) {
+ $this->name = "{$this->name}{$suffix}";
+ $this->slug = "{$this->slug}{$suffix}";
+ $this->relative_path_cache = null;
+ $this->relative_url_cache = null;
+ }
+ } else {
+ // Split the filename into its base and extension. This uses a regexp similar to
+ // legal_file::change_extension (which isn't always the same as pathinfo).
+ if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $this->name, $matches)) {
+ $base_name = $matches[1];
+ $extension = $matches[2]; // includes a leading dot
+ } else {
+ $base_name = $this->name;
+ $extension = "";
+ }
+ $base_name_escaped = Database::escape_for_like($base_name);
+ // Note: below query uses LIKE with wildcard % at end, which is still sargable (i.e. quick)
+ while (db::build()
+ ->from("items")
+ ->where("parent_id", "=", $this->parent_id)
+ ->where("id", $this->id ? "<>" : "IS NOT", $this->id)
+ ->and_open()
+ ->where("name", "LIKE", "{$base_name_escaped}{$suffix}.%")
+ ->or_where("slug", "=", "{$this->slug}{$suffix}")
+ ->close()
+ ->count_records()) {
+ $suffix = "-" . (($suffix_num <= 99) ? sprintf("%02d", $suffix_num++) : random::int());
+ }
+ if ($suffix) {
+ $this->name = "{$base_name}{$suffix}{$extension}";
+ $this->slug = "{$this->slug}{$suffix}";
+ $this->relative_path_cache = null;
+ $this->relative_url_cache = null;
+ }
+ }
+ }
+
+ /**
+ * Process the data file info. Get its metadata and extension.
+ * If valid, use it to sanitize the item name and update the
+ * width, height, and mime type.
+ */
+ private function _process_data_file_info() {
+ try {
+ if ($this->is_photo()) {
+ list ($this->width, $this->height, $this->mime_type, $extension) =
+ photo::get_file_metadata($this->data_file);
+ } else if ($this->is_movie()) {
+ list ($this->width, $this->height, $this->mime_type, $extension) =
+ movie::get_file_metadata($this->data_file);
+ } else {
+ // Albums don't have data files.
+ $this->data_file = null;
+ return;
+ }
+
+ // Sanitize the name based on the idenified extension, but only set $this->name if different
+ // to ensure it isn't unnecessarily marked as "changed"
+ $name = legal_file::sanitize_filename($this->name, $extension, $this->type);
+ if ($this->name != $name) {
+ $this->name = $name;
+ }
+
+ // Data file valid - make sure the flag is reset to false.
+ $this->data_file_error = false;
+ } catch (Exception $e) {
+ // Data file invalid - set the flag so it's reported during item validation.
+ $this->data_file_error = true;
+ }
+ }
+
+ /**
+ * Return the Item_Model representing the cover for this album.
+ * @return Item_Model or null if there's no cover
+ */
+ public function album_cover() {
+ if (!$this->is_album()) {
+ return null;
+ }
+
+ if (empty($this->album_cover_item_id)) {
+ return null;
+ }
+
+ try {
+ return model_cache::get("item", $this->album_cover_item_id);
+ } catch (Exception $e) {
+ // It's possible (unlikely) that the item was deleted, if so keep going.
+ return null;
+ }
+ }
+
+ /**
+ * Find the position of the given child id in this album. The resulting value is 1-indexed, so
+ * the first child in the album is at position 1.
+ *
+ * This method stands as a backward compatibility for gallery 3.0, and will
+ * be deprecated in version 3.1.
+ */
+ public function get_position($child, $where=array()) {
+ return item::get_position($child, $where);
+ }
+
+ /**
+ * Return an <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_img($extra_attrs=array(), $max=null, $center_vertically=false) {
+ list ($height, $width) = $this->scale_dimensions($max);
+ if ($center_vertically && $max) {
+ // The constant is divide by 2 to calculate the file and 10 to convert to em
+ $margin_top = (int)(($max - $height) / 20);
+ $extra_attrs["style"] = "margin-top: {$margin_top}em";
+ $extra_attrs["title"] = $this->title;
+ }
+ $attrs = array_merge($extra_attrs,
+ array(
+ "src" => $this->thumb_url(),
+ "alt" => $this->title,
+ "width" => $width,
+ "height" => $height)
+ );
+ // html::image forces an absolute url which we don't want
+ return "<img" . html::attributes($attrs) . "/>";
+ }
+
+ /**
+ * Calculate the largest width/height that fits inside the given maximum, while preserving the
+ * aspect ratio. Don't upscale.
+ * @param int $max Maximum size of the largest dimension
+ * @return array
+ */
+ public function scale_dimensions($max) {
+ $width = $this->thumb_width;
+ $height = $this->thumb_height;
+
+ if ($width <= $max && $height <= $max) {
+ return array($height, $width);
+ }
+
+ if ($height) {
+ if (isset($max)) {
+ if ($width > $height) {
+ $height = (int)($max * $height / $width);
+ $width = $max;
+ } else {
+ $width = (int)($max * $width / $height);
+ $height = $max;
+ }
+ }
+ } else {
+ // Missing thumbnail, can happen on albums with no photos yet.
+ // @todo we should enforce a placeholder for those albums.
+ $width = 0;
+ $height = 0;
+ }
+ return array($height, $width);
+ }
+
+ /**
+ * Return an <img> tag for the resize.
+ * @param array $extra_attrs Extra attributes to add to the img tag
+ * @return string
+ */
+ public function resize_img($extra_attrs) {
+ $attrs = array_merge($extra_attrs,
+ array("src" => $this->resize_url(),
+ "alt" => $this->title,
+ "width" => $this->resize_width,
+ "height" => $this->resize_height)
+ );
+ // html::image forces an absolute url which we don't want
+ return "<img" . html::attributes($attrs) . "/>";
+ }
+
+ /**
+ * Return a view for movies. By default this is a Flowplayer v3 <script> tag, but
+ * movie_img events can override this and provide their own player/view. If no player/view
+ * is found and the movie is unsupported by Flowplayer v3, this returns a simple download link.
+ * @param array $extra_attrs
+ * @return string
+ */
+ public function movie_img($extra_attrs) {
+ $max_size = module::get_var("gallery", "resize_size", 640);
+ $width = $this->width;
+ $height = $this->height;
+ if ($width == 0 || $height == 0) {
+ // Not set correctly, likely because ffmpeg isn't available. Making the window 0x0 causes the
+ // video to be effectively unviewable. So, let's guess: set width to max_size and guess a
+ // height (using 4:3 aspect ratio). Once the video metadata is loaded, js in
+ // movieplayer.html.php will correct these values.
+ $width = $max_size;
+ $height = ceil($width * 3/4);
+ }
+ $attrs = array_merge(array("id" => "g-item-id-{$this->id}"), $extra_attrs,
+ array("class" => "g-movie"));
+
+ // Run movie_img events, which can either:
+ // - generate a view, which is used in place of the standard Flowplayer v3 player
+ // (use view variable)
+ // - alter the arguments sent to the standard player
+ // (use fp_params and fp_config variables)
+ $movie_img = new stdClass();
+ $movie_img->max_size = $max_size;
+ $movie_img->width = $width;
+ $movie_img->height = $height;
+ $movie_img->attrs = $attrs;
+ $movie_img->url = $this->file_url(true);
+ $movie_img->filename = $this->name;
+ $movie_img->fp_params = array(); // additional Flowplayer params values (will be json encoded)
+ $movie_img->fp_config = array(); // additional Flowplayer config values (will be json encoded)
+ $movie_img->view = array();
+ module::event("movie_img", $movie_img, $this);
+
+ if (count($movie_img->view) > 0) {
+ // View generated - use it
+ $view = implode("\n", $movie_img->view);
+ } else {
+ // View NOT generated - see if filetype supported by Flowplayer v3
+ // Note that the extension list below is hard-coded and doesn't use the legal_file helper
+ // since anything else will not work in Flowplayer v3.
+ if (in_array(strtolower(pathinfo($movie_img->filename, PATHINFO_EXTENSION)),
+ array("flv", "mp4", "m4v", "mov", "f4v"))) {
+ // Filetype supported by Flowplayer v3 - use it (default)
+ $view = new View("movieplayer.html");
+ $view->max_size = $movie_img->max_size;
+ $view->width = $movie_img->width;
+ $view->height = $movie_img->height;
+ $view->attrs = $movie_img->attrs;
+ $view->url = $movie_img->url;
+ $view->fp_params = $movie_img->fp_params;
+ $view->fp_config = $movie_img->fp_config;
+ } else {
+ // Filetype NOT supported by Flowplayer v3 - display download link
+ $attrs = array_merge($attrs, array("style" => "width: {$max_size}px;",
+ "download" => $this->name, // forces download (HTML5 only)
+ "class" => "g-movie g-movie-download-link"));
+ $view = html::anchor($this->file_url(true), t("Click here to download item."), $attrs);
+ }
+ }
+ return $view;
+ }
+
+ /**
+ * Return all of the children of this album. Unless you specify a specific sort order, the
+ * results will be ordered by this album's sort order.
+ *
+ * @chainable
+ * @param integer SQL limit
+ * @param integer SQL offset
+ * @param array additional where clauses
+ * @param array order_by
+ * @return array ORM
+ */
+ function children($limit=null, $offset=null, $where=array(), $order_by=null) {
+ if (empty($order_by)) {
+ $order_by = array($this->sort_column => $this->sort_order);
+ // Use id as a tie breaker
+ if ($this->sort_column != "id") {
+ $order_by["id"] = "ASC";
+ }
+ }
+ return parent::children($limit, $offset, $where, $order_by);
+ }
+
+ /**
+ * Return the children of this album, and all of it's sub-albums. Unless you specify a specific
+ * sort order, the results will be ordered by this album's sort order. Note that this
+ * album's sort order is imposed on all sub-albums, regardless of their sort order.
+ *
+ * @chainable
+ * @param integer SQL limit
+ * @param integer SQL offset
+ * @param array additional where clauses
+ * @return object ORM_Iterator
+ */
+ function descendants($limit=null, $offset=null, $where=array(), $order_by=null) {
+ if (empty($order_by)) {
+ $order_by = array($this->sort_column => $this->sort_order);
+ // Use id as a tie breaker
+ if ($this->sort_column != "id") {
+ $order_by["id"] = "ASC";
+ }
+ }
+ return parent::descendants($limit, $offset, $where, $order_by);
+ }
+
+ /**
+ * Specify our rules here so that we have access to the instance of this model.
+ */
+ public function validate(Validation $array=null) {
+ if (!$array) {
+ $this->rules = array(
+ "album_cover_item_id" => array("callbacks" => array(array($this, "valid_album_cover"))),
+ "description" => array("rules" => array("length[0,65535]")),
+ "mime_type" => array("callbacks" => array(array($this, "valid_field"))),
+ "name" => array("rules" => array("length[0,255]", "required"),
+ "callbacks" => array(array($this, "valid_name"))),
+ "parent_id" => array("callbacks" => array(array($this, "valid_parent"))),
+ "rand_key" => array("rule" => array("decimal")),
+ "slug" => array("rules" => array("length[0,255]", "required"),
+ "callbacks" => array(array($this, "valid_slug"))),
+ "sort_column" => array("callbacks" => array(array($this, "valid_field"))),
+ "sort_order" => array("callbacks" => array(array($this, "valid_field"))),
+ "title" => array("rules" => array("length[0,255]", "required")),
+ "type" => array("callbacks" => array(array($this, "read_only"),
+ array($this, "valid_field"))),
+ );
+
+ // Conditional rules
+ if ($this->id == 1) {
+ // We don't care about the name and slug for the root album.
+ $this->rules["name"] = array();
+ $this->rules["slug"] = array();
+ }
+
+ // Movies and photos must have data files. Verify the data file on new items, or if it has
+ // been replaced.
+ if (($this->is_photo() || $this->is_movie()) && $this->data_file) {
+ $this->rules["name"]["callbacks"][] = array($this, "valid_data_file");
+ }
+ }
+
+ parent::validate($array);
+ }
+
+ /**
+ * Validate that the desired slug does not conflict.
+ */
+ public function valid_slug(Validation $v, $field) {
+ if (preg_match("/[^A-Za-z0-9-_]/", $this->slug)) {
+ $v->add_error("slug", "not_url_safe");
+ } else if (db::build()
+ ->from("items")
+ ->where("parent_id", "=", $this->parent_id)
+ ->where("id", "<>", $this->id)
+ ->where("slug", "=", $this->slug)
+ ->count_records()) {
+ $v->add_error("slug", "conflict");
+ }
+ }
+
+ /**
+ * Validate the item name. It can't conflict with other names, can't contain slashes or
+ * trailing periods.
+ */
+ public function valid_name(Validation $v, $field) {
+ if (strpos($this->name, "/") !== false) {
+ $v->add_error("name", "no_slashes");
+ return;
+ }
+
+ if (rtrim($this->name, ".") !== $this->name) {
+ $v->add_error("name", "no_trailing_period");
+ return;
+ }
+
+ // Do not accept files with double extensions, they can cause problems on some
+ // versions of Apache.
+ if (!$this->is_album() && substr_count($this->name, ".") > 1) {
+ $v->add_error("name", "illegal_data_file_extension");
+ }
+
+ if ($this->is_movie() || $this->is_photo()) {
+ $ext = pathinfo($this->name, PATHINFO_EXTENSION);
+
+ if (!$this->loaded() && !$ext) {
+ // New items must have an extension
+ $v->add_error("name", "illegal_data_file_extension");
+ return;
+ }
+
+ if ($this->is_photo() && !legal_file::get_photo_extensions($ext) ||
+ $this->is_movie() && !legal_file::get_movie_extensions($ext)) {
+ $v->add_error("name", "illegal_data_file_extension");
+ }
+ }
+
+ if ($this->is_album()) {
+ if (db::build()
+ ->from("items")
+ ->where("parent_id", "=", $this->parent_id)
+ ->where("name", "=", $this->name)
+ ->merge_where($this->id ? array(array("id", "<>", $this->id)) : null)
+ ->count_records()) {
+ $v->add_error("name", "conflict");
+ return;
+ }
+ } else {
+ if (preg_match("/^(.*)(\.[^\.\/]*?)$/", $this->name, $matches)) {
+ $base_name = $matches[1];
+ } else {
+ $base_name = $this->name;
+ }
+ $base_name_escaped = Database::escape_for_like($base_name);
+ if (db::build()
+ ->from("items")
+ ->where("parent_id", "=", $this->parent_id)
+ ->where("name", "LIKE", "{$base_name_escaped}.%")
+ ->merge_where($this->id ? array(array("id", "<>", $this->id)) : null)
+ ->count_records()) {
+ $v->add_error("name", "conflict");
+ return;
+ }
+ }
+
+ if ($this->parent_id == 1 && Kohana::auto_load("{$this->slug}_Controller")) {
+ $v->add_error("slug", "reserved");
+ return;
+ }
+ }
+
+ /**
+ * Make sure that the data file is well formed (it exists and isn't empty).
+ */
+ public function valid_data_file(Validation $v, $field) {
+ if (!is_file($this->data_file)) {
+ $v->add_error("name", "bad_data_file_path");
+ } else if (filesize($this->data_file) == 0) {
+ $v->add_error("name", "empty_data_file");
+ } else if ($this->data_file_error) {
+ $v->add_error("name", "invalid_data_file");
+ }
+ }
+
+ /**
+ * Make sure that the parent id refers to an album.
+ */
+ public function valid_parent(Validation $v, $field) {
+ if ($this->id == 1) {
+ if ($this->parent_id != 0) {
+ $v->add_error("parent_id", "invalid");
+ }
+ } else {
+ $query = db::build()
+ ->from("items")
+ ->where("id", "=", $this->parent_id)
+ ->where("type", "=", "album");
+
+ // If this is an existing item, make sure the new parent is not part of our hierarchy
+ if ($this->loaded()) {
+ $query->and_open()
+ ->where("left_ptr", "<", $this->left_ptr)
+ ->or_where("right_ptr", ">", $this->right_ptr)
+ ->close();
+ }
+
+ if ($query->count_records() != 1) {
+ $v->add_error("parent_id", "invalid");
+ }
+ }
+ }
+
+ /**
+ * Make sure the album cover item id refers to a valid item, or is null.
+ */
+ public function valid_album_cover(Validation $v, $field) {
+ if ($this->id == 1) {
+ return;
+ }
+
+ if ($this->album_cover_item_id && ($this->is_photo() || $this->is_movie() ||
+ db::build()
+ ->from("items")
+ ->where("id", "=", $this->album_cover_item_id)
+ ->where("type", "<>", "album")
+ ->count_records() != 1)) {
+ $v->add_error("album_cover_item_id", "invalid_item");
+ }
+ }
+
+ /**
+ * Make sure that the type is valid.
+ */
+ public function valid_field(Validation $v, $field) {
+ switch($field) {
+ case "mime_type":
+ if ($this->is_movie()) {
+ $legal_values = legal_file::get_movie_types();
+ } else if ($this->is_photo()) {
+ $legal_values = legal_file::get_photo_types();
+ }
+ break;
+
+ case "sort_column":
+ if (!array_key_exists($this->sort_column, $this->object)) {
+ $v->add_error($field, "invalid");
+ }
+ break;
+
+ case "sort_order":
+ $legal_values = array("ASC", "DESC", "asc", "desc");
+ break;
+
+ case "type":
+ $legal_values = array("album", "photo", "movie");
+ break;
+
+ default:
+ $v->add_error($field, "unvalidated_field");
+ break;
+ }
+
+ if (isset($legal_values) && !in_array($this->$field, $legal_values)) {
+ $v->add_error($field, "invalid");
+ }
+ }
+
+ /**
+ * This field cannot be changed after it's been set.
+ */
+ public function read_only(Validation $v, $field) {
+ if ($this->loaded() && isset($this->changed[$field])) {
+ $v->add_error($field, "read_only");
+ }
+ }
+
+ /**
+ * Same as ORM::as_array() but convert id fields into their RESTful form.
+ *
+ * @param array if specified, only return the named fields
+ */
+ public function as_restful_array($fields=array()) {
+ if ($fields) {
+ $data = array();
+ foreach ($fields as $field) {
+ if (isset($this->object[$field])) {
+ $data[$field] = $this->__get($field);
+ }
+ }
+ $fields = array_flip($fields);
+ } else {
+ $data = $this->as_array();
+ }
+
+ // Convert item ids to rest URLs for consistency
+ if (empty($fields) || isset($fields["parent"])) {
+ if ($tmp = $this->parent()) {
+ $data["parent"] = rest::url("item", $tmp);
+ }
+ unset($data["parent_id"]);
+ }
+
+ if (empty($fields) || isset($fields["album_cover"])) {
+ if ($tmp = $this->album_cover()) {
+ $data["album_cover"] = rest::url("item", $tmp);
+ }
+ unset($data["album_cover_item_id"]);
+ }
+
+ if (empty($fields) || isset($fields["web_url"])) {
+ $data["web_url"] = $this->abs_url();
+ }
+
+ if (!$this->is_album()) {
+ if (access::can("view_full", $this)) {
+ if (empty($fields) || isset($fields["file_url"])) {
+ $data["file_url"] = rest::url("data", $this, "full");
+ }
+ if (empty($fields) || isset($fields["file_size"])) {
+ $data["file_size"] = filesize($this->file_path());
+ }
+ if (access::user_can(identity::guest(), "view_full", $this)) {
+ if (empty($fields) || isset($fields["file_url_public"])) {
+ $data["file_url_public"] = $this->file_url(true);
+ }
+ }
+ }
+ }
+
+ if ($this->is_photo()) {
+ if (empty($fields) || isset($fields["resize_url"])) {
+ $data["resize_url"] = rest::url("data", $this, "resize");
+ }
+ if (empty($fields) || isset($fields["resize_size"])) {
+ $data["resize_size"] = filesize($this->resize_path());
+ }
+ if (access::user_can(identity::guest(), "view", $this)) {
+ if (empty($fields) || isset($fields["resize_url_public"])) {
+ $data["resize_url_public"] = $this->resize_url(true);
+ }
+ }
+ }
+
+ if ($this->has_thumb()) {
+ if (empty($fields) || isset($fields["thumb_url"])) {
+ $data["thumb_url"] = rest::url("data", $this, "thumb");
+ }
+ if (empty($fields) || isset($fields["thumb_size"])) {
+ $data["thumb_size"] = filesize($this->thumb_path());
+ }
+ if (access::user_can(identity::guest(), "view", $this)) {
+ if (empty($fields) || isset($fields["thumb_url_public"])) {
+ $data["thumb_url_public"] = $this->thumb_url(true);
+ }
+ }
+ }
+
+ if (empty($fields) || isset($fields["can_edit"])) {
+ $data["can_edit"] = access::can("edit", $this);
+ }
+
+ if (empty($fields) || isset($fields["can_add"])) {
+ $data["can_add"] = access::can("add", $this);
+ }
+
+ // Elide some internal-only data that is going to cause confusion in the client.
+ foreach (array("relative_path_cache", "relative_url_cache", "left_ptr", "right_ptr",
+ "thumb_dirty", "resize_dirty", "weight") as $key) {
+ unset($data[$key]);
+ }
+ return $data;
+ }
+
+ /**
+ * Increments the view counter of this item
+ * 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.
+ */
+ public function increment_view_count() {
+ db::query("UPDATE {items} SET `view_count` = `view_count` + 1 WHERE `id` = $this->id")
+ ->execute();
+ }
+
+ private function _cache_buster($path) {
+ return "?m=" . (string)(file_exists($path) ? filemtime($path) : 0);
+ }
+}
diff --git a/modules/gallery/models/log.php b/modules/gallery/models/log.php
new file mode 100644
index 0000000..b01bcd2
--- /dev/null
+++ b/modules/gallery/models/log.php
@@ -0,0 +1,38 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+ /**
+ * @see ORM::__get()
+ */
+ public function __get($column) {
+ if ($column == "user") {
+ // This relationship depends on an outside module, which may not be present so handle
+ // failures gracefully.
+ try {
+ return identity::lookup_user($this->user_id);
+ } catch (Exception $e) {
+ Kohana_Log::add("alert", "Unable to load user with id $this->user_id");
+ return null;
+ }
+ } else {
+ return parent::__get($column);
+ }
+ }
+}
diff --git a/modules/gallery/models/message.php b/modules/gallery/models/message.php
new file mode 100644
index 0000000..0fa803d
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/module.php b/modules/gallery/models/module.php
new file mode 100644
index 0000000..0b2fb77
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/outgoing_translation.php b/modules/gallery/models/outgoing_translation.php
new file mode 100644
index 0000000..66e2091
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/permission.php b/modules/gallery/models/permission.php
new file mode 100644
index 0000000..26d1ee7
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/models/task.php b/modules/gallery/models/task.php
new file mode 100644
index 0000000..31332ba
--- /dev/null
+++ b/modules/gallery/models/task.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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core 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=null) {
+ $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 delete($ignored_id=null) {
+ Cache::instance()->delete($this->_cache_key());
+ return parent::delete();
+ }
+
+ public function owner() {
+ return identity::lookup_user($this->owner_id);
+ }
+
+ /**
+ * Log a message to the task log.
+ * @params $msg mixed a string or array of strings
+ */
+ public function log($msg) {
+ $key = $this->_cache_key();
+ $log = Cache::instance()->get($key);
+
+ if (is_array($msg)) {
+ $msg = implode("\n", $msg);
+ }
+
+ // Save for 30 days.
+ $log .= !empty($log) ? "\n" : "";
+ Cache::instance()->set($key, "$log{$msg}",
+ array("task", "log", "import"), 2592000);
+ }
+
+ /**
+ * Retrieve the cached log information for this task.
+ * @returns the log data or null if there is no log data
+ */
+ public function get_log() {
+ $log_data = Cache::instance()->get($this->_cache_key());
+ return $log_data !== null ? $log_data : false;
+ }
+
+ /**
+ * Build the task cache key
+ * @returns the key to use in access the cache
+ */
+ private function _cache_key() {
+ return md5("$this->id; $this->name; $this->callback");
+ }
+} \ 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 0000000..2e4d932
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core 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 0000000..7049e7d
--- /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-2013 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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_Core extends ORM {
+}
diff --git a/modules/gallery/module.info b/modules/gallery/module.info
new file mode 100644
index 0000000..7f49b72
--- /dev/null
+++ b/modules/gallery/module.info
@@ -0,0 +1,7 @@
+name = "Gallery 3"
+description = "Gallery core application"
+version = 57
+author_name = "Gallery Team"
+author_url = "http://codex.galleryproject.org/Gallery:Team"
+info_url = "http://codex.galleryproject.org/Gallery3:Modules:gallery"
+discuss_url = "http://galleryproject.org/forum_module_gallery"
diff --git a/modules/gallery/vendor/joomla/crypt.php b/modules/gallery/vendor/joomla/crypt.php
new file mode 100644
index 0000000..c7d477d
--- /dev/null
+++ b/modules/gallery/vendor/joomla/crypt.php
@@ -0,0 +1,151 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * @package Joomla.Platform
+ * @subpackage Crypt
+ *
+ * @copyright Copyright (C) 2005 - 2011 Open Source Matters, Inc. All rights reserved.
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+// defined('JPATH_PLATFORM') or die;
+
+/**
+ * JCrypt is a Joomla Platform class for handling basic encryption/decryption of data.
+ *
+ * @package Joomla.Platform
+ * @subpackage Crypt
+ * @since 12.1
+ */
+class JCrypt
+{
+ /**
+ * Generate random bytes.
+ *
+ * @param integer $length Length of the random data to generate
+ *
+ * @return string Random binary data
+ *
+ * @since 12.1
+ */
+ public static function genRandomBytes($length = 16)
+ {
+ $sslStr = '';
+ /*
+ * if a secure randomness generator exists and we don't
+ * have a buggy PHP version use it.
+ */
+ if (
+ function_exists('openssl_random_pseudo_bytes')
+ && (version_compare(PHP_VERSION, '5.3.4') >= 0
+ || substr(PHP_OS, 0, 3) !== 'WIN'
+ )
+ )
+ {
+ $sslStr = openssl_random_pseudo_bytes($length, $strong);
+ if ($strong)
+ {
+ return $sslStr;
+ }
+ }
+
+ /*
+ * Collect any entropy available in the system along with a number
+ * of time measurements of operating system randomness.
+ */
+ $bitsPerRound = 2;
+ $maxTimeMicro = 400;
+ $shaHashLength = 20;
+ $randomStr = '';
+ $total = $length;
+
+ // Check if we can use /dev/urandom.
+ $urandom = false;
+ $handle = null;
+ if (function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
+ {
+ $handle = @fopen('/dev/urandom', 'rb');
+ if ($handle)
+ {
+ $urandom = true;
+ }
+ }
+
+ while ($length > strlen($randomStr))
+ {
+ $bytes = ($total > $shaHashLength)? $shaHashLength : $total;
+ $total -= $bytes;
+ /*
+ * Collect any entropy available from the PHP system and filesystem.
+ * If we have ssl data that isn't strong, we use it once.
+ */
+ $entropy = rand() . uniqid(mt_rand(), true) . $sslStr;
+ $entropy .= implode('', @fstat(fopen( __FILE__, 'r')));
+ $entropy .= memory_get_usage();
+ $sslStr = '';
+ if ($urandom)
+ {
+ stream_set_read_buffer($handle, 0);
+ $entropy .= @fread($handle, $bytes);
+ }
+ else
+ {
+ /*
+ * There is no external source of entropy so we repeat calls
+ * to mt_rand until we are assured there's real randomness in
+ * the result.
+ *
+ * Measure the time that the operations will take on average.
+ */
+ $samples = 3;
+ $duration = 0;
+ for ($pass = 0; $pass < $samples; ++$pass)
+ {
+ $microStart = microtime(true) * 1000000;
+ $hash = sha1(mt_rand(), true);
+ for ($count = 0; $count < 50; ++$count)
+ {
+ $hash = sha1($hash, true);
+ }
+ $microEnd = microtime(true) * 1000000;
+ $entropy .= $microStart . $microEnd;
+ if ($microStart > $microEnd) {
+ $microEnd += 1000000;
+ }
+ $duration += $microEnd - $microStart;
+ }
+ $duration = $duration / $samples;
+
+ /*
+ * Based on the average time, determine the total rounds so that
+ * the total running time is bounded to a reasonable number.
+ */
+ $rounds = (int)(($maxTimeMicro / $duration) * 50);
+
+ /*
+ * Take additional measurements. On average we can expect
+ * at least $bitsPerRound bits of entropy from each measurement.
+ */
+ $iter = $bytes * (int) ceil(8 / $bitsPerRound);
+ for ($pass = 0; $pass < $iter; ++$pass)
+ {
+ $microStart = microtime(true);
+ $hash = sha1(mt_rand(), true);
+ for ($count = 0; $count < $rounds; ++$count)
+ {
+ $hash = sha1($hash, true);
+ }
+ $entropy .= $microStart . microtime(true);
+ }
+ }
+
+ $randomStr .= sha1($entropy, true);
+ }
+
+ if ($urandom)
+ {
+ @fclose($handle);
+ }
+
+ return substr($randomStr, 0, $length);
+ }
+}
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 0000000..6745f0d
--- /dev/null
+++ b/modules/gallery/views/admin_advanced_settings.html.php
@@ -0,0 +1,57 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-admin-advanced-settings" class="g-block">
+ <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="g-action-status" class="g-message-block">
+ <li class="g-warning"><?= t("Change these values at your own risk!") ?></li>
+ </ul>
+
+ <?= t("Filter:") ?> <input id="g-admin-advanced-settings-filter" type="text"></input>
+ <div class="g-block-content">
+ <table>
+ <tr>
+ <th> <?= t("Module") ?> </th>
+ <th> <?= t("Name") ?> </th>
+ <th> <?= t("Value") ?></th>
+ </tr>
+ <? foreach ($vars as $var): ?>
+ <tr class="setting-row <?= text::alternate("g-odd", "g-even") ?>">
+ <td> <?= html::clean($var->module_name) ?> </td>
+ <td> <?= html::clean($var->name) ?> </td>
+ <td>
+ <a href="<?= url::site("admin/advanced_settings/edit/$var->module_name/" . html::clean($var->name)) ?>"
+ class="g-dialog-link"
+ title="<?= t("Edit %var (%module_name)", array("var" => $var->name, "module_name" => $var->module_name))->for_html_attr() ?>">
+ <? if (!isset($var->value) || $var->value === ""): ?>
+ <i> <?= t("empty") ?> </i>
+ <? else: ?>
+ <?= html::clean($var->value) ?>
+ <? endif ?>
+ </a>
+ </td>
+ </tr>
+ <? endforeach ?>
+ </table>
+ </div>
+
+ <script>
+ $(document).ready(function() {
+ $("#g-admin-advanced-settings-filter").keyup(function() {
+ var filter = $(this).attr("value");
+ if (filter) {
+ $("tr.setting-row").fadeOut("fast");
+ $("tr.setting-row").each(function() {
+ if ($(this).text().indexOf(filter) > 0) {
+ $(this).stop().show();
+ }
+ });
+ } else {
+ $("tr.setting-row").show();
+ }
+ });
+ });
+ </script>
+</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 0000000..5a8ed23
--- /dev/null
+++ b/modules/gallery/views/admin_block_log_entries.html.php
@@ -0,0 +1,15 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<ul>
+ <? foreach ($entries as $entry): ?>
+ <li class="<?= log::severity_class($entry->severity) ?>" style="direction: ltr">
+ <? if ($entry->user->guest): ?>
+ </span><?= html::clean($entry->user->name) ?></span>
+ <? else: ?>
+ <a href="<?= user_profile::url($entry->user->id) ?>"><?= html::clean($entry->user->name) ?></a>
+ <? endif ?>
+ <?= gallery::date_time($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 0000000..cb276ae
--- /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 0000000..f9725ee
--- /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="g-item g-photo">
+ <a href="<?= $photo->url() ?>" title="<?= html::purify($photo->title)->for_html_attr() ?>">
+ <img <?= photo::img_dimensions($photo->width, $photo->height, 72) ?>
+ src="<?= $photo->thumb_url() ?>" alt="<?= html::purify($photo->title)->for_html_attr() ?>" />
+ </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 0000000..9a594fa
--- /dev/null
+++ b/modules/gallery/views/admin_block_platform.html.php
@@ -0,0 +1,24 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<ul>
+ <li>
+ <?= t("Host name: %host_name", array("host_name" => php_uname("n"))) ?>
+ </li>
+ <li>
+ <?= t("Operating system: %os %version", array("os" => php_uname("s"), "version" => php_uname("r"))) ?>
+ </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" => join(" ", sys_getloadavg()))) ?>
+ </li>
+ <li>
+ <?= t("Graphics toolkit: %toolkit", array("toolkit" => module::get_var("gallery", "graphics_toolkit"))) ?>
+ </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 0000000..c8c5476
--- /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" => gallery::version_string())) ?>
+ </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 0000000..d3765d1
--- /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 class="g-text">
+ <li>
+ <?= t("General Settings - choose your <a href=\"%graphics_url\">graphics</a> and <a href=\"%language_url\">language</a> settings.",
+ array("graphics_url" => html::mark_clean(url::site("admin/graphics")),
+ "language_url" => html::mark_clean(url::site("admin/languages")))) ?>
+ </li>
+ <li>
+ <?= t("Appearance - <a href=\"%theme_url\">choose a theme</a>, or <a href=\"%theme_options_url\">customize the way it looks</a>.",
+ array("theme_url" => html::mark_clean(url::site("admin/themes")),
+ "theme_options_url" => html::mark_clean(url::site("admin/theme_options")))) ?>
+ </li>
+ <li>
+ <?= t("Customize - <a href=\"%modules_url\">install modules</a> to add cool features!",
+ array("modules_url" => html::mark_clean(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 0000000..cf90ef2
--- /dev/null
+++ b/modules/gallery/views/admin_dashboard.html.php
@@ -0,0 +1,43 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ update_blocks = function() {
+ $.get(<?= html::js_string(url::site("admin/dashboard/reorder")) ?>,
+ {"csrf": "<?= $csrf ?>",
+ "dashboard_center[]": $("#g-admin-dashboard").sortable(
+ "toArray", {attribute: "block_id"}),
+ "dashboard_sidebar[]": $("#g-admin-dashboard-sidebar").sortable(
+ "toArray", {attribute: "block_id"})});
+ };
+
+ $(document).ready(function(){
+ $("#g-admin-dashboard .g-block .ui-widget-header").addClass("g-draggable");
+ $("#g-admin-dashboard").sortable({
+ connectWith: ["#g-admin-dashboard-sidebar"],
+ cursor: "move",
+ handle: $(".ui-widget-header"),
+ opacity: 0.6,
+ placeholder: "g-target",
+ stop: update_blocks
+ });
+
+ $("#g-admin-dashboard-sidebar .g-block .ui-widget-header").addClass("g-draggable");
+ $("#g-admin-dashboard-sidebar").sortable({
+ connectWith: ["#g-admin-dashboard"],
+ cursor: "move",
+ handle: $(".ui-widget-header"),
+ opacity: 0.6,
+ placeholder: "g-target",
+ stop: update_blocks
+ });
+ });
+</script>
+<div>
+ <? if ($obsolete_modules_message): ?>
+ <p class="g-warning">
+ <?= $obsolete_modules_message ?>
+ </p>
+ <? endif ?>
+</div>
+<div id="g-admin-dashboard">
+ <?= $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 0000000..1f45bb1
--- /dev/null
+++ b/modules/gallery/views/admin_graphics.html.php
@@ -0,0 +1,40 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $(".g-available .g-block").equal_heights();
+ select_toolkit = function(el) {
+ if (!$(this).hasClass("g-unavailable")) {
+ window.location = <?= html::js_string(url::site("admin/graphics/choose/__TK__?csrf=$csrf")) ?>
+ .replace("__TK__", $(this).attr("id"));
+ }
+ };
+ $("#g-admin-graphics div.g-available .g-block").click(select_toolkit);
+ });
+</script>
+
+<div id="g-admin-graphics" class="g-block ui-helper-clearfix">
+ <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.") ?>
+ <?= t("Can't decide which toolkit to choose? <a href=\"%url\">We can help!</a>", array("url" => "http://codex.galleryproject.org/Gallery3:Choosing_A_Graphics_Toolkit")) ?>
+ </p>
+
+ <div class="g-block-content">
+ <h2> <?= t("Active toolkit") ?> </h2>
+ <? if ($active == "none"): ?>
+ <?= new View("admin_graphics_none.html") ?>
+ <? else: ?>
+ <?= new View("admin_graphics_$active.html", array("tk" => $tk->$active, "is_active" => true)) ?>
+ <? endif ?>
+
+ <div class="g-available">
+ <h2> <?= t("Available toolkits") ?> </h2>
+ <? foreach (array_keys((array)$tk) as $id): ?>
+ <? if ($id != $active): ?>
+ <?= new View("admin_graphics_$id.html", array("tk" => $tk->$id, "is_active" => false)) ?>
+ <? endif ?>
+ <? endforeach ?>
+ </div>
+ </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 0000000..1cc9dc9
--- /dev/null
+++ b/modules/gallery/views/admin_graphics_gd.html.php
@@ -0,0 +1,30 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="gd" class="g-block<?= $is_active ? " g-selected" : "" ?><?= $tk->installed ? " g-installed-toolkit" : " g-unavailable" ?>">
+ <img class="logo" width="170" height="110" src="<?= url::file("modules/gallery/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->installed && $tk->rotate): ?>
+ <div class="g-module-status g-info">
+ <?= t("You have GD version %version.", array("version" => $tk->version)) ?>
+ </div>
+ <p>
+ <a class="g-button ui-state-default ui-corner-all"><?= t("Activate GD") ?></a>
+ </p>
+ <? elseif ($tk->installed): ?>
+ <? if ($tk->error): ?>
+ <p class="g-module-status g-warning">
+ <?= $tk->error ?>
+ </p>
+ <? endif ?>
+ <p>
+ <a class="g-button ui-state-default ui-corner-all"><?= t("Activate GD") ?></a>
+ </p>
+ <? else: ?>
+ <div class="g-module-status g-info">
+ <?= t("You do not have GD installed.") ?>
+ </div>
+ <? 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 0000000..5dae144
--- /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="g-block<?= $is_active ? " g-selected" : "" ?><?= $tk->installed ? " g-installed-toolkit" : " g-unavailable" ?>">
+ <img class="logo" width="107" height="76" src="<?= url::file("modules/gallery/images/graphicsmagick.png"); ?>" alt="<? t("Visit the GraphicsMagick project site") ?>" />
+ <h3> <?= t("GraphicsMagick") ?> </h3>
+ <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->installed): ?>
+ <div class="g-module-status g-info">
+ <?= t("GraphicsMagick version %version is available in %dir", array("version" => $tk->version, "dir" => $tk->dir)) ?>
+ </div>
+ <p>
+ <a class="g-button ui-state-default ui-corner-all"><?= t("Activate Graphics Magic") ?></a>
+ </p>
+ <? else: ?>
+ <div class="g-module-status g-warning">
+ <?= $tk->error ?>
+ </div>
+ <? 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 0000000..9c1a990
--- /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="g-block<?= $is_active ? " g-selected" : "" ?><?= $tk->installed ? " g-installed-toolkit" : " g-unavailable" ?>">
+ <img class="logo" width="114" height="118" src="<?= url::file("modules/gallery/images/imagemagick.jpg"); ?>" alt="<? t("Visit the ImageMagick project site") ?>" />
+ <h3> <?= t("ImageMagick") ?> </h3>
+ <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->installed): ?>
+ <div class="g-module-status g-info">
+ <?= t("ImageMagick version %version is available in %dir", array("version" => $tk->version, "dir" => $tk->dir)) ?>
+ </div>
+ <p>
+ <a class="g-button ui-state-default ui-corner-all"><?= t("Activate ImageMagick") ?></a>
+ </p>
+ <? elseif ($tk->error): ?>
+ <div class="g-module-status g-warning">
+ <?= $tk->error ?>
+ </div>
+ <? 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 0000000..e0fc417
--- /dev/null
+++ b/modules/gallery/views/admin_graphics_none.html.php
@@ -0,0 +1,8 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+
+<div id="none" class="g-module-status g-warning g-block">
+ <h3> <?= 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 use 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 0000000..d6a9c22
--- /dev/null
+++ b/modules/gallery/views/admin_languages.html.php
@@ -0,0 +1,118 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ var old_default_locale = <?= html::js_string($default_locale) ?>;
+
+ $("#g-languages-form").ready(function() {
+ $("input[name='installed_locales[]']").change(function (event) {
+ if (this.checked) {
+ $("input[type='radio'][value='" + this.value + "']").enable();
+ } else {
+ if ($("input[type='radio'][value='" + this.value + "']").selected()) { // if you deselect your default language, switch to some other installed language
+ $("input[type='radio'][value='" + old_default_locale + "']").attr("checked", "checked");
+ }
+ $("input[type='radio'][value='" + this.value + "']").attr("disabled", "disabled");
+ }
+ });
+
+ $("#g-languages-form").ajaxForm({
+ dataType: "json",
+ success: function(data) {
+ if (data.result == "success") {
+ el = $('<a href="' + <?= html::js_string(url::site("admin/maintenance/start/gallery_task::update_l10n?csrf=$csrf")) ?> + '"></a>'); // this is a little hack to trigger the update_l10n task in a dialog
+ el.gallery_dialog();
+ el.trigger('click');
+ }
+ }
+ });
+ });
+</script>
+
+<div class="g-block">
+ <h1> <?= t("Languages and translation") ?> </h1>
+
+ <div class="g-block-content">
+
+ <div id="g-languages" class="g-block">
+ <h2> <?= t("Languages") ?> </h2>
+ <p>
+ <?= t("Install new languages, update installed ones and set the default language for your Gallery.") ?>
+ </p>
+
+ <div class="g-block-content ui-helper-clearfix">
+ <form id="g-languages-form" method="post" action="<?= url::site("admin/languages/save") ?>">
+ <?= access::csrf_form_field() ?>
+ <table class="g-left">
+ <tr>
+ <th> <?= t("Installed") ?> </th>
+ <th> <?= t("Language") ?> </th>
+ <th> <?= t("Default language") ?> </th>
+ </tr>
+ <? $i = 0 ?>
+ <? foreach ($available_locales as $code => $display_name): ?>
+ <? if ($i == (int) (count($available_locales)/2)): ?>
+ </table>
+ <table class="g-left">
+ <tr>
+ <th> <?= t("Installed") ?> </th>
+ <th> <?= t("Language") ?> </th>
+ <th> <?= t("Default language") ?> </th>
+ </tr>
+ <? endif ?>
+ <tr class="<?= (isset($installed_locales[$code])) ? "g-available" : "" ?><?= ($default_locale == $code) ? " g-selected" : "" ?>">
+ <td> <?= form::checkbox("installed_locales[]", $code, isset($installed_locales[$code])) ?> </td>
+ <td> <?= $display_name ?> </td>
+ <td>
+ <?= form::radio("default_locale", $code, ($default_locale == $code), ((isset($installed_locales[$code]))?'':'disabled="disabled"') ) ?>
+ </td>
+ </tr>
+ <? $i++ ?>
+ <? endforeach ?>
+ </table>
+ <input type="submit" value="<?= t("Update languages")->for_html_attr() ?>" />
+ </form>
+ </div>
+ </div>
+
+ <div id="g-translations" class="g-block">
+ <h2> <?= t("Translations") ?> </h2>
+ <p>
+ <?= t("Create your own translations and share them with the rest of the Gallery community.") ?>
+ </p>
+
+ <div class="g-block-content">
+ <a href="http://codex.galleryproject.org/Gallery3:Localization" target="_blank"
+ class="g-right ui-state-default ui-corner-all ui-icon ui-icon-help"
+ title="<?= t("Localization documentation")->for_html_attr() ?>">
+ <?= t("Localization documentation") ?>
+ </a>
+
+ <h3><?= t("Translating Gallery") ?></h3>
+
+ <p><?= t("Follow these steps to begin translating Gallery.") ?></p>
+
+ <ol>
+ <li><?= t("Make sure the target language is installed and up to date (check above).") ?></li>
+ <li><?= t("Make sure you have selected the right target language (currently %default_locale).",
+ array("default_locale" => locales::display_name())) ?></li>
+ <li><?= t("Start the translation mode and the translation interface will appear at the bottom of each Gallery page.") ?></li>
+ </ol>
+ <a href="<?= url::site("l10n_client/toggle_l10n_mode?csrf=".access::csrf_token()) ?>"
+ class="g-button ui-state-default ui-corner-all ui-icon-left">
+ <span class="ui-icon ui-icon-power"></span>
+ <? if (Session::instance()->get("l10n_mode", false)): ?>
+ <?= t("Stop translation mode") ?>
+ <? else: ?>
+ <?= t("Start translation mode") ?>
+ <? endif ?>
+ </a>
+
+ <h3><?= t("Sharing your translations") ?></h3>
+ <p>
+ <?= t("Sharing your own translations with the Gallery community is easy. Please do!") ?>
+ </p>
+ <?= $share_translations_form ?>
+ </div>
+ </div>
+
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_maintenance.html.php b/modules/gallery/views/admin_maintenance.html.php
new file mode 100644
index 0000000..230e935
--- /dev/null
+++ b/modules/gallery/views/admin_maintenance.html.php
@@ -0,0 +1,212 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-admin-maintenance" class="g-block">
+ <h1> <?= t("Maintenance") ?> </h1>
+ <div class="g-block-content">
+ <div id="g-maintenance-mode">
+ <p>
+ <?= t("When you're performing maintenance on your Gallery, you can enable <b>maintenance mode</b> which prevents any non-admin from accessing your Gallery. Some of the tasks below will automatically put your Gallery in maintenance mode for you.") ?>
+ </p>
+ <ul id="g-action-status" class="g-message-block">
+ <? if (module::get_var("gallery", "maintenance_mode")): ?>
+ <li class="g-warning">
+ <?= t("Maintenance mode is <b>on</b>. Non admins cannot access your Gallery. <a href=\"%enable_maintenance_mode_url\">Turn off maintenance mode</a>", array("enable_maintenance_mode_url" => url::site("admin/maintenance/maintenance_mode/0?csrf=$csrf"))) ?>
+ </li>
+ <? else: ?>
+ <li class="g-info">
+ <?= t("Maintenance mode is off. User access is permitted. <a href=\"%enable_maintenance_mode_url\">Turn on maintenance mode</a>", array("enable_maintenance_mode_url" => url::site("admin/maintenance/maintenance_mode/1?csrf=$csrf"))) ?>
+ </li>
+ <? endif ?>
+ </ul>
+ </div>
+ </div>
+
+ <div class="g-block-content">
+ <div id="g-available-tasks">
+ <h2> <?= t("Maintenance tasks") ?> </h2>
+ <p>
+ <?= t("Occasionally your Gallery will require some maintenance. Here are some tasks you can use to keep it running smoothly.") ?>
+ </p>
+ <table>
+ <tr>
+ <th>
+ <?= t("Name") ?>
+ </th>
+ <th>
+ <?= t("Description") ?>
+ </th>
+ <th>
+ <?= t("Action") ?>
+ </th>
+ </tr>
+ <? foreach ($task_definitions as $task): ?>
+ <tr class="<?= text::alternate("g-odd", "g-even") ?> <?= log::severity_class($task->severity) ?>">
+ <td class="<?= log::severity_class($task->severity) ?>">
+ <?= $task->name ?>
+ </td>
+ <td>
+ <?= $task->description ?>
+ </td>
+ <td>
+ <a href="<?= url::site("admin/maintenance/start/$task->callback?csrf=$csrf") ?>"
+ class="g-dialog-link g-button ui-icon-left ui-state-default ui-corner-all">
+ <?= t("run") ?>
+ </a>
+ </td>
+ </tr>
+ <? endforeach ?>
+ </table>
+ </div>
+
+ <? if ($running_tasks->count()): ?>
+ <div id="g-running-tasks">
+ <a href="<?= url::site("admin/maintenance/cancel_running_tasks?csrf=$csrf") ?>"
+ class="g-button g-right ui-icon-left ui-state-default ui-corner-all">
+ <?= t("cancel all running") ?></a>
+ <h2> <?= t("Running 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 ($running_tasks as $task): ?>
+ <tr class="<?= text::alternate("g-odd", "g-even") ?> <?= $task->state == "stalled" ? "g-warning" : "" ?>">
+ <td class="<?= $task->state == "stalled" ? "g-warning" : "" ?>">
+ <?= gallery::date_time($task->updated) ?>
+ </td>
+ <td>
+ <?= $task->name ?>
+ </td>
+ <td>
+ <? if ($task->done): ?>
+ <? if ($task->state == "cancelled"): ?>
+ <?= t("Cancelled") ?>
+ <? endif ?>
+ <?= t("Close") ?>
+ <? elseif ($task->state == "stalled"): ?>
+ <?= t("Stalled") ?>
+ <? else: ?>
+ <?= t("%percent_complete% Complete", array("percent_complete" => $task->percent_complete)) ?>
+ <? endif ?>
+ </td>
+ <td>
+ <?= $task->status ?>
+ </td>
+ <td>
+ <?= html::clean($task->owner()->name) ?>
+ </td>
+ <td>
+ <? if ($task->state == "stalled"): ?>
+ <a class="g-dialog-link g-button ui-icon-left ui-state-default ui-corner-all"
+ href="<?= url::site("admin/maintenance/resume/$task->id?csrf=$csrf") ?>">
+ <?= t("resume") ?>
+ </a>
+ <? endif ?>
+ <? if ($task->get_log()): ?>
+ <a href="<?= url::site("admin/maintenance/show_log/$task->id?csrf=$csrf") ?>" class="g-dialog-link g-button ui-state-default ui-corner-all">
+ <?= t("view log") ?>
+ </a>
+ <? endif ?>
+ <a href="<?= url::site("admin/maintenance/cancel/$task->id?csrf=$csrf") ?>"
+ class="g-button ui-icon-left ui-state-default ui-corner-all">
+ <?= t("cancel") ?>
+ </a>
+ </td>
+ </tr>
+ <? endforeach ?>
+ </table>
+ </div>
+ <? endif ?>
+
+ <? if ($finished_tasks->count()): ?>
+ <div id="g-finished-tasks">
+ <a href="<?= url::site("admin/maintenance/remove_finished_tasks?csrf=$csrf") ?>"
+ class="g-button g-right ui-icon-left ui-state-default ui-corner-all">
+ <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="<?= text::alternate("g-odd", "g-even") ?> <?= $task->state == "success" ? "g-success" : "g-error" ?>">
+ <td class="<?= $task->state == "success" ? "g-success" : "g-error" ?>">
+ <?= gallery::date_time($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>
+ <?= html::clean($task->owner()->name) ?>
+ </td>
+ <td>
+ <? if ($task->done): ?>
+ <a href="<?= url::site("admin/maintenance/remove/$task->id?csrf=$csrf") ?>" class="g-button ui-state-default ui-corner-all">
+ <?= t("remove") ?>
+ </a>
+ <? if ($task->get_log()): ?>
+ <a href="<?= url::site("admin/maintenance/show_log/$task->id?csrf=$csrf") ?>" class="g-dialog-link g-button ui-state-default ui-corner-all">
+ <?= t("view log") ?>
+ </a>
+ <? endif ?>
+ <? else: ?>
+ <a href="<?= url::site("admin/maintenance/resume/$task->id?csrf=$csrf") ?>" class="g-dialog-link g-button" ui-state-default ui-corner-all>
+ <?= t("resume") ?>
+ </a>
+ <a href="<?= url::site("admin/maintenance/cancel/$task->id?csrf=$csrf") ?>" class="g-button ui-state-default ui-corner-all">
+ <?= t("cancel") ?>
+ </a>
+ <? endif ?>
+ </ul>
+ </td>
+ </tr>
+ <? endforeach ?>
+ </table>
+ </div>
+ <? endif ?>
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_maintenance_show_log.html.php b/modules/gallery/views/admin_maintenance_show_log.html.php
new file mode 100644
index 0000000..ecf882f
--- /dev/null
+++ b/modules/gallery/views/admin_maintenance_show_log.html.php
@@ -0,0 +1,19 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ dismiss = function() {
+ window.location.reload();
+ };
+ download = function() {
+ // send request
+ $('<form action="<?= url::site("admin/maintenance/save_log/$task->id?csrf=$csrf") ?>" method="post"></form>').
+appendTo('body').submit().remove();
+ };
+</script>
+<div id="g-task-log-dialog">
+ <h1> <?= $task->name ?> </h1>
+ <div class="g-task-log g-text-small">
+ <pre><?= html::purify($task->get_log()) ?></pre>
+ </div>
+ <button id="g-close" class="ui-state-default ui-corner-all" onclick="dismiss()"><?= t("Close") ?></button>
+ <button id="g-save" class="ui-state-default ui-corner-all" onclick="download()"><?= t("Download") ?></button>
+</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 0000000..013ac01
--- /dev/null
+++ b/modules/gallery/views/admin_maintenance_task.html.php
@@ -0,0 +1,84 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ var target_value;
+ var animation = null;
+ var delta = 1;
+ var consecutive_error_count = 0;
+ animate_progress_bar = function() {
+ var current_value = parseInt($(".g-progress-bar div").css("width").replace("%", ""));
+ if (target_value > current_value) {
+ // speed up
+ delta = Math.min(delta + 0.04, 3);
+ } else {
+ // slow down
+ delta = Math.max(delta - 0.05, 1);
+ }
+
+ if (target_value == 100) {
+ $(".g-progress-bar").progressbar("value", 100);
+ } else if (current_value != target_value || delta != 1) {
+ var new_value = Math.min(current_value + delta, target_value);
+ $(".g-progress-bar").progressbar("value", new_value);
+ animation = setTimeout(function() { animate_progress_bar(target_value); }, 100);
+ } else {
+ animation = null;
+ delta = 1;
+ }
+ $.fn.gallery_hover_init();
+ }
+
+ var FAILED_MSG = <?= t("Something went wrong...sorry! <a>Retry</a> or check the task log for details")->for_js() ?>;
+ var ERROR_MSG = <?= t("Something went wrong! Trying again in a moment... (__COUNT__)")->for_js() ?>;
+ update = function() {
+ $.ajax({
+ url: <?= html::js_string(url::site("admin/maintenance/run/$task->id?csrf=$csrf")) ?>,
+ dataType: "json",
+ success: function(data) {
+ target_value = data.task.percent_complete;
+ consecutive_error_count = 0;
+ if (!animation) {
+ animate_progress_bar();
+ }
+ $("#g-status").html("" + data.task.status);
+ if (data.task.done) {
+ $("#g-pause-button").hide();
+ $("#g-done-button").show();
+ } else {
+ setTimeout(update, 100);
+ }
+ },
+ error: function(req, textStatus, errorThrown) {
+ if (textStatus == "timeout" || textStatus == "parsererror") {
+ consecutive_error_count++;
+ if (consecutive_error_count == 5) {
+ $("#g-status").html(FAILED_MSG);
+ $("#g-pause-button").hide();
+ $("#g-done-button").show();
+ consecutive_error_count = 0; // in case of a manual retry
+ $("#g-status a").attr("href", "javascript:update()");
+ } else {
+ $("#g-status").html(ERROR_MSG.replace("__COUNT__", consecutive_error_count));
+ // Give a little time to back off before retrying
+ setTimeout(update, 1500 * consecutive_error_count);
+ }
+ }
+ }
+ });
+ }
+ $(".g-progress-bar").progressbar({value: 0});
+ update();
+ dismiss = function() {
+ window.location.reload();
+ }
+</script>
+<div id="g-progress">
+ <h1> <?= $task->name ?> </h1>
+ <div class="g-progress-bar"></div>
+ <div id="g-status">
+ <?= t("Starting up...") ?>
+ </div>
+ <div class="g-text-right">
+ <button id="g-pause-button" class="ui-state-default ui-corner-all" onclick="dismiss()"><?= t("Pause") ?></button>
+ <button id="g-done-button" class="ui-state-default ui-corner-all" style="display: none" onclick="dismiss()"><?= t("Close") ?></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 0000000..96576ae
--- /dev/null
+++ b/modules/gallery/views/admin_modules.html.php
@@ -0,0 +1,129 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div class="g-block ui-helper-clearfix">
+ <script type="text/javascript">
+ $("#g-module-update-form").ready(function() {
+ $("#g-module-update-form").ajaxForm({
+ dataType: "json",
+ success: function(data) {
+ if (data.reload) {
+ window.location = "<? url::site("/admin/modules") ?>";
+ } else {
+ $("body").append('<div id="g-dialog">' + data.dialog + '</div>');
+ $("#g-dialog").dialog({
+ bgiframe: true,
+ autoOpen: true,
+ autoResize: true,
+ modal: true,
+ resizable: false,
+ height: 400,
+ width: 500,
+ position: "center",
+ title: <?= t("Confirm module activation")->for_js() ?>,
+ buttons: {
+ <?= t("Continue")->for_js() ?>: function() {
+ $("form", this).submit();
+ $(".ui-dialog-buttonpane button:contains(" + <?= t("Continue")->for_js() ?> + ")")
+ .attr("disabled", "disabled")
+ .addClass("ui-state-disabled");
+ },
+ <?= t("Cancel")->for_js() ?>: function() {
+ $(this).dialog("destroy").remove();
+ }
+ }
+ });
+ if (!data.allow_continue) {
+ $(".ui-dialog-buttonpane button:contains(" + <?= t("Continue")->for_js() ?> + ")")
+ .attr("disabled", "disabled")
+ .addClass("ui-state-disabled");
+ }
+ }
+ }
+ });
+ });
+ </script>
+ <h1> <?= t("Gallery Modules") ?> </h1>
+ <p>
+ <?= t("Power up your Gallery by <a href=\"%url\">adding more modules</a>! Each module provides new cool features.", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Modules")) ?>
+ </p>
+
+ <? if ($obsolete_modules_message): ?>
+ <p class="g-warning">
+ <?= $obsolete_modules_message ?>
+ </p>
+ <? endif ?>
+
+ <div class="g-block-content">
+ <form id="g-module-update-form" method="post" action="<?= url::site("admin/modules/confirm") ?>">
+ <?= access::csrf_form_field() ?>
+ <table>
+ <tr>
+ <th> <?= t("Installed") ?> </th>
+ <th style="width: 8em"> <?= t("Name") ?> </th>
+ <th> <?= t("Version") ?> </th>
+ <th> <?= t("Description") ?> </th>
+ <th style="width: 60px"> <?= t("Details") ?> </th>
+ </tr>
+ <? foreach ($available as $module_name => $module_info): ?>
+ <tr class="<?= text::alternate("g-odd", "g-even") ?>">
+ <? $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>
+ <td style="white-space: nowrap">
+ <ul class="g-buttonset">
+ <li>
+ <a target="_blank"
+ <? if (isset($module_info->author_url)): ?>
+ class="ui-state-default ui-icon ui-icon-person ui-corner-left"
+ href="<?= $module_info->author_url ?>"
+ <? else: ?>
+ class="ui-state-disabled ui-icon ui-icon-person ui-corner-left"
+ href="#"
+ <? endif ?>
+
+ <? if (isset($module_info->author_name)): ?>
+ title="<?= $module_info->author_name ?>"
+ <? endif ?>
+ >
+ <? if (isset($module_info->author_name)): ?>
+ <?= $module_info->author_name ?>
+ <? endif ?>
+ </a>
+ </li>
+ <li>
+ <a target="_blank"
+ <? if (isset($module_info->info_url)): ?>
+ class="ui-state-default ui-icon ui-icon-info"
+ href="<?= $module_info->info_url ?>"
+ <? else: ?>
+ class="ui-state-disabled ui-icon ui-icon-info"
+ href="#"
+ <? endif ?>
+ >
+ <?= t("info") ?>
+ </a>
+ </li>
+ <li>
+ <a target="_blank"
+ <? if (isset($module_info->discuss_url)): ?>
+ class="ui-state-default ui-icon ui-icon-comment ui-corner-right"
+ href="<?= $module_info->discuss_url ?>"
+ <? else: ?>
+ class="ui-state-disabled ui-icon ui-icon-comment ui-corner-right"
+ href="#"
+ <? endif ?>
+ >
+ <?= t("discuss") ?>
+ </a>
+ </li>
+ </ul>
+ </td>
+ </tr>
+ <? endforeach ?>
+ </table>
+ <input type="submit" value="<?= t("Update")->for_html_attr() ?>" />
+ </form>
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_modules_confirm.html.php b/modules/gallery/views/admin_modules_confirm.html.php
new file mode 100644
index 0000000..8c4cb2b
--- /dev/null
+++ b/modules/gallery/views/admin_modules_confirm.html.php
@@ -0,0 +1,22 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div class="ui-helper-clearfix">
+ <p>
+ <?= t("The following issue(s) have been identified:") ?>
+ </p>
+
+ <div id="g-admin-modules-messages" class="g-block-content">
+ <ul>
+ <? foreach (array("error" => "g-error", "warn" => "g-warning") as $type => $css_class): ?>
+ <? foreach ($messages[$type] as $message): ?>
+ <li class="<?= $css_class ?>" style="padding-bottom: 0"><?= $message ?></li>
+ <? endforeach ?>
+ <? endforeach ?>
+ </ul>
+ <form method="post" action="<?= url::site("admin/modules/save") ?>">
+ <?= access::csrf_form_field() ?>
+ <? foreach ($modules as $module): ?>
+ <?= form::hidden($module, 1) ?>
+ <? endforeach ?>
+ </form>
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_movies.html.php b/modules/gallery/views/admin_movies.html.php
new file mode 100644
index 0000000..abf8fb2
--- /dev/null
+++ b/modules/gallery/views/admin_movies.html.php
@@ -0,0 +1,44 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-movies-admin" class="g-block ui-helper-clearfix">
+ <h1> <?= t("Movies settings") ?> </h1>
+ <p>
+ <?= t("Gallery comes with everything it needs to upload and play movies.") ?>
+ <?= t("However, it needs the FFmpeg toolkit to extract thumbnails and size information from them.") ?>
+ </p>
+ <p>
+ <?= t("Although popular, FFmpeg is not installed on all Linux systems.") ?>
+ <?= t("To use FFmpeg without fully installing it, download a pre-compiled, <b>static build</b> of FFmpeg from one of the links <a href=\"%url\">here</a>.", array("url" => "http://ffmpeg.org/download.html")) ?>
+ <?= t("Then, put the \"ffmpeg\" file in Gallery's \"bin\" directory (e.g. \"/gallery3/bin\"), where Gallery will auto-detect it.") ?>
+ </p>
+ <p>
+ <?= t("Movies will work without FFmpeg, but their thumbnails will be placeholders.") ?>
+ </p>
+ <p>
+ <?= t("Can't get FFmpeg configured on your system? <a href=\"%url\">We can help!</a>",
+ array("url" => "http://codex.galleryproject.org/Gallery3:FAQ#Why_does_it_say_I.27m_missing_ffmpeg.3F")) ?>
+ </p>
+
+ <div class="g-available">
+ <h2> <?= t("Current configuration") ?> </h2>
+ <div id="g-ffmpeg" class="g-block">
+ <img class="logo" width="284" height="70" src="<?= url::file("modules/gallery/images/ffmpeg.png") ?>" alt="<? t("Visit the FFmpeg project site") ?>" />
+ <p>
+ <?= t("FFmpeg is a cross-platform standalone audio/video program.") ?><br/>
+ <?= t("Please refer to the <a href=\"%url\">FFmpeg website</a> for more information.", array("url" => "http://ffmpeg.org")) ?>
+ </p>
+ <div class="g-module-status g-info">
+ <? if ($ffmpeg_dir): ?>
+ <? if ($ffmpeg_version): ?>
+ <p><?= t("FFmpeg version %version was found in %dir", array("version" => $ffmpeg_version, "dir" => $ffmpeg_dir)) ?></p>
+ <? else: ?>
+ <p><?= t("FFmpeg (of unknown version) was found in %dir", array("dir" => $ffmpeg_dir)) ?></p>
+ <? endif ?>
+ <? else: ?>
+ <p><?= t("We could not locate FFmpeg on your system.") ?></p>
+ <? endif ?>
+ </div>
+ </div>
+ </div>
+
+ <?= $form ?>
+</div>
diff --git a/modules/gallery/views/admin_sidebar.html.php b/modules/gallery/views/admin_sidebar.html.php
new file mode 100644
index 0000000..75499cb
--- /dev/null
+++ b/modules/gallery/views/admin_sidebar.html.php
@@ -0,0 +1,64 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ $(document).ready(function(){
+ $(".g-admin-blocks-list").equal_heights();
+ var extra_ht = $(".g-admin-blocks-list li").length * $(".g-admin-blocks-list li:first").height();
+ $(".g-admin-blocks-list").height($(".g-admin-blocks-list").height() + extra_ht);
+ });
+
+ $(function() {
+ $(".g-admin-blocks-list ul").sortable({
+ connectWith: ".g-sortable-blocks",
+ opacity: .7,
+ placeholder: "g-target",
+ update: function(event,ui) {
+ if ($(this).attr("id") == "g-active-blocks") {
+ var active_blocks = "";
+ $("ul#g-active-blocks li").each(function(i) {
+ active_blocks += "&block["+i+"]="+$(this).attr("ref");
+ });
+ $.getJSON($("#g-site-blocks").attr("ref").replace("__ACTIVE__", active_blocks), function(data) {
+ if (data.result == "success") {
+ $("ul#g-available-blocks").html(data.available);
+ $("ul#g-active-blocks").html(data.active);
+ $("#g-action-status").remove();
+ var message = "<ul id=\"g-action-status\" class=\"g-message-block\">";
+ message += "<li class=\"g-success\">" + data.message + "</li>";
+ message += "</ul>";
+ $("#g-block-admin").before(message);
+ $("#g-action-status li").gallery_show_message();
+ }
+ });
+ }
+ }
+ }).disableSelection();
+ });
+</script>
+
+<div id="g-block-admin" class="g-block ui-helper-clearfix">
+ <h1> <?= t("Manage sidebar") ?> </h1>
+ <p>
+ <?= t("Select and drag blocks from the available column to the active column to add to the sidebar; remove by dragging the other way.") ?>
+ </p>
+
+ <div class="g-block-content">
+ <div id="g-site-blocks" ref="<?= url::site("admin/sidebar/update?csrf={$csrf}__ACTIVE__") ?>">
+ <div class="g-admin-blocks-list g-left">
+ <h3><?= t("Available blocks") ?></h3>
+ <div>
+ <ul id="g-available-blocks" class="g-sortable-blocks">
+ <?= $available ?>
+ </ul>
+ </div>
+ </div>
+ <div class="g-admin-blocks-list g-left">
+ <h3><?= t("Active blocks") ?></h3>
+ <div>
+ <ul id="g-active-blocks" class="g-sortable-blocks">
+ <?= $active ?>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_sidebar_blocks.html.php b/modules/gallery/views/admin_sidebar_blocks.html.php
new file mode 100644
index 0000000..48aa3f0
--- /dev/null
+++ b/modules/gallery/views/admin_sidebar_blocks.html.php
@@ -0,0 +1,5 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+
+<? foreach ($blocks as $ref => $text): ?>
+<li class="g-draggable" ref="<?= $ref ?>"><?= $text ?></li>
+<? endforeach ?>
diff --git a/modules/gallery/views/admin_theme_options.html.php b/modules/gallery/views/admin_theme_options.html.php
new file mode 100644
index 0000000..e452913
--- /dev/null
+++ b/modules/gallery/views/admin_theme_options.html.php
@@ -0,0 +1,7 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div class="g-block">
+ <h1> <?= t("Theme options") ?> </h1>
+ <div class="g-block-content">
+ <?= $form ?>
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_themes.html.php b/modules/gallery/views/admin_themes.html.php
new file mode 100644
index 0000000..547f27d
--- /dev/null
+++ b/modules/gallery/views/admin_themes.html.php
@@ -0,0 +1,98 @@
+<?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>
+
+<div class="g-block ui-helper-clearfix">
+ <h1> <?= t("Theme choice") ?> </h1>
+ <p>
+ <?= t("Make your Gallery beautiful <a href=\"%url\">with a new theme</a>! There are separate themes for the regular site and for the administration interface. Click a theme below to preview and activate it.", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Themes")) ?>
+ </p>
+
+ <div class="g-block-content">
+ <div id="g-site-theme">
+ <h2> <?= t("Gallery theme") ?> </h2>
+ <div class="g-block g-selected ui-helper-clearfix">
+ <img src="<?= url::file("themes/{$site}/thumbnail.png") ?>"
+ alt="<?= html::clean_attribute($themes[$site]->name) ?>" />
+ <h3> <?= $themes[$site]->name ?> </h3>
+ <p>
+ <?= $themes[$site]->description ?>
+ </p>
+ <? $v = new View("admin_themes_buttonset.html"); $v->info = $themes[$site]; print $v; ?>
+ </div>
+
+ <h2> <?= t("Available Gallery themes") ?> </h2>
+ <div class="g-available">
+ <? $count = 0 ?>
+ <? foreach ($themes as $id => $info): ?>
+ <? if (!$info->site) continue ?>
+ <? if ($id == $site) continue ?>
+ <div class="g-block ui-helper-clearfix">
+ <a href="<?= url::site("admin/themes/preview/site/$id") ?>" class="g-dialog-link" title="<?= t("Theme Preview: %theme_name", array("theme_name" => $info->name))->for_html_attr() ?>">
+ <img src="<?= url::file("themes/{$id}/thumbnail.png") ?>"
+ alt="<?= html::clean_attribute($info->name) ?>" />
+ <h3> <?= $info->name ?> </h3>
+ <p>
+ <?= $info->description ?>
+ </p>
+ </a>
+ <? $v = new View("admin_themes_buttonset.html"); $v->info = $info; print $v; ?>
+ </div>
+ <? $count++ ?>
+ <? endforeach ?>
+
+ <? if (!$count): ?>
+ <p>
+ <?= t("There are no other site themes available. <a href=\"%url\">Download one now!</a>", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Themes")) ?>
+ </p>
+ <? endif ?>
+ </div>
+ </div>
+
+ <div id="g-admin-theme">
+ <h2> <?= t("Admin theme") ?> </h2>
+ <div class="g-block g-selected ui-helper-clearfix">
+ <img src="<?= url::file("themes/{$admin}/thumbnail.png") ?>"
+ alt="<?= html::clean_attribute($themes[$admin]->name) ?>" />
+ <h3> <?= $themes[$admin]->name ?> </h3>
+ <p>
+ <?= $themes[$admin]->description ?>
+ </p>
+ <? $v = new View("admin_themes_buttonset.html"); $v->info = $themes[$admin]; print $v; ?>
+ </div>
+
+ <h2> <?= t("Available admin themes") ?> </h2>
+ <div class="g-available">
+ <? $count = 0 ?>
+ <? foreach ($themes as $id => $info): ?>
+ <? if (!$info->admin) continue ?>
+ <? if ($id == $admin) continue ?>
+ <div class="g-block ui-helper-clearfix">
+ <a href="<?= url::site("admin/themes/preview/admin/$id") ?>" class="g-dialog-link" title="<?= t("Theme Preview: %theme_name", array("theme_name" => $info->name))->for_html_attr() ?>">
+ <img src="<?= url::file("themes/{$id}/thumbnail.png") ?>"
+ alt="<?= html::clean_attribute($info->name) ?>" />
+ <h3> <?= $info->name ?> </h3>
+ <p>
+ <?= $info->description ?>
+ </p>
+ </a>
+ <? $v = new View("admin_themes_buttonset.html"); $v->info = $info; print $v; ?>
+ </div>
+ <? $count++ ?>
+ <? endforeach ?>
+
+ <? if (!$count): ?>
+ <p>
+ <?= t("There are no other admin themes available. <a href=\"%url\">Download one now!</a>", array("url" => "http://codex.galleryproject.org/Category:Gallery_3:Themes")) ?>
+ </p>
+ <? endif ?>
+ </div>
+ </div>
+
+ </div>
+</div>
diff --git a/modules/gallery/views/admin_themes_buttonset.html.php b/modules/gallery/views/admin_themes_buttonset.html.php
new file mode 100644
index 0000000..bf474a2
--- /dev/null
+++ b/modules/gallery/views/admin_themes_buttonset.html.php
@@ -0,0 +1,48 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<ul class="g-buttonset">
+ <li>
+ <a target="_blank"
+ <? if (isset($info['author_url'])): ?>
+ class="ui-state-default ui-icon ui-icon-person ui-corner-left"
+ href="<?= $info['author_url'] ?>"
+ <? else: ?>
+ class="ui-state-disabled ui-icon ui-icon-person ui-corner-left"
+ href="#"
+ <? endif ?>
+
+ <? if (isset($info['author_name'])): ?>
+ title="<?= $info['author_name'] ?>"
+ <? endif ?>
+ >
+ <? if (isset($info['author_name'])): ?>
+ <?= $info['author_name'] ?>
+ <? endif ?>
+ </a>
+ </li>
+ <li>
+ <a target="_blank"
+ <? if (isset($info['info_url'])): ?>
+ class="ui-state-default ui-icon ui-icon-info"
+ href="<?= $info['info_url'] ?>"
+ <? else: ?>
+ class="ui-state-disabled ui-icon ui-icon-info"
+ href="#"
+ <? endif ?>
+ >
+ <?= t("info") ?>
+ </a>
+ </li>
+ <li>
+ <a target="_blank"
+ <? if (isset($info['discuss_url'])): ?>
+ class="ui-state-default ui-icon ui-icon-comment ui-corner-right"
+ href="<?= $info['discuss_url'] ?>"
+ <? else: ?>
+ class="ui-state-disabled ui-icon ui-icon-comment ui-corner-right"
+ href="#"
+ <? endif ?>
+ >
+ <?= t("discuss") ?>
+ </a>
+ </li>
+</ul>
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 0000000..80a6158
--- /dev/null
+++ b/modules/gallery/views/admin_themes_preview.html.php
@@ -0,0 +1,8 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<h1><?= t("Preview of the %theme_name theme", array("theme_name" => $info->name)) ?></h1>
+<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/error.html.php b/modules/gallery/views/error.html.php
new file mode 100644
index 0000000..5d81b65
--- /dev/null
+++ b/modules/gallery/views/error.html.php
@@ -0,0 +1,12 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-error">
+ <h1>
+ <?= t("Dang... Something went wrong!") ?>
+ </h1>
+ <h2>
+ <?= t("We tried really hard, but it's broken.") ?>
+ </h2>
+ <p>
+ <?= t("Talk to your Gallery administrator for help fixing this!") ?>
+ </p>
+</div> \ No newline at end of file
diff --git a/modules/gallery/views/error_404.html.php b/modules/gallery/views/error_404.html.php
new file mode 100644
index 0000000..42f62b6
--- /dev/null
+++ b/modules/gallery/views/error_404.html.php
@@ -0,0 +1,26 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-error">
+ <h1>
+ <?= t("Dang... Page not found!") ?>
+ </h1>
+ <? if ($is_guest): ?>
+ <h2>
+ <?= t("Hey wait, you're not signed in yet!") ?>
+ </h2>
+ <p>
+ <?= t("Maybe the page exists, but is only visible to authorized users.") ?>
+ <?= t("Please sign in to find out.") ?>
+ </p>
+ <?= $login_form ?>
+ <script type="text/javascript">
+ $(document).ready(function() {
+ $("#g-username").focus();
+ });
+ </script>
+ <? else: ?>
+ <p>
+ <?= t("Maybe the page exists, but is only visible to authorized users.") ?>
+ <?= t("If you think this is an error, talk to your Gallery administrator!") ?>
+ </p>
+ <? endif; ?>
+</div>
diff --git a/modules/gallery/views/error_admin.html.php b/modules/gallery/views/error_admin.html.php
new file mode 100644
index 0000000..036e204
--- /dev/null
+++ b/modules/gallery/views/error_admin.html.php
@@ -0,0 +1,307 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<? $error_id = uniqid("error") ?>
+<? if (!function_exists("t")) { function t($msg) { return $msg; } } ?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <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: 60em;
+ margin: 20px auto;
+ }
+
+ #framework_error {
+ height: 6em;
+ }
+
+ #framework_error .crashlogo {
+ position: relative;
+ top: .3em;
+ font-size: 6.0em;
+ }
+
+ #framework_error .title {
+ position: relative;
+ top: -2.5em;
+ padding: 0px;
+ text-align: center;
+ }
+
+ div#error_details {
+ text-align: left;
+ }
+
+ code {
+ font-family: monospace;
+ font-size: 12px;
+ margin: 20px 20px 20px 0px;
+ color: #333;
+ white-space: pre-wrap;
+ white-space: -moz-pre-wrap;
+ word-wrap: break-word;
+ }
+
+ code .line {
+ padding-left: 10px;
+ }
+
+ 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;
+ }
+
+ .collapsed {
+ display: none;
+ }
+
+ .highlight {
+ font-weight: bold;
+ color: darkred;
+ }
+
+ #kohana_error .message {
+ display: block;
+ padding-bottom: 10px;
+ }
+
+ .source {
+ border: solid 1px #ccc;
+ background: #efe;
+ margin-bottom: 5px;
+ }
+
+ table {
+ width: 100%;
+ display: block;
+ margin: 0 0 0.4em;
+ padding: 0;
+ border-collapse: collapse;
+ background: #efe;
+ }
+
+ table td {
+ border: solid 1px #ddd;
+ text-align: left;
+ vertical-align: top;
+ padding: 0.4em;
+ }
+
+ .args table td.key {
+ width: 200px;
+ }
+
+ .number {
+ padding-right: 1em;
+ }
+
+ #g-platform h2, #g-stats h2 {
+ font-size: 1.1em;
+ }
+ </style>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title><?= t("Something went wrong!") ?></title>
+
+ <script type="text/javascript">
+ function koggle(elem) {
+ elem = document.getElementById(elem);
+ if (elem.style && elem.style["display"]) {
+ // Only works with the "style" attr
+ var disp = elem.style["display"];
+ } else {
+ if (elem.currentStyle) {
+ // For MSIE, naturally
+ var disp = elem.currentStyle["display"];
+ } else {
+ if (window.getComputedStyle) {
+ // For most other browsers
+ var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
+ }
+ }
+ }
+
+ // Toggle the state of the "display" style
+ elem.style.display = disp == 'block' ? 'none' : 'block';
+ return false;
+ }
+ </script>
+ </head>
+ <body>
+ <? try { $user = identity::active_user(); } catch (Exception $e) { } ?>
+ <div class="big_box" id="framework_error">
+ <div class="crashlogo">
+ :-(
+ </div>
+ <div class="title">
+ <h1>
+ <?= t("Dang... Something went wrong!") ?>
+ </h1>
+ <h2>
+ <?= t("We tried really hard, but it's broken.") ?>
+ </h2>
+ </div>
+ </div>
+ <div class="big_box" id="error_details">
+ <h2>
+ <?= t("Hey wait, you're an admin! We can tell you stuff.") ?>
+ </h2>
+ <p>
+ There's an error message below and you can find more details
+ in gallery3/var/logs (look for the file with the most recent
+ date on it). Stuck? Stop by the <a href="http://galleryproject.org/forum/96">Gallery 3
+ Forums</a> and ask for help. You can also look at our list
+ of <a href="http://sourceforge.net/apps/trac/gallery/roadmap">open
+ tickets</a> to see if the problem you're seeing has been
+ reported. If you post a request, here's some useful
+ information to include:
+ <?= @gallery_block::get("platform_info") ?>
+ <?= @gallery_block::get("stats") ?>
+ </p>
+ <div id="kohana_error">
+ <h3>
+ <span class="type">
+ <?= $type?> [ <?= $code ?> ]:
+ </span>
+ <span class="message">
+ <?= html::purify($message) ?>
+ </span>
+ </h3>
+ <div id="<?= $error_id ?>" class="content">
+ <ol class="trace">
+ <li class="snippet">
+ <p>
+ <span class="file">
+ <?= Kohana_Exception::debug_path($file)?>[ <?= $line?> ]
+ </span>
+ </p>
+
+ <div class="source">
+ <? if (Kohana_Exception::$source_output and $source_code = Kohana_Exception::debug_source($file, $line)): ?><code><? foreach ($source_code as $num => $row): ?><span class="line <?= ($num == $line) ? "highlight" : ""?>"><span class="number"><?= $num ?></span><?= htmlspecialchars($row, ENT_NOQUOTES, Kohana::CHARSET) ?></span><? endforeach ?></code>
+ <? endif ?>
+ </div>
+ </li>
+
+ <? if (Kohana_Exception::$trace_output): ?>
+ <? foreach (Kohana_Exception::trace($trace) as $i => $step): ?>
+ <li class="snippet">
+ <p>
+ <span class="file">
+ <? if ($step["file"]): $source_id = "$error_id.source.$i" ?>
+ <? if (Kohana_Exception::$source_output and $step["source"]): ?>
+ <a href="#<?= $source_id ?>" onclick="return koggle('<?= $source_id ?>')"><?= Kohana_Exception::debug_path($step["file"])?>[ <?= $step["line"]?> ]</a>
+ <? else: ?>
+ <span class="file"><?= Kohana_Exception::debug_path($step["file"])?>[ <?= $step["line"]?> ]</span>
+ <? endif ?>
+ <? else: ?>
+ {<?= t("PHP internal call")?>}
+ <? endif?>
+ </span>
+ &raquo;
+ <?= $step["function"]?>(<? if ($step["args"]): $args_id = "$error_id.args.$i" ?>
+ <a href="#<?= $args_id ?>" onclick="return koggle('<?= $args_id ?>')"><?= t("arguments")?></a>
+ <? endif?>)
+ </p>
+ <? if (isset($args_id)): ?>
+ <div id="<?= $args_id ?>" class="args collapsed">
+ <table cellspacing="0">
+ <? foreach ($step["args"] as $name => $arg): ?>
+ <tr>
+ <td class="key">
+ <pre><?= $name?></pre>
+ </td>
+ <td class="value">
+ <pre><?= Kohana_Exception::safe_dump($arg, $name) ?></pre>
+ </td>
+ </tr>
+ <? endforeach?>
+ </table>
+ </div>
+ <? endif?>
+ <? if (Kohana_Exception::$source_output and $step["source"] and isset($source_id)): ?>
+ <pre id="<?= $source_id ?>" class="source collapsed"><code><? foreach ($step["source"] as $num => $row): ?><span class="line <?= ($num == $step["line"]) ? "highlight" : "" ?>"><span class="number"><?= $num ?></span><?= htmlspecialchars($row, ENT_NOQUOTES, Kohana::CHARSET) ?></span><? endforeach ?></code></pre>
+ <? endif?>
+ </li>
+ <? unset($args_id, $source_id) ?>
+ <? endforeach?>
+ </ol>
+ <? endif ?>
+
+ </div>
+ <h2>
+ <a href="#<?= $env_id = $error_id."environment" ?>" onclick="return koggle('<?= $env_id ?>')"><?= t("Environment")?></a>
+ </h2>
+ <div id="<?= $env_id ?>" class="content collapsed">
+ <? $included = get_included_files()?>
+ <h3><a href="#<?= $env_id = $error_id."environment_included" ?>" onclick="return koggle('<?= $env_id ?>')"><?= t("Included files")?></a>(<?= count($included)?>)</h3>
+ <div id="<?= $env_id ?>" class="collapsed">
+ <table cellspacing="0">
+ <? foreach ($included as $file): ?>
+ <tr>
+ <td>
+ <pre><?= Kohana_Exception::debug_path($file)?></pre>
+ </td>
+ </tr>
+ <? endforeach?>
+ </table>
+ </div>
+ <? $included = get_loaded_extensions()?>
+ <h3><a href="#<?= $env_id = $error_id."environment_loaded" ?>" onclick="return koggle('<?= $env_id ?>')"><?= t("Loaded extensions")?></a>(<?= count($included)?>)</h3>
+ <div id="<?= $env_id ?>" class="collapsed">
+ <table cellspacing="0">
+ <? foreach ($included as $file): ?>
+ <tr>
+ <td>
+ <pre><?= Kohana_Exception::debug_path($file)?></pre>
+ </td>
+ </tr>
+ <? endforeach?>
+ </table>
+ </div>
+ <? foreach (array("_SESSION", "_GET", "_POST", "_FILES", "_COOKIE", "_SERVER") as $var): ?>
+ <? if ( empty($GLOBALS[$var]) OR ! is_array($GLOBALS[$var])) continue ?>
+ <h3><a href="#<?= $env_id = "$error_id.environment" . strtolower($var) ?>"
+ onclick="return koggle('<?= $env_id ?>')">$<?= $var?></a></h3>
+ <div id="<?= $env_id ?>" class="collapsed">
+ <table cellspacing="0">
+ <? foreach ($GLOBALS[$var] as $key => $value): ?>
+ <tr>
+ <td class="key">
+ <code>
+ <?= html::purify($key) ?>
+ </code>
+ </td>
+ <td class="value">
+ <pre><?= Kohana_Exception::safe_dump($value, $key) ?></pre>
+ </td>
+ </tr>
+ <? endforeach?>
+ </table>
+ </div>
+ <? endforeach?>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/modules/gallery/views/error_cli.txt.php b/modules/gallery/views/error_cli.txt.php
new file mode 100644
index 0000000..9f476f5
--- /dev/null
+++ b/modules/gallery/views/error_cli.txt.php
@@ -0,0 +1,3 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<? echo Kohana_Exception::text($e), "\n";
+
diff --git a/modules/gallery/views/error_user.html.php b/modules/gallery/views/error_user.html.php
new file mode 100644
index 0000000..09ab752
--- /dev/null
+++ b/modules/gallery/views/error_user.html.php
@@ -0,0 +1,60 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<? if (!function_exists("t")) { function t($msg) { return $msg; } } ?>
+<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: 60em;
+ margin: 20px auto;
+ }
+
+ #framework_error {
+ height: 8em;
+ }
+
+ #framework_error .crashlogo {
+ position: relative;
+ top: .3em;
+ font-size: 6em;
+ }
+
+ #framework_error .title {
+ position: relative;
+ top: -3em;
+ text-align: center;
+ margin: 0 auto;
+ }
+ </style>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title><?= t("Something went wrong!") ?></title>
+ </head>
+ <body>
+ <div class="big_box" id="framework_error">
+ <div class="crashlogo">
+ :-(
+ </div>
+ <div class="title">
+ <h1>
+ <?= t("Dang... Something went wrong!") ?>
+ </h1>
+ <h2>
+ <?= t("We tried really hard, but it's broken.") ?>
+ </h2>
+ <p>
+ <?= t("Talk to your Gallery administrator for help fixing this!") ?>
+ </p>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/modules/gallery/views/form.html.php b/modules/gallery/views/form.html.php
new file mode 100644
index 0000000..abc3216
--- /dev/null
+++ b/modules/gallery/views/form.html.php
@@ -0,0 +1,77 @@
+<?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->type == 'script') {
+ print $input->render();
+ } else {
+ if ($input->error_messages()) {
+ print "$prefix<li class=\"g-error\">\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=\"g-message g-error\">\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/form_uploadify.html.php b/modules/gallery/views/form_uploadify.html.php
new file mode 100644
index 0000000..bba6db7
--- /dev/null
+++ b/modules/gallery/views/form_uploadify.html.php
@@ -0,0 +1,167 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript" src="<?= url::file("lib/swfobject.js") ?>"></script>
+<script type="text/javascript" src="<?= url::file("lib/uploadify/jquery.uploadify.min.js") ?>"></script>
+<script type="text/javascript">
+ <? $flash_minimum_version = "9.0.24" ?>
+ var success_count = 0;
+ var error_count = 0;
+ var updating = 0;
+ $("#g-add-photos-canvas").ready(function () {
+ var update_status = function() {
+ if (updating) {
+ // poor man's mutex
+ setTimeout(function() { update_status(); }, 500);
+ }
+ updating = 1;
+ $.get("<?= url::site("uploader/status/_S/_E") ?>"
+ .replace("_S", success_count).replace("_E", error_count),
+ function(data) {
+ $("#g-add-photos-status-message").html(data);
+ updating = 0;
+ });
+ };
+
+ if (swfobject.hasFlashPlayerVersion("<?= $flash_minimum_version ?>")) {
+ $("#g-uploadify").uploadify({
+ width: 298,
+ height: 32,
+ uploader: "<?= url::file("lib/uploadify/uploadify.swf.php") ?>",
+ script: "<?= url::site("uploader/add_photo/{$album->id}") ?>",
+ scriptData: <?= json_encode($script_data) ?>,
+ fileExt: "<?= implode(";", $extensions) ?>",
+ fileDesc: <?= t("Photos and movies")->for_js() ?>,
+ cancelImg: "<?= url::file("lib/uploadify/cancel.png") ?>",
+ simUploadLimit: <?= $simultaneous_upload_limit ?>,
+ sizeLimit: <?= $size_limit_bytes ?>,
+ wmode: "transparent",
+ hideButton: true, /* should be true */
+ auto: true,
+ multi: true,
+ onAllComplete: function(filesUploaded, errors, allbytesLoaded, speed) {
+ $("#g-upload-cancel-all")
+ .addClass("ui-state-disabled")
+ .attr("disabled", "disabled");
+ $("#g-upload-done")
+ .removeClass("ui-state-disabled")
+ .attr("disabled", null);
+ return true;
+ },
+ onClearQueue: function(event) {
+ $("#g-upload-cancel-all")
+ .addClass("ui-state-disabled")
+ .attr("disabled", "disabled");
+ $("#g-upload-done")
+ .removeClass("ui-state-disabled")
+ .attr("disabled", null);
+ return true;
+ },
+ onComplete: function(event, queueID, fileObj, response, data) {
+ var re = /^error: (.*)$/i;
+ var msg = re.exec(response);
+ $("#g-add-photos-status ul").append(
+ "<li id=\"q" + queueID + "\" class=\"g-success\"><span></span> - " +
+ <?= t("Completed")->for_js() ?> + "</li>");
+ $("#g-add-photos-status li#q" + queueID + " span").text(fileObj.name);
+ setTimeout(function() { $("#q" + queueID).slideUp("slow").remove() }, 5000);
+ success_count++;
+ update_status();
+ return true;
+ },
+ onError: function(event, queueID, fileObj, errorObj) {
+ if (errorObj.type == "HTTP") {
+ if (errorObj.info == "500") {
+ error_msg = <?= t("Unable to process this photo")->for_js() ?>;
+ } else if (errorObj.info == "404") {
+ error_msg = <?= t("The upload script was not found")->for_js() ?>;
+ } else if (errorObj.info == "400") {
+ error_msg = <?= t("This photo is too large (max is %size bytes)",
+ array("size" => $size_limit))->for_js() ?>;
+ } else {
+ msg += (<?= t("Server error: __INFO__ (__TYPE__)")->for_js() ?>
+ .replace("__INFO__", errorObj.info)
+ .replace("__TYPE__", errorObj.type));
+ }
+ } else if (errorObj.type == "File Size") {
+ error_msg = <?= t("This photo is too large (max is %size bytes)",
+ array("size" => $size_limit))->for_js() ?>;
+ } else {
+ error_msg = <?= t("Server error: __INFO__ (__TYPE__)")->for_js() ?>
+ .replace("__INFO__", errorObj.info)
+ .replace("__TYPE__", errorObj.type);
+ }
+ msg = " - <a target=\"_blank\" href=\"http://codex.galleryproject.org/Gallery3:Troubleshooting:Uploading\">" +
+ error_msg + "</a>";
+
+ $("#g-add-photos-status ul").append(
+ "<li id=\"q" + queueID + "\" class=\"g-error\"><span></span>" + msg + "</li>");
+ $("#g-add-photos-status li#q" + queueID + " span").text(fileObj.name);
+ $("#g-uploadify").uploadifyCancel(queueID);
+ error_count++;
+ update_status();
+ },
+ onSelect: function(event) {
+ if ($("#g-upload-cancel-all").hasClass("ui-state-disabled")) {
+ $("#g-upload-cancel-all")
+ .removeClass("ui-state-disabled")
+ .attr("disabled", null);
+ $("#g-upload-done")
+ .addClass("ui-state-disabled")
+ .attr("disabled", "disabled");
+ }
+ return true;
+ }
+ });
+ } else {
+ $(".requires-flash").hide();
+ $(".no-flash").show();
+ }
+ });
+</script>
+
+<div class="requires-flash">
+ <? if ($suhosin_session_encrypt || (identity::active_user()->admin && !$movies_allowed)): ?>
+ <div class="g-message-block g-info">
+ <? if ($suhosin_session_encrypt): ?>
+ <p class="g-error">
+ <?= 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/")) ?>
+ </p>
+ <? endif ?>
+
+ <? if (identity::active_user()->admin && !$movies_allowed): ?>
+ <p class="g-warning">
+ <?= t("Movie uploading is disabled on your system. <a href=\"%help_url\">Help!</a>", array("help_url" => url::site("admin/movies"))) ?>
+ </p>
+ <? endif ?>
+ </div>
+ <? endif ?>
+
+ <div>
+ <ul class="g-breadcrumbs">
+ <? foreach ($album->parents() as $i => $parent): ?>
+ <li<? if ($i == 0) print " class=\"g-first\"" ?>> <?= html::clean($parent->title) ?> </li>
+ <? endforeach ?>
+ <li class="g-active"> <?= html::purify($album->title) ?> </li>
+ </ul>
+ </div>
+
+ <div id="g-add-photos-canvas">
+ <button id="g-add-photos-button" class="g-button ui-state-default ui-corner-all" href="#"><?= t("Select photos (%size max per file)...", array("size" => $size_limit)) ?></button>
+ <span id="g-uploadify"></span>
+ </div>
+ <div id="g-add-photos-status">
+ <ul id="g-action-status" class="g-message-block">
+ </ul>
+ </div>
+</div>
+
+<div class="no-flash" style="display: none">
+ <p>
+ <?= t("Your browser must have Adobe Flash Player version %flash_minimum_version or greater installed to use this feature.", array("flash_minimum_version" => $flash_minimum_version)) ?>
+ </p>
+ <a href="http://www.adobe.com/go/getflashplayer">
+ <img src="<?= request::protocol() ?>://www.adobe.com/images/shared/download_buttons/get_flash_player.gif"
+ alt=<?= t("Get Adobe Flash Player")->for_js() ?> />
+ </a>
+</div>
diff --git a/modules/gallery/views/form_uploadify_buttons.html.php b/modules/gallery/views/form_uploadify_buttons.html.php
new file mode 100644
index 0000000..e002d82
--- /dev/null
+++ b/modules/gallery/views/form_uploadify_buttons.html.php
@@ -0,0 +1,11 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div class="requires-flash">
+ <!-- Proxy the done request back to our form, since its been ajaxified -->
+ <button id="g-upload-done" class="ui-state-default ui-corner-all" onclick="$('#g-add-photos-form').submit();return false;">
+ <?= t("Done") ?>
+ </button>
+ <button id="g-upload-cancel-all" class="ui-state-default ui-corner-all ui-state-disabled" onclick="$('#g-uploadify').uploadifyClearQueue();return false;" disabled="disabled">
+ <?= t("Cancel uploads") ?>
+ </button>
+ <span id="g-add-photos-status-message" />
+</div>
diff --git a/modules/gallery/views/in_place_edit.html.php b/modules/gallery/views/in_place_edit.html.php
new file mode 100644
index 0000000..2d6cbe9
--- /dev/null
+++ b/modules/gallery/views/in_place_edit.html.php
@@ -0,0 +1,21 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<?= form::open($action, array("method" => "post", "id" => "g-in-place-edit-form", "class" => "g-short-form")) ?>
+ <?= access::csrf_form_field() ?>
+ <ul>
+ <li<? if (!empty($errors["input"])): ?> class="g-error"<? endif ?>>
+ <?= form::input("input", $form["input"], " class=\"textbox\"") ?>
+ </li>
+ <li>
+ <?= form::submit(array("class" => "submit ui-state-default"), t("Save")) ?>
+ </li>
+ <li><a href="#" class="g-cancel"><?= t("Cancel") ?></a></li>
+ <? if (!empty($errors["input"])): ?>
+ <li>
+ <p id="g-in-place-edit-message" class="g-error"><?= $errors["input"] ?></p>
+ </li>
+ <? endif ?>
+ </ul>
+</form>
+
+
+
diff --git a/modules/gallery/views/kohana/error.php b/modules/gallery/views/kohana/error.php
new file mode 100644
index 0000000..0e84f09
--- /dev/null
+++ b/modules/gallery/views/kohana/error.php
@@ -0,0 +1,46 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<?
+// This is the template for all HTML errors. If you're throwing an exception and you want your
+// error to appear differently, extend Kohana_Exception and specify a different template.
+
+// Log validation exceptions to ease debugging
+if ($e instanceof ORM_Validation_Exception) {
+ Kohana_Log::add("error", "Validation errors: " . print_r($e->validation->errors(), 1));
+}
+
+if (php_sapi_name() == "cli") {
+ include Kohana::find_file("views", "error_cli.txt");
+ return;
+}
+
+try {
+ // Admins get a special error page
+ $user = identity::active_user();
+ if ($user && $user->admin) {
+ include Kohana::find_file("views", "error_admin.html");
+ return;
+ }
+} catch (Exception $ignored) {
+}
+
+// Try to show a themed error page for 404 errors
+if ($e instanceof Kohana_404_Exception) {
+ if (Router::$controller == "file_proxy") {
+ print "File not found";
+ } else {
+ $view = new Theme_View("page.html", "other", "error");
+ $view->page_title = t("Dang... Page not found!");
+ $view->content = new View("error_404.html");
+ $user = identity::active_user();
+ $view->content->is_guest = $user && $user->guest;
+ if ($view->content->is_guest) {
+ $view->content->login_form = new View("login_ajax.html");
+ $view->content->login_form->form = auth::get_login_form("login/auth_html");
+ }
+ print $view;
+ }
+ return;
+}
+
+header("HTTP/1.1 500 Internal Server Error");
+include Kohana::find_file("views", "error_user.html");
diff --git a/modules/gallery/views/kohana_profiler.php b/modules/gallery/views/kohana_profiler.php
new file mode 100644
index 0000000..c753434
--- /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 0000000..47a45e2
--- /dev/null
+++ b/modules/gallery/views/l10n_client.html.php
@@ -0,0 +1,82 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="l10n-client" class="hidden">
+ <div class="labels">
+ <span id="l10n-client-toggler">
+ <a id="g-minimize-l10n">_</a>
+ <a id="g-close-l10n" title="<?= t("Stop the translation mode")->for_html_attr() ?>"
+ href="<?= html::clean_attribute(url::site("l10n_client/toggle_l10n_mode?csrf=".access::csrf_token())) ?>">X</a>
+ </span>
+ <div class="label strings"><h2><?= t("Page text") ?>
+ <? if (!Input::instance()->get('show_all_l10n_messages')): ?>
+ <a style="background-color:#fff" href="<?= url::site("admin/languages?show_all_l10n_messages=1") ?>"><?= t("(Show all)") ?></a>
+ <? endif; ?>
+ </h2></div>
+ <div class="label source"><h2><?= t("Source") ?></div>
+ <div class="label translation"><h2><?= t("Translation to %language",
+ array("language" => locales::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" ?>">
+ <? if (is_array($string["source"])): ?>
+ [one] - <?= $string["source"]["one"] ?><br/>
+ [other] - <?= $string["source"]["other"] ?>
+ <? else: ?>
+ <?= $string["source"] ?>
+ <? endif; ?>
+ </li>
+ <? endforeach; ?>
+ </ul>
+
+ <?= $l10n_search_form ?>
+ </div>
+ <div id="l10n-client-string-editor">
+ <div class="source">
+ <p class="source-text"></p>
+ <p id="source-text-tmp-space" style="display:none"></p>
+ </div>
+ <div class="translation">
+ <form method="post" action="<?= url::site("l10n_client/save") ?>" id="g-l10n-client-save-form">
+ <?= access::csrf_form_field() ?>
+ <?= form::hidden("l10n-message-key") ?>
+ <?= form::textarea("l10n-edit-translation", "", ' id="l10n-edit-translation" rows="5" class="translationField"') ?>
+ <div id="plural-zero" class="translationField hidden">
+ <label for="l10n-edit-plural-translation-zero">[zero]</label>
+ <?= form::textarea("l10n-edit-plural-translation-zero", "", ' rows="2"') ?>
+ </div>
+ <div id="plural-one" class="translationField hidden">
+ <label for="l10n-edit-plural-translation-one">[one]</label>
+ <?= form::textarea("l10n-edit-plural-translation-one", "", ' rows="2"') ?>
+ </div>
+ <div id="plural-two" class="translationField hidden">
+ <label for="l10n-edit-plural-translation-two">[two]</label>
+ <?= form::textarea("l10n-edit-plural-translation-two", "", ' rows="2"') ?>
+ </div>
+ <div id="plural-few" class="translationField hidden">
+ <label for="l10n-edit-plural-translation-few">[few]</label>
+ <?= form::textarea("l10n-edit-plural-translation-few", "", ' rows="2"') ?>
+ </div>
+ <div id="plural-many" class="translationField hidden">
+ <label for="l10n-edit-plural-translation-many">[many]</label>
+ <?= form::textarea("l10n-edit-plural-translation-many", "", ' rows="2"') ?>
+ </div>
+ <div id="plural-other" class="translationField hidden">
+ <label for="l10n-edit-plural-translation-other">[other]</label>
+ (<a href="http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html"><?= t("learn more about plural forms") ?></a>)
+ <?= form::textarea("l10n-edit-plural-translation-other", "", ' rows="2"') ?>
+ </div>
+ <input type="submit" name="l10n-edit-save" value="<?= t("Save translation")->for_html_attr() ?>"/>
+ <a href="javascript: Gallery.l10nClient.copySourceText()"
+ class="g-button ui-state-default ui-corner-all"><?= t("Copy source text") ?></a>
+ </form>
+ </div>
+ </div>
+ <script type="text/javascript">
+ var MSG_TRANSLATE_TEXT = <?= t("Translate text")->for_js() ?>;
+ var l10n_client_data = <?= json_encode($string_list) ?>;
+ var plural_forms = <?= json_encode($plural_forms) ?>;
+ var toggle_l10n_mode_url = <?= html::js_string(url::site("l10n_client/toggle_l10n_mode")) ?>;
+ var csrf = <?= html::js_string(access::csrf_token()) ?>;
+ </script>
+</div>
diff --git a/modules/gallery/views/login_ajax.html.php b/modules/gallery/views/login_ajax.html.php
new file mode 100644
index 0000000..a40d195
--- /dev/null
+++ b/modules/gallery/views/login_ajax.html.php
@@ -0,0 +1,52 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ $("#g-login-form").ready(function() {
+ $("#g-password-reset").click(function() {
+ $.ajax({
+ url: "<?= url::site("password/reset") ?>",
+ success: function(data) {
+ $("#g-login").html(data);
+ $("#ui-dialog-title-g-dialog").html(<?= t("Reset password")->for_js() ?>);
+ $(".submit").addClass("g-button ui-state-default ui-corner-all");
+ $(".submit").gallery_hover_init();
+ ajaxify_login_reset_form();
+
+ // See comment about IE7 below
+ setTimeout('$("#g-name").focus()', 100);
+ }
+ });
+ });
+
+ // Setting the focus here doesn't work on IE7, perhaps because the field is
+ // not ready yet? So set a timeout and do it the next time we're idle
+ setTimeout('$("#g-username").focus()', 100);
+ });
+
+ function ajaxify_login_reset_form() {
+ $("#g-login form").ajaxForm({
+ dataType: "json",
+ success: function(data) {
+ if (data.form) {
+ $("#g-login form").replaceWith(data.form);
+ ajaxify_login_reset_form();
+ }
+ if (data.result == "success") {
+ $("#g-dialog").dialog("close");
+ window.location.reload();
+ }
+ }
+ });
+ };
+</script>
+<div id="g-login">
+ <ul>
+ <li id="g-login-form">
+ <?= $form ?>
+ </li>
+ <? if (identity::is_writable() && !module::get_var("gallery", "maintenance_mode")): ?>
+ <li>
+ <a href="#" id="g-password-reset" class="g-right g-text-small"><?= t("Forgot your password?") ?></a>
+ </li>
+ <? endif ?>
+ </ul>
+</div>
diff --git a/modules/gallery/views/login_current_user.html.php b/modules/gallery/views/login_current_user.html.php
new file mode 100644
index 0000000..9452557
--- /dev/null
+++ b/modules/gallery/views/login_current_user.html.php
@@ -0,0 +1,7 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<li>
+ <? $name = $menu->label->for_html() ?>
+ <? $hover_text = t("Your profile")->for_html_attr() ?>
+ <?= t("Logged in as %name", array("name" => html::mark_clean(
+ "<a href='$menu->url' title='$hover_text' id='$menu->id'>{$name}</a>"))) ?>
+</li>
diff --git a/modules/gallery/views/menu.html.php b/modules/gallery/views/menu.html.php
new file mode 100644
index 0000000..17a249d
--- /dev/null
+++ b/modules/gallery/views/menu.html.php
@@ -0,0 +1,24 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<? if (!$menu->is_empty()): // Don't show the menu if it has no choices ?>
+<? if ($menu->is_root): ?>
+<ul <?= $menu->css_id ? "id='$menu->css_id'" : "" ?> class="<?= $menu->css_class ?>">
+ <? foreach ($menu->elements as $element): ?>
+ <?= $element->render() ?>
+ <? endforeach ?>
+</ul>
+
+<? else: ?>
+
+<li title="<?= $menu->label->for_html_attr() ?>">
+ <a href="#">
+ <?= $menu->label->for_html() ?>
+ </a>
+ <ul>
+ <? foreach ($menu->elements as $element): ?>
+ <?= $element->render() ?>
+ <? endforeach ?>
+ </ul>
+</li>
+
+<? endif ?>
+<? endif ?>
diff --git a/modules/gallery/views/menu_ajax_link.html.php b/modules/gallery/views/menu_ajax_link.html.php
new file mode 100644
index 0000000..06cd6f9
--- /dev/null
+++ b/modules/gallery/views/menu_ajax_link.html.php
@@ -0,0 +1,10 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<li>
+ <a <?= $menu->css_id ? "id='{$menu->css_id}'" : "" ?>
+ class="g-ajax-link <?= $menu->css_class ?>"
+ href="<?= $menu->url ?>"
+ title="<?= $menu->label->for_html_attr() ?>"
+ ajax_handler="<?= $menu->ajax_handler ?>">
+ <?= $menu->label->for_html() ?>
+ </a>
+</li>
diff --git a/modules/gallery/views/menu_dialog.html.php b/modules/gallery/views/menu_dialog.html.php
new file mode 100644
index 0000000..b44080c
--- /dev/null
+++ b/modules/gallery/views/menu_dialog.html.php
@@ -0,0 +1,9 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<li>
+ <a <?= $menu->css_id ? "id='{$menu->css_id}'" : "" ?>
+ class="g-dialog-link <?= $menu->css_class ?>"
+ href="<?= $menu->url ?>"
+ title="<?= $menu->label->for_html_attr() ?>">
+ <?= $menu->label->for_html() ?>
+ </a>
+</li>
diff --git a/modules/gallery/views/menu_link.html.php b/modules/gallery/views/menu_link.html.php
new file mode 100644
index 0000000..a36d275
--- /dev/null
+++ b/modules/gallery/views/menu_link.html.php
@@ -0,0 +1,9 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<li>
+ <a <?= $menu->css_id ? "id='{$menu->css_id}'" : "" ?>
+ class="g-menu-link <?= $menu->css_class ?>"
+ href="<?= $menu->url ?>"
+ title="<?= $menu->label->for_html_attr() ?>">
+ <?= $menu->label->for_html() ?>
+ </a>
+</li>
diff --git a/modules/gallery/views/movieplayer.html.php b/modules/gallery/views/movieplayer.html.php
new file mode 100644
index 0000000..3cf5d17
--- /dev/null
+++ b/modules/gallery/views/movieplayer.html.php
@@ -0,0 +1,50 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<?= html::anchor($url, "", $attrs) ?>
+<script type="text/javascript">
+ var id = "<?= $attrs["id"] ?>";
+ var max_size = <?= $max_size ?>;
+ // set the size of the movie html anchor, taking into account max_size and height of control bar
+ function set_movie_size(width, height) {
+ if((width > max_size) || (height > max_size)) {
+ if (width > height) {
+ height = Math.ceil(height * max_size / width);
+ width = max_size;
+ } else {
+ width = Math.ceil(width * max_size / height);
+ height = max_size;
+ }
+ }
+ height += flowplayer(id).getConfig().plugins.controls.height;
+ $("#" + id).css({width: width, height: height});
+ };
+ // setup flowplayer
+ flowplayer(id,
+ $.extend(true, {
+ "src": "<?= url::abs_file("lib/flowplayer.swf.php") ?>",
+ "wmode": "transparent",
+ "provider": "pseudostreaming"
+ }, <?= json_encode($fp_params) ?>),
+ $.extend(true, {
+ "plugins": {
+ "pseudostreaming": {
+ "url": "<?= url::abs_file("lib/flowplayer.pseudostreaming-byterange.swf.php") ?>"
+ },
+ "controls": {
+ "autoHide": "always",
+ "hideDelay": 2000,
+ "height": 24,
+ "url": "<?= url::abs_file("lib/flowplayer.controls.swf.php") ?>"
+ }
+ },
+ "clip": {
+ "scaling": "fit",
+ "onMetaData": function(clip) {
+ // set movie size a second time using actual size from metadata
+ set_movie_size(parseInt(clip.metaData.width), parseInt(clip.metaData.height));
+ }
+ }
+ }, <?= json_encode($fp_config) ?>)
+ ).ipad();
+ // set movie size using width and height passed from movie_img function
+ $("document").ready(set_movie_size(<?= $width ?>, <?= $height ?>));
+</script>
diff --git a/modules/gallery/views/permissions_browse.html.php b/modules/gallery/views/permissions_browse.html.php
new file mode 100644
index 0000000..0b27336
--- /dev/null
+++ b/modules/gallery/views/permissions_browse.html.php
@@ -0,0 +1,62 @@
+<?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) {
+ $("#g-edit-permissions-form").html(data);
+ $(".g-active").removeClass("g-active");
+ $("#item-" + id).addClass("g-active");
+ }
+ });
+ }
+
+ 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) {
+ $("#g-edit-permissions-form").load(form_url.replace("__ITEM__", item_id));
+ }
+ });
+ }
+</script>
+<div id="g-permissions">
+ <? if (!$htaccess_works): ?>
+ <ul id="g-action-status" class="g-message-block">
+ <li class="g-error">
+ <?= t("Oh no! Your server needs a configuration change in order for you to hide photos! Ask your server administrator to enable <a %mod_rewrite_attrs>mod_rewrite</a> and set <a %apache_attrs><i>AllowOverride FileInfo Options</i></a> to fix this.",
+ array("mod_rewrite_attrs" => html::mark_clean('href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html" target="_blank"'),
+ "apache_attrs" => html::mark_clean('href="http://httpd.apache.org/docs/2.0/mod/core.html#allowoverride" target="_blank"'))) ?>
+ </li>
+ </ul>
+ <? endif ?>
+
+ <p><?= t("Edit permissions for album:") ?></p>
+
+ <ul class="g-breadcrumbs">
+ <? $i = 0 ?>
+ <? foreach ($parents as $parent): ?>
+ <li id="item-<?= $parent->id ?>"<? if ($i == 0) print " class=\"g-first\"" ?>>
+ <? if (access::can("edit", $parent)): ?>
+ <a href="javascript:show(<?= $parent->id ?>)"> <?= html::purify($parent->title) ?> </a>
+ <? else: ?>
+ <?= html::purify($parent->title) ?>
+ <? endif ?>
+ </li>
+ <? $i++ ?>
+ <? endforeach ?>
+ <li class="g-active" id="item-<?= $item->id ?>">
+ <a href="javascript:show(<?= $item->id ?>)">
+ <?= html::purify($item->title) ?>
+ </a>
+ </li>
+ </ul>
+
+ <div id="g-edit-permissions-form">
+ <?= $form ?>
+ </div>
+</div>
diff --git a/modules/gallery/views/permissions_form.html.php b/modules/gallery/views/permissions_form.html.php
new file mode 100644
index 0000000..f171411
--- /dev/null
+++ b/modules/gallery/views/permissions_form.html.php
@@ -0,0 +1,92 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<fieldset>
+ <legend> <?= t('Edit Permissions') ?> </legend>
+ <table>
+ <tr>
+ <th> </th>
+ <? foreach ($groups as $group): ?>
+ <th> <?= html::clean($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="g-denied">
+ <img src="<?= url::file(gallery::find_file("images", "ico-denied.png")) ?>"
+ title="<?= t('denied and locked through parent album')->for_html_attr() ?>"
+ alt="<?= t('denied icon')->for_html_attr() ?>" />
+ <a href="javascript:show(<?= $lock->id ?>)" title="<?= t('click to go to parent album')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-lock.png")) ?>" alt="<?= t('locked icon')->for_html_attr() ?>" />
+ </a>
+ </td>
+ <? else: ?>
+ <? if ($intent === access::INHERIT): ?>
+ <? if ($allowed): ?>
+ <td class="g-allowed">
+ <a href="javascript:set('allow',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)" title="<?= t('allowed through parent album, click to allow explicitly')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-success-passive.png")) ?>" alt="<?= t('passive allowed icon')->for_html_attr() ?>" />
+ </a>
+ <a href="javascript:set('deny',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('click to deny')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-denied-inactive.png")) ?>" alt="<?= t('inactive denied icon')->for_html_attr() ?>" />
+ </a>
+ </td>
+ <? else: ?>
+ <td class="g-denied">
+ <a href="javascript:set('allow',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('click to allow')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-success-inactive.png")) ?>" alt="<?= t('inactive allowed icon')->for_html_attr() ?>" />
+ </a>
+ <a href="javascript:set('deny',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('denied through parent album, click to deny explicitly')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-denied-passive.png")) ?>" alt="<?= t('passive denied icon')->for_html_attr() ?>" />
+ </a>
+ </td>
+ <? endif ?>
+
+ <? elseif ($intent === access::DENY): ?>
+ <td class="g-denied">
+ <a href="javascript:set('allow',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('click to allow')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-success-inactive.png")) ?>" alt="<?= t('inactive allowed icon')->for_html_attr() ?>" />
+ </a>
+ <? if ($item->id == 1): ?>
+ <img src="<?= url::file(gallery::find_file("images", "ico-denied.png")) ?>" alt="<?= t('denied icon')->for_html_attr() ?>" title="<?= t('denied')->for_html_attr() ?>"/>
+ <? else: ?>
+ <a href="javascript:set('reset',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('denied, click to reset')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-denied.png")) ?>" alt="<?= t('denied icon')->for_html_attr() ?>" />
+ </a>
+ <? endif ?>
+ </td>
+ <? elseif ($intent === access::ALLOW): ?>
+ <td class="g-allowed">
+ <? if ($item->id == 1): ?>
+ <img src="<?= url::file(gallery::find_file("images", "ico-success.png")) ?>" title="<?= t("allowed")->for_html_attr() ?>" alt="<?= t('allowed icon')->for_html_attr() ?>" />
+ <? else: ?>
+ <a href="javascript:set('reset',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('allowed, click to reset')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-success.png")) ?>" alt="<?= t('allowed icon')->for_html_attr() ?>" />
+ </a>
+ <? endif ?>
+ <a href="javascript:set('deny',<?= $group->id ?>,<?= $permission->id ?>,<?= $item->id ?>)"
+ title="<?= t('click to deny')->for_html_attr() ?>">
+ <img src="<?= url::file(gallery::find_file("images", "ico-denied-inactive.png")) ?>" alt="<?= t('inactive denied icon')->for_html_attr() ?>" />
+ </a>
+ </td>
+ <? endif ?>
+ <? endif ?>
+ </td>
+ <? endforeach ?>
+ </tr>
+ <? endforeach ?>
+ </table>
+</fieldset>
diff --git a/modules/gallery/views/quick_delete_confirm.html.php b/modules/gallery/views/quick_delete_confirm.html.php
new file mode 100644
index 0000000..176ffb9
--- /dev/null
+++ b/modules/gallery/views/quick_delete_confirm.html.php
@@ -0,0 +1,12 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div class="ui-helper-clearfix">
+ <p>
+ <? if ($item->is_album()): ?>
+ <?= t("Delete the album <b>%title</b>? All photos and movies in the album will also be deleted.",
+ array("title" => html::purify($item->title))) ?>
+ <? else: ?>
+ <?= t("Are you sure you want to delete <b>%title</b>?", array("title" => html::purify($item->title))) ?>
+ <? endif ?>
+ </p>
+ <?= $form ?>
+</div>
diff --git a/modules/gallery/views/reauthenticate.html.php b/modules/gallery/views/reauthenticate.html.php
new file mode 100644
index 0000000..9a6696f
--- /dev/null
+++ b/modules/gallery/views/reauthenticate.html.php
@@ -0,0 +1,15 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div>
+ <p>
+ <?= t("The administration session has expired, please re-authenticate to access the administration area.") ?>
+ </p>
+ <p>
+ <?= t("You are currently logged in as %user_name.", array("user_name" => $user_name)) ?>
+ </p>
+ <?= $form ?>
+ <script type="text/javascript">
+ $("#g-reauthenticate-form").ready(function() {
+ $("#g-password").focus();
+ });
+ </script>
+</div> \ No newline at end of file
diff --git a/modules/gallery/views/upgrade_checker_block.html.php b/modules/gallery/views/upgrade_checker_block.html.php
new file mode 100644
index 0000000..c984d99
--- /dev/null
+++ b/modules/gallery/views/upgrade_checker_block.html.php
@@ -0,0 +1,55 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<p>
+ <?= t("Gallery can check to see if there is a new version available for you to use. It is a good idea to upgrade your Gallery to get the latest features and security fixes. Your privacy is important so no information about your Gallery is shared during this process. You can disable this feature below.") ?>
+</p>
+
+<p>
+ <? if (gallery::RELEASE_CHANNEL == "release"): ?>
+ <?= t("You are using the official Gallery %version release, code named <i>%code_name</i>.", array("version" => gallery::VERSION, "code_name" => gallery::CODE_NAME)) ?>
+ <? elseif (isset($build_number)): ?>
+ <?= t("You are using an experimental snapshot of Gallery %version (build %build_number on branch %branch).", array("version" => gallery::VERSION, "branch" => gallery::RELEASE_BRANCH, "build_number" => $build_number)) ?>
+ <? else: ?>
+ <?= t("You are using an experimental snapshot of Gallery %version (branch %branch) but your gallery3/.build_number file is missing so we don't know what build you have. You should probably upgrade so that you have that file.", array("version" => gallery::VERSION, "branch" => gallery::RELEASE_BRANCH, "build_number" => $build_number)) ?>
+ <? endif ?>
+</p>
+
+<? if ($new_version): ?>
+<ul class="g-message-block">
+ <li class="g-message g-info">
+ <?= $new_version ?>
+ </li>
+</ul>
+<? endif ?>
+
+<p>
+ <a class="g-button ui-state-default ui-corner-all"
+ href="<?= url::site("admin/upgrade_checker/check_now?csrf=$csrf") ?>">
+ <?= t("Check now") ?>
+ </a>
+ <? if ($auto_check_enabled): ?>
+ <a class="g-button ui-state-default ui-corner-all"
+ href="<?= url::site("admin/upgrade_checker/set_auto/0?csrf=$csrf") ?>">
+ <?= t("Disable automatic checking") ?>
+ </a>
+ <? else: ?>
+ <a class="g-button ui-state-default ui-corner-all"
+ href="<?= url::site("admin/upgrade_checker/set_auto/1?csrf=$csrf") ?>">
+ <?= t("Enable automatic checking") ?>
+ </a>
+ <? endif ?>
+</p>
+
+<p class="g-text-small">
+ <? if ($auto_check_enabled): ?>
+ <?= t("Automatic upgrade checking is enabled.") ?>
+ <? else: ?>
+ <?= t("Automatic upgrade checking is disabled.") ?>
+ <? endif ?>
+ <? if (!$version_info): ?>
+ <?= t("No upgrade checks have been made yet.") ?>
+ <? else: ?>
+ <?= t("The last upgrade check was made on %date.",
+ array("date" => gallery::date_time($version_info->timestamp))) ?>
+ <? endif ?>
+</p>
+
diff --git a/modules/gallery/views/upgrader.html.php b/modules/gallery/views/upgrader.html.php
new file mode 100644
index 0000000..4c611f7
--- /dev/null
+++ b/modules/gallery/views/upgrader.html.php
@@ -0,0 +1,163 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <head>
+ <title><?= t("Gallery 3 upgrader") ?></title>
+ <link rel="stylesheet" type="text/css" href="<?= url::file("modules/gallery/css/upgrader.css") ?>"
+ media="screen,print,projection" />
+ <script src="<?= url::file("lib/jquery.js") ?>" type="text/javascript"></script>
+ </head>
+ <body<? if (locales::is_rtl()) { echo ' class="rtl"'; } ?>>
+ <div id="outer">
+ <img id="logo" src="<?= url::file("modules/gallery/images/gallery.png") ?>" />
+ <div id="inner">
+ <? if ($can_upgrade): ?>
+ <div id="dialog" style="visibility: hidden">
+ <a id="dialog_close_link" style="display: none" onclick="$('#dialog').fadeOut(); return false;" href="#" class="close">[x]</a>
+ <div id="busy" style="display: none">
+ <h1>
+ <img width="16" height="16" src="<?= url::file("themes/wind/images/loading-small.gif") ?>"/>
+ <?= t("Upgrade in progress!") ?>
+ </h1>
+ <p>
+ <?= t("Please don't refresh or leave the page.") ?>
+ </p>
+ </div>
+ <div id="done" style="display: none">
+ <h1> <?= t("That's it!") ?> </h1>
+ <p>
+ <?= t("Your Gallery is up to date.<br/><a href=\"%url\">Return to your Gallery</a>",
+ array("url" => html::mark_clean(url::base()))) ?>
+ </p>
+ </div>
+ <div id="failed" style="display: none">
+ <h1> <?= t("Some modules failed to upgrade!") ?> </h1>
+ <p>
+ <?= t("Failed modules are <span class=\"failed\">highlighted</span>. Try getting newer versions or <a href=\"%admin_modules\">deactivating those modules</a>.", array("admin_modules" => url::site("admin/modules"))) ?>
+ </p>
+ </div>
+ </div>
+ <script type="text/javascript">
+ $(document).ready(function() {
+ $("#dialog").css("left", Math.round(($(window).width() - $("#dialog").width()) / 2));
+ $("#dialog").css("top", Math.round(($(window).height() - $("#dialog").height()) / 2));
+ $("#upgrade_link").click(function(event) { show_busy() });
+
+ <? if ($done): ?>
+ show_done();
+ <? endif ?>
+
+ <? if ($failed): ?>
+ show_failed();
+ <? endif ?>
+ });
+
+ var show_busy = function() {
+ $("#dialog").css("visibility", "visible");
+ $("#busy").show();
+ $("#upgrade_link").parent().removeClass("button-active");
+ $("#upgrade_link").replaceWith($("#upgrade_link").html())
+ }
+
+ var show_done = function() {
+ $("#dialog").css("visibility", "visible");
+ $("#done").show();
+ $("#dialog_close_link").show();
+ }
+
+ var show_failed = function() {
+ $("#dialog").css("visibility", "visible");
+ $("#failed").show();
+ $("#dialog_close_link").show();
+ }
+ </script>
+ <div id="welcome_message">
+ <p class="<?= $done ? "muted" : "" ?>">
+ <?= t("Welcome to the Gallery upgrader. One click and you're done!") ?>
+ </p>
+ </div>
+
+ <? if ($done): ?>
+ <div id="upgrade_button" class="button muted">
+ <?= t("Upgrade all") ?>
+ </div>
+ <? else: ?>
+ <div id="upgrade_button" class="button button-active">
+ <a id="upgrade_link" href="<?= url::site("upgrader/upgrade?csrf=" . access::csrf_token()) ?>">
+ <?= t("Upgrade all") ?>
+ </a>
+ </div>
+ <? endif ?>
+
+ <? if ($obsolete_modules_message): ?>
+ <div id="obsolete_modules_message">
+ <p>
+ <span class="failed"><?= t("Warning!") ?></span>
+ <?= $obsolete_modules_message ?>
+ </p>
+ </div>
+ <? endif ?>
+
+ <table>
+ <tr class="<?= $done ? "muted" : "" ?>">
+ <th class="name"> <?= t("Module name") ?> </th>
+ <th> <?= t("Installed version") ?> </th>
+ <th> <?= t("Available version") ?> </th>
+ </tr>
+
+ <? foreach ($available as $id => $module): ?>
+ <? if ($module->active): ?>
+ <tr class="<?= $module->version == $module->code_version ? "current" : "upgradeable" ?> <?= in_array($id, $failed) ? "failed" : "" ?>" >
+ <td class="name <?= $id ?>">
+ <?= t($module->name) ?>
+ </td>
+ <td>
+ <?= $module->version ?>
+ </td>
+ <td>
+ <?= $module->code_version ?>
+ </td>
+ </tr>
+ <? else: ?>
+ <? @$inactive++ ?>
+ <? endif ?>
+ <? endforeach ?>
+ </table>
+
+ <? if (@$inactive): ?>
+ <p class="<?= $done ? "muted" : "" ?>">
+ <?= t("The following modules are inactive and don't require an upgrade.") ?>
+ </p>
+ <ul class="<?= $done ? "muted" : "" ?>">
+ <? foreach ($available as $module): ?>
+ <? if (!$module->active): ?>
+ <li>
+ <?= t($module->name) ?>
+ </li>
+ <? endif ?>
+ <? endforeach ?>
+ </ul>
+ <? endif ?>
+ <? else: // can_upgrade ?>
+ <h1> <?= t("Who are you?") ?> </h1>
+ <p>
+ <?= t("You're not logged in as an administrator, so we have to verify you to make sure it's ok for you to do an upgrade. To prove you can run an upgrade, create a file called <b> %name </b> in your <b>%tmp_dir_path</b> directory.",
+ array("name" => "$upgrade_token",
+ "tmp_dir_path" => "gallery3/var/tmp")) ?>
+ </p>
+ <a href="<?= url::site("upgrader?") ?>"><?= t("Ok, I've done that") ?></a>
+ <? endif // can_upgrade ?>
+ </div>
+ <div id="footer">
+ <p>
+ <em>
+ <?= t("Did something go wrong? Try the <a href=\"%faq_url\">FAQ</a> or ask in the <a href=\"%forums_url\">Gallery forums</a>.",
+ array("faq_url" => "http://codex.galleryproject.org/Gallery3:FAQ",
+ "forums_url" => "http://galleryproject.org/forum")) ?>
+ </em>
+ </p>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/modules/gallery/views/user_languages_block.html.php b/modules/gallery/views/user_languages_block.html.php
new file mode 100644
index 0000000..3776ca1
--- /dev/null
+++ b/modules/gallery/views/user_languages_block.html.php
@@ -0,0 +1,19 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<?= form::dropdown("g-select-session-locale", $installed_locales, $selected) ?>
+<script type="text/javascript">
+ $("select[name=g-select-session-locale]").change(function() {
+ var old_locale_preference = <?= html::js_string($selected) ?>;
+ var locale = $(this).val();
+ if (old_locale_preference == locale) {
+ return;
+ }
+
+ var expires = -1;
+ if (locale) {
+ expires = 365;
+ }
+ $.cookie("g_locale", locale, {"expires": expires, "path": "/"});
+ window.location.reload(true);
+ });
+</script>
+
diff --git a/modules/gallery/views/user_profile.html.php b/modules/gallery/views/user_profile.html.php
new file mode 100644
index 0000000..257bd7c
--- /dev/null
+++ b/modules/gallery/views/user_profile.html.php
@@ -0,0 +1,47 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#g-profile-return").click(function(event) {
+ history.go(-1);
+ return false;
+ })
+ });
+</script>
+<div id="g-user-profile">
+ <div class="ui-helper-clearfix">
+ <a id="g-profile-return" class="g-button g-right ui-state-default ui-corner-all" href="#">
+ <?= t("Return") ?>
+ </a>
+ <? if ($editable): ?>
+ <a class="g-button g-right ui-state-default ui-corner-all g-dialog-link" href="<?= url::site("users/form_change_email/{$user->id}") ?>">
+ <?= t("Change email") ?>
+ </a>
+ <a class="g-button g-right ui-state-default ui-corner-all g-dialog-link" href="<?= url::site("users/form_change_password/{$user->id}") ?>">
+ <?= t("Change password") ?>
+ </a>
+ <a class="g-button g-right ui-state-default ui-corner-all g-dialog-link" href="<?= url::site("form/edit/users/{$user->id}") ?>">
+ <?= t("Edit") ?>
+ </a>
+ <? endif ?>
+ <? if ($contactable): ?>
+ <a class="g-button g-right ui-state-default ui-corner-all g-dialog-link"
+ href="<?= url::site("user_profile/contact/{$user->id}") ?>">
+ <?= t("Contact") ?>
+ </a>
+ <? endif ?>
+ </div>
+ <h1>
+ <img src="<?= $user->avatar_url(40, $theme->url("images/avatar.jpg", true)) ?>"
+ alt="<?= html::clean_attribute($user->display_name()) ?>"
+ class="g-avatar g-left" width="40" height="40" />
+ <?= t("User profile: %name", array("name" => $user->display_name())) ?>
+ </h1>
+ <? foreach ($info_parts as $info): ?>
+ <div class="g-block">
+ <h2><?= html::purify($info->title) ?></h2>
+ <div class="g-block-content">
+ <?= $info->view ?>
+ </div>
+ </div>
+ <? endforeach ?>
+</div>
diff --git a/modules/gallery/views/user_profile_info.html.php b/modules/gallery/views/user_profile_info.html.php
new file mode 100644
index 0000000..e559abd
--- /dev/null
+++ b/modules/gallery/views/user_profile_info.html.php
@@ -0,0 +1,9 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<table>
+ <? foreach ($user_profile_data as $label => $value): ?>
+ <tr>
+ <th><?= html::clean($label) ?></th>
+ <td><?= html::purify($value) ?></td>
+ </tr>
+ <? endforeach ?>
+</table>
diff --git a/modules/gallery/views/welcome_message.html.php b/modules/gallery/views/welcome_message.html.php
new file mode 100644
index 0000000..bb6b4a8
--- /dev/null
+++ b/modules/gallery/views/welcome_message.html.php
@@ -0,0 +1,36 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-welcome-message">
+ <h1 style="display: none">
+ <?= t("Welcome to Gallery 3!") ?>
+ </h1>
+
+ <p>
+ <h2>
+ <?= t("Congratulations on choosing Gallery to host your photos. You're going to have a great experience!") ?>
+ </h2>
+ </p>
+
+ <p>
+ <?= t("First things first. You're logged in to the <b>%user_name</b> account. You should change your password to something that you'll remember.", array("user_name" => $user->name)) ?>
+ </p>
+
+ <p>
+ <a href="<?= url::site("admin/users/edit_user_form/{$user->id}") ?>"
+ title="<?= t("Edit your profile")->for_html_attr() ?>"
+ id="g-after-install-change-password-link"
+ class="g-button ui-state-default ui-corner-all">
+ <?= t("Change password and email now") ?>
+ </a>
+ <script type="text/javascript">
+ $("#g-after-install-change-password-link").gallery_dialog();
+ </script>
+ </p>
+
+ <p>
+ <?= t("Want to learn more? The <a href=\"%url\">Gallery website</a> has news and information about the Gallery project and community.", array("url" => "http://galleryproject.org")) ?>
+ </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.galleryproject.org/Main_Page", "forum_url" => "http://galleryproject.org/forum")) ?>
+ </p>
+</div>
diff --git a/modules/gallery/views/welcome_message_loader.html.php b/modules/gallery/views/welcome_message_loader.html.php
new file mode 100644
index 0000000..d1ff2f3
--- /dev/null
+++ b/modules/gallery/views/welcome_message_loader.html.php
@@ -0,0 +1,7 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<span id="g-welcome-message-link"
+ title="<?= t("Welcome to Gallery 3")->for_html_attr() ?>"
+ href="<?= url::site("welcome_message") ?>"/>
+<script type="text/javascript">
+ $(document).ready(function(){$("#g-welcome-message-link").gallery_dialog({immediate: true});});
+</script>