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

/**
 * Habari Site class
 *
 * Contains functions for getting details about the site directories and URLs.
 *
 */
class Site
{
	/**
	 * Constants
	 * CONFIG_LOCAL Local installation
	 * CONFIG_SUBDIR Subdirectory or multisite installation
	 * CONFIG_SUBDOMAIN Subdomain installation
	 */
	const CONFIG_LOCAL = 0;
	const CONFIG_SUBDIR = 1;
	const CONFIG_SUBDOMAIN = 2;

	/**
	 * @staticvar $config_path Filesystem path to config.php
	 * @staticvar $config_dir Multisite directory to config.php
	 * @staticvar $config_type Installation type (local, subdir, subdomain)
	 * @staticvar $scriptname the name of the currently executing script (index.php)
	 * @staticvar $habari_url fully-qualified URL to the habari directory
	 */
	static $config_path;
	static $config_dir;
	static $config_type = Site::CONFIG_LOCAL;
	static $scriptname;
	static $habari_url;

	/**
	 * Constructor
	 * This class should not be instantiated
	 */
	private function __construct()
	{
	}

	/**
	 * script_name is a helper function to determine the name of the script
	 * not all PHP installations return the same values for $_SERVER['SCRIPT_URL']
	 * and $_SERVER['SCRIPT_NAME']
	 */
	public static function script_name()
	{
		switch ( true ) {
			case isset ( self::$scriptname ):
				break;
			case isset( $_SERVER['SCRIPT_NAME'] ):
				self::$scriptname = $_SERVER['SCRIPT_NAME'];
				break;
			case isset( $_SERVER['PHP_SELF'] ):
				self::$scriptname = $_SERVER['PHP_SELF'];
				break;
			default:
				Error::raise(_t('Could not determine script name.'));
				die();
		}
		return self::$scriptname;
	}

	/**
	 * is() returns a boolean value for whether the current site is the
	 * primary site, or a multi-site, as determined by the location of
	 * the config.php that is in use for this request.
	 * valid values are "main" and "primary" (synonymous) and "multi"
	 * @examples:
	 *	if ( Site::is('main') )
	 *	if ( Site::is('multi') )
	 * @param string The name of the boolean to test
	 * @return bool the result of the check
	 */
	public static function is( $what )
	{
		switch ( strtolower( $what ) ) {
			case 'main':
			case 'primary':
				if ( Site::$config_type == Site::CONFIG_LOCAL ) {
					return true;
				}
				else {
					return false;
				}
				break;
			case 'multi':
				if ( Site::$config_type != Site::CONFIG_LOCAL ) {
					return true;
				}
				else {
					return false;
				}
				break;
		}
	}

	/**
	 * get_url returns a fully-qualified URL
	 *	'host' returns http://www.habariproject.org
	 *	'habari' returns http://www.habariproject.org/habari, if you
	 *		have Habari installed into a /habari/ sub-directory
	 *	'user' returns one of the following:
	 *		http://www.habariproject.org/user
	 *		http://www.habariproject.org/user/sites/x.y.z
	 *	'theme' returns one of the following:
	 *		http://www.habariproject.org/user/themes/theme_name
	 *		http://www.habariproject.org/user/sites/x.y.z/themes/theme_name
	 *	'admin' returns http://www.habariproject.org/admin
	 *	'admin_theme' returns http://www.habariproject.org/system/admin
	 *	'system' returns http://www.habariproject.org/system
	 *	'scripts' returns http://www.habariproject.org/scripts
	 *	'3rdparty' returns http://www.habariproject.org/3rdparty
	 *	'hostname' returns www.habariproject.org
	 * @param string the name of the URL to return
	 * @param bool whether to include a trailing slash.  Default: No
	 * @return string URL
	 */
	public static function get_url( $name, $trail = false )
	{
		$url = '';

		switch( strtolower( $name ) ) {
			case 'host':
				$protocol = 'http';
				// If we're running on a port other than 80, i
				// add the port number to the value returned
				// from host_url
				$port = 80; // Default in case not set.
				if ( isset( $_SERVER['SERVER_PORT'] ) ) {
					$port = $_SERVER['SERVER_PORT'];
				}
				$portpart = '';
				$host = Site::get_url('hostname');
				// if the port isn't a standard port, and isn't part of $host already, add it
				if ( ( $port != 80 ) && ( $port != 443 ) && ( MultiByte::substr($host, MultiByte::strlen($host) - strlen($port) ) != $port ) ) {
					$portpart = ':' . $port;
				}
				if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] != 'off' ) {
					$protocol = 'https';
				}
				$url = $protocol . '://' . $host . $portpart;
				break;
			case 'habari':
				if ( NULL !== self::$habari_url ) {
					$url = self::$habari_url;
				}
				else {
					$url = Site::get_url( 'host' );
					$path = trim( dirname( Site::script_name() ), '/\\' );
					if ( '' != $path ) {
						$url .= '/' . $path;
					}
					self::$habari_url = $url;
				}
				break;
			case 'user':
				$url = Site::get_url('host') . Site::get_path('base', true) . Site::get_path('user');
				break;
			case 'theme':
				$theme = Themes::get_theme_dir();
				if ( file_exists( Site::get_dir( 'config' ) . '/themes/' . $theme ) ) {
					$url = Site::get_url( 'user' ) .  '/themes/' . $theme;
				}
				elseif ( file_exists( HABARI_PATH . '/user/themes/' . $theme ) ) {
					$url = Site::get_url( 'habari' ) . '/user/themes/' . $theme;
				}
				elseif ( file_exists( HABARI_PATH . '/3rdparty/themes/' . $theme ) ) {
					$url = Site::get_url( 'habari') . '/3rdparty/themes/' . $theme;
				}
				else {
					$url = Site::get_url( 'habari' ) . '/system/themes/' . $theme;
				}
				break;
			case 'admin':
				$url = Site::get_url( 'habari' ) . '/admin';
				break;
			case 'admin_theme':
				$url = Site::get_url( 'habari' ) . '/system/admin';
				break;
			case 'login':
				$url = Site::get_url( 'habari' ) . '/auth/login';
				break;
			case 'logout':
				$url = Site::get_url( 'habari' ) . '/auth/logout';
				break;
			case 'system':
				$url = Site::get_url( 'habari' ) . '/system';
				break;
			case 'scripts':
				$url = Site::get_url( 'habari' ) . '/scripts';
				break;
			case '3rdparty':
				$url = Site::get_url( 'habari' ) . '/3rdparty';
				break;
			case 'hostname':
				// HTTP_HOST is not set for HTTP/1.0 requests
				$url = ( $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' || !isset( $_SERVER['HTTP_HOST'] ) ) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
				break;
		}
		$url .= Utils::trail( $trail );
		$url = Plugins::filter( 'site_url_' . $name, $url );
		return $url;
	}

	/**
	 * get_path returns a relative URL path, without leading protocol or host
	 *	'base' returns the URL sub-directory in which Habari is installed, if any.
	 *	'user' returns one of the following:
	 *		user
	 *		user/sites/x.y.z
	 *	'theme' returns one of the following:
	 *		/user/themes/theme_name
	 *		/user/sites/x.y.z/themes/theme_dir
	 * @param string the name of the path to return
	 * @param bool whether to include a trailing slash.  Default: No
	 */
	public static function get_path( $name, $trail = false )
	{
		$path = '';
		switch ( strtolower( $name ) ) {
			case 'base':
				$path = rtrim(dirname(Site::script_name()),'/\\');
				break;
			case 'user':
				if ( Site::is('main') ) {
					$path = 'user';
				}
				else {
					$path = ltrim( str_replace ( HABARI_PATH, '', Site::get_dir('config') ), '/' );
				}
				break;
			case 'theme':
				$theme = Themes::get_theme_dir();
				if ( file_exists( Site::get_dir( 'config' ) . '/themes/' . $theme ) ) {
					$path = Site::get_path('user') . '/themes/' . $theme;
				}
				elseif ( file_exists( HABARI_PATH . '/3rdparty/themes/' . $theme ) ) {
					$url = Site::get_url( 'habari') . '/3rdparty/themes/' . $theme;
				}
				else {
					$path = Site::get_path('base') . '/user/themes/' . $theme;
				}
				break;
		}
		$path .= Utils::trail( $trail );
		// if running Habari in docroot, get_url('base') will return
		// a double slash.  Let's fix that.
		$path = str_replace( '//', '/', $path);
		$path = Plugins::filter( 'site_path_' . $name, $path );
		return $path;
	}

	/**
	 * get_dir returns a complete filesystem path to the requested item
	 *	'config_file' returns the complete path to the config.php file, including the filename
	 *	'config' returns the path of the directory containing config.php
	 *	'user' returns the path of the user directory
	 *	'theme' returns the path of the site's active theme
	 * @param string the name of the path item to return
	 * @param bool whether to include a trailing slash.  Default: No
	 * @return string Path
	 */
	public static function get_dir( $name, $trail = false )
	{
		$path = '';

		switch ( strtolower( $name ) ) {
			case 'config_file':
				$path = Site::get_dir('config') . '/config.php';
				break;
			case 'config':
				if ( self::$config_path ) {
					return self::$config_path;
				}

				self::$config_path = HABARI_PATH;

				$config_dirs = preg_replace( '/^' . preg_quote( HABARI_PATH, '/' ) . '\/user\/sites\/(.*)\/config.php/', '$1', Utils::glob( HABARI_PATH . '/user/sites/*/config.php' ) );

				if ( empty( $config_dirs ) ) {
					return self::$config_path;
				}

				$server = InputFilter::parse_url( Site::get_url('habari') ) ;
				$server = ( isset( $server['port'] ) ) ? $server['port'] . '.' . $server['host'] . '.' : $server['host'] . '.';

				$request = explode('/', trim( $_SERVER['REQUEST_URI'], '/' ) );
				$match = trim( $server, '.' );
				$x = count( $request );
				do {
					if ( in_array( $match, $config_dirs ) ) {
						self::$config_dir = $match;
						self::$config_path = HABARI_PATH . '/user/sites/' . self::$config_dir;
						self::$config_type = ($x > 0) ? Site::CONFIG_SUBDOMAIN : Site::CONFIG_SUBDIR;
						break;
					}

					$match = MultiByte::substr( $match, MultiByte::strpos( $match, '.' ) + 1 );
					$x--;
				} while( MultiByte::strpos( $match,'.' ) !== false );

				$path = self::$config_path;
				break;
			case 'user':
				if ( Site::get_dir('config') == HABARI_PATH ) {
					$path = HABARI_PATH . '/user';
				}
				else {
					$path = Site::get_dir('config');
				}
				break;
			case 'theme':
				$theme = Themes::get_theme_dir();
				if ( file_exists( Site::get_dir( 'config' ) . '/themes/' . $theme ) ) {
					$path = Site::get_dir('user') . '/themes/' . $theme;
				}
				elseif ( file_exists( HABARI_PATH . '/user/themes/' . $theme ) ) {
					$path = HABARI_PATH . '/user/themes/' . $theme;
				}
				elseif ( file_exists( HABARI_PATH . '/3rdparty/themes/' . $theme ) ) {
					$url = Site::get_url( 'habari') . '/3rdparty/themes/' . $theme;
				}
				else {
					$path = HABARI_PATH . '/system/themes/' . $theme;
				}
				break;
			case 'admin_theme':
				$path = HABARI_PATH . '/system/admin';
				break;
		}
		$path .= Utils::trail( $trail );
		$path = Plugins::filter( 'site_dir_' . $name, $path );
		return $path;
	}

	/**
	 * out_url echos out a URL
	 * @param string the URL to display
	 * @param bool whether or not to include a trailing slash.  Default: No
	 */
	public static function out_url( $url, $trail = false )
	{
		echo Site::get_url( $url, $trail );
	}

	/**
	 * out_path echos a URL path
	 * @param string the URL path to display
	 * @param bool whether or not to include a trailing slash.  Default: No
	 */
	public static function out_path( $path, $trail = false )
	{
		echo Site::get_path( $path, $trail );
	}

	/**
	 * our_dir echos our a filesystem directory
	 * @param string the filesystem directory to display
	 * @param bool whether or not to include a trailing slash.  Default: No
	 */
	public static function out_dir( $dir, $trail = false )
	{
		echo Site::get_dir( $dir, $trail );
	}

