code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Habari UserRecord Class
 *
 * @todo TODO Fix this documentation!
 *
 * Includes an instance of the UserInfo class; for holding inforecords about the user
 * If the User object describes an existing user; use the internal info object to get, set, unset and test for existence (isset) of
 * info records
 * <code>
 * $this->info = new UserInfo ( 1 );  // Info records of user with id = 1
 * $this->info->option1 = "blah"; // set info record with name "option1" to value "blah"
 * $info_value = $this->info->option1; // get value of info record with name "option1" into variable $info_value
 * if ( isset ($this->info->option1) )  // test for existence of "option1"
 * unset ( $this->info->option1 ); // delete "option1" info record
 * </code>
 *
 */
class User extends QueryRecord implements FormStorage, IsContent
{
	/**
	 * Static storage for the currently logged-in User record
	 */
	private static $identity = null;

	private $inforecords = null;

	private $group_list = null;

	protected $url_args;

	/**
	 * Get default fields for this record.
	 * @return array an array of the fields used in the User table
	 */
	public static function default_fields()
	{
		return array(
			'id' => '',
			'username' => '',
			'email' => '',
			'password' => ''
		);
	}

	/**
	 * Constructor for the User class
	 * @param array $paramarray an associative array of initial User fields
	 */
	public function __construct( $paramarray = array() )
	{
		// Defaults
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields );
		parent::__construct( $paramarray );
		$this->exclude_fields( 'id' );
		/* $this->fields['id'] could be null in case of a new user. If so, the info object is _not_ safe to use till after set_key has been called. Info records can be set immediately in any other case. */

	}

	/**
	 * Build and return the anonymous user
	 * @return object user object
	 */
	public static function anonymous()
	{
		static $anonymous = null;
		if ( $anonymous == null ) {
			$anonymous = new User();
			$anonymous->id = 0;
			$anonymous->username = _t( 'Anonymous' );
			Plugins::act( 'create_anonymous_user', $anonymous );
		}
		return $anonymous;
	}

	/**
	 * Check for the existence of a cookie, and return a user object of the user, if successful
	 * @return User user object, or false if no valid cookie exists
	 */
	public static function identify()
	{
		$out = false;
		// Let plugins set the user
		if ( $out = Plugins::filter('user_identify', $out) ) {
			self::$identity = $out;
		}
		// Is the logged-in user not cached already?
		if ( isset( self::$identity ) ) {
			// is this user acting as another user?
			if ( isset( $_SESSION['sudo'] ) ) {
				// if so, let's return that user data
				$out = self::get_by_id( intval( $_SESSION['sudo'] ) );
			}
			else {
				// otherwise return the logged-in user
				$out = self::$identity;
			}
		}
		if ( isset( $_SESSION['user_id'] ) ) {
			if ( $user = self::get_by_id( intval( $_SESSION['user_id'] ) ) ) {
				// Cache the user in the static variable
				self::$identity = $user;
				$out = $user;
			}
		}
		if(!$out) {
			$out = self::anonymous();
		}
		return $out;
	}

	/**
	 * Creates a new user object and saves it to the database
	 * @param array An associative array of user fields
	 * @return User the User object that was created
	**/
	public static function create( $paramarray )
	{
		$user = new User( $paramarray );
		$user->insert();
		return $user;
	}

	/**
	 * Save a new user to the users table
	 */
	public function insert()
	{
		$allow = true;
		$allow = Plugins::filter( 'user_insert_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'user_insert_before', $this );
		$this->exclude_fields( 'id' );
		$result = parent::insertRecord( DB::table( 'users' ) );
		$this->fields['id'] = DB::last_insert_id(); // Make sure the id is set in the user object to match the row id
		$this->info->set_key( $this->id );
		/* If a new user is being created and inserted into the db, info is only safe to use _after_ this set_key call. */
		// $this->info->option_default = "saved";

		// Set the default timezone, date format, and time format
		$this->info->locale_tz = Options::get( 'timezone' );
		$this->info->locale_date_format = Options::get( 'dateformat' );
		$this->info->locale_time_format = Options::get( 'timeformat' );

		$this->info->commit();

		if ( $result ) {
			// Add the user to the default authenticated group if it exists
			if ( UserGroup::exists( 'authenticated' ) ) {
				$this->add_to_group( 'authenticated' );
			}
		}

		EventLog::log( _t( 'New user created: %s', array( $this->username ) ), 'info', 'default', 'habari' );
		Plugins::act( 'user_insert_after', $this );

		return $result;
	}

	/**
	 * Updates an existing user in the users table
	 */
	public function update()
	{
		$allow = true;
		$allow = Plugins::filter( 'user_update_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'user_update_before', $this );
		$this->info->commit();
		$result = parent::updateRecord( DB::table( 'users' ), array( 'id' => $this->id ) );
		Plugins::act( 'user_update_after', $this );
		return $result;
	}

	/**
	 * Delete a user account
	 */
	public function delete()
	{
		$allow = true;
		$allow = Plugins::filter( 'user_delete_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'user_delete_before', $this );

		// remove any userinfo records
		$this->info->delete_all();

		// remove all this user's permissions
		DB::query( 'DELETE FROM {user_token_permissions} WHERE user_id=?', array( $this->id ) );
		// remove user from any groups
		DB::query( 'DELETE FROM {users_groups} WHERE user_id=?', array( $this->id ) );
		EventLog::log( _t( 'User deleted: %s', array( $this->username ) ), 'info', 'default', 'habari' );
		$result = parent::deleteRecord( DB::table( 'users' ), array( 'id' => $this->id ) );
		Plugins::act( 'user_delete_after', $this );
		return $result;
	}

	/**
	 * Save the user id into the session
	 */
	public function remember()
	{
		$_SESSION['user_id'] = $this->id;
		ACL::clear_caches();
		Session::set_userid( $this->id );
	}

	/**
	 * Delete the user id from the session
	 * @param boolean $redirect Redirect the user to base_url after destroying session?
	 */
	public function forget( $redirect = true )
	{

		// if the user is not actually logged in, just return so we don't throw any errors later
		if ( $this->loggedin != true ) {
			return;
		}

		// is this user acting as another user?
		if ( isset( $_SESSION['sudo'] ) ) {
			// if so, remove the sudo token, but don't log out
			// the user
			unset( $_SESSION['sudo'] );

			if ( $redirect ) {
				Utils::redirect( Site::get_url( 'admin' ) );
			}
			else {
				// we want to return, not continue processing, or we'd log out the user too
				return;
			}
		}
		ACL::clear_caches();
		Plugins::act( 'user_forget', $this );
		Session::clear_userid( $_SESSION['user_id'] );
		unset( $_SESSION['user_id'] );

		if ( $redirect ) {
			Utils::redirect( Site::get_url( 'habari' ) );
		}
	}

	/**
	* Check a user's credentials to see if they are legit
	* -- calls all auth plugins BEFORE checking local database.
	*
	* @param string $who A username
	* @param string $pw A password
	* @return User|boolean a User object, or false
	*/
	public static function authenticate( $who, $pw )
	{
		if ( '' === $who || '' === $pw ) {
			return false;
		}

		$user = new StdClass();
		$require = false;
		$user = Plugins::filter( 'user_authenticate', $user, $who, $pw );
		if ( $user instanceof User ) {
			self::$identity = $user;
			Plugins::act( 'user_authenticate_successful', self::$identity );
			EventLog::log( _t( 'Successful login for %s', array( $user->username ) ), 'info', 'authentication', 'habari' );
			// set the cookie
			$user->remember();
			return self::$identity;
		}
		elseif ( !is_object( $user ) ) {
			Plugins::act( 'user_authenticate_failure', 'plugin' );
			EventLog::log( _t( 'Login attempt (via authentication plugin) for non-existent user %s', array( $who ) ), 'warning', 'authentication', 'habari' );
			Session::error( _t( 'Invalid username/password' ) );
			self::$identity = null;
			return false;
		}

		// Check by name first. Allows for the '@' to be in the username, without it being an email address
		$user = self::get_by_name( $who );

		if ( ! $user ) {
			// No such user.
			Plugins::act( 'user_authenticate_failure', 'non-existent' );
			EventLog::log( _t( 'Login attempt for non-existent user %s', array( $who ) ), 'warning', 'authentication', 'habari' );
			Session::error( _t( 'Invalid username/password' ) );
			self::$identity = null;
			return false;
		}

		if ( Utils::crypt( $pw, $user->password ) ) {
			// valid credentials were supplied
			self::$identity = $user;
			Plugins::act( 'user_authenticate_successful', self::$identity );
			EventLog::log( _t( 'Successful login for %s', array( $user->username ) ), 'info', 'authentication', 'habari' );
			// set the cookie
			$user->remember();
			return self::$identity;
		}
		else {
			// Wrong password.
			Plugins::act( 'user_authenticate_failure', 'bad_pass' );
			EventLog::log( _t( 'Wrong password for user %s', array( $user->username ) ), 'warning', 'authentication', 'habari' );
			Session::error( _t( 'Invalid username/password' ) );
			self::$identity = null;
			return false;
		}
	}

	/**
	 * Fetch a user from the database by name, ID, or email address.
	 * This is a wrapper function that will invoke the appropriate
	 * get_by_* method.
	 *
	 * @param mixed $who user ID, username, or e-mail address
	 * @return object User object, or false
	 */
	public static function get( $who )
	{
		if ( $who instanceof User ) {
			$user = $who;
		}
		elseif ( is_numeric( $who ) ) {
			// Got a User ID
			$user = self::get_by_id( $who );
		}
		else {
			// Got username or email
			$user = self::get_by_name( $who );
			if ( ! $user && strpos( $who, '@' ) !== false ) {
				// Got an email address
				$user = self::get_by_email( $who );
			}
		}
		// $user will be a user object, or false depending on the
		// results of the get_by_* method called above
		return $user;
	}

	/**
	 * Select a user from the database by its id
	 *
	 * @param int $id The id of a user
	 * @return User The User object of the specified user
	 */
	public static function get_by_id( $id )
	{
		if ( 0 == $id ) {
			return false;
		}

		$params = array(
			'id' => $id,
			'limit' => 1,
			'fetch_fn' => 'get_row',
			);

		return Users::get( $params );
	}

	/**
	 * Select a user from the database by its username
	 *
	 * @param string $username The name of a user
	 * @return User The User object of the specified user
	 */
	public static function get_by_name( $username )
	{
		if ( '' == $username ) {
			return false;
		}

		$params = array(
			'username' => $username,
			'limit' => 1,
			'fetch_fn' => 'get_row',
			);

		return Users::get( $params );
	}

	/**
	 * Select a user from the database by its email address
	 *
	 * @param string $email The email address of a user
	 * @return User The User object of the specified user
	 */
	public static function get_by_email( $email )
	{
		if ( '' === $email ) {
			return false;
		}

		$params = array(
			'email' => $email,
			'limit' => 1,
			'fetch_fn' => 'get_row',
			);

		return Users::get( $params );
	}

	/**
	 * Return the id of a user
	 *
	 * @param mixed $user The id, name, or email address of a user
	 * @return integer The id of the specified user or false if that user doesn't exist
	 */
	public static function get_id( $user )
	{
		if ( is_int( $user ) ) {
			return $user;
		}
		$user = self::get( $user );
		return $user->id;
	}

	/**
	 * Returns the number of posts written by this user
	 * @param mixed A status on which to filter posts (published, draft, etc).  If false, no filtering will be performed.  Default: no filtering
	 * @return int The number of posts written by this user
	 */
	public function count_posts( $status = false )
	{
		return Posts::count_by_author( $this->id, $status );
	}

	/**
	 * Returns an array of information about the commenter
	 * If this is a logged-in user, then return details from their user profile.
	 * If this is a returning commenter, then return details from their cookie
	 * otherwise return empty strings.
	 * @return Array an array of name, email and URL
	 */
	public static function commenter()
	{
		$cookie = 'comment_' . Options::get( 'GUID' );
		$commenter = array();
		if ( self::identify() ) {
			$commenter['name'] = self::identify()->username;
			$commenter['email'] = self::identify()->email;
			$commenter['url'] = Site::get_url( 'habari' );
		}
		elseif ( isset( $_COOKIE[$cookie] ) ) {
			list( $commenter['name'], $commenter['email'], $commenter['url'] ) = explode( '#', urldecode( $_COOKIE[$cookie] ) );
		}
		else {
			$commenter['name'] = '';
			$commenter['email'] = '';
			$commenter['url'] = '';
		}
		return $commenter;
	}

	/**
	 * Determine if a user has a specific token permission
	 *
	 * @param string $token The name of the token for which to check permission
	 * @param string $access The type of access to check for (read, write, full, etc.)
	 * @return boolean True if this user has the requested access, false if not
	 */
	public function can( $token, $access = 'any' )
	{
		return ACL::user_can( $this, $token, $access );
	}

	/**
	 * Determine if a user has any of a set of tokens
	 *
	 * @param array $token_access An array of tokens and the permissions to
	 * check for each of them.
	 * @return boolean True if this user has the requested access, false if not
	 */
	public function can_any( $token_access = array() )
	{
		$token_access = Utils::single_array( $token_access );

		foreach ( $token_access as $token => $access ) {
			$access = Utils::single_array( $access );
			foreach ( $access as $mask ) {
				if ( is_bool( $mask ) ) {
					if ( $this->can( $token ) ) {
						return true;
					}
				}
				else {
					if ( $this->can( $token, $mask ) ) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Determine if a user has been denied access to a specific token
	 *
	 * @param string $token The name of the token to detect
	 * @return boolean True if this user has been denied access to the requested token, false if not
	 */
	public function cannot( $token )
	{
		return ACL::user_cannot( $this, $token );
	}

	/**
	 * Assign permissions to one or more new tokens to this user
	 * @param mixed A token ID, name, or array of the same
	 * @param string The access to grant
	**/
	public function grant( $tokens, $access = 'full' )
	{
		$tokens = Utils::single_array( $tokens );
		// Use ids internally for all tokens
		$tokens = array_map( array( 'ACL', 'token_id' ), $tokens );

		foreach ( $tokens as $token ) {
			ACL::grant_user( $this->id, $token, $access );
			EventLog::log( _t( 'User %1$s: Access to %2$s changed to %3$s', array( $this->username, ACL::token_name( $token ), $access ) ), 'notice', 'user', 'habari' );
		}
	}

	/**
	 * Deny permissions to one or more tokens to this user
	 * @param mixed The token ID or name to be denied, or an array of the same
	**/
	public function deny( $tokens )
	{
		$this->grant( $tokens, 'deny' );
	}

	/**
	 * Remove permissions to one or more tokens from a user
	 * @param mixed a token ID, name, or array of the same
	**/
	public function revoke( $tokens )
	{
		$tokens = Utils::single_array( $tokens );
		// get token IDs
		$tokens = array_map( array( 'ACL', 'token_id' ), $tokens );
		foreach ( $tokens as $token ) {
			ACL::revoke_user_token( $this->id, $token );
			EventLog::log( _t( 'User %1$s: Permission to %2$s revoked.', array( $this->username, ACL::token_name( $token ) ) ), 'notice', 'user', 'habari' );
		}
	}

	/**
	 * Returns an array of groups to which this user belongs
	 * @param bool Whether to refresh the cache
	 * @return Array an array of group IDs to which this user belongs
	**/
	private function list_groups( $refresh = false )
	{
		if ( ( empty( $this->group_list ) ) || $refresh ) {
			$this->group_list = DB::get_column( 'SELECT group_id FROM {users_groups} WHERE user_id=?', array( $this->id ) );
		}
		return $this->group_list;
	}

	/**
	 * function in_group
	 * Whether or not this user is is in the specified group
	 * @param int|string $group a group ID or name
	 * @return bool Whether or not this user is in the specified group
	**/
	public function in_group( $group )
	{
		$groups = $this->list_groups();
		return in_array( UserGroup::id( $group ), $groups );
	}

	/**
	 * function add_to_group
	 * @param integer|string|UserGroup $group A group ID, name, or UserGroup instance
	 * @return null
	**/
	public function add_to_group( $group )
	{
		$group = UserGroup::get( $group );
		if ( $group instanceOf UserGroup ) {
			$group->add( $this->id );
			EventLog::log( _t( ' User %1$s: Added to %2$s group.', array( $this->username, $group->name ) ), 'notice', 'user', 'habari' );
		}
	}

	/**
	 * function remove_from_group
	 * removes this user from a group
	 * @param integer|string|UserGroup $group A group ID, name, or UserGroup instance
	 * @return null
	**/
	public function remove_from_group( $group )
	{
		$group = UserGroup::get( $group );
		if ( $group instanceOf UserGroup ) {
			$group->remove( $this->id );
			EventLog::log( _t( ' User %1$s: Removed from %2$s group.', array( $this->username, $group->name ) ), 'notice', 'user', 'habari' );
		}
	}

	/**
	 * Capture requests for the info object so that it can be initialized properly when
	 * the constructor is bypassed (see PDO::FETCH_CLASS pecularities). Passes all other
	 * requests to parent.
	 *
	 * @param string $name requested field name
	 * @return mixed the requested field value
	 */
	public function __get( $name )
	{
		$fieldnames = array_merge( array_keys( $this->fields ), array( 'groups', 'displayname', 'loggedin', 'info' ) );
		$filter = false;
		if ( !in_array( $name, $fieldnames ) && strpos( $name, '_' ) !== false ) {
			$field_matches = implode('|', $fieldnames);
			if(preg_match( '/^(' . $field_matches . ')_(.+)$/', $name, $matches )) {
				list( $junk, $name, $filter )= $matches;
			}
		}

		switch ( $name ) {
			case 'info':
				$out = $this->get_info();
				break;
			case 'groups':
				$out = $this->list_groups();
				break;
			case 'displayname':
				$out = ( empty( $this->info->displayname ) ) ? $this->username : $this->info->displayname;
				break;
			case 'loggedin':
				$out = $this->id != 0;
				break;
			default:
				$out = parent::__get( $name );
				break;
		}

		$out = Plugins::filter( "user_get", $out, $name, $this );
		$out = Plugins::filter( "user_{$name}", $out, $this );
		if ( $filter ) {
			$out = Plugins::filter( "user_{$name}_{$filter}", $out, $this );
		}
		return $out;
	}

	/**
	 * function get_info
	 * Gets the info object for this user, which contains data from the userinfo table
	 * related to this user.
	 * @return UserInfo object
	 */
	private function get_info()
	{
		if ( ! isset( $this->inforecords ) ) {
			// If this user isn't in the database yet...
			if (  0 == $this->id ) {
				$this->inforecords = new UserInfo();
			}
			else {
				$this->inforecords = new UserInfo( $this->id );
			}
		}
		else {
			$this->inforecords->set_key( $this->id );
		}
		return $this->inforecords;
	}

	/**
	 * Returns a set of properties used by URL::get to create URLs
	 * @return array Properties of this post used to build a URL
	 */
	public function get_url_args()
	{
		if ( !$this->url_args ) {
			$this->url_args = array_merge( URL::extract_args( $this->info, 'info_' ), $this->to_array() );
		}
		return $this->url_args;
	}

	/**
	 * Stores a form value into the object
	 *
	 * @param string $key The name of a form component that will be stored
	 * @param mixed $value The value of the form component to store
	 */
	function field_save($key, $value)
	{
		$this->info->$key = $value;
		$this->info->commit();
	}

	/**
	 * Loads form values from an object
	 *
	 * @param string $key The name of a form component that will be loaded
	 * @return mixed The stored value returned
	 */
	function field_load($key)
	{
		return $this->info->$key;
	}

	/**
	 * Returns the content type of the object instance
	 *
	 * @return array An array of content types that this object represents, starting with the most specific
	 */
	function content_type()
	{
		return array(
			$this->id . '.user',
			$this->username . '.user',
			'user'
		);
	}
}

?>


code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Habari UserGroup Class
 *
 */
class UserGroup extends QueryRecord
{
	// These arrays hold the current membership, permission and token settings for this group
	// $member_ids is not NOT matched key and value pairs ( like array('foo'=>'foo') )
	private $member_ids = null;
	private $permissions;

	/**
	 * get default fields for this record
	 * @return array an array of the fields used in the UserGroup table
	 */
	public static function default_fields()
	{
		return array(
			'id' => '',
			'name' => ''
		);
	}

	/**
	 * Constructor for the UserGroup class
	 * @param array $paramarray an associative array of UserGroup fields
	 */
	public function __construct( $paramarray = array() )
	{
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields
		);
		parent::__construct( $paramarray );

		// exclude field keys from the $this->fields array that should not be updated in the database on insert/update
		$this->exclude_fields( array( 'id' ) );
	}

	/**
	 * Create a new UserGroup object and save it to the database
	 * @param array $paramarray An associative array of UserGroup fields
	 * @return UserGroup the UserGroup that was created
	 * @todo Make this function accept only a name, since you can't set an id into an autoincrement field, and we don't try.
	 */
	public static function create( $paramarray )
	{
		$usergroup = new UserGroup( $paramarray );
		if ( $usergroup->insert() ) {
			return $usergroup;
		}
		else {
			// Does the group already exist?
			if ( isset( $paramarray['name'] ) ) {
				$exists = DB::get_value( 'SELECT count(1) FROM {groups} WHERE name = ?', array( $paramarray['name'] ) );
				if ( $exists ) {
					return UserGroup::get_by_name( $paramarray['name'] );
				}
			}
			return false;
		}
	}

	/**
	 * Save a new UserGroup to the UserGroup table
	 */
	public function insert()
	{
		$exists = DB::get_value( 'SELECT count(1) FROM {groups} WHERE name = ?', array( $this->name ) );
		if ( $exists ) {
			return false;
		}

		$allow = true;
		// plugins have the opportunity to prevent insertion
		$allow = Plugins::filter( 'usergroup_insert_allow', $allow, $this );
		if ( ! $allow ) {
			return false;
		}
		Plugins::act( 'usergroup_insert_before', $this );
		$this->exclude_fields( 'id' );
		$result = parent::insertRecord( DB::table( 'groups' ) );
		$this->fields['id'] = DB::last_insert_id();

		$this->set_member_list();

		EventLog::log( _t( 'New group created: %s', array( $this->name ) ), 'info', 'default', 'habari' );
		Plugins::act( 'usergroup_insert_after', $this );
		return $result;
	}

	/**
	 * Updates an existing UserGroup in the DB
	 */
	public function update()
	{
		$allow = true;
		// plugins have the opportunity to prevent modification
		$allow = Plugins::filter( 'usergroup_update_allow', $allow, $this );
		if ( ! $allow ) {
			return false;
		}
		Plugins::act( 'usergroup_update_before', $this );

		$this->set_member_list();

		EventLog::log( _t( 'User Group updated: %s', array( $this->name ) ), 'info', 'default', 'habari' );
		Plugins::act( 'usergroup_update_after', $this );
	}

	/**
	 * Set the member list for this group
	 */
	protected function set_member_list()
	{
		$this->load_member_cache();

		// Remove all users from this group in preparation for adding the current list
		DB::query( 'DELETE FROM {users_groups} WHERE group_id=?', array( $this->id ) );
		// Add the current list of users into the group
		foreach ( $this->member_ids as $user_id ) {
			DB::query( 'INSERT INTO {users_groups} (user_id, group_id) VALUES (?, ?)', array( $user_id, $this->id ) );
		}
		EventLog::log( _t( 'User Group %s: Member list reset', array( $this->name ) ), 'notice', 'user', 'habari' );
	}

	/**
	 * Delete a UserGroup
	 */
	public function delete()
	{
		$allow = true;
		// plugins have the opportunity to prevent deletion
		$allow = Plugins::filter( 'usergroup_delete_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}

		$name = $this->name;
		Plugins::act( 'usergroup_delete_before', $this );
		// remove all this group's permissions
		$results = DB::query( 'DELETE FROM {group_token_permissions} WHERE group_id=?', array( $this->id ) );
		// remove all this group's members
		$results = DB::query( 'DELETE FROM {users_groups} WHERE group_id=?', array( $this->id ) );
		// remove this group
		$result = parent::deleteRecord( DB::table( 'groups' ), array( 'id' => $this->id ) );
		Plugins::act( 'usergroup_delete_after', $this );
		EventLog::log( _t( 'User Group %s: Group deleted.', array( $name ) ), 'notice', 'user', 'habari' );
		return $result;
	}

	/**
	 * function __get
	 * magic get function for returning virtual properties of the class
	 * @param mixed the property to get
	 * @return mixed the property
	 */
	public function __get( $param )
	{
		switch ( $param ) {
			case 'members':
				$this->load_member_cache();
				return (array) $this->member_ids;
				break;
			case 'users':
				$this->load_member_cache();
				$results = DB::get_results( 'SELECT u.* FROM {users} u INNER JOIN {users_groups} ug ON ug.user_id = u.id WHERE ug.group_id= ?', array( $this->id ), 'User' );
				if ( in_array( 0, $this->member_ids ) ) {
					$results[] = User::anonymous();
				}
				return $results;
			case 'permissions':
				$this->load_permissions_cache();
				return $this->permissions;
				break;
			default:
				return parent::__get( $param );
				break;
		}
	}

	/**
	 * Add one or more users to this group
	 * @param mixed $users a user ID or name, or an array of the same
	 */
	public function add( $users )
	{
		$this->load_member_cache();
		$users = Utils::single_array( $users );
		// Use ids internally for all users
		$user_ids = array_map( array( 'User', 'get_id' ), $users );
		// Remove users from group membership
		$this->member_ids = array_merge( (array) $this->member_ids, (array) $user_ids );
		// List each group member exactly once
		$this->member_ids = array_unique( $this->member_ids );
		$this->update();

		EventLog::log( _t( 'User Group %1$s: Users were added to the group.', array( $this->name ) ), 'notice', 'user', 'habari' );
	}

	/**
	 * Remove one or more user from this group
	 * @param mixed $users A user ID or name, or an array of the same
	 */
	public function remove( $users )
	{
		$this->load_member_cache();
		$users = Utils::single_array( $users );
		// Use ids internally for all users
		$users = array_map( array( 'User', 'get_id' ), $users );
		// Remove users from group membership
		$this->member_ids = array_diff( $this->member_ids, $users );
		$this->update();

		EventLog::log( _t( 'User Group %1$s: Users were removed from the group.', array( $this->name ) ), 'notice', 'user', 'habari' );
	}

	/**
	 * Assign one or more new permission tokens to this group
	 * @param mixed A permission token ID, name, or array of the same
	 */
	public function grant( $tokens, $access = 'full' )
	{
		$tokens = Utils::single_array( $tokens );
		// Use ids internally for all tokens
		$tokens = array_map( array( 'ACL', 'token_id' ), $tokens );

		// grant the new permissions
		foreach ( $tokens as $token ) {
			ACL::grant_group( $this->id, $token, $access );
		}
	}

	/**
	 * Deny one or more permission tokens to this group
	 * @param mixed The permission ID or name to be denied, or an array of the same
	 */
	public function deny( $tokens )
	{
		$this->grant( $tokens, 'deny' );
	}

	/**
	 * Remove one or more permissions from a group
	 * @param mixed a permission ID, name, or array of the same
	 */
	public function revoke( $tokens )
	{
		$tokens = Utils::single_array( $tokens );
		$tokens = array_map( array( 'ACL', 'token_id' ), $tokens );

		foreach ( $tokens as $token ) {
			ACL::revoke_group_token( $this->id, $token );
		}
	}

	/**
	 * Determine whether members of a group can do something.
	 * This function should not be used to determine composite permissions among several groups
	 * @param mixed a permission ID or name
	 * @return boolean If this group has been granted and not denied this permission, return true.  Otherwise, return false.
	 * @see ACL::group_can()
	 * @see ACL::user_can()
	 */
	public function can( $token, $access = 'full' )
	{
		$token = ACL::token_id( $token );
		$this->load_permissions_cache();
		if ( isset( $this->permissions[$token] ) && ACL::access_check( $this->permissions[$token], $access ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Return the access bitmask for a specific token for this group.
	 *
	 * @param string $token The
	 * @return
	 */
	public function get_access( $token )
	{
		$token = ACL::token_id( $token );
		$this->load_permissions_cache();
		if ( isset( $this->permissions[$token] ) ) {
			return ACL::get_bitmask( $this->permissions[$token] );
		}
		return false;
	}

	/**
	 * Returns an array of token ids and bitmask permissions that are associated with this group
	 *
	 * @return array An array of token ids
	 */
	public function get_tokens()
	{
		$this->load_permissions_cache();
		return $this->permissions;
	}

	/**
	 * Clear permissions cache.
	 */
	public function clear_permissions_cache()
	{
		//unset( $this->permissions );
		$this->permissions = null;
	}

	/**
	 * Load permissions cache.
	 */
	public function load_permissions_cache()
	{
		if ( is_null( $this->permissions ) ) {
			if ( $results = DB::get_results( 'SELECT token_id, access_mask FROM {group_token_permissions} WHERE group_id=?', array( $this->id ) ) ) {
				foreach ( $results as $result ) {
					$this->permissions[$result->token_id] = ACL::get_bitmask( $result->access_mask );
				}
			}
		}
	}

	/**
	 * Fetch a group from the database by ID or name.
	 * This is a wrapper for get_by_id() and get_by_name()
	 * @param integer|string|UserGroup $group A group ID, name, or UserGroup
	 * @return UserGroup|boolean UserGroup object, or boolean false
	 */
	public static function get( $group )
	{
		if ( $group instanceof UserGroup ) {
			return $group;
		}
		if ( is_numeric( $group ) ) {
			return self::get_by_id( $group );
		}
		else {
			return self::get_by_name( $group );
		}
	}

	/**
	 * Select a group from the DB by its ID
	 * @param int A group ID
	 * @return mixed A UserGroup object, or boolean false
	 */
	public static function get_by_id( $id )
	{
		return DB::get_row( 'SELECT * FROM {groups} WHERE id=?', array( $id ), 'UserGroup' );
	}

	/**
	 * Select a group from the DB by its name
	 * @param string A group name
	 * @return mixed A UserGroup object, or boolean false
	 */
	public static function get_by_name( $name )
	{
		return DB::get_row( 'SELECT * FROM {groups} WHERE name=?', array( $name ), 'UserGroup' );
	}

	/**
	 * Determine whether a group exists
	 * @param mixed The name or ID of the group
	 * @return bool Whether the group exists or not
	 */
	public static function exists( $group )
	{
		return !is_null( self::id( $group ) );
	}

	/**
	 * Given a group's ID, return its friendly name
	 * @param int a group's ID
	 * @return string the group's name or false if the group doesn't exist
	 */
	public static function name( $id )
	{
		$check_field = is_numeric( $id ) ? 'id' : 'name';
		$name = DB::get_value( "SELECT name FROM {groups} WHERE {$check_field}=?", array( $id ) );
		return $name;  // get_value returns false if no record is returned
	}

	/**
	 * Given a group's name, return its ID
	 * @param string|UserGroup a group's name or a group object
	 * @return int the group's ID, or false if the group doesn't exist
	 */
	public static function id( $name )
	{
		if($name instanceof UserGroup) {
			return $name->id;
		}
		elseif(is_numeric($name)) {
			$check_field = 'id';
		}
		else {
			$check_field = 'name';
		}
		$id = DB::get_value( "SELECT id FROM {groups} WHERE {$check_field}=?", array( $name ) );
		return $id; // get_value returns false if no record is returned
	}

	/**
	 * Determine whether the specified user is a member of the group
	 * @param mixed A user ID or name
	 * @return bool True if the user is in the group, otherwise false
	 */
	public function member( $user_id )
	{
		if ( ! is_numeric( $user_id ) ) {
			$user = User::get( $user_id );
			$user_id = $user->id;
		}

		if ( in_array( $user_id, $this->member_ids ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Cache the member ids that belong to this group
	 *
	 * @param boolean $refresh Optional. If true, refresh the cache
	 */
	protected function load_member_cache( $refresh = false )
	{
		// if we have an ID, load this UserGroup's members & permissions
		if ( $this->id && ( $refresh || !isset( $this->member_ids ) ) ) {
			$this->member_ids = DB::get_column( 'SELECT user_id FROM {users_groups} WHERE group_id= ?', array( $this->id ) );
		}
	}
}
?>

repo_name: DaSourcerer/habari


code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Habari UserGroups Class
 *
 */
class UserGroups extends ArrayObject
{
	protected $get_param_cache; // Stores info about the last set of data fetched that was not a single value

	/**
	 * Returns a group or grops based on supplied parameters.
	 * @todo This class should cache query results!
	 *
	 * @param array $paramarray An associated array of parameters, or a querystring
	 * @return array An array of UserGroup objects, or a single UserGroup object, depending on request
	 */
	public static function get( $paramarray = array() )
	{
		$params = array();
		$fns = array( 'get_results', 'get_row', 'get_value' );
		$select = '';
		// what to select -- by default, everything
		foreach ( UserGroup::default_fields() as $field => $value ) {
			$select .= ( '' == $select )
				? "{groups}.$field"
				: ", {groups}.$field";
		}
		// defaults
		$orderby = 'id ASC';
		$nolimit = true;

		// Put incoming parameters into the local scope
		$paramarray = Utils::get_params( $paramarray );

		// Transact on possible multiple sets of where information that is to be OR'ed
		if ( isset( $paramarray['where'] ) && is_array( $paramarray['where'] ) ) {
			$wheresets = $paramarray['where'];
		}
		else {
			$wheresets = array( array() );
		}

		$wheres = array();
		$join = '';
		if ( isset( $paramarray['where'] ) && is_string( $paramarray['where'] ) ) {
			$wheres[] = $paramarray['where'];
		}
		else {
			foreach ( $wheresets as $paramset ) {
				// safety mechanism to prevent empty queries
				$where = array();
				$paramset = array_merge( (array) $paramarray, (array) $paramset );

				$default_fields = UserGroup::default_fields();
				foreach ( UserGroup::default_fields() as $field => $scrap ) {
					if ( !isset( $paramset[$field] ) ) {
						continue;
					}
					switch ( $field ) {
						case 'id':
							if ( !is_numeric( $paramset[$field] ) ) {
								continue;
							}
						default:
							$where[] = "{$field} = ?";
							$params[] = $paramset[$field];
					}
				}

				if ( count( $where ) > 0 ) {
					$wheres[] = ' (' . implode( ' AND ', $where ) . ') ';
				}
			}
		}

		// Get any full-query parameters
		$possible = array( 'fetch_fn', 'count', 'nolimit', 'limit', 'offset' );
		foreach ( $possible as $varname ) {
			if ( isset( $paramarray[$varname] ) ) {
				$$varname = $paramarray[$varname];
			}
		}

		if ( isset( $fetch_fn ) ) {
			if ( ! in_array( $fetch_fn, $fns ) ) {
				$fetch_fn = $fns[0];
			}
		}
		else {
			$fetch_fn = $fns[0];
		}

		// is a count being request?
		if ( isset( $count ) ) {
			$select = "COUNT($count)";
			$fetch_fn = 'get_value';
			$orderby = '';
		}
		// Are we asking for a single UserGroup or possibly multiple UserGroups
		$single = false;
		if ( isset( $limit ) ) {
			$single = ($limit == 1);
			$limit = " LIMIT $limit";
			if ( isset( $offset ) ) {
				$limit .= " OFFSET $offset";
			}
		}
		if ( isset( $nolimit ) ) {
			$limit = '';
		}

		$query = ' SELECT ' . $select . ' FROM {groups} ' . $join;

		if ( count( $wheres ) > 0 ) {
			$query .= ' WHERE ' . implode( " \nOR\n ", $wheres );
		}
		$query .= ( ($orderby == '') ? '' : ' ORDER BY ' . $orderby ) . $limit;

		DB::set_fetch_mode( PDO::FETCH_CLASS );

		$results = DB::$fetch_fn( $query, $params, 'UserGroup' );

		if ( 'get_results' != $fetch_fn ) {
			// return the results
			return $results;
		}
		elseif ( is_array( $results ) ) {
			$c = __CLASS__;
			$return_value = new $c( $results );
			$return_value->get_param_cache = $paramarray;
			return $return_value;
		}
	}

	/**
	 * Select all groups from the database
	 *
	 * @return Groups
	 */
	public static function get_all()
	{

		$params = array(
			'orderby' => 'name ASC'
			);

		return self::get( $params );
	}

}
?>


code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Habari UserInfo class
 *
 * User metadata
 */
class UserInfo extends InfoRecords
{
	function __construct ( $user_id = null )
	{
		// call parent with appropriate  parameters
		parent::__construct( DB::table( 'userinfo' ), 'user_id', $user_id );
	}
}

?>


code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Habari Users Class
 *
 */
class Users extends ArrayObject
{
	protected $get_param_cache; // Stores info about the last set of data fetched that was not a single value

	/**
	 * Returns a user or users based on supplied parameters.
	 * @todo This class should cache query results!
	 *
	 * @param array $paramarray An associated array of parameters, or a querystring
	 * @return array An array of User objects, or a single User object, depending on request
	 */
	public static function get( $paramarray = array() )
	{
		$params = array();
		$fns = array( 'get_results', 'get_row', 'get_value' );
		$select = '';
		// what to select -- by default, everything
		foreach ( User::default_fields() as $field => $value ) {
			$select .= ( '' == $select )
				? "{users}.$field"
				: ", {users}.$field";
		}
		// defaults
		$orderby = 'id ASC';
		$nolimit = true;

		// Put incoming parameters into the local scope
		$paramarray = Utils::get_params( $paramarray );

		// Transact on possible multiple sets of where information that is to be OR'ed
		if ( isset( $paramarray['where'] ) && is_array( $paramarray['where'] ) ) {
			$wheresets = $paramarray['where'];
		}
		else {
			$wheresets = array( array() );
		}

		$wheres = array();
		$join = '';
		if ( isset( $paramarray['where'] ) && is_string( $paramarray['where'] ) ) {
			$wheres[] = $paramarray['where'];
		}
		else {
			foreach ( $wheresets as $paramset ) {
				// safety mechanism to prevent empty queries
				$where = array();
				$paramset = array_merge( (array) $paramarray, (array) $paramset );

				$default_fields = User::default_fields();

				foreach ( User::default_fields() as $field => $scrap ) {
					if ( !isset( $paramset[$field] ) ) {
						continue;
					}
					switch ( $field ) {
						case 'id':
							if ( !is_numeric( $paramset[$field] ) ) {
								continue;
							}
						default:
							$where[] = "{$field} = ?";
							$params[] = $paramset[$field];
					}
				}

				if ( isset( $paramset['info'] ) && is_array( $paramset['info'] ) ) {
					$join .= 'INNER JOIN {userinfo} ON {users}.id = {userinfo}.user_id';
					foreach ( $paramset['info'] as $info_name => $info_value ) {
						$where[] = '{userinfo}.name = ? AND {userinfo}.value = ?';
						$params[] = $info_name;
						$params[] = $info_value;
					}
				}

				if ( isset( $paramset['group'] ) && is_array( $paramset['group'] ) ) {
					$join .= ' INNER JOIN {users_groups} ON {users}.id = {users_groups}.user_id';
					foreach ( $paramset['group'] as $group ) {
						$group_id = UserGroup::get_by_name( $group )->id;
						$where[] = '{users_groups}.group_id = ?';
						$params[] = $group_id;
					}
				}

				if ( isset( $paramset['not:id'] ) ) {
					if ( is_array( $paramset['not:id'] ) ) {
						array_walk( $paramset['not:id'], function(&$a) {$a = intval( $a );} );
						$where[] = "{users}.id NOT IN (" . implode( ',', array_fill( 0, count( $paramset['not:id'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['not:id'] );
					}
					else {
						$where[] = "{users}.id != ?";
						$params[] = (int) $paramset['not:id'];
					}
				}

				if ( isset( $paramset['criteria'] ) ) {
					if ( isset( $paramset['criteria_fields'] ) ) {
						// Support 'criteria_fields' => 'author,ip' rather than 'criteria_fields' => array( 'author', 'ip' )
						if ( !is_array( $paramset['criteria_fields'] ) && is_string( $paramset['criteria_fields'] ) ) {
							$paramset['criteria_fields'] = explode( ',', $paramset['criteria_fields'] );
						}
					}
					else {
						$paramset['criteria_fields'] = array( 'username' );
					}
					$paramset['criteria_fields'] = array_unique( $paramset['criteria_fields'] );

					// this regex matches any unicode letters (\p{L}) or numbers (\p{N}) inside a set of quotes (but strips the quotes) OR not in a set of quotes
					preg_match_all( '/(?<=")([\p{L}\p{N}]+[^"]*)(?=")|([\p{L}\p{N}]+)/u', $paramset['criteria'], $matches );
					$where_search = array();
					foreach ( $matches[0] as $word ) {
						foreach ( $paramset['criteria_fields'] as $criteria_field ) {
							$where_search[] .= "( LOWER( {users}.$criteria_field ) LIKE ? )";
							$params[] = '%' . MultiByte::strtolower( $word ) . '%';
						}
					}
					if ( count( $where_search ) > 0 ) {
						$where[] = '(' . implode( " \nOR\n ", $where_search ).')';
					}
				}

				if ( count( $where ) > 0 ) {
					$wheres[] = ' (' . implode( ' AND ', $where ) . ') ';
				}
			}
		}

		// Get any full-query parameters
		$possible = array( 'fetch_fn', 'count', 'nolimit', 'limit', 'offset' );
		foreach ( $possible as $varname ) {
			if ( isset( $paramarray[$varname] ) ) {
				$$varname = $paramarray[$varname];
			}
		}

		if ( isset( $fetch_fn ) ) {
			if ( ! in_array( $fetch_fn, $fns ) ) {
				$fetch_fn = $fns[0];
			}
		}
		else {
			$fetch_fn = $fns[0];
		}

		// is a count being request?
		if ( isset( $count ) ) {
			$select = "COUNT($count)";
			$fetch_fn = 'get_value';
			$orderby = '';
		}
		if ( isset( $limit ) ) {
			unset( $nolimit );
			$limit = " LIMIT $limit";
			if ( isset( $offset ) ) {
				$limit .= " OFFSET $offset";
			}
		}
		if ( isset( $nolimit ) ) {
			$limit = '';
		}

		$query = '
			SELECT ' . $select
			. ' FROM {users} '
			. $join;

		if ( count( $wheres ) > 0 ) {
			$query .= ' WHERE ' . implode( " \nOR\n ", $wheres );
		}
		$query .= ( ( $orderby == '' ) ? '' : ' ORDER BY ' . $orderby ) . $limit;
		// Utils::debug($paramarray, $fetch_fn, $query, $params);

		DB::set_fetch_mode( PDO::FETCH_CLASS );
		DB::set_fetch_class( 'User' );
		$results = DB::$fetch_fn( $query, $params, 'User' );

		if ( 'get_results' != $fetch_fn ) {
			// return the results
			return $results;
		}
		elseif ( is_array( $results ) ) {
			$c = __CLASS__;
			$return_value = new $c( $results );
			$return_value->get_param_cache = $paramarray;
			return $return_value;
		}
	}

	/**
	 * Select a user from the database by userinfo
	 *
	 * @param string|array $key
	 * @param string|null $value
	 * @return Users|bool
	 */
	public static function get_by_info( $key, $value = null )
	{
		// If no value was specified, check if several info were passed
		if ( null === $value ) {
			if ( is_array( $key ) ) {
				$params['info'] = $key;
			}
			else {
				// We need a value to compare to
				return false;
			}
		}
		else {
			$params['info'] = array( $key => $value );
		}

		return self::get( $params );
	}

	/**
	 * Select all users from the database
	 *
	 * @return Users
	 */
	public static function get_all()
	{
		$params = array(
			'orderby' => 'username ASC'
		);

		return self::get( $params );
	}

}
?>


code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Habari Utility Class
 *
 */
class Utils
{
	public static $debug_defined = false;

	/**
	 * Utils constructor
	 * This class should not be instantiated.
	 */
	private function __construct()
	{
	}

	/**
	 * function get_params
	 * Returns an associative array of parameters, whether the input value is
	 * a querystring or an associative array.
	 * @param mixed An associative array or querystring parameter list
	 * @return array An associative array of parameters
	 */
	public static function get_params( $params )
	{
		if ( is_array( $params ) || $params instanceof Traversable ) {
			return $params;
		}
		$paramarray = array();
		parse_str( $params, $paramarray );
		return $paramarray;
	}

	/**
	 * function end_in_slash
	 * Forces a string to end in a single slash
	 * @param string A string, usually a path
	 * @return string The string with the slash added or extra slashes removed, but with one slash only
	 */
	public static function end_in_slash( $value )
	{
		return rtrim( $value, '\\/' ) . '/';
	}

	/**
	 * function redirect
	 * Redirects the request to a new URL
	 * @param string $url The URL to redirect to, or omit to redirect to the current url
	 * @param boolean $continue Whether to continue processing the script (default false for security reasons, cf. #749)
	 */
	public static function redirect( $url = '', $continue = false )
	{
		if ( $url == '' ) {
			$url = Controller::get_full_url();
		}
		header( 'Location: ' . $url, true, 302 );

		if ( ! $continue ) exit;
	}

	/**
	 * function atomtime
	 * Returns RFC-3339 time from a time string or integer timestamp
	 * @param mixed A string of time or integer timestamp
	 * @return string An RFC-3339 formatted time
	 */
	public static function atomtime( $t )
	{
		if ( ! is_numeric( $t ) ) {
			$t = strtotime( $t );
		}
		$vdate = date( DATE_ATOM, $t );
		// If the date format used for timezone was O instead of P...
		if ( substr( $vdate, -3, 1 ) != ':' ) {
			$vdate = substr( $vdate, 0, -2 ) . ':' . substr( $vdate, -2, 2 );
		}
		return $vdate;
	}

	/**
	 * function nonce
	 * Returns a random 12-digit hex number
	 */
	public static function nonce()
	{
		return sprintf( '%06x', rand( 0, 16776960 ) ) . sprintf( '%06x', rand( 0, 16776960 ) );
	}

	/**
	 * function WSSE
	 * returns an array of tokens used for WSSE authentication
	 *    http://www.xml.com/pub/a/2003/12/17/dive.html
	 *    http://www.sixapart.com/developers/atom/protocol/atom_authentication.html
	 * @param String a nonce
	 * @param String a timestamp
	 * @return Array an array of WSSE authentication elements
	 */
	public static function WSSE( $nonce = '', $timestamp = '' )
	{
		if ( '' === $nonce ) {
			$nonce = Utils::crypt( Options::get( 'GUID' ) . Utils::nonce() );
		}
		if ( '' === $timestamp ) {
			$timestamp = date( 'c' );
		}
		$user = User::identify();
		$wsse = array(
			'nonce' => $nonce,
			'timestamp' => $timestamp,
			'digest' => base64_encode( pack( 'H*', sha1( $nonce . $timestamp . $user->password ) ) )
		);
		return $wsse;
	}

	/**
	 * function stripslashes
	 * Removes slashes from escaped strings, including strings in arrays
	 */
	public static function stripslashes( $value )
	{
		if ( is_array( $value ) ) {
			$value = array_map( array( 'Utils', 'stripslashes' ), $value );
		}
		elseif ( !empty( $value ) && is_string( $value ) ) {
			$value = stripslashes( $value );
		}
		return $value;
	}

	/**
	 * function addslashes
	 * Adds slashes to escape strings, including strings in arrays
	 */
	public static function addslashes( $value )
	{
		if ( is_array( $value ) ) {
			$value = array_map( array( 'Utils', 'addslashes' ), $value );
		}
		else if ( !empty( $value ) && is_string( $value ) ) {
			$value = addslashes( $value );
		}
		return $value;
	}

	/**
	 * function de_amp
	 * Returns & entities in a URL querystring to their previous & glory, for use in redirects
	 * @param string $value A URL, maybe with a querystring
	 */
	public static function de_amp( $value )
	{
		$url = InputFilter::parse_url( $value );
		$url[ 'query' ] = str_replace( '&', '&', $url[ 'query' ] );
		return InputFilter::glue_url( $url );
	}

	/**
	 * function revert_magic_quotes_gpc
	 * Reverts magicquotes_gpc behavior
	 */
	public static function revert_magic_quotes_gpc()
	{
		/* We should only revert the magic quotes once per page hit */
		static $revert = true;
		if ( get_magic_quotes_gpc() && $revert ) {
			$_GET = self::stripslashes( $_GET );
			$_POST = self::stripslashes( $_POST );
			$_COOKIE = self::stripslashes( $_COOKIE );
			$revert = false;
		}
	}

	/**
	 * function quote_spaced
	 * Adds quotes around values that have spaces in them
	 * @param string A string value that might have spaces
	 * @return string The string value, quoted if it has spaces
	 */
	public static function quote_spaced( $value )
	{
		return ( strpos( $value, ' ' ) === false ) ? $value : '"' . $value . '"';
	}

	/**
	 * function implode_quoted
	 * Behaves like the implode() function, except it quotes values that contain spaces
	 * @param string A separator between each value
	 * @param	array An array of values to separate
	 * @return string The concatenated string
	 */
	public static function implode_quoted( $separator, $values )
	{
		if ( ! is_array( $values ) ) {
			$values = array();
		}
		$values = array_map( array( 'Utils', 'quote_spaced' ), $values );
		return implode( $separator, $values );
	}

	/**
	 * Returns a string of question mark parameter
	 * placeholders.
	 *
	 * Useful when building, for instance, an IN() list for SQL
	 *
	 * @param		count		Number of placeholders to put in the string
	 * @return	string	Placeholder string
	 */
	public static function placeholder_string( $count )
	{
		if ( Utils::is_traversable( $count ) ) {
			$count = count( $count );
		}
		return rtrim( str_repeat( '?,', $count ), ',' );
	}

	/**
	 * function archive_pages
	 * Returns the number of pages in an archive using the number of items per page set in options
	 * @param integer Number of items in the archive
	 * @param integer Number of items per page
	 * @returns integer Number of pages based on pagination option.
	 */
	public static function archive_pages( $item_total, $items_per_page = null )
	{
		if ( $items_per_page ) {
			return ceil( $item_total / $items_per_page );
		}
		return ceil( $item_total / Options::get( 'pagination' ) );
	}

	/**
	* Used with array_map to create an array of PHP stringvar-style search/replace strings using optional pre/postfixes
	*
	* $mapped_values= array_map(array('Utils', 'map_array'), $values);
	*
	* @param string $value The value to wrap
	* @param string $prefix The prefix for the returned value
	* @param string $postfix The postfix for the returned value
	* @return string The wrapped value
	*/
	public static function map_array( $value, $prefix = '{$', $postfix = '}' )
	{
		return $prefix . $value . $postfix;
	}

	/**
	 * Helper function used by debug()
	 * Not for external use.
	 */
	public static function debug_reveal( $show, $hide, $debugid, $close = false )
	{
		$reshow = $restyle = $restyle2 = '';
		if ( $close ) {
			$reshow = "onclick=\"debugtoggle('debugshow-{$debugid}');debugtoggle('debughide-{$debugid}');return false;\"";
			$restyle = "";
			$restyle2 = "";
		}
		return "$show{$restyle}$hide{$restyle2}";
	}

	/**
	 * Outputs a call stack with parameters, and a dump of the parameters passed.
	 * @params mixed Any number of parameters to output in the debug box.
	 */
	public static function debug()
	{
		$debugid = md5( microtime() );
		$tracect = 0;

		$fooargs = func_get_args();
		echo "
";
		if ( !self::$debug_defined ) {
			$output = "

			";
			echo $output;
			self::$debug_defined = true;
		}
		if ( function_exists( 'debug_backtrace' ) ) {
			$output = "
";
			$backtrace = array_reverse( debug_backtrace(), true );
			$odd = '';
			$tracect = 0;
			foreach ( $backtrace as $trace ) {
				$file = $line = $class = $type = $function = '';
				$args = array();
				extract( $trace );
				if ( isset( $class ) ) $fname = $class . $type . $function; else $fname = $function;
				if ( !isset( $file ) || $file=='' ) $file = '[Internal PHP]'; else $file = basename( $file );
				$odd = $odd == '' ? 'class="utils__odd"' : '';
				$output .= "
{$file} ({$line}):	{$fname}(";
				$comma = '';
				foreach ( (array)$args as $arg ) {
					$tracect++;
					$argout = print_r( $arg, 1 );
					$output .= $comma . Utils::debug_reveal( gettype( $arg ), htmlentities( $argout ), $debugid . $tracect, true );
					$comma = ', ';
				}
				$output .= ");
";
			}
			$output .= "
";
			echo Utils::debug_reveal( 'Call Stack', $output, $debugid );
		}
		echo "
";
		foreach ( $fooargs as $arg1 ) {
			echo '' . gettype( $arg1 ) . ' ';
			if ( gettype( $arg1 ) == 'boolean' ) {
				echo htmlentities( var_export( $arg1 ) ) . '
';
			}
			else {
				echo htmlentities( print_r( $arg1, true ) ) . "
";
			}
		}
		echo "

";
	}

	/**
	 * Outputs debug information like ::debug() but using Firebug's Console.
	 * @params mixed Any number of parameters to output in the debug box.
	 */
	public static function firedebug()
	{
		$fooargs = func_get_args();
		$output = "";
		echo $output;
	}

	/**
	 * Utils::firebacktrace()
	 *
	 * @param array $backtrace An array of backtrace details from debug_backtrace()
	 * @return string Javascript output that will display the backtrace in the Firebug console.
	 */
	public static function firebacktrace( $backtrace )
	{
		$output = '';
		extract( end( $backtrace ) );
		if ( isset( $class ) ) $fname = $class . $type . $function; else $fname = $function;
		if ( !isset( $file ) || $file=='' ) $file = '[Internal PHP]'; else $file = basename( $file );
		$output .= "console.group(\"%s(%s):  %s(…)\", \"" . basename( $file ) . "\", \"{$line}\", \"{$fname}\");\n";
		foreach ( $backtrace as $trace ) {
			$file = $line = $class = $type = $function = '';
			$args = array();
			extract( $trace );
			if ( isset( $class ) ) $fname = $class . $type . $function; else $fname = $function;
			if ( !isset( $file ) || $file=='' ) $file = '[Internal PHP]'; else $file = basename( $file );

			$output .= "console.group(\"%s(%s):  %s(%s)\", \"{$file}\", \"{$line}\", \"{$fname}\", \"";

			$output2 = $comma = $argtypes = '';
			foreach ( (array)$args as $arg ) {
				$argout = str_replace( "\n", '\n', addslashes( print_r( $arg, 1 ) ) );
				//$output .= $comma . Utils::debug_reveal( gettype($arg), htmlentities($argout), $debugid . $tracect, true );
				$argtypes .= $comma . gettype( $arg );
				$output2 .= "console.log(\"$argout\");\n";
				$comma = ', ';
			}
			$argtypes = trim( $argtypes );
			$output .= "{$argtypes}\");\n{$output2}";
			$output .= "console.groupEnd();\n";
			//$output .= ");
";
		}
		return $output;
	}

	/**
	 * Crypt a given password, or verify a given password against a given hash.
	 *
	 * @todo Enable best algo selection after DB schema change.
	 *
	 * @param string $password the password to crypt or verify
	 * @param string $hash (optional) if given, verify $password against $hash
	 * @return crypted password, or boolean for verification
	 */
	public static function crypt( $password, $hash = null )
	{
		if ( $hash == null ) {
			return self::ssha512( $password, $hash );
		}
		elseif ( strlen( $hash ) > 3 ) { // need at least {, } and a char :p
			// verify
			if ( $hash{0} == '{' ) {
				// new hash from the block
				$algo = strtolower( substr( $hash, 1, strpos( $hash, '}', 1 ) - 1 ) );
				switch ( $algo ) {
					case 'sha1':
					case 'ssha':
					case 'ssha512':
					case 'md5':
						return self::$algo( $password, $hash );
					default:
						Error::raise( _t( 'Unsupported digest algorithm "%s"', array( $algo ) ) );
						return false;
				}
			}
			else {
				// legacy sha1
				return ( sha1( $password ) == $hash );
			}
		}
		else {
			Error::raise( _t( 'Invalid hash' ) );
		}
	}

	/**
	 * Crypt or verify a given password using SHA.
	 *
	 * Passwords should not be stored using this method, but legacy systems might require it.
	 */
	public static function sha1( $password, $hash = null )
	{
		$marker = '{SHA1}';
		if ( $hash == null ) {
			return $marker . sha1( $password );
		}
		else {
			return ( sha1( $password ) == substr( $hash, strlen( $marker ) ) );
		}
	}

	/**
	 * Crypt or verify a given password using MD5.
	 *
	 * Passwords should not be stored using this method, but legacy systems might require it.
	 */
	public static function md5( $password, $hash = null )
	{
		$marker = '{MD5}';
		if ( $hash == null ) {
			return $marker . md5( $password );
		}
		else {
			return ( md5( $password ) == substr( $hash, strlen( $marker ) ) );
		}
	}

	/**
	 * Crypt or verify a given password using SSHA.
	 * Implements the {Seeded,Salted}-SHA algorithm as per RfC 2307.
	 *
	 * @param string $password the password to crypt or verify
	 * @param string $hash (optional) if given, verify $password against $hash
	 * @return crypted password, or boolean for verification
	 */
	public static function ssha( $password, $hash = null )
	{
		$marker = '{SSHA}';
		if ( $hash == null ) { // encrypt
			// create salt (4 byte)
			$salt = '';
			for ( $i = 0; $i < 4; $i++ ) {
				$salt .= chr( mt_rand( 0, 255 ) );
			}
			// get digest
			$digest = sha1( $password . $salt, true );
			// b64 for storage
			return $marker . base64_encode( $digest . $salt );
		}
		else { // verify
			// is this a SSHA hash?
			if ( ! substr( $hash, 0, strlen( $marker ) ) == $marker ) {
				Error::raise( _t( 'Invalid hash' ) );
				return false;
			}
			// cut off {SSHA} marker
			$hash = substr( $hash, strlen( $marker ) );
			// b64 decode
			$hash = base64_decode( $hash );
			// split up
			$digest = substr( $hash, 0, 20 );
			$salt = substr( $hash, 20 );
			// compare
			return ( sha1( $password . $salt, true ) == $digest );
		}
	}

	/**
	 * Crypt or verify a given password using SSHA512.
	 * Implements a modified version of the {Seeded,Salted}-SHA algorithm
	 * from RfC 2307, using SHA-512 instead of SHA-1.
	 *
	 * Requires the new hash*() functions.
	 *
	 * @param string $password the password to crypt or verify
	 * @param string $hash (optional) if given, verify $password against $hash
	 * @return crypted password, or boolean for verification
	 */
	public static function ssha512( $password, $hash = null )
	{
		$marker = '{SSHA512}';
		if ( $hash == null ) { // encrypt
			$salt = '';
			for ( $i = 0; $i < 4; $i++ ) {
				$salt .= chr( mt_rand( 0, 255 ) );
			}
			$digest = hash( 'sha512', $password . $salt, true );
			return $marker . base64_encode( $digest . $salt );
		}
		else { // verify
			if ( ! substr( $hash, 0, strlen( $marker ) ) == $marker ) {
				Error::raise( _t( 'Invalid hash' ) );
				return false;
			}
			$hash = substr( $hash, strlen( $marker ) );
			$hash = base64_decode( $hash );
			$digest = substr( $hash, 0, 64 );
			$salt = substr( $hash, 64 );
			return ( hash( 'sha512', $password . $salt, true ) == $digest );
		}
	}

	/**
	 * Return an array of date information
	 * Just like getdate() but also returns 0-padded versions of day and month in mday0 and mon0
	 * @param integer $timestamp A unix timestamp
	 * @return array An array of date data
	 */
	public static function getdate( $timestamp )
	{
		$info = getdate( $timestamp );
		$info[ 'mon0' ] = substr( '0' . $info[ 'mon' ], -2, 2 );
		$info[ 'mday0' ] = substr( '0' . $info[ 'mday' ], -2, 2 );
		return $info;
	}

	/**
	 * Return a formatted date/time trying to use strftime() AND date()
	 * @param string $format The format for the date.  If it contains non-escaped percent signs, it uses strftime(),	otherwise date()
	 * @param integer $timestamp The unix timestamp of the time to format
	 * @return string The formatted time
	 */
	public static function locale_date( $format, $timestamp )
	{
		$matches = preg_split( '/((?<!\\\\)%[a-z]\\s*)/iu', $format, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
		$output = '';
		foreach ( $matches as $match ) {
			if ( $match{0} == '%' ) {
				$output .= strftime( $match, $timestamp );
			}
			else {
				$output .= date( $match, $timestamp );
			}
		}
		return $output;
	}

	/**
	 * Return a sanitized slug, replacing non-alphanumeric characters to dashes
	 * @param string $string The string to sanitize. Non-alphanumeric characters will be replaced by dashes
	 * @param string $separator The slug separator, '-' by default
	 * @return string The sanitized slug
	 */
	public static function slugify( $string, $separator = '-' )
	{
		// Decode HTML entities
		// Replace non-alphanumeric characters to dashes. Exceptions: %, _, -
		// Note that multiple separators are collapsed automatically by the preg_replace.
		// Convert all characters to lowercase.
		// Trim spaces on both sides.
		$slug = rtrim( MultiByte::strtolower( preg_replace( '/[^\p{L}\p{N}_]+/u', $separator, preg_replace( '/\p{Po}/u', '', html_entity_decode( $string ) ) ) ), $separator );
		// Let people change the behavior.
		$slug = Plugins::filter( 'slugify', $slug, $string );

		return $slug;
	}

	/**
	 * Create an HTML select tag with options and a current value
	 *
	 * @param string $name The name and id of the select control
	 * @param array $options An associative array of values to use as the select options
	 * @param string $current The value of the currently selected option
	 * @param array $properties An associative array of additional properties to assign to the select control
	 * @return string The select control markup
	 */
	public static function html_select( $name, $options, $current = null, $properties = array() )
	{
		$id = isset($properties['id']) ? $properties['id'] : $name;
		$output = '<select id="' . $id . '" name="' . $name . '"';
		foreach ( $properties as $key => $value ) {
			$output .= " {$key}=\"{$value}\"";
		}
		$output .= ">\n";
		foreach ( $options as $value => $text ) {
			$output .= '<option value="' . $value . '"';
			if ( $current == (string)$value ) {
				$output .= ' selected="selected"';
			}
			$output .= '>' . $text . "</option>\n";
		}
		$output .= '</select>';
		return $output;
	}

	/**
	 * Creates one or more HTML checkboxes
	 * @param string The name of the checkbox element.  If there are
	 *	multiple checkboxes for the same name, this method will
	 *	automatically apply "[]" at the end of the name
	 * @param array An array of checkbox options.  Each element should be
	 *	an array containing "name" and "value".  If the checkbox
	 *	should be checked, it should have a "checked" element.
	 * @return string The HTML of the checkboxes
	 */
	public static function html_checkboxes( $name, $options )
	{
		$output = '';
		$multi = false;
		if ( count( $options > 1 ) ) {
			$multi = true;
		}
		foreach ( $options as $option ) {
			$output .= '<input type="checkbox" id="' . $option[ 'name' ] . '" name="' . $option[ 'name' ];
			if ( $multi ) {
				$output .= '[]';
			}
			$output .= '" value="' . $option[ 'value' ] . '"';
			if ( isset( $option[ 'checked' ] ) ) {
				$output .= ' checked';
			}
			$output .= '>';
		}
		return $output;
	}

	/**
	 * Trims longer phrases to shorter ones with elipsis in the middle
	 * @param string The string to truncate
	 * @param integer The length of the returned string
	 * @param bool Whether to place the ellipsis in the middle (true) or
	 * at the end (false)
	 * @return string The truncated string
	 */
	public static function truncate( $str, $len = 10, $middle = true )
	{
		// make sure $len is a positive integer
		if ( ! is_numeric( $len ) || ( 0 > $len ) ) {
			return $str;
		}
		// if the string is less than the length specified, bail out
		if ( MultiByte::strlen( $str ) <= $len ) {
			return $str;
		}

		// okay.  Shuold we place the ellipse in the middle?
		if ( $middle ) {
			// yes, so compute the size of each half of the string
			$len = round( ( $len - 3 ) / 2 );
			// and place an ellipse in between the pieces
			return MultiByte::substr( $str, 0, $len ) . '…' . MultiByte::substr( $str, -$len );
		}
		else {
			// no, the ellipse goes at the end
			$len = $len - 3;
			return MultiByte::substr( $str, 0, $len ) . '…';
		}
	}

	/**
	 * Check the PHP syntax of the specified code.
	 * Performs a syntax (lint) check on the specified code testing for scripting errors.
	 *
	 * @param string $code The code string to be evaluated. It does not have to contain PHP opening tags.
	 * @return bool Returns true if the lint check passed, and false if the link check failed.
	 */
	public static function php_check_syntax( $code, &$error = null )
	{
		$b = 0;

		foreach ( token_get_all( $code ) as $token ) {
			if ( is_array( $token ) ) {
				$token = token_name( $token[0] );
			}
			switch ( $token ) {
				case 'T_CURLY_OPEN':
				case 'T_DOLLAR_OPEN_CURLY_BRACES':
				case 'T_CURLY_OPENT_VARIABLE': // This is not documented in the manual. (11.05.07)
				case '{':
					++$b;
					break;
				case '}':
					--$b;
					break;
			}
		}

		if ( $b ) {
			$error = _t( 'Unbalanced braces.' );
			return false; // Unbalanced braces would break the eval below
		}
		else {
			ob_start(); // Catch potential parse error messages
			$display_errors = ini_set( 'display_errors', 'on' ); // Make sure we have something to catch
			$error_reporting = error_reporting( E_ALL ^ E_NOTICE );
			$code = eval( ' if (0){' . $code . '}' ); // Put $code in a dead code sandbox to prevent its execution
			ini_set( 'display_errors', $display_errors ); // be a good citizen
			error_reporting( $error_reporting );
			$error = ob_get_clean();

			return false !== $code;
		}
	}

	/**
	 * Check the PHP syntax of (and execute) the specified file.
	 *
	 * @see Utils::php_check_syntax()
	 */
	public static function php_check_file_syntax( $file, &$error = null )
	{
		// Prepend and append PHP opening tags to prevent eval() failures.
		$code = ' ?>' . file_get_contents( $file ) . '<?php ';

		return self::php_check_syntax( $code, $error );
	}

	/**
	 * Replacement for system glob that returns an empty array if there are no results
	 *
	 * @param string $pattern The glob() file search pattern
	 * @param integer $flags Standard glob() flags
	 * @return array An array of result files, or an empty array if no results found
	 */
	public static function glob( $pattern, $flags = 0 )
	{
		if ( ! defined( 'GLOB_NOBRACE' ) || ! ( ( $flags & GLOB_BRACE ) == GLOB_BRACE ) ) {
			// this platform supports GLOB_BRACE out of the box or GLOB_BRACE wasn't requested
			$results = glob( $pattern, $flags );
		}
		elseif ( ! preg_match_all( '/\{.*?\}/', $pattern, $m ) ) {
			// GLOB_BRACE used, but this pattern doesn't even use braces
			$results = glob( $pattern, $flags ^ GLOB_BRACE );
		}
		else {
			// pattern uses braces, but platform doesn't support GLOB_BRACE
			$braces = array();
			foreach ( $m[0] as $raw_brace ) {
				$braces[ preg_quote( $raw_brace ) ] = '(?:' . str_replace( ',', '|', preg_quote( substr( $raw_brace, 1, -1 ), '/' ) ) . ')';
			}
			$new_pattern = preg_replace( '/\{.*?\}/', '*', $pattern );
			$pattern = preg_quote( $pattern, '/' );
			$pattern = str_replace( '\\*', '.*', $pattern );
			$pattern = str_replace( '\\?', '.', $pattern );
			$regex = '/' . str_replace( array_keys( $braces ), array_values( $braces ), $pattern ) . '/';
			$results = preg_grep( $regex, Utils::glob( $new_pattern, $flags ^ GLOB_BRACE ) );
		}

		if ( $results === false ) $results = array();
		return $results;
	}

	/**
	 * Produces a human-readable size string.
	 * For example, converts 12345 into 12.34KB
	 *
	 * @param integer $bytesize Number of bytes
	 * @return string Human-readable string
	 */
	public static function human_size( $bytesize )
	{
		$sizes = array(
			' bytes',
			'KiB',
			'MiB',
			'GiB',
			'TiB',
			'PiB'
			);
		$tick = 0;
		$max_tick = count( $sizes ) - 1;
		while ( $bytesize > 1024 && $tick < $max_tick ) {
			$tick++;
			$bytesize /= 1024;
		}

		return sprintf( '%0.2f%s', $bytesize, $sizes[ $tick ] );
	}

	/**
	 * Convert a single non-array variable into an array with that one element
	 *
	 * @param mixed $element Some value, either an array or not
	 * @return array Either the original array value, or the passed value as the single element of an array
	 */
	public static function single_array( $element )
	{
		if ( !is_array( $element ) && !$element instanceof Traversable ) {
			return array( $element );
		}
		return $element;
	}

	/**
	 * Return the mimetype of a file
	 *
	 * @param string $filename the path of a file
	 * @return string The mimetype of the file.
	 */
	public static function mimetype( $filename )
	{
		$mimetype = null;
		if ( function_exists( 'finfo_open' ) && file_exists($filename) ) {
			$finfo = finfo_open( FILEINFO_MIME );
			$mimetype = finfo_file( $finfo, $filename );
			/* FILEINFO_MIME Returns the mime type and mime encoding as defined by RFC 2045.
			 * So only return the mime type, not the encoding.
			 */
			if ( ( $pos = strpos( $mimetype, ';' ) ) !== false ) {
				$mimetype = substr( $mimetype, 0, $pos );
			}
			finfo_close( $finfo );
		}

		if ( empty( $mimetype ) ) {
			$pi = pathinfo( $filename );
			switch ( strtolower( $pi[ 'extension' ] ) ) {
				// hacky, hacky, kludge, kludge...
				case 'jpg':
				case 'jpeg':
					$mimetype = 'image/jpeg';
					break;
				case 'gif':
					$mimetype = 'image/gif';
					break;
				case 'png':
					$mimetype = 'image/png';
					break;
				case 'mp3':
					$mimetype = 'audio/mpeg3';
					break;
				case 'wav':
					$mimetype = 'audio/wav';
					break;
				case 'mpg':
				case 'mpeg':
					$mimetype = 'video/mpeg';
					break;
				case 'swf':
					$mimetype = 'application/x-shockwave-flash';
					break;
				case 'htm':
				case 'html':
				$mimetype = 'text/html';
				break;
			}
		}
		$mimetype = Plugins::filter( 'get_mime_type', $mimetype, $filename );
		return $mimetype;
	}

	/**
	 * Returns a trailing slash or a string, depending on the value passed in
	 *
	 * @param mixed $value A trailing string value
	 * @return string A slash if true, the value if value passed, emptystring if false
	 */
	public static function trail( $value = false )
	{
		if ( $value === true ) {
			return '/';
		}
		elseif ( $value ) {
			return $value;
		}
		return '';
	}

	/**
	 * Send email
	 *
	 * @param string $to The destination address
	 * @param string $subject The subject of the message
	 * @param string $message The message itself
	 * @param array $headers An array of key=>value pairs for additional email headers
	 * @param string $parameters Additional parameters to mail()
	 * @return boolean True if sending the message succeeded
	 */
	public static function mail( $to, $subject, $message, $headers = array(), $parameters = '' )
	{
		$mail = array(
			'to' => $to,
			'subject' => $subject,
			'message' => $message,
			'headers' => $headers,
			'parameters' => $parameters,
		);
		$mail = Plugins::filter( 'mail', $mail );

		$handled = false;
		$handled = Plugins::filter( 'send_mail', $handled, $mail );
		if ( $handled ) {
			return true;
		}
		else {
			$additional_headers = array();
			foreach ( $headers as $header_key => $header_value ) {
				$header_key = trim( $header_key );
				$header_value = trim( $header_value );
				if ( strpos( $header_key . $header_value, "\n" ) === false ) {
					$additional_headers[] = "{$header_key}: {$header_value}";
				}
			}
			$additional_headers = implode( "\r\n", $additional_headers );
		}
		return mail( $to, $subject, $message, $additional_headers, $parameters );
	}

	/**
	 * Create a random password of a specific length
	 *
	 * @param integer $length Length of the password, if not provded, 10
	 * @return string A random password
	 */
	public static function random_password( $length = 10 )
	{
		$password = '';
		$character_set = '1234567890!@#$^*qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVNBM';
		$data = str_split( $character_set );
		for ( $i = 0; $i < $length; $i++ ) {
			$password .= $data[rand( 1, strlen( $character_set ) ) - 1];
		}
		return $password;
	}

	/**
	 * Does a bitwise OR of all the numbers in an array
	 * @param array $input An array of integers
	 * @return int The bitwise OR of the input array
	 */
	public static function array_or( $input )
	{
		return array_reduce( $input, array( 'Utils', 'ror' ), 0 );
	}

	/**
	 * Helper function for array_or
	 */
	public static function ror( $v, $w )
	{
		return $v |= $w;
	}

	/**
	 * Checks whether the correct HTTP method was used for the request
	 *
	 * @param array $expected Expected HTTP methods for the request
	 */
	public static function check_request_method( $expected )
	{
		if ( !in_array( $_SERVER['REQUEST_METHOD'], $expected ) ) {
			if ( in_array( $_SERVER['REQUEST_METHOD'], array( 'GET', 'HEAD', 'POST', 'PUT', 'DELETE' ) ) ) {
				header( 'HTTP/1.1 405 Method Not Allowed', true, 405 );
			}
			else {
				header( 'HTTP/1.1 501 Method Not Implemented', true, 501 );
			}
			header( 'Allow: ' . implode( ',', $expected ) );
			exit;
		}
	}

	/**
	 * Returns a regex pattern equivalent to the given glob pattern
	 *
	 * @return string regex pattern with '/' delimiter
	 */
	public static function glob_to_regex( $glob )
	{
		$pattern = $glob;
		// braces need more work
		$braces = array();
		if ( preg_match_all( '/\{.*?\}/', $pattern, $m ) ) {
			foreach ( $m[0] as $raw_brace ) {
				$braces[ preg_quote( $raw_brace ) ] = '(?:' . str_replace( ',', '|', preg_quote( substr( $raw_brace, 1, -1 ), '/' ) ) . ')';
			}
		}
		$pattern = preg_quote( $pattern, '/' );
		$pattern = str_replace( '\\*', '.*', $pattern );
		$pattern = str_replace( '\\?', '.', $pattern );
		$pattern = str_replace( array_keys( $braces ), array_values( $braces ), $pattern );
		return '/' . $pattern . '/';
	}

	/**
	 * Return the port used for a specific URL scheme
	 *
	 * @param string $scheme The scheme in question
	 * @return integer the port used for the scheme
	 */
	public static function scheme_ports( $scheme = null )
	{
		$scheme_ports = array(
			'ftp' => 21,
			'ssh' => 22,
			'telnet' => 23,
			'http' => 80,
			'pop3' => 110,
			'nntp' => 119,
			'news' => 119,
			'irc' => 194,
			'imap3' => 220,
			'https' => 443,
			'nntps' => 563,
			'imaps' => 993,
			'pop3s' => 995,
		);
		if ( is_null( $scheme ) ) {
			return $scheme_ports;
		}
		return $scheme_ports[ $scheme ];
	}

	/**
	 * determines if the given that is travesable in foreach
	 *
	 * @param mixed $data
	 * @return bool
	 */
	public static function is_traversable( $data )
	{
		return ( is_array( $data ) || ( $data instanceof Traversable && $data instanceof Countable ) );
	}

	/**
	* Get the remote IP address, but try and take into account users who are
	* behind proxies, whether they know it or not.
	* @return The client's IP address.
	*/
	public static function get_ip( $default = '0.0.0.0' )
	{
		// @todo in particular HTTP_X_FORWARDED_FOR could be a comma-separated list of IPs that have handled it, the client being the left-most. we should handle that...
		$keys = array( 'HTTP_CLIENT_IP', 'HTTP_FORWARDED', 'HTTP_X_FORWARDED', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_CLUSTER_CLIENT_IP', 'REMOTE_ADDR' );

		$return = '';
		foreach ( $keys as $key ) {
			if ( isset( $_SERVER[ $key ] ) ) {
				$return = $_SERVER[ $key ];
			}
		}

		// make sure whatever IP we got was valid
		$valid = filter_var( $return, FILTER_VALIDATE_IP );

		if ( $valid === false ) {
			return $default;
		}
		else {
			return $return;
		}

	}

	/**
	* Call htmlspecialchars() with the correct flags and encoding,
	*  without double escaping strings.
	* See http://php.net/manual/en/function.htmlspecialchars.php for details on the parameters
	* and purpose of the function.
	*
	* @todo Should htmlspecialchars_decode() be used instead of html_entity_decode()?
	*
	* @param $string. string. The string to escape
	* @param $quote_flag. integer. Sets what quotes and doublequotes are escaped
	* @param $encoding. string. The encoding of the passed string
	*
	* @return The escaped string
	*/
	public static function htmlspecialchars( $string, $quote_flag = ENT_COMPAT, $encoding = 'UTF-8' )
	{
		return htmlspecialchars( html_entity_decode( $string, ENT_QUOTES, $encoding ), $quote_flag, $encoding );
	}

	/**
	* Convenience function to find a usable PCRE regular expression
	* delimiter for a particular string.  (I.e., some character that
	* *isn't* found in the string.)
	*
	* @param $string. string. The string for which to find a delimiter.
	* @param $choices. string. Delimiters from which to choose one.
	* @param $encoding. string. The encoding of the passed string
	*
	* @return A valid regex delimiter, or null if none of the choices work.
	*/
	public static function regexdelim( $string, $choices = null )
	{
		/*
		 * Supply some default possibilities for delimiters if we
		 * weren't given an explicit list.
		 */
		if ( ! isset( $choices ) ) {
			$choices = sprintf( '%c%c%c%c%c%c%c',
				167, /* § */
				164, /* ¤ */
				165, /* ¥ */
				ord( '`' ),
				ord( '~' ),
				ord( '%' ),
				ord( '#' )
			);
		}
		$a_delims = str_split( $choices );
		/*
		 * Default condition is 'we didn't find one.'
		 */
		$delim = null;
		/*
		 * Check for each possibility by scanning the text for it.
		 * If it isn't found, it's a valid choice, so break out of the
		 * loop.
		 */
		foreach ( $a_delims as $tdelim ) {
			if ( ! strstr( $string, $tdelim ) ) {
				$delim = $tdelim;
				break;
			}
		}
		return $delim;
	}

	/**
	 * Create a list of html element attributes from an associative array
	 *
	 * @param array $attrs An associative array of parameters
	 * @return string The parameters turned into a string of tag attributes
	 */
	public static function html_attr($attrs)
	{
		$out = '';
		foreach($attrs as $key => $value) {
			$value = is_array($value) ? implode(' ', $value) : $value;
			if($value != '') {
				$out .= ($out == '' ? '' : ' ') . $key . '="' . Utils::htmlspecialchars($value) . '"';
			}
		}
		return $out;
	}

	/**
	 * Get a list of the PHP ini settings relevant to Habari
	 *
	 * @return Array The relevant PHP ini settings as array of strings
	 */
	public static function get_ini_settings()
	{
		$settings = array();
		$keys = array(
			'safe_mode',
			'open_basedir',
			'display_errors',
			'session.gc_probability',
			'session.gc_maxlifetime',
			'error_reporting',
			'memory_limit',
		);
		foreach($keys as $key ) {
			$val = ini_get( $key );
			if ( $val === false ) {
				$settings[] = $key . ': ' . _t( 'Not set' );
			}
			else {
				$settings[] = $key . ': ' . ( strlen( $val ) ? $val : '0' );
			}
		}
		return $settings;
	}

	/**
	 * Are we in a testing environment?
	 * @static
	 * @param string $key The querystring key that can specify the environment to use
	 * @return bool True if this is a test environment.
	 */
	public static function env_test($key = '_useenv')
	{
		return Utils::env_is('test', $key);
	}

	/**
	 * Are we in a specific environment?
	 * @static
	 * @param string $env The environment to test for
	 * @param string $key The querystring key that can specify the environment to use
	 * @return bool True if this is a test environment.
	 */
	public static function env_is($env, $key = '_useenv')
	{
		if(defined('UNIT_TEST')) {
			$_GET[$key] = is_string(UNIT_TEST) ? UNIT_TEST : 'test';
		}
		if(
			(isset($_GET[$key]) && $_GET[$key] == $env) ||
			(isset($_COOKIE[$key]) && $_COOKIE[$key] == $env && (!isset($_GET[$key]) || $_GET[$key] == $env))
		) {
			setcookie($key, $env);
			return true;
		}
		else {
			setcookie($key, false);
			return false;
		}
	}

	/**
	 * Given an array of arrays, return an array that contains the value of a particular common field
	 * Example:
	 * $a = array(
	 *   array('foo'=>1, 'bar'=>2),
	 *   array('foo'=>3, 'bar'=>4),
	 * );
	 * $b = Utils::array_map_field($a, 'foo'); // $b = array(1, 3);
	 *
	 * @param Traversable $array An array of arrays or objects with similar keys or properties
	 * @param string $field The name of a common field within each array/object
	 * @return array An array of the values of the specified field within each array/object
	 */
	public static function array_map_field($array, $field, $key = null)
	{
		if(count($array) == 0) {
			return $array;
		}
		if(is_null($key)) {
			if($array instanceof ArrayObject) {
				$array = $array->getArrayCopy();
			}
			return array_map( function( $element ) use ($field) {
				return is_array($element) ? $element[$field] : (is_object($element) ? $element->$field : null);
			}, $array);
		}
		else {
			return array_combine(
				Utils::array_map_field($array, $key),
				Utils::array_map_field($array, $field)
			);
		}
	}

	/**
	 * Replace shortcodes in content with shortcode output
	 * @static
	 * @param string $content The content within which to replace shortcodes
	 * @param Object $obj_context The object context in which the content was found
	 * @return string The content with shortcodes replaced
	 */
	public static function replace_shortcodes($content, $obj_context)
	{
		$regex = '%\[(\w+?)(?:\s+([^\]]+?))?/\]|\[(\w+?)(?:\s+(.+?))?(?<!/)](?:(.*?)\[/\3])%si';
		if(preg_match_all($regex, $content, $match_set, PREG_SET_ORDER)) {
			foreach($match_set as $matches) {
				$matches = array_pad($matches, 6, '');
				$code = $matches[1] . $matches[3];
				$attrs = $matches[2] . $matches[4];
				$code_contents = $matches[5];
				if(preg_match($regex, $code_contents)) {
					$code_contents = self::replace_shortcodes($code_contents, $obj_context);
				}
				preg_match_all('#(\w+)\s*=\s*(?:(["\'])?(.*?)\2|(\S+))#i', $attrs, $attr_match, PREG_SET_ORDER);
				$attrs = array();
				foreach($attr_match as $attr) {
					$attr = array_pad($attr, 5, '');
					$attrs[$attr[1]] = $attr[3] . $attr[4];
				}
				$replacement = Plugins::filter('shortcode_' . $code, $matches[0], $code, $attrs, $code_contents, $obj_context);
				$content = str_replace($matches[0], $replacement, $content);
			}
		}
		return $content;
	}
}
?>


code:
<?php
/**
 * @package Habari
 *
 */

/**
 * Class to create and format UUIDs.
 *
 */
class UUID
{
	private $uuid = array();

	/**
	 * Create a UUID (Universally Unique IDentifier) as per RfC 4122.
	 *
	 * Currently, only version 4 UUIDs are supported (Section 4.4,
	 * "Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers").
	 *
	 * @param int $version UUID version to generate (currently, only version 4 is supported)
	 */
	public function __construct( $version = 4 )
	{
		$uuid = array();
		for ( $i = 0; $i < 16; $i++ ) {
			$uuid[] = mt_rand( 0, 255 );
		}
		// variant (bit 6 = 1, bit 7 = 0)
		$uuid[8] = ( $uuid[8] & 0x3f ) | 0x80;
		/* // weird byte orders make my head hurt!
		// version (bits 4-7 = 0100);
		$uuid[7] = ( $uuid[7] & 0x0f ) | 0x40;
		*/
		// version (bits 12-15 = 0100)
		$uuid[6] = ( $uuid[6] & 0x0f ) | 0x40;

		$this->uuid = $uuid;
	}

	/**
	 * @return a string representation of this object.
	 */
	public function __toString()
	{
		return $this->get_hex();
	}

	/**
	 * @return the generated UUID as an array of bytes
	 */
	public function get_array()
	{
		return $this->uuid;
	}

	/**
	 * @return the generated UUID as a string of bytes
	 */
	public function get_raw()
	{
		return implode( '', array_map( 'chr', $this->uuid ) );
	}

	/**
	 * @return the canonical hexadecimal representation of the generated UUID
	 */
	public function get_hex()
	{
		$uuid_hex = '';
		for ( $i = 0; $i < 16; $i++ ) {
			if ( 4==$i || 6==$i || 8==$i || 10==$i ) {
				$uuid_hex.= '-';
			}
			$uuid_hex.= sprintf( '%02x', $this->uuid[$i] );
		}

		return $uuid_hex;
	}

	/**
	 * Create a UUID and return its canonical hexadecimal representation.
	 *
	 * @return the canonical hexadecimal representation of the generated UUID
	 */
	public static function get()
	{
		$uuid = new UUID();
		return $uuid->get_hex();
	}

}

?>


code:
<?php
/**
 * @package Habari
 *
 */

/**
* Habari Version Class
*
* Base class for managing metadata about various Habari objects
*
*/
class Version
{
	// DB and API versions are incremented by one as the DB structure or API change
	const DB_VERSION = 5101;
	const API_VERSION = 4957;

	const HABARI_VERSION = '0.9-alpha';

	/**
	 * Get the database version
	 *
	 * @return integer The revision in which the most recent database change took place
	 */
	public static function get_dbversion()
	{
		return Version::DB_VERSION;
	}

	/**
	 * Get the API version
	 *
	 * @return integer The revision in which the most recent API change took place
	 */
	public static function get_apiversion()
	{
		return Version::API_VERSION;
	}

	/**
	 * Get the Habari version
	 *
	 * @return string A version_compare()-compatible string of this version of Habari
	 * @see version_compare
	 */
	public static function get_habariversion()
	{
		return Version::HABARI_VERSION;
	}

	/**
	 * Determine whether this might possibly have a .git directory, based solely on the existence of a hyphen in the version string.
	 *
	 * @return boolean True if this is a development version, false if not
	 */
	public static function is_devel()
	{
		return strpos( Version::HABARI_VERSION, '-' ) !== false;
	}

	/**
	 * Store the current database version in the options table
	 */
	public static function save_dbversion()
	{
		Options::set( 'db_version', Version::DB_VERSION );
	}

	/**
	 * Determine if the database needs to be updated based on the source database version being newer than the schema last applied to the database
	 *
	 * @return boolean True if an update is needed
	 */
	public static function requires_upgrade()
	{
		if ( Options::get( 'db_version' ) < Version::DB_VERSION ) {
			return true;
		}
		return false;
	}

	/**
	 * Attempt to return the shortened git hash of any path Habari can access
	 * @static
	 *
	 * @param String $path Where to check for a .git directory
	 * @return String The first 7 chars of the revision hash
	 */
	public static function get_git_short_hash( $path = null )
	{
		$rev = '';
		$path = is_null( $path ) ? HABARI_PATH . '/system' : $path;
		$ref_file = $path . '/.git/HEAD';
		if ( file_exists( $ref_file ) ) {
			$info = file_get_contents( $ref_file );
			// If the contents of this file start with "ref: ", it means we need to look where it tells us for the hash.
			// CAVEAT: This is only really useful if the master branch is checked out
			if ( strpos( $info, 'ref: ' ) === false ) {
				$rev = substr( $info, 0, 7 );
			} else {
				preg_match( '/ref: (.*)/', $info, $match );
				$rev = substr( file_get_contents( $path . '/.git/' . $match[1] ), 0, 7 );
			}
		}
		return $rev;
	}
}

?>