diff --git a/auth-schema-mysql.sql b/auth-schema-mysql.sql index 74394ff..f19ffdc 100644 --- a/auth-schema-mysql.sql +++ b/auth-schema-mysql.sql @@ -38,7 +38,8 @@ CREATE TABLE IF NOT EXISTS `user_tokens` ( `expires` int(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_token` (`token`), - KEY `fk_user_id` (`user_id`) + KEY `fk_user_id` (`user_id`), + KEY `expires` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `roles_users` diff --git a/classes/kohana/auth/orm.php b/classes/kohana/auth/orm.php index 4ba2637..73eb8c0 100644 --- a/classes/kohana/auth/orm.php +++ b/classes/kohana/auth/orm.php @@ -4,7 +4,7 @@ * * @package Kohana/Auth * @author Kohana Team - * @copyright (c) 2007-2011 Kohana Team + * @copyright (c) 2007-2012 Kohana Team * @license http://kohanaframework.org/license */ class Kohana_Auth_ORM extends Auth { @@ -60,9 +60,9 @@ public function logged_in($role = NULL) /** * Logs a user in. * - * @param string username - * @param string password - * @param boolean enable autologin + * @param string $username + * @param string $password + * @param boolean $remember enable autologin * @return boolean */ protected function _login($user, $password, $remember) @@ -89,7 +89,7 @@ protected function _login($user, $password, $remember) { // Token data $data = array( - 'user_id' => $user->id, + 'user_id' => $user->pk(), 'expires' => time() + $this->_config['lifetime'], 'user_agent' => sha1(Request::$user_agent), ); @@ -116,8 +116,8 @@ protected function _login($user, $password, $remember) /** * Forces a user to be logged in, without specifying a password. * - * @param mixed username string, or user ORM object - * @param boolean mark the session as forced + * @param mixed $user username string, or user ORM object + * @param boolean $mark_session_as_forced mark the session as forced * @return boolean */ public function force_login($user, $mark_session_as_forced = FALSE) @@ -180,18 +180,20 @@ public function auto_login() /** * Gets the currently logged in user from the session (with auto_login check). - * Returns FALSE if no user is currently logged in. + * Returns $default if no user is currently logged in. * + * @param mixed $default to return in case user isn't logged in * @return mixed */ public function get_user($default = NULL) { $user = parent::get_user($default); - if ( ! $user) + if ($user === $default) { // check for "remembered" login - $user = $this->auto_login(); + if (($user = $this->auto_login()) === FALSE) + return $default; } return $user; @@ -200,8 +202,8 @@ public function get_user($default = NULL) /** * Log a user out and remove any autologin cookies. * - * @param boolean completely destroy the session - * @param boolean remove all tokens for user + * @param boolean $destroy completely destroy the session + * @param boolean $logout_all remove all tokens for user * @return boolean */ public function logout($destroy = FALSE, $logout_all = FALSE) @@ -219,7 +221,13 @@ public function logout($destroy = FALSE, $logout_all = FALSE) if ($token->loaded() AND $logout_all) { - ORM::factory('user_token')->where('user_id', '=', $token->user_id)->delete_all(); + // Delete all user tokens. This isn't the most elegant solution but does the job + $tokens = ORM::factory('user_token')->where('user_id','=',$token->user_id)->find_all(); + + foreach ($tokens as $_token) + { + $_token->delete(); + } } elseif ($token->loaded()) { @@ -233,7 +241,7 @@ public function logout($destroy = FALSE, $logout_all = FALSE) /** * Get the stored password for a username. * - * @param mixed username string, or user ORM object + * @param mixed $user username string, or user ORM object * @return string */ public function password($user) @@ -254,7 +262,7 @@ public function password($user) * Complete the login for a user by incrementing the logins and setting * session data: user_id, username, roles. * - * @param object user ORM object + * @param object $user user ORM object * @return void */ protected function complete_login($user) diff --git a/classes/kohana/orm.php b/classes/kohana/orm.php index 048d351..299ba93 100644 --- a/classes/kohana/orm.php +++ b/classes/kohana/orm.php @@ -10,7 +10,7 @@ * * @package Kohana/ORM * @author Kohana Team - * @copyright (c) 2007-2010 Kohana Team + * @copyright (c) 2007-2012 Kohana Team * @license http://kohanaframework.org/license */ class Kohana_ORM extends Model implements serializable { @@ -389,7 +389,7 @@ public function reload_columns($force = FALSE) else { // Grab column information from database - $this->_table_columns = $this->list_columns(TRUE); + $this->_table_columns = $this->list_columns(); // Load column cache ORM::$_column_cache[$this->_object_name] = $this->_table_columns; @@ -418,6 +418,9 @@ public function clear() // Reset primary key $this->_primary_key_value = NULL; + + // Reset the loaded state + $this->_loaded = FALSE; $this->reset(); @@ -486,7 +489,7 @@ public function __toString() * Allows serialization of only the object data and state, to prevent * "stale" objects being unserialized, which also requires less memory. * - * @return array + * @return string */ public function serialize() { @@ -503,7 +506,7 @@ public function serialize() * Check whether the model data has been modified. * If $field is specified, checks whether that field was modified. * - * @param string field to check for changes + * @param string $field field to check for changes * @return bool Whether or not the field has changed */ public function changed($field = NULL) @@ -563,7 +566,13 @@ public function __get($column) $col = $model->_object_name.'.'.$model->_primary_key; $val = $this->_object[$this->_belongs_to[$column]['foreign_key']]; - $model->where($col, '=', $val)->find(); + // Make sure we don't run WHERE "AUTO_INCREMENT column" = NULL queries. This would + // return the last inserted record instead of an empty result. + // See: http://mysql.localhost.net.ar/doc/refman/5.1/en/server-session-variables.html#sysvar_sql_auto_is_null + if ($val !== NULL) + { + $model->where($col, '=', $val)->find(); + } return $this->_related[$column] = $model; } @@ -672,7 +681,9 @@ public function set($column, $value) $this->_related[$column] = $value; // Update the foreign key of this model - $this->_object[$this->_belongs_to[$column]['foreign_key']] = $value->pk(); + $this->_object[$this->_belongs_to[$column]['foreign_key']] = ($value instanceof ORM) + ? $value->pk() + : NULL; $this->_changed[$column] = $this->_belongs_to[$column]['foreign_key']; } @@ -856,7 +867,8 @@ protected function _build($type) $this->_db_builder = DB::update(array($this->_table_name, $this->_object_name)); break; case Database::DELETE: - $this->_db_builder = DB::delete(array($this->_table_name, $this->_object_name)); + // Cannot use an alias for DELETE queries + $this->_db_builder = DB::delete($this->_table_name); } // Process pending database method calls @@ -922,6 +934,24 @@ public function find_all() return $this->_load_result(TRUE); } + /** + * Returns an array of columns to include in the select query. This method + * can be overridden to change the default select behavior. + * + * @return array Columns to select + */ + protected function _build_select() + { + $columns = array(); + + foreach ($this->_table_columns as $column => $_) + { + $columns[] = array($this->_object_name.'.'.$column, $column); + } + + return $columns; + } + /** * Loads a database result, either as a new record for this model, or as * an iterator for multiple rows. @@ -941,7 +971,7 @@ protected function _load_result($multiple = FALSE) } // Select all columns by default - $this->_db_builder->select($this->_object_name.'.*'); + $this->_db_builder->select_array($this->_build_select()); if ( ! isset($this->_db_applied['order_by']) AND ! empty($this->_sorting)) { @@ -1195,7 +1225,7 @@ public function create(Validation $validation = NULL) throw new Kohana_Exception('Cannot create :model model because it is already loaded.', array(':model' => $this->_object_name)); // Require model validation before saving - if ( ! $this->_valid) + if ( ! $this->_valid OR $validation) { $this->check($validation); } @@ -1226,6 +1256,10 @@ public function create(Validation $validation = NULL) // Load the insert id as the primary key if it was left out $this->_object[$this->_primary_key] = $this->_primary_key_value = $result[0]; } + else + { + $this->_primary_key_value = $this->_object[$this->_primary_key]; + } // Object is now loaded and saved $this->_loaded = $this->_saved = TRUE; @@ -1249,16 +1283,16 @@ public function update(Validation $validation = NULL) if ( ! $this->_loaded) throw new Kohana_Exception('Cannot update :model model because it is not loaded.', array(':model' => $this->_object_name)); - if (empty($this->_changed)) + // Run validation if the model isn't valid or we have additional validation rules. + if ( ! $this->_valid OR $validation) { - // Nothing to update - return $this; + $this->check($validation); } - // Require model validation before saving - if ( ! $this->_valid) + if (empty($this->_changed)) { - $this->check($validation); + // Nothing to update + return $this; } $data = array(); @@ -1315,7 +1349,7 @@ public function save(Validation $validation = NULL) } /** - * Deletes a single record or multiple records, ignoring relationships. + * Deletes a single record while ignoring relationships. * * @chainable * @return ORM @@ -1677,9 +1711,9 @@ public function errors_filename() /** * Alias of and_where() * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column value + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value * @return $this */ public function where($column, $op, $value) @@ -1696,9 +1730,9 @@ public function where($column, $op, $value) /** * Creates a new "AND WHERE" condition for the query. * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column value + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value * @return $this */ public function and_where($column, $op, $value) @@ -1715,9 +1749,9 @@ public function and_where($column, $op, $value) /** * Creates a new "OR WHERE" condition for the query. * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column value + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value * @return $this */ public function or_where($column, $op, $value) @@ -1818,8 +1852,8 @@ public function or_where_close() /** * Applies sorting with "ORDER BY ..." * - * @param mixed column name or array($column, $alias) or object - * @param string direction of sorting + * @param mixed $column column name or array($column, $alias) or object + * @param string $direction direction of sorting * @return $this */ public function order_by($column, $direction = NULL) @@ -1836,7 +1870,7 @@ public function order_by($column, $direction = NULL) /** * Return up to "LIMIT ..." results * - * @param integer maximum results to return + * @param integer $number maximum results to return * @return $this */ public function limit($number) @@ -1853,7 +1887,7 @@ public function limit($number) /** * Enables or disables selecting only unique columns using "SELECT DISTINCT" * - * @param boolean enable or disable distinct columns + * @param boolean $value enable or disable distinct columns * @return $this */ public function distinct($value) @@ -1870,7 +1904,7 @@ public function distinct($value) /** * Choose the columns to select from. * - * @param mixed column name or array($column, $alias) or object + * @param mixed $columns column name or array($column, $alias) or object * @param ... * @return $this */ @@ -1890,7 +1924,7 @@ public function select($columns = NULL) /** * Choose the tables to select "FROM ..." * - * @param mixed table name or array($table, $alias) or object + * @param mixed $tables table name or array($table, $alias) or object * @param ... * @return $this */ @@ -1910,8 +1944,8 @@ public function from($tables) /** * Adds addition tables to "JOIN ...". * - * @param mixed column name or array($column, $alias) or object - * @param string join type (LEFT, RIGHT, INNER, etc) + * @param mixed $table column name or array($column, $alias) or object + * @param string $type join type (LEFT, RIGHT, INNER, etc) * @return $this */ public function join($table, $type = NULL) @@ -1928,9 +1962,9 @@ public function join($table, $type = NULL) /** * Adds "ON ..." conditions for the last created JOIN statement. * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column name or array($column, $alias) or object + * @param mixed $ci column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $c2 column name or array($column, $alias) or object * @return $this */ public function on($c1, $op, $c2) @@ -1947,7 +1981,7 @@ public function on($c1, $op, $c2) /** * Creates a "GROUP BY ..." filter. * - * @param mixed column name or array($column, $alias) or object + * @param mixed $columns column name or array($column, $alias) or object * @param ... * @return $this */ @@ -1967,9 +2001,9 @@ public function group_by($columns) /** * Alias of and_having() * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column value + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value * @return $this */ public function having($column, $op, $value = NULL) @@ -1980,9 +2014,9 @@ public function having($column, $op, $value = NULL) /** * Creates a new "AND HAVING" condition for the query. * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column value + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value * @return $this */ public function and_having($column, $op, $value = NULL) @@ -1999,9 +2033,9 @@ public function and_having($column, $op, $value = NULL) /** * Creates a new "OR HAVING" condition for the query. * - * @param mixed column name or array($column, $alias) or object - * @param string logic operator - * @param mixed column value + * @param mixed $column column name or array($column, $alias) or object + * @param string $op logic operator + * @param mixed $value column value * @return $this */ public function or_having($column, $op, $value = NULL) @@ -2102,7 +2136,7 @@ public function or_having_close() /** * Start returning results after "OFFSET ..." * - * @param integer starting result number + * @param integer $number starting result number * @return $this */ public function offset($number) @@ -2119,7 +2153,7 @@ public function offset($number) /** * Enables the query to be cached for a specified amount of time. * - * @param integer number of seconds to cache + * @param integer $lifetime number of seconds to cache * @return $this * @uses Kohana::$cache_life */ @@ -2137,8 +2171,8 @@ public function cached($lifetime = NULL) /** * Set the value of a parameter in the query. * - * @param string parameter key to replace - * @param mixed value to use + * @param string $param parameter key to replace + * @param mixed $value value to use * @return $this */ public function param($param, $value) @@ -2156,8 +2190,8 @@ public function param($param, $value) * Checks whether a column value is unique. * Excludes itself if loaded. * - * @param string the field to check for uniqueness - * @param mixed the value to check for uniqueness + * @param string $field the field to check for uniqueness + * @param mixed $value the value to check for uniqueness * @return bool whteher the value is unique */ public function unique($field, $value) diff --git a/classes/kohana/orm/validation/exception.php b/classes/kohana/orm/validation/exception.php index 6e0bfd9..bff0c3c 100644 --- a/classes/kohana/orm/validation/exception.php +++ b/classes/kohana/orm/validation/exception.php @@ -4,7 +4,7 @@ * * @package Kohana/ORM * @author Kohana Team - * @copyright (c) 2007-2010 Kohana Team + * @copyright (c) 2007-2012 Kohana Team * @license http://kohanaframework.org/license */ class Kohana_ORM_Validation_Exception extends Kohana_Exception { @@ -150,7 +150,11 @@ protected function generate_errors($alias, array $array, $directory, $translate) { if (is_array($object)) { - $errors[$key] = $this->generate_errors($key, $object, $directory, $translate); + $errors[$key] = ($key === '_external') + // Search for errors in $alias/_external.php + ? $this->generate_errors($alias.'/'.$key, $object, $directory, $translate) + // Regular models get their own file not nested within $alias + : $this->generate_errors($key, $object, $directory, $translate); } elseif ($object instanceof Validation) { diff --git a/classes/model/auth/user.php b/classes/model/auth/user.php index b02833c..e1b60bf 100644 --- a/classes/model/auth/user.php +++ b/classes/model/auth/user.php @@ -4,7 +4,7 @@ * * @package Kohana/Auth * @author Kohana Team - * @copyright (c) 2007-2011 Kohana Team + * @copyright (c) 2007-2012 Kohana Team * @license http://kohanaframework.org/license */ class Model_Auth_User extends ORM { diff --git a/classes/model/auth/user/token.php b/classes/model/auth/user/token.php index ede1ee8..cace271 100644 --- a/classes/model/auth/user/token.php +++ b/classes/model/auth/user/token.php @@ -4,13 +4,18 @@ * * @package Kohana/Auth * @author Kohana Team - * @copyright (c) 2007-2011 Kohana Team + * @copyright (c) 2007-2012 Kohana Team * @license http://kohanaframework.org/license */ class Model_Auth_User_Token extends ORM { // Relationships protected $_belongs_to = array('user' => array()); + + protected $_created_column = array( + 'column' => 'created', + 'format' => TRUE, + ); /** * Handles garbage collection and deleting of expired objects. diff --git a/config/userguide.php b/config/userguide.php index a7d270e..a70ac01 100644 --- a/config/userguide.php +++ b/config/userguide.php @@ -17,7 +17,7 @@ 'description' => 'Official ORM module, a modeling library for object relational mapping.', // Copyright message, shown in the footer for this module - 'copyright' => '© 2008–2011 Kohana Team', + 'copyright' => '© 2008–2012 Kohana Team', ) ) ); diff --git a/guide/orm/filters.md b/guide/orm/filters.md index 5f20e60..218f0a4 100644 --- a/guide/orm/filters.md +++ b/guide/orm/filters.md @@ -1,21 +1,40 @@ # Filters -Filters in ORM work much like they used to when they were part of the Validate class in 3.0.x however they have been modified to match the flexible syntax of [Validation] rules in 3.1.x. Filters run as soon as the field is set in your model and should be used to format the data before it is inserted into the Database. +Filters in ORM work much like they used to when they were part of the Validate class in 3.0.x. However, they have been modified to match the flexible syntax of [Validation] rules in 3.1.x. -Define your filters the same way you define rules, as an array returned by the `ORM::filters()` method like the following: +Filters run as soon as the field is set in your model and should be used to format the data before it is inserted into the Database. Filters are defined the same way you define [rules](validation), as an array returned by the `ORM::filters()` method, like the following: public function filters() { return array( + // Field Filters + // $field_name => array(mixed $callback[, array $params = array(':value')]), 'username' => array( + // PHP Function Callback, default implicit param of ':value' array('trim'), ), 'password' => array( - array(array($this, 'hash_password')), + // Callback method with object context and params + array(array($this, 'hash_password'), array(':value', Model_User::salt())), ), 'created_on' => array( + // Callback static method with params array('Format::date', array(':value', 'Y-m-d H:i:s')), ), + 'other_field' => array( + // Callback static method with implicit param of ':value' + array('MyClass::static_method'), + // Callback method with object context with implicit param of ':value' + array(array($this, 'change_other_field')), + // PHP function callback with explicit params + array('str_replace', array('luango', 'thomas', ':value'), + // Function as the callback (PHP 5.3+) + array(function($value) { + // Do something to $value and return it. + return some_function($value); + }), + ), + ); } diff --git a/guide/orm/using.md b/guide/orm/using.md index 8d54d65..878c62a 100644 --- a/guide/orm/using.md +++ b/guide/orm/using.md @@ -73,3 +73,22 @@ To delete an object, you can call the [ORM::delete] method on a loaded ORM model $user = ORM::factory('user', 20); $user->delete(); + +## Mass assignment + + +To set multiple values at once, use [ORM::values] + + try + { + $user = ORM::factory('user') + ->values($this->request->post(), array('username','password')) + ->create(); + } + catch (ORM_Validation_Exception $e) + { + // Handle validation errors ... + } + +[!!] Although the second argument is optional, it is *highly recommended* to specify the list of columns you expect to change. Not doing so will leave your code _vulnerable_ in case the attacker adds fields you didn't expect. + diff --git a/guide/orm/validation.md b/guide/orm/validation.md index d83414f..609496b 100644 --- a/guide/orm/validation.md +++ b/guide/orm/validation.md @@ -77,23 +77,26 @@ Certain forms contain information that should not be validated by the model, but public function action_create() { - try - { - $user = ORM::factory('user'); - $user->username = $_POST['username']; - $user->password = $_POST['password']; - - $extra_rules = Validation::factory($_POST) - ->rule('password_confirm', 'matches', array( - ':validation', ':field', 'password' - )); - - // Pass the extra rules to be validated with the model - $user->save($extra_rules); - } - catch (ORM_Validation_Exception $e) + if ($this->request->method === Request::POST) { - $errors = $e->errors('models'); + try + { + $user = ORM::factory('user'); + + $user->values($this->request->post(), array('username','password')); + + $extra_rules = Validation::factory($this->request->post()) + ->rule('password_confirm', 'matches', array( + ':validation', ':field', 'password' + )); + + // Pass the extra rules to be validated with the model + $user->save($extra_rules); + } + catch (ORM_Validation_Exception $e) + { + $errors = $e->errors('models'); + } } }