/*
I'm unclear whether we need these.  If so, they likely belong in a new method, since they're neither URLs, paths, nor directories.

			case 'config_type':
				self::get_config_path();
				$path= self::$config_type;
				break;
			case 'config_name':
				self::get_dir('config');
				$path= self::$config_dir;
				break;
*/

}

?>


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

require( HABARI_PATH . '/3rdparty/smarty/libs/Smarty.class.php' );

/**
 * Habari SmartyEngine template engine subclass
 *
 * The SmartyEngine is a subclass of the abstract TemplateEngine class
 * which uses the Smarty templating system to handle display of template files
 *
 */
class SmartyEngine extends TemplateEngine
{
	// Actual Smarty template processor
	private $smarty = null;

	/**
	 * Constructor for SmartyEngine
	 *
	 * Sets up default values for required settings.
	 */
	public function __construct()
	{
		$this->smarty = new Smarty();
		$this->smarty->compile_dir = HABARI_PATH . '/3rdparty/smarty/templates_c/';
		$this->smarty->cache_dir = HABARI_PATH . '/3rdparty/smarty/cached/';
		$this->smarty->plugins_dir = HABARI_PATH . '/3rdparty/smarty/libs/plugins/';
		$this->smarty->force_compile = DEBUG;
		$this->smarty->compile_check = DEBUG;
		$this->smarty->caching = !DEBUG;
	}

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

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

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

	/**
	 * 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 name is set, false if not set
	 */
	public function __isset( $key )
	{
		return ( !empty( $this->smarty->get_template_vars( $key ) ) ) ? true : false;
	}

	/**
	 * 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__);
			// Set directory now to allow theme to load theme directory after constructor.
			$this->smarty->template_dir = dirname($template_file);
			$this->smarty->display( basename($template_file) );
		}

	}

	/**
	 * Returns the existance of the specified template name
	 *
	 * @param template $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 ), '.tpl' ) );
			$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 )
	{
		// Set directory now to allow theme to load theme directory after contructor.
		$this->smarty->template_dir = $this->template_dir;
		$this->smarty->fetch( $template );
	}

	/**
	 * 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 = '' )
	{
		if ( ! is_array( $key ) ) {
			$this->smarty->assign( $key, $value );
		}
		else {
			$this->smarty->assign( $key );
		}
	}

	/**
	 * 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( $name )
	{
		return isset($this->smarty->_tpl_vars[$name]);
	}

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

?>

repo_name: DaSourcerer/habari


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

/**
 * RequestProcessor using sockets (fsockopen).
 */
class SocketRequestProcessor implements RequestProcessor
{
	private $response_body = '';
	private $response_headers = '';
	private $executed = FALSE;

	/**
	 * Maximum number of redirects to follow.
	 */
	private $max_redirs = 5;

	private $redir_count = 0;

	public function execute( $method, $url, $headers, $body, $timeout )
	{
		$result = $this->_request( $method, $url, $headers, $body, $timeout );

		if ( $result && ! Error::is_error( $result ) ) {
			list( $response_headers, $response_body )= $result;
			$this->response_headers = $response_headers;
			$this->response_body = $response_body;
			$this->executed = TRUE;

			return TRUE;
		}
		else {
			return $result;
		}
	}

	private function _request( $method, $url, $headers, $body, $timeout )
	{
		$urlbits = InputFilter::parse_url( $url );

		return $this->_work( $method, $urlbits, $headers, $body, $timeout );
	}

	/**
	 * @todo Does not honor timeouts on the actual request, only on the connect() call.
	 */
	private function _work( $method, $urlbits, $headers, $body, $timeout )
	{
		$_errno = 0;
		$_errstr = '';

		if ( !isset( $urlbits['port'] ) || $urlbits['port'] == 0 ) {
			if ( array_key_exists( $urlbits['scheme'], Utils::scheme_ports() ) ) {
				$urlbits['port'] = Utils::scheme_ports($urlbits['scheme']);
			}
			else {
				// todo: Error::raise()?
				$urlbits['port'] = 80;
			}
		}

		if ( !in_array( $urlbits['scheme'], stream_get_transports() ) ) {
			$transport = ( $urlbits['scheme'] == 'https' ) ? 'ssl' : 'tcp';
		}
		else {
			$transport = $urlbits['scheme'];
		}

		$fp = @fsockopen( $transport . '://' . $urlbits['host'], $urlbits['port'], $_errno, $_errstr, $timeout );

		if ( $fp === FALSE ) {
			return Error::raise( sprintf( _t('%s: Error %d: %s while connecting to %s:%d'), __CLASS__, $_errno, $_errstr, $urlbits['host'], $urlbits['port'] ),
				E_USER_WARNING );
		}

		// timeout to fsockopen() only applies for connecting
		stream_set_timeout( $fp, $timeout );

		// fix headers
		$headers['Host'] = $urlbits['host'];
		$headers['Connection'] = 'close';

		// merge headers into a list
		$merged_headers = array();
		foreach ( $headers as $k => $v ) {
			$merged_headers[] = $k . ': ' . $v;
		}

		// build the request
		$request = array();
		$resource = $urlbits['path'];
		if ( isset( $urlbits['query'] ) ) {
			$resource.= '?' . $urlbits['query'];
		}

		$request[] = "{$method} {$resource} HTTP/1.1";
		$request = array_merge( $request, $merged_headers );

		$request[] = '';

		if ( $method === 'POST' ) {
			$request[] = $body;
		}

		$request[] = '';

		$out = implode( "\r\n", $request );

		if ( ! fwrite( $fp, $out, strlen( $out ) ) ) {
			return Error::raise( _t('Error writing to socket.') );
		}

		$in = '';

		while ( ! feof( $fp ) ) {
			$in.= fgets( $fp, 1024 );
		}

		fclose( $fp );

		list( $header, $body )= explode( "\r\n\r\n", $in );

		// to make the following REs match $ correctly
		// and thus not break parse_url
		$header = str_replace( "\r\n", "\n", $header );

		preg_match( '#^HTTP/1\.[01] ([1-5][0-9][0-9]) ?(.*)#', $header, $status_matches );

		if ( $status_matches[1] == '301' || $status_matches[1] == '302' ) {
			if ( preg_match( '|^Location: (.+)$|mi', $header, $location_matches ) ) {
				$redirect_url = $location_matches[1];

				$redirect_urlbits = InputFilter::parse_url( $redirect_url );

				if ( !isset( $redirect_url['host'] ) ) {
					$redirect_urlbits['host'] = $urlbits['host'];
				}

				$this->redir_count++;

				if ( $this->redir_count > $this->max_redirs ) {
					return Error::raise( _t('Maximum number of redirections exceeded.') );
				}

				return $this->_work( $method, $redirect_urlbits, $headers, $body, $timeout );
			}
			else {
				return Error::raise( _t('Redirection response without Location: header.') );
			}
		}

		if ( preg_match( '|^Transfer-Encoding:.*chunked.*|mi', $header ) ) {
			$body = $this->_unchunk( $body );
		}

		return array( $header, $body );
	}

	private function _unchunk( $body )
	{
		/* see <http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html> */
		$result = '';
		$chunk_size = 0;

		do {
			$chunk = explode( "\r\n", $body, 2 );
			list( $chunk_size_str, )= explode( ';', $chunk[0], 2 );
			$chunk_size = hexdec( $chunk_size_str );

			if ( $chunk_size > 0 ) {
				$result .= MultiByte::substr( $chunk[1], 0, $chunk_size );
				$body = MultiByte::substr( $chunk[1], $chunk_size+1 );
			}
		}
		while ( $chunk_size > 0 );
		// this ignores trailing header fields

		return $result;
	}

	public function get_response_body()
	{
		if ( ! $this->executed ) {
			return Error::raise( _t('Request did not yet execute.') );
		}

		return $this->response_body;
	}

	public function get_response_headers()
	{
		if ( ! $this->executed ) {
			return Error::raise( _t('Request did not yet execute.') );
		}

		return $this->response_headers;
	}
}


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

/**
 * Habari Stack Class
 *
 * This class allows Habari to accumulate a group of unique values that
 * can be output using a specific formatting string.
 * This is useful for collecting a set of unique javascript references to output
 * and then insert them at a specific point on the page.
 *
 * <code>
 * // Add jquery to the javascript stack:
 * Stack::add( 'template_header_javascript', Site::get_url('scripts') . '/jquery.js', 'jquery' );
 *
 * // Add stylesheet to theme_stylesheet stack with media type
 * Stack::add( 'template_stylesheet', array( Site::get_url('theme') . '/style.css', 'screen' ), 'style' );
 *
 * // Output the javascript stack:
 * Stack::out( 'template_header_javascript', '<script src="%s" type="text/javascript"></script>' );
 *
 * // Output the theme_stylesheet stack:
 * Stack::out( 'template_stylesheet', '<link rel="stylesheet" type="text/css"  href="%s" media="%s">' );
 * </code>
 *
 */
class Stack
{
	private static $stacks = array();
	private static $stack_sort = array();
	private static $sorting;

	/**
	 * Private constructor for Stack.
	 * Stack objects should only be created using the static
	 * method Static::create_stack(), or one of the methods that
	 * adds a value directly to a stack.  This prevents multiple Stack
	 * objects from being created with the same name.
	 *
	 * @param mixed $input An array or ArrayObject to create the stack from.
	 * @return array The created stack
	 **/
	private function __construct( $input )
	{
		parent::__construct( $input );
	}

	/**
	 * Retreive a named stack instance
	 * @param string $stack_name The name of the stack to return
	 * @return Stack The requested stack
	 **/
	public static function get_named_stack( $stack_name )
	{
		if ( isset( self::$stacks[$stack_name] ) ) {
			return self::$stacks[$stack_name];
		}
		else {
			return self::create_stack( $stack_name );
		}
	}

	/**
	 * Check for the existence of a given stack item.
	 *
	 * @param string $stack_name The name of the stack in which to check.
	 * @param string $value The value to check for.
	 * @return boolean TRUE if the item exists, FALSE otherwise.
	 */
	public static function has ( $stack_name, $value_name )
	{

		// get the stack
		$stack = self::get_named_stack( $stack_name );

		if ( isset( $stack[ $value_name ] ) ) {
			return true;
		}
		else {
			return false;
		}

	}

	/**
	 * Get a single item from a given stack.
	 *
	 * @param string $stack_name The name of the stack to fetch an item from.
	 * @param string $value The item to fetch.
	 * @param mixed $default_value The default value to return if the item does not exist in the stack.
	 * @return mixed The item, or $default_value if it does not exist.
	 */
	public static function get_item ( $stack_name, $value_name, $default_value = null )
	{

		// get the stack
		$stack = self::get_named_stack( $stack_name );

		if ( isset( $stack[ $value_name ] ) ) {
			return $stack[ $value_name ];
		}
		else {
			return $default_value;
		}

	}

	/**
	 * Creates and retreives a named stack instance
	 * @param string $stack_name The name of the stack to create and return
	 * @return array The created stack
	 **/
	public static function create_stack( $stack_name )
	{
		if ( empty( self::$stacks[$stack_name] ) ) {
			$stack = array();
			self::$stacks[$stack_name] = $stack;
			self::$stack_sort[$stack_name] = array();
		}
		return self::$stacks[$stack_name];
	}

	/**
	 * Add a value to a stack
	 * @param string $stack_name The name of the stack
	 * @param mixed $value The value to add
	 * @param string $value_name The name of the value to add
	 * @param string $after The name of the stack element to insert this new element after
	 * @return array The stack that was added to
	 **/
	public static function add( $stack_name, $value, $value_name = null, $after = null )
	{
		$stack = self::get_named_stack( $stack_name );
		$value_name = $value_name ? $value_name : md5( serialize( $value ) );
		if ( !is_null($after) ) {
			if ( !is_array($after) ) {
				$after = array($after);
			}
			foreach ($after as $a ) {
				if ( !isset(self::$stack_sort[$stack_name]) ) {
					self::$stack_sort[$stack_name] = array();
				}
				if ( !isset(self::$stack_sort[$stack_name][$a]) ) {
					self::$stack_sort[$stack_name][$a] = array();
				}
				self::$stack_sort[$stack_name][$a][$value_name] = $value_name;
			}
		}
		$stack[$value_name] = $value;
		self::$stacks[$stack_name] = $stack;
		return $stack;
	}

