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

/**
 * Habari Posts Class
 *
 * class Posts
 * This class provides two key features.
 * 1: Posts contains static method get() that returns the
 * requested posts based on the passed criteria.  Depending on the type of
 * request, different types are returned. See the function for details
 * 2: An instance of Posts functions as an array (by extending ArrayObject) and
 * is returned by Posts::get() as the results of a query.  This allows the
 * result of Posts::get() to be iterated (for example, in a foreach construct)
 * and to have properties that can be accessed that describe the results
 * (for example, $posts->onepost).
 */
class Posts extends ArrayObject implements IsContent
{
	public $get_param_cache; // Stores info about the last set of data fetched that was not a single value

	/**
	 * function __get
	 * Returns properties of a Posts object.
	 * This is the function that returns information about the set of posts that
	 * was requested.  This function should offer property names that are identical
	 * to properties of instances of the URL class.  A call to Posts::get()
	 * without parameters should return mostly the same property values as the
	 * global $url object for the request.  The difference would occur when
	 * the data returned doesn't necessarily match the request, such as when
	 * several posts are requested, but only one is available to return.
	 * @param string The name of the property to return.
	 */
	public function __get( $name )
	{
		switch ( $name ) {
			case 'onepost':
				return ( count( $this ) == 1 );
		}

		return false;
	}

	/**
	 * Returns a post or posts based on supplied parameters.
	 * <b>THIS CLASS SHOULD CACHE QUERY RESULTS!</b>
	 *
	 * @param array $paramarry An associated array of parameters, or a querystring
	 * @return array An array of Post objects, or a single post object, depending on request
	 */
	public static function get( $paramarray = array() )
	{
		$join_params = array();
		$params = array();
		$fns = array( 'get_results', 'get_row', 'get_value' );
		$select_ary = array();

		// Default fields to select, everything by default
		foreach ( Post::default_fields() as $field => $value ) {
			$select_ary[$field] = "{posts}.$field AS $field";
		}

		// Default parameters
		$orderby = 'pubdate DESC';

		// If $paramarray is a querystring, convert it to an array
		$paramarray = Utils::get_params( $paramarray );

		// Define the WHERE sets to process and OR in the final SQL statement
		if ( isset( $paramarray['where'] ) && is_array( $paramarray['where'] ) ) {
			$wheresets = $paramarray['where'];
		}
		else {
			$wheresets = array( array() );
		}

		/* Start building the WHERE clauses */

		$wheres = array();
		$joins = array();

		// If the request as a textual WHERE clause, skip the processing of the $wheresets since it's empty
		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 );
				// $nots= preg_grep( '%^not:(\w+)$%iu', (array) $paramset );

