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