	/**
	 * Remove a value to a stack
	 * @param string $stack_name The name of the stack
	 * @param string $value_name The name of the value to remove
	 * @return array The rest of the stack, post-remove
	 **/
	public static function remove( $stack_name, $value_name )
	{
		$stack = self::get_named_stack( $stack_name );
		if ( isset( $stack[$value_name] ) ) {
			unset( $stack[$value_name] );
		}
		self::$stacks[$stack_name] = $stack;
		return $stack;
	}

	public static function get_sorted_stack( $stack_name )
	{
		self::$sorting = $stack_name;
		$stack = self::get_named_stack($stack_name);

		uksort($stack, array('Stack', 'sort_stack_cmp'));
		return $stack;
	}

	public static function sort_stack_cmp( $a, $b )
	{
		$aa = isset(self::$stack_sort[self::$sorting][$a]) ? self::$stack_sort[self::$sorting][$a] : array();
		$ba = isset(self::$stack_sort[self::$sorting][$b]) ? self::$stack_sort[self::$sorting][$b] : array();
		$acb = isset($aa[$b]);
		$bca = isset($ba[$a]);
		$ac = count($aa);
		$bc = count($ba);
		if ( ($acb && $bca) || !($acb || $bca) ) {
			if ( $ac == $bc ) {
				// they are equal in 'bias', so go with the order in which they were added.
				return 1;
			}
			return $ac > $bc ? -1 : 1;
		}
		elseif ( $acb ) {
			return -1;
		}
		elseif ( $bca ) {
			return 1;
		}
	}

	/**
	 * Returns all of the values of the stack
	 * @param string $stack_name The name of the stack to output
	 * @param mixed $format A printf-style formatting string or callback used to output each stack element
	 **/
	public static function get( $stack_name, $format = null)
	{
		$out = '';
		$stack = self::get_sorted_stack( $stack_name );
		$stack = Plugins::filter( 'stack_out', $stack, $stack_name, $format );
		foreach( $stack as $element ) {
			if ( is_callable($format) ) {
				$out.= call_user_func_array( $format, (array) $element );
			}
			elseif ( is_string( $format ) ) {
				$out.= vsprintf( $format, (array) $element );
			}
			else {
				$out.= $element;
			}
		}
		return $out;
	}

	/**
	 * Outputs all of the values of the stack
	 * @param string $stack_name The name of the stack to output
	 * @param mixed $format A printf-style formatting string or callback used to output each stack element
	 **/
	public static function out( $stack_name, $format = null)
	{
		echo self::get( $stack_name, $format );
	}

	/**
	 * A callback for Stack::get() that outputs scripts as reference or inline depending on their content
	 *
	 * @param string $element The script element in the stack
	 * @return string The resulting script tag
	 */
	public static function scripts( $element )
	{
		if ( ( strpos($element, 'http://') === 0 || strpos($element, 'https://' ) === 0 ) && strpos($element, "\n") === FALSE ) {
			$output = sprintf( '<script src="%s" type="text/javascript"></script>'."\r\n", $element);
		}
		else {
			$output = sprintf( '<script type="text/javascript">%s</script>'."\r\n", $element);
		}
		return $output;
	}

	/**
	 * A callback for Stack::get() that outputs styles as link or inline style tags depending on their content
	 *
	 * @param string $element The style element in the stack
	 * @param string $typename The media disposition of the content
	 * @return string The resulting style or link tag
	 */
	public static function styles( $element, $typename )
	{
		if ( ( strpos($element, 'http://') === 0 || strpos($element, 'https://' ) === 0 ) && strpos($element, "\n") === FALSE ) {
			$output = sprintf( '<link rel="stylesheet" type="text/css" href="%s" media="%s">'."\r\n", $element, $typename);
		}
		else {
			$output = sprintf( '<style type="text/css" media="%s">%s</style>'."\r\n", $typename, $element);
		}
		return $output;
	}
}

?>


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

/**
 * SuperGlobals class
 *
 */
class SuperGlobal extends ArrayIterator
{
	protected $values = array();
	protected $raw_values = array();

	public function __construct($array)
	{
		if (!is_array($array) && !$array instanceof SuperGlobal) {
			throw new Exception('Parameter must be array or SuperGlobal');
		}
		parent::__construct($array);
	}

	/**
	 * Convert $_GET, $_POST and $_SERVER into SuperGlobal instances, also kill $_REQUEST
	 *
	 * @return
	 */
	public static function process_gps()
	{
		/* We should only revert the magic quotes once per page hit */
		static $revert = true;

		if (!$revert) {
			// our work has already been done
			return;
		}

		if ( get_magic_quotes_gpc() ) {
			$_GET = Utils::stripslashes($_GET);
			$_POST = Utils::stripslashes($_POST);
		}

		$_GET = new SuperGlobal($_GET);
		$_POST = new SuperGlobal($_POST);
		$_SERVER = new SuperGlobal($_SERVER);
		unset($_REQUEST);

		$revert = false;
	}

	/**
	 * Convert $_COOKIE into SuperGlobal instance
	 *
	 */
	public static function process_c()
	{
		/* We should only revert the magic quotes once per page hit */
		static $revert = true;

		if (!$revert) {
			// our work has already been done
			return;
		}

		if ( get_magic_quotes_gpc() ) {
			$_COOKIE = Utils::stripslashes($_COOKIE);
		}

		$_COOKIE = new SuperGlobal($_COOKIE);

		$revert = false;
	}

	/**
	 * Return the raw, unfiltered value of the requested index
	 *
	 * @param mixed $index The index of the value
	 * @return mixed The unfiltered value
	 */
	public function raw($index)
	{
		if(isset($this->raw_values[$index])) {
			return $this->raw_values[$index];
		}
		$cp = $this->get_array_copy_raw();
		if(isset($cp[$index])) {
			$this->raw_values[$index] = $cp[$index];
			return $this->raw_values[$index];
		}
	}

	/**
	 * Return a copy of the filtered array. Implments ArrayIterator::getArrayCopy()
	 */
	public function getArrayCopy()
	{
		return array_map( array( $this, 'base_filter'), parent::getArrayCopy() );
	}

	/**
	 * Return a copy of the unfiltered array.
	 */
	public function get_array_copy_raw()
	{
		return parent::getArrayCopy();
	}

	/**
	 * Return the current array element, filtered. Implements ArrayIterator::current()
	 */
	public function current()
	{
		return $this->offsetGet(parent::key());
	}

	/**
	 * Return the value of an array offset.  Allows the values to be filtered
	 *
	 * @param mixed $index The index of the array
	 * @return mixed The filtered value at the array index
	 */
	public function offsetGet($index)
	{
		if(isset($this->values[$index])) {
			return $this->values[$index];
		}
		$cp = $this->get_array_copy_raw();
		if(isset($cp[$index])) {
			$this->values[$index] = $this->base_filter($cp[$index]);
			return $this->values[$index];
		}
	}

	/**
	 * Set the value of the array, clear caches for that index
	 *
	 * @param mixed $index The array index
	 * @param mixed $value Tha value to store
	 */
	public function offsetSet($index, $value)
	{
		unset($this->values[$index]);
		unset($this->raw_values[$index]);
		parent::offsetSet($index, $value);
	}

	/**
	 * Recursively filter array values and strings using InputFilter::filter()
	 *
	 * @param mixed $value A value to filter
	 * @return mixes The filtered value
	 */
	protected function base_filter($value)
	{
		if(is_array($value)) {
			return array_map(array($this, 'base_filter'), $value);
		}
		elseif(is_string($value)) {
			return InputFilter::filter($value);
		}
		else {
			return $value;
		}
	}

	/**
	 * Merges the contents of one or more arrays or ArrayObjects with this SuperGlobal
	 *
	 * @param mixed One or more array-like structures to merge into this array.
	 * @return SuperGlobal The merged array
	 */
	public function merge()
	{
		$args = func_get_args();
		$cp = $this->get_array_copy_raw();
		foreach($args as $ary) {
			if(is_array($ary)) {
				foreach($ary as $key => $value) {
					if(is_numeric($key)) {
						$cp[] = $value;
					}
					else {
						$cp[$key] = $value;
					}
				}
			}
			elseif($ary instanceof SuperGlobal) {
				// loop to get raw data.
				while( $ary->valid() ) {
					if( is_numeric($ary->key()) ) {
						$cp[] = $ary->raw($ary->key());
					}
					else {
						$cp[$ary->key()] = $ary->raw($ary->key());
					}
					$ary->next();
				}
			}
			elseif($ary instanceof ArrayObject || $ary instanceof ArrayIterator) {
				$arycp = $ary->getArrayCopy();  // Don't trigger offsetGet for ArrayObject
				foreach($arycp as $key => $value) {
					if(is_numeric($key)) {
						$cp[] = $value;
					}
					else {
						$cp[$key] = $value;
					}
				}
			}
			else {
				$cp[] = $ary;
			}
		}
		return new SuperGlobal($cp);
	}

	/**
	 * Filters this SuperGlobal based on an array or arrays of keys
	 *
	 * @param mixed An array of key values that should be returned, or a string of a key value to be returned
	 * @return SuperGlobal The values from this array that match the supplied keys
	 */
	public function filter_keys()
	{
		$keys = array();
		$args = func_get_args();
		foreach($args as $ary) {
			if(!is_array($ary)) {
				$ary = array($ary);
			}
			$keys = array_merge($keys, array_values($ary));
		}
		$cp = $this->get_array_copy_raw();
		$cp = array_intersect_key($cp, array_flip($keys));
		return new SuperGlobal($cp);
	}

	/**
	 * Apply a map function to this array, like array_map()
	 * @param callback $fn the name of the function to map through
	 * @return SuperGlobal the result of the mapping
	 **/
	public function map($fn)
	{
		return new SuperGlobal(array_map($fn, $this->get_array_copy_raw()));
	}

	/**
	 * Use a regular expression replacement to change the keys in an array
	 * @param string $replacement (optional) The regex replacement value
	 * @param string $search_regex (optional) The regex search value
	 * @return SuperGlobal The re-keyed array
	 **/
	public function rekey($replacement = '{\$$0}', $search_regex = '/^.*$/')
	{
		$output = array();
		foreach($this->get_array_copy_raw() as $k => $v) {
			$output[preg_replace($search_regex, $replacement, $k)] = $v;
		}
		return new SuperGlobal($output);
	}
}

?>


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

/**
 * Class which describes a single Tag object
 *
 */
class Tag
{

	public $tag_text = '';
	public $tag_slug = '';
	public $id = 0;

	/**
	 * Constructor for the Tag class.
	 * @param array $paramarray an associative array of initial Tag field values.
	 **/
	public function __construct( $paramarray = array() )
	{
		foreach ( $paramarray as $key => $value ) {
			$this->$key = $value;
		}
	}

	/**
	 * function __get
	 * Overrides QueryRecord __get to implement custom object properties
	 * @param string Name of property to return
	 * @return mixed The requested field value
	 **/
	public function __get( $name )
	{
		$term = Term::get( Tags::vocabulary()->id, $this->id );

		switch ( $name ) {
			case 'tag':
			case 'tag_text':
				$out = $term->term_display;
				break;
			case 'tag_text_searchable':
				// if it's got spaces, then quote it.
				if ( strpos($term->term_display, ' ') !== FALSE ) {
					$out = '\'' . str_replace("'", "\'", $term->term_display) . '\'';
				}
				else {
					$out = $term->term_display;
				}
				break;
			case 'slug':
			case 'tag_slug':
				$out = $term->term;
				break;
			case 'count':
				$out = $this->get_count();
				break;
			default:
				break;
		}
		return $out;
	}

	/**
	 * Return a single requested tag.
	 *
	 * <code>
	 * $tag = Tag::get( 'Tag text' );
	 * $tag = Tag::get( 'tag-slug' );
	 * $tag = Tag::get( 23 ); // tag id
	 * </code>
	 *
	 * @param mixed $tag The tag's name, slug, or id
	 * @return Tag The first tag that matched the given criteria or FALSE on failure
	 **/
	public static function get( $tag )
	{
		return Tags::get_one( $tag );
	}

