<?php/** * $header * @copyright Copyright 2007, Peter Goodman * @package valkyrie * @subpackage Libraries.user * @author Peter Goodman *//** * Library to easily manage a user system. This library lets the programmer * get a user login system up and running super quickly by having them configure * this class through a (pre) filter using User::configure(). The point of this * class is that it can sit on top of any database user table schema, and isn't * confined to any specific column names. */class User { const COOKIE_KEY = 'vkua'; const SESSION_KEY = 'vkua'; /** * @internal */ protected $definition, $table_name, $as_name = '', $as_pass = '', $as_login_key = '', $pass_hash_func = 'md5'; /** * @internal */ private $dba, //$session, $model; /** * @internal */ private $is_logged = FALSE; /** * Constructor. * @internal */ public function __construct(DatabaseDriver $dba, DatabaseModel $model) { // need to do this because we overloaded the // already overloaded methods. This will implicitly // load the session library if it isn't already $this->dba = $dba; $this->model = $model; // start the session if it hasn't been start_session(); if(!isset($_SESSION[self::SESSION_KEY])) { $_SESSION[self::SESSION_KEY] = array(); } if(!empty($_SESSION[self::SESSION_KEY])) { $this->is_logged = TRUE; } } /** * Bring in the dependencies. */ static public function create(Registry $registry) { return new User($registry->dba, $registry->models['database']); } /** * Destructor. * @internal */ public function __destruct() { unset($this->dba, $_SESSION, $this->model); } /** * Because this is a library it can be cloned. We don't * want to let that so we will throw an exception if it * is ever tried. */ final public function __clone() { throw new Exception("User library is not allowed to be cloned."); } /** * Is this user logged in or not? * @return bool */ public function isLogged() { return $this->is_logged; } /** * Getter function useful when the user object is passed to the template * engine, eg: <cond:if isset="user.logged_in"> */ public function getLoggedIn() { return $this->isLogged() ? TRUE : NULL; } /** * Save any session info to the database. This is meant to emulate * DatabaseRecord::save */ public function save() { if(!$this->isLogged()) { return FALSE; } $args = array(); $pk = $this->definition->primary; foreach($_SESSION[self::SESSION_KEY] as $key => $val) { // make sure that the field exists in the definition and that // we are also not trying to update the primary key if($this->definition->hasField($key) && $key != $pk) { // validate the field try { $this->validateField($key, $val); } catch(FailedValidationException $e) { return FALSE; } $args["{$key}=?"] = $val; } } // we've got stuff to update! if(!empty($args)) { $vals = array_values($args); $vals[] = $_SESSION[self::SESSION_KEY][$pk]; $this->dba->update("UPDATE {$this->table_name} SET ". implode(",", array_keys($args)) ." WHERE {$pk}=?", $vals); } return TRUE; } /** * Log a user in. */ public function login($as_name, $as_pass, $bool_autologin) { // no need to log this user in twice if($this->isLogged()) { return FALSE; } $as_pass = $this->hash($as_pass); $user = $this->dba->select("SELECT * FROM {$this->table_name} WHERE {$this->as_name}=? AND {$this->as_pass}=? LIMIT 1", array($as_name, $as_pass))->current(); if(empty($user)) { return FALSE; } if($bool_autologin && !empty($this->as_login_key)) { // unique login key $login_key = md5(uniqid(rand(), TRUE)); // get PHP's default (or user overridden) cookie params $cookie_params = session_get_cookie_params(); // expire in 30 days setcookie(self::COOKIE_KEY, $login_key, time()+2592000, $cookie_params['path'], $cookie_params['domain'], TRUE, TRUE); // update the user table $this->dba->update("UPDATE {$this->table_name} SET {$this->as_login_key}=?", array($login_key)); } $_SESSION[self::SESSION_KEY] = $user; $this->is_logged = TRUE; return TRUE; } /** * Log the user out. */ public function logout() { unset($_SESSION[self::SESSION_KEY]); $this->is_logged = FALSE; $this->killCookie(); } /** * Auto log in a user. This is best placed in a pre-filter. * @internal */ private function autologin() { // user is logged in, don't bother if($this->isLogged()) { return FALSE; } // make sure the cookie even exists if(isset($_COOKIE[self::COOKIE_KEY])) { if(empty($this->as_login_key)) { return $this->killCookie(); } $user = $this->dba->select("SELECT * FROM {$this->table_name} WHERE {$this->as_login_key}=?", array($_COOKIE[self::COOKIE_KEY]))->current(); if(empty($user)) { return $this->killCookie(); } $_SESSION[self::SESSION_KEY] = $user; $this->is_logged = TRUE; } } /** * Kill any cookies asociated with this user. * @internal */ private function killCookie() { unset($_COOKIE[self::COOKIE_KEY]); setcookie(self::COOKIE_KEY, "", time()-1000, $cookie_params['path'], $cookie_params['domain'], TRUE, TRUE); return FALSE; } /** * Hash the password. * @internal */ private function hash($pass) { // an object callback has been passed if(is_array($this->pass_hash_func)) { list($obj, $fn) = $this->pass_hash_func; $pass = $obj->$fn($pass); // a string callback has been passed } else { $fn = $this->pass_hash_func; $pass = $fn($pass); } return $pass; } /** * Configure the User library to accept login/out events and such. * @param string $definition_id The ID of the model definition for the users table. * @param string $as_name The name of the column that users will identify themselves with, eg: user name or email. * @param string $as_pass The name of the column that the password is stored in. * @param string $as_login_key The name of the column that the login key (for auto logging in) is stored in. * @param mixed $hash_method The callback function (either string or array) used to hash the password. */ public function configure($definition_id, $as_name, $as_pass, $as_login_key = FALSE, $hash_method = FALSE) { // configure it $this->definition = $this->model->getModelDefinition($definition_id); $this->table_name = $this->definition->getTable('name'); $this->as_name = $as_name; $this->as_pass = $as_pass; $this->as_login_key = $as_login_key; $this->pass_hash_func = !$hash_method ? 'md5' : $hash_method; // auto log the user in, needs to be done in here, we can't put // it in the constructor otherwise the object won't be configured // for use to get any useful info from the db $this->autologin(); } /** * Overloaded methods. Because this class extends the registry we can * also access these methods through ArrayAccess */ public function __get($key = '') { $ret = NULL; if(isset($_SESSION[self::SESSION_KEY][$key])) { $ret =& $_SESSION[self::SESSION_KEY][$key]; } return $ret; } public function __set($key = '', $val) { $_SESSION[self::SESSION_KEY][$key] = $val; } public function __isset($key = '') { return $this->isLogged() && isset($_SESSION[self::SESSION_KEY][$key]); } public function __unset($key = '') { if($this->isLogged()) { unset($_SESSION[self::SESSION_KEY][$key]); } }}