summaryrefslogtreecommitdiff
path: root/modules/gallery/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gallery/helpers')
-rw-r--r--modules/gallery/helpers/MY_remote.php163
-rw-r--r--modules/gallery/helpers/MY_url.php81
-rw-r--r--modules/gallery/helpers/access.php628
-rw-r--r--modules/gallery/helpers/album.php132
-rw-r--r--modules/gallery/helpers/batch.php40
-rw-r--r--modules/gallery/helpers/block_manager.php67
-rw-r--r--modules/gallery/helpers/dir.php40
-rw-r--r--modules/gallery/helpers/gallery.php52
-rw-r--r--modules/gallery/helpers/gallery_block.php100
-rw-r--r--modules/gallery/helpers/gallery_event.php46
-rw-r--r--modules/gallery/helpers/gallery_installer.php278
-rw-r--r--modules/gallery/helpers/gallery_menu.php162
-rw-r--r--modules/gallery/helpers/gallery_search.php24
-rw-r--r--modules/gallery/helpers/gallery_task.php166
-rw-r--r--modules/gallery/helpers/gallery_theme.php137
-rw-r--r--modules/gallery/helpers/graphics.php387
-rw-r--r--modules/gallery/helpers/item.php105
-rw-r--r--modules/gallery/helpers/l10n_client.php203
-rw-r--r--modules/gallery/helpers/l10n_scanner.php154
-rw-r--r--modules/gallery/helpers/locale.php119
-rw-r--r--modules/gallery/helpers/log.php108
-rw-r--r--modules/gallery/helpers/message.php108
-rw-r--r--modules/gallery/helpers/model_cache.php46
-rw-r--r--modules/gallery/helpers/module.php357
-rw-r--r--modules/gallery/helpers/movie.php153
-rw-r--r--modules/gallery/helpers/photo.php171
-rw-r--r--modules/gallery/helpers/rest.php116
-rw-r--r--modules/gallery/helpers/site_status.php132
-rw-r--r--modules/gallery/helpers/task.php83
-rw-r--r--modules/gallery/helpers/theme.php62
-rw-r--r--modules/gallery/helpers/xml.php35
31 files changed, 4455 insertions, 0 deletions
diff --git a/modules/gallery/helpers/MY_remote.php b/modules/gallery/helpers/MY_remote.php
new file mode 100644
index 00000000..4abf5bf1
--- /dev/null
+++ b/modules/gallery/helpers/MY_remote.php
@@ -0,0 +1,163 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class remote extends remote_Core {
+
+ static function post($url, $post_data_array, $extra_headers=array()) {
+ $post_data_raw = self::_encode_post_data($post_data_array, $extra_headers);
+
+ /* Read the web page into a buffer */
+ list ($response_status, $response_headers, $response_body) =
+ self::do_request($url, 'POST', $extra_headers, $post_data_raw);
+
+ return array($response_body, $response_status, $response_headers);
+ }
+
+ static function success($response_status) {
+ return preg_match("/^HTTP\/\d+\.\d+\s2\d{2}(\s|$)/", trim($response_status));
+ }
+
+ /**
+ * Encode the post data. For each key/value pair, urlencode both the key and the value and then
+ * concatenate together. As per the specification, each key/value pair is separated with an
+ * ampersand (&)
+ * @param array $post_data_array the key/value post data
+ * @param array $extra_headers extra headers to pass to the server
+ * @return string the encoded post data
+ */
+ private static function _encode_post_data($post_data_array, &$extra_headers) {
+ $post_data_raw = '';
+ foreach ($post_data_array as $key => $value) {
+ if (!empty($post_data_raw)) {
+ $post_data_raw .= '&';
+ }
+ $post_data_raw .= urlencode($key) . '=' . urlencode($value);
+ }
+
+ $extra_headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ $extra_headers['Content-Length'] = strlen($post_data_raw);
+
+ return $post_data_raw;
+ }
+
+ /**
+ * A single request, without following redirects
+ *
+ * @todo: Handle redirects? If so, only for GET (i.e. not for POST), and use G2's WebHelper_simple::_parseLocation logic.
+ */
+ static function do_request($url, $method='GET', $headers=array(), $body='') {
+ /* Convert illegal characters */
+ $url = str_replace(' ', '%20', $url);
+
+ $url_components = self::_parse_url_for_fsockopen($url);
+ $handle = fsockopen(
+ $url_components['fsockhost'], $url_components['port'], $errno, $errstr, 5);
+ if (empty($handle)) {
+ // log "Error $errno: '$errstr' requesting $url";
+ return array(null, null, null);
+ }
+
+ $header_lines = array('Host: ' . $url_components['host']);
+ foreach ($headers as $key => $value) {
+ $header_lines[] = $key . ': ' . $value;
+ }
+
+ $success = fwrite($handle, sprintf("%s %s HTTP/1.0\r\n%s\r\n\r\n%s",
+ $method,
+ $url_components['uri'],
+ implode("\r\n", $header_lines),
+ $body));
+ if (!$success) {
+ // Zero bytes written or false was returned
+ // log "fwrite failed in requestWebPage($url)" . ($success === false ? ' - false' : ''
+ return array(null, null, null);
+ }
+ fflush($handle);
+
+ /*
+ * Read the status line. fgets stops after newlines. The first line is the protocol
+ * version followed by a numeric status code and its associated textual phrase.
+ */
+ $response_status = trim(fgets($handle, 4096));
+ if (empty($response_status)) {
+ // 'Empty http response code, maybe timeout'
+ return array(null, null, null);
+ }
+
+ /* Read the headers */
+ $response_headers = array();
+ while (!feof($handle)) {
+ $line = trim(fgets($handle, 4096));
+ if (empty($line)) {
+ break;
+ }
+
+ /* Normalize the line endings */
+ $line = str_replace("\r", '', $line);
+
+ list ($key, $value) = explode(':', $line, 2);
+ if (isset($response_headers[$key])) {
+ if (!is_array($response_headers[$key])) {
+ $response_headers[$key] = array($response_headers[$key]);
+ }
+ $response_headers[$key][] = trim($value);
+ } else {
+ $response_headers[$key] = trim($value);
+ }
+ }
+
+ /* Read the body */
+ $response_body = '';
+ while (!feof($handle)) {
+ $response_body .= fread($handle, 4096);
+ }
+ fclose($handle);
+
+ return array($response_status, $response_headers, $response_body);
+ }
+
+ /**
+ * Prepare for fsockopen call.
+ * @param string $url
+ * @return array url components
+ * @access private
+ */
+ private static function _parse_url_for_fsockopen($url) {
+ $url_components = parse_url($url);
+ if (strtolower($url_components['scheme']) == 'https') {
+ $url_components['fsockhost'] = 'ssl://' . $url_components['host'];
+ $default_port = 443;
+ } else {
+ $url_components['fsockhost'] = $url_components['host'];
+ $default_port = 80;
+ }
+ if (empty($url_components['port'])) {
+ $url_components['port'] = $default_port;
+ }
+ if (empty($url_components['path'])) {
+ $url_components['path'] = '/';
+ }
+ $uri = $url_components['path']
+ . (empty($url_components['query']) ? '' : '?' . $url_components['query']);
+ /* Unescape ampersands, since if the url comes from form input it will be escaped */
+ $url_components['uri'] = str_replace('&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 00000000..5e8bfc9e
--- /dev/null
+++ b/modules/gallery/helpers/MY_url.php
@@ -0,0 +1,81 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class url extends url_Core {
+ static function site($uri, $protocol=false) {
+ if (($pos = strpos($uri, "?")) !== false) {
+ list ($uri, $query) = explode("?", $uri, 2);
+ $query = "?$query";
+ } else {
+ $query = "";
+ }
+
+ $parts = explode("/", $uri, 3);
+ if ($parts[0] == "albums" || $parts[0] == "photos") {
+ $uri = model_cache::get("item", $parts[1])->relative_path();
+ }
+ return parent::site($uri . $query, $protocol);
+ }
+
+ static function parse_url() {
+ if (Router::$controller) {
+ return;
+ }
+
+ $count = count(Router::$segments);
+ foreach (ORM::factory("item")
+ ->where("name", html_entity_decode(Router::$segments[$count - 1], ENT_QUOTES))
+ ->where("level", $count + 1)
+ ->find_all() as $match) {
+ if ($match->relative_path() == html_entity_decode(Router::$current_uri, ENT_QUOTES)) {
+ $item = $match;
+ }
+ }
+
+ if (!empty($item)) {
+ Router::$controller = "{$item->type}s";
+ Router::$controller_path = MODPATH . "gallery/controllers/{$item->type}s.php";
+ Router::$method = $item->id;
+ }
+ }
+
+ /**
+ * Just like url::file() except that it returns an absolute URI
+ */
+ static function abs_file($path) {
+ return url::base(
+ false, (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') ? 'http' : 'https') . $path;
+ }
+
+ /**
+ * Just like url::site() except that it returns an absolute URI and
+ * doesn't take a protocol parameter.
+ */
+ static function abs_site($path) {
+ return url::site(
+ $path, (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') ? 'http' : 'https');
+ }
+
+ /**
+ * Just like url::current except that it returns an absolute URI
+ */
+ static function abs_current($qs=false) {
+ return self::abs_site(url::current($qs));
+ }
+}
diff --git a/modules/gallery/helpers/access.php b/modules/gallery/helpers/access.php
new file mode 100644
index 00000000..c48f0b79
--- /dev/null
+++ b/modules/gallery/helpers/access.php
@@ -0,0 +1,628 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/**
+ * API for Gallery Access control.
+ *
+ * Permissions are hierarchical, and apply only to groups and albums. They cascade down from the
+ * top of the Gallery to the bottom, so if you set a permission in the root album, that permission
+ * applies for any sub-album unless the sub-album overrides it. Likewise, any permission applied
+ * to an album applies to any photos inside the album. Overrides can be applied at any level of
+ * the hierarchy for any permission other than View permissions.
+ *
+ * View permissions are an exceptional case. In the case of viewability, we want to ensure that
+ * if an album's parent is inaccessible, then this album must be inaccessible also. So while view
+ * permissions cascade downwards and you're free to set the ALLOW permission on any album, that
+ * ALLOW permission will be ignored unless all that album's parents are marked ALLOW also.
+ *
+ * Implementatation Notes:
+ *
+ * Notes refer to this example album hierarchy:
+ * A1
+ * / \
+ * A2 A3
+ * / \
+ * A4 A5
+ *
+ * o We have the concept of "intents". A user can specify that he intends for A3 to be
+ * inaccessible (ie: a DENY on the "view" permission to the EVERYBODY group). Once A3 is
+ * inaccessible, A5 can never be displayed to that group. If A1 is made inaccessible, then the
+ * entire tree is hidden. If subsequently A1 is made accessible, then the whole tree is
+ * available again *except* A3 and below since the user's "intent" for A3 is maintained.
+ *
+ * o Intents are specified as <group_id, perm, item_id> tuples. It would be inefficient to check
+ * these tuples every time we want to do a lookup, so we use these intents to create an entire
+ * table of permissions for easy lookup in the Access_Cache_Model. There's a 1:1 mapping
+ * between Item_Model and Access_Cache_Model entries.
+ *
+ * o For efficiency, we create columns in Access_Intent_Model and Access_Cache_Model for each of
+ * the possible Group_Model and Permission_Model combinations. This may lead to performance
+ * issues for very large Gallery installs, but for small to medium sized ones (5-10 groups, 5-10
+ * permissions) it's especially efficient because there's a single field value for each
+ * group/permission/item combination.
+ *
+ * o For efficiency, we store the cache columns for view permissions directly in the Item_Model.
+ * This means that we can filter items by group/permission combination without doing any table
+ * joins making for an especially efficient permission check at the expense of having to
+ * maintain extra columns for each item.
+ *
+ * o If at any time the Access_Cache_Model becomes invalid, we can rebuild the entire table from
+ * the Access_Intent_Model
+ */
+class access_Core {
+ const DENY = 0;
+ const ALLOW = 1;
+ const UNKNOWN = 2;
+
+ /**
+ * Does the active user have this permission on this item?
+ *
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return boolean
+ */
+ static function can($perm_name, $item) {
+ if (!$item->loaded) {
+ return false;
+ }
+
+ if (user::active()->admin) {
+ return true;
+ }
+
+ $resource = $perm_name == "view" ?
+ $item : model_cache::get("access_cache", $item->id, "item_id");
+ foreach (user::group_ids() as $id) {
+ if ($resource->__get("{$perm_name}_$id") === self::ALLOW) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If the active user does not have this permission, failed with an access::forbidden().
+ *
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return boolean
+ */
+ static function required($perm_name, $item) {
+ if (!self::can($perm_name, $item)) {
+ self::forbidden();
+ }
+ }
+
+ /**
+ * Does this group have this permission on this item?
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return boolean
+ */
+ static function group_can($group, $perm_name, $item) {
+ $resource = $perm_name == "view" ?
+ $item : model_cache::get("access_cache", $item->id, "item_id");
+ return $resource->__get("{$perm_name}_{$group->id}") === self::ALLOW;
+ }
+
+ /**
+ * Return this group's intent for this permission on this item.
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return integer access::ALLOW, access::DENY or null for no intent
+ */
+ static function group_intent($group, $perm_name, $item) {
+ $intent = model_cache::get("access_intent", $item->id, "item_id");
+ return $intent->__get("{$perm_name}_{$group->id}");
+ }
+
+ /**
+ * Is the permission on this item locked by a parent? If so return the nearest parent that
+ * locks it.
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return ORM_Model item that locks this one
+ */
+ static function locked_by($group, $perm_name, $item) {
+ if ($perm_name != "view") {
+ return null;
+ }
+
+ // For view permissions, if any parent is self::DENY, then those parents lock this one.
+ // Return
+ $lock = ORM::factory("item")
+ ->where("`left` <= $item->left")
+ ->where("`right` >= $item->right")
+ ->where("items.id <> $item->id")
+ ->join("access_intents", "items.id", "access_intents.item_id")
+ ->where("access_intents.view_$group->id", 0)
+ ->orderby("level", "DESC")
+ ->limit(1)
+ ->find();
+
+ if ($lock->loaded) {
+ return $lock;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Terminate immediately with an HTTP 503 Forbidden response.
+ */
+ static function forbidden() {
+ throw new Exception("@todo FORBIDDEN", 503);
+ }
+
+ /**
+ * Internal method to set a permission
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @param boolean $value
+ */
+ private static function _set(Group_Model $group, $perm_name, $album, $value) {
+ if (get_class($group) != "Group_Model") {
+ throw new Exception("@todo PERMISSIONS_ONLY_WORK_ON_GROUPS");
+ }
+ if (!$album->loaded) {
+ throw new Exception("@todo INVALID_ALBUM $album->id");
+ }
+ if (!$album->is_album()) {
+ throw new Exception("@todo INVALID_ALBUM_TYPE not an album");
+ }
+ $access = model_cache::get("access_intent", $album->id, "item_id");
+ $access->__set("{$perm_name}_{$group->id}", $value);
+ $access->save();
+
+ if ($perm_name == "view") {
+ self::_update_access_view_cache($group, $album);
+ } else {
+ self::_update_access_non_view_cache($group, $perm_name, $album);
+ }
+
+ self::_update_htaccess_files($album, $group, $perm_name, $value);
+ }
+
+ /**
+ * Allow a group to have a permission on an item.
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ */
+ static function allow($group, $perm_name, $item) {
+ self::_set($group, $perm_name, $item, self::ALLOW);
+ }
+
+ /**
+ * Deny a group the given permission on an item.
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ */
+ static function deny($group, $perm_name, $item) {
+ self::_set($group, $perm_name, $item, self::DENY);
+ }
+
+ /**
+ * Unset the given permission for this item and use inherited values
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ */
+ static function reset($group, $perm_name, $item) {
+ if ($item->id == 1) {
+ throw new Exception("@todo CANT_RESET_ROOT_PERMISSION");
+ }
+ self::_set($group, $perm_name, $item, null);
+ }
+
+ /**
+ * Register a permission so that modules can use it.
+ *
+ * @param string $name The internal name for for this permission
+ * @param string $display_name The internationalized version of the displayable name
+ * @return void
+ */
+ static function register_permission($name, $display_name) {
+ $permission = ORM::factory("permission", $name);
+ if ($permission->loaded) {
+ throw new Exception("@todo PERMISSION_ALREADY_EXISTS $name");
+ }
+ $permission->name = $name;
+ $permission->display_name = $display_name;
+ $permission->save();
+
+ foreach (self::_get_all_groups() as $group) {
+ self::_add_columns($name, $group);
+ }
+ }
+
+ /**
+ * Delete a permission.
+ *
+ * @param string $perm_name
+ * @return void
+ */
+ static function delete_permission($name) {
+ foreach (self::_get_all_groups() as $group) {
+ self::_drop_columns($name, $group);
+ }
+ $permission = ORM::factory("permission")->where("name", $name)->find();
+ if ($permission->loaded) {
+ $permission->delete();
+ }
+ }
+
+ /**
+ * Add the appropriate columns for a new group
+ *
+ * @param Group_Model $group
+ * @return void
+ */
+ static function add_group($group) {
+ foreach (ORM::factory("permission")->find_all() as $perm) {
+ self::_add_columns($perm->name, $group);
+ }
+ }
+
+ /**
+ * Remove a group's permission columns (usually when it's deleted)
+ *
+ * @param Group_Model $group
+ * @return void
+ */
+ static function delete_group($group) {
+ foreach (ORM::factory("permission")->find_all() as $perm) {
+ self::_drop_columns($perm->name, $group);
+ }
+ }
+
+ /**
+ * Add new access rows when a new item is added.
+ *
+ * @param Item_Model $item
+ * @return void
+ */
+ static function add_item($item) {
+ $access_intent = ORM::factory("access_intent", $item->id);
+ if ($access_intent->loaded) {
+ throw new Exception("@todo ITEM_ALREADY_ADDED $item->id");
+ }
+ $access_intent = ORM::factory("access_intent");
+ $access_intent->item_id = $item->id;
+ $access_intent->save();
+
+ // Create a new access cache entry and copy the parents values.
+ $access_cache = ORM::factory("access_cache");
+ $access_cache->item_id = $item->id;
+ if ($item->id != 1) {
+ $parent_access_cache =
+ ORM::factory("access_cache")->where("item_id", $item->parent()->id)->find();
+ foreach (self::_get_all_groups() as $group) {
+ foreach (ORM::factory("permission")->find_all() as $perm) {
+ $field = "{$perm->name}_{$group->id}";
+ if ($perm->name == "view") {
+ $item->$field = $item->parent()->$field;
+ } else {
+ $access_cache->$field = $parent_access_cache->$field;
+ }
+ }
+ }
+ }
+ $item->save();
+ $access_cache->save();
+ }
+
+ /**
+ * Delete appropriate access rows when an item is deleted.
+ *
+ * @param Item_Model $item
+ * @return void
+ */
+ static function delete_item($item) {
+ ORM::factory("access_intent")->where("item_id", $item->id)->find()->delete();
+ ORM::factory("access_cache")->where("item_id", $item->id)->find()->delete();
+ }
+
+ /**
+ * Verify our Cross Site Request Forgery token is valid, else throw an exception.
+ */
+ static function verify_csrf() {
+ $input = Input::instance();
+ if ($input->post("csrf", $input->get("csrf", null)) !== Session::instance()->get("csrf")) {
+ self::forbidden();
+ }
+ }
+
+ /**
+ * Get the Cross Site Request Forgery token for this session.
+ * @return string
+ */
+ static function csrf_token() {
+ $session = Session::instance();
+ $csrf = $session->get("csrf");
+ if (empty($csrf)) {
+ $csrf = md5(rand());
+ $session->set("csrf", $csrf);
+ }
+ return $csrf;
+ }
+
+ /**
+ * Generate an <input> element containing the Cross Site Request Forgery token for this session.
+ * @return string
+ */
+ static function csrf_form_field() {
+ return "<input type=\"hidden\" name=\"csrf\" value=\"" . self::csrf_token() . "\"/>";
+ }
+
+ /**
+ * Internal method to get all available groups.
+ *
+ * @return ORM_Iterator
+ */
+ private static function _get_all_groups() {
+ // When we build the gallery package, it's possible that the user module is not installed yet.
+ // This is ok at packaging time, so work around it.
+ if (module::is_active("user")) {
+ return ORM::factory("group")->find_all();
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Internal method to remove Permission/Group columns
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @return void
+ */
+ private static function _drop_columns($perm_name, $group) {
+ $db = Database::instance();
+ $field = "{$perm_name}_{$group->id}";
+ $cache_table = $perm_name == "view" ? "items" : "access_caches";
+ $db->query("ALTER TABLE {{$cache_table}} DROP `$field`");
+ $db->query("ALTER TABLE {access_intents} DROP `$field`");
+ ORM::factory("access_intent")->clear_cache();
+ }
+
+ /**
+ * Internal method to add Permission/Group columns
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @return void
+ */
+ private static function _add_columns($perm_name, $group) {
+ $db = Database::instance();
+ $field = "{$perm_name}_{$group->id}";
+ $cache_table = $perm_name == "view" ? "items" : "access_caches";
+ $db->query("ALTER TABLE {{$cache_table}} ADD `$field` SMALLINT NOT NULL DEFAULT 0");
+ $db->query("ALTER TABLE {access_intents} ADD `$field` BOOLEAN DEFAULT NULL");
+ $db->update("access_intents", array($field => 0), array("item_id" => 1));
+ ORM::factory("access_intent")->clear_cache();
+ }
+
+ /**
+ * Update the Access_Cache model based on information from the Access_Intent model for view
+ * permissions only.
+ *
+ * @todo: use database locking
+ *
+ * @param Group_Model $group
+ * @param Item_Model $item
+ * @return void
+ */
+ private static function _update_access_view_cache($group, $item) {
+ $access = ORM::factory("access_intent")->where("item_id", $item->id)->find();
+
+ $db = Database::instance();
+ $field = "view_{$group->id}";
+
+ // With view permissions, deny values in the parent can override allow values in the child,
+ // so start from the bottom of the tree and work upwards overlaying negative on top of
+ // positive.
+ //
+ // If the item's intent is ALLOW or DEFAULT, it's possible that some ancestor has specified
+ // DENY and this ALLOW cannot be obeyed. So in that case, back up the tree and find any
+ // non-DEFAULT and non-ALLOW parent and propagate from there. If we can't find a matching
+ // item, then its safe to propagate from here.
+ if ($access->$field !== self::DENY) {
+ $tmp_item = ORM::factory("item")
+ ->where("left <", $item->left)
+ ->where("right >", $item->right)
+ ->join("access_intents", "access_intents.item_id", "items.id")
+ ->where("access_intents.$field", self::DENY)
+ ->orderby("left", "DESC")
+ ->limit(1)
+ ->find();
+ if ($tmp_item->loaded) {
+ $item = $tmp_item;
+ }
+ }
+
+ // We will have a problem if we're trying to change a DENY to an ALLOW because the
+ // access_caches table will already contain DENY values and we won't be able to overwrite
+ // them according the rule above. So mark every permission below this level as UNKNOWN so
+ // that we can tell which permissions have been changed, and which ones need to be updated.
+ $db->update("items", array($field => self::UNKNOWN),
+ array("left >=" => $item->left, "right <=" => $item->right));
+
+ $query = ORM::factory("access_intent")
+ ->select(array("access_intents.$field", "items.left", "items.right", "items.id"))
+ ->join("items", "items.id", "access_intents.item_id")
+ ->where("left >=", $item->left)
+ ->where("right <=", $item->right)
+ ->where("type", "album")
+ ->where("access_intents.$field IS NOT", null)
+ ->orderby("level", "DESC")
+ ->find_all();
+ foreach ($query as $row) {
+ if ($row->$field == self::ALLOW) {
+ // Propagate ALLOW for any row that is still UNKNOWN.
+ $db->update("items", array($field => $row->$field),
+ array($field => self::UNKNOWN, "left >=" => $row->left, "right <=" => $row->right));
+ } else if ($row->$field == self::DENY) {
+ // DENY overwrites everything below it
+ $db->update("items", array($field => $row->$field),
+ array("left >=" => $row->left, "right <=" => $row->right));
+ }
+ }
+
+ // Finally, if our intent is DEFAULT at this point it means that we were unable to find a
+ // DENY parent in the hierarchy to propagate from. So we'll still have a UNKNOWN values in
+ // the hierarchy, and all of those are safe to change to ALLOW.
+ $db->update("items", array($field => self::ALLOW),
+ array($field => self::UNKNOWN, "left >=" => $item->left, "right <=" => $item->right));
+ }
+
+ /**
+ * Update the Access_Cache model based on information from the Access_Intent model for non-view
+ * permissions.
+ *
+ * @todo: use database locking
+ *
+ * @param Group_Model $group
+ * @param string $perm_name
+ * @param Item_Model $item
+ * @return void
+ */
+ private static function _update_access_non_view_cache($group, $perm_name, $item) {
+ $access = ORM::factory("access_intent")->where("item_id", $item->id)->find();
+
+ $db = Database::instance();
+ $field = "{$perm_name}_{$group->id}";
+
+ // If the item's intent is DEFAULT, then we need to back up the chain to find the nearest
+ // parent with an intent and propagate from there.
+ //
+ // @todo To optimize this, we wouldn't need to propagate from the parent, we could just
+ // propagate from here with the parent's intent.
+ if ($access->$field === null) {
+ $tmp_item = ORM::factory("item")
+ ->join("access_intents", "items.id", "access_intents.item_id")
+ ->where("left <", $item->left)
+ ->where("right >", $item->right)
+ ->where("$field IS NOT", null)
+ ->orderby("left", "DESC")
+ ->limit(1)
+ ->find();
+ if ($tmp_item->loaded) {
+ $item = $tmp_item;
+ }
+ }
+
+ // With non-view permissions, each level can override any permissions that came above it
+ // so start at the top and work downwards, overlaying permissions as we go.
+ $query = ORM::factory("access_intent")
+ ->select(array("access_intents.$field", "items.left", "items.right"))
+ ->join("items", "items.id", "access_intents.item_id")
+ ->where("left >=", $item->left)
+ ->where("right <=", $item->right)
+ ->where("$field IS NOT", null)
+ ->orderby("level", "ASC")
+ ->find_all();
+ foreach ($query as $row) {
+ $db->query(
+ "UPDATE {access_caches} SET `$field` = {$row->$field} " .
+ "WHERE `item_id` IN " .
+ " (SELECT `id` FROM {items} " .
+ " WHERE `left` >= $row->left " .
+ " AND `right` <= $row->right)");
+ }
+ }
+
+ /**
+ * Maintain .htacccess files to prevent direct access to albums, resizes and thumbnails when we
+ * apply the view and view_full permissions to guest users.
+ */
+ private static function _update_htaccess_files($album, $group, $perm_name, $value) {
+ if ($group->id != 1 || !($perm_name == "view" || $perm_name == "view_full")) {
+ return;
+ }
+
+ $dirs = array($album->file_path());
+ if ($perm_name == "view") {
+ $dirs[] = dirname($album->resize_path());
+ $dirs[] = dirname($album->thumb_path());
+ }
+
+ $base_url = url::site("file_proxy");
+ foreach ($dirs as $dir) {
+ if ($value === self::DENY) {
+ $fp = fopen("$dir/.htaccess", "w+");
+ fwrite($fp, "<IfModule mod_rewrite.c>\n");
+ fwrite($fp, " RewriteEngine On\n");
+ fwrite($fp, " RewriteRule (.*) $base_url/\$1 [L]\n");
+ fwrite($fp, "</IfModule>\n");
+ fwrite($fp, "<IfModule !mod_rewrite.c>\n");
+ fwrite($fp, " Order Deny,Allow\n");
+ fwrite($fp, " Deny from All\n");
+ fwrite($fp, "</IfModule>\n");
+ fclose($fp);
+ } else {
+ @unlink($dir . "/.htaccess");
+ }
+ }
+ }
+
+ static function private_key() {
+ return module::get_var("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/tmp/security_test/success");
+
+ @mkdir(VARPATH . "tmp/security_test");
+ if ($fp = @fopen(VARPATH . "tmp/security_test/.htaccess", "w+")) {
+ fwrite($fp, "RewriteEngine On\n");
+ fwrite($fp, "RewriteRule verify $success_url [L]\n");
+ fclose($fp);
+ }
+
+ if ($fp = @fopen(VARPATH . "tmp/security_test/success", "w+")) {
+ fwrite($fp, "success");
+ fclose($fp);
+ }
+
+ list ($response) = remote::do_request(url::abs_file("var/tmp/security_test/verify"));
+ $works = $response == "HTTP/1.1 200 OK";
+ @dir::unlink(VARPATH . "tmp/security_test");
+
+ return $works;
+ }
+}
diff --git a/modules/gallery/helpers/album.php b/modules/gallery/helpers/album.php
new file mode 100644
index 00000000..362b93d0
--- /dev/null
+++ b/modules/gallery/helpers/album.php
@@ -0,0 +1,132 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * This is the API for handling albums.
+ *
+ * Note: by design, this class does not do any permission checking.
+ */
+class album_Core {
+ /**
+ * Create a new album.
+ * @param integer $parent_id id of parent album
+ * @param string $name the name of this new album (it will become the directory name on disk)
+ * @param integer $title the title of the new album
+ * @param string $description (optional) the longer description of this album
+ * @return Item_Model
+ */
+ static function create($parent, $name, $title, $description=null, $owner_id=null) {
+ if (!$parent->loaded || !$parent->is_album()) {
+ throw new Exception("@todo INVALID_PARENT");
+ }
+
+ if (strpos($name, "/")) {
+ throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH");
+ }
+
+ // We don't allow trailing periods as a security measure
+ // ref: http://dev.kohanaphp.com/issues/684
+ if (rtrim($name, ".") != $name) {
+ throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD");
+ }
+
+ $album = ORM::factory("item");
+ $album->type = "album";
+ $album->title = $title;
+ $album->description = $description;
+ $album->name = $name;
+ $album->owner_id = $owner_id;
+ $album->thumb_dirty = 1;
+ $album->resize_dirty = 1;
+ $album->rand_key = ((float)mt_rand()) / (float)mt_getrandmax();
+ $album->sort_column = "weight";
+ $album->sort_order = "ASC";
+
+ while (ORM::factory("item")
+ ->where("parent_id", $parent->id)
+ ->where("name", $album->name)
+ ->find()->id) {
+ $album->name = "{$name}-" . rand();
+ }
+
+ $album = $album->add_to_parent($parent);
+ mkdir($album->file_path());
+ mkdir(dirname($album->thumb_path()));
+ mkdir(dirname($album->resize_path()));
+
+ module::event("item_created", $album);
+
+ return $album;
+ }
+
+ static function get_add_form($parent) {
+ $form = new Forge("albums/{$parent->id}", "", "post", array("id" => "gAddAlbumForm"));
+ $group = $form->group("add_album")
+ ->label(t("Add an album to %album_title", array("album_title" => $parent->title)));
+ $group->input("title")->label(t("Title"));
+ $group->textarea("description")->label(t("Description"));
+ $group->input("name")->label(t("Directory Name"))
+ ->callback("item::validate_no_slashes")
+ ->error_messages("no_slashes", t("The directory name can't contain the \"/\" character"));
+ $group->hidden("type")->value("album");
+ $group->submit("")->value(t("Create"));
+ $form->add_rules_from(ORM::factory("item"));
+ return $form;
+ }
+
+ static function get_edit_form($parent) {
+ $form = new Forge("albums/{$parent->id}", "", "post", array("id" => "gEditAlbumForm"));
+ $form->hidden("_method")->value("put");
+ $group = $form->group("edit_album")->label(t("Edit Album"));
+
+ $group->input("title")->label(t("Title"))->value($parent->title);
+ $group->textarea("description")->label(t("Description"))->value($parent->description);
+ if ($parent->id != 1) {
+ $group->input("dirname")->label(t("Directory Name"))->value($parent->name)
+ ->callback("item::validate_no_slashes")
+ ->error_messages("no_slashes", t("The directory name can't contain a \"/\""))
+ ->callback("item::validate_no_trailing_period")
+ ->error_messages("no_trailing_period", t("The directory name can't end in \".\""));
+ }
+
+ $sort_order = $group->group("sort_order", array("id" => "gAlbumSortOrder"))
+ ->label(t("Sort Order"));
+
+ $sort_order->dropdown("column", array("id" => "gAlbumSortColumn"))
+ ->label(t("Sort by"))
+ ->options(array("weight" => t("Default"),
+ "captured" => t("Capture Date"),
+ "created" => t("Creation Date"),
+ "title" => t("Title"),
+ "updated" => t("Updated Date"),
+ "view_count" => t("Number of views"),
+ "rand_key" => t("Random")))
+ ->selected($parent->sort_column);
+ $sort_order->dropdown("direction", array("id" => "gAlbumSortDirection"))
+ ->label(t("Order"))
+ ->options(array("ASC" => t("Ascending"),
+ "DESC" => t("Descending")))
+ ->selected($parent->sort_order);
+ $group->hidden("type")->value("album");
+ $group->submit("")->value(t("Modify"));
+ $form->add_rules_from(ORM::factory("item"));
+ return $form;
+ }
+}
diff --git a/modules/gallery/helpers/batch.php b/modules/gallery/helpers/batch.php
new file mode 100644
index 00000000..0faa3369
--- /dev/null
+++ b/modules/gallery/helpers/batch.php
@@ -0,0 +1,40 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class batch_Core {
+ static function start() {
+ $session = Session::instance();
+ $session->set("batch_level", $session->get("batch_level", 0) + 1);
+ }
+
+ static function stop() {
+ $session = Session::instance();
+ $batch_level = $session->get("batch_level", 0) - 1;
+ if ($batch_level > 0) {
+ $session->set("batch_level", $batch_level);
+ } else {
+ $session->delete("batch_level");
+ module::event("batch_complete");
+ }
+ }
+
+ static function in_progress() {
+ return Session::instance()->get("batch_level", 0) > 0;
+ }
+}
diff --git a/modules/gallery/helpers/block_manager.php b/modules/gallery/helpers/block_manager.php
new file mode 100644
index 00000000..20b641d4
--- /dev/null
+++ b/modules/gallery/helpers/block_manager.php
@@ -0,0 +1,67 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class block_manager_Core {
+ static function get_active($location) {
+ return unserialize(module::get_var("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 = self::get_active($location);
+ $blocks[rand()] = array($module_name, $block_id);
+ self::set_active($location, $blocks);
+ }
+
+ static function remove($location, $block_id) {
+ $blocks = self::get_active($location);
+ unset($blocks[$block_id]);
+ self::set_active($location, $blocks);
+ }
+
+ static function get_available() {
+ $blocks = array();
+
+ foreach (module::active() as $module) {
+ $class_name = "{$module->name}_block";
+ if (method_exists($class_name, "get_list")) {
+ foreach (call_user_func(array($class_name, "get_list")) as $id => $title) {
+ $blocks["{$module->name}:$id"] = $title;
+ }
+ }
+ }
+ return $blocks;
+ }
+
+ static function get_html($location) {
+ $active = self::get_active($location);
+ $result = "";
+ foreach ($active as $id => $desc) {
+ if (method_exists("$desc[0]_block", "get")) {
+ $block = call_user_func(array("$desc[0]_block", "get"), $desc[1]);
+ $block->id = $id;
+ $result .= $block;
+ }
+ }
+ return $result;
+ }
+}
diff --git a/modules/gallery/helpers/dir.php b/modules/gallery/helpers/dir.php
new file mode 100644
index 00000000..1717bdd9
--- /dev/null
+++ b/modules/gallery/helpers/dir.php
@@ -0,0 +1,40 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class dir_Core {
+ static function unlink($path) {
+ if (is_dir($path) && is_writable($path)) {
+ foreach (new DirectoryIterator($path) as $resource) {
+ if ($resource->isDot()) {
+ unset($resource);
+ continue;
+ } else if ($resource->isFile()) {
+ unlink($resource->getPathName());
+ } else if ($resource->isDir()) {
+ dir::unlink($resource->getRealPath());
+ }
+ unset($resource);
+ }
+ return @rmdir($path);
+ }
+ return false;
+ }
+
+
+}
diff --git a/modules/gallery/helpers/gallery.php b/modules/gallery/helpers/gallery.php
new file mode 100644
index 00000000..34671f1f
--- /dev/null
+++ b/modules/gallery/helpers/gallery.php
@@ -0,0 +1,52 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery_Core {
+ /**
+ * If Gallery is in maintenance mode, then force all non-admins to get routed to a "This site is
+ * down for maintenance" page.
+ */
+ static function maintenance_mode() {
+ $maintenance_mode = Kohana::config("gallery.maintenance_mode", false, false);
+
+ if (Router::$controller != "login" && !empty($maintenance_mode) && !user::active()->admin) {
+ Router::$controller = "maintenance";
+ Router::$controller_path = MODPATH . "gallery/controllers/maintenance.php";
+ Router::$method = "index";
+ }
+ }
+
+ /**
+ * This function is called when the Gallery is fully initialized. We relay it to modules as the
+ * "gallery_ready" event. Any module that wants to perform an action at the start of every
+ * request should implement the <module>_event::gallery_ready() handler.
+ */
+ static function ready() {
+ module::event("gallery_ready");
+ }
+
+ /**
+ * This function is called right before the Kohana framework shuts down. We relay it to modules
+ * as the "gallery_shutdown" event. Any module that wants to perform an action at the start of
+ * every request should implement the <module>_event::gallery_shutdown() handler.
+ */
+ static function shutdown() {
+ module::event("gallery_shutdown");
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery_block.php b/modules/gallery/helpers/gallery_block.php
new file mode 100644
index 00000000..abc8a195
--- /dev/null
+++ b/modules/gallery/helpers/gallery_block.php
@@ -0,0 +1,100 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery_block_Core {
+ static function get_list() {
+ return array(
+ "welcome" => t("Welcome to Gallery 3!"),
+ "photo_stream" => t("Photo Stream"),
+ "log_entries" => t("Log Entries"),
+ "stats" => t("Gallery Stats"),
+ "platform_info" => t("Platform Information"),
+ "project_news" => t("Gallery Project News"));
+ }
+
+ static function get($block_id) {
+ $block = new Block();
+ switch($block_id) {
+ case "welcome":
+ $block->css_id = "gWelcome";
+ $block->title = t("Welcome to Gallery3");
+ $block->content = new View("admin_block_welcome.html");
+ break;
+
+ case "photo_stream":
+ $block->css_id = "gPhotoStream";
+ $block->title = t("Photo Stream");
+ $block->content = new View("admin_block_photo_stream.html");
+ $block->content->photos =
+ ORM::factory("item")->where("type", "photo")->orderby("created", "DESC")->find_all(10);
+ break;
+
+ case "log_entries":
+ $block->css_id = "gLogEntries";
+ $block->title = t("Log Entries");
+ $block->content = new View("admin_block_log_entries.html");
+ $block->content->entries = ORM::factory("log")->orderby("timestamp", "DESC")->find_all(5);
+ break;
+
+ case "stats":
+ $block->css_id = "gStats";
+ $block->title = t("Gallery Stats");
+ $block->content = new View("admin_block_stats.html");
+ $block->content->album_count = ORM::factory("item")->where("type", "album")->count_all();
+ $block->content->photo_count = ORM::factory("item")->where("type", "photo")->count_all();
+ break;
+
+ case "platform_info":
+ $block->css_id = "gPlatform";
+ $block->title = t("Platform Information");
+ $block->content = new View("admin_block_platform.html");
+ if (is_readable("/proc/loadavg")) {
+ $block->content->load_average =
+ join(" ", array_slice(split(" ", array_shift(file("/proc/loadavg"))), 0, 3));
+ } else {
+ $block->content->load_average = t("Unavailable");
+ }
+ break;
+
+ case "project_news":
+ $block->css_id = "gProjectNews";
+ $block->title = t("Gallery Project News");
+ $block->content = new View("admin_block_news.html");
+ $block->content->feed = feed::parse("http://gallery.menalto.com/node/feed", 3);
+ break;
+
+ case "block_adder":
+ $block->css_id = "gBlockAdder";
+ $block->title = t("Dashboard Content");
+ $block->content = self::get_add_block_form();
+ }
+
+ return $block;
+ }
+
+ static function get_add_block_form() {
+ $form = new Forge("admin/dashboard/add_block", "", "post",
+ array("id" => "gAddDashboardBlockForm"));
+ $group = $form->group("add_block")->label(t("Add Block"));
+ $group->dropdown("id")->label("Available Blocks")->options(block_manager::get_available());
+ $group->submit("center")->value(t("Add to center"));
+ $group->submit("sidebar")->value(t("Add to sidebar"));
+ return $form;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery_event.php b/modules/gallery/helpers/gallery_event.php
new file mode 100644
index 00000000..aa11b7c0
--- /dev/null
+++ b/modules/gallery/helpers/gallery_event.php
@@ -0,0 +1,46 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+class gallery_event_Core {
+ static function group_created($group) {
+ access::add_group($group);
+ }
+
+ static function group_before_delete($group) {
+ access::delete_group($group);
+ }
+
+ static function item_created($item) {
+ access::add_item($item);
+ }
+
+ static function item_before_delete($item) {
+ access::delete_item($item);
+ }
+
+ static function user_login($user) {
+ // If this user is an admin, check to see if there are any post-install tasks that we need
+ // to run and take care of those now.
+ if ($user->admin && module::get_var("gallery", "choose_default_tookit", null)) {
+ graphics::choose_default_toolkit();
+ module::clear_var("gallery", "choose_default_tookit");
+ }
+ }
+}
diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php
new file mode 100644
index 00000000..e3aa403f
--- /dev/null
+++ b/modules/gallery/helpers/gallery_installer.php
@@ -0,0 +1,278 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery__installer {
+ static function install($initial_install=false) {
+ $db = Database::instance();
+ if ($initial_install) {
+ $version = 0;
+ } else {
+ $version = module::get_version("gallery");
+ }
+
+ if ($version == 0) {
+ $db->query("CREATE TABLE {access_caches} (
+ `id` int(9) NOT NULL auto_increment,
+ `item_id` int(9),
+ PRIMARY KEY (`id`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {access_intents} (
+ `id` int(9) NOT NULL auto_increment,
+ `item_id` int(9),
+ PRIMARY KEY (`id`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {graphics_rules} (
+ `id` int(9) NOT NULL auto_increment,
+ `active` BOOLEAN default 0,
+ `args` varchar(255) default NULL,
+ `module_name` varchar(64) NOT NULL,
+ `operation` varchar(64) NOT NULL,
+ `priority` int(9) NOT NULL,
+ `target` varchar(32) NOT NULL,
+ PRIMARY KEY (`id`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {items} (
+ `id` int(9) NOT NULL auto_increment,
+ `album_cover_item_id` int(9) default NULL,
+ `captured` int(9) default NULL,
+ `created` int(9) default NULL,
+ `description` varchar(2048) default NULL,
+ `height` int(9) default NULL,
+ `left` int(9) NOT NULL,
+ `level` int(9) NOT NULL,
+ `mime_type` varchar(64) default NULL,
+ `name` varchar(255) default NULL,
+ `owner_id` int(9) default NULL,
+ `parent_id` int(9) NOT NULL,
+ `rand_key` float default NULL,
+ `relative_path_cache` varchar(255) default NULL,
+ `resize_dirty` boolean default 1,
+ `resize_height` int(9) default NULL,
+ `resize_width` int(9) default NULL,
+ `right` int(9) NOT NULL,
+ `sort_column` varchar(64) default NULL,
+ `sort_order` char(4) default 'ASC',
+ `thumb_dirty` boolean default 1,
+ `thumb_height` int(9) default NULL,
+ `thumb_width` int(9) default NULL,
+ `title` varchar(255) default NULL,
+ `type` varchar(32) NOT NULL,
+ `updated` int(9) default NULL,
+ `view_count` int(9) default 0,
+ `weight` int(9) NOT NULL default 0,
+ `width` int(9) default NULL,
+ PRIMARY KEY (`id`),
+ KEY `parent_id` (`parent_id`),
+ KEY `type` (`type`),
+ KEY `random` (`rand_key`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {logs} (
+ `id` int(9) NOT NULL auto_increment,
+ `category` varchar(64) default NULL,
+ `html` varchar(255) default NULL,
+ `message` text default NULL,
+ `referer` varchar(255) default NULL,
+ `severity` int(9) default 0,
+ `timestamp` int(9) default 0,
+ `url` varchar(255) default NULL,
+ `user_id` int(9) default 0,
+ PRIMARY KEY (`id`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {messages} (
+ `id` int(9) NOT NULL auto_increment,
+ `key` varchar(255) default NULL,
+ `severity` varchar(32) default NULL,
+ `value` varchar(255) default NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`key`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {modules} (
+ `id` int(9) NOT NULL auto_increment,
+ `active` BOOLEAN default 0,
+ `name` varchar(64) default NULL,
+ `version` int(9) default NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`name`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {themes} (
+ `id` int(9) NOT NULL auto_increment,
+ `name` varchar(64) default NULL,
+ `version` int(9) default NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`name`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {permissions} (
+ `id` int(9) NOT NULL auto_increment,
+ `display_name` varchar(64) default NULL,
+ `name` varchar(64) default NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`name`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {incoming_translations} (
+ `id` int(9) NOT NULL auto_increment,
+ `key` char(32) NOT NULL,
+ `locale` char(10) NOT NULL,
+ `message` text NOT NULL,
+ `revision` int(9) DEFAULT NULL,
+ `translation` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`key`, `locale`),
+ KEY `locale_key` (`locale`, `key`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {outgoing_translations} (
+ `id` int(9) NOT NULL auto_increment,
+ `base_revision` int(9) DEFAULT NULL,
+ `key` char(32) NOT NULL,
+ `locale` char(10) NOT NULL,
+ `message` text NOT NULL,
+ `translation` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`key`, `locale`),
+ KEY `locale_key` (`locale`, `key`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {sessions} (
+ `session_id` varchar(127) NOT NULL,
+ `data` text NOT NULL,
+ `last_activity` int(10) UNSIGNED NOT NULL,
+ PRIMARY KEY (`session_id`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {tasks} (
+ `id` int(9) NOT NULL auto_increment,
+ `callback` varchar(128) default NULL,
+ `context` text NOT NULL,
+ `done` boolean default 0,
+ `name` varchar(128) default NULL,
+ `owner_id` int(9) default NULL,
+ `percent_complete` int(9) default 0,
+ `state` varchar(32) default NULL,
+ `status` varchar(255) default NULL,
+ `updated` int(9) default NULL,
+ PRIMARY KEY (`id`),
+ KEY (`owner_id`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ $db->query("CREATE TABLE {vars} (
+ `id` int(9) NOT NULL auto_increment,
+ `module_name` varchar(64) NOT NULL,
+ `name` varchar(64) NOT NULL,
+ `value` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY(`module_name`, `name`))
+ ENGINE=InnoDB DEFAULT CHARSET=utf8;");
+
+ foreach (array("albums", "logs", "modules", "resizes", "thumbs", "tmp", "uploads") as $dir) {
+ @mkdir(VARPATH . $dir);
+ }
+
+ access::register_permission("view", "View");
+ access::register_permission("view_full", "View Full Size");
+ access::register_permission("edit", "Edit");
+ access::register_permission("add", "Add");
+
+ $root = ORM::factory("item");
+ $root->type = "album";
+ $root->title = "Gallery";
+ $root->description = "";
+ $root->left = 1;
+ $root->right = 2;
+ $root->parent_id = 0;
+ $root->level = 1;
+ $root->thumb_dirty = 1;
+ $root->resize_dirty = 1;
+ $root->sort_column = "weight";
+ $root->sort_order = "ASC";
+ $root->save();
+ access::add_item($root);
+
+ module::set_var("gallery", "active_site_theme", "default");
+ module::set_var("gallery", "active_admin_theme", "admin_default");
+ 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);
+
+ // Add rules for generating our thumbnails and resizes
+ graphics::add_rule(
+ "gallery", "thumb", "resize",
+ array("width" => 200, "height" => 200, "master" => Image::AUTO),
+ 100);
+ graphics::add_rule(
+ "gallery", "resize", "resize",
+ array("width" => 640, "height" => 480, "master" => Image::AUTO),
+ 100);
+
+ // Instantiate default themes (site and admin)
+ foreach (array("default", "admin_default") as $theme_name) {
+ $theme_info = new ArrayObject(parse_ini_file(THEMEPATH . $theme_name . "/theme.info"),
+ ArrayObject::ARRAY_AS_PROPS);
+ $theme = ORM::factory("theme");
+ $theme->name = $theme_name;
+ $theme->version = $theme_info->version;
+ $theme->save();
+ }
+
+ block_manager::add("dashboard_sidebar", "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", "photo_stream");
+ block_manager::add("dashboard_center", "gallery", "log_entries");
+
+ module::set_version("gallery", 1);
+ module::set_var("gallery", "version", "3.0 pre-beta git");
+ module::set_var("gallery", "choose_default_tookit", 1);
+ }
+ }
+
+ static function uninstall() {
+ $db = Database::instance();
+ $db->query("DROP TABLE IF EXISTS {access_caches}");
+ $db->query("DROP TABLE IF EXISTS {access_intents}");
+ $db->query("DROP TABLE IF EXISTS {graphics_rules}");
+ $db->query("DROP TABLE IF EXISTS {items}");
+ $db->query("DROP TABLE IF EXISTS {logs}");
+ $db->query("DROP TABLE IF EXISTS {messages}");
+ $db->query("DROP TABLE IF EXISTS {modules}");
+ $db->query("DROP TABLE IF EXISTS {themes}");
+ $db->query("DROP TABLE IF EXISTS {incoming_translations}");
+ $db->query("DROP TABLE IF EXISTS {outgoing_translations}");
+ $db->query("DROP TABLE IF EXISTS {permissions}");
+ $db->query("DROP TABLE IF EXISTS {sessions}");
+ $db->query("DROP TABLE IF EXISTS {tasks}");
+ $db->query("DROP TABLE IF EXISTS {vars}");
+ foreach (array("albums", "resizes", "thumbs", "uploads",
+ "modules", "logs", "database.php") as $entry) {
+ system("/bin/rm -rf " . VARPATH . $entry);
+ }
+ }
+}
diff --git a/modules/gallery/helpers/gallery_menu.php b/modules/gallery/helpers/gallery_menu.php
new file mode 100644
index 00000000..1dc9cb41
--- /dev/null
+++ b/modules/gallery/helpers/gallery_menu.php
@@ -0,0 +1,162 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery_menu_Core {
+ static function site($menu, $theme) {
+ if (file_exists(MODPATH . "gallery/controllers/scaffold.php") && user::active()->admin) {
+ $menu->append($scaffold_menu = Menu::factory("submenu")
+ ->id("scaffold")
+ ->label("Scaffold"));
+ $scaffold_menu->append(Menu::factory("link")
+ ->id("scaffold_home")
+ ->label("Dashboard")
+ ->url(url::site("scaffold")));
+ }
+
+ $menu->append(Menu::factory("link")
+ ->id("home")
+ ->label(t("Home"))
+ ->url(url::site("albums/1")));
+
+ $item = $theme->item();
+
+ if (user::active()->admin || ($item && access::can("edit", $item))) {
+ $menu->append($options_menu = Menu::factory("submenu")
+ ->id("options_menu")
+ ->label(t("Options")));
+
+ if ($item && access::can("edit", $item)) {
+ $options_menu
+ ->append(Menu::factory("dialog")
+ ->id("edit_item")
+ ->label($item->is_album() ? t("Edit album") : t("Edit photo"))
+ ->url(url::site("form/edit/{$item->type}s/$item->id")));
+
+ // @todo Move album options menu to the album quick edit pane
+ // @todo Create resized item quick edit pane menu
+ if ($item->is_album()) {
+ $options_menu
+ ->append(Menu::factory("dialog")
+ ->id("add_item")
+ ->label(t("Add a photo"))
+ ->url(url::site("simple_uploader/app/$item->id")))
+ ->append(Menu::factory("dialog")
+ ->id("add_album")
+ ->label(t("Add an album"))
+ ->url(url::site("form/add/albums/$item->id?type=album")))
+ ->append(Menu::factory("dialog")
+ ->id("edit_permissions")
+ ->label(t("Edit permissions"))
+ ->url(url::site("permissions/browse/$item->id")));
+ }
+ }
+ }
+
+ if (user::active()->admin) {
+ $menu->append($admin_menu = Menu::factory("submenu")
+ ->id("admin_menu")
+ ->label(t("Admin")));
+ self::admin($admin_menu, $theme);
+ foreach (module::active() as $module) {
+ if ($module->name == "gallery") {
+ continue;
+ }
+ $class = "{$module->name}_menu";
+ if (method_exists($class, "admin")) {
+ call_user_func_array(array($class, "admin"), array(&$admin_menu, $theme));
+ }
+ }
+ }
+ }
+
+ static function album($menu, $theme) {
+ }
+
+ static function photo($menu, $theme) {
+ if (access::can("view_full", $theme->item())) {
+ $menu
+ ->append(Menu::factory("link")
+ ->id("fullsize")
+ ->label(t("View full size"))
+ ->url("#")
+ ->css_class("gFullSizeLink"));
+ }
+ $menu
+ ->append(Menu::factory("link")
+ ->id("album")
+ ->label(t("Return to album"))
+ ->url($theme->item()->parent()->url("show={$theme->item->id}"))
+ ->css_id("gAlbumLink"));
+ }
+
+ static function admin($menu, $theme) {
+ $menu
+ ->append(Menu::factory("link")
+ ->id("dashboard")
+ ->label(t("Dashboard"))
+ ->url(url::site("admin")))
+ ->append(Menu::factory("submenu")
+ ->id("settings_menu")
+ ->label(t("Settings"))
+ ->append(Menu::factory("link")
+ ->id("graphics_toolkits")
+ ->label(t("Graphics"))
+ ->url(url::site("admin/graphics")))
+ ->append(Menu::factory("link")
+ ->id("languages")
+ ->label(t("Languages"))
+ ->url(url::site("admin/languages")))
+ ->append(Menu::factory("link")
+ ->id("l10n_mode")
+ ->label(Session::instance()->get("l10n_mode", false)
+ ? t("Stop translating") : t("Start translating"))
+ ->url(url::site("l10n_client/toggle_l10n_mode?csrf=" .
+ access::csrf_token())))
+ ->append(Menu::factory("link")
+ ->id("advanced")
+ ->label("Advanced")
+ ->url(url::site("admin/advanced_settings"))))
+ ->append(Menu::factory("link")
+ ->id("modules")
+ ->label(t("Modules"))
+ ->url(url::site("admin/modules")))
+ ->append(Menu::factory("submenu")
+ ->id("content_menu")
+ ->label(t("Content")))
+ ->append(Menu::factory("submenu")
+ ->id("appearance_menu")
+ ->label(t("Appearance"))
+ ->append(Menu::factory("link")
+ ->id("themes")
+ ->label(t("Theme Choice"))
+ ->url(url::site("admin/themes")))
+ ->append(Menu::factory("link")
+ ->id("theme_details")
+ ->label(t("Theme Options"))
+ ->url(url::site("admin/theme_details"))))
+ ->append(Menu::factory("link")
+ ->id("maintenance")
+ ->label(t("Maintenance"))
+ ->url(url::site("admin/maintenance")))
+ ->append(Menu::factory("submenu")
+ ->id("statistics_menu")
+ ->label(t("Statistics"))
+ ->url("#"));
+ }
+}
diff --git a/modules/gallery/helpers/gallery_search.php b/modules/gallery/helpers/gallery_search.php
new file mode 100644
index 00000000..2a4029d3
--- /dev/null
+++ b/modules/gallery/helpers/gallery_search.php
@@ -0,0 +1,24 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery_search_Core {
+ static function item_index_data($item) {
+ return join(" ", array($item->description, $item->name, $item->title));
+ }
+}
diff --git a/modules/gallery/helpers/gallery_task.php b/modules/gallery/helpers/gallery_task.php
new file mode 100644
index 00000000..6046bfc4
--- /dev/null
+++ b/modules/gallery/helpers/gallery_task.php
@@ -0,0 +1,166 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery_task_Core {
+ static function available_tasks() {
+ $dirty_count = graphics::find_dirty_images_query()->count();
+ $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);
+
+ return $tasks;
+ }
+
+ /**
+ * Task that rebuilds all dirty images.
+ * @param Task_Model the task
+ */
+ static function rebuild_dirty_images($task) {
+ $result = graphics::find_dirty_images_query();
+ $remaining = $result->count();
+ $completed = $task->get("completed", 0);
+
+ $i = 0;
+ foreach ($result as $row) {
+ $item = ORM::factory("item", $row->id);
+ if ($item->loaded) {
+ graphics::generate($item);
+ }
+
+ $completed++;
+ $remaining--;
+
+ if (++$i == 2) {
+ break;
+ }
+ }
+
+ $task->status = t2("Updated: 1 image. Total: %total_count.",
+ "Updated: %count images. Total: %total_count.",
+ $completed,
+ array("total_count" => ($remaining + $completed)));
+
+ if ($completed + $remaining > 0) {
+ $task->percent_complete = (int)(100 * $completed / ($completed + $remaining));
+ } else {
+ $task->percent_complete = 100;
+ }
+
+ $task->set("completed", $completed);
+ if ($remaining == 0) {
+ $task->done = true;
+ $task->state = "success";
+ site_status::clear("graphics_dirty");
+ }
+ }
+
+ static function update_l10n(&$task) {
+ $start = microtime(true);
+ $dirs = $task->get("dirs");
+ $files = $task->get("files");
+ $cache = $task->get("cache", array());
+ $i = 0;
+
+ switch ($task->get("mode", "init")) {
+ case "init": // 0%
+ $dirs = array("gallery", "modules", "themes", "installer");
+ $files = array();
+ $task->set("mode", "find_files");
+ $task->status = t("Finding files");
+ break;
+
+ case "find_files": // 0% - 10%
+ while (($dir = array_pop($dirs)) && microtime(true) - $start < 0.5) {
+ if (basename($dir) == "tests") {
+ continue;
+ }
+
+ foreach (glob(DOCROOT . "$dir/*") as $path) {
+ $relative_path = str_replace(DOCROOT, "", $path);
+ if (is_dir($path)) {
+ $dirs[] = $relative_path;
+ } else {
+ $files[] = $relative_path;
+ }
+ }
+ }
+
+ $task->status = t2("Finding files: found 1 file",
+ "Finding files: found %count files", count($files));
+
+ if (!$dirs) {
+ $task->set("mode", "scan_files");
+ $task->set("total_files", count($files));
+ $task->status = t("Scanning files");
+ $task->percent_complete = 10;
+ }
+ break;
+
+ case "scan_files": // 10% - 90%
+ while (($file = array_pop($files)) && microtime(true) - $start < 0.5) {
+ $file = DOCROOT . $file;
+ switch (pathinfo($file, PATHINFO_EXTENSION)) {
+ case "php":
+ l10n_scanner::scan_php_file($file, $cache);
+ break;
+
+ case "info":
+ l10n_scanner::scan_info_file($file, $cache);
+ break;
+ }
+ }
+
+ $total_files = $task->get("total_files");
+ $task->status = t2("Scanning files: scanned 1 file",
+ "Scanning files: scanned %count files", $total_files - count($files));
+
+ $task->percent_complete = 10 + 80 * ($total_files - count($files)) / $total_files;
+ if (empty($files)) {
+ $task->set("mode", "fetch_updates");
+ $task->status = t("Fetching updates");
+ $task->percent_complete = 90;
+ }
+ break;
+
+ case "fetch_updates": // 90% - 100%
+ l10n_client::fetch_updates();
+ $task->done = true;
+ $task->state = "success";
+ $task->status = t("Translations installed/updated");
+ $task->percent_complete = 100;
+ }
+
+ $task->set("files", $files);
+ $task->set("dirs", $dirs);
+ $task->set("cache", $cache);
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/gallery_theme.php b/modules/gallery/helpers/gallery_theme.php
new file mode 100644
index 00000000..0acccb45
--- /dev/null
+++ b/modules/gallery/helpers/gallery_theme.php
@@ -0,0 +1,137 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class gallery_theme_Core {
+ static function head($theme) {
+ $session = Session::instance();
+ $buf = "";
+ if ($session->get("debug")) {
+ $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" .
+ url::file("gallery/css/debug.css") . "\" />";
+ }
+ if (($theme->page_type == "album" || $theme->page_type == "photo")
+ && access::can("edit", $theme->item())) {
+ $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" .
+ url::file("gallery/css/quick.css") . "\" />";
+ $buf .= html::script("gallery/js/quick.js");
+ }
+ if ($theme->page_type == "photo" && access::can("view_full", $theme->item())) {
+ $buf .= "<script type=\"text/javascript\" >" .
+ " var fullsize_detail = { " .
+ " close: \"" . url::file("gallery/images/ico-close.png") . "\", " .
+ " url: \"" . $theme->item()->file_url() . "\", " .
+ " width: " . $theme->item()->width . ", " .
+ " height: " . $theme->item()->height . "};" .
+ "</script>";
+ $buf .= html::script("gallery/js/fullsize.js");
+ }
+
+ if ($session->get("l10n_mode", false)) {
+ $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" .
+ url::file("gallery/css/l10n_client.css") . "\" />";
+ $buf .= html::script("lib/jquery.cookie.js");
+ $buf .= html::script("gallery/js/l10n_client.js");
+ }
+
+ return $buf;
+ }
+
+ static function resize_top($theme, $item) {
+ if (access::can("edit", $item)) {
+ $edit_link = url::site("quick/pane/$item->id?page_type=photo");
+ return "<div class=\"gQuick\" href=\"$edit_link\">";
+ }
+ }
+
+ static function resize_bottom($theme, $item) {
+ if (access::can("edit", $item)) {
+ return "</div>";
+ }
+ }
+
+ static function thumb_top($theme, $child) {
+ if (access::can("edit", $child)) {
+ $edit_link = url::site("quick/pane/$child->id?page_type=album");
+ return "<div class=\"gQuick\" href=\"$edit_link\">";
+ }
+ }
+
+ static function thumb_bottom($theme, $child) {
+ if (access::can("edit", $child)) {
+ return "</div>";
+ }
+ }
+
+ static function admin_head($theme) {
+ $session = Session::instance();
+ $buf = "";
+ if ($session->get("debug")) {
+ $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" .
+ url::file("gallery/css/debug.css") . "\" />";
+ }
+
+ if ($session->get("l10n_mode", false)) {
+ $buf .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"" .
+ url::file("gallery/css/l10n_client.css") . "\" />";
+ $buf .= html::script("lib/jquery.cookie.js");
+ $buf .= html::script("gallery/js/l10n_client.js");
+ }
+
+ return $buf;
+ }
+
+ static function page_bottom($theme) {
+ $session = Session::instance();
+ if ($session->get("profiler", false)) {
+ $profiler = new Profiler();
+ $profiler->render();
+ }
+ if ($session->get("l10n_mode", false)) {
+ return L10n_Client_Controller::l10n_form();
+ }
+
+ if ($session->get("after_install")) {
+ $session->delete("after_install");
+ return new View("after_install_loader.html");
+ }
+ }
+
+ static function admin_page_bottom($theme) {
+ $session = Session::instance();
+ if ($session->get("profiler", false)) {
+ $profiler = new Profiler();
+ $profiler->render();
+ }
+ if ($session->get("l10n_mode", false)) {
+ return L10n_Client_Controller::l10n_form();
+ }
+ }
+
+ static function credits() {
+ return "<li class=\"first\">" .
+ t("Powered by <a href=\"%url\">Gallery %version</a>",
+ array("url" => "http://gallery.menalto.com",
+ "version" => module::get_var("gallery", "version"))) .
+ "</li>";
+ }
+
+ static function admin_credits() {
+ return gallery_theme::credits();
+ }
+} \ 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 00000000..605b9ff8
--- /dev/null
+++ b/modules/gallery/helpers/graphics.php
@@ -0,0 +1,387 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class graphics_Core {
+ private static $init;
+
+ /**
+ * Add a new graphics rule.
+ *
+ * Rules are applied to targets (thumbnails and resizes) in priority order. Rules are functions
+ * in the graphics class. So for example, the following rule:
+ *
+ * graphics::add_rule("gallery", "thumb", "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
+ * @param array $args arguments to the operation
+ * @param integer $priority the priority for this rule (lower priorities are run first)
+ */
+ static function add_rule($module_name, $target, $operation, $args, $priority) {
+ $rule = ORM::factory("graphics_rule");
+ $rule->module_name = $module_name;
+ $rule->target = $target;
+ $rule->operation = $operation;
+ $rule->priority = $priority;
+ $rule->args = serialize($args);
+ $rule->active = true;
+ $rule->save();
+
+ self::mark_dirty($target == "thumb", $target == "resize");
+ }
+
+ /**
+ * Remove any matching graphics rules
+ * @param string $module_name the module that added the rule
+ * @param string $target the target for this operation ("thumb" or "resize")
+ * @param string $operation the name of the operation
+ */
+ static function remove_rule($module_name, $target, $operation) {
+ ORM::factory("graphics_rule")
+ ->where("module_name", $module_name)
+ ->where("target", $target)
+ ->where("operation", $operation)
+ ->delete_all();
+
+ self::mark_dirty($target == "thumb", $target == "resize");
+ }
+
+ /**
+ * Remove all rules for this module
+ * @param string $module_name
+ */
+ static function remove_rules($module_name) {
+ $status = Database::instance()->delete("graphics_rules", array("module_name" => $module_name));
+ if (count($status)) {
+ self::mark_dirty(true, true);
+ }
+ }
+
+ /**
+ * Activate the rules for this module, typically done when the module itself is deactivated.
+ * Note that this does not mark images as dirty so that if you deactivate and reactivate a
+ * module it won't cause all of your images to suddenly require a rebuild.
+ */
+ static function activate_rules($module_name) {
+ Database::instance()
+ ->update("graphics_rules",array("active" => true), array("module_name" => $module_name));
+ }
+
+ /**
+ * Deactivate the rules for this module, typically done when the module itself is deactivated.
+ * Note that this does not mark images as dirty so that if you deactivate and reactivate a
+ * module it won't cause all of your images to suddenly require a rebuild.
+ */
+ static function deactivate_rules($module_name) {
+ Database::instance()
+ ->update("graphics_rules",array("active" => false), array("module_name" => $module_name));
+ }
+
+ /**
+ * Rebuild the thumb and resize for the given item.
+ * @param Item_Model $item
+ */
+ static function generate($item) {
+ if ($item->is_album()) {
+ if (!$cover = $item->album_cover()) {
+ return;
+ }
+ $input_file = $cover->file_path();
+ $input_item = $cover;
+ } else {
+ $input_file = $item->file_path();
+ $input_item = $item;
+ }
+
+ if ($item->thumb_dirty) {
+ $ops["thumb"] = $item->thumb_path();
+ }
+ if ($item->resize_dirty && !$item->is_album() && !$item->is_movie()) {
+ $ops["resize"] = $item->resize_path();
+ }
+
+ if (empty($ops)) {
+ return;
+ }
+
+ try {
+ foreach ($ops as $target => $output_file) {
+ if ($input_item->is_movie()) {
+ // Convert the movie to a JPG first
+ $output_file = preg_replace("/...$/", "jpg", $output_file);
+ movie::extract_frame($input_file, $output_file);
+ $working_file = $output_file;
+ } else {
+ $working_file = $input_file;
+ }
+
+ foreach (ORM::factory("graphics_rule")
+ ->where("target", $target)
+ ->where("active", true)
+ ->orderby("priority", "asc")
+ ->find_all() as $rule) {
+ $args = array($working_file, $output_file, unserialize($rule->args));
+ call_user_func_array(array("graphics", $rule->operation), $args);
+ $working_file = $output_file;
+ }
+ }
+
+ if (!empty($ops["thumb"])) {
+ $dims = getimagesize($item->thumb_path());
+ $item->thumb_width = $dims[0];
+ $item->thumb_height = $dims[1];
+ $item->thumb_dirty = 0;
+ }
+
+ if (!empty($ops["resize"])) {
+ $dims = getimagesize($item->resize_path());
+ $item->resize_width = $dims[0];
+ $item->resize_height = $dims[1];
+ $item->resize_dirty = 0;
+ }
+ $item->save();
+ } catch (Kohana_Exception $e) {
+ // Something went wrong rebuilding the image. Leave it dirty and move on.
+ // @todo we should handle this better.
+ Kohana::log("error", "Caught exception rebuilding image: {$item->title}\n" .
+ $e->getTraceAsString());
+ }
+ }
+
+ /**
+ * Resize an image. Valid options are width, height and master. Master is one of the Image
+ * master dimension constants.
+ *
+ * @param string $input_file
+ * @param string $output_file
+ * @param array $options
+ */
+ static function resize($input_file, $output_file, $options) {
+ if (!self::$init) {
+ self::init_toolkit();
+ }
+
+ if (filesize($input_file) == 0) {
+ throw new Exception("@todo MALFORMED_INPUT_FILE");
+ }
+
+ $dims = getimagesize($input_file);
+ if (max($dims[0], $dims[1]) < min($options["width"], $options["height"])) {
+ // Image would get upscaled; do nothing
+ copy($input_file, $output_file);
+ } else {
+ Image::factory($input_file)
+ ->resize($options["width"], $options["height"], $options["master"])
+ ->quality(module::get_var("gallery", "image_quality"))
+ ->save($output_file);
+ }
+ }
+
+ /**
+ * Rotate an image. Valid options are degrees
+ *
+ * @param string $input_file
+ * @param string $output_file
+ * @param array $options
+ */
+ static function rotate($input_file, $output_file, $options) {
+ if (!self::$init) {
+ self::init_toolkit();
+ }
+
+ Image::factory($input_file)
+ ->quality(module::get_var("gallery", "image_quality"))
+ ->rotate($options["degrees"])
+ ->save($output_file);
+ }
+
+ /**
+ * Overlay an image on top of the input file.
+ *
+ * Valid options are: file, mime_type, position, transparency_percent, padding
+ *
+ * Valid positions: northwest, north, northeast,
+ * west, center, east,
+ * southwest, south, southeast
+ *
+ * padding is in pixels
+ *
+ * @param string $input_file
+ * @param string $output_file
+ * @param array $options
+ */
+ static function composite($input_file, $output_file, $options) {
+ if (!self::$init) {
+ self::init_toolkit();
+ }
+
+ list ($width, $height) = getimagesize($input_file);
+ list ($w_width, $w_height) = getimagesize($options["file"]);
+
+ $pad = isset($options["padding"]) ? $options["padding"] : 10;
+ $top = $pad;
+ $left = $pad;
+ $y_center = max($height / 2 - $w_height / 2, $pad);
+ $x_center = max($width / 2 - $w_width / 2, $pad);
+ $bottom = max($height - $w_height - $pad, $pad);
+ $right = max($width - $w_width - $pad, $pad);
+
+ switch ($options["position"]) {
+ case "northwest": $x = $left; $y = $top; break;
+ case "north": $x = $x_center; $y = $top; break;
+ case "northeast": $x = $right; $y = $top; break;
+ case "west": $x = $left; $y = $y_center; break;
+ case "center": $x = $x_center; $y = $y_center; break;
+ case "east": $x = $right; $y = $y_center; break;
+ case "southwest": $x = $left; $y = $bottom; break;
+ case "south": $x = $x_center; $y = $bottom; break;
+ case "southeast": $x = $right; $y = $bottom; break;
+ }
+
+ Image::factory($input_file)
+ ->composite($options["file"], $x, $y, $options["transparency"])
+ ->quality(module::get_var("gallery", "image_quality"))
+ ->save($output_file);
+ }
+
+ /**
+ * Return a query result that locates all items with dirty images.
+ * @return Database_Result Query result
+ */
+ static function find_dirty_images_query() {
+ return Database::instance()->query(
+ "SELECT `id` FROM {items} " .
+ "WHERE ((`thumb_dirty` = 1 AND (`type` <> 'album' OR `album_cover_item_id` IS NOT NULL))" .
+ " OR (`resize_dirty` = 1 AND `type` = 'photo')) " .
+ " AND `id` != 1");
+ }
+
+ /**
+ * Mark thumbnails and resizes as dirty. They will have to be rebuilt.
+ */
+ static function mark_dirty($thumbs, $resizes) {
+ if ($thumbs || $resizes) {
+ $db = Database::instance();
+ $fields = array();
+ if ($thumbs) {
+ $fields["thumb_dirty"] = 1;
+ }
+ if ($resizes) {
+ $fields["resize_dirty"] = 1;
+ }
+ $db->update("items", $fields, true);
+ }
+
+ $count = self::find_dirty_images_query()->count();
+ if ($count) {
+ site_status::warning(
+ t2("One of your photos is out of date. <a %attrs>Click here to fix it</a>",
+ "%count of your photos are out of date. <a %attrs>Click here to fix them</a>",
+ $count,
+ array("attrs" => sprintf(
+ 'href="%s" class="gDialogLink"',
+ url::site("admin/maintenance/start/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() {
+ $gd = function_exists("gd_info") ? gd_info() : array();
+ $exec = function_exists("exec");
+ if (!isset($gd["GD Version"])) {
+ $gd["GD Version"] = false;
+ }
+ return array("gd" => $gd,
+ "imagemagick" => $exec ? dirname(exec("which convert")) : false,
+ "graphicsmagick" => $exec ? dirname(exec("which gm")) : false);
+ }
+
+ /**
+ * This needs to be run once, after the initial install, to choose a graphics toolkit.
+ */
+ static function choose_default_toolkit() {
+ // Detect a graphics toolkit
+ $toolkits = graphics::detect_toolkits();
+ foreach (array("imagemagick", "graphicsmagick", "gd") as $tk) {
+ if ($toolkits[$tk]) {
+ module::set_var("gallery", "graphics_toolkit", $tk);
+ module::set_var("gallery", "graphics_toolkit_path", $tk == "gd" ? "" : $toolkits[$tk]);
+ 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" => url::site("admin/graphics"))),
+ "missing_graphics_toolkit");
+ }
+ }
+
+ /**
+ * Choose which driver the Kohana Image library uses.
+ */
+ static function init_toolkit() {
+ switch(module::get_var("gallery", "graphics_toolkit")) {
+ case "gd":
+ Kohana::config_set("image.driver", "GD");
+ break;
+
+ case "imagemagick":
+ Kohana::config_set("image.driver", "ImageMagick");
+ Kohana::config_set(
+ "image.params.directory", module::get_var("gallery", "graphics_toolkit_path"));
+ break;
+
+ case "graphicsmagick":
+ Kohana::config_set("image.driver", "GraphicsMagick");
+ Kohana::config_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, resize)
+ * @return boolean
+ */
+ static function can($func) {
+ if (module::get_var("gallery", "graphics_toolkit") == "gd" &&
+ $func == "rotate" &&
+ !function_exists("imagerotate")) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/gallery/helpers/item.php b/modules/gallery/helpers/item.php
new file mode 100644
index 00000000..7daaf1e1
--- /dev/null
+++ b/modules/gallery/helpers/item.php
@@ -0,0 +1,105 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class item_Core {
+ static function move($source, $target) {
+ access::required("edit", $source);
+ access::required("edit", $target);
+
+ $parent = $source->parent();
+ if ($parent->album_cover_item_id == $source->id) {
+ if ($parent->children_count() > 1) {
+ foreach ($parent->children(2) as $child) {
+ if ($child->id != $source->id) {
+ $new_cover_item = $child;
+ break;
+ }
+ }
+ item::make_album_cover($new_cover_item);
+ } else {
+ item::remove_album_cover($parent);
+ }
+ }
+
+ $source->move_to($target);
+
+ // If the target has no cover item, make this it.
+ if ($target->album_cover_item_id == null) {
+ item::make_album_cover($source);
+ }
+ }
+
+ static function make_album_cover($item) {
+ $parent = $item->parent();
+ access::required("edit", $parent);
+
+ model_cache::clear("item", $parent->album_cover_item_id);
+ $parent->album_cover_item_id = $item->is_album() ? $item->album_cover_item_id : $item->id;
+ $parent->thumb_dirty = 1;
+ $parent->save();
+ graphics::generate($parent);
+ $grand_parent = $parent->parent();
+ if ($grand_parent && $grand_parent->album_cover_item_id == null) {
+ item::make_album_cover($parent);
+ }
+ }
+
+ static function remove_album_cover($album) {
+ access::required("edit", $album);
+ @unlink($album->thumb_path());
+
+ model_cache::clear("item", $album->album_cover_item_id) ;
+ $album->album_cover_item_id = null;
+ $album->thumb_width = 0;
+ $album->thumb_height = 0;
+ $album->thumb_dirty = 1;
+ $album->save();
+ graphics::generate($album);
+ }
+
+ static function validate_no_slashes($input) {
+ if (strpos($input->value, "/") !== false) {
+ $input->add_error("no_slashes", 1);
+ }
+ }
+
+ static function validate_no_trailing_period($input) {
+ if (rtrim($input->value, ".") !== $input->value) {
+ $input->add_error("no_trailing_period", 1);
+ }
+ }
+
+ static function validate_no_name_conflict($input) {
+ $itemid = Input::instance()->post("item");
+ if (is_array($itemid)) {
+ $itemid = $itemid[0];
+ }
+ $item = ORM::factory("item")
+ ->in("id", $itemid)
+ ->find();
+ if (Database::instance()
+ ->from("items")
+ ->where("parent_id", $item->parent_id)
+ ->where("id <>", $item->id)
+ ->where("name", $input->value)
+ ->count_records()) {
+ $input->add_error("conflict", 1);
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/l10n_client.php b/modules/gallery/helpers/l10n_client.php
new file mode 100644
index 00000000..d26739f5
--- /dev/null
+++ b/modules/gallery/helpers/l10n_client.php
@@ -0,0 +1,203 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+class l10n_client_Core {
+
+ private static function _server_url() {
+ return "http://gallery.menalto.com/index.php";
+ }
+
+ static function server_api_key_url() {
+ return self::_server_url() . "?q=translations/userkey/" .
+ self::client_token();
+ }
+
+ static function client_token() {
+ return md5("l10n_client_client_token" . access::private_key());
+ }
+
+ static function api_key($api_key=null) {
+ if ($api_key !== null) {
+ module::set_var("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 ? self::api_key() : $api_key;
+ $parts = explode(":", $api_key);
+ return empty($parts) ? 0 : $parts[0];
+ }
+
+ private static function _sign($payload, $api_key=null) {
+ $api_key = $api_key == null ? self::api_key() : $api_key;
+ return md5($api_key . $payload . self::client_token());
+ }
+
+ static function validate_api_key($api_key) {
+ $version = "1.0";
+ $url = self::_server_url() . "?q=translations/status";
+ $signature = self::_sign($version, $api_key);
+
+ list ($response_data, $response_status) = remote::post(
+ $url, array("version" => $version,
+ "client_token" => self::client_token(),
+ "signature" => $signature,
+ "uid" => self::server_uid($api_key)));
+ if (!remote::success($response_status)) {
+ return false;
+ }
+ return true;
+ }
+
+ static function fetch_updates() {
+ $request->locales = array();
+ $request->messages = new stdClass();
+
+ $locales = locale::installed();
+ foreach ($locales as $locale => $locale_data) {
+ $request->locales[] = $locale;
+ }
+
+ // @todo Batch requests (max request size)
+ foreach (Database::instance()
+ ->select("key", "locale", "revision", "translation")
+ ->from("incoming_translations")
+ ->get()
+ ->as_array() as $row) {
+ if (!isset($request->messages->{$row->key})) {
+ $request->messages->{$row->key} = 1;
+ }
+ if (!empty($row->revision) && !empty($row->translation)) {
+ if (!is_object($request->messages->{$row->key})) {
+ $request->messages->{$row->key} = new stdClass();
+ }
+ $request->messages->{$row->key}->{$row->locale} = $row->revision;
+ }
+ }
+ // @todo Include messages from outgoing_translations?
+
+ $request_data = json_encode($request);
+ $url = self::_server_url() . "?q=translations/fetch";
+ list ($response_data, $response_status) = remote::post($url, array("data" => $request_data));
+ if (!remote::success($response_status)) {
+ throw new Exception("@todo TRANSLATIONS_FETCH_REQUEST_FAILED " . $response_status);
+ }
+ if (empty($response_data)) {
+ log::info("translations", "Translations fetch request resulted in an empty response");
+ return;
+ }
+
+ $response = json_decode($response_data);
+
+ // Response format (JSON payload):
+ // [{key:<key_1>, translation: <JSON encoded translation>, rev:<rev>, locale:<locale>},
+ // {key:<key_2>, ...}
+ // ]
+ $count = count($response);
+ log::info("translations", "Installed $count new / updated translation messages");
+
+ foreach ($response as $message_data) {
+ // @todo Better input validation
+ if (empty($message_data->key) || empty($message_data->translation) ||
+ empty($message_data->locale) || empty($message_data->rev)) {
+ throw new Exception("@todo TRANSLATIONS_FETCH_REQUEST_FAILED: Invalid response data");
+ }
+ $key = $message_data->key;
+ $locale = $message_data->locale;
+ $revision = $message_data->rev;
+ $translation = serialize(json_decode($message_data->translation));
+
+ // @todo Should we normalize the incoming_translations table into messages(id, key, message)
+ // and incoming_translations(id, translation, locale, revision)? Or just allow
+ // incoming_translations.message to be NULL?
+ $locale = $message_data->locale;
+ $entry = ORM::factory("incoming_translation")
+ ->where(array("key" => $key, "locale" => $locale))
+ ->find();
+ if (!$entry->loaded) {
+ // @todo Load a message key -> message (text) dict into memory outside of this loop
+ $root_entry = ORM::factory("incoming_translation")
+ ->where(array("key" => $key, "locale" => "root"))
+ ->find();
+ $entry->key = $key;
+ $entry->message = $root_entry->message;
+ $entry->locale = $locale;
+ }
+ $entry->revision = $revision;
+ $entry->translation = $translation;
+ $entry->save();
+ }
+ }
+
+ static function submit_translations() {
+ // Request format (HTTP POST):
+ // client_token = <client_token>
+ // uid = <l10n server user id>
+ // signature = md5(user_api_key($uid, $client_token) . $data . $client_token))
+ // data = // JSON payload
+ //
+ // {<key_1>: {message: <JSON encoded message>
+ // translations: {<locale_1>: <JSON encoded translation>,
+ // <locale_2>: ...}},
+ // <key_2>: {...}
+ // }
+
+ // @todo Batch requests (max request size)
+ // @todo include base_revision in submission / how to handle resubmissions / edit fights?
+ foreach (Database::instance()
+ ->select("key", "message", "locale", "base_revision", "translation")
+ ->from("outgoing_translations")
+ ->get() as $row) {
+ $key = $row->key;
+ if (!isset($request->{$key})) {
+ $request->{$key}->message = json_encode(unserialize($row->message));
+ }
+ $request->{$key}->translations->{$row->locale} = json_encode(unserialize($row->translation));
+ }
+
+ // @todo reduce memory consumpotion, e.g. free $request
+ $request_data = json_encode($request);
+ $url = self::_server_url() . "?q=translations/submit";
+ $signature = self::_sign($request_data);
+
+ list ($response_data, $response_status) = remote::post(
+ $url, array("data" => $request_data,
+ "client_token" => self::client_token(),
+ "signature" => $signature,
+ "uid" => self::server_uid()));
+
+ if (!remote::success($response_status)) {
+ throw new Exception("@todo TRANSLATIONS_SUBMISSION_FAILED " . $response_status);
+ }
+ if (empty($response_data)) {
+ return;
+ }
+
+ $response = json_decode($response_data);
+ // Response format (JSON payload):
+ // [{key:<key_1>, locale:<locale_1>, rev:<rev_1>, status:<rejected|accepted|pending>},
+ // {key:<key_2>, ...}
+ // ]
+
+ // @todo Move messages out of outgoing into incoming, using new rev?
+ // @todo show which messages have been rejected / are pending?
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/l10n_scanner.php b/modules/gallery/helpers/l10n_scanner.php
new file mode 100644
index 00000000..80b6f01c
--- /dev/null
+++ b/modules/gallery/helpers/l10n_scanner.php
@@ -0,0 +1,154 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * Scans all source code for messages that need to be localized.
+ */
+class l10n_scanner_Core {
+ // Based on Drupal's potx module, originally written by:
+ // G‡bor Hojtsy http://drupal.org/user/4166
+ public static $cache;
+
+ static function process_message($message, &$cache) {
+ if (empty($cache)) {
+ foreach (Database::instance()
+ ->select("key")
+ ->from("incoming_translations")
+ ->where("locale", "root")
+ ->get() as $row) {
+ $cache[$row->key] = true;
+ }
+ }
+
+ $key = I18n::get_message_key($message);
+ if (array_key_exists($key, $cache)) {
+ return $cache[$key];
+ }
+
+ $entry = ORM::factory("incoming_translation", array("key" => $key));
+ if (!$entry->loaded) {
+ $entry->key = $key;
+ $entry->message = serialize($message);
+ $entry->locale = "root";
+ $entry->save();
+ }
+ }
+
+ static function scan_php_file($file, &$cache) {
+ $code = file_get_contents($file);
+ $raw_tokens = token_get_all($code);
+ unset($code);
+
+ $tokens = array();
+ $func_token_list = array("t" => array(), "t2" => array());
+ $token_number = 0;
+ // Filter out HTML / whitespace, and build a lookup for global function calls.
+ foreach ($raw_tokens as $token) {
+ if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) {
+ if (is_array($token)) {
+ if ($token[0] == T_STRING && in_array($token[1], array("t", "t2"))) {
+ $func_token_list[$token[1]][] = $token_number;
+ }
+ }
+ $tokens[] = $token;
+ $token_number++;
+ }
+ }
+ unset($raw_tokens);
+
+ if (!empty($func_token_list["t"])) {
+ l10n_scanner::_parse_t_calls($tokens, $func_token_list["t"], $cache);
+ }
+ if (!empty($func_token_list["t2"])) {
+ l10n_scanner::_parse_plural_calls($tokens, $func_token_list["t2"], $cache);
+ }
+ }
+
+ static function scan_info_file($file, &$cache) {
+ $code = file_get_contents($file);
+ if (preg_match("#name\s*?=\s*(.*?)\ndescription\s*?=\s*(.*)\n#", $code, $matches)) {
+ unset($matches[0]);
+ foreach ($matches as $string) {
+ l10n_scanner::process_message($string, $cache);
+ }
+ }
+ }
+
+ private static function _parse_t_calls(&$tokens, &$call_list, &$cache) {
+ foreach ($call_list as $index) {
+ $function_name = $tokens[$index++];
+ $parens = $tokens[$index++];
+ $first_param = $tokens[$index++];
+ $next_token = $tokens[$index];
+
+ if ($parens == "(") {
+ if (in_array($next_token, array(")", ","))
+ && (is_array($first_param) && ($first_param[0] == T_CONSTANT_ENCAPSED_STRING))) {
+ $message = self::_escape_quoted_string($first_param[1]);
+ l10n_scanner::process_message($message, $cache);
+ } else {
+ // t() found, but inside is something which is not a string literal.
+ // @todo Call status callback with error filename/line.
+ }
+ }
+ }
+ }
+
+ private static function _parse_plural_calls(&$tokens, &$call_list, &$cache) {
+ foreach ($call_list as $index) {
+ $function_name = $tokens[$index++];
+ $parens = $tokens[$index++];
+ $first_param = $tokens[$index++];
+ $first_separator = $tokens[$index++];
+ $second_param = $tokens[$index++];
+ $next_token = $tokens[$index];
+
+ if ($parens == "(") {
+ if ($first_separator == "," && $next_token == ","
+ && is_array($first_param) && $first_param[0] == T_CONSTANT_ENCAPSED_STRING
+ && is_array($second_param) && $second_param[0] == T_CONSTANT_ENCAPSED_STRING) {
+ $singular = self::_escape_quoted_string($first_param[1]);
+ $plural = self::_escape_quoted_string($first_param[1]);
+ l10n_scanner::process_message(array("one" => $singular, "other" => $plural), $cache);
+ } else {
+ // t2() found, but inside is something which is not a string literal.
+ // @todo Call status callback with error filename/line.
+ }
+ }
+ }
+ }
+
+ /**
+ * Escape quotes in a strings depending on the surrounding
+ * quote type used.
+ *
+ * @param $str The strings to escape
+ */
+ private static function _escape_quoted_string($str) {
+ $quo = substr($str, 0, 1);
+ $str = substr($str, 1, -1);
+ if ($quo == '"') {
+ $str = stripcslashes($str);
+ } else {
+ $str = strtr($str, array("\\'" => "'", "\\\\" => "\\"));
+ }
+ return addcslashes($str, "\0..\37\\\"");
+ }
+}
diff --git a/modules/gallery/helpers/locale.php b/modules/gallery/helpers/locale.php
new file mode 100644
index 00000000..2ba0f255
--- /dev/null
+++ b/modules/gallery/helpers/locale.php
@@ -0,0 +1,119 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * This is the API for handling locales.
+ */
+class locale_Core {
+ private static $locales;
+
+ /**
+ * Return the list of available locales.
+ */
+ static function available() {
+ if (empty(self::$locales)) {
+ self::_init_language_data();
+ }
+
+ return self::$locales;
+ }
+
+ static function installed() {
+ $available = self::available();
+ $default = module::get_var("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 = array_merge($locales, array($default));
+
+ module::set_var("gallery", "installed_locales", join("|", $locales));
+ }
+
+ // @todo Might want to add a localizable language name as well.
+ private static function _init_language_data() {
+ $l["af_ZA"] = "Afrikaans"; // Afrikaans
+ $l["ar_SA"] = "&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;"; // Arabic
+ $l["bg_BG"] = "&#x0411;&#x044a;&#x043b;&#x0433;&#x0430;&#x0440;&#x0441;&#x043a;&#x0438;"; // Bulgarian
+ $l["ca_ES"] = "Catalan"; // Catalan
+ $l["cs_CZ"] = "&#x010c;esky"; // Czech
+ $l["da_DK"] = "Dansk"; // Danish
+ $l["de_DE"] = "Deutsch"; // German
+ $l["el_GR"] = "Greek"; // Greek
+ $l["en_GB"] = "English (UK)"; // English (UK)
+ $l["en_US"] = "English (US)"; // English (US)
+ $l["es_AR"] = "Espa&#241;ol (AR)"; // Spanish (AR)
+ $l["es_ES"] = "Espa&#241;ol"; // Spanish (ES)
+ $l["es_MX"] = "Espa&#241;ol (MX)"; // Spanish (MX)
+ $l["et_EE"] = "Eesti"; // Estonian
+ $l["eu_ES"] = "Euskara"; // Basque
+ $l["fa_IR"] = "&#1601;&#1575;&#1585;&#1587;&#1610;"; // Farsi
+ $l["fi_FI"] = "Suomi"; // Finnish
+ $l["fr_FR"] = "Fran&#231;ais"; // French
+ $l["ga_IE"] = "Gaeilge"; // Irish
+ $l["he_IL"] = "&#1506;&#1489;&#1512;&#1497;&#1514;"; // Hebrew
+ $l["hu_HU"] = "Magyar"; // Hungarian
+ $l["is_IS"] = "Icelandic"; // Icelandic
+ $l["it_IT"] = "Italiano"; // Italian
+ $l["ja_JP"] = "&#x65e5;&#x672c;&#x8a9e;"; // Japanese
+ $l["ko_KR"] = "&#xd55c;&#xad6d;&#xb9d0;"; // Korean
+ $l["lt_LT"] = "Lietuvi&#371;"; // Lithuanian
+ $l["lv_LV"] = "Latvie&#353;u"; // Latvian
+ $l["nl_NL"] = "Nederlands"; // Dutch
+ $l["no_NO"] = "Norsk bokm&#229;l"; // Norwegian
+ $l["pl_PL"] = "Polski"; // Polish
+ $l["pt_BR"] = "Portugu&#234;s Brasileiro"; // Portuguese (BR)
+ $l["pt_PT"] = "Portugu&#234;s"; // Portuguese (PT)
+ $l["ro_RO"] = "Rom&#226;n&#259;"; // Romanian
+ $l["ru_RU"] = "&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;"; // Russian
+ $l["sk_SK"] = "Sloven&#269;ina"; // Slovak
+ $l["sl_SI"] = "Sloven&#353;&#269;ina"; // Slovenian
+ $l["sr_CS"] = "Srpski"; // Serbian
+ $l["sv_SE"] = "Svenska"; // Swedish
+ $l["tr_TR"] = "T&#252;rk&#231;e"; // Turkish
+ $l["uk_UA"] = "Українська"; // Ukrainian
+ $l["vi_VN"] = "Ti&#7871;ng Vi&#7879;t"; // Vietnamese
+ $l["zh_CN"] = "&#31616;&#20307;&#20013;&#25991;"; // Chinese (CN)
+ $l["zh_TW"] = "&#32321;&#39636;&#20013;&#25991;"; // Chinese (TW)
+ asort($l, SORT_LOCALE_STRING);
+ self::$locales = $l;
+ }
+
+ static function display_name($locale=null) {
+ if (empty(self::$locales)) {
+ self::_init_language_data();
+ }
+ $locale or $locale = I18n::instance()->locale();
+
+ return self::$locales["$locale"];
+ }
+
+ static function is_rtl($locale) {
+ return in_array($locale, array("he_IL", "fa_IR", "ar_SA"));
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/log.php b/modules/gallery/helpers/log.php
new file mode 100644
index 00000000..451f985a
--- /dev/null
+++ b/modules/gallery/helpers/log.php
@@ -0,0 +1,108 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class log_Core {
+ const SUCCESS = 1;
+ const INFO = 2;
+ const WARNING = 3;
+ const ERROR = 4;
+
+ /**
+ * Report a successful event.
+ * @param string $category an arbitrary category we can use to filter log messages
+ * @param string $message a detailed log message
+ * @param string $html an html snippet presented alongside the log message to aid the admin
+ */
+ static function success($category, $message, $html="") {
+ self::_add($category, $message, $html, self::SUCCESS);
+ }
+
+ /**
+ * Report an informational event.
+ * @param string $category an arbitrary category we can use to filter log messages
+ * @param string $message a detailed log message
+ * @param string $html an html snippet presented alongside the log message to aid the admin
+ */
+ static function info($category, $message, $html="") {
+ self::_add($category, $message, $html, self::INFO);
+ }
+
+ /**
+ * Report that something went wrong, not fatal, but worth investigation.
+ * @param string $category an arbitrary category we can use to filter log messages
+ * @param string $message a detailed log message
+ * @param string $html an html snippet presented alongside the log message to aid the admin
+ */
+ static function warning($category, $message, $html="") {
+ self::_add($category, $message, $html, self::WARNING);
+ }
+
+ /**
+ * Report that something went wrong that should be fixed.
+ * @param string $category an arbitrary category we can use to filter log messages
+ * @param string $message a detailed log message
+ * @param string $html an html snippet presented alongside the log message to aid the admin
+ */
+ static function error($category, $message, $html="") {
+ self::_add($category, $message, $html, self::ERROR);
+ }
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $category an arbitrary category we can use to filter log messages
+ * @param string $message a detailed log message
+ * @param integer $severity INFO, WARNING or ERROR
+ * @param string $html an html snippet presented alongside the log message to aid the admin
+ */
+ private static function _add($category, $message, $html, $severity) {
+ $log = ORM::factory("log");
+ $log->category = $category;
+ $log->message = $message;
+ $log->severity = $severity;
+ $log->html = $html;
+ $log->url = substr(url::abs_current(true), 0, 255);
+ $log->referer = request::referrer(null);
+ $log->timestamp = time();
+ $log->user_id = user::active()->id;
+ $log->save();
+ }
+
+
+ /**
+ * Convert a message severity to a CSS class
+ * @param integer $severity
+ * @return string
+ */
+ static function severity_class($severity) {
+ switch($severity) {
+ case self::SUCCESS:
+ return "gSuccess";
+
+ case self::INFO:
+ return "gInfo";
+
+ case self::WARNING:
+ return "gWarning";
+
+ case self::ERROR:
+ return "gError";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/message.php b/modules/gallery/helpers/message.php
new file mode 100644
index 00000000..af3b96cc
--- /dev/null
+++ b/modules/gallery/helpers/message.php
@@ -0,0 +1,108 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class message_Core {
+ const SUCCESS = 1;
+ const INFO = 2;
+ const WARNING = 3;
+ const ERROR = 4;
+
+ /**
+ * Report a successful event.
+ * @param string $msg a detailed message
+ */
+ static function success($msg) {
+ self::_add($msg, self::SUCCESS);
+ }
+
+ /**
+ * Report an informational event.
+ * @param string $msg a detailed message
+ */
+ static function info($msg) {
+ self::_add($msg, self::INFO);
+ }
+
+ /**
+ * Report that something went wrong, not fatal, but worth investigation.
+ * @param string $msg a detailed message
+ */
+ static function warning($msg) {
+ self::_add($msg, self::WARNING);
+ }
+
+ /**
+ * Report that something went wrong that should be fixed.
+ * @param string $msg a detailed message
+ */
+ static function error($msg) {
+ self::_add($msg, self::ERROR);
+ }
+
+ /**
+ * Save a message in the session for our next page load.
+ * @param string $msg a detailed message
+ * @param integer $severity one of the severity constants
+ */
+ private static function _add($msg, $severity) {
+ $session = Session::instance();
+ $status = $session->get("messages");
+ $status[] = array($msg, $severity);
+ $session->set("messages", $status);
+ }
+
+ /**
+ * Get any pending messages. There are two types of messages, transient and permanent.
+ * Permanent messages are used to let the admin know that there are pending administrative
+ * issues that need to be resolved. Transient ones are only displayed once.
+ * @return html text
+ */
+ static function get() {
+ $buf = array();
+
+ $messages = Session::instance()->get_once("messages", array());
+ foreach ($messages as $msg) {
+ $buf[] = "<li class=\"" . self::severity_class($msg[1]) . "\">$msg[0]</li>";
+ }
+ if ($buf) {
+ return "<ul id=\"gMessage\">" . implode("", $buf) . "</ul>";
+ }
+ }
+
+ /**
+ * Convert a message severity to a CSS class
+ * @param integer $severity
+ * @return string
+ */
+ static function severity_class($severity) {
+ switch($severity) {
+ case self::SUCCESS:
+ return "gSuccess";
+
+ case self::INFO:
+ return "gInfo";
+
+ case self::WARNING:
+ return "gWarning";
+
+ case self::ERROR:
+ return "gError";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php
new file mode 100644
index 00000000..2649fdbd
--- /dev/null
+++ b/modules/gallery/helpers/model_cache.php
@@ -0,0 +1,46 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class model_cache_Core {
+ private static $cache;
+
+ static function get($model_name, $id, $field_name="id") {
+ if (TEST_MODE || empty(self::$cache->$model_name->$field_name->$id)) {
+ $model = ORM::factory($model_name)->where($field_name, $id)->find();
+ if (!$model->loaded) {
+ throw new Exception("@todo MISSING_MODEL $model_name:$id");
+ }
+ self::$cache->$model_name->$field_name->$id = $model;
+ }
+
+ return self::$cache->$model_name->$field_name->$id;
+ }
+
+ static function clear($model_name, $id, $field_name="id") {
+ if (!empty(self::$cache->$model_name->$field_name->$id)) {
+ unset(self::$cache->$model_name->$field_name->$id);
+ }
+ }
+
+ static function set($model) {
+ self::$cache->{$model->object_name}
+ ->{$model->primary_key}
+ ->{$model->{$model->primary_key}} = $model;
+ }
+}
diff --git a/modules/gallery/helpers/module.php b/modules/gallery/helpers/module.php
new file mode 100644
index 00000000..a6cc87ac
--- /dev/null
+++ b/modules/gallery/helpers/module.php
@@ -0,0 +1,357 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * This is the API for handling modules.
+ *
+ * Note: by design, this class does not do any permission checking.
+ */
+class module_Core {
+ public static $active = array();
+ public static $modules = array();
+ public static $var_cache = null;
+
+ /**
+ * Set the version of the corresponding Module_Model
+ * @param string $module_name
+ * @param integer $version
+ */
+ static function set_version($module_name, $version) {
+ $module = self::get($module_name);
+ if (!$module->loaded) {
+ $module->name = $module_name;
+ $module->active = $module_name == "gallery"; // only gallery is active by default
+ }
+ $module->version = 1;
+ $module->save();
+ Kohana::log("debug", "$module_name: version is now $version");
+ }
+
+ /**
+ * Load the corresponding Module_Model
+ * @param string $module_name
+ */
+ static function get($module_name) {
+ // @todo can't easily use model_cache here because it throw an exception on missing models.
+ return ORM::factory("module", array("name" => $module_name));
+ }
+
+ /**
+ * Check to see if a module is installed
+ * @param string $module_name
+ */
+ static function is_installed($module_name) {
+ return array_key_exists($module_name, self::$modules);
+ }
+
+ /**
+ * Check to see if a module is active
+ * @param string $module_name
+ */
+ static function is_active($module_name) {
+ return array_key_exists($module_name, self::$modules) &&
+ self::$modules[$module_name]->active;
+ }
+
+ /**
+ * Return the list of available modules, including uninstalled modules.
+ */
+ static function available() {
+ $modules = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
+ foreach (array_merge(array("gallery/module.info"), glob(MODPATH . "*/module.info")) as $file) {
+ $module_name = basename(dirname($file));
+ $modules->$module_name = new ArrayObject(parse_ini_file($file), ArrayObject::ARRAY_AS_PROPS);
+ $modules->$module_name->installed = self::is_installed($module_name);
+ $modules->$module_name->active = self::is_active($module_name);
+ $modules->$module_name->version = self::get_version($module_name);
+ $modules->$module_name->locked = false;
+ }
+
+ // Lock certain modules
+ $modules->gallery->locked = true;
+ $modules->user->locked = true;
+ $modules->ksort();
+
+ return $modules;
+ }
+
+ /**
+ * Return a list of all the active modules in no particular order.
+ */
+ static function active() {
+ return self::$active;
+ }
+
+ /**
+ * Install a module. This will call <module>_installer::install(), which is responsible for
+ * creating database tables, setting module variables and and calling module::set_version().
+ * Note that after installing, the module must be activated before it is available for use.
+ * @param string $module_name
+ */
+ static function install($module_name) {
+ $kohana_modules = Kohana::config("core.modules");
+ $kohana_modules[] = MODPATH . $module_name;
+ Kohana::config_set("core.modules", $kohana_modules);
+
+ $installer_class = "{$module_name}_installer";
+ if (method_exists($installer_class, "install")) {
+ call_user_func_array(array($installer_class, "install"), array());
+ }
+
+ // Now the module is installed but inactive, so don't leave it in the active path
+ array_pop($kohana_modules);
+ Kohana::config_set("core.modules", $kohana_modules);
+
+ log::success(
+ "module", t("Installed module %module_name", array("module_name" => $module_name)));
+ }
+
+ /**
+ * Activate an installed module. This will call <module>_installer::activate() which should take
+ * any steps to make sure that the module is ready for use. This will also activate any
+ * existing graphics rules for this module.
+ * @param string $module_name
+ */
+ static function activate($module_name) {
+ $kohana_modules = Kohana::config("core.modules");
+ $kohana_modules[] = MODPATH . $module_name;
+ Kohana::config_set("core.modules", $kohana_modules);
+
+ $installer_class = "{$module_name}_installer";
+ if (method_exists($installer_class, "activate")) {
+ call_user_func_array(array($installer_class, "activate"), array());
+ }
+
+ $module = self::get($module_name);
+ if ($module->loaded) {
+ $module->active = true;
+ $module->save();
+ }
+
+ self::load_modules();
+ graphics::activate_rules($module_name);
+ log::success(
+ "module", t("Activated module %module_name", array("module_name" => $module_name)));
+ }
+
+ /**
+ * Deactivate an installed module. This will call <module>_installer::deactivate() which
+ * should take any cleanup steps to make sure that the module isn't visible in any way.
+ * @param string $module_name
+ */
+ static function deactivate($module_name) {
+ $installer_class = "{$module_name}_installer";
+ if (method_exists($installer_class, "deactivate")) {
+ call_user_func_array(array($installer_class, "deactivate"), array());
+ }
+
+ $module = self::get($module_name);
+ if ($module->loaded) {
+ $module->active = false;
+ $module->save();
+ }
+
+ self::load_modules();
+ graphics::deactivate_rules($module_name);
+ log::success(
+ "module", t("Deactivated module %module_name", array("module_name" => $module_name)));
+ }
+
+ /**
+ * Uninstall a deactivated module. This will call <module>_installer::uninstall() which should
+ * take whatever steps necessary to make sure that all traces of a module are gone.
+ * @param string $module_name
+ */
+ static function uninstall($module_name) {
+ $installer_class = "{$module_name}_installer";
+ if (method_exists($installer_class, "uninstall")) {
+ call_user_func(array($installer_class, "uninstall"));
+ }
+
+ graphics::remove_rule($module_name);
+ $module = self::get($module_name);
+ if ($module->loaded) {
+ $module->delete();
+ }
+
+ // We could delete the module vars here too, but it's nice to leave them around
+ // in case the module gets reinstalled.
+
+ self::load_modules();
+ log::success(
+ "module", t("Uninstalled module %module_name", array("module_name" => $module_name)));
+ }
+
+ /**
+ * Load the active modules. This is called at bootstrap time.
+ */
+ static function load_modules() {
+ // Reload module list from the config file since we'll do a refresh after calling install()
+ $core = Kohana::config_load("core");
+ $kohana_modules = $core["modules"];
+ $modules = ORM::factory("module")->find_all();
+
+ self::$modules = array();
+ self::$active = array();
+ foreach ($modules as $module) {
+ self::$modules[$module->name] = $module;
+ if ($module->active) {
+ self::$active[] = $module;
+ }
+ $kohana_modules[] = MODPATH . $module->name;
+ // @todo: force 'gallery' to be at the end
+ }
+
+ Kohana::config_set("core.modules", $kohana_modules);
+ }
+
+ /**
+ * Run a specific event on all active modules.
+ * @param string $name the event name
+ * @param mixed $data data to pass to each event handler
+ */
+ static function event($name, &$data=null) {
+ $args = func_get_args();
+ array_shift($args);
+ $function = str_replace(".", "_", $name);
+
+ foreach (self::$modules as $module) {
+ if (!$module->active) {
+ continue;
+ }
+
+ $class = "{$module->name}_event";
+ if (method_exists($class, $function)) {
+ call_user_func_array(array($class, $function), $args);
+ }
+ }
+ }
+
+ /**
+ * Get a variable from this module
+ * @param string $module_name
+ * @param string $name
+ * @param string $default_value
+ * @return the value
+ */
+ static function get_var($module_name, $name, $default_value=null) {
+ // We cache all vars in gallery._cache so that we can load all vars at once for
+ // performance.
+ if (empty(self::$var_cache)) {
+ $row = Database::instance()
+ ->select("value")
+ ->from("vars")
+ ->where(array("module_name" => "gallery", "name" => "_cache"))
+ ->get()
+ ->current();
+ if ($row) {
+ self::$var_cache = unserialize($row->value);
+ } else {
+ // gallery._cache doesn't exist. Create it now.
+ foreach (Database::instance()
+ ->select("module_name", "name", "value")
+ ->from("vars")
+ ->orderby("module_name", "name")
+ ->get() as $row) {
+ if ($row->module_name == "gallery" && $row->name == "_cache") {
+ // This could happen if there's a race condition
+ continue;
+ }
+ self::$var_cache->{$row->module_name}->{$row->name} = $row->value;
+ }
+ $cache = ORM::factory("var");
+ $cache->module_name = "gallery";
+ $cache->name = "_cache";
+ $cache->value = serialize(self::$var_cache);
+ $cache->save();
+ }
+ }
+
+ if (isset(self::$var_cache->$module_name->$name)) {
+ return self::$var_cache->$module_name->$name;
+ } else {
+ return $default_value;
+ }
+ }
+
+ /**
+ * Store a variable for this module
+ * @param string $module_name
+ * @param string $name
+ * @param string $value
+ */
+ static function set_var($module_name, $name, $value) {
+ $var = ORM::factory("var")
+ ->where("module_name", $module_name)
+ ->where("name", $name)
+ ->find();
+ if (!$var->loaded) {
+ $var->module_name = $module_name;
+ $var->name = $name;
+ }
+ $var->value = $value;
+ $var->save();
+
+ Database::instance()->delete("vars", array("module_name" => "gallery", "name" => "_cache"));
+ self::$var_cache = null;
+ }
+
+ /**
+ * Increment the value of a variable for this module
+ * @param string $module_name
+ * @param string $name
+ * @param string $increment (optional, default is 1)
+ */
+ static function incr_var($module_name, $name, $increment=1) {
+ Database::instance()->query(
+ "UPDATE {vars} SET `value` = `value` + $increment " .
+ "WHERE `module_name` = '$module_name' " .
+ "AND `name` = '$name'");
+
+ Database::instance()->delete("vars", array("module_name" => "gallery", "name" => "_cache"));
+ self::$var_cache = null;
+ }
+
+ /**
+ * Remove a variable for this module.
+ * @param string $module_name
+ * @param string $name
+ */
+ static function clear_var($module_name, $name) {
+ $var = ORM::factory("var")
+ ->where("module_name", $module_name)
+ ->where("name", $name)
+ ->find();
+ if ($var->loaded) {
+ $var->delete();
+ }
+
+ Database::instance()->delete("vars", array("module_name" => "gallery", "name" => "_cache"));
+ self::$var_cache = null;
+ }
+
+ /**
+ * Return the version of the installed module.
+ * @param string $module_name
+ */
+ static function get_version($module_name) {
+ return self::get($module_name)->version;
+ }
+}
diff --git a/modules/gallery/helpers/movie.php b/modules/gallery/helpers/movie.php
new file mode 100644
index 00000000..15225fe7
--- /dev/null
+++ b/modules/gallery/helpers/movie.php
@@ -0,0 +1,153 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * This is the API for handling movies.
+ *
+ * Note: by design, this class does not do any permission checking.
+ */
+class movie_Core {
+ /**
+ * Create a new movie.
+ * @param integer $parent_id id of parent album
+ * @param string $filename path to the photo file on disk
+ * @param string $name the filename to use for this photo in the album
+ * @param integer $title the title of the new photo
+ * @param string $description (optional) the longer description of this photo
+ * @return Item_Model
+ */
+ static function create($parent, $filename, $name, $title,
+ $description=null, $owner_id=null) {
+ if (!$parent->loaded || !$parent->is_album()) {
+ throw new Exception("@todo INVALID_PARENT");
+ }
+
+ if (!is_file($filename)) {
+ throw new Exception("@todo MISSING_MOVIE_FILE");
+ }
+
+ if (strpos($name, "/")) {
+ throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH");
+ }
+
+ // We don't allow trailing periods as a security measure
+ // ref: http://dev.kohanaphp.com/issues/684
+ if (rtrim($name, ".") != $name) {
+ throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD");
+ }
+
+ $movie_info = movie::getmoviesize($filename);
+
+ // Force an extension onto the name
+ $pi = pathinfo($filename);
+ if (empty($pi["extension"])) {
+ $pi["extension"] = image_type_to_extension($movie_info[2], false);
+ $name .= "." . $pi["extension"];
+ }
+
+ $movie = ORM::factory("item");
+ $movie->type = "movie";
+ $movie->title = $title;
+ $movie->description = $description;
+ $movie->name = $name;
+ $movie->owner_id = $owner_id ? $owner_id : user::active();
+ $movie->width = $movie_info[0];
+ $movie->height = $movie_info[1];
+ $movie->mime_type = strtolower($pi["extension"]) == "mp4" ? "video/mp4" : "video/x-flv";
+ $movie->thumb_dirty = 1;
+ $movie->resize_dirty = 1;
+ $movie->sort_column = "weight";
+ $movie->rand_key = ((float)mt_rand()) / (float)mt_getrandmax();
+
+ // Randomize the name if there's a conflict
+ while (ORM::Factory("item")
+ ->where("parent_id", $parent->id)
+ ->where("name", $movie->name)
+ ->find()->id) {
+ // @todo Improve this. Random numbers are not user friendly
+ $movie->name = rand() . "." . $pi["extension"];
+ }
+
+ // This saves the photo
+ $movie->add_to_parent($parent);
+
+ // If the thumb or resize already exists then rename it
+ if (file_exists($movie->resize_path()) ||
+ file_exists($movie->thumb_path())) {
+ $movie->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"];
+ $movie->save();
+ }
+
+ copy($filename, $movie->file_path());
+
+ module::event("item_created", $movie);
+
+ // Build our thumbnail
+ graphics::generate($movie);
+
+ // If the parent has no cover item, make this it.
+ if (access::can("edit", $parent) && $parent->album_cover_item_id == null) {
+ item::make_album_cover($movie);
+ }
+
+ return $movie;
+ }
+
+ static function getmoviesize($filename) {
+ $ffmpeg = self::find_ffmpeg();
+ if (empty($ffmpeg)) {
+ throw new Exception("@todo MISSING_FFMPEG");
+ }
+
+ $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($filename) . " 2>&1";
+ $result = `$cmd`;
+ if (preg_match("/Stream.*?Video:.*?(\d+)x(\d+).*\ +([0-9\.]+) (fps|tb).*/",
+ $result, $regs)) {
+ list ($width, $height) = array($regs[1], $regs[2]);
+ } else {
+ list ($width, $height) = array(0, 0);
+ }
+ return array($width, $height);
+ }
+
+ static function extract_frame($input_file, $output_file) {
+ $ffmpeg = self::find_ffmpeg();
+ if (empty($ffmpeg)) {
+ throw new Exception("@todo MISSING_FFMPEG");
+ }
+
+ $cmd = escapeshellcmd($ffmpeg) . " -i " . escapeshellarg($input_file) .
+ " -an -ss 00:00:03 -an -r 1 -vframes 1" .
+ " -y -f mjpeg " . escapeshellarg($output_file);
+ exec($cmd);
+ }
+
+ static function find_ffmpeg() {
+ if (!$ffmpeg_path = module::get_var("gallery", "ffmpeg_path")) {
+ if (function_exists("exec")) {
+ $ffmpeg_path = exec("which ffmpeg");
+ if ($ffmpeg_path) {
+ module::set_var("gallery", "ffmpeg_path", $ffmpeg_path);
+ }
+ }
+ }
+ return $ffmpeg_path;
+ }
+}
diff --git a/modules/gallery/helpers/photo.php b/modules/gallery/helpers/photo.php
new file mode 100644
index 00000000..c1c005f5
--- /dev/null
+++ b/modules/gallery/helpers/photo.php
@@ -0,0 +1,171 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * This is the API for handling photos.
+ *
+ * Note: by design, this class does not do any permission checking.
+ */
+class photo_Core {
+ /**
+ * Create a new photo.
+ * @param integer $parent_id id of parent album
+ * @param string $filename path to the photo file on disk
+ * @param string $name the filename to use for this photo in the album
+ * @param integer $title the title of the new photo
+ * @param string $description (optional) the longer description of this photo
+ * @return Item_Model
+ */
+ static function create($parent, $filename, $name, $title,
+ $description=null, $owner_id=null) {
+ if (!$parent->loaded || !$parent->is_album()) {
+ throw new Exception("@todo INVALID_PARENT");
+ }
+
+ if (!is_file($filename)) {
+ throw new Exception("@todo MISSING_IMAGE_FILE");
+ }
+
+ if (strpos($name, "/")) {
+ throw new Exception("@todo NAME_CANNOT_CONTAIN_SLASH");
+ }
+
+ // We don't allow trailing periods as a security measure
+ // ref: http://dev.kohanaphp.com/issues/684
+ if (rtrim($name, ".") != $name) {
+ throw new Exception("@todo NAME_CANNOT_END_IN_PERIOD");
+ }
+
+ $image_info = getimagesize($filename);
+
+ // Force an extension onto the name
+ $pi = pathinfo($filename);
+ if (empty($pi["extension"])) {
+ $pi["extension"] = image_type_to_extension($image_info[2], false);
+ $name .= "." . $pi["extension"];
+ }
+
+ $photo = ORM::factory("item");
+ $photo->type = "photo";
+ $photo->title = $title;
+ $photo->description = $description;
+ $photo->name = $name;
+ $photo->owner_id = $owner_id ? $owner_id : user::active();
+ $photo->width = $image_info[0];
+ $photo->height = $image_info[1];
+ $photo->mime_type = empty($image_info['mime']) ? "application/unknown" : $image_info['mime'];
+ $photo->thumb_dirty = 1;
+ $photo->resize_dirty = 1;
+ $photo->sort_column = "weight";
+ $photo->rand_key = ((float)mt_rand()) / (float)mt_getrandmax();
+
+ // Randomize the name if there's a conflict
+ while (ORM::Factory("item")
+ ->where("parent_id", $parent->id)
+ ->where("name", $photo->name)
+ ->find()->id) {
+ // @todo Improve this. Random numbers are not user friendly
+ $photo->name = rand() . "." . $pi["extension"];
+ }
+
+ // This saves the photo
+ $photo->add_to_parent($parent);
+
+ /*
+ * If the thumb or resize already exists then rename it. We need to do this after the save
+ * because the resize_path and thumb_path both call relative_path which caches the
+ * path. Before add_to_parent the relative path will be incorrect.
+ */
+ if (file_exists($photo->resize_path()) ||
+ file_exists($photo->thumb_path())) {
+ $photo->name = $pi["filename"] . "-" . rand() . "." . $pi["extension"];
+ $photo->save();
+ }
+
+ copy($filename, $photo->file_path());
+
+ module::event("item_created", $photo);
+
+ // Build our thumbnail/resizes
+ graphics::generate($photo);
+
+ // If the parent has no cover item, make this it.
+ if (access::can("edit", $parent) && $parent->album_cover_item_id == null) {
+ item::make_album_cover($photo);
+ }
+
+ return $photo;
+ }
+
+ static function get_add_form($parent) {
+ $form = new Forge("albums/{$parent->id}", "", "post", array("id" => "gAddPhotoForm"));
+ $group = $form->group("add_photo")->label(
+ t("Add Photo to %album_title", array("album_title" =>$parent->title)));
+ $group->input("title")->label(t("Title"));
+ $group->textarea("description")->label(t("Description"));
+ $group->input("name")->label(t("Filename"));
+ $group->upload("file")->label(t("File"))->rules("required|allow[jpg,png,gif,flv,mp4]");
+ $group->hidden("type")->value("photo");
+ $group->submit("")->value(t("Upload"));
+ $form->add_rules_from(ORM::factory("item"));
+ return $form;
+ }
+
+ static function get_edit_form($photo) {
+ $form = new Forge("photos/$photo->id", "", "post", array("id" => "gEditPhotoForm"));
+ $form->hidden("_method")->value("put");
+ $group = $form->group("edit_photo")->label(t("Edit Photo"));
+ $group->input("title")->label(t("Title"))->value($photo->title);
+ $group->textarea("description")->label(t("Description"))->value($photo->description);
+ $group->input("filename")->label(t("Filename"))->value($photo->name)
+ ->error_messages("conflict", t("There is already a file with this name"))
+ ->callback("item::validate_no_slashes")
+ ->error_messages("no_slashes", t("The photo name can't contain a \"/\""))
+ ->callback("item::validate_no_trailing_period")
+ ->error_messages("no_trailing_period", t("The photo name can't end in \".\""));
+
+ $group->submit("")->value(t("Modify"));
+ $form->add_rules_from(ORM::factory("item"));
+ return $form;
+ }
+
+ /**
+ * Return scaled width and height.
+ *
+ * @param integer $width
+ * @param integer $height
+ * @param integer $max the target size for the largest dimension
+ * @param string $format the output format using %d placeholders for width and height
+ */
+ static function img_dimensions($width, $height, $max, $format="width=\"%d\" height=\"%d\"") {
+ if (!$width || !$height) {
+ return "";
+ }
+
+ if ($width > $height) {
+ $new_width = $max;
+ $new_height = (int)$max * ($height / $width);
+ } else {
+ $new_height = $max;
+ $new_width = (int)$max * ($width / $height);
+ }
+ return sprintf($format, $new_width, $new_height);
+ }
+}
diff --git a/modules/gallery/helpers/rest.php b/modules/gallery/helpers/rest.php
new file mode 100644
index 00000000..a63b94c8
--- /dev/null
+++ b/modules/gallery/helpers/rest.php
@@ -0,0 +1,116 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+class rest_Core {
+ const OK = "200 OK";
+ const CREATED = "201 Created";
+ const ACCEPTED = "202 Accepted";
+ const NO_CONTENT = "204 No Content";
+ const RESET_CONTENT = "205 Reset Content";
+ const PARTIAL_CONTENT = "206 Partial Content";
+ const MOVED_PERMANENTLY = "301 Moved Permanently";
+ const FOUND = "302 Found";
+ const SEE_OTHER = "303 See Other";
+ const NOT_MODIFIED = "304 Not Modified";
+ const TEMPORARY_REDIRECT = "307 Temporary Redirect";
+ const BAD_REQUEST = "400 Bad Request";
+ const UNAUTHORIZED = "401 Unauthorized";
+ const FORBIDDEN = "403 Forbidden";
+ const NOT_FOUND = "404 Not Found";
+ const METHOD_NOT_ALLOWED = "405 Method Not Allowed";
+ const NOT_ACCEPTABLE = "406 Not Acceptable";
+ const CONFLICT = "409 Conflict";
+ const GONE = "410 Gone";
+ const LENGTH_REQUIRED = "411 Length Required";
+ const PRECONDITION_FAILED = "412 Precondition Failed";
+ const UNSUPPORTED_MEDIA_TYPE = "415 Unsupported Media Type";
+ const EXPECTATION_FAILED = "417 Expectation Failed";
+ const INTERNAL_SERVER_ERROR = "500 Internal Server Error";
+ const SERVICE_UNAVAILABLE = "503 Service Unavailable";
+
+ const XML = "application/xml";
+ const ATOM = "application/atom+xml";
+ const RSS = "application/rss+xml";
+ const JSON = "application/json";
+ const HTML = "text/html";
+
+ /**
+ * We're expecting to run in an environment that only supports GET/POST, so expect to tunnel
+ * PUT and DELETE through POST.
+ *
+ * Returns the HTTP request method taking into consideration PUT/DELETE tunneling.
+ * @return string HTTP request method
+ */
+ static function request_method() {
+ if (request::method() == "get") {
+ return "get";
+ } else {
+ $input = Input::instance();
+ switch (strtolower($input->post("_method", $input->get("_method", request::method())))) {
+ case "put": return "put";
+ case "delete": return "delete";
+ default: return "post";
+ }
+ }
+ }
+
+ /**
+ * Choose an output format based on what the client prefers to accept.
+ * @return string "html", "xml" or "json"
+ */
+ static function output_format() {
+ // Pick a format, but let it be overridden.
+ $input = Input::instance();
+ $fmt = $input->get(
+ "_format", $input->post(
+ "_format", request::preferred_accept(
+ array("xhtml", "html", "xml", "json"))));
+
+ // Some browsers (Chrome!) prefer xhtml over html, but we'll normalize this to html for now.
+ if ($fmt == "xhtml") {
+ $fmt = "html";
+ }
+ return $fmt;
+ }
+
+ /**
+ * Set HTTP response code.
+ * @param string Use one of the status code constants defined in this class.
+ */
+ static function http_status($status_code) {
+ header("HTTP/1.1 " . $status_code);
+ }
+
+ /**
+ * Set HTTP Location header.
+ * @param string URL
+ */
+ static function http_location($url) {
+ header("Location: " . $url);
+ }
+
+ /**
+ * Set HTTP Content-Type header.
+ * @param string content type
+ */
+ static function http_content_type($type) {
+ header("Content-Type: " . $type);
+ }
+}
diff --git a/modules/gallery/helpers/site_status.php b/modules/gallery/helpers/site_status.php
new file mode 100644
index 00000000..6d47e565
--- /dev/null
+++ b/modules/gallery/helpers/site_status.php
@@ -0,0 +1,132 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class site_status_Core {
+ const SUCCESS = 1;
+ const INFO = 2;
+ const WARNING = 3;
+ const ERROR = 4;
+
+ /**
+ * Report a successful event.
+ * @param string $msg a detailed message
+ * @param string $permanent_key make this message permanent and store it under this key
+ */
+ static function success($msg, $permanent_key) {
+ self::_add($msg, self::SUCCESS, $permanent_key);
+ }
+
+ /**
+ * Report an informational event.
+ * @param string $msg a detailed message
+ * @param string $permanent_key make this message permanent and store it under this key
+ */
+ static function info($msg, $permanent_key) {
+ self::_add($msg, self::INFO, $permanent_key);
+ }
+
+ /**
+ * Report that something went wrong, not fatal, but worth investigation.
+ * @param string $msg a detailed message
+ * @param string $permanent_key make this message permanent and store it under this key
+ */
+ static function warning($msg, $permanent_key) {
+ self::_add($msg, self::WARNING, $permanent_key);
+ }
+
+ /**
+ * Report that something went wrong that should be fixed.
+ * @param string $msg a detailed message
+ * @param string $permanent_key make this message permanent and store it under this key
+ */
+ static function error($msg, $permanent_key) {
+ self::_add($msg, self::ERROR, $permanent_key);
+ }
+
+ /**
+ * Save a message in the session for our next page load.
+ * @param string $msg a detailed message
+ * @param integer $severity one of the severity constants
+ * @param string $permanent_key make this message permanent and store it under this key
+ */
+ private static function _add($msg, $severity, $permanent_key) {
+ $message = ORM::factory("message")
+ ->where("key", $permanent_key)
+ ->find();
+ if (!$message->loaded) {
+ $message->key = $permanent_key;
+ }
+ $message->severity = $severity;
+ $message->value = $msg;
+ $message->save();
+ }
+
+ /**
+ * Remove any permanent message by key.
+ * @param string $permanent_key
+ */
+ static function clear($permanent_key) {
+ $message = ORM::factory("message")->where("key", $permanent_key)->find();
+ if ($message->loaded) {
+ $message->delete();
+ }
+ }
+
+ /**
+ * Get any pending messages. There are two types of messages, transient and permanent.
+ * Permanent messages are used to let the admin know that there are pending administrative
+ * issues that need to be resolved. Transient ones are only displayed once.
+ * @return html text
+ */
+ static function get() {
+ if (!user::active()->admin) {
+ return;
+ }
+ $buf = array();
+ foreach (ORM::factory("message")->find_all() as $msg) {
+ $value = str_replace('__CSRF__', access::csrf_token(), $msg->value);
+ $buf[] = "<li class=\"" . self::severity_class($msg->severity) . "\">$value</li>";
+ }
+
+ if ($buf) {
+ return "<ul id=\"gSiteStatus\">" . implode("", $buf) . "</ul>";
+ }
+ }
+
+ /**
+ * Convert a message severity to a CSS class
+ * @param integer $severity
+ * @return string
+ */
+ static function severity_class($severity) {
+ switch($severity) {
+ case self::SUCCESS:
+ return "gSuccess";
+
+ case self::INFO:
+ return "gInfo";
+
+ case self::WARNING:
+ return "gWarning";
+
+ case self::ERROR:
+ return "gError";
+ }
+ }
+}
diff --git a/modules/gallery/helpers/task.php b/modules/gallery/helpers/task.php
new file mode 100644
index 00000000..a8a004ab
--- /dev/null
+++ b/modules/gallery/helpers/task.php
@@ -0,0 +1,83 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class task_Core {
+ /**
+ * Get all available tasks
+ */
+ static function get_definitions() {
+ $tasks = array();
+ foreach (module::active() as $module) {
+ $class_name = "{$module->name}_task";
+ if (method_exists($class_name, "available_tasks")) {
+ foreach (call_user_func(array($class_name, "available_tasks")) as $task) {
+ $tasks[$task->callback] = $task;
+ }
+ }
+ }
+
+ return $tasks;
+ }
+
+ static function create($task_def, $context) {
+ $task = ORM::factory("task");
+ $task->callback = $task_def->callback;
+ $task->name = $task_def->name;
+ $task->percent_complete = 0;
+ $task->status = "";
+ $task->state = "started";
+ $task->owner_id = user::active()->id;
+ $task->context = serialize($context);
+ $task->save();
+
+ return $task;
+ }
+
+ static function cancel($task_id) {
+ $task = ORM::factory("task", $task_id);
+ if (!$task->loaded) {
+ throw new Exception("@todo MISSING_TASK");
+ }
+ $task->done = 1;
+ $task->state = "cancelled";
+ $task->save();
+
+ return $task;
+ }
+
+ static function remove($task_id) {
+ $task = ORM::factory("task", $task_id);
+ if ($task->loaded) {
+ $task->delete();
+ }
+ }
+
+ static function run($task_id) {
+ $task = ORM::factory("task", $task_id);
+ if (!$task->loaded) {
+ throw new Exception("@todo MISSING_TASK");
+ }
+
+ $task->state = "running";
+ call_user_func_array($task->callback, array(&$task));
+ $task->save();
+
+ return $task;
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/helpers/theme.php b/modules/gallery/helpers/theme.php
new file mode 100644
index 00000000..af340db6
--- /dev/null
+++ b/modules/gallery/helpers/theme.php
@@ -0,0 +1,62 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * This is the API for handling themes.
+ *
+ * Note: by design, this class does not do any permission checking.
+ */
+class theme_Core {
+ /**
+ * Load the active theme. This is called at bootstrap time. We will only ever have one theme
+ * active for any given request.
+ */
+ static function load_themes() {
+ $modules = Kohana::config("core.modules");
+ if (Router::$controller == "admin") {
+ array_unshift($modules, THEMEPATH . module::get_var("gallery", "active_admin_theme"));
+ } else {
+ array_unshift($modules, THEMEPATH . module::get_var("gallery", "active_site_theme"));
+ }
+
+ Kohana::config_set("core.modules", $modules);
+ }
+
+ static function get_edit_form_admin() {
+ $form = new Forge("admin/theme_details/save/", "", null, array("id" =>"gThemeDetailsForm"));
+ $group = $form->group("edit_theme");
+ $group->input("page_size")->label(t("Items per page"))->id("gPageSize")
+ ->rules("required|valid_digit")
+ ->value(module::get_var("gallery", "page_size"));
+ $group->input("thumb_size")->label(t("Thumbnail size (in pixels)"))->id("gThumbSize")
+ ->rules("required|valid_digit")
+ ->value(module::get_var("gallery", "thumb_size"));
+ $group->input("resize_size")->label(t("Resized image size (in pixels)"))->id("gResizeSize")
+ ->rules("required|valid_digit")
+ ->value(module::get_var("gallery", "resize_size"));
+ $group->textarea("header_text")->label(t("Header text"))->id("gHeaderText")
+ ->value(module::get_var("gallery", "header_text"));
+ $group->textarea("footer_text")->label(t("Footer text"))->id("gFooterText")
+ ->value(module::get_var("gallery", "footer_text"));
+ $group->submit("")->value(t("Save"));
+ return $form;
+ }
+}
+
diff --git a/modules/gallery/helpers/xml.php b/modules/gallery/helpers/xml.php
new file mode 100644
index 00000000..e734e90c
--- /dev/null
+++ b/modules/gallery/helpers/xml.php
@@ -0,0 +1,35 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class xml_Core {
+ static function to_xml($array, $element_names) {
+ $xml = "<$element_names[0]>\n";
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $xml .= xml::to_xml($value, array_slice($element_names, 1));
+ } else if (is_object($value)) {
+ $xml .= xml::to_xml($value->as_array(), array_slice($element_names, 1));
+ } else {
+ $xml .= "<$key>$value</$key>\n";
+ }
+ }
+ $xml .= "</$element_names[0]>\n";
+ return $xml;
+ }
+}