	/**
	 * Check if a tag exists on a published post, to see if we should match this rewrite rule.
	 *
	 * @return Boolean Whether the tag exists on a published post.
	 **/
	public static function rewrite_tag_exists($rule, $slug, $parameters)
	{
		$tags = explode(' ', $rule->named_arg_values['tag']);
		$tags = array_map('trim', $tags, array_fill(0, count($tags), '-') );
		$tags = array_map(array('Tag', 'get'), $tags);
		$initial_tag_count = count($tags);
		$tags = array_filter($tags);
		// Are all of the tags we asked for actual tags on this site?
		if ( count($tags) != $initial_tag_count ) {
			return false;
		}
		$tag_params = array();
		foreach ( $tags as $tag ) {
			$tag_params[] = $tag->tag_text;
		}
		return ($tag instanceOf Tag && Posts::count_by_tag($tag_params, Post::status('published')) > 0);
	}

	/**
	 * Create a tag and save it.
	 *
	 * @param array $paramarray An associative array of tag fields
	 * @return Tag The new Tag object
	 **/
	public static function create( $paramarray )
	{
		$tag = new Tag( $paramarray );
		$tag = $tag->insert();
		return $tag;
	}

	/**
	 * Attaches (relates) a tag to a post
	 *
	 * @param		tag_id		The ID of the tag
	 * @param		post_id		The ID of the post
	 * @return	TRUE or FALSE depending if relation was created.
	 */
	public static function attach_to_post( $tag_id, $post_id )
	{
		$term = Tags::vocabulary()->get_term( $tag_id );
		$result = TRUE;

		Plugins::act( 'tag_attach_to_post_before', $tag_id, $post_id );

		$result = $term->associate( Tags::object_type(), $post_id);

		Plugins::act( 'tag_attach_to_post_after', $tag_id, $post_id );

		return $result;
	}

	/**
	 * Detaches a tag from a post (removes their association )
	 *
	 * @param		tag_id		The ID of the tag
	 * @param		post_id		The ID of the post
	 * @return	TRUE or FALSE depending if association was removed.
	 */
	public static function detach_from_post( $tag_id, $post_id )
	{
		$term = Tags::vocabulary()->get_term( $tag_id );
		Plugins::act( 'tag_detach_from_post_before', $tag_id, $post_id );

		$result = $term->dissociate( Tags::object_type(), $post_id );

		// should we delete the tag if it's the only one left?
		if ( 0 == count( $term->objects( Tags::object_type() ) ) ) {
			$delete = true;
			$delete = Plugins::filter( 'tag_detach_from_post_delete_empty_tag', $delete, $tag_id );

			if ( $delete ) {
				$term->delete();
			}
		}

		Plugins::act( 'tag_detach_from_post_after', $tag_id, $post_id, $result );

		return $result;

	}