				if ( isset( $paramset['id'] ) ) {
					if ( is_array( $paramset['id'] ) ) {
						array_walk( $paramset['id'], create_function( '&$a,$b', '$a = intval($a);' ) );
						$where[] = "{posts}.id IN (" . implode( ',', array_fill( 0, count( $paramset['id'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['id'] );
					}
					else {
						$where[] = "{posts}.id = ?";
						$params[] = (int) $paramset['id'];
					}
				}
				if ( isset( $paramset['not:id'] ) ) {
					if ( is_array( $paramset['not:id'] ) ) {
						array_walk( $paramset['not:id'], create_function( '&$a,$b', '$a = intval($a);' ) );
						$where[] = "{posts}.id NOT IN (" . implode( ',', array_fill( 0, count( $paramset['not:id'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['not:id'] );
					}
					else {
						$where[] = "{posts}.id != ?";
						$params[] = (int) $paramset['not:id'];
					}
				}
				if ( isset( $paramset['status'] ) && ( $paramset['status'] != 'any' ) && ( 0 !== $paramset['status'] )) {
					if ( is_array( $paramset['status'] ) ) {
						// remove 'any' from the list if we have an array
						$paramset['status'] = array_diff( $paramset['status'], array( 'any' ) );
						array_walk( $paramset['status'], create_function( '&$a,$b', '$a = Post::status($a);' ) );
						$where[] = "{posts}.status IN (" . implode( ',', array_fill( 0, count( $paramset['status'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['status'] );
					}
					else {
						$where[] = "{posts}.status = ?";
						$params[] = (int) Post::status( $paramset['status'] );
					}
				}
				if ( isset( $paramset['content_type'] ) && ( $paramset['content_type'] != 'any' ) && ( 0 !== $paramset['content_type'] ) ) {
					if ( is_array( $paramset['content_type'] ) ) {
						// remove 'any' from the list if we have an array
						$paramset['content_type'] = array_diff( $paramset['content_type'], array( 'any' ) );
						array_walk( $paramset['content_type'], create_function( '&$a,$b', '$a = Post::type($a);' ) );
						$where[] = "{posts}.content_type IN (" . implode( ',', array_fill( 0, count( $paramset['content_type'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['content_type'] );
					}
					else {
						$where[] = "{posts}.content_type = ?";
						$params[] = (int) Post::type( $paramset['content_type'] );
					}
				}
				if ( isset( $paramset['not:content_type'] ) ) {
					if ( is_array( $paramset['not:content_type'] ) ) {
						array_walk( $paramset['not:content_type'], create_function( '&$a,$b', '$a = Post::type($a);' ) );
						$where[] = "{posts}.content_type NOT IN (" . implode( ',', array_fill( 0, count( $paramset['not:content_type'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['not:content_type'] );
					}
					else {
						$where[] = "{posts}.content_type != ?";
						$params[] = (int) Post::type( $paramset['not:content_type'] );
					}
				}
				if ( isset( $paramset['slug'] ) ) {
					if ( is_array( $paramset['slug'] ) ) {
						$where[] = "{posts}.slug IN (" . implode( ',', array_fill( 0, count( $paramset['slug'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['slug'] );
					}
					else {
						$where[] = "{posts}.slug = ?";
						$params[] = (string) $paramset['slug'];
					}
				}
				if ( isset( $paramset['user_id'] ) && 0 !== $paramset['user_id'] ) {
					if ( is_array( $paramset['user_id'] ) ) {
						array_walk( $paramset['user_id'], create_function( '&$a,$b', '$a = intval($a);' ) );
						$where[] = "{posts}.user_id IN (" . implode( ',', array_fill( 0, count( $paramset['user_id'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['user_id'] );
					}
					else {
						$where[] = "{posts}.user_id = ?";
						$params[] = (int) $paramset['user_id'];
					}

				}

				if ( isset( $paramset['tag'] ) || isset( $paramset['tag_slug'] )) {
					$joins['tag2post_posts'] = ' JOIN {object_terms} ON {posts}.id = {object_terms}.object_id';
					$joins['tags_tag2post'] = ' JOIN {terms} ON {object_terms}.term_id = {terms}.id';

					if ( isset( $paramset['tag'] ) ) {
						if ( is_array( $paramset['tag'] ) ) {
							$where[] = "{terms}.term_display IN (" . implode( ',', array_fill( 0, count( $paramset['tag'] ), '?' ) ) . ")" . '  AND {object_terms}.object_type_id = ?';
							$params = array_merge( $params, $paramset['tag'] );
						}
						else {
							$where[] = '{terms}.term_display = ? AND {object_terms}.object_type_id = ?';
							$params[] = (string) $paramset['tag'];
						}
					}
					if ( isset( $paramset['tag_slug'] ) ) {
						if ( is_array( $paramset['tag_slug'] ) ) {
							$where[] = "{terms}.term IN (" . implode( ',', array_fill( 0, count( $paramset['tag_slug'] ), '?' ) ) . ")" . ' AND {object_terms}.object_type_id = ?';
							$params = array_merge( $params, $paramset['tag_slug'] );
						}
						else {
							$where[] = '{terms}.term= ? AND {object_terms}.object_type_id = ?';
							$params[] = (string) $paramset['tag_slug'];
						}
					}
					$params[] = Vocabulary::object_type_id( Tags::object_type() );
				}

				if ( isset( $paramset['all:tag'] ) ) {
					$joins['tag2post_posts'] = ' JOIN {object_terms} ON {posts}.id = {object_terms}.object_id';
					$joins['tags_tag2post'] = ' JOIN {terms} ON {object_terms}.term_id = {terms}.id';

					if ( is_array( $paramset['all:tag'] ) ) {
						$where[] = '{terms}.term_display IN (' . Utils::placeholder_string( $paramset['all:tag']) . ')' . ' AND {object_terms}.object_type_id = ?';
						$params = array_merge( $params, $paramset['all:tag'] );

						$groupby = '{posts}.id';
						$having = 'count(*) = ' . count( $paramset['all:tag'] );
					}
					else {
						// this is actually the same as plain 'tag' for a single tag search - go with it
						$where[] = '{terms}.term_display = ? AND {object_terms}.object_type_id = ?';
						$params[] = $paramset['all:tag'];
					}
					$params[] = Vocabulary::object_type_id( Tags::object_type() );
				}

				if ( isset( $paramset['all:tag_slug'] ) ) {
					$joins['tag2post_posts'] = ' JOIN {object_terms} ON {posts}.id = {object_terms}.object_id';
					$joins['tags_tag2post'] = ' JOIN {terms} ON {object_terms}.term_id = {terms}.id';

					if ( is_array( $paramset['all:tag_slug'] ) ) {
						$where[] = '{terms}.term IN (' . Utils::placeholder_string( $paramset['all:tag_slug']) . ')' . ' AND {object_terms}.object_type_id = ?';
						$params = array_merge( $params, $paramset['all:tag_slug'] );

						$groupby = '{posts}.id';
						$having = 'count(*) = ' . count( $paramset['all:tag_slug'] );
					}
					else {
						// this is actually the same as plain 'tag' for a single tag search - go with it
						$where[] = '{terms}.term = ? AND {object_terms}.object_type_id = ?';
						$params[] = $paramset['all:tag_slug'];
					}
					$params[] = Vocabulary::object_type_id( Tags::object_type() );
				}

				if ( isset( $paramset['not:tag'] ) ) {
					$nottag = Utils::single_array( $paramset['not:tag'] );
					$where[] = 'NOT EXISTS (SELECT 1
						FROM {object_terms}
						INNER JOIN {terms} ON {terms}.id = {object_terms}.term_id
						WHERE {terms}.term_display IN (' . Utils::placeholder_string( $nottag ) . ')
						AND {object_terms}.object_id = {posts}.id
						AND {object_terms}.object_type_id = ?)
					';
					$params = array_merge( $params, $nottag );
					$params[] = Vocabulary::object_type_id( Tags::object_type() );
				}

				if ( isset( $paramset['not:tag_slug'] ) ) {
					$nottag = Utils::single_array( $paramset['not:tag_slug'] );
					$where[] = 'NOT EXISTS (SELECT 1
						FROM {object_terms}
						INNER JOIN {terms} ON {terms}.id = {object_terms}.term_id
						WHERE {terms}.term_display IN (' . Utils::placeholder_string( $nottag ) . ')
						AND {object_terms}.object_id = {posts}.id
						AND {object_terms}.object_type_id = ?)
					';
					$params = array_merge( $params, $nottag );
					$params[] = Vocabulary::object_type_id( Tags::object_type() );
				}

				if ( isset( $paramset['criteria'] ) ) {
					preg_match_all( '/(?<=")([\p{L}\p{N}]+[^"]*)(?=")|([\p{L}\p{N}]+)/u', $paramset['criteria'], $matches );
					foreach ( $matches[0] as $word ) {
						$where[] .= "({posts}.title LIKE CONCAT('%',?,'%') OR {posts}.content LIKE CONCAT('%',?,'%'))";
						$params[] = $word;
						$params[] = $word;  // Not a typo (there are two ? in the above statement)
					}
				}

				if ( isset( $paramset['all:info'] ) || isset( $paramset['info'] ) ) {

					// merge the two possibile calls together
					$infos = array_merge( isset( $paramset['all:info'] ) ? $paramset['all:info'] : array(), isset( $paramset['info'] ) ? $paramset['info'] : array() );

					if ( Utils::is_traversable( $infos ) ) {
						$pi_count = 0;
						foreach ( $infos as $info_key => $info_value ) {
							$pi_count++;
							$joins['info_' . $info_key] = " LEFT JOIN {postinfo} ipi{$pi_count} ON {posts}.id = ipi{$pi_count}.post_id AND ipi{$pi_count}.name = ? AND ipi{$pi_count}.value = ?";
							$join_params[] = $info_key;
							$join_params[] = $info_value;
							$where[] = "ipi{$pi_count}.name <> ''";

							$select_ary["info_{$info_key}_value"] = "ipi{$pi_count}.value AS info_{$info_key}_value";
						}
					}

				}

				if ( isset( $paramset['any:info'] ) ) {
					if ( Utils::is_traversable( $paramset['any:info'] ) ) {
						$pi_count = 0;
						$pi_where = array();
						foreach ( $paramset['any:info'] as $info_key => $info_value ) {
							$pi_count++;

							$join_params[] = $info_key;
							if ( is_array($info_value) ) {
								$joins['any_info_' . $info_key] = " LEFT JOIN {postinfo} aipi{$pi_count} ON {posts}.id = aipi{$pi_count}.post_id AND aipi{$pi_count}.name = ? AND aipi{$pi_count}.value IN (" .Utils::placeholder_string(count($info_value)).")";
								$join_params = array_merge($join_params, $info_value);
							}
							else {
								$joins['any_info_' . $info_key] = " LEFT JOIN {postinfo} aipi{$pi_count} ON {posts}.id = aipi{$pi_count}.post_id AND aipi{$pi_count}.name = ? AND aipi{$pi_count}.value = ?";
								$join_params[] = $info_value;
							}

							$pi_where[] = "aipi{$pi_count}.name <> ''";

							$select_ary["info_{$info_key}_value"] = "aipi{$pi_count}.value AS info_{$info_key}_value";
						}
						$where[] = '(' . implode(' OR ', $pi_where) . ')';
					}
				}

				if ( isset( $paramset['has:info'] ) ) {
					$the_ins = array();
					$has_info = Utils::single_array( $paramset['has:info'] );
					$pi_count = 0;
					$pi_where = array();
					foreach ( $has_info as $info_name ) {
						$pi_count++;
						$joins['has_info_' . $info_name] = " LEFT JOIN {postinfo} hipi{$pi_count} ON {posts}.id = hipi{$pi_count}.post_id AND hipi{$pi_count}.name = ?";
						$join_params[] = $info_name;
						$pi_where[] = "hipi{$pi_count}.name <> ''";

						$select_ary["info_{$info_name}_value"] = "hipi{$pi_count}.value AS info_{$info_name}_value";

					}
					$where[] = '(' . implode(' OR ', $pi_where) . ')';
				}

				if ( isset( $paramset['not:all:info'] ) || isset( $paramset['not:info'] ) ) {

					// merge the two possible calls together
					$infos = array_merge( isset( $paramset['not:all:info'] ) ? $paramset['not:all:info'] : array(), isset( $paramset['not:info'] ) ? $paramset['not:info'] : array() );

					if ( Utils::is_traversable( $infos ) ) {
						$the_ins = array();

						foreach ( $infos as $info_key => $info_value ) {

							$the_ins[] = ' ({postinfo}.name = ? AND {postinfo}.value = ? ) ';
							$params[] = $info_key;
							$params[] = $info_value;

						}

						$where[] = '
							{posts}.id NOT IN (
							SELECT post_id FROM {postinfo}
							WHERE ( ' . implode( ' OR ', $the_ins ) . ' )
							GROUP BY post_id
							HAVING COUNT(*) = ' . count( $infos ) . ' )
						';
						// see that hard-coded number? sqlite wets itself if we use a bound parameter... don't change that

					}

				}

				if ( isset( $paramset['not:any:info'] ) ) {

					if ( Utils::is_traversable( $paramset['not:any:info'] ) ) {

						foreach ( $paramset['not:any:info'] as $info_key => $info_value ) {

							$the_ins[] = ' ({postinfo}.name = ? AND {postinfo}.value = ? ) ';
							$params[] = $info_key;
							$params[] = $info_value;

						}

						$where[] = '
							{posts}.id NOT IN (
								SELECT post_id FROM {postinfo}
								WHERE ( ' . implode( ' OR ', $the_ins ) . ' )
							)
						';

					}

				}

				/**
				 * Build the statement needed to filter by pubdate:
				 * If we've got the day, then get the date;
				 * If we've got the month, but no date, get the month;
				 * If we've only got the year, get the whole year.
				 */
				if ( isset( $paramset['day'] ) && isset( $paramset['month'] ) && isset( $paramset['year'] ) ) {
					$where[] = 'pubdate BETWEEN ? AND ?';
					$startDate = sprintf( '%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day'] );
					$startDate = HabariDateTime::date_create( $startDate );
					$params[] = $startDate->sql;
					$params[] = $startDate->modify( '+1 day' )->sql;
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], $paramset['day'], $paramset['year'] ) );
					//$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'], $paramset['day'], $paramset['year'] ) );
				}
				elseif ( isset( $paramset['month'] ) && isset( $paramset['year'] ) ) {
					$where[] = 'pubdate BETWEEN ? AND ?';
					$startDate = sprintf( '%d-%02d-%02d', $paramset['year'], $paramset['month'], 1 );
					$startDate = HabariDateTime::date_create( $startDate );
					$params[] = $startDate->sql;
					$params[] = $startDate->modify( '+1 month' )->sql;
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], 1, $paramset['year'] ) );
					//$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'] + 1, 0, $paramset['year'] ) );
				}
				elseif ( isset( $paramset['year'] ) ) {
					$where[] = 'pubdate BETWEEN ? AND ?';
					$startDate = sprintf( '%d-%02d-%02d', $paramset['year'], 1, 1 );
					$startDate = HabariDateTime::date_create( $startDate );
					$params[] = $startDate->sql;
					$params[] = $startDate->modify( '+1 year' )->sql;
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, 1, 1, $paramset['year'] ) );
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, -1, 1, 1, $paramset['year'] + 1 ) );
				}

				if ( isset( $paramset['after'] ) ) {
					$where[] = 'pubdate > ?';
					$params[] = HabariDateTime::date_create( $paramset['after'] )->sql;
				}

				if ( isset( $paramset['before'] ) ) {
					$where[] = 'pubdate < ?';
					$params[] = HabariDateTime::date_create( $paramset['before'] )->sql;
				}

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

		// Only show posts to which the current user has permission
		if ( isset($paramset['ignore_permissions']) ) {
			$master_perm_where = '';
		}
		else {
			// This set of wheres will be used to generate a list of post_ids that this user can read
			$perm_where = array();
			$perm_where_denied = array();
			$params_where = array();
			$where = array();

			// Get the tokens that this user is granted or denied access to read
			$read_tokens = isset($paramset['read_tokens']) ? $paramset['read_tokens'] : ACL::user_tokens(User::identify(), 'read', true);
			$deny_tokens = isset($paramset['deny_tokens']) ? $paramset['deny_tokens'] : ACL::user_tokens(User::identify(), 'deny', true);

			// If a user can read his own posts, let him
			if ( User::identify()->can('own_posts', 'read') ) {
				$perm_where['own_posts_id'] = '{posts}.user_id = ?';
				$params_where[] = User::identify()->id;
			}

			// If a user can read any post type, let him
			if ( User::identify()->can( 'post_any', 'read' ) ) {
				$perm_where = array('post_any' => '(1=1)');
				$params_where = array();
			}
			else {
				// If a user can read specific post types, let him
				$permitted_post_types = array();
				foreach ( Post::list_active_post_types() as $name => $posttype ) {
					if ( User::identify()->can( 'post_' . Utils::slugify($name), 'read' ) ) {
						$permitted_post_types[] = $posttype;
					}
				}
				if ( count($permitted_post_types) > 0 ) {
					$perm_where[] = '{posts}.content_type IN (' . implode(',', $permitted_post_types) . ')';
				}

				// If a user can read posts with specific tokens, let him
				if ( count($read_tokens) > 0 ) {
					$joins['post_tokens__allowed'] = ' LEFT JOIN {post_tokens} pt_allowed ON {posts}.id= pt_allowed.post_id AND pt_allowed.token_id IN ('.implode(',', $read_tokens).')';
					$perm_where['perms_join_null'] = 'pt_allowed.post_id IS NOT NULL';
				}

			}

			// If a user is denied access to all posts, do so
			if ( User::identify()->cannot( 'post_any' ) ) {
				$perm_where_denied = array('(1=0)');
			}
			else {
				// If a user is denied read access to specific post types, deny him
				$denied_post_types = array();
				foreach ( Post::list_active_post_types() as $name => $posttype ) {
					if ( User::identify()->cannot( 'post_' . Utils::slugify($name) ) ) {
						$denied_post_types[] = $posttype;
					}
				}
				if ( count($denied_post_types) > 0 ) {
					$perm_where_denied[] = '{posts}.content_type NOT IN (' . implode(',', $denied_post_types) . ')';
				}
			}

			// If there are granted permissions to check, add them to the where clause
			if ( count($perm_where) == 0 && !isset($joins['post_tokens__allowed']) ) {
				// You have no grants.  You get no posts.
				$where['perms_granted'] = '(1=0)';
			}
			elseif ( count($perm_where) > 0 ) {
				$where['perms_granted'] = '
					(' . implode(' OR ', $perm_where) . ')
				';
				$params = array_merge( $join_params, $params, $params_where );
			}

			if ( count($deny_tokens) > 0 ) {
				$joins['post_tokens__denied'] = ' LEFT JOIN {post_tokens} pt_denied ON {posts}.id= pt_denied.post_id AND pt_denied.token_id IN ('.implode(',', $deny_tokens).')';
				$perm_where_denied['perms_join_null'] = 'pt_denied.post_id IS NULL';
			}

			// If there are denied permissions to check, add them to the where clause
			if ( count($perm_where_denied) > 0 ) {
				$where['perms_denied'] = '
					(' . implode(' AND ', $perm_where_denied) . ')
				';
			}

			$master_perm_where = implode( ' AND ', $where );
		}

		// Extract the remaining parameters which will be used onwards
		// For example: page number, fetch function, limit
		$paramarray = new SuperGlobal($paramarray);
		$extract = $paramarray->filter_keys('page', 'fetch_fn', 'count', 'orderby', 'groupby', 'limit', 'offset', 'nolimit', 'having');
		foreach ( $extract as $key => $value ) {
			$$key = $value;
		}

		// Define the LIMIT if it does not exist, unless specific posts are requested
		if ( !isset( $limit ) && !isset( $paramset['id']) && !isset( $paramset['slug'])) {
			$limit = Options::get('pagination') ? (int) Options::get('pagination') : 5;
		}
		elseif( !isset( $limit ) ) {
			$selected_posts = 0;
			if(isset($paramset['id'])) {
				$selected_posts += count(Utils::single_array($paramset['id']));
			}
			if(isset($paramset['slug'])) {
				$selected_posts += count(Utils::single_array($paramset['slug']));
			}
			$limit = $selected_posts > 0 ? $selected_posts : '';
		}

		// Calculate the OFFSET based on the page number
		if ( isset( $page ) && is_numeric( $page ) && !isset($paramset['offset']) ) {
			$offset = ( intval( $page ) - 1 ) * intval( $limit );
		}

		/**
		 * Determine which fetch function to use:
		 * If it is specified, make sure it is valid (based on the $fns array defined at the beginning of this function);
		 * Else, use 'get_results' which will return a Posts array of Post objects.
		 */
		if ( isset( $fetch_fn ) ) {
			if ( ! in_array( $fetch_fn, $fns ) ) {
				$fetch_fn = $fns[0];
			}
		}
		else {
			$fetch_fn = $fns[0];
		}

		/**
		 * Turn the requested fields into a comma-separated SELECT field clause
		 */
		$select = implode(', ', $select_ary);

		/**
		 * If a count is requested:
		 * Replace the current fields to select with a COUNT();
		 * Change the fetch function to 'get_value';
		 * Remove the ORDER BY since it's useless.
		 * Remove the GROUP BY (tag search added it)
		 */
		if ( isset( $count ) ) {
			$select = "COUNT($count)";
			$fetch_fn = 'get_value';
			$orderby = '';
			$groupby = '';
			$having = '';
		}

		// If the month counts are requested, replaced the select clause
		if ( isset( $paramset['month_cts'] ) ) {
			if ( isset( $paramset['tag'] ) || isset( $paramset['tag_slug'] ))
				$select = 'MONTH(FROM_UNIXTIME(pubdate)) AS month, YEAR(FROM_UNIXTIME(pubdate)) AS year, COUNT(DISTINCT {posts}.id) AS ct';
			else
				$select = 'MONTH(FROM_UNIXTIME(pubdate)) AS month, YEAR(FROM_UNIXTIME(pubdate)) AS year, COUNT(*) AS ct';
			$groupby = 'year, month';
			$orderby = 'year, month';
		}

		// Remove the LIMIT if 'nolimit' or 'month_cts' is set
		// Doing this first should allow OFFSET to work
		if ( isset( $nolimit ) || isset( $paramset['month_cts'] ) ) {
			$limit = '';
		}

		// Define the LIMIT and add the OFFSET if it exists
		if ( !empty( $limit ) ) {
			$limit = " LIMIT $limit";
			if ( isset( $offset ) ) {
				$limit .= " OFFSET $offset";
			}
		}
		else {
			$limit = '';
		}

		/* All SQL parts are constructed, on to real business! */

		/**
		 * Build the final SQL statement
		 */
		$query = '
			SELECT DISTINCT ' . $select . '
			FROM {posts} ' . implode(' ', $joins);

		if ( count( $wheres ) > 0) {
			$query .= ' WHERE (' . implode( " \nOR\n ", $wheres ) . ')';
			$query .= ($master_perm_where == '') ? '' : ' AND (' . $master_perm_where . ')';
		}
		elseif ( $master_perm_where != '' ) {
			$query .= ' WHERE (' . $master_perm_where . ')';
		}
		$query .= ( ! isset($groupby) || $groupby == '' ) ? '' : ' GROUP BY ' . $groupby;
		$query .= ( ! isset($having) || $having == '' ) ? '' : ' HAVING ' . $having;
		$query .= ( ( $orderby == '' ) ? '' : ' ORDER BY ' . $orderby ) . $limit;

		/**
		 * DEBUG: Uncomment the following line to display everything that happens in this function
		 */
		//print_R('<pre>'.$query.'</pre>');
		//Utils::debug( $paramarray, $fetch_fn, $query, $params );
		//Session::notice($query);

		/**
		 * Execute the SQL statement using the PDO extension
		 */
		DB::set_fetch_mode( PDO::FETCH_CLASS );
		DB::set_fetch_class( 'Post' );
		$results = DB::$fetch_fn( $query, $params, 'Post' );

//		Utils::debug( $paramarray, $fetch_fn, $query, $params, $results );
//		var_dump( $query );

		/**
		 * Return the results
		 */
		if ( 'get_results' != $fetch_fn ) {
			// Since a single result was requested, return a single Post object.
			return $results;
		}
		elseif ( is_array( $results ) ) {
			// With multiple results, return a Posts array of Post objects.
			$c = __CLASS__;
			$return_value = new $c( $results );
			$return_value->get_param_cache = $paramarray;
			return $return_value;
		}
	}

	/**
	 * function by_status
	 * select all posts of a given status
	 * @param int a status value
	 * @return array an array of Comment objects with the same status
	 */
	public static function by_status ( $status )
	{
		return self::get( array( 'status' => $status ) );
	}

	/**
	 * function by_slug
	 * select all post content by slug
	 * @param string a post slug
	 * @return array an array of post content
	 */
	public static function by_slug ( $slug = '' )
	{
		return self::get( array( 'slug' => $slug ) );
	}

	/**
	 * static count_total
	 * return a count for the total number of posts
	 * @param mixed a status value to filter posts by; if FALSE, then no filtering will be performed
	 * @return int the number of posts of specified type ( published or draft )
	 */
	public static function count_total( $status = FALSE )
	{
		$params = array( 'count' => 1 );
		if ( $status !== FALSE ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * return a count for the number of posts last queried
	 * @return int the number of posts of specified type ( published or draft )
	 */
	public function count_all()
	{
		$params = array_merge( ( array ) $this->get_param_cache, array( 'count' => '*', 'nolimit' => 1 ) );
		return Posts::get( $params );
	}

	/**
	 * static count_by_author
	 * return a count of the number of posts by the specified author
	 * @param int an author ID
	 * @param mixed a status value to filter posts by; if FALSE, then no filtering will be performed
	 * @return int the number of posts by the specified author
	 */
	public static function count_by_author( $user_id, $status = FALSE )
	{
		$params = array( 'user_id' => $user_id, 'count' => 1 );
		if ( FALSE !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static count_by_tag
	 * return a count of the number of posts with the assigned tag
	 * @param string A tag
	 * @param mixed a status value to filter posts by; if FALSE, then no filtering will be performed
	 * @return int the number of posts with the specified tag
	 */
	public static function count_by_tag( $tag, $status = FALSE )
	{
		$params = array( 'tag' => $tag, 'count' => 1 );
		if ( FALSE !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * Reassigns the author of a specified set of posts
	 * @param mixed a user ID or name
	 * @param mixed an array of post IDs, an array of Post objects, or an instance of Posts
	 * @return bool Whether the rename operation succeeded or not
	 */
	public static function reassign( $user, $posts )
	{
		// allow plugins the opportunity to prevent reassignment
		$allow = true;
		$allow = Plugins::filter( 'posts_reassign_allow', $allow );
		if ( ! $allow ) {
			return false;
		}

		if ( ! is_int( $user ) ) {
			$u = User::get( $user );
			$user = $u->id;
		}
		// safety checks
		if ( ( $user == 0 ) || empty( $posts ) ) {
			return false;
		}
		switch ( true ) {
			case is_integer( reset( $posts ) ):
				break;
			case reset( $posts ) instanceof Post:
				$ids = array();
				foreach ( $posts as $post ) {
					$ids[] = $post->id;
				}
				$posts = $ids;
				break;
			default:
				return false;
		}
		$ids = implode( ',', $posts );
		Plugins::act( 'posts_reassign_before', array( $user, $posts ) );
		$results = DB::query( "UPDATE {posts} SET user_id=? WHERE id IN ({$ids})", array( $user ) );
		Plugins::act( 'posts_reassign_after', array( $user, $posts ) );

		return $results;
	}

	/**
	 * function publish_scheduled_posts
	 *
	 * Callback function to publish scheduled posts
	 */
	public static function publish_scheduled_posts( $params )
	{
		$select = array();
		// Default fields to select, everything by default
		foreach ( Post::default_fields() as $field => $value ) {
			$select[$field] = "{posts}.$field AS $field";
		}
		$select = implode(',', $select);
		$posts = DB::get_results('SELECT ' . $select . ' FROM {posts} WHERE {posts}.status = ? AND {posts}.pubdate <= ? ORDER BY {posts}.pubdate DESC', array( Post::status( 'scheduled' ), HabariDateTime::date_create() ), 'Post' );
		foreach ( $posts as $post ) {
			$post->publish();
		}
	}

	/**
	 * function update_scheduled_posts_cronjob
	 *
	 * Creates or recreates the cronjob to publish
	 * scheduled posts. It is called whenever a post
	 * is updated or created
	 *
	 */
	public static function update_scheduled_posts_cronjob()
	{
		$min_time = DB::get_value( 'SELECT MIN(pubdate) FROM {posts} WHERE status = ?', array( Post::status( 'scheduled' ) ) );

		CronTab::delete_cronjob( 'publish_scheduled_posts' );
		if ( $min_time ) {
			CronTab::add_single_cron( 'publish_scheduled_posts', array( 'Posts', 'publish_scheduled_posts'),  $min_time, 'Next run: ' . HabariDateTime::date_create( $min_time )->get( 'c' ) );
		}
	}

	/**
	 * Returns an ascending post
	 *
	 * @params The Post from which to start
	 * @params The params by which to work out what is the next ascending post
	 * @return Post The ascending post
	 */
	public static function ascend( $post, $params = null)
	{
		$posts = null;
		$ascend = false;
		if ( !$params ) {
			$params = array( 'where' => "pubdate >= '{$post->pubdate->sql}' AND content_type = {$post->content_type} AND status = {$post->status}", 'limit' => 2, 'orderby' => 'pubdate ASC' );
			$posts = Posts::get($params);
		}
		elseif ( $params instanceof Posts ) {
			$posts = $params;
		}
		else {
			if ( !array_key_exists( 'orderby', $params ) ) {
				$params['orderby'] = 'pubdate ASC';
			}
			$posts = Posts::get($params);
		}
		// find $post and return the next one.
		$index = $posts->search( $post );
		$target = $index + 1;
		if ( array_key_exists( $target, $posts ) ) {
			$ascend = $posts[$target];
		}
		return $ascend;
	}

	/**
	 * Returns a descending post
	 *
	 * @params The Post from which to start
	 * @params The params by which to work out what is the next descending post
	 * @return Post The descending post
	 */
	public static function descend( $post, $params = null)
	{
		$posts = null;
		$descend = false;
		if ( !$params ) {
			$params = array( 'where' => "pubdate <= '{$post->pubdate->sql}' AND content_type = {$post->content_type} AND status = {$post->status}", 'limit' => 2, 'orderby' => 'pubdate DESC' );
			$posts = Posts::get($params);
		}
		elseif ( $params instanceof Posts ) {
			$posts = array_reverse($params);
		}
		else {
			if ( !array_key_exists( 'orderby', $params ) ) {
				$params['orderby'] = 'pubdate DESC';
			}
			$posts = Posts::get($params);
		}
		// find $post and return the next one.
		$index = $posts->search( $post );
		$target = $index + 1;
		if ( array_key_exists( $target, $posts ) ) {
			$descend = $posts[$target];
		}
		return $descend;
	}

	/**
	 * Search this Posts object for the needle, returns its key if found
	 *
	 * @param Post $needle Post object to find within this Posts object
	 * @return mixed Returns the index of the needle, on failure, null is returned
	 */
	public function search( $needle )
	{
		return array_search( $needle, $this->getArrayCopy() );
	}

	/**
	 * Parses a search string for status, type, author, and tag keywords. Returns
	 * an associative array which can be passed to Posts::get(). If multiple
	 * authors, statuses, tags, or types are specified, we assume an implicit OR
	 * such that (e.g.) any author that matches would be returned.
	 *
	 * @param string $search_string The search string
	 * @return array An associative array which can be passed to Posts::get()
	 */
	public static function search_to_get( $search_string )
	{
		// if adding to this array, make sure you update the consequences of a search on this below in the switch.
		$keywords = array( 'author' => 1, 'status' => 1, 'type' => 1, 'tag' => 1, 'info' => 1 );
		$statuses = Post::list_post_statuses();
		$types = Post::list_active_post_types();
		$arguments = array(
			'user_id' => array(),
			'status' => array(),
			'content_type' => array(),
			'tag' => array(),
			'info' => array()
		);
		$criteria = '';

		// this says, find stuff that has the keyword at the start, and then some term straight after.
		// the terms should have no whitespace, or if it does, be ' delimited.
		// ie tag:foo or tag:'foo bar'
		$flag_regex = '/(?P<flag>' . implode( '|', array_keys( $keywords ) ) . '):(?P<value>[^\'"][^\s]*(?:\s|$)|([\'"]+)(?P<quotedvalue>[^\3]+)(?<!\\\)\3)/Uui';

		// now do some matching.
		preg_match_all( $flag_regex , $search_string, $matches, PREG_SET_ORDER );

		// now we remove those terms from the search string, otherwise the keyword search below has issues. It will pick up things like
		// from tag:'pair of' -> matches of'
		$criteria = trim(preg_replace( $flag_regex, '', $search_string));

		// go through flagged things.
		foreach ($matches as $match) {
			// switch on the type match. ie status, type et al.
			// also, trim out the quote marks that have been matched.
			if( isset($match['quotedvalue']) && $match['quotedvalue'] ) {
				$value = stripslashes($match['quotedvalue']);
			}
			else {
				$value = $match['value'];
			}
			$value = trim( $value );

			switch( strtolower($match['flag']) )  {
				case 'author':
					if ( $u = User::get( $value ) ) {
						$arguments['user_id'][] = (int) $u->id;
					}
					break;
				case 'tag':
					$arguments['tag'][] = $value;
					break;
				case 'status':
					if ( isset( $statuses[$value] ) ) {
						$arguments['status'][] = (int) $statuses[$value];
					}
					break;
				case 'type':
					if ( isset( $types[$value] ) ) {
						$arguments['content_type'][] = (int) $types[$value];
					}
					break;
				case 'info':
					if( strpos($value, ':') !== FALSE ) {
						list( $infokey, $infovalue ) = explode( ':', $value, 2 );
						$arguments['info'][] = array($infokey=>$infovalue);
					}
					break;
			}
		}

		// flatten keys that have single-element or no-element arrays
		foreach ( $arguments as $key => $arg ) {
			switch ( count( $arg ) ) {
				case 0:
					unset( $arguments[$key] );
					break;
				case 1:
					$arguments[$key] = $arg[0];
					break;
			}
		}

		if ( $criteria != '' ) {
			$arguments['criteria'] = $criteria;
		}

		return $arguments;
	}

	/**
	 * Check if the requested post is of the type specified, to see if a rewrite rule matches.
	 *
	 * @return Boolean Whether the requested post matches the content type of the rule.
	 */
	public static function rewrite_match_type($rule, $slug, $parameters)
	{
		$args = $rule->named_arg_values;
		$args['count'] = true;
		$postcount = Posts::get($args);
		return $postcount > 0;
	}

	/**
	 * Return the type of the content represented by this object
	 *
	 * @return string The name of the content representedt by this object
	 */
	function content_type ()
	{
		if ( isset($this->preset) ) {
			return 'posts.' . $this->preset;
		}
		return 'posts';
	}
}
?>


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

/**
 * Class to assist in profiling queries
 *
 */
class QueryProfile
{
	public $start_time;     // time that query started execution
	public $end_time;       // time that query ended execution
	public $query_text;     // SQL text
	public $backtrace = '';  // stack backtrace for debugging.

	/**
	 * Constructor for the query profile.  Automatically sets the
	 * start time for the query
	 *
	 * @param query SQL being executed
	 */
	public function __construct($query)
	{
		$this->query_text = $query;
		/* Backtracing is very verbose. Enable only if set via query string */
		if ( isset($_GET['db_profile'] )
			&& $_GET['db_profile'] == 'verbose')
			$this->backtrace = debug_backtrace();
	}

	public function start()
	{
		$this->start_time = $this->get_time_in_microseconds();
	}

	public function stop()
	{
		$this->end_time = $this->get_time_in_microseconds();
	}

	public function __get($name)
	{
		switch ($name) {
			case 'total_time':
				return $this->end_time - $this->start_time;
			default:
				return $this->$name;
		}
	}

	/**
	 * Returns an integer representing the current time
	 * in microseconds from Epoch
	 *
	 * @return  int the number of microseconds since epoch.
	 */
	public static function get_time_in_microseconds()
	{
		list($usec, $sec)= explode(' ', microtime());
		return ((float) (1000*$sec)  + $usec);
	}

}

repo_name: DaSourcerer/habari


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

/**
 * Habari QueryRecord Class
 *
 */
class QueryRecord implements URLProperties
{
	protected $fields = array();  // Holds field values from db
	protected $newfields = array(); // Holds updated field values to commit to db
	protected $unsetfields = array(); // Holds field names to remove when committing to the db
	private $properties_loaded = array(); // Set [$name] to true after first load of properties

	/**
	 * constructor __construct
	 * Constructor for the QueryRecord class.
	 * @param array an associative array of initial field values.
	 **/
	public function __construct($paramarray = array())
	{
		$params = Utils::get_params($paramarray);
		if (count($params) ) {
			// Defaults
			$this->fields = array_merge(
				$this->fields,
				$params
			);
			// mark any passed params as loaded when creating this object
			$this->properties_loaded = array_merge(
				$this->properties_loaded,
				array_combine( array_keys($params), array_fill(0, count($params), TRUE) )
			);
		}
	}

	/**
	 * function __get
	 * Handles getting virtual properties for this class
	 * @param string Name of the property
	 * @return mixed The set value or NULL if none exists
	 **/
	public function __get($name)
	{
		if ( isset( $this->newfields[$name] ) ) {
			return $this->newfields[$name];
		}
		else if ( isset( $this->fields[$name] ) ) {
			return $this->fields[$name];
		}
		else {
			return NULL;
		}
	}

	/**
	 * function __set
	 * Handles setting virtual properties for this class
	 * @param string Name of the property
	 * @param mixed Value to set it to
	 * @return mixed The set value
	 **/
	public function __set($name, $value)
	{
		if( isset($this->properties_loaded[$name]) ) {
			$this->newfields[$name] = $value;
		}
		else {
			$this->fields[$name] = $value;
			$this->properties_loaded[$name] = TRUE;
		}
		return $value;
	}

	/**
	* Magic isset for QueryRecord, returns whether a property value is set.
	* @param string $name The name of the parameter
	* @return boolean True if the value is set, false if not
	*/
	public function __isset($name)
	{
		return ( isset( $this->newfields[$name] ) || isset( $this->fields[$name] ) );
	}

	/**
	* Registers a (list of) fields(s) as being managed exclusively by the database.
	* @param mixed A database field name (string) or an array of field names
	*/
	public function exclude_fields( $fields )
	{
		if(is_array($fields))
		{
			$this->unsetfields = array_flip($fields);
		}
		else {
			$this->unsetfields[$fields] = $fields;
		}
	}

	/**
	* returns an array of fields that should not be included in any database insert operation
	* @return array an array of field names
	*/
	public function list_excluded_fields()
	{
		return $this->unsetfields;
	}

	/**
	 * This is the public interface that inserts a record
	 */
	public function insert()
	{
		return null;
	}

	/**
	 * function insertRecord(
	 * Inserts this record's fields as a new row
	 * @param string Table to update
	 * @return boolean True on success, false if not
	 **/
	/*
	 * Again, the parent class's method's signature must match that of the
	 * child class's signature
	 */
	protected function insertRecord($table)
	{
		$merge =  array_merge($this->fields, $this->newfields);
		return DB::insert($table, array_diff_key($merge, $this->unsetfields));
	}

	/**
	 * function to_array
	 * Returns an array with the current field settings
	 * @return array The field settings as they would be saved
	 **/
	public function to_array()
	{
		return array_merge($this->fields, $this->newfields);
	}

	/**
	 * Returns an array with the current field settings
	 * @return array The field settings as they would be saved
	 **/
	public function get_url_args()
	{
		return $this->to_array();
	}
	/**
	 * This is the public interface that updates a record
	 */
	public function update()
	{
		return null;
	}
	/**
	 * function updateRecord
	 * Updates this record's fields using the new data
	 * @param string Table to update
	 * @param array An associative array of field data to match
	 * @return boolean True on success, false if not
	 **/
	protected function updateRecord($table, $updatekeyfields = array() )
	{
		$merge = array_merge($this->fields, $this->newfields);
		return DB::update($table, array_diff_key($merge, $this->unsetfields), $updatekeyfields);
	}

	/**
	 * This is the public interface that deletes a record
	 */
	public function delete()
	{
		return null;
	}
	/**
	 * function deleteRecord
	 * Deletes a record based on the match array
	 * @param string Table to delete from
	 * @param array An associative array of field data to match
	 * @return boolean True on success, false if not
	 **/
	protected function deleteRecord($table, $updatekeyfields)
	{
		return DB::delete($table, $updatekeyfields);
	}

}

?>


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

/**
 * Habari RawPHPEngine class
 *
 *
 * The RawPHPEngine is a subclass of the abstract TemplateEngine class
 * which is intended for those theme designers who choose to use raw PHP
 * to design theme templates.
 */
class RawPHPEngine extends TemplateEngine
{
	// Internal data to be extracted into template symbol table
	protected $engine_vars = array();
	protected $available_templates = null;
	protected $template_map =array();
	protected $var_stack = array();

	/**
	 * Constructor for RawPHPEngine
	 *
	 * Sets up default values for required settings.
	 */
	public function __construct()
	{
		// Nothing to do here...
	}

	/**
	 * Tries to retrieve a variable from the internal array engine_vars.
	 * Method returns the value if succesful, returns false otherwise.
	 *
	 * @param key name of variable
	 */
	public function __get( $key )
	{
		return isset( $this->engine_vars[$key] ) ? $this->engine_vars[$key] : null;
	}

	/**
	 * Assigns a variable to the template engine for use in
	 * constructing the template's output.
	 *
	 * @param key name of variable
	 * @param value value of variable
	 */
	public function __set( $key, $value )
	{
		$this->engine_vars[$key] = $value;
	}

	/**
	 * Unassigns a variable to the template engine.
	 *
	 * @param key name of variable
	 */
	public function __unset( $key )
	{
		unset( $this->engine_vars[$key] );
	}

	/**
	 * Detects if a variable is assigned to the template engine for use in
	 * constructing the template's output.
	 *
	 * @param key name of variable
	 * @returns boolean true if name is set, false if not set
	 */
	public function __isset( $key )
	{
		return isset( $this->engine_vars[$key] );
	}

	/**
	 * A function which outputs the result of a transposed
	 * template to the output stream
	 *
	 * @param template  Name of template to display
	 */
	public function display( $template )
	{
		/**
		 * @todo  Here would be a good place to notify observers of output.
		 *        For instance, having sessions/headers output before
		 *        the template content...
		 */
		extract( $this->engine_vars );
		if ( $this->template_exists( $template ) ) {
			//$template_file= Plugins::filter('include_template_file', $this->template_dir . $template . '.php', $template, __CLASS__);
			$template_file = isset($this->template_map[$template]) ? $this->template_map[$template] : null;
			$template_file = Plugins::filter('include_template_file', $template_file, $template, __CLASS__);
			include ( $template_file );
		}
	}

	/**
	 * Returns the existance of the specified template name
	 *
	 * @param string $template Name of template to detect
	 * @returns boolean True if the template exists, false if not
	 */
	public function template_exists( $template )
	{
		if ( empty( $this->available_templates ) ) {
			if(!is_array($this->template_dir)) {
				$this->template_dir = array($this->template_dir);
			}
			$alltemplates = array();
			$dirs = array_reverse($this->template_dir);
			foreach($dirs as $dir) {
				$templates = Utils::glob( $dir . '*.*' );
				$alltemplates = array_merge($alltemplates, $templates);
			}
			$this->available_templates = array_map( 'basename', $alltemplates, array_fill( 1, count( $alltemplates ), '.php' ) );
			$this->template_map = array_combine($this->available_templates, $alltemplates);
			array_unique($this->available_templates);
			$this->available_templates = Plugins::filter('available_templates', $this->available_templates, __CLASS__);
		}
		return in_array( $template, $this->available_templates );
	}

	/**
	 * A function which returns the content of the transposed
	 * template as a string
	 *
	 * @param string $template Name of template to fetch
	 */
	public function fetch( $template )
	{
		ob_start();
		$this->display( $template );
		$contents = ob_get_clean();
		return $contents;
	}

	/**
	 * Assigns a variable to the template engine for use in
	 * constructing the template's output.
	 *
	 * @param key name( s ) of variable
	 * @param value value of variable
	 */
	public function assign( $key, $value = '' )
	{
		$this->$key = $value;
	}

	/**
	 * Detects if a variable is assigned to the template engine for use in
	 * constructing the template's output.
	 *
	 * @param string $key name of variable
	 * @returns boolean true if key is set, false if not set
	 */
	public function assigned( $key )
	{
		return isset( $this->$key );
	}

	/**
	 * Clear all of the assigned template variables
	 */
	public function clear()
	{
		$this->engine_vars = array();
	}

	/**
	 * Appends to an existing variable more values
	 *
	 * @param key name of variable
	 * @param value value of variable
	 */
	public function append( $key, $value ='' )
	{
		if ( ! isset( $this->engine_vars[$key] ) ) {
			$this->engine_vars[$key][] = $value;
		}
		else {
			$this->engine_vars[$key] = $value;
		}
	}
}
?>


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

/**
 * Holds the basic RemoteRequest functionality.
 *
 * Interface for Request Processors. RemoteRequest uses a RequestProcessor to
 * do the actual work.
 *
 */
interface RequestProcessor
{
	public function execute( $method, $url, $headers, $body, $timeout );

	public function get_response_body();
	public function get_response_headers();
}

/**
 * Generic class to make outgoing HTTP requests.
 *
 */
class RemoteRequest
{
	private $method = 'GET';
	private $url;
	private $params = array();
	private $headers = array();
	private $postdata = array();
	private $files = array();
	private $body = '';
	private $timeout = 180;
	private $processor = NULL;
	private $executed = FALSE;

	private $response_body = '';
	private $response_headers = '';

	private $user_agent = 'Habari';

	/**
	 * @param string $url URL to request
	 * @param string $method Request method to use (default 'GET')
	 * @param int $timeuot Timeout in seconds (default 180)
	 */
	public function __construct( $url, $method = 'GET', $timeout = 180 )
	{
		$this->method = strtoupper( $method );
		$this->url = $url;
		$this->set_timeout( $timeout );

		$this->user_agent .= '/' . Version::HABARI_VERSION;
		$this->add_header( array( 'User-Agent' => $this->user_agent ) );

		// can't use curl's followlocation in safe_mode with open_basedir, so
		// fallback to srp for now
		if ( function_exists( 'curl_init' )
			 && ! ( ini_get( 'safe_mode' ) && ini_get( 'open_basedir' ) ) ) {
			$this->processor = new CURLRequestProcessor;
		}
		else {
			$this->processor = new SocketRequestProcessor;
		}
	}

	/**
	 * DO NOT USE THIS FUNCTION.
	 * This function is only to be used by the test case for RemoteRequest!
	 */
	public function __set_processor( $processor )
	{
		$this->processor = $processor;
	}

	/**
	 * Add a request header.
	 * @param mixed $header The header to add, either as a string 'Name: Value' or an associative array 'name'=>'value'
	 */
	public function add_header( $header )
	{
		if ( is_array( $header ) ) {
			$this->headers = array_merge( $this->headers, $header );
		}
		else {
			list( $k, $v )= explode( ': ', $header );
			$this->headers[$k] = $v;
		}
	}

	/**
	 * Add a list of headers.
	 * @param array $headers List of headers to add.
	 */
	public function add_headers( $headers )
	{
		foreach ( $headers as $header ) {
			$this->add_header( $header );
		}
	}

	/**
	 * Set the request body.
	 * Only used with POST requests, will raise a warning if used with GET.
	 * @param string $body The request body.
	 */
	public function set_body( $body )
	{
		if ( $this->method !== 'POST' )
			return Error::raise( _t('Trying to add a request body to a non-POST request'), E_USER_WARNING );

		$this->body = $body;
	}

	/**
	 * Set the request query parameters (i.e., the URI's query string).
	 * Will be merged with existing query info from the URL.
	 * @param array $params
	 */
	public function set_params( $params )
	{
		if ( ! is_array( $params ) )
			$params = parse_str( $params );

		$this->params = $params;
	}

	/**
	 * Set the timeout.
	 * @param int $timeout Timeout in seconds
	 */
	public function set_timeout( $timeout )
	{
		$this->timeout = $timeout;
		return $this->timeout;
	}

	/**
	 * set postdata
	 *
	 * @access public
	 * @param mixed $name
	 * @param string $value
	 */
	public function set_postdata($name, $value = null)
	{
		if (is_array($name)) {
			$this->postdata = array_merge($this->postdata, $name);
		}
		else {
			$this->postdata[$name] = $value;
		}
	}

	/**
	 * set file
	 *
	 * @access public
	 * @param string $name
	 * @param string $filename
	 * @param string $content_type
	 */
	public function set_file($name, $filename, $content_type = null, $override_filename = null)
	{
		if (!file_exists($filename)) {
			return Error::raise(sprintf(_t('File %s not found.'), $filename), E_USER_WARNING);
		}
		if (empty($content_type)) $content_type = 'application/octet-stream';
		$this->files[$name] = array('filename' => $filename, 'content_type' => $content_type, 'override_filename' => $override_filename);
		$this->headers['Content-Type'] = 'multipart/form-data';
	}

	/**
	 * A little housekeeping.
	 */
	private function prepare()
	{
		// remove anchors (#foo) from the URL
		$this->url = $this->strip_anchors( $this->url );
		// merge query params from the URL with params given
		$this->url = $this->merge_query_params( $this->url, $this->params );

		if ( $this->method === 'POST' ) {
			if ( !isset( $this->headers['Content-Type'] ) || ( $this->headers['Content-Type'] == 'application/x-www-form-urlencoded' ) ) {
				// TODO should raise a warning
				$this->add_header( array( 'Content-Type' => 'application/x-www-form-urlencoded' ) );

				if($this->body != '' && count($this->postdata) > 0) {
					$this->body .= '&';
				}
				$this->body .= http_build_query( $this->postdata, '', '&' );
			}
			elseif ( $this->headers['Content-Type'] == 'multipart/form-data' ) {
				$boundary = md5( Utils::nonce() );
				$this->headers['Content-Type'] .= '; boundary=' . $boundary;

				$parts = array();
				if ( $this->postdata && is_array( $this->postdata ) ) {
					reset( $this->postdata );
					while ( list( $name, $value ) = each( $this->postdata ) ) {
						$parts[] = "Content-Disposition: form-data; name=\"{$name}\"\r\n\r\n{$value}\r\n";
					}
				}

				if ( $this->files && is_array( $this->files ) ) {
					reset( $this->files );
					while ( list( $name, $fileinfo ) = each( $this->files ) ) {
						$filename = basename( $fileinfo['filename'] );
						if ( !empty( $fileinfo['override_filename'] ) ) {
							$filename = $fileinfo['override_filename'];
						}
						$part = "Content-Disposition: form-data; name=\"{$name}\"; filename=\"{$filename}\"\r\n";
						$part .= "Content-Type: {$fileinfo['content_type']}\r\n\r\n";
						$part .= file_get_contents( $fileinfo['filename'] ) . "\r\n";
						$parts[] = $part;
					}
				}

				if ( !empty( $parts ) ) {
					$this->body = "--{$boundary}\r\n" . join("--{$boundary}\r\n", $parts) . "--{$boundary}--\r\n";
				}
			}
			$this->add_header( array( 'Content-Length' => strlen( $this->body ) ) );
		}
	}

	/**
	 * Actually execute the request.
	 * On success, returns TRUE and populates the response_body and response_headers fields.
	 * On failure, throws error.
	 */
	public function execute()
	{
		$this->prepare();
		$result = $this->processor->execute( $this->method, $this->url, $this->headers, $this->body, $this->timeout );

		if ( $result && ! Error::is_error( $result ) ) { // XXX exceptions?
			$this->response_headers = $this->processor->get_response_headers();
			$this->response_body = $this->processor->get_response_body();
			$this->executed = TRUE;

			return TRUE;
		}
		else {
			// actually, processor->execute should throw an Error which would bubble up
			// we need a new Error class and error handler for that, though
			$this->executed = FALSE;

			return $result;
		}
	}

	public function executed()
	{
		return $this->executed;
	}

	/**
	 * Return the response headers. Raises a warning and returns '' if the request wasn't executed yet.
	 */
	public function get_response_headers()
	{
		if ( !$this->executed )
			return Error::raise( _t('Trying to fetch response headers for a pending request.'), E_USER_WARNING );

		return $this->response_headers;
	}

	/**
	 * Return the response body. Raises a warning and returns '' if the request wasn't executed yet.
	 */
	public function get_response_body()
	{
		if ( !$this->executed )
			return Error::raise( _t('Trying to fetch response body for a pending request.'), E_USER_WARNING );

		return $this->response_body;
	}

	/**
	 * Remove anchors (#foo) from given URL.
	 */
	private function strip_anchors( $url )
	{
		return preg_replace( '/(#.*?)?$/', '', $url );
	}

	/**
	 * Call the filter hook.
	 */
	private function __filter( $data, $url )
	{
		return Plugins::filter( 'remoterequest', $data, $url );
	}

	/**
	 * Merge query params from the URL with given params.
	 * @param string $url The URL
	 * @param string $params An associative array of parameters.
	 */
	private function merge_query_params( $url, $params )
	{
		$urlparts = InputFilter::parse_url( $url );

		if ( ! isset( $urlparts['query'] ) ) {
			$urlparts['query'] = '';
		}

		if ( ! is_array( $params ) ) {
			parse_str( $params, $params );
		}

		$urlparts['query'] = http_build_query( array_merge( Utils::get_params( $urlparts['query'] ), $params ), '', '&' );

		return InputFilter::glue_url( $urlparts );
	}

	/**
	 * Static helper function to quickly fetch an URL, with semantics similar to
	 * PHP's file_get_contents. Does not support
	 *
	 * Returns the content on success or FALSE if an error occurred.
	 *
	 * @param string $url The URL to fetch
	 * @param bool $use_include_path whether to search the PHP include path first (unsupported)
	 * @param resource $context a stream context to use (unsupported)
	 * @param int $offset how many bytes to skip from the beginning of the result
	 * @param int $maxlen how many bytes to return
	 * @return string description
	 */
	public static function get_contents( $url, $use_include_path = FALSE, $context = NULL, $offset =0, $maxlen = -1 )
	{
		$rr = new RemoteRequest( $url );
		if ( $rr->execute() === TRUE) {
			return ( $maxlen != -1
				? MultiByte::substr( $rr->get_response_body(), $offset, $maxlen )
				: MultiByte::substr( $rr->get_response_body(), $offset ) );
		}
		else {
			return FALSE;
		}
	}

}

?>


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

/**
 * Habari RewriteRule Class
 *
 * Helper class to encapsulate rewrite rule data
 *
 */
class RewriteRule extends QueryRecord
{

	const RULE_SYSTEM = 0;
	const RULE_THEME = 1;
	const RULE_PLUGIN = 2;
	const RULE_CUSTOM = 5;

	public $entire_match = NULL; // Entire matched string from the URL
	public $named_arg_values = array(); // Values of named arguments filled during URL::parse()
	private $m_named_args = NULL; // Named arguments matches

	/**
	 * Returns the defined database columns for a rewrite rule.
	 * @return array Array of columns in the rewrite_rules table
	 */
	public static function default_fields()
	{
		return array(
			'rule_id' => 0,
			'name' => '',
			'parse_regex' => '/.^/',
			'build_str' => '',
			'handler' => '',
			'action' => '',
			'priority' => 0,
			'is_active' => 0,
			'rule_class' => RewriteRule::RULE_CUSTOM,
			'description' => '',
			'parameters' => '',
		);
	}

	/**
	 * Constructor for the rewrite_rule class.
	 * @param array $paramarray an associative array or querystring of initial field values
	 */
	public function __construct( $paramarray = array() )
	{
		// Defaults
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields );

		parent::__construct( $paramarray );
		$this->exclude_fields( 'rule_id' );
	}

	/**
	 * Match the stub against this rule
	 * Also sets internal structures based on a successful match
	 * @param string The URL stub to match against
	 * @return boolean True if this rule matches the stub, false if not
	 */
	public function match( $stub )
	{
		if ( preg_match( $this->parse_regex, $stub, $pattern_matches ) > 0 ) {
			$this->entire_match = array_shift( $pattern_matches ); // The entire matched string is returned at index 0
			$named_args = $this->named_args; // Direct call shows a PHP notice

			if ( $parameters = unserialize($this->parameters) ) {
				$this->named_arg_values = array_merge($this->named_arg_values, $parameters);
			}

			foreach ( $named_args as $keys ) {
				foreach ( $keys as $key ) {
					if ( !empty( $pattern_matches[$key] ) ) {
						$this->named_arg_values[$key] = urldecode( str_replace( '%252F', '%2F', $pattern_matches[$key] ) );
					}
				}
			}

			if ( preg_match( '/^\\{\\$(\\w+)\\}$/u', $this->action, $matches ) > 0 ) {
				$this->action = $this->named_arg_values[$matches[1]];
			}

			if ( isset($parameters['require_match']) ) {
				return call_user_func($parameters['require_match'], $this, $stub, $parameters);
			}

			return true;
		}
		return false;
	}

	/**
	 * Builds a URL using this rule based on the passed in data
	 * @param array $args An associative array of arguments to use for replacement in the rule
	 * @param boolean $useall If true (default), then all passed parameters that are not part of the built URL are tacked onto the URL as querystring
	 * @return string The URL created from the substituted arguments
	 */
	public function build( $args, $useall = true, $noamp = false )
	{
		$named_args = $this->named_args; // Direct call prints a PHP notice
		$named_args_combined = array_flip( array_merge( $named_args['required'], $named_args['optional'] ) );

		$args_defined = array_intersect_key( $args, $named_args_combined );
		$args_query = array_diff( $args, $args_defined );
		$args = Plugins::filter( 'rewrite_args', $args, $this->name );
		// Replace defined arguments with their value
		$searches = array();
		$replacements = array();
		foreach ( $named_args as $keys ) {
			foreach ( $keys as $key ) {
				if ( !empty( $args[$key] ) ) {
					$searches[] = '/{\$'.$key.'}/';
					$replacements[] = str_replace( '%2F', '%252F', urlencode( $args[$key] ) );
				}
			}
		}

		// Remove undefined arguments
		$searches[] = '/\([^\(\)]*\$+[^\(\)]*\)/';
		$replacements[] = '';
		// Remove parens left from defined optional arguments
		$searches[] = '/\(|\)/';
		$replacements[] = '';

		$return_url = preg_replace( $searches, $replacements, $this->build_str );

		// Append any remaining args as query string arguments
		if ( $useall ) {
			$args = array_diff_key( $args, $named_args_combined );
			$query_seperator = ( $noamp ) ? '&' : '&';
			$return_url.= ( count( $args ) == 0 ) ? '' : '?' . http_build_query( $args, '', $query_seperator );
		}

		return $return_url;
	}

	/**
	 * Returns a distance from 0 indicating the appropriateness of the rule
	 * based on the passed-in arguments.
	 * @param array $args An array of arguments
	 * @return integer Returns 0 for an exact match, a higher number for less of a match
	 * @todo Enable this logic
	 */
	public function arg_match( $args )
	{
		return 0; // Let's let this logic linger for a little while

		/* This needs further testing once that logic is established */
		$named_args = $this->named_args; // Direct call prints a PHP notice
		$named_args_combined = array_flip( array_merge( $named_args['required'], $named_args['optional'] ) );

		$args = Plugins::filter( 'rewrite_args', $args, $this->name );

		$diffargs = array_diff_key( $args, $named_args_combined );
		$sameargs = array_intersect_key( $args, $named_args_combined );
		$rating = count( $named_args_combined ) - count( $sameargs ) + count( $diffargs );

		return $rating;
	}

	/**
	 * Magic property getter for this class
	 * @param string $name The name of the class property to return
	 * @returns mixed The value of that field in this object
	 */
	public function __get( $name )
	{
		switch ( $name ) {
			case 'named_args':
				if ( empty( $this->m_named_args ) ) {
					preg_match_all( '/(?<!\()\{\$(\w+?)\}(?!\))/u', $this->build_str, $required );
					preg_match_all( '/(?<=\()[^\(\)]*\{\$(\\w+?)\}[^\(\)]*(?=\))/u', $this->build_str, $optional );
					$this->m_named_args['required'] = $required[1];
					$this->m_named_args['optional'] = $optional[1];
				}
				return $this->m_named_args;
			default:
				return parent::__get( $name );
		}
	}

	/**
	 * Saves a new rewrite rule to the rewrite_rules table
	 */
	public function insert()
	{
		return parent::insertRecord( DB::table( 'rewrite_rules' ) );
	}

	/**
	 * Updates an existing rule in the rewrite_rules table
	 */
	public function update()
	{
		return parent::updateRecord( DB::table( 'rewrite_rules' ), array( 'rule_id' => $this->rule_id ) );
	}

	/**
	 * Deletes an existing rule
	 */
	public function delete()
	{
		return parent::deleteRecord( DB::table( 'rewrite_rules' ), array( 'rule_id' => $this->rule_id ) );
	}

	/**
	 * Create an old-style rewrite rule
	 * @param string $build_str
	 * @param string $handler
	 * @param string $action
	 * @return RewriteRule The created rule
	 */
	public static function create_url_rule( $build_str, $handler, $action )
	{
		$arr = explode( '/', $build_str );

		$searches = array('/^([^"\']+)$/', '/^["\'](.+)["\']$/');
		$replacements = array('(?P<\1>.+)', '\1');
		$re_arr = preg_replace( $searches, $replacements, $arr );

		$searches = array('/^([^"\']+)$/', '/^["\'](.+)["\']$/');
		$replacements = array('{$\1}', '\1');
		$str_arr = preg_replace( $searches, $replacements, $arr );

		$regex = '/^' . implode( '\/', $re_arr ) . '\/?$/i';
		$build_str = implode( '/', $str_arr );

		return new RewriteRule( array(
		 'name' => $action,
		 'parse_regex' => $regex,
		 'build_str' => $build_str,
		 'handler' => $handler,
		 'action' => $action,
		 'priority' => 1,
		 'is_active' => 1,
		 'rule_class' => RewriteRule::RULE_CUSTOM,
		 'description' => 'Custom old-style rule.',
		 ) );
	}

}
?>


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

/**
 * Class for storing and retrieving rewrite rules from the DB.
 */
class RewriteRules extends ArrayObject
{
	// cache sorted rules
	protected static $sorted_rules_cache = null;

	/**
	 * Add pre-defined rules to an array of rules only if rules with their names don't already exist
	 *
	 * @param array $rules An array of RewriteRule objects
	 * @return array An array of rules with the system rules potentially added
	 */
	public static function add_system_rules($rules)
	{
		$default_rules = array(
			// Display content
			array( 'name' => 'display_home', 'parse_regex' => '#^(?:page/(?P<page>0|1))?/?$#', 'build_str' => '(page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_home', 'priority' => 1000, 'description' => 'Homepage (index) display' ),
			array( 'name' => 'display_entries', 'parse_regex' => '#^(?:page/(?P<page>[2-9]|[1-9][0-9]+))/?$#', 'build_str' => '(page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_entries', 'priority' => 999, 'description' => 'Display multiple entries' ),
			array( 'name' => 'display_entries_by_date', 'parse_regex' => '#^(?P<year>[1,2]{1}[\d]{3})(?:/(?P<month>[\d]{2}))?(?:/(?P<day>[\d]{2}))?(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => '{$year}(/{$month})(/{$day})(/page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_date', 'priority' => 2, 'description' => 'Displays posts for a specific date.' ),
			array( 'name' => 'display_entries_by_tag', 'parse_regex' => '#^tag/(?P<tag>[^/]*)(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => 'tag/{$tag}(/page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_tag', 'priority' => 5, 'description' => 'Return posts matching specified tag.', 'parameters' => serialize( array( 'require_match' => array('Tag', 'rewrite_tag_exists') ) ) ),
			array( 'name' => 'display_entry', 'parse_regex' => '#^(?P<slug>[^/]+)(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => '{$slug}(/page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_post', 'priority' => 100, 'description' => 'Return entry matching specified slug', 'parameters' => serialize( array( 'require_match' => array('Posts', 'rewrite_match_type'), 'content_type'=>'entry' ) ) ),
			array( 'name' => 'display_page', 'parse_regex' => '#^(?P<slug>[^/]+)(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => '{$slug}(/page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_post', 'priority' => 100, 'description' => 'Return page matching specified slug', 'parameters' => serialize( array( 'require_match' => array('Posts', 'rewrite_match_type'), 'content_type'=>'page' ) ) ),
			array( 'name' => 'display_search', 'parse_regex' => '#^search(?:/(?P<criteria>[^/]+))?(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => 'search(/{$criteria})(/page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'search', 'priority' => 8, 'description' => 'Searches posts' ),
			array( 'name' => 'display_404', 'parse_regex' => '/^.*$/', 'build_str' => '', 'handler' => 'UserThemeHandler', 'action' => 'display_404', 'priority' => 9999, 'description' => 'Displays an error page when a URL is not matched.' ),
			array( 'name' => 'display_post', 'parse_regex' => '#^(?P<slug>[^/]+)(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => '{$slug}(/page/{$page})', 'handler' => 'UserThemeHandler', 'action' => 'display_post', 'priority' => 9998, 'description' => 'Fallback to return post matching specified slug if no content_type match' ),

			// Form actions
			array( 'name' => 'submit_feedback', 'parse_regex' => '#^(?P<id>[0-9]+)/feedback/?$#i', 'build_str' => '{$id}/feedback', 'handler' => 'FeedbackHandler', 'action' => 'add_comment', 'priority' => 8, 'description' => 'Adds a comment to a post' ),

			// User actions
			array( 'name' => 'admin', 'parse_regex' => '#^admin(?:/?$|/(?P<page>[^/]*))/?$#i', 'build_str' => 'admin/({$page})', 'handler' => 'AdminHandler', 'action' => 'admin', 'priority' => 6, 'description' => 'An admin action' ),
			array( 'name' => 'auth', 'parse_regex' => '#^auth/(?P<page>[^/]*)$#i', 'build_str' => 'auth/{$page}', 'handler' => 'UserHandler', 'action' => '{$page}', 'priority' => 7, 'description' => 'A user action or display, for instance the login screen' ),
			array( 'name' => 'user', 'parse_regex' => '#^user/(?P<page>[^/]*)$#i', 'build_str' => 'user/{$page}', 'handler' => 'UserHandler', 'action' => '{$page}', 'priority' => 7, 'description' => 'A user action or display, for instance the login screen' ),
			array( 'name' => 'user_profile', 'parse_regex' => '#^admin/(?P<page>user)/(?P<user>[^/]+)/?$#', 'build_str' => 'admin/{$page}/{$user}', 'handler' => 'AdminHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'The profile page for a specific user' ),

			// AJAX requests
			array( 'name' => 'ajax', 'parse_regex' => '#^ajax/(?P<context>[^/]+)/?$#i', 'build_str' => 'ajax/{$context}', 'handler' => 'AjaxHandler', 'action' => 'ajax', 'priority' => 8, 'description' => 'Ajax handling' ),
			array( 'name' => 'auth_ajax', 'parse_regex' => '#^auth_ajax/(?P<context>[^/]+)/?$#i', 'build_str' => 'auth_ajax/{$context}', 'handler' => 'AjaxHandler', 'action' => 'auth_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling' ),
			array( 'name' => 'admin_ajax', 'parse_regex' => '#^admin_ajax/(?P<context>[^/]+)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for the admin' ),

			// Atom Syndication Format
			array( 'name' => 'rsd', 'parse_regex' => '/^rsd$/i', 'build_str' => 'rsd', 'handler' => 'AtomHandler', 'action' => 'rsd', 'priority' => 1, 'description' => 'RSD output' ),
			array( 'name' => 'atom_entry', 'parse_regex' => '#^(?P<slug>[^/]+)/atom/?$#i', 'build_str' => '{$slug}/atom', 'handler' => 'AtomHandler', 'action' => 'entry', 'priority' => 8, 'description' => 'Atom Publishing Protocol' ),
			array( 'name' => 'atom_feed', 'parse_regex' => '#^atom/(?P<index>[^/]+)(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => 'atom/{$index}(/page/{$page})', 'handler' => 'AtomHandler', 'action' => 'collection', 'priority' => 8, 'description' => 'Atom collection' ),
			array( 'name' => 'atom_feed_comments', 'parse_regex' => '#^atom/comments(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => 'atom/comments(/page/{$page})', 'handler' => 'AtomHandler', 'action' => 'comments', 'priority' => 7, 'description' => 'Entries comments' ),
			array( 'name' => 'atom_feed_tag', 'parse_regex' => '#^tag/(?P<tag>[^/]+)/atom(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => 'tag/{$tag}/atom(/page/{$page})', 'handler' => 'AtomHandler', 'action' => 'tag_collection', 'priority' => 8, 'description' => 'Atom Tag Collection', 'parameters' => serialize( array( 'require_match' => array('Tag', 'rewrite_tag_exists') ) ) ),
			array( 'name' => 'atom_feed_entry_comments', 'parse_regex' => '#^(?P<slug>[^/]+)/atom/comments(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => '{$slug}/atom/comments(/page/{$page})', 'handler' => 'AtomHandler', 'action' => 'entry_comments', 'priority' => 8, 'description' => 'Entry comments' ),
			array( 'name' => 'atom_feed_page_comments', 'parse_regex' => '#^(?P<slug>[^/]+)/atom/comments(?:/page/(?P<page>\d+))?/?$#i', 'build_str' => '{$slug}/atom/comments(/page/{$page})', 'handler' => 'AtomHandler', 'action' => 'entry_comments', 'priority' => 8, 'description' => 'Page comments' ),

			// Atom Publishing Protocol
			array( 'name' => 'atompub_servicedocument', 'parse_regex' => '/^atom$/i', 'build_str' => 'atom', 'handler' => 'AtomHandler', 'action' => 'introspection', 'priority' => 1, 'description' => 'Atom introspection' ),

			// Cron handling
			array( 'name' => 'cron', 'parse_regex' => '#^cron/(?P<time>[0-9.]+)/?$#i', 'build_str' => 'cron/{$time}', 'handler' => 'CronTab', 'action' => 'poll_cron', 'priority' => 1, 'description' => 'Asyncronous cron processing' ),

			// XMLRPC requests
			array( 'name' => 'xmlrpc', 'parse_regex' => '#^xmlrpc/?$#i', 'build_str' => 'xmlrpc', 'handler' => 'XMLRPCServer', 'action' => 'xmlrpc_call', 'priority' => 8, 'description' => 'Handle incoming XMLRPC requests.' ),
		);
		$default_rules = Plugins::filter('default_rewrite_rules', $default_rules);
		$default_rules_properties = array( 'is_active' => 1, 'rule_class' => RewriteRule::RULE_SYSTEM );
		$rule_names = array_flip( array_map( create_function( '$a', 'return $a->name;' ), $rules ) );
		foreach ( $default_rules as $default_rule ) {
			if ( !isset( $rule_names[$default_rule['name']] ) ) {
				$rule_properties = array_merge( $default_rule, $default_rules_properties );
				$rules[] = new RewriteRule( $rule_properties );
			}
		}
		return $rules;
	}

	/**
	 * Return the active rewrite rules, both in the database and applied by plugins
	 *
	 * @return array Array of RewriteRule objects for active rewrite rules
	 */
	public static function get_active()
	{
		static $system_rules;

		if ( !isset($system_rules) ) {
			$sql = "
				SELECT rr.rule_id, rr.name, rr.parse_regex, rr.build_str, rr.handler, rr.action, rr.priority, rr.parameters
				FROM {rewrite_rules} AS rr
				WHERE rr.is_active= 1
				ORDER BY rr.priority";
			$db_rules = DB::get_results( $sql, array(), 'RewriteRule' );

			$system_rules = self::add_system_rules( $db_rules );
		}
		$rewrite_rules = Plugins::filter('rewrite_rules', $system_rules);

		$rewrite_rules = self::sort_rules($rewrite_rules);

		// cache the sorted rules for this instance to use
		$c = __CLASS__;
		self::$sorted_rules_cache = new $c ( $rewrite_rules );
		return self::$sorted_rules_cache;
	}

	/**
	 * Helper function for sorting rewrite rules by priority.
	 *
	 * Required because plugins would insert their rules at the end of the array,
	 * which would allow any other rule (including the one that executes by default
	 * when no other rules work) to execute first.
	 *
	 * @param array $rewrite_rules An array of RewriteRules
	 * @return array Sorted rewrite rules by priority
	 */
	public static function sort_rules($rewrite_rules)
	{
		$pr = array();
		$max_priority = 0;
		foreach ( $rewrite_rules as $r ) {
			$priority = $r->priority;
			$pr[$priority][] = $r;
			$max_priority = max($max_priority, $priority);
		}
		$rewrite_rules = array();
		for ( $z = 0; $z <= $max_priority; $z++ ) {
			if ( isset($pr[$z]) ) {
				$rewrite_rules = array_merge($rewrite_rules, $pr[$z]);
			}
		}
		return $rewrite_rules;
	}

	/**
	 * Get a RewriteRule by its name
	 *
	 * @param string $name The name of the rule
	 * @return RewriteRule The rule requested
	 * @todo Make this return more than one rule when more than one rule matches.
	 */
	public static function by_name( $name )
	{
		static $named = null;

		if ( self::$sorted_rules_cache == null ) {
			self::get_active();
		}

		if ( $named == null ) {
			$named = array();
			$rules = self::$sorted_rules_cache;
			foreach ( $rules as $rule ) {
				$named[$rule->name][] = $rule;
			}
		}

		return isset($named[$name]) ? $named[$name] : false;
	}
}

?>


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

/**
 * XMLRPC Client
 */
class RPCClient
{
	private $url;
	private $method;
	private $params;
	private $request_body;
	private $result = FALSE;

	/**
	 * @param string URL
	 * @param string method to call
	 * @param string method's arguments
	 */
	function __construct( $url, $method, $params )
	{
		if ( ! function_exists( 'xmlrpc_encode_request' ) ) {
			return Error::raise( _t('xmlrpc extension not found') );
		}
		$this->url = $url;
		$this->method = $method;
		$this->params = $params;

		$this->request_body = xmlrpc_encode_request( $method, $params );
	}

	/**
	 * Execute the request. Populates result field.
	 */
	public function execute()
	{
		$rr = new RemoteRequest( $this->url, 'POST' );
		$rr->add_header( 'Content-Type: text/xml;charset=utf-8' );
		$rr->set_body( $this->request_body );

		// should throw an error on failure
		$rr->execute();
		// in that case, we should never get here

		$this->result = xmlrpc_decode($rr->get_response_body());
	}

	/**
	 * Return the (decoded) result of the request, or FALSE if the result was invalid.
	 */
	public function get_result()
	{
		return $this->result;
	}
}

?>


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

/**
 * Habari Session class
 *
 * Manages sessions for the PHP session routines
 *
 */
class Session
{
	/*
	 * The initial data. Used to determine whether we should write anything.
	 */
	private static $initial_data;

	/**
	 * Initialize the session handlers
	 */
	static function init()
	{
		// If https request only set secure cookie
		// IIS sets the value of HTTPS to 'off' if not https
		if ( (defined('FORCE_SECURE_SESSION') && FORCE_SECURE_SESSION == true) ||
			(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] != 'off') ) {
			session_set_cookie_params(null, null, null, true);
		}

		session_set_save_handler(
			array( 'Session', 'open' ),
			array( 'Session', 'close' ),
			array( 'Session', 'read' ),
			array( 'Session', 'write' ),
			array( 'Session', 'destroy' ),
			array( 'Session', 'gc' )
		);
		register_shutdown_function( 'session_write_close' );

		if ( ! isset($_SESSION) ) {
			session_start();
		}
		return true;
	}

	/**
	 * Executed when opening a session.
	 * Not useful for Habari
	 */
	static function open( $save_path, $session_name )
	{
		// Does this function need to do anything?
		return true;
	}

	/**
	 * Executed when closing a session.
	 * Not useful for Habari
	 */
	static function close()
	{
		// Does this function need to do anything?
		return true;
	}

	/**
	 * Read session data from the database to return into the $_SESSION global.
	 * Verifies against a number of parameters for security purposes.
	 *
	 * @param string $session_id The id generated by PHP for the session.
	 * @return string The retrieved session.
	 */
	static function read( $session_id )
	{
		// for offline testing
		$remote_address = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
		// not always set, even by real browsers
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
		$session = DB::get_row( 'SELECT * FROM {sessions} WHERE token = ?', array( $session_id ) );

		// Verify session exists
		if ( !$session ) {
			self::$initial_data = false;
			return false;
		}

		$dodelete = false;

		if ( !defined( 'SESSION_SKIP_SUBNET' ) || SESSION_SKIP_SUBNET != true ) {
			// Verify on the same subnet
			$subnet = self::get_subnet( $remote_address );
			if ( $session->subnet != $subnet ) {
				$dodelete = true;
			}
		}

		// Verify expiry
		if ( HabariDateTime::date_create( time() )->int > $session->expires ) {
			Session::error( _t('Your session expired.'), 'expired_session' );
			$dodelete = true;
		}

		// Verify User Agent
		if ( $user_agent != $session->ua ) {
			$dodelete = true;
		}

		// Let plugins ultimately decide
		$dodelete = Plugins::filter( 'session_read', $dodelete, $session, $session_id );

		if ( $dodelete ) {
			$sql = 'DELETE FROM {sessions} WHERE token = ?';
			$args = array( $session_id );
			$sql = Plugins::filter( 'sessions_clean', $sql, 'read', $args );
			DB::query( $sql, $args );
			return false;
		}

		// Do garbage collection, since PHP is bad at it
		$probability = ini_get( 'session.gc_probability' );
		// Allow plugins to control the probability of a gc event, return >=100 to always collect garbage
		$probability = Plugins::filter( 'gc_probability', ( is_numeric($probability) && $probability > 0 ) ? $probability : 1 );
		if( rand(1, 100) <= $probability ) {
			self::gc( ini_get( 'session.gc_maxlifetime' ) );
		}

		// Throttle session writes, so as to not hammer the DB
		self::$initial_data = ( ini_get('session.gc_maxlifetime') - $session->expires + HabariDateTime::date_create( time() )->int < 120 ) ? $session->data : FALSE;

		return $session->data;
	}

	/**
	 * Commit $_SESSION data to the database for this user.
	 *
	 * @param string $session_id The PHP-generated session id
	 * @param string $data Data from session stored as a string
	 */
	static function write( $session_id, $data )
	{
		// for offline testing
		$remote_address = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
		// not always set, even by real browsers
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';

		// Should we write this data?  Don't bother for search spiders, for example.
		$dowrite = ( $data !== self::$initial_data );
		$dowrite = Plugins::filter( 'session_write', $dowrite, $session_id, $data );

		if ( $dowrite ) {
			// DB::update() checks if the record key exists, and inserts if not
			$record = array(
				'subnet' => self::get_subnet( $remote_address ),
				'expires' => HabariDateTime::date_create( time() )->int + ini_get('session.gc_maxlifetime'),
				'ua' => $user_agent,
				'data' => $data,
			);
			DB::update(
				DB::table( 'sessions' ),
				$record,
				array( 'token' => $session_id )
			);
		}
	}

	/**
	 * Destroy stored session data by session id
	 *
	 * @param string $session_id The PHP generated session id
	 * @return
	 */
	static function destroy( $session_id )
	{
		$sql = 'DELETE FROM {sessions} WHERE token = ?';
		$args = array( $session_id );
		$sql = Plugins::filter( 'sessions_clean', $sql, 'destroy', $args );
		DB::query( $sql, $args );
		return true;
	}

	/**
	 * Session garbage collection deletes expired sessions
	 *
	 * @param mixed $max_lifetime Unused - The session expiration time, in seconds.
	 */
	static function gc( $max_lifetime )
	{
		$sql = 'DELETE FROM {sessions} WHERE expires < ?';
		$args = array( HabariDateTime::date_create( time() )->int );
		$sql = Plugins::filter( 'sessions_clean', $sql, 'gc', $args );
		DB::query( $sql, $args );
		return true;
	}

	/**
	 * Sets the user_id attached to the current session
	 *
	 * @param integer $user_id The user id of the current user
	 */
	static function set_userid( $user_id )
	{
		DB::query( 'UPDATE {sessions} SET user_id = ? WHERE token = ?', array( $user_id, session_id() ) );
	}

	/**
	 * Clear the user_id attached to sessions, delete other sessions that are associated to the user_id
	 * @param integer $user_id The user_id to clear.
	 */
	static function clear_userid( $user_id )
	{
		DB::query( 'DELETE FROM {sessions} WHERE user_id = ? AND token <> ?', array( $user_id, session_id() ) );
		DB::query( 'UPDATE {sessions} SET user_id = NULL WHERE token = ?', array( session_id() ) );
	}

	/**
	 * Adds a value to a session set
	 *
	 * @param string $set Name of the set
	 * @param mixed $value value to store
	 * @param string $key Optional unique key for the set under which to store the value
	 */
	static function add_to_set( $set, $value, $key = null )
	{
		if ( !isset( $_SESSION[$set] ) ) {
			$_SESSION[$set] = array();
		}
		if ( $key ) {
			$_SESSION[$set][$key] = $value;
		}
		else {
			$_SESSION[$set][] = $value;
		}
	}

	/**
	 * Store a notice message in the user's session
	 *
	 * @param string $notice The notice message
	 * @param string $key An optional id that would guarantee a single unique message for this key
	 */
	static function notice( $notice, $key = null )
	{
		self::add_to_set( 'notices', $notice, $key );
	}

	/**
	 * Store an error message in the user's session
	 *
	 * @param string $error The error message
	 * @param string $key An optional id that would guarantee a single unique message for this key
	 */
	static function error( $error, $key = null )
	{
		self::add_to_set( 'errors', $error, $key );
	}

	/**
	 * Return a set of messages
	 *
	 * @param string $set The name of the message set
	 * @param boolean $clear true to clear the messages from the session upon receipt
	 * @return array An array of message strings
	 */
	static function get_set( $set, $clear = true )
	{
		if ( !isset( $_SESSION[$set] ) ) {
			$set_array = array();
		}
		else {
			$set_array = $_SESSION[$set];
		}
		if ( $clear ) {
			unset( $_SESSION[$set] );
		}
		return $set_array;
	}

	/**
	 * Get all notice messsages from the user session
	 *
	 * @param boolean $clear true to clear the messages from the session upon receipt
	 * @return array And array of notice messages
	 */
	static function get_notices( $clear = true )
	{
		return self::get_set( 'notices', $clear );
	}

	/**
	 * Retrieve a specific notice from stored errors.
	 *
	 * @param string $key ID of the notice to retrieve
	 * @param boolean $clear true to clear the notice from the session upon receipt
	 * @return string Return the notice message
	 */
	static function get_notice( $key, $clear = true )
	{
		$notices = self::get_notices( false );
		if ( isset( $notices[$key] ) ) {
			$notice = $notices[$key];
			if ( $clear ) {
				self::remove_notice( $key );
			}
			return $notice;
		}
	}

	/**
	 * Get all error messsages from the user session
	 *
	 * @param boolean $clear true to clear the messages from the session upon receipt
	 * @return array And array of error messages
	 */
	static function get_errors( $clear = true )
	{
		return self::get_set( 'errors', $clear );
	}

	/**
	 * Retrieve a specific error from stored errors.
	 *
	 * @param string $key ID of the error to retrieve
	 * @param boolean $clear true to clear the error from the session upon receipt
	 * @return string Return the error message
	 */
	static function get_error( $key, $clear = true )
	{
		$errors = self::get_errors( false );
		if ( isset( $errors[$key] ) ) {
			$error = $errors[$key];
			if ( $clear ) {
				self::remove_error( $key );
			}
			return $error;
		}
	}

	/**
	 * Removes a specific notice from the stored notices.
	 *
	 * @param string $key ID of the notice to remove
	 * @return boolean True or false depending if the notice was removed successfully.
	 */
	static function remove_notice( $key )
	{
		unset( $_SESSION['notices'][$key] );
		return ( !isset( $_SESSION['notices'][$key] ) ? true : false );
	}

	/**
	 * Removes a specific error from the stored errors.
	 *
	 * @param string $key ID of the error to remove
	 * @return boolean True or false depending if the error was removed successfully.
	 */
	static function remove_error( $key )
	{
		unset( $_SESSION['errors'][$key] );
		return ( !isset( $_SESSION['errors'][$key] ) ? true : false );
	}

	/**
	 * Return output of notice and error messages
	 *
	 * @param boolean $clear true to clear the messages from the session upon receipt
	 * @param array $callback a reference to a callback function for formatting the the messages
	 * @return mixed output of messages
	 */
	static function messages_get( $clear = true, $callback = null )
	{
		$errors = self::get_errors( $clear );
		$notices = self::get_notices( $clear );

		// if callback is 'array', then just return the raw data
		if ( $callback == 'array' ) {
			$output = array_merge( $errors, $notices );
		}
		// if a function is passed in $callback, call it
		else if ( isset( $callback ) && is_callable( $callback ) ) {
			$output = call_user_func( $callback, $notices, $errors );
		}
		// default to html output
		else {
			$output = Format::html_messages( $notices, $errors );
		}

		return $output;
	}

	/**
	 * Output notice and error messages
	 *
	 * @param boolean $clear true to clear the messages from the session upon receipt
	 * @param boolean $use_humane_msg true to use the humane messages system, false
	 * to use and unordered list
	 */
	static function messages_out( $clear = true, $callback = null )
	{
		echo self::messages_get( $clear, $callback );
	}

	/**
	 * Determine if there are messages that should be displayed
	 * Messages are not cleared when calling this function.
	 *
	 * @return boolean true if there are messages to display.
	 */
	static function has_messages()
	{
		return ( count( self::get_notices( false ) + self::get_errors( false ) ) ) ? true : false;
	}

	/**
	 * Determine if there are error messages to display
	 *
	 * @param string $key Optional key of the unique error message
	 * @return boolean true if there are errors, false if not
	 */
	static function has_errors( $key = null )
	{
		if ( isset( $key ) ) {
			return isset( $_SESSION['errors'][$key] );
		}
		else {
			return count( self::get_errors( false ) ) ? true : false;
		}
	}

	protected static function get_subnet( $remote_address = '' ) {

		$long_addr = ip2long( $remote_address );

		if ( $long_addr >= ip2long('0.0.0.0') && $long_addr <= ip2long('127.255.255.255') ) {
			// class A
			return sprintf("%u", $long_addr) >> 24;
		}
		else if ( $long_addr >= ip2long('128.0.0.0') && $long_addr <= ip2long('191.255.255.255') ) {
			// class B
			return sprintf("%u", $long_addr) >> 16;
		}
		else {
			// class C, D, or something we missed
			return sprintf("%u", $long_addr) >> 8;
		}

	}

}

?>


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

/**
 * Habari Singleton class
 *
 * Singleton base class for subclassing generic singleton pattern
 * classes
 */
abstract class Singleton
{
	// Single instance of class available.
	private static $instances = array();

	/**
	 * Declarations that extend this method must have the same signature
	 * (arguments and returned types) to pass E_STRICT
	 *
	 * @return object instance
	 */
	protected static function instance()
	{
		/*
		 * It is important to note that subclasses MUST override this
		 * method, as get_class will ALWAYS return 'Singleton' when
		 * subclasses call this method through inheritance
		 * return self::getInstanceOf( get_class() );
		 */
		trigger_error(_t('Not implemented: instance'), E_USER_WARNING);
		return null;
	}

	/**
	 * Returns the single shared static instance variable
	 * which facilitates the Singleton pattern
	 *
	 * @note  each subclass should implement an instance() method which
	 * passes the class name to the parent::getInstanceOf() function.
	 *
	 * @return object instance
	 */
	/*
	 * The overridden methods can't have a different signature, so there needs
	 * to be two functions: one, internal to a class, that calls this one
	 * (a singleton factory) that produces objects of the requested classes
	 */
	protected static function getInstanceOf( $class )
	{
		if ( ! isset( self::$instances[$class] ) ) {
			self::$instances[$class] = new $class();
		}
		return self::$instances[$class];
	}

	/**
	 * Prevent instance construction and cloning (copying of object
	 * instance)
	 */
	protected final function __construct() {}
	private final function __clone() {}
}

?>