summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Kinkade <nkinkade@nkinka.de>2010-01-19 16:34:34 +0000
committerNathan Kinkade <nkinkade@nkinka.de>2010-01-19 16:34:34 +0000
commite47505081f2c1017a68f763e1170b44fddd1e722 (patch)
treeeb511a8d9bae266ed6cf86f789f270642af93e90
parent0a67b836a9b5021c91b9c327d3693991c3248dfc (diff)
parent9384f987bb96d0d39787ff9d3d16a70c01cd76e0 (diff)
Merge branch 'master' of git://github.com/gallery/gallery3
-rw-r--r--installer/install.sql2
-rw-r--r--modules/comment/controllers/admin_comments.php5
-rw-r--r--modules/comment/models/comment.php4
-rw-r--r--modules/gallery/controllers/l10n_client.php17
-rw-r--r--modules/gallery/css/l10n_client.css30
-rw-r--r--modules/gallery/helpers/MY_url.php14
-rw-r--r--modules/gallery/helpers/access.php4
-rw-r--r--modules/gallery/helpers/gallery_installer.php18
-rw-r--r--modules/gallery/helpers/graphics.php5
-rw-r--r--modules/gallery/helpers/model_cache.php14
-rw-r--r--modules/gallery/js/l10n_client.js49
-rw-r--r--modules/gallery/models/item.php4
-rw-r--r--modules/gallery/module.info2
-rw-r--r--modules/gallery/tests/Access_Helper_Test.php1
-rw-r--r--modules/gallery/tests/Album_Helper_Test.php2
-rw-r--r--modules/gallery/tests/Gallery_I18n_Test.php5
-rw-r--r--modules/gallery/tests/Gallery_Installer_Test.php2
-rw-r--r--modules/gallery/tests/Gallery_Rest_Helper_Test.php12
-rw-r--r--modules/gallery/tests/Item_Model_Test.php2
-rw-r--r--modules/gallery/tests/Url_Security_Test.php43
-rw-r--r--modules/gallery/tests/xss_data.txt2
-rw-r--r--modules/gallery/views/l10n_client.html.php2
-rw-r--r--modules/notification/helpers/notification.php29
-rw-r--r--modules/notification/helpers/notification_event.php7
-rw-r--r--modules/notification/views/item_updated.html.php4
-rw-r--r--modules/rest/helpers/rest_event.php5
-rw-r--r--modules/rss/controllers/rss.php8
-rw-r--r--modules/search/helpers/search_event.php5
-rw-r--r--modules/server_add/controllers/server_add.php5
-rw-r--r--modules/tag/tests/Tag_Rest_Helper_Test.php4
-rw-r--r--modules/user/models/group.php4
-rw-r--r--modules/user/models/user.php4
-rw-r--r--system/libraries/Database_Builder.php996
-rw-r--r--system/libraries/ORM.php73
34 files changed, 832 insertions, 551 deletions
diff --git a/installer/install.sql b/installer/install.sql
index 6e7c06a2..95a57d86 100644
--- a/installer/install.sql
+++ b/installer/install.sql
@@ -89,7 +89,7 @@ CREATE TABLE {graphics_rules} (
) AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
SET character_set_client = @saved_cs_client;
INSERT INTO {graphics_rules} VALUES (1,1,'a:3:{s:5:\"width\";i:200;s:6:\"height\";i:200;s:6:\"master\";i:2;}','gallery','gallery_graphics::resize',100,'thumb');
-INSERT INTO {graphics_rules} VALUES (2,1,'a:3:{s:5:\"width\";i:640;s:6:\"height\";i:480;s:6:\"master\";i:2;}','gallery','gallery_graphics::resize',100,'resize');
+INSERT INTO {graphics_rules} VALUES (2,1,'a:3:{s:5:\"width\";i:640;s:6:\"height\";i:640;s:6:\"master\";i:2;}','gallery','gallery_graphics::resize',100,'resize');
DROP TABLE IF EXISTS {groups};
SET @saved_cs_client = @@character_set_client;
SET character_set_client = utf8;
diff --git a/modules/comment/controllers/admin_comments.php b/modules/comment/controllers/admin_comments.php
index 880c33a7..b7dc5fb3 100644
--- a/modules/comment/controllers/admin_comments.php
+++ b/modules/comment/controllers/admin_comments.php
@@ -121,9 +121,10 @@ class Admin_Comments_Controller extends Admin_Controller {
public function delete_all_spam() {
access::verify_csrf();
- ORM::factory("comment")
+ db::build()
+ ->delete("comments")
->where("state", "=", "spam")
- ->delete_all();
+ ->execute();
url::redirect("admin/comments/queue/spam");
}
}
diff --git a/modules/comment/models/comment.php b/modules/comment/models/comment.php
index 59b85233..e0b82039 100644
--- a/modules/comment/models/comment.php
+++ b/modules/comment/models/comment.php
@@ -65,12 +65,14 @@ class Comment_Model extends ORM {
}
}
$visible_change = $this->original()->state == "published" || $this->state == "published";
+
+ $original = clone $this->original();
parent::save();
if (isset($created)) {
module::event("comment_created", $this);
} else {
- module::event("comment_updated", $this->original(), $this);
+ module::event("comment_updated", $original, $this);
}
// We only notify on the related items if we're making a visible change.
diff --git a/modules/gallery/controllers/l10n_client.php b/modules/gallery/controllers/l10n_client.php
index 71df1cf1..e20bab50 100644
--- a/modules/gallery/controllers/l10n_client.php
+++ b/modules/gallery/controllers/l10n_client.php
@@ -29,9 +29,9 @@ class L10n_Client_Controller extends Controller {
$key = $input->post("l10n-message-key");
$root_message = ORM::factory("incoming_translation")
- ->where(array("key" => $key,
- "locale" => "root"))
- ->find();
+ ->where("key", "=", $key)
+ ->where("locale", "=", "root")
+ ->find();
if (!$root_message->loaded()) {
throw new Exception("@todo bad request data / illegal state");
@@ -56,8 +56,8 @@ class L10n_Client_Controller extends Controller {
}
$entry = ORM::factory("outgoing_translation")
- ->where(array("key" => $key,
- "locale" => $locale))
+ ->where("key", "=", $key)
+ ->where("locale", "=", $locale)
->find();
if (!$entry->loaded()) {
@@ -70,8 +70,8 @@ class L10n_Client_Controller extends Controller {
$entry->translation = serialize($translation);
$entry_from_incoming = ORM::factory("incoming_translation")
- ->where(array("key" => $key,
- "locale" => $locale))
+ ->where("key", "=", $key)
+ ->where("locale", "=", $locale)
->find();
if (!$entry_from_incoming->loaded()) {
@@ -102,10 +102,9 @@ class L10n_Client_Controller extends Controller {
}
private static function _l10n_client_search_form() {
- $form = new Forge("l10n_client/search", "", "post", array("id" => "g-l10n-search-form"));
+ $form = new Forge("#", "", "post", array("id" => "g-l10n-search-form"));
$group = $form->group("l10n_search");
$group->input("l10n-search")->id("g-l10n-search");
- $group->submit("l10n-search-filter-clear")->value(t("X"));
return $form;
}
diff --git a/modules/gallery/css/l10n_client.css b/modules/gallery/css/l10n_client.css
index 542da8e6..3771c049 100644
--- a/modules/gallery/css/l10n_client.css
+++ b/modules/gallery/css/l10n_client.css
@@ -86,7 +86,7 @@
}
#l10n-client .string-list {
- height:17em;
+ height: 16em;
overflow:auto;
list-style:none; list-style-image:none;
margin:0em; padding:0em;}
@@ -129,10 +129,9 @@
font-weight:bold;}
#l10n-client #g-l10n-search-form {
- background:#eee;
- text-align:center;
- height:2em; line-height:2em;
- margin:0em; padding:.5em .5em;
+ background: #eee;
+ margin: 0em;
+ padding: .25em .25em;
}
#l10n-client #g-l10n-search-form .form-item,
@@ -148,8 +147,14 @@
padding:0em;
}
-#l10n-client #g-l10n-search-form input.form-text {
- width:80%;
+#l10n-client #g-l10n-search-form fieldset {
+ margin-bottom: 0;
+ padding: .25em .25em;
+}
+
+
+#l10n-client #g-l10n-search-form input {
+ width: 96.75%;
}
#l10n-client #g-l10n-search-form #search-filter-clear {
@@ -164,7 +169,9 @@
#l10n-client-string-editor .source {
overflow:hidden;
- width:50%; float:left;}
+ width:50%;
+ float:left;
+}
#l10n-client-string-editor .source .source-text {
line-height:1.5em;
@@ -178,10 +185,13 @@
#l10n-client-string-editor .translation {
overflow:hidden;
- width:49%; float:right;}
+ width:49%;
+ float: right;
+}
#g-l10n-client-save-form {
- padding:0em;}
+ padding: 0em;
+}
#l10n-client form ul,
#l10n-client form li,
diff --git a/modules/gallery/helpers/MY_url.php b/modules/gallery/helpers/MY_url.php
index 74284951..8a7909b6 100644
--- a/modules/gallery/helpers/MY_url.php
+++ b/modules/gallery/helpers/MY_url.php
@@ -89,4 +89,18 @@ class url extends url_Core {
static function abs_current($qs=false) {
return self::abs_site(url::current($qs));
}
+
+ /**
+ * Just like url::merge except that it escapes any XSS in the path.
+ */
+ static function merge($params) {
+ return htmlspecialchars(parent::merge($params));
+ }
+
+ /**
+ * Just like url::current except that it escapes any XSS in the path.
+ */
+ static function current($qs=false, $suffix=false) {
+ return htmlspecialchars(parent::current($qs, $suffix));
+ }
}
diff --git a/modules/gallery/helpers/access.php b/modules/gallery/helpers/access.php
index 8ce7e436..e0a0e979 100644
--- a/modules/gallery/helpers/access.php
+++ b/modules/gallery/helpers/access.php
@@ -66,8 +66,8 @@
* the Access_Intent_Model
*/
class access_Core {
- const DENY = false;
- const ALLOW = true;
+ const DENY = "0";
+ const ALLOW = "1";
const INHERIT = null; // access_intent
const UNKNOWN = null; // cache (access_cache, items)
diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php
index 410b6413..1e0ad28c 100644
--- a/modules/gallery/helpers/gallery_installer.php
+++ b/modules/gallery/helpers/gallery_installer.php
@@ -240,7 +240,7 @@ class gallery_installer {
100);
graphics::add_rule(
"gallery", "resize", "gallery_graphics::resize",
- array("width" => 640, "height" => 480, "master" => Image::AUTO),
+ array("width" => 640, "height" => 640, "master" => Image::AUTO),
100);
// Instantiate default themes (site and admin)
@@ -440,6 +440,22 @@ class gallery_installer {
module::set_var("gallery", "simultaneous_upload_limit", 5);
module::set_version("gallery", $version = 21);
}
+
+ // Update the graphics rules table so that the maximum height for resizes is 640 not 480.
+ // Fixes ticket #671
+ if ( $version == 21) {
+ $resize_rule = ORM::factory("graphics_rule")
+ ->where("id", "=", "2")
+ ->find();
+ // make sure it hasn't been changed already
+ $args = unserialize($resize_rule->args);
+ if ($args["height"] == 480 && $args["width"] == 640) {
+ $args["height"] = 640;
+ $resize_rule->args = serialize($args);
+ $resize_rule->save();
+ }
+ module::set_version("gallery", $version = 22);
+ }
}
static function uninstall() {
diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php
index 7577d7ac..5a290905 100644
--- a/modules/gallery/helpers/graphics.php
+++ b/modules/gallery/helpers/graphics.php
@@ -60,11 +60,12 @@ class graphics_Core {
* @param string $operation the name of the operation(<defining class>::method)
*/
static function remove_rule($module_name, $target, $operation) {
- ORM::factory("graphics_rule")
+ db::build()
+ ->delete("graphics_rules")
->where("module_name", "=", $module_name)
->where("target", "=", $target)
->where("operation", "=", $operation)
- ->delete_all();
+ ->execute();
self::mark_dirty($target == "thumb", $target == "resize");
}
diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php
index 302e42d9..88756407 100644
--- a/modules/gallery/helpers/model_cache.php
+++ b/modules/gallery/helpers/model_cache.php
@@ -18,27 +18,25 @@
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class model_cache_Core {
- private static $cache;
+ private static $cache = array();
static function get($model_name, $id, $field_name="id") {
- if (TEST_MODE || empty(self::$cache->$model_name->$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;
+ self::$cache[$model_name][$field_name][$id] = $model;
}
- return self::$cache->$model_name->$field_name->$id;
+ return self::$cache[$model_name][$field_name][$id];
}
static function clear() {
- self::$cache = new stdClass();
+ self::$cache = array();
}
static function set($model) {
- self::$cache->{$model->object_name}
- ->{$model->primary_key}
- ->{$model->{$model->primary_key}} = $model;
+ self::$cache[$model->object_name][$model->primary_key][$model->{$model->primary_key}] = $model;
}
}
diff --git a/modules/gallery/js/l10n_client.js b/modules/gallery/js/l10n_client.js
index 9acb6ca8..d0d6f619 100644
--- a/modules/gallery/js/l10n_client.js
+++ b/modules/gallery/js/l10n_client.js
@@ -17,7 +17,6 @@ $(document).ready(function() {
Gallery.attachBehaviors(this);
});
-
// Store all l10n_client related data + methods in its own object
jQuery.extend(Gallery, {
l10nClient: new (function() {
@@ -51,7 +50,8 @@ jQuery.extend(Gallery, {
this.filter(false);
break;
}
- }
+ };
+
// Toggle the l10nclient
this.toggle = function(state) {
switch(state) {
@@ -82,15 +82,21 @@ jQuery.extend(Gallery, {
$.cookie('Gallery_l10n_client', '0', {expires: 7, path: '/'});
break;
}
- }
+ };
+
// Get a string from the DOM tree
this.getString = function(index, type) {
- return l10n_client_data[index][type];
- }
+ if (index < l10n_client_data.length) {
+ return l10n_client_data[index][type];
+ }
+ return "";
+ };
+
// Set a string in the DOM tree
this.setString = function(index, data) {
l10n_client_data[index]['translation'] = data;
- }
+ };
+
// Display the source message
this.showSourceMessage = function(source, is_plural) {
if (is_plural) {
@@ -101,10 +107,11 @@ jQuery.extend(Gallery, {
var pretty_source = $('#source-text-tmp-space').text(source).html();
}
$('#l10n-client-string-editor .source-text').html(pretty_source);
- }
+ };
this.isPluralMessage = function(message) {
return typeof(message) == 'object';
- }
+ };
+
this.updateTranslationForm = function(translation, is_plural) {
$('.translationField').addClass('hidden');
if (is_plural) {
@@ -122,10 +129,11 @@ jQuery.extend(Gallery, {
$('#plural-' + form).removeClass('hidden');
}
} else {
- $('#l10n-edit-translation').attr('value', translation);
+ $('#l10n-edit-translation').attr('value', translation);
$('#l10n-edit-translation').removeClass('hidden');
}
- }
+ };
+
// Filter the string list by a search string
this.filter = function(search) {
if(search == false || search == '') {
@@ -140,7 +148,7 @@ jQuery.extend(Gallery, {
$('#l10n-client #g-l10n-search').val(search);
}
}
- }
+ };
this.copySourceText = function() {
var index = Gallery.l10nClient.selected;
@@ -166,7 +174,7 @@ jQuery.extend(Gallery, {
}
}
- }
+ };
})
});
@@ -205,8 +213,8 @@ Gallery.behaviors.l10nClient = function(context) {
Gallery.l10nClient.toggle(0);
}
});
-
- // Close the l10n client using an AJAX call and refreshing the page
+
+ // Close the l10n client using an AJAX call and refreshing the page
$('#g-close-l10n').click(function(event) {
$.ajax({
type: "GET",
@@ -222,10 +230,15 @@ Gallery.behaviors.l10nClient = function(context) {
// Register keybindings using jQuery hotkeys
// TODO: Either remove hotkeys code or add query.hotkeys.js.
if($.hotkeys) {
- $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle')});
- $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #g-l10n-search', type:'keyup'}, function(){Gallery.l10nClient.key('clear')});
+ $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle');});
+ $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #g-l10n-search', type:'keyup'}, function(){Gallery.l10nClient.key('clear');});
}
+ // never actually submit the form as the search is done in the browser
+ $('#g-l10n-search-form').submit(function() {
+ return false;
+ });
+
// Custom listener for l10n_client livesearch
$('#l10n-client #g-l10n-search').keyup(function(key) {
Gallery.l10nClient.filter($('#l10n-client #g-l10n-search').val());
@@ -246,9 +259,9 @@ Gallery.behaviors.l10nClient = function(context) {
var num_plural_forms = plural_forms.length;
// Store translation in local js
+ var translation = {};
if (is_plural) {
- var translation = {};
- for (var i = 0; i < num_plural_forms; i++) {
+ for (var i = 0; i < num_plural_forms; i++) {
var form = plural_forms[i];
translation[form] = $('#g-l10n-client-save-form #l10n-edit-plural-translation-' + form).attr('value');
}
diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php
index 4a3d26e9..6851e1a3 100644
--- a/modules/gallery/models/item.php
+++ b/modules/gallery/models/item.php
@@ -416,9 +416,11 @@ class Item_Model extends ORM_MPTT {
$send_event = 1;
}
}
+
+ $original = clone $this->original();
parent::save();
if (isset($send_event)) {
- module::event("item_updated", $this->original(), $this);
+ module::event("item_updated", $original, $this);
}
return $this;
}
diff --git a/modules/gallery/module.info b/modules/gallery/module.info
index b3366f7d..107d9a12 100644
--- a/modules/gallery/module.info
+++ b/modules/gallery/module.info
@@ -1,4 +1,4 @@
name = "Gallery 3"
description = "Gallery core application"
-version = 21
+version = 22
diff --git a/modules/gallery/tests/Access_Helper_Test.php b/modules/gallery/tests/Access_Helper_Test.php
index 084bfb47..b2244766 100644
--- a/modules/gallery/tests/Access_Helper_Test.php
+++ b/modules/gallery/tests/Access_Helper_Test.php
@@ -74,6 +74,7 @@ class Access_Helper_Test extends Unit_Test_Case {
access::deny(identity::everybody(), "view", $item);
access::deny(identity::registered_users(), "view", $item);
+ $item->reload();
$user = identity::create_user("access_test", "Access Test", "");
foreach ($user->groups() as $group) {
diff --git a/modules/gallery/tests/Album_Helper_Test.php b/modules/gallery/tests/Album_Helper_Test.php
index 1284b8cc..ef0905da 100644
--- a/modules/gallery/tests/Album_Helper_Test.php
+++ b/modules/gallery/tests/Album_Helper_Test.php
@@ -38,7 +38,7 @@ class Album_Helper_Test extends Unit_Test_Case {
}
public function create_conflicting_album_test() {
- $rand = rand();
+ $rand = "name_" . rand();
$root = ORM::factory("item", 1);
$album1 = album::create($root, $rand, $rand, $rand);
$album2 = album::create($root, $rand, $rand, $rand);
diff --git a/modules/gallery/tests/Gallery_I18n_Test.php b/modules/gallery/tests/Gallery_I18n_Test.php
index 895e3051..5d2fd994 100644
--- a/modules/gallery/tests/Gallery_I18n_Test.php
+++ b/modules/gallery/tests/Gallery_I18n_Test.php
@@ -28,9 +28,10 @@ class Gallery_I18n_Test extends Unit_Test_Case {
'locale_dir' => VARPATH . 'locale/');
$this->i18n = Gallery_I18n::instance($config);
- ORM::factory("incoming_translation")
+ db::build()
+ ->delete("incoming_translations")
->where("locale", "=", "te_ST")
- ->delete_all();
+ ->execute();
$messages_te_ST = array(
array('Hello world', 'Hallo Welt'),
diff --git a/modules/gallery/tests/Gallery_Installer_Test.php b/modules/gallery/tests/Gallery_Installer_Test.php
index 43399fb4..74a07b1a 100644
--- a/modules/gallery/tests/Gallery_Installer_Test.php
+++ b/modules/gallery/tests/Gallery_Installer_Test.php
@@ -41,7 +41,7 @@ class Gallery_Installer_Test extends Unit_Test_Case {
$this->assert_equal("Gallery", $root->title);
$this->assert_equal(1, $root->left_ptr);
$this->assert_equal($max_right_ptr, $root->right_ptr);
- $this->assert_equal(null, $root->parent_id);
+ $this->assert_equal(0, $root->parent_id);
$this->assert_equal(1, $root->level);
}
}
diff --git a/modules/gallery/tests/Gallery_Rest_Helper_Test.php b/modules/gallery/tests/Gallery_Rest_Helper_Test.php
index cd0aabae..f8cf6190 100644
--- a/modules/gallery/tests/Gallery_Rest_Helper_Test.php
+++ b/modules/gallery/tests/Gallery_Rest_Helper_Test.php
@@ -94,8 +94,8 @@ class Gallery_Rest_Helper_Test extends Unit_Test_Case {
"path" => $photo->relative_url(),
"thumb_url" => $photo->thumb_url(),
"thumb_dimensions" => array(
- "width" => $photo->thumb_width,
- "height" => $photo->thumb_height),
+ "width" => (string)$photo->thumb_width,
+ "height" => (string)$photo->thumb_height),
"has_thumb" => true,
"title" => $photo->title))))),
gallery_rest::get($request));
@@ -115,14 +115,14 @@ class Gallery_Rest_Helper_Test extends Unit_Test_Case {
"parent_path" => $child->relative_url(),
"title" => $photo->title,
"thumb_url" => $photo->thumb_url(),
- "thumb_size" => array("height" => $photo->thumb_height,
- "width" => $photo->thumb_width),
+ "thumb_size" => array("height" => (string)$photo->thumb_height,
+ "width" => (string)$photo->thumb_width),
"resize_url" => $photo->resize_url(),
"resize_size" => array("height" => $photo->resize_height,
"width" => $photo->resize_width),
"url" => $photo->file_url(),
- "size" => array("height" => $photo->height,
- "width" => $photo->width),
+ "size" => array("height" => (string)$photo->height,
+ "width" => (string)$photo->width),
"description" => $photo->description,
"slug" => $photo->slug))),
gallery_rest::get($request));
diff --git a/modules/gallery/tests/Item_Model_Test.php b/modules/gallery/tests/Item_Model_Test.php
index bf5fca1a..d03a03f4 100644
--- a/modules/gallery/tests/Item_Model_Test.php
+++ b/modules/gallery/tests/Item_Model_Test.php
@@ -52,7 +52,7 @@ class Item_Model_Test extends Unit_Test_Case {
public function updating_view_count_only_doesnt_change_updated_date_test() {
$item = self::_create_random_item();
$item->reload();
- $this->assert_same(0, $item->view_count);
+ $this->assert_equal(0, $item->view_count);
// Force the updated date to something well known
db::build()
diff --git a/modules/gallery/tests/Url_Security_Test.php b/modules/gallery/tests/Url_Security_Test.php
new file mode 100644
index 00000000..de25880f
--- /dev/null
+++ b/modules/gallery/tests/Url_Security_Test.php
@@ -0,0 +1,43 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-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_Security_Test extends Unit_Test_Case {
+ public function setup() {
+ $this->save = array(Router::$current_uri, Router::$complete_uri, $_GET);
+ }
+
+ public function teardown() {
+ list(Router::$current_uri, Router::$complete_uri, $_GET) = $this->save;
+ }
+
+ public function xss_in_current_url_test() {
+ Router::$current_uri = "foo/<xss>/bar";
+ Router::$complete_uri = "foo/<xss>/bar?foo=bar";
+ $this->assert_same("foo/&lt;xss&gt;/bar", url::current());
+ $this->assert_same("foo/&lt;xss&gt;/bar?foo=bar", url::current(true));
+ }
+
+ public function xss_in_merged_url_test() {
+ Router::$current_uri = "foo/<xss>/bar";
+ Router::$complete_uri = "foo/<xss>/bar?foo=bar";
+ $_GET = array("foo" => "bar");
+ $this->assert_same("foo/&lt;xss&gt;/bar?foo=bar", url::merge(array()));
+ $this->assert_same("foo/&lt;xss&gt;/bar?foo=bar&amp;a=b", url::merge(array("a" => "b")));
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/tests/xss_data.txt b/modules/gallery/tests/xss_data.txt
index a264286c..1530c73e 100644
--- a/modules/gallery/tests/xss_data.txt
+++ b/modules/gallery/tests/xss_data.txt
@@ -137,7 +137,7 @@ modules/gallery/views/l10n_client.html.php 26 DIRTY $strin
modules/gallery/views/l10n_client.html.php 32 DIRTY $l10n_search_form
modules/gallery/views/l10n_client.html.php 41 DIRTY access::csrf_form_field()
modules/gallery/views/l10n_client.html.php 42 DIRTY form::hidden("l10n-message-key")
-modules/gallery/views/l10n_client.html.php 43 DIRTY form::textarea("l10n-edit-translation","",' rows="5" class="translationField"')
+modules/gallery/views/l10n_client.html.php 43 DIRTY form::textarea("l10n-edit-translation","",' id="l10n-edit-translation" rows="5" class="translationField"')
modules/gallery/views/l10n_client.html.php 46 DIRTY form::textarea("l10n-edit-plural-translation-zero","",' rows="2"')
modules/gallery/views/l10n_client.html.php 50 DIRTY form::textarea("l10n-edit-plural-translation-one","",' rows="2"')
modules/gallery/views/l10n_client.html.php 54 DIRTY form::textarea("l10n-edit-plural-translation-two","",' rows="2"')
diff --git a/modules/gallery/views/l10n_client.html.php b/modules/gallery/views/l10n_client.html.php
index 9d14bbb2..47a45e29 100644
--- a/modules/gallery/views/l10n_client.html.php
+++ b/modules/gallery/views/l10n_client.html.php
@@ -40,7 +40,7 @@
<form method="post" action="<?= url::site("l10n_client/save") ?>" id="g-l10n-client-save-form">
<?= access::csrf_form_field() ?>
<?= form::hidden("l10n-message-key") ?>
- <?= form::textarea("l10n-edit-translation", "", ' rows="5" class="translationField"') ?>
+ <?= form::textarea("l10n-edit-translation", "", ' id="l10n-edit-translation" rows="5" class="translationField"') ?>
<div id="plural-zero" class="translationField hidden">
<label for="l10n-edit-plural-translation-zero">[zero]</label>
<?= form::textarea("l10n-edit-plural-translation-zero", "", ' rows="2"') ?>
diff --git a/modules/notification/helpers/notification.php b/modules/notification/helpers/notification.php
index e9fc3f33..dfeab9fc 100644
--- a/modules/notification/helpers/notification.php
+++ b/modules/notification/helpers/notification.php
@@ -92,19 +92,22 @@ class notification {
return array_keys($subscribers);
}
- static function send_item_updated($item) {
+ static function send_item_updated($original, $item) {
$subscribers = self::get_subscribers($item);
if (!$subscribers) {
return;
}
$v = new View("item_updated.html");
+ $v->original = $original;
$v->item = $item;
$v->subject = $item->is_album() ?
- t("Album %title updated", array("title" => $item->original("title"))) :
+ t("Album \"%title\" updated", array("title" => $original->title)) :
($item->is_photo() ?
- t("Photo %title updated", array("title" => $item->original("title")))
- : t("Movie %title updated", array("title" => $item->original("title"))));
+ t("Photo \"%title\" updated", array("title" => $original->title))
+ : t("Movie \"%title\" updated", array("title" => $original->title)));
+
+ Kohana_Log::add("error",print_r($v->render(),1));
self::_notify($subscribers, $item, $v->render(), $v->subject);
}
@@ -119,12 +122,12 @@ class notification {
$v = new View("item_added.html");
$v->item = $item;
$v->subject = $item->is_album() ?
- t("Album %title added to %parent_title",
+ t("Album \"%title\" added to \"%parent_title\"",
array("title" => $item->title, "parent_title" => $parent->title)) :
($item->is_photo() ?
- t("Photo %title added to %parent_title",
+ t("Photo \"%title\" added to \"%parent_title\"",
array("title" => $item->title, "parent_title" => $parent->title)) :
- t("Movie %title added to %parent_title",
+ t("Movie \"%title\" added to \"%parent_title\"",
array("title" => $item->title, "parent_title" => $parent->title)));
self::_notify($subscribers, $item, $v->render(), $v->subject);
@@ -140,12 +143,12 @@ class notification {
$v = new View("item_deleted.html");
$v->item = $item;
$v->subject = $item->is_album() ?
- t("Album %title removed from %parent_title",
+ t("Album \"%title\" removed from \"%parent_title\"",
array("title" => $item->title, "parent_title" => $parent->title)) :
($item->is_photo() ?
- t("Photo %title removed from %parent_title",
+ t("Photo \"%title\" removed from \"%parent_title\"",
array("title" => $item->title, "parent_title" => $parent->title))
- : t("Movie %title removed from %parent_title",
+ : t("Movie \"%title\" removed from \"%parent_title\"",
array("title" => $item->title, "parent_title" => $parent->title)));
self::_notify($subscribers, $item, $v->render(), $v->subject);
@@ -161,10 +164,10 @@ class notification {
$v = new View("comment_published.html");
$v->comment = $comment;
$v->subject = $item->is_album() ?
- t("A new comment was published for album %title", array("title" => $item->title)) :
+ t("A new comment was published for album \"%title\"", array("title" => $item->title)) :
($item->is_photo() ?
- t("A new comment was published for photo %title", array("title" => $item->title))
- : t("A new comment was published for movie %title", array("title" => $item->title)));
+ t("A new comment was published for photo \"%title\"", array("title" => $item->title))
+ : t("A new comment was published for movie \"%title\"", array("title" => $item->title)));
self::_notify($subscribers, $item, $v->render(), $v->subject);
}
diff --git a/modules/notification/helpers/notification_event.php b/modules/notification/helpers/notification_event.php
index 2c7ede27..edbf6e39 100644
--- a/modules/notification/helpers/notification_event.php
+++ b/modules/notification/helpers/notification_event.php
@@ -23,7 +23,7 @@ class notification_event_Core {
// so we don't pass the exception up the call stack
static function item_updated($original, $new) {
try {
- notification::send_item_updated($new);
+ notification::send_item_updated($original, $new);
} catch (Exception $e) {
Kohana_Log::add("error", "@todo notification_event::item_updated() failed");
Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
@@ -89,9 +89,10 @@ class notification_event_Core {
static function user_before_delete($user) {
try {
- ORM::factory("subscription")
+ db::build()
+ ->delete("subscriptions")
->where("user_id", "=", $user->id)
- ->delete_all();
+ ->execute();
} catch (Exception $e) {
Kohana_Log::add("error", "@todo notification_event::user_before_delete() failed");
Kohana_Log::add("error", $e->getMessage() . "\n" . $e->getTraceAsString());
diff --git a/modules/notification/views/item_updated.html.php b/modules/notification/views/item_updated.html.php
index 47856cab..7020fd53 100644
--- a/modules/notification/views/item_updated.html.php
+++ b/modules/notification/views/item_updated.html.php
@@ -7,7 +7,7 @@
<h2> <?= html::clean($subject) ?> </h2>
<table>
<tr>
- <? if ($item->original("title") != $item->title): ?>
+ <? if ($original->title != $item->title): ?>
<td><?= t("New title:") ?></td>
<td><?= html::clean($item->title) ?></td>
<? else: ?>
@@ -19,7 +19,7 @@
<td><?= t("Url:") ?></td>
<td><a href="<?= $item->abs_url() ?>"><?= $item->abs_url() ?></a></td>
</tr>
- <? if ($item->original("description") != $item->description): ?>
+ <? if ($original->description != $item->description): ?>
<tr>
<td><?= t("New description:") ?></td>
<td><?= html::clean($item->description) ?></td>
diff --git a/modules/rest/helpers/rest_event.php b/modules/rest/helpers/rest_event.php
index 00cea7eb..860c8e41 100644
--- a/modules/rest/helpers/rest_event.php
+++ b/modules/rest/helpers/rest_event.php
@@ -23,9 +23,10 @@ class rest_event {
* the user_homes directory.
*/
static function user_before_delete($user) {
- ORM::factory("user_access_token")
+ db::build()
+ ->delete("user_access_tokens")
->where("id", "=", $user->id)
- ->delete_all();
+ ->execute();
}
/**
diff --git a/modules/rss/controllers/rss.php b/modules/rss/controllers/rss.php
index 41c781d9..3066ba16 100644
--- a/modules/rss/controllers/rss.php
+++ b/modules/rss/controllers/rss.php
@@ -52,14 +52,12 @@ class Rss_Controller extends Controller {
$view->feed = $feed;
$view->pub_date = date("D, d M Y H:i:s T");
- $feed->uri = url::abs_site(str_replace("&", "&amp;", url::merge($_GET)));
+ $feed->uri = url::abs_site(url::merge($_GET));
if ($page > 1) {
- $feed->previous_page_uri =
- url::abs_site(str_replace("&", "&amp;", url::merge(array("page" => $page - 1))));
+ $feed->previous_page_uri = url::abs_site(url::merge(array("page" => $page - 1)));
}
if ($page < $feed->max_pages) {
- $feed->next_page_uri =
- url::abs_site(str_replace("&", "&amp;", url::merge(array("page" => $page + 1))));
+ $feed->next_page_uri = url::abs_site(url::merge(array("page" => $page + 1)));
}
header("Content-Type: application/rss+xml");
diff --git a/modules/search/helpers/search_event.php b/modules/search/helpers/search_event.php
index 1add6e5f..97041f8c 100644
--- a/modules/search/helpers/search_event.php
+++ b/modules/search/helpers/search_event.php
@@ -27,9 +27,10 @@ class search_event_Core {
}
static function item_deleted($item) {
- ORM::factory("search_record")
+ db::build()
+ ->delete("search_records")
->where("item_id", "=", $item->id)
- ->delete_all();
+ ->execute();
}
static function item_related_update($item) {
diff --git a/modules/server_add/controllers/server_add.php b/modules/server_add/controllers/server_add.php
index f6e3a4dd..d7572b52 100644
--- a/modules/server_add/controllers/server_add.php
+++ b/modules/server_add/controllers/server_add.php
@@ -266,7 +266,10 @@ class Server_Add_Controller extends Admin_Controller {
$task->done = true;
$task->state = "success";
$task->percent_complete = 100;
- ORM::factory("server_add_file")->where("task_id", "=", $task->id)->delete_all();
+ db::build()
+ ->delete("server_add_files")
+ ->where("task_id", "=", $task->id)
+ ->execute();
message::info(t2("Successfully added one photo / album",
"Successfully added %count photos / albums",
$task->get("completed_files")));
diff --git a/modules/tag/tests/Tag_Rest_Helper_Test.php b/modules/tag/tests/Tag_Rest_Helper_Test.php
index 4e8dd527..514538d4 100644
--- a/modules/tag/tests/Tag_Rest_Helper_Test.php
+++ b/modules/tag/tests/Tag_Rest_Helper_Test.php
@@ -85,8 +85,8 @@ class Tag_Rest_Helper_Test extends Unit_Test_Case {
$this->assert_equal(
json_encode(array("status" => "OK",
- "tags" => array(array("name" => "albums", "count" => 2),
- array("name" => "photos", "count" => 2)))),
+ "tags" => array(array("name" => "albums", "count" => "2"),
+ array("name" => "photos", "count" => "2")))),
tag_rest::get($request));
}
diff --git a/modules/user/models/group.php b/modules/user/models/group.php
index 515788a3..10f6f4b3 100644
--- a/modules/user/models/group.php
+++ b/modules/user/models/group.php
@@ -41,11 +41,13 @@ class Group_Model extends ORM implements Group_Definition {
if (!$this->loaded()) {
$created = 1;
}
+
+ $original = clone $this->original();
parent::save();
if (isset($created)) {
module::event("group_created", $this);
} else {
- module::event("group_updated", $this->original(), $this);
+ module::event("group_updated", $original, $this);
}
return $this;
}
diff --git a/modules/user/models/user.php b/modules/user/models/user.php
index 7d5bf413..edba2a2c 100644
--- a/modules/user/models/user.php
+++ b/modules/user/models/user.php
@@ -69,11 +69,13 @@ class User_Model extends ORM implements User_Definition {
if (!$this->loaded()) {
$created = 1;
}
+
+ $original = clone $this->original();
parent::save();
if (isset($created)) {
module::event("user_created", $this);
} else {
- module::event("user_updated", $this->original(), $this);
+ module::event("user_updated", $original, $this);
}
return $this;
}
diff --git a/system/libraries/Database_Builder.php b/system/libraries/Database_Builder.php
index 4e6951e7..62b2a163 100644
--- a/system/libraries/Database_Builder.php
+++ b/system/libraries/Database_Builder.php
@@ -1,6 +1,16 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
- * Database builder
+ * The Database Query Builder provides methods for creating database agnostic queries and
+ * data manipulation.
+ *
+ * ##### A basic select query
+ *
+ * $builder = new Database_Builder;
+ * $kohana = $builder
+ * ->select()
+ * ->where('name', '=', 'Kohana')
+ * ->from('frameworks')
+ * ->execute();
*
* @package Kohana
* @author Kohana Team
@@ -30,6 +40,7 @@ class Database_Builder_Core {
protected $values = array();
protected $type;
protected $distinct = FALSE;
+ protected $reset = TRUE;
// TTL for caching (using Cache library)
protected $ttl = FALSE;
@@ -40,273 +51,195 @@ class Database_Builder_Core {
}
/**
- * Resets all query components
+ * Compiles the builder object into a SQL query. Useful for debugging
+ *
+ * ##### Example
+ *
+ * echo $builder->select()->from('products');
+ * // Output: SELECT * FROM `products`
+ *
+ * @return string Compiled query
*/
- public function reset()
- {
- $this->select = array();
- $this->from = array();
- $this->join = array();
- $this->where = array();
- $this->group_by = array();
- $this->having = array();
- $this->order_by = array();
- $this->limit = NULL;
- $this->offset = NULL;
- $this->set = array();
- $this->values = array();
- }
-
public function __toString()
{
return $this->compile();
}
/**
- * Compiles the builder object into a SQL query
+ * Creates a `SELECT` query with support for column aliases, database functions,
+ * subqueries or a [Database_Expression]
*
- * @return string Compiled query
+ * ##### Examples
+ *
+ * // Simple select
+ * echo $builder->select()->from('products');
+ *
+ * // Select with database function
+ * echo $builder->select(array('records_found' => 'COUNT("*")'))->from('products');
+ *
+ * // Select with sub query
+ * echo $builder->select(array('field', 'test' => db::select('test')->from('table')))->from('products');
+ *
+ * @chainable
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
*/
- protected function compile()
+ public function select($columns = NULL)
{
- if ( ! is_object($this->db))
- {
- // Use default database for compiling to string if none is given
- $this->db = Database::instance($this->db);
- }
-
- if ($this->type === Database::SELECT)
- {
- // SELECT columns FROM table
- $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
- $sql .= $this->compile_select();
-
- if ( ! empty($this->from))
- {
- $sql .= "\nFROM ".$this->compile_from();
- }
- }
- elseif ($this->type === Database::UPDATE)
- {
- $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
- }
- elseif ($this->type === Database::INSERT)
- {
- $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
- }
- elseif ($this->type === Database::DELETE)
- {
- $sql = 'DELETE FROM '.$this->compile_from();
- }
-
- if ( ! empty($this->join))
- {
- $sql .= $this->compile_join();
- }
-
- if ( ! empty($this->where))
- {
- $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
- }
-
- if ( ! empty($this->having))
- {
- $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
- }
-
- if ( ! empty($this->group_by))
- {
- $sql .= "\n".'GROUP BY '.$this->compile_group_by();
- }
+ $this->type = Database::SELECT;
- if ( ! empty($this->order_by))
+ if ($columns === NULL)
{
- $sql .= "\nORDER BY ".$this->compile_order_by();
+ $columns = array('*');
}
-
- if (is_int($this->limit))
+ elseif ( ! is_array($columns))
{
- $sql .= "\nLIMIT ".$this->limit;
+ $columns = func_get_args();
}
- if (is_int($this->offset))
- {
- $sql .= "\nOFFSET ".$this->offset;
- }
+ $this->select = array_merge($this->select, $columns);
- return $sql;
+ return $this;
}
/**
- * Compiles the SELECT portion of the query
+ * Creates a `DISTINCT SELECT` query. For more information see see [Database_Builder::select].
*
- * @return string
+ * @chainable
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
*/
- protected function compile_select()
+ public function select_distinct($columns = NULL)
{
- $vals = array();
-
- foreach ($this->select as $alias => $name)
- {
- if ($name instanceof Database_Builder)
- {
- // Using a subquery
- $name->db = $this->db;
- $vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias);
- }
- elseif (is_string($alias))
- {
- $vals[] = $this->db->quote_column($name, $alias);
- }
- else
- {
- $vals[] = $this->db->quote_column($name);
- }
- }
-
- return implode(', ', $vals);
+ $this->select($columns);
+ $this->distinct = TRUE;
+ return $this;
}
/**
- * Compiles the FROM portion of the query
+ * Add tables to the FROM portion of the builder
*
- * @return string
+ * ##### Example
+ *
+ * $builder->select()->from('products')
+ * ->from(array('other' => 'other_table'));
+ * // Output: SELECT * FROM `products`, `other_table` AS `other`
+ *
+ * @chainable
+ * @param string|array table name or array(alias => table)
+ * @return Database_Builder
*/
- protected function compile_from()
+ public function from($tables)
{
- $vals = array();
-
- foreach ($this->from as $alias => $name)
+ if ( ! is_array($tables))
{
- if (is_string($alias))
- {
- // Using AS format so escape both
- $vals[] = $this->db->quote_table($name, $alias);
- }
- else
- {
- // Just using the table name itself
- $vals[] = $this->db->quote_table($name);
- }
+ $tables = func_get_args();
}
- return implode(', ', $vals);
+ $this->from = array_merge($this->from, $tables);
+
+ return $this;
}
/**
- * Compiles the JOIN portion of the query
+ * Add conditions to the `WHERE` clause. Alias for [Database_Builder::and_where].
*
- * @return string
+ * @chainable
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
*/
- protected function compile_join()
+ public function where($columns, $op = '=', $value = NULL)
{
- $sql = '';
- foreach ($this->join as $join)
- {
- list($table, $keys, $type) = $join;
-
- if ($type !== NULL)
- {
- // Join type
- $sql .= ' '.$type;
- }
-
- $sql .= ' JOIN '.$this->db->quote_table($table);
-
- $condition = '';
- if ($keys instanceof Database_Expression)
- {
- $condition = (string) $keys;
- }
- elseif (is_array($keys))
- {
- // ON condition is an array of matches
- foreach ($keys as $key => $value)
- {
- if ( ! empty($condition))
- {
- $condition .= ' AND ';
- }
-
- $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
- }
- }
-
- if ( ! empty($condition))
- {
- // Add ON condition
- $sql .= ' ON ('.$condition.')';
- }
- }
-
- return $sql;
+ return $this->and_where($columns, $op, $value);
}
/**
- * Compiles the GROUP BY portion of the query
+ * Add conditions to the `WHERE` clause separating multiple conditions with `AND`.
+ * This function supports all `WHERE` operators including `LIKE` and `IN`. It can
+ * also be used with a [Database_Expression] or subquery.
*
- * @return string
+ * ##### Examples
+ *
+ * // Basic where condition
+ * $builder->where('field', '=', 'value');
+ *
+ * // Multiple conditions with an array (you can also chain where() function calls)
+ * $builder->where(array(array('field', '=', 'value'), array(...)));
+ *
+ * // With a database expression
+ * $builder->where('field', '=', db::expr('field + 1'));
+ * // or a function
+ * $builder->where('field', '=', db::expr('UNIX_TIMESTAMP()'));
+ *
+ * // With a subquery
+ * $builder->where('field', 'IN', db::select('id')->from('table'));
+ *
+ * [!!] You must manually escape all data you pass into a database expression!
+ *
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
*/
- protected function compile_group_by()
+ public function and_where($columns, $op = '=', $value = NULL)
{
- $vals = array();
-
- foreach ($this->group_by as $column)
+ if (is_array($columns))
{
- // Escape the column
- $vals[] = $this->db->quote_column($column);
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('AND' => $column);
+ }
}
-
- return implode(', ', $vals);
+ else
+ {
+ $this->where[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
}
/**
- * Compiles the ORDER BY portion of the query
+ * Add conditions to the `WHERE` clause separating multiple conditions with `OR`.
+ * For more information about building a `WHERE` clause see [Database_Builder::and_where]
*
- * @return string
+ * @chainable
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
*/
- public function compile_order_by()
+ public function or_where($columns, $op = '=', $value = NULL)
{
- $ordering = array();
-
- foreach ($this->order_by as $column => $order_by)
+ if (is_array($columns))
{
- list($column, $direction) = each($order_by);
-
- $column = $this->db->quote_column($column);
-
- if ($direction !== NULL)
+ foreach ($columns as $column)
{
- $direction = ' '.$direction;
+ $this->where[] = array('OR' => $column);
}
-
- $ordering[] = $column.$direction;
}
-
- return implode(', ', $ordering);
- }
-
- /**
- * Compiles the SET portion of the query for UPDATE
- *
- * @return string
- */
- public function compile_set()
- {
- $vals = array();
-
- foreach ($this->set as $key => $value)
+ else
{
- // Using an UPDATE so Key = Val
- $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
+ $this->where[] = array('OR' => array($columns, $op, $value));
}
-
- return implode(', ', $vals);
+ return $this;
}
/**
* Join tables to the builder
*
+ * ##### Example
+ *
+ * // Basic join
+ * db::select()->from('products')
+ * ->join('reviews', 'reviews.product_id', 'products.id');
+ *
+ * // Advanced joins
+ * echo db::select()->from('products')
+ * ->join('reviews', 'field', db::expr('advanced condition here'), 'RIGHT');
+ *
+ * @chainable
* @param mixed Table name
* @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
* @param mixed Value if $keys is not an array or Database_Expression
@@ -331,26 +264,120 @@ class Database_Builder_Core {
}
/**
- * Add tables to the FROM portion of the builder
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `LEFT`.
*
- * @param string|array table name or array(alias => table)
- * @return Database_Builder
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
*/
- public function from($tables)
+ public function left_join($table, $keys, $value = NULL)
{
- if ( ! is_array($tables))
- {
- $tables = func_get_args();
- }
+ return $this->join($table, $keys, $value, 'LEFT');
+ }
- $this->from = array_merge($this->from, $tables);
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `RIGHT`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function right_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT');
+ }
- return $this;
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'INNER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `OUTER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function outer_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'OUTER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `FULL`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function full_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'FULL');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `LEFT INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function left_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT INNER');
+ }
+
+ /**
+ * This function is an alias for [Database_Builder::join]
+ * with the join type set to `RIGHT INNER`.
+ *
+ * @chainable
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @return Database_Builder
+ */
+ public function right_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT INNER');
}
/**
* Add fields to the GROUP BY portion
*
+ * ##### Example
+ *
+ * db::select()->from('products')
+ * ->group_by(array('name', 'cat_id'));
+ * // Output: SELECT * FROM `products` GROUP BY `name`, `cat_id`
+ *
+ * @chainable
* @param mixed Field names or an array of fields
* @return Database_Builder
*/
@@ -369,6 +396,7 @@ class Database_Builder_Core {
/**
* Add conditions to the HAVING clause (AND)
*
+ * @chainable
* @param mixed Column name or array of columns => vals
* @param string Operation to perform
* @param mixed Value
@@ -382,6 +410,7 @@ class Database_Builder_Core {
/**
* Add conditions to the HAVING clause (AND)
*
+ * @chainable
* @param mixed Column name or array of triplets
* @param string Operation to perform
* @param mixed Value
@@ -406,6 +435,7 @@ class Database_Builder_Core {
/**
* Add conditions to the HAVING clause (OR)
*
+ * @chainable
* @param mixed Column name or array of triplets
* @param string Operation to perform
* @param mixed Value
@@ -430,6 +460,7 @@ class Database_Builder_Core {
/**
* Add fields to the ORDER BY portion
*
+ * @chainable
* @param mixed Field names or an array of fields (field => direction)
* @param string Direction or NULL for ascending
* @return Database_Builder
@@ -461,6 +492,7 @@ class Database_Builder_Core {
/**
* Limit rows returned
*
+ * @chainable
* @param int Number of rows
* @return Database_Builder
*/
@@ -474,6 +506,7 @@ class Database_Builder_Core {
/**
* Offset into result set
*
+ * @chainable
* @param int Offset
* @return Database_Builder
*/
@@ -484,46 +517,25 @@ class Database_Builder_Core {
return $this;
}
- public function left_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'LEFT');
- }
-
- public function right_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'RIGHT');
- }
-
- public function inner_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'INNER');
- }
-
- public function outer_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'OUTER');
- }
-
- public function full_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'FULL');
- }
-
- public function left_inner_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'LEFT INNER');
- }
-
- public function right_inner_join($table, $keys, $value = NULL)
- {
- return $this->join($table, $keys, $value, 'RIGHT INNER');
- }
-
+ /**
+ * Alias for [Database_Builder::and_open]
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
public function open($clause = 'WHERE')
{
return $this->and_open($clause);
}
+ /**
+ * Open new **ANDs** parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
public function and_open($clause = 'WHERE')
{
if ($clause === 'WHERE')
@@ -538,6 +550,13 @@ class Database_Builder_Core {
return $this;
}
+ /**
+ * Open new **OR** parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
public function or_open($clause = 'WHERE')
{
if ($clause === 'WHERE')
@@ -552,6 +571,13 @@ class Database_Builder_Core {
return $this;
}
+ /**
+ * Close close parenthesis set
+ *
+ * @chainable
+ * @param string Clause (WHERE OR HAVING)
+ * @return Database_Builder
+ */
public function close($clause = 'WHERE')
{
if ($clause === 'WHERE')
@@ -567,63 +593,102 @@ class Database_Builder_Core {
}
/**
- * Add conditions to the WHERE clause (AND)
+ * Set values for UPDATE
*
+ * @chainable
* @param mixed Column name or array of columns => vals
- * @param string Operation to perform
- * @param mixed Value
+ * @param mixed Value (can be a Database_Expression)
* @return Database_Builder
*/
- public function where($columns, $op = '=', $value = NULL)
+ public function set($keys, $value = NULL)
{
- return $this->and_where($columns, $op, $value);
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ $this->set = array_merge($keys, $this->set);
+
+ return $this;
}
/**
- * Add conditions to the WHERE clause (AND)
+ * Columns used for INSERT queries
*
- * @param mixed Column name or array of triplets
- * @param string Operation to perform
- * @param mixed Value
+ * @chainable
+ * @param array Columns
* @return Database_Builder
*/
- public function and_where($columns, $op = '=', $value = NULL)
+ public function columns($columns)
{
- if (is_array($columns))
- {
- foreach ($columns as $column)
- {
- $this->where[] = array('AND' => $column);
- }
- }
- else
+ if ( ! is_array($columns))
{
- $this->where[] = array('AND' => array($columns, $op, $value));
+ $columns = func_get_args();
}
+
+ $this->columns = $columns;
+
return $this;
}
/**
- * Add conditions to the WHERE clause (OR)
+ * Values used for INSERT queries
*
- * @param mixed Column name or array of triplets
- * @param string Operation to perform
- * @param mixed Value
+ * @chainable
+ * @param array Values
* @return Database_Builder
*/
- public function or_where($columns, $op = '=', $value = NULL)
+ public function values($values)
{
- if (is_array($columns))
- {
- foreach ($columns as $column)
- {
- $this->where[] = array('OR' => $column);
- }
- }
- else
+ if ( ! is_array($values))
{
- $this->where[] = array('OR' => array($columns, $op, $value));
+ $values = func_get_args();
}
+
+ $this->values[] = $values;
+
+ return $this;
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @chainable
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Builder
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+ /**
+ * Resets the database builder after execution. By default after you `execute()` a query
+ * the database builder will reset to its default state. You can use `reset(FALSE)`
+ * to stop this from happening. This is useful for pagination when you might want to
+ * apply a limit to the previous query.
+ *
+ * ##### Example
+ *
+ * $db = new Database_Builder;
+ * $all_results = $db->select()
+ * ->where('id', '=', 3)
+ * ->from('products')
+ * ->reset(FALSE)
+ * ->execute();
+ *
+ * // Run the query again with a limit of 10
+ * $ten_results = $db->limit(10)
+ * ->execute();
+ * @chainable
+ * @param bool reset builder
+ * @return Database_Builder
+ */
+ public function reset($reset = TRUE)
+ {
+ $this->reset = (bool) $reset;
return $this;
}
@@ -734,43 +799,6 @@ class Database_Builder_Core {
}
/**
- * Set values for UPDATE
- *
- * @param mixed Column name or array of columns => vals
- * @param mixed Value (can be a Database_Expression)
- * @return Database_Builder
- */
- public function set($keys, $value = NULL)
- {
- if (is_string($keys))
- {
- $keys = array($keys => $value);
- }
-
- $this->set = array_merge($keys, $this->set);
-
- return $this;
- }
-
- /**
- * Columns used for INSERT queries
- *
- * @param array Columns
- * @return Database_Builder
- */
- public function columns($columns)
- {
- if ( ! is_array($columns))
- {
- $columns = func_get_args();
- }
-
- $this->columns = $columns;
-
- return $this;
- }
-
- /**
* Compiles the columns portion of the query for INSERT
*
* @return string
@@ -781,24 +809,6 @@ class Database_Builder_Core {
}
/**
- * Values used for INSERT queries
- *
- * @param array Values
- * @return Database_Builder
- */
- public function values($values)
- {
- if ( ! is_array($values))
- {
- $values = func_get_args();
- }
-
- $this->values[] = $values;
-
- return $this;
- }
-
- /**
* Compiles the VALUES portion of the query for INSERT
*
* @return string
@@ -816,45 +826,9 @@ class Database_Builder_Core {
}
/**
- * Create a SELECT query and specify selected columns
- *
- * @param string|array column name or array(alias => column)
- * @return Database_Builder
- */
- public function select($columns = NULL)
- {
- $this->type = Database::SELECT;
-
- if ($columns === NULL)
- {
- $columns = array('*');
- }
- elseif ( ! is_array($columns))
- {
- $columns = func_get_args();
- }
-
- $this->select = array_merge($this->select, $columns);
-
- return $this;
- }
-
- /**
- * Create a SELECT query and specify selected columns
- *
- * @param string|array column name or array(alias => column)
- * @return Database_Builder
- */
- public function select_distinct($columns = NULL)
- {
- $this->select($columns);
- $this->distinct = TRUE;
- return $this;
- }
-
- /**
* Create an UPDATE query
*
+ * @chainable
* @param string Table name
* @param array Array of Keys => Values
* @param array WHERE conditions
@@ -885,6 +859,7 @@ class Database_Builder_Core {
/**
* Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts
*
+ * @chainable
* @param string Table name
* @param array Array of Keys => Values
* @return Database_Builder
@@ -910,6 +885,7 @@ class Database_Builder_Core {
/**
* Create a DELETE query
*
+ * @chainable
* @param string Table name
* @param array WHERE conditions
* @return Database_Builder
@@ -980,8 +956,11 @@ class Database_Builder_Core {
$query = $this->compile();
- // Reset the query after executing
- $this->reset();
+ if ($this->reset)
+ {
+ // Reset the query after executing
+ $this->_reset();
+ }
if ($this->ttl !== FALSE AND $this->type === Database::SELECT)
{
@@ -996,16 +975,267 @@ class Database_Builder_Core {
}
/**
- * Set caching for the query
+ * Compiles the builder object into a SQL query
*
- * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
- * @return Database_Builder
+ * @return string Compiled query
*/
- public function cache($ttl = NULL)
+ protected function compile()
{
- $this->ttl = $ttl;
+ if ( ! is_object($this->db))
+ {
+ // Use default database for compiling to string if none is given
+ $this->db = Database::instance($this->db);
+ }
- return $this;
+ if ($this->type === Database::SELECT)
+ {
+ // SELECT columns FROM table
+ $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
+ $sql .= $this->compile_select();
+
+ if ( ! empty($this->from))
+ {
+ $sql .= "\nFROM ".$this->compile_from();
+ }
+ }
+ elseif ($this->type === Database::UPDATE)
+ {
+ $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
+ }
+ elseif ($this->type === Database::INSERT)
+ {
+ $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
+ }
+ elseif ($this->type === Database::DELETE)
+ {
+ $sql = 'DELETE FROM '.$this->compile_from();
+ }
+
+ if ( ! empty($this->join))
+ {
+ $sql .= $this->compile_join();
+ }
+
+ if ( ! empty($this->where))
+ {
+ $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
+ }
+
+ if ( ! empty($this->having))
+ {
+ $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
+ }
+
+ if ( ! empty($this->group_by))
+ {
+ $sql .= "\n".'GROUP BY '.$this->compile_group_by();
+ }
+
+ if ( ! empty($this->order_by))
+ {
+ $sql .= "\nORDER BY ".$this->compile_order_by();
+ }
+
+ if (is_int($this->limit))
+ {
+ $sql .= "\nLIMIT ".$this->limit;
+ }
+
+ if (is_int($this->offset))
+ {
+ $sql .= "\nOFFSET ".$this->offset;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the SELECT portion of the query
+ *
+ * @return string
+ */
+ protected function compile_select()
+ {
+ $vals = array();
+
+ foreach ($this->select as $alias => $name)
+ {
+ if ($name instanceof Database_Builder)
+ {
+ // Using a subquery
+ $name->db = $this->db;
+ $vals[] = '('.(string) $name.') AS '.$this->db->quote_column($alias);
+ }
+ elseif (is_string($alias))
+ {
+ $vals[] = $this->db->quote_column($name, $alias);
+ }
+ else
+ {
+ $vals[] = $this->db->quote_column($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the FROM portion of the query
+ *
+ * @return string
+ */
+ protected function compile_from()
+ {
+ $vals = array();
+
+ foreach ($this->from as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ // Using AS format so escape both
+ $vals[] = $this->db->quote_table($name, $alias);
+ }
+ else
+ {
+ // Just using the table name itself
+ $vals[] = $this->db->quote_table($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the JOIN portion of the query
+ *
+ * @return string
+ */
+ protected function compile_join()
+ {
+ $sql = '';
+ foreach ($this->join as $join)
+ {
+ list($table, $keys, $type) = $join;
+
+ if ($type !== NULL)
+ {
+ // Join type
+ $sql .= ' '.$type;
+ }
+
+ $sql .= ' JOIN '.$this->db->quote_table($table);
+
+ $condition = '';
+ if ($keys instanceof Database_Expression)
+ {
+ $condition = (string) $keys;
+ }
+ elseif (is_array($keys))
+ {
+ // ON condition is an array of matches
+ foreach ($keys as $key => $value)
+ {
+ if ( ! empty($condition))
+ {
+ $condition .= ' AND ';
+ }
+
+ $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
+ }
+ }
+
+ if ( ! empty($condition))
+ {
+ // Add ON condition
+ $sql .= ' ON ('.$condition.')';
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the GROUP BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_group_by()
+ {
+ $vals = array();
+
+ foreach ($this->group_by as $column)
+ {
+ // Escape the column
+ $vals[] = $this->db->quote_column($column);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the ORDER BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_order_by()
+ {
+ $ordering = array();
+
+ foreach ($this->order_by as $column => $order_by)
+ {
+ list($column, $direction) = each($order_by);
+
+ $column = $this->db->quote_column($column);
+
+ if ($direction !== NULL)
+ {
+ $direction = ' '.$direction;
+ }
+
+ $ordering[] = $column.$direction;
+ }
+
+ return implode(', ', $ordering);
+ }
+
+ /**
+ * Compiles the SET portion of the query for UPDATE
+ *
+ * @return string
+ */
+ protected function compile_set()
+ {
+ $vals = array();
+
+ foreach ($this->set as $key => $value)
+ {
+ // Using an UPDATE so Key = Val
+ $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Resets all query components
+ */
+ protected function _reset()
+ {
+ $this->select = array();
+ $this->from = array();
+ $this->join = array();
+ $this->where = array();
+ $this->group_by = array();
+ $this->having = array();
+ $this->order_by = array();
+ $this->limit = NULL;
+ $this->offset = NULL;
+ $this->set = array();
+ $this->values = array();
+ $this->type = NULL;
+ $this->distinct = FALSE;
+ $this->reset = TRUE;
+ $this->ttl = FALSE;
}
} // End Database_Builder
diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php
index 60439106..4dd2eaf0 100644
--- a/system/libraries/ORM.php
+++ b/system/libraries/ORM.php
@@ -443,11 +443,11 @@ class ORM_Core {
// Data has changed
$this->changed[$column] = $column;
- // Object is no longer saved
- $this->_saved = FALSE;
+ // Object is no longer saved or valid
+ $this->_saved = $this->_valid = FALSE;
}
- $this->object[$column] = $this->load_type($column, $value);
+ $this->object[$column] = $value;
}
elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
{
@@ -904,6 +904,9 @@ class ORM_Core {
if ($this->saved() === TRUE)
{
+ // Always force revalidation after saving
+ $this->_valid = FALSE;
+
// Clear the per-request database cache
$this->db->clear_cache(NULL, Database::PER_REQUEST);
}
@@ -1374,12 +1377,6 @@ class ORM_Core {
{
if ( ! $ignore_changed OR ! isset($this->changed[$column]))
{
- if (isset($this->table_columns[$column]))
- {
- // The type of the value can be determined, convert the value
- $value = $this->load_type($column, $value);
- }
-
$this->object[$column] = $value;
}
}
@@ -1403,64 +1400,6 @@ class ORM_Core {
return $this;
}
-
- /**
- * Loads a value according to the types defined by the column metadata.
- *
- * @param string column name
- * @param mixed value to load
- * @return mixed
- */
- protected function load_type($column, $value)
- {
- $type = gettype($value);
- if ($type == 'object' OR $type == 'array' OR ! isset($this->table_columns[$column]))
- return $value;
-
- // Load column data
- $column = $this->table_columns[$column];
-
- if ($value === NULL AND ! empty($column['nullable']))
- return $value;
-
- if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
- {
- // Use boolean for BINARY(1) fields
- $column['type'] = 'boolean';
- }
-
- switch ($column['type'])
- {
- case 'int':
- if ($value === '' AND ! empty($column['nullable']))
- {
- // Forms will only submit strings, so empty integer values must be null
- $value = NULL;
- }
- elseif ((float) $value > PHP_INT_MAX)
- {
- // This number cannot be represented by a PHP integer, so we convert it to a string
- $value = (string) $value;
- }
- else
- {
- $value = (int) $value;
- }
- break;
- case 'float':
- $value = (float) $value;
- break;
- case 'boolean':
- $value = (bool) $value;
- break;
- case 'string':
- $value = (string) $value;
- break;
- }
-
- return $value;
- }
-
/**
* Loads a database result, either as a new object for this model, or as
* an iterator for multiple rows.