	/**
	 * function insert
	 * Saves a new tag's data into the terms table
	 */
	public function insert()
	{
		$allow = true;
		$allow = Plugins::filter( 'tag_insert_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'tag_insert_before', $this );

		$term = new Term( array( 'term' => $this->tag_slug, 'term_display' => $this->tag_text ) );
		$term = Tags::vocabulary()->add_term( $term );

		if ( $term ) {
			EventLog::log( sprintf(_t('New tag %1$s (%2$s);  Slug: %3$s'), $this->id, $this->tag_text, $this->tag_slug), 'info', 'content', 'habari' );
			Plugins::act( 'tag_insert_after', $this );
			return new Tag( array( 'tag_text' => $term->term_display, 'tag_slug' => $term->term, 'id' => $term->id ) );
		}
		else {
			return FALSE;
		}

	}

	/**
	 * function update
	 * Update an existing tag's data in the terms table
	 */
	public function update()
	{
		$allow = true;
		$allow = Plugins::filter( 'tag_update_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'tag_update_before', $this );

		$term = Tags::vocabulary()->get_term( $this->id );
		$term->term = $this->tag_slug;
		$term->term_display = $this->tag_text;
		$result = $term->update();

		$term = Tags::vocabulary()->get_term( $this->id );
		if ( $result ) {
			$this->tag_text = $term->term_display;
			$this->tag_slug = $term->term;
		}

		Plugins::act( 'tag_update_after', $this );
		return $result;
	}

	/**
	 * function delete
	 * Deletes an existing tag and all relations to it
	 */
	public function delete()
	{
		$vocabulary = Tags::vocabulary();

		$allow = true;
		$allow = Plugins::filter( 'tag_delete_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		// invoke plugins
		Plugins::act( 'tag_delete_before', $this );

		// Delete the actual term record
		$term = $vocabulary->get_term( $this->id );
		$result = $vocabulary->delete_term( $term );

		EventLog::log( sprintf(_t('Tag %1$s (%2$s) deleted.'), $this->id, $this->tag_text), 'info', 'content', 'habari' );

		Plugins::act( 'tag_delete_after', $this );
		return $result;
	}

	/**
	 * Handle calls to this Tag object that are implemented by plugins
	 * @param string $name The name of the function called
	 * @param array $args Arguments passed to the function call
	 * @return mixed The value returned from any plugin filters, null if no value is returned
	 **/
	public function __call( $name, $args )
	{
		array_unshift($args, 'tag_call_' . $name, null, $this);
		return call_user_func_array(array('Plugins', 'filter'), $args);
	}

	/**
	 * Get a count of how many times the tag has been used in a post
	 * @return integer The number of times the tag has been used
	 **/
	protected function get_count()
	{
		$term = Tags::vocabulary()->get_term( $this->id );
		return count( $term->objects( Tags::object_type() ) );
	}

	/**
	 * Get a count of how many times the tag has been used in a post
	 * @return integer The number of times the tag has been used
	 **/
	public function count( $object_type = 'post' )
	{
		$term = Tags::vocabulary()->get_term( $this->id );
		return count( $term->objects( $object_type ) );
	}

}

?>


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

/**
* Habari Tags Class
*
*/
class Tags extends ArrayObject
{
	protected static $vocabulary = 'tags';
	protected static $object_type = 'post';

	/**
	 * Returns a tag or tags based on supplied parameters.
	 * @return array An array of Tag objects
	 **/
	public static function get()
	{
		$tags = array();
		$terms = Tags::vocabulary()->get_tree('term_display ASC');
		foreach( $terms as $term ) {
			$tags[] = new Tag( array( 'tag_text' => $term->term_display, 'tag_slug' => $term->term, 'id' => $term->id ) );
		}
		return $tags;

	}

	/**
	 * Return a tag based on an id, tag text or slug
	 *
	 * @return Tag The tag object
	 **/
	public static function get_one( $tag )
	{
		$term = Tags::vocabulary()->get_term( $tag );
		if ( !$term instanceOf Term ) {
			return FALSE;
		}
		$tag = new Tag( array( 'tag_text' => $term->term_display, 'tag_slug' => $term->term, 'id' => $term->id ) );
		return $tag;
	}

	/**
	 * Deletes a tag
	 *
	 * @param Tag $tag The tag to be deleted
	 **/
	public static function delete($tag)
	{
		$tag->delete();
	}

	/**
	 * Renames tags.
	 * If the master tag exists, the tags will be merged with it.
	 * If not, it will be created first.
	 *
	 * @param Array tags The tag text, slugs or ids to be renamed
	 * @param mixed master The Tag to which they should be renamed, or the slug, text or id of it
	 **/
	public static function rename($master, $tags, $object_type = 'post' )
	{
		$vocabulary = Tags::vocabulary();
		$type_id = Vocabulary::object_type_id( $object_type );

		$post_ids = array();
		$tag_names = array();

		// get array of existing tags first to make sure we don't conflict with a new master tag
		foreach ( $tags as $tag ) {

			$posts = array();
			$term = $vocabulary->get_term( $tag );

			// get all the post ID's tagged with this tag
			$posts = $term->objects( $object_type );

			if ( count( $posts ) > 0 ) {
				// merge the current post ids into the list of all the post_ids we need for the new tag
				$post_ids = array_merge( $post_ids, $posts );
			}

			$tag_names[] = $tag;
			if ( $tag != $master ) {
				$vocabulary->delete_term( $term->id );
			}
		}

		// get the master term
		$master_term = $vocabulary->get_term( $master );

		if ( !isset($master_term->term ) ) {
			// it didn't exist, so we assume it's tag text and create it
			$master_term = $vocabulary->add_term( $master );

			$master_ids = array();
		}
		else {
			// get the posts the tag is already on so we don't duplicate them
			$master_ids = $master_term->objects( $object_type );

		}

		if ( count( $post_ids ) > 0 ) {
			// only try and add the master tag to posts it's not already on
			$post_ids = array_diff( $post_ids, $master_ids );
		}
		else {
			$post_ids = $master_ids;
		}
		// link the master tag to each distinct post we removed tags from
		foreach ( $post_ids as $post_id ) {
			$master_term->associate( $object_type, $post_id );
		}

		EventLog::log(sprintf(
			_n('Tag %s has been renamed to %s.',
				 'Tags %s have been renamed to %s.',
				  count( $tags )
			), implode( $tag_names, ', ' ), $master ), 'info', 'tag', 'habari'
		);

	}

	/**
	 * Returns the number of times the most used tag is used.
	 *
	 * @return int The number of times the most used tag is used.
	 **/
	public static function max_count()
	{
		return DB::get_value( 'SELECT count( t2.object_id ) AS max FROM {terms} t, {object_terms} t2 WHERE t2.term_id = t.id AND t.vocabulary_id = ? GROUP BY t.id ORDER BY max DESC LIMIT 1', array( Tags::vocabulary()->id ) );
	}

	/**
	 * Returns the number of tags in the database.
	 *
	 * @return int The number of tags in the database.
	 **/
	public static function count_total()
	{
		return count( Tags::vocabulary()->get_tree() );
	}

	/**
	 * Returns the count of times a tag is used.
	 *
	 * @param mixed The tag to count usage.
	 * @return int The number of times a tag is used.
	 **/
	public static function post_count($tag, $object_type = 'post' )
	{
		$tag = Tags::get_one( $tag );
		return $tag->count( $object_type );
	}

	/**
	 * Return a tag based on a tag's text
	 *
	 * @return	A Tag object
	 **/
	public static function get_by_text($tag)
	{
		return Tags::get_one( $tag );
	}

	/**
	 * Return a tag based on a tag's text
	 *
	 * @return	A Tag object
	 **/
	public static function get_by_slug($tag)
	{
		return Tags::get_one( $tag );
	}

	/**
	 * Returns a Tag object based on a supplied ID
	 *
	 * @param Integer tag_id The ID of the tag to retrieve
	 * @return	A Tag object
	 */
	public static function get_by_id( $tag )
	{
		return Tags::get_one( $tag );
	}

	/**
	 * Returns the tags vocabulary
	 *
	 * @return String The tags vocabulary
	 */
	public static function vocabulary()
	{
		return Vocabulary::get( self::$vocabulary );
	}

	/**
	 * Returns the default type Tags uses
	 *
	 * @return String The default type name
	 */
	public static function object_type()
	{
		return self::$object_type;
	}

	/**
	 * Save the tags associated to this object into the terms and object_terms tables
	 *
	 * @param Array $tags strings. The tag names to associate to the object
	 * @param Integer $object_id. The id of the object being tagged
	 * @param String $object_type. The name of the type of the object being tagged. Defaults to post
	 *
	 * @return boolean. Whether the associating succeeded or not. TRUE
	 */

	public static function save_associations( $tags, $object_id, $object_type = 'post' )
	{
		return Tags::vocabulary()->set_object_terms( $object_type, $object_id, $tags );
	}

	public static function get_associations( $object_id, $object_type = 'post' )
	{
		return Tags::vocabulary()->get_object_terms( $object_type, $object_id );
	}

}
?>


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

/**
 * Habari TemplateEngine abstract base class
 *
 *
 * The TemplateEngine is an abstract base class to allow any template
 * engine to supply templates for the UI.  For an example
 * implementation, see RawPHPEngine or SmartyEngine
 *
 */
abstract class TemplateEngine
{
	// directory where the template resides
	protected $template_dir = null;

	abstract function __construct(); // virtual - implement in derived class

	/**
	 * A function which outputs the result of a transposed
	 * template to the output stream
	 *
	 * @param template  Name of template to display
	 */
	abstract function display( $template ); // virtual - implement in derived class

	/**
	 * Returns the existance of the specified template name
	 *
	 * @param template $template Name of template to detect
	 * @returns boolean True if the template exists, false if not
	 */
	abstract function template_exists( $template ); // virtual - implement in derived class

	/**
	 * A function which returns the content of the transposed
	 * template as a string
	 *
	 * @param template  Name of template to fetch
	 */
	abstract function fetch( $template ); // virtual - implement in derived class

	/**
	 * 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
	 */
	abstract function __get( $key ); // virtual - implement in derived class

	/**
	 * 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
	 */
	abstract function __set( $key, $value ); // virtual - implement in derived class

	/**
	 * Unassigns a variable to the template engine.
	 *
	 * @param key name of variable
	 */
	abstract function __unset( $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 key is set, false if not set
	 */
	abstract function __isset( $key ); // virtual - implement in derived class

	/**
	 * 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
	 */
	abstract function assign( $key, $value ='' ); // virtual - implement in derived class

	/**
	 * 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
	 */
	abstract function assigned( $key ); // virtual - implement in derived class

	/**
	 * Clear all of the assigned values
	 */
	abstract function clear(); // vitrual - implement in derived class

	/**
	 * Appends to an existing variable more values
	 *
	 * @param key name of variable
	 * @param value value of variable
	 */
	abstract function append( $key, $value ='' ); // virtual - implement in derived class

	/**
	 * Sets the directory for the engine to find templates
	 *
	 * @param mixed Directory path as string or array.  Earlier elements override later elements.
	 */
	public function set_template_dir( $dir )
	{
		$this->template_dir = $dir;
	}
}

?>


code:
<?php

/**
 * Term Class
 *
 * Term is part of the vocabulary system. A term exists in a vocabulary and can
 * be associated with objects, such as posts.
 *
 */

class Term extends QueryRecord
{
	// static variable to hold the vocabulary this term belongs to
	private $vocabulary = null;

	/**
	 * Return the defined database columns for a Term.
	 * @return array Array of columns in the Term table
	 **/
	public static function default_fields()
	{
		return array(
			'id' => 0,
			'term' => '',
			'term_display' => '',
			'vocabulary_id' => 0,
			'mptt_left' => 0,
			'mptt_right' => 0
		);
	}

	/**
	 * Term constructor
	 * Creates a Term instance
	 *
	 * @param array $paramarray an associative array of initial term values
	 **/
	public function __construct( $paramarray = array() )
	{
		// Defaults
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields
		);

		if(is_string($paramarray)) {
			$paramarray = array(
				'term_display' => $paramarray,
				'term' => Utils::slugify($paramarray),
			);
		}

		parent::__construct( $paramarray );

		// TODO does term need to be a slug ?
		// TODO How should we handle neither being present ?
		// A Vocabulary may be used for organization, display, or both.
		// Therefore, a Term can be constructed with a term, term_display, or both
		if ( $this->term == '' ) {
			$this->fields[ 'term' ] = Utils::slugify( $this->fields[ 'term_display' ] );
		}

		$this->exclude_fields( 'id' );
	}

	/**
	 * Fetch a term from a specified vocabulary
	 *
	 * @param mixed $vocab_id The id of a vocabulary, or a Vocabulary object
	 * @param mixed $term A Term id, term text, or null for the root node of the vocabulary
	 * @return Term The requested Term object instance
	 */
	public static function get( $vocab_id, $term = null )
	{
		if ( $vocab_id instanceof Vocabulary ) {
			$vocab_id = $vocab_id->id;
		}
		$params = array( 'vocab_id' => (int) $vocab_id );
		$query = '';
		if ( is_null( $term )  ) {
			// The root node has an mptt_left value of 1
 			$params[ 'left' ] = 1;
			$query = 'SELECT * FROM {terms} WHERE vocabulary_id = :vocab_id AND mptt_left = :left';
		}
		else {
			$params[ 'term_id' ] = (int) $term;
			$params[ 'term' ] = $term;
			$query = 'SELECT * FROM {terms} WHERE vocabulary_id = :vocab_id AND (id = ABS(:term_id) OR term = :term OR term_display = :term)';
		}
		return DB::get_row( $query, $params, 'Term' );
	}

	/**
	 * Generate a new slug for the post.
	 *
	 * @return string The slug
	 */
	protected function setslug()
	{
		// determine the base value from:
		// - the new slug
		if ( isset( $this->newfields[ 'term' ]) && $this->newfields[ 'term' ] != '' ) {
			$value = $this->newfields[ 'term' ];
		}
		// - the existing slug
		elseif ( $this->fields[ 'term' ] != '' ) {
			$value = $this->fields[ 'term' ];
		}
		// - the new term display text
		elseif ( isset( $this->newfields[ 'term_display' ] ) && $this->newfields[ 'term_display' ] != '' ) {
			$value = $this->newfields[ 'term_display' ];
		}
		// - the existing term display text
		elseif ( $this->fields[ 'term_display' ] != '' ) {
			$value = $this->fields[ 'term_display' ];
		}

		// make sure our slug is unique
		$slug = Plugins::filter( 'term_setslug', $value );
		$slug = Utils::slugify( $slug );
		$postfix = '';
		$postfixcount = 0;
		do {
			if ( ! $slugcount = DB::get_row( 'SELECT COUNT(term) AS ct FROM {terms} WHERE term = ? AND vocabulary_id = ?;', array( $slug . $postfix, $this->fields['vocabulary_id'] ) ) ) {
				Utils::debug( DB::get_errors() );
				exit;
			}
			if ( $slugcount->ct != 0 ) {
				$postfix = "-" . ( ++$postfixcount );
			}
		} while ( $slugcount->ct != 0 );

		return $this->newfields[ 'term' ] = $slug . $postfix;
	}

	/**
	 * function insert
	 * Saves a new term to the terms table
	 */
	public function insert()
	{
		$this->setslug();

		// Let plugins disallow and act before we write to the database
		$allow = true;
		$allow = Plugins::filter( 'term_insert_allow', $allow, $this );
		if ( !$allow ) {
			return false;
		}
		Plugins::act( 'term_insert_before', $this );

		$result = parent::insertRecord( DB::table( 'terms' ) );

		// Make sure the id is set in the term object to match the row id
		$this->newfields[ 'id' ] = DB::last_insert_id();

		// Update the term's fields with anything that changed
		$this->fields = array_merge( $this->fields, $this->newfields );

		// We've inserted the term, reset newfields
		$this->newfields = array();

		EventLog::log( _t( 'New term %1$s: %2$s', array( $this->id, $this->term_display ) ), 'info', 'content', 'habari' );

		// Let plugins act after we write to the database
		Plugins::act( 'term_insert_after', $this );

		return $result;
	}

	/**
	 * function update
	 * Updates an existing term in the terms table
	 */
	public function update()
	{
		// Let plugins disallow and act before we write to the database
		$allow = true;
		$allow = Plugins::filter( 'term_update_allow', $allow, $this );
		if ( !$allow ) {
			return;
		}
		Plugins::act( 'term_update_before', $this );

		// Call setslug() only when term is changed
		if ( isset( $this->newfields[ 'term' ] ) && $this->newfields[ 'term' ] != '' ) {
			if ( $this->fields[ 'term' ] != $this->newfields[ 'term' ] ) {
				$this->setslug();
			}
		}

		$result = parent::updateRecord( '{terms}', array( 'id' => $this->id ) );

		// Let plugins act after we write to the database
		Plugins::act( 'term_update_after', $this );

		return $result;
	}

	/**
	 * Delete an existing term
	 */
	public function delete()
	{
		// Let plugins disallow and act before we write to the database
		$allow = true;
		$allow = Plugins::filter( 'term_delete_allow', $allow, $this );
		if ( !$allow ) {
			return false;
		}
		Plugins::act( 'term_delete_before', $this );

		DB::query( "DELETE FROM {object_terms} WHERE term_id = :id", array( 'id' => $this->id) );

		$result = parent::deleteRecord( '{terms}', array( 'id'=>$this->id ) );
		EventLog::log( sprintf( _t( 'Term %1$s (%2$s) deleted.' ), $this->id, $this->term_display ), 'info', 'content', 'habari' );

		// Let plugins act after we write to the database
		Plugins::act( 'term_delete_after', $this );
		return $result;
	}

	/**
	 * Find this Term's ancestors.
	 * @return Array Direct ancestors from the root to this Term in descendant order.
	 **/
	public function ancestors()
	{
		$params = array( 'vocab_id' => $this->vocabulary_id, 'left' => $this->mptt_left, 'right' => $this->mptt_right );
		$query = 'SELECT * FROM {terms} WHERE vocabulary_id=:vocab_id AND mptt_left<:left AND mptt_right>:right ORDER BY mptt_left ASC';
		return DB::get_results( $query, $params, 'Term' );
	}

	/**
	 * Find all Terms in this Term's Vocabulary that are not its ancestors, or it.
	 * @return Array of Terms in MPTT left-to-right order.
	 **/
	public function not_ancestors()
	{
		$params = array( 'vocab_id' => $this->vocabulary_id, 'left' => $this->mptt_left, 'right' => $this->mptt_right );
		$query = 'SELECT * FROM {terms} WHERE vocabulary_id=:vocab_id AND (mptt_left>:left OR mptt_right<:right) ORDER BY mptt_left ASC';
		return DB::get_results( $query, $params, 'Term' );
	}

	/**
	 * Find this Term's descendants.
	 * @return Array of all descendants in MPTT left-to-right order.
	 **/
	public function descendants()
	{
		$params = array( 'vocab_id' => $this->vocabulary_id, 'left' => $this->mptt_left, 'right' => $this->mptt_right );
		$query = 'SELECT * FROM {terms} WHERE vocabulary_id=:vocab_id AND mptt_left>:left AND mptt_right<:right ORDER BY mptt_left ASC';
		return DB::get_results( $query, $params, 'Term' );
	}

	/**
	 * Find all Terms in this Term's Vocabulary that are not its descendants, or it.
	 * @return Array of Terms in MPTT left-to-right order.
	 **/
	public function not_descendants()
	{
		$params = array( 'vocab_id' => $this->vocabulary_id, 'left' => $this->mptt_left, 'right' => $this->mptt_right );
		$query = 'SELECT * FROM {terms} WHERE vocabulary_id=:vocab_id AND mptt_left NOT BETWEEN :left AND :right ORDER BY mptt_left ASC';
		return DB::get_results( $query, $params, 'Term' );
	}

	/**
	 * The Term that is this Term's parent in hierarchy.
	 * @return Term This Term's parent
	 **/
	public function parent()
	{
		$params = array( 'vocab_id' => $this->vocabulary_id, 'left' => $this->mptt_left, 'right' => $this->mptt_right );
		$query = 'SELECT * FROM {terms} WHERE vocabulary_id=:vocab_id AND mptt_left<:left AND mptt_right>:right ORDER BY mptt_left DESC LIMIT 1';
		return DB::get_row( $query, $params, 'Term' );
	}

	/**
	 * Find this Term's siblings.
	 * @return Array of all siblings including self.
	 **/
	public function siblings()
	{
		return $this->parent()->children();
	}

	/**
	 * Find this Term's children.
	 * @return Array of all direct children (compare to descendants()).
	 **/
	public function children()
	{
		$params = array( 'vocab' => $this->vocabulary_id,
			'left' => $this->mptt_left,
			'right' => $this->mptt_right
		);
		/**
		 * If we INNER JOIN the terms table with itself on ALL the descendants of our term,
		 * then descendants one level down are listed once, two levels down are listed twice,
		 * etc. If we return only those terms which appear once, we get immediate children.
		 * ORDER BY NULL to avoid the MySQL filesort.
		 */
		$query = <<<SQL
SELECT child.term as term,
	child.term_display as term_display,
	child.mptt_left as mptt_left,
	child.mptt_right as mptt_right,
	child.vocabulary_id as vocabulary_id
FROM {terms} as parent
INNER JOIN {terms} as child
	ON child.mptt_left BETWEEN parent.mptt_left AND parent.mptt_right
	AND child.vocabulary_id = parent.vocabulary_id
WHERE parent.mptt_left > :left AND parent.mptt_right < :right
	AND parent.vocabulary_id = :vocab
GROUP BY child.term
HAVING COUNT(child.term)=1
ORDER BY NULL
SQL;
		return DB::get_results( $query, $params, 'Term' );
	}

	/**
	 * Find objects of a given type associated with this Term.
	 *
	 * @param $type string. The name of the object type for which the associations are wanted.
	 * @return Array of object ids associated with this term for the given type.
	 **/
	public function objects( $type )
	{
		$type_id = Vocabulary::object_type_id( $type );
		$results = DB::get_column( "SELECT object_id FROM {object_terms} WHERE term_id = ? AND object_type_id = ?", array( $this->id, $type_id ) );
		return $results;
	}

	/**
	 * Associate this term to an object of a certain type via its id.
	 * @param $type string. The name of the object type we want to set an association for
	 * @param $id integer. The object's id
	 *
	 **/
	public function associate( $type, $id )
	{
		$result = TRUE;
		$type_id = Vocabulary::object_type_id( $type );

		Plugins::act( 'term_associate_to_object_before', $this->id, $id, $type_id );

		if( ! DB::exists( "{object_terms}", array( 'term_id' => $this->id, 'object_id' => $id, 'object_type_id' => $type_id ) ) ) {
			$result = DB::insert( "{object_terms}", array( 'term_id' => $this->id, 'object_id' => $id, 'object_type_id' => $type_id ) );
		}

		Plugins::act( 'term_associate_to_object_after', $this->id, $id, $type_id );

		return $result;
	}

	/**
	 * Disassociate this term from an object of a certain type via its id.
	 * @param $type string. The name of the object type we want to unset an association for
	 * @param $id integer. The object's id
	 *
	 **/
	public function dissociate( $type = NULL, $id = NULL )
	{
		$result = TRUE;

		$type_id = Vocabulary::object_type_id( $type );
		Plugins::act( 'term_dissociate_from_object_before', $this->id, $id, $type_id );

		$result = DB::query( "DELETE FROM {object_terms} WHERE term_id = ? AND object_id = ? AND object_type_id = ?", array( $this->id, $id, $type_id ) );

		Plugins::act( 'term_dissociate_from_object_after', $this->id, $id, $type_id, $result );

		return $result;
	}

	/**
	 * Allow output when the term is cast to a string
	 * @return string The terms display text
	 *
	 **/
	public function __tostring()
	{
		return $this->term_display;
	}

	/**
	 * function __get
	 * Overrides QueryRecord __get to implement custom object properties
	 * @param $name string Name of property to return
	 * @return mixed The requested field value
	 **/
	public function __get( $name )
	{
		switch( $name ) {
			case 'vocabulary':
				return Vocabulary::get_by_id( $this->vocabulary_id );
			default:
				return parent::__get( $name );
		}
	}

}

?>


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

/**
 * Habari Theme Class
 *
 * The Theme class is the behind-the-scenes representation of
 * of a set of UI files that compose the visual theme of the blog
 *
 */
class Theme extends Pluggable
{
	public $name = null;
	public $version = null;
	public $template_engine = null;
	public $theme_dir = null;
	public $config_vars = array();
	private $var_stack = array(array());
	private $current_var_stack = 0;

	/**
	 * We build the Post filters by analyzing the handler_var
	 * data which is assigned to the handler ( by the Controller and
	 * also, optionally, by the Theme )
	 */
	public $valid_filters = array(
		'content_type',
		'not:content_type',
		'slug',
		'status',
		'page',
		'tag',
		'not:tag',
		'month',
		'year',
		'day',
		'criteria',
		'limit',
		'nolimit',
		'offset',
		'fetch_fn',
		'id',
	);

	/**
	 * Constructor for theme
	 *
	 * If no parameter is supplied, then the constructor
	 * Loads the active theme from the database.
	 *
	 * If no theme option is set, a fatal error is thrown
	 *
	 * @param name            ( optional ) override the default theme lookup
	 * @param template_engine ( optional ) specify a template engine
	 * @param theme_dir       ( optional ) specify a theme directory
	 */
	public function __construct( $themedata )
	{
		$this->name = $themedata->name;
		$this->version = $themedata->version;
		$this->theme_dir = $themedata->theme_dir;
		// Set up the corresponding engine to handle the templating
		$this->template_engine = new $themedata->template_engine();
		$this->template_engine->set_template_dir( $themedata->theme_dir );
		$this->plugin_id = $this->plugin_id();
		$this->load();
	}

	/**
	 * Loads a theme's metadata from an XML file in theme's
	 * directory.
	 *
	 */
	public function info( )
	{

		$xml_file = dirname($this->getfile) . '/theme.xml';
		if ( $xml_content = file_get_contents( $xml_file ) ) {
			$theme_data = new SimpleXMLElement( $xml_file );
			return $theme_data;
			// Is it a valid theme xml file?
			if ( isset( $theme_data->theme ) ) {
				$valid_named_elements = array(
					'name',
					'version',
					'template_engine',
					'theme_dir'
				);
				// Assigns based on wether or not it's a valid named element.
				foreach ( $theme_data->theme->children() as $key => $value ) {
					$key = strtolower( $key );
					if ( in_array( $key, $valid_named_elements ) ) {
						$this->$key = $value;
					}
					else {
						$this->config_vars[$key] = $value;
					}
				}
			}
		}
	}

	/**
	 * Assign the default variables that would be used in every template
	 */
	public function add_template_vars()
	{
		if ( !$this->template_engine->assigned( 'user' ) ) {
			$this->assign('user', User::identify() );
		}

		if ( !$this->template_engine->assigned( 'loggedin' ) ) {
			$this->assign('loggedin', User::identify()->loggedin );
		}

		if ( !$this->template_engine->assigned( 'page' ) ) {
			$this->assign('page', isset( $this->page ) ? $this->page : 1 );
		}

		$handler = Controller::get_handler();
		if ( isset( $handler ) ) {
			Plugins::act( 'add_template_vars', $this, Controller::get_handler()->handler_vars );
		}
	}

	/**
	 * Find the first template that matches from the list provided and display it
	 * @param array $template_list The list of templates to search for
	 */
	public function display_fallback( $template_list, $display_function = 'display' )
	{
		foreach ( (array)$template_list as $template ) {
			if ( $this->template_exists( $template ) ) {
				$this->assign('_template_list', $template_list);
				$this->assign('_template', $template);
				return $this->$display_function( $template );
			}
		}
		return false;
	}

	/**
	 * Determine if a template exists in the current theme
	 *
	 * @param string $template_name The name of the template to detect
	 * @return boolean True if template exists
	 */
	public function template_exists( $template_name )
	{
		return $this->template_engine->template_exists( $template_name );
	}

	/**
	 * Grabs post data and inserts that data into the internal
	 * handler_vars array, which eventually gets extracted into
	 * the theme's ( and thereby the template_engine's ) local
	 * symbol table for use in the theme's templates
	 *
	 * This is the default, generic function to grab posts.  To
	 * "filter" the posts retrieved, simply pass any filters to
	 * the handler_vars variables associated with the post retrieval.
	 * For instance, to filter by tag, ensure that handler_vars['tag']
	 * contains the tag to filter by.  Simple as that.
	 */
	public function act_display( $paramarray = array( 'user_filters'=> array() ) )
	{
		Utils::check_request_method( array( 'GET', 'HEAD', 'POST' ) );

		// Get any full-query parameters
		$possible = array( 'user_filters', 'fallback', 'posts', 'post', 'content_type' );
		foreach ( $possible as $varname ) {
			if ( isset( $paramarray[$varname] ) ) {
				$$varname = $paramarray[$varname];
			}
		}

		$where_filters = array();
		$where_filters = Controller::get_handler()->handler_vars->filter_keys( $this->valid_filters );
		//$where_filters['status'] = Post::status( 'published' );
		if ( array_key_exists( 'tag', $where_filters ) ) {
			$tags = explode(' ', $where_filters['tag']);
			$not_tag = array();
			$all_tag = array();
			foreach ( $tags as $tag ) {
				if ( $tag[0] == '-' ) {
					$tag = substr($tag, 1);
					$not_tag[] = Utils::slugify($tag);
				}
				else {
					$all_tag[] = Utils::slugify($tag);
				}
			}
			if ( count($not_tag) > 0 ) {
				$where_filters['not:tag_slug'] = $not_tag;
			}
			if ( count($all_tag) > 0 ) {
				$where_filters['all:tag_slug'] = $all_tag;
			}
			unset( $where_filters['tag'] );
		}
		if ( User::identify()->loggedin ) {
			$where_filters['status'] = isset( $_GET['preview'] ) ? Post::status( 'any' ) : Post::status( 'published' );
		}
		else {
			$where_filters['status'] = Post::status( 'published' );
		}

		if ( !isset( $posts ) ) {
			$user_filters = Plugins::filter( 'template_user_filters', $user_filters );
			$user_filters = array_intersect_key( $user_filters, array_flip( $this->valid_filters ) );
			$where_filters = $where_filters->merge( $user_filters );
			$where_filters = Plugins::filter( 'template_where_filters', $where_filters );

			$posts = Posts::get( $where_filters );
		}

		$this->assign( 'posts', $posts );

		/*
		   if( !isset( $this->page ) ) {
		   if( isset( $page ) ) {
		   $this->assign( 'page', $page );
		   }
		   elseif( isset( Controller::get_handler()->handler_vars['page'] ) ) {
		   $this->assign( 'page', Controller::get_handler()->handler_vars['page'] );
		   }
		   }*/

		if ( $posts !== false && count( $posts ) > 0 ) {
			if ( count($posts) == 1 ) {
				$post = $posts;
				Stack::add('body_class', Post::type_name($post->content_type) . '-' . $post->id);
			}
			else {
				$post = reset($posts);
				Stack::add('body_class', 'multiple');
			}
			$this->assign( 'post', $post );
			$type = Post::type_name($post->content_type);
		}
		elseif ( ( $posts === false ) ||
			( isset( $where_filters['page'] ) && $where_filters['page'] > 1 && count( $posts ) == 0 ) ) {
			if ( $this->template_exists( '404' ) ) {
				$fallback = array( '404' );
				// Replace template variables with the 404 rewrite rule
				$this->request->{URL::get_matched_rule()->name} = false;
				$this->request->{URL::set_404()->name} = true;
				$this->matched_rule = URL::get_matched_rule();
				// 404 status header sent in act_display_404, but we're past
				// that, so send it now.
				header( 'HTTP/1.1 404 Not Found', true, 404 );
			}
			else {
				$this->display( 'header' );
				echo '<h2>';
				_e( "Whoops! 404. The page you were trying to access is not really there. Please try again." );
				echo '</h2>';
				header( 'HTTP/1.1 404 Not Found', true, 404 );
				$this->display( 'footer' );
				die;
			}
		}

		$extract = $where_filters->filter_keys('page','type','id','slug','posttag','year','month','day','tag','tag_slug');
		foreach($extract as $key => $value) {
			$$key = $value;
		}

		$this->assign( 'page', isset($page)? $page:1 );

		if ( !isset( $fallback ) ) {
			// Default fallbacks based on the number of posts
			$fallback = array( '{$type}.{$id}', '{$type}.{$slug}', '{$type}.tag.{$posttag}' );
			if ( count( $posts ) > 1 ) {
				$fallback[] = '{$type}.multiple';
				$fallback[] = 'multiple';
			}
			else {
				$fallback[] = '{$type}.single';
				$fallback[] = 'single';
			}
		}

		$searches = array( '{$id}','{$slug}','{$year}','{$month}','{$day}','{$type}','{$tag}', );
		$replacements = array(
			( isset( $post ) && $post instanceof Post ) ? $post->id : '-',
			( isset( $post ) && $post instanceof Post ) ? $post->slug : '-',
			isset( $year ) ? $year : '-',
			isset( $month ) ? $month : '-',
			isset( $day ) ? $day : '-',
			isset( $type ) ? $type : '-',
			isset( $tag_slug ) ? $tag_slug : '-',
		);
		$fallback[] = 'home';
		$fallback = Plugins::filter( 'template_fallback', $fallback );
		$fallback = array_values( array_unique( str_replace( $searches, $replacements, $fallback ) ) );
		for ( $z = 0; $z < count( $fallback ); $z++ ) {
			if ( ( strpos( $fallback[$z], '{$posttag}' ) !== false ) && ( isset( $post ) ) && ( $post instanceof Post ) ) {
				$replacements = array();
				if ( $alltags = $post->tags ) {
					foreach ( $alltags as $tag_slug => $tag_text ) {
						$replacements[] = str_replace( '{$posttag}', $tag_slug, $fallback[$z] );
					}
					array_splice( $fallback, $z, 1, $replacements );
				}
				else {
					break;
				}
			}
		}
		return $this->display_fallback( $fallback );
	}

	/**
	 * Helper function: Displays the home page
	 * @param array $user_filters Additional arguments used to get the page content
	 */
	public function act_display_home( $user_filters = array() )
	{
		$paramarray['fallback'] = array(
			'home',
			'multiple',
		);

		// Makes sure home displays only entries
		$default_filters = array(
			'content_type' => Post::type( 'entry' ),
		);

		$paramarray['user_filters'] = array_merge( $default_filters, $user_filters );

		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Displays multiple entries
	 * @param array $user_filters Additional arguments used to get the page content
	 */
	public function act_display_entries( $user_filters = array() )
	{
		$paramarray['fallback'] = array(
		 	'{$type}.multiple',
			'multiple',
		);

		// Makes sure home displays only entries
		$default_filters = array(
			'content_type' => Post::type( 'entry' ),
		);

		$paramarray['user_filters'] = array_merge( $default_filters, $user_filters );

		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Display a post
	 * @param array $user_filters Additional arguments used to get the page content
	 */
	public function act_display_post( $user_filters = array() )
	{
		$paramarray['fallback'] = array(
		 '{$type}.{$id}',
		 '{$type}.{$slug}',
		 '{$type}.tag.{$posttag}',
		 '{$type}.single',
		 '{$type}.multiple',
		 'single',
		 'multiple',
		);

		// Does the same as a Post::get()
		$default_filters = array(
		 'fetch_fn' => 'get_row',
		 'limit' => 1,
		);

		// Remove the page from filters.
		$page_key = array_search( 'page', $this->valid_filters );
		unset( $this->valid_filters[$page_key] );

		$paramarray['user_filters'] = array_merge( $default_filters, $user_filters );

		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Display the posts for a tag
	 * @param array $user_filters Additional arguments used to get the page content
	 */
	public function act_display_tag( $user_filters = array() )
	{
		$paramarray['fallback'] = array(
			'tag.{$tag}',
			'tag',
			'multiple',
		);

		// Makes sure home displays only entries
		$default_filters = array(
			'content_type' => Post::type( 'entry' ),
		);

		$this->assign( 'tag', htmlentities( Controller::get_var( 'tag' ), ENT_QUOTES, 'UTF-8' ) );
		$paramarray['user_filters'] = array_merge( $default_filters, $user_filters );

		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Display the posts for a specific date
	 * @param array $user_filters Additional arguments used to get the page content
	 */
	public function act_display_date( $user_filters = array() )
	{
		$handler_vars = Controller::get_handler()->handler_vars;
		$y = isset( $handler_vars['year'] );
		$m = isset( $handler_vars['month'] );
		$d = isset( $handler_vars['day'] );

		if ( $y && $m && $d ) {
			$paramarray['fallback'][] = 'year.{$year}.month.{$month}.day.{$day}';
		}
		if ( $y && $m && $d) {
			$paramarray['fallback'][] = 'year.month.day';
		}
		if ( $m && $d ) {
			$paramarray['fallback'][] = 'month.{$month}.day.{$day}';
		}
		if ( $y && $m ) {
			$paramarray['fallback'][] = 'year.{$year}.month.{$month}';
		}
		if ( $y && $d ) {
			$paramarray['fallback'][] = 'year.{$year}.day.{$day}';
		}
		if ( $m && $d ) {
			$paramarray['fallback'][] = 'month.day';
		}
		if ( $y && $d ) {
			$paramarray['fallback'][] = 'year.day';
		}
		if ( $y && $m ) {
			$paramarray['fallback'][] = 'year.month';
		}
		if ( $m ) {
			$paramarray['fallback'][] = 'month.{$month}';
		}
		if ( $d ) {
			$paramarray['fallback'][] = 'day.{$day}';
		}
		if ( $y ) {
			$paramarray['fallback'][] = 'year.{$year}';
		}
		if ( $y ) {
			$paramarray['fallback'][] = 'year';
		}
		if ( $m ) {
			$paramarray['fallback'][] = 'month';
		}
		if ( $d ) {
			$paramarray['fallback'][] = 'day';
		}
		$paramarray['fallback'][] = 'date';
		$paramarray['fallback'][] = 'multiple';
		$paramarray['fallback'][] = 'home';

		$paramarray['user_filters'] = $user_filters;
		if ( !isset( $paramarray['user_filters']['content_type'] ) ) {
			$paramarray['user_filters']['content_type'] = Post::type( 'entry' );
		}

		$this->assign( 'year' , $y ? (int)Controller::get_var( 'year' ) : NULL );
		$this->assign( 'month' , $m ? (int)Controller::get_var( 'month' ) : NULL );
		$this->assign( 'day' , $d ? (int)Controller::get_var( 'day' ) : NULL );

		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Display the posts for a specific criteria
	 * @param array $user_filters Additional arguments used to get the page content
	 */
	public function act_search( $user_filters = array() )
	{
		$paramarray['fallback'] = array(
			'search',
			'multiple',
		);

		$paramarray['user_filters'] = $user_filters;

		$this->assign( 'criteria', htmlentities( Controller::get_var('criteria'), ENT_QUOTES, 'UTF-8' ) );
		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Display a 404 template
	 *
	 * @param array $user_filters Additional arguments user to get the page content
	 */
	public function act_display_404( $user_filters = array() )
	{
		$paramarray['fallback'] = array(
			'404',
		);

		header( 'HTTP/1.1 404 Not Found' );
		$paramarray['user_filters'] = $user_filters;
		return $this->act_display( $paramarray );
	}

	/**
	 * Helper function: Avoids having to call $theme->template_engine->display( 'template_name' );
	 * @param string $template_name The name of the template to display
	 */
	public function display( $template_name )
	{
		$this->add_template_vars();

		$this->play_var_stack();

		$this->template_engine->assign( 'theme', $this );
		$this->template_engine->display( $template_name );
	}

	/**
	 * Helper function: Avoids having to call $theme->template_engine->fetch( 'template_name' );
	 *
	 * @param string $template_name The name of the template to display
	 * @param boolean $unstack If true, end the current template variable buffer upon returning
	 * @return string The content of the template
	 */
	public function fetch( $template_name, $unstack = false )
	{
		$this->play_var_stack();

		$this->add_template_vars();

		$this->template_engine->assign( 'theme', $this );

		$return = $this->fetch_unassigned( $template_name );
		if ( $unstack ) {
			$this->end_buffer();
		}
		return $return;
	}

	/**
	 * Play back the full stack of template variables to assign them into the template
	 */
	protected function play_var_stack()
	{
		$this->template_engine->clear();
		for ( $z = 0; $z <= $this->current_var_stack; $z++ ) {
			foreach($this->var_stack[$z] as $key => $value) {
				$this->template_engine->assign( $key, $value );
			}
		}
	}

	/**
	 * Calls the template engine's fetch() method without pre-assigning template variables.
	 * Assumes that the template variables have already been set.
	 *
	 * @param string $template_name The name of the template to display
	 * @return string The content of the template
	 */
	public function fetch_unassigned( $template_name )
	{
		return $this->template_engine->fetch( $template_name );
	}

	/**
	 * Helper function: Avoids having to call $theme->template_engine->key= 'value';
	 */
	public function assign( $key, $value )
	{
		$this->var_stack[$this->current_var_stack][$key] = $value;
	}

	/**
	 * Aggregates and echos the additional header code by combining Plugins and Stack calls.
	 */
	public function theme_header( $theme )
	{
		Plugins::act( 'template_header', $theme );
		$output = Stack::get( 'template_stylesheet', array('Stack', 'styles') );
		$output.= Stack::get( 'template_header_javascript', array('Stack', 'scripts') );
		Plugins::act( 'template_header_after', $theme );
		return $output;
	}

	/**
	 * Aggregates and echos the additional footer code by combining Plugins and Stack calls.
	 */
	public function theme_footer( $theme )
	{
		Plugins::act( 'template_footer', $theme );
		$output = Stack::get( 'template_footer_javascript', array('Stack', 'scripts') );
		return $output;
	}

	/**
	 * Display an object using a template designed for the type of object it is
	 * The $object is assigned into the theme using the $content template variable
	 *
	 * @param Theme $theme The theme used to display the object
	 * @param object $object An object to display
	 * @param string $context The context in which the object will be displayed
	 * @return
	 */
	public function theme_content( $theme, $object, $context = null )
	{
		$fallback = array();
		$content_types = array();
		if ( $object instanceof IsContent ) {
			$content_types = Utils::single_array($object->content_type());
		}
		if ( is_object($object) ) {
			$content_types[] = strtolower(get_class($object));
		}
		$content_types[] = 'content';
		$content_types = array_flip($content_types);
		if ( isset($context) ) {
			foreach($content_types as $type => $type_id) {
				$content_type = $context . $object->content_type();
				$fallback[] = strtolower($context . '.' . $type);
			}
		}
		foreach ( $content_types as $type => $type_id ) {
			$fallback[] = strtolower($type);
		}
		if ( isset($context) ) {
			$fallback[] = strtolower($context);
		}
		$fallback = array_unique($fallback);

		$this->content = $object;
		return $this->display_fallback( $fallback, 'fetch' );
	}

	/**
	 * Returns the appropriate alternate feed based on the currently matched rewrite rule.
	 *
	 * @param mixed $return Incoming return value from other plugins
	 * @param Theme $theme The current theme object
	 * @return string Link to the appropriate alternate Atom feed
	 */
	public function theme_feed_alternate( $theme )
	{
		$matched_rule = URL::get_matched_rule();
		if ( is_object( $matched_rule ) ) {
			// This is not a 404
			$rulename = $matched_rule->name;
		}
		else {
			// If this is a 404 and no rewrite rule matched the request
			$rulename = '';
		}
		switch ( $rulename ) {
			case 'display_entry':
			case 'display_page':
				return URL::get( 'atom_entry', array( 'slug' => Controller::get_var( 'slug' ) ) );
				break;
			case 'display_entries_by_tag':
				return URL::get( 'atom_feed_tag', array( 'tag' => Controller::get_var( 'tag' ) ) );
				break;
			case 'display_home':
			default:
				return URL::get( 'atom_feed', array( 'index' => '1' ) );
		}
		return '';
	}

	/**
	 * Returns the feedback URL to which comments should be submitted for the indicated Post
	 *
	 * @param Theme $theme The current theme
	 * @param Post $post The post object to get the feedback URL for
	 * @return string The URL to the feedback entrypoint for this comment
	 */
	public function theme_comment_form_action( $theme, $post )
	{
		return URL::get( 'submit_feedback', array( 'id' => $post->id ) );
	}

	/**
	 * Build a collection of paginated URLs to be used for pagination.
	 *
	 * @param integer Current page
	 * @param integer Total pages
	 * @param string The RewriteRule name used to build the links.
	 * @param array Various settings used by the method and the RewriteRule.
	 * @return string Collection of paginated URLs built by the RewriteRule.
	 */
	public static function theme_page_selector( $theme, $rr_name = NULL, $settings = array() )
	{
		$current = $theme->page;
		$items_per_page = isset($theme->posts->get_param_cache['limit']) ?
			$theme->posts->get_param_cache['limit'] :
			Options::get('pagination');
		$total = Utils::archive_pages( $theme->posts->count_all(), $items_per_page );

		// Make sure the current page is valid
		if ( $current > $total ) {
			$current = $total;
		}
		else if ( $current < 1 ) {
			$current = 1;
		}

		// Number of pages to display on each side of the current page.
		$leftSide = isset( $settings['leftSide'] ) ? $settings['leftSide'] : 1;
		$rightSide = isset( $settings['rightSide'] ) ? $settings['rightSide'] : 1;

		// Add the page '1'.
		$pages[] = 1;

		// Add the pages to display on each side of the current page, based on $leftSide and $rightSide.
		for ( $i = max( $current - $leftSide, 2 ); $i < $total && $i <= $current + $rightSide; $i++ ) {
			$pages[] = $i;
		}

		// Add the last page if there is more than one page.
		if ( $total > 1 ) {
			$pages[] = (int) $total;
		}

		// Sort the array by natural order.
		natsort( $pages );

		// This variable is used to know the last page processed by the foreach().
		$prevpage = 0;
		// Create the output variable.
		$out = '';

		if ( 1 === count($pages) && isset( $settings['hideIfSinglePage'] ) &&  $settings['hideIfSinglePage'] === true ) {
			return '';
		}

		foreach ( $pages as $page ) {
			$settings['page'] = $page;

			// Add ... if the gap between the previous page is higher than 1.
			if ( ($page - $prevpage) > 1 ) {
				$out .= ' …';
			}
			// Wrap the current page number with square brackets.
			$caption = ( $page == $current ) ?  $current  : $page;
			// Build the URL using the supplied $settings and the found RewriteRules arguments.
			$url = URL::get( $rr_name, $settings , false );
			// Build the HTML link.
			$out .= ' <a href="' . $url . '" ' . ( ( $page == $current ) ? 'class="current-page"' : '' ) . '>' . $caption . '</a>';

			$prevpage = $page;
		}

		return $out;
	}

	/**
	 *Provides a link to the previous page
	 *
	 * @param string $text text to display for link
	 */
	public function theme_prev_page_link( $theme, $text = NULL )
	{
		$settings = array();

		// If there's no previous page, skip and return null
		$settings['page'] = (int) ( $theme->page - 1);
		if ( $settings['page'] < 1) {
			return null;
		}

		// If no text was supplied, use default text
		if ( $text == '' ) {
			$text = '← ' . _t( 'Previous' );
		}

		return '<a class="prev-page" href="' . URL::get(null, $settings, false) . '" title="' . $text . '">' . $text . '</a>';
	}

	/**
	 *Provides a link to the next page
	 *
	 * @param string $text text to display for link
	 */
	public function theme_next_page_link( $theme, $text = NULL )
	{
		$settings = array();

		// If there's no next page, skip and return null
		$settings['page'] = (int) ( $theme->page + 1);
		$items_per_page = isset($theme->posts->get_param_cache['limit']) ?
			$theme->posts->get_param_cache['limit'] :
			Options::get('pagination');
		$total = Utils::archive_pages( $theme->posts->count_all(), $items_per_page );
		if ( $settings['page'] > $total ) {
			return null;
		}

		// If no text was supplied, use default text
		if ( $text == '' ) {
			$text = _t( 'Next' ) . ' →';
		}

		return '<a class="next-page" href="' . URL::get(null, $settings, false) . '" title="' . $text . '">' . $text . '</a>';
	}

	/**
	 * Returns a full qualified URL of the specified post based on the comments count, and links to the post.
 	 *
	 * Passed strings are localized prior to parsing therefore to localize "%d Comments" in french, it would be "%d Commentaires".
	 *
	 * Since we use sprintf() in the final concatenation, you must format passed strings accordingly.
	 *
	 * @param Theme $theme The current theme object
	 * @param Post $post Post object used to build the comments link
	 * @param string $zero String to return when there are no comments
	 * @param string $one String to return when there is one comment
	 * @param string $many String to return when there are more than one comment
	 * @return string Linked string to display for comment count
	 * @see Theme::theme_comments_count()
	 */
	public function theme_comments_link( $theme, $post, $zero, $one, $many )
	{
		$count = $theme->comments_count_return( $post, $zero, $one, $many );
		return '<a href="' . $post->permalink . '#comments" title="' . _t( 'Read Comments' ) . '">' . end( $count ) . '</a>';
	}

	/**
	 * Returns a full qualified URL of the specified post based on the comments count.
 	 *
	 * Passed strings are localized prior to parsing therefore to localize "%d Comments" in french, it would be "%d Commentaires".
	 *
	 * Since we use sprintf() in the final concatenation, you must format passed strings accordingly.
	 *
	 * @param Theme $theme The current theme object
	 * @param Post $post Post object used to build the comments link
	 * @param string $zero String to return when there are no comments
	 * @param string $one String to return when there is one comment
	 * @param string $many String to return when there are more than one comment
	 * @return string String to display for comment count
	 */
	public function theme_comments_count( $theme, $post, $zero = '', $one = '', $many = '' )
	{
		$count = $post->comments->approved->count;
		if ( empty( $zero ) ) {
			$zero = _t( '%d Comments' );
		}
		if ( empty( $one ) ) {
			$one = _t( '%d Comment' );
		}
		if ( empty( $many ) ) {
			$many = _t( '%d Comments' );
		}

		if ( $count >= 1 ) {
			$text = _n( $one, $many, $count );
		}
		else {
			$text = $zero;
		}
		return sprintf( $text, $count );
	}

	/**
	 * Returns the count of queries executed
	 *
	 * @return integer The query count
	 */
	public function theme_query_count()
	{
		return count(DB::get_profiles());
	}

	/**
	 * Returns total query execution time in seconds
	 *
	 * @return float Query execution time in seconds, with fractions.
	 */
	public function theme_query_time( )
	{
		return array_sum(array_map(create_function('$a', 'return $a->total_time;'), DB::get_profiles()));
	}

	/**
	 * Returns a humane commenter's link for a comment if a URL is supplied, or just display the comment author's name
	 *
	 * @param Theme $theme The current theme
	 * @param Comment $comment The comment object
	 * @return string A link to the comment author or the comment author's name with no link
	 */
	public function theme_comment_author_link( $theme, $comment )
	{
		$url = $comment->url;
		if ( $url != '' ) {
			$parsed_url = InputFilter::parse_url($url);
				if ( $parsed_url['host'] == '') {
					$url = '';
			}
			else {
				$url = InputFilter::glue_url($parsed_url);
			}
		}
		if ( $url != '' ) {
			return '<a href="'.$url.'">' . $comment->name . '</a>';
		}
		else {
			return $comment->name;
		}
	}

	/**
	 * 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->var_stack[$this->current_var_stack][$key] );
	}

	/**
	 * Set a template variable, a property alias for assign()
	 *
	 * @param string $key The template variable to set
	 * @param mixed $value The value of the variable
	 */
	public function __set( $key, $value )
	{
		$this->assign( $key, $value );
	}

	/**
	 * Get a template variable value
	 *
	 * @param string $key The template variable name to get
	 * @return mixed The value of the variable
	 */
	public function __get( $key )
	{
		if ( isset( $this->var_stack[$this->current_var_stack][$key] ) ) {
			return $this->var_stack[$this->current_var_stack][$key];
		}
		return '';
	}

	/**
	 * Remove a template variable value
	 *
	 * @param string $key The template variable name to unset
	 */
	public function __unset( $key )
	{
		unset($this->var_stack[$this->current_var_stack][$key]);
	}

	/**
	 * Start a new template variable buffer
	 */
	public function start_buffer()
	{
		$this->current_var_stack++;
		$this->var_stack[$this->current_var_stack] = $this->var_stack[$this->current_var_stack - 1];
	}

	/**
	 * End the current template variable buffer
	 */
	public function end_buffer()
	{
		unset($this->var_stack[$this->current_var_stack]);
		$this->current_var_stack--;
	}

	/**
	 * Handle methods called on this class or its descendants that are not defined by this class.
	 * Allow plugins to provide additional theme actions, like a custom act_display_*()
	 *
	 * @param string $function The method that was called.
	 * @param array $params An array of parameters passed to the method
	 **/
	public function __call( $function, $params )
	{
		if ( strpos( $function, 'act_' ) === 0 ) {
			// The first parameter is an array, get it
			if ( count( $params ) > 0 ) {
				list( $user_filters )= $params;
			}
			else {
				$user_filters = array();
			}
			$action = substr( $function, 4 );
			Plugins::act( 'theme_action', $action, $this, $user_filters );
		}
		else {
			$purposed = 'output';
			if ( preg_match( '/^(.*)_(return|end)$/', $function, $matches ) ) {
				$purposed = $matches[2];
				$function = $matches[1];
			}
			array_unshift( $params, $function, $this );
			$result = call_user_func_array( array( 'Plugins', 'theme' ), $params );
			switch( $purposed ) {
				case 'return':
					return $result;
				case 'end':
					echo end( $result );
					return end( $result );
				default:
					$output = implode( '', ( array ) $result );
					echo $output;
					return $output;
			}
		}
	}

	/**
	 * Retrieve the block objects for the current scope and specified area
	 * Incomplete!
	 *
	 * @param string $area The area to which blocks will be output
	 * @param string $scope The scope to which blocks will be output
	 * @param Theme $theme The theme that is outputting these blocks
	 * @return array An array of Block instances to render
	 * @todo Finish this function to pull data from a block_instances table
	 */
	public function get_blocks($area, $scope, $theme)
	{
		$blocks = DB::get_results('SELECT b.* FROM {blocks} b INNER JOIN {blocks_areas} ba ON ba.block_id = b.id WHERE ba.area = ? AND ba.scope_id = ? ORDER BY ba.display_order ASC', array($area, $scope), 'Block');
		Plugins::act('get_blocks', $blocks);
		return $blocks;
	}

	/**
	 * Matches the scope criteria against the current request
	 *
	 * @param array $criteria An array of scope criteria data in RPN, where values are arrays and operators are strings
	 * @return boolean True if the criteria matches the current request
	 */
	function check_scope_criteria($criteria)
	{
		$stack = array();
		foreach ( $criteria as $crit ) {
			if ( is_array($crit) ) {
				$value = false;
				switch ( $crit[0] ) {
					case 'request':
						$value = URL::get_matched_rule()->name == $crit[1];
						break;
					case 'token':
						if ( isset($crit[2]) ) {
							$value = User::identify()->can($crit[1], $crit[2]);
						}
						else {
							$value = User::identify()->can($crit[1]);
						}
						break;
					default:
						$value = Plugins::filter('scope_criteria_value', $value, $crit[1], $crit[2]);
						break;
				}
				$stack[] = $value;
			}
			else {
				switch($crit) {
					case 'not':
						$stack[] = ! array_pop($stack);
						break;
					case 'or':
						$value1 = array_pop($stack);
						$value2 = array_pop($stack);
						$stack[] = $value1 || $value2;
						break;
					case 'and':
						$value1 = array_pop($stack);
						$value2 = array_pop($stack);
						$stack[] = $value1 && $value2;
						break;
					default:
						Plugins::act('scope_criteria_operator', $stack, $crit);
						break;
				}
			}
		}
		return array_pop($stack);
	}

	/**
	 * Retrieve current scope data from the database based on the requested area
	 *
	 * @param string $area The area for which a scope may be applied
	 * @return array An array of scope data
	 */
	public function get_scopes($area)
	{
		$scopes = DB::get_keyvalue('SELECT s.id, s.criteria FROM {scopes} s INNER JOIN {blocks_areas} ba ON ba.scope_id = s.id WHERE ba.area = ? ORDER BY s.priority ASC', array($area));
		foreach ( $scopes as $key => $value ) {
			$scopes[$key] = unserialize($value);
		}
		Plugins::act('get_scopes', $scopes);
		return $scopes;
	}

	/**
	 * Displays blocks associated to the specified area and current scope.
	 *
	 * @param Theme $theme The theme with which this area will be output
	 * @param string $area The area to which blocks will be output
	 * @param string $scope Used to force a specific scope
	 * @return string the output of all the blocks
	 */
	public function theme_area($theme, $area, $scope = null)
	{

		// This array would normally come from the database via:
		$scopes = $this->get_scopes($area);

		$active_scope = 0;
		foreach ( $scopes as $scope_id => $scope_criteria ) {
			if ( $this->check_scope_criteria($scope_criteria) ) {
				$active_scope = $scope_id;
				break;
			}
		}

 		$area_blocks = $this->get_blocks($area, $active_scope, $theme);

		$this->area = $area;
		$begin = '';
		$begin = Plugins::filter('area_begin', $begin, $area, $this);

		$output = '';
		foreach ( $area_blocks as $block_instance_id => $block ) {
			$hook = 'block_content_' . $block->type;
			Plugins::act($hook, $block, $this);
			$output .= implode( '', $this->content_return($block, $area));
		}

		$this->area = '';
		$end = '';
		$end = Plugins::filter('area_end', $end, $area, $this);

		$output = $begin . $output . $end;

		return $output;
	}

 	/**
 	 * A theme function for outputting CSS classes based on the requested content
 	 * @param Theme $theme A Theme object instance
 	 * @param mixed $args Additional classes that should be added to the ones generated
 	 * @return string The resultant classes
 	 */
	function theme_body_class($theme, $args = array())
	{
		$body_class = array();
		foreach ( get_object_vars($this->request) as $key => $value ) {
			if ( $value ) {
				$body_class[$key] = $key;
			}
		}

		$body_class = array_unique(array_merge($body_class, Stack::get_named_stack('body_class'), Utils::single_array($args)));
		$body_class = Plugins::filter('body_class', $body_class, $theme);
		return implode(' ', $body_class);
	}

}
?>