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

/**
 * Contains error-related functions and Habari's error handler.
 *
 **/
class Error extends Exception
{
	protected $message = '';
	protected $is_error = false;

	/**
	 * Constructor for the Error class
	 *
	 * @param string $message Exception to display
	 * @param integer $code Code of the exception
	 * @param boolean $is_error true if the exception represents an error handled by the Error class
	 */
	public function __construct( $message = 'Generic Habari Error', $code = 0, $is_error = false )
	{
		parent::__construct( $message, $code );
		$this->is_error = $is_error;
	}

	/**
	 * function handle_errors
	 *
	 * Configures the Error class to handle all errors.
	 */
	public static function handle_errors()
	{
		set_error_handler( array( 'Error', 'error_handler' ) );
		set_exception_handler( array( 'Error', 'exception_handler' ) );
	}

	/**
	 * Used to handle all uncaught exceptions.
	 */
	public static function exception_handler( $exception )
	{
		if ( isset( $exception->is_error ) && $exception->is_error ) {
			return;
		}
		printf(
			"
\n%s: %s in %s line %s\n
",
			get_class( $exception ),
			$exception->getMessage(),
			$exception->file,
			$exception->line
		);

		if ( DEBUG ) {
			self::print_backtrace( $exception->getTrace() );
		}

		if ( Options::get( 'log_backtraces' ) || DEBUG ) {
			$backtrace = print_r( $exception->getTrace(), true );
		}
		else {
			$backtrace = null;
		}

		EventLog::log( $exception->getMessage() . ' in ' . $exception->file . ':' . $exception->line, 'err', 'default', null, $backtrace );
	}

	/**
	 * Get the error text, file, and line number from the backtrace, which is more accurate
	 *
	 * @return string The contructed error string
	 */
	public function humane_error()
	{
		$trace = $this->getTrace();
		$trace1 = reset( $trace );

		$file = isset( $trace1['file'] ) ? $trace1['file'] : $this->getFile();
		$line = isset( $trace1['line'] ) ? $trace1['line'] : $this->getLine();

		return _t( '%1$s in %2$s line %3$s on request of "%4$s"', array( $this->getMessage(), $file, $line, $_SERVER['REQUEST_URI'] ) );
	}

	/**
	 * Used to handle all PHP errors after Error::handle_errors() is called.
	 */
	public static function error_handler( $errno, $errstr, $errfile, $errline, $errcontext )
	{
		if ( ( $errno & error_reporting() ) === 0 ) {
			return;
		}

		if ( !function_exists( '_t' ) ) {
			function _t( $v )
			{
				return $v;
			}
		}

		// Don't be fooled, we can't actually handle most of these.
		$error_names = array(
			// @locale A fatal PHP error at runtime. Code execution is stopped
			E_ERROR => _t( 'Error' ),
			// @locale A non-fatal PHP error at runtime. Code execution is not stopped
			E_WARNING => _t( 'Warning' ),
			// @locale A fatal PHP error generated while parsing the PHP
			E_PARSE => _t( 'Parse Error' ),
			// @locale PHP encountered something at runtime that could be an error or intended
			E_NOTICE => _t( 'Notice' ),
			// @locale A fatal PHP error during PHP startup. Code execution is stopped.
			E_CORE_ERROR => _t( 'Core Error' ),
			// @locale A non-fatal PHP during PHP startup. Code execution is not stopped.
			E_CORE_WARNING => _t( 'Core Warning' ),
			// @locale A fatal PHP error at runtime. Code execution is stopped.
			E_COMPILE_ERROR => _t( 'Compile Error' ),
			// @locale A non-fatal PHP error at runtime. Code execution is not stopped.
			E_COMPILE_WARNING => _t( 'Compile Warning' ),
			// @locale A fatal error generated by Habari or an addon. Code execution is stopped.
			E_USER_ERROR => _t( 'User Error' ),
			// @locale A non-fatal error generated by Habari or an addon. Code execution is not stopped.
			E_USER_WARNING => _t( 'User Warning' ),
			// @locale PHP encountered something generated by Habari or an addon at runtime that could be an error or intended
			E_USER_NOTICE => _t( 'User Notice' ),
			// @locale A suggestion from PHP that the code may need updated for interoperability or forward compatibility
			E_STRICT => _t( 'Strict Notice' ),
			// @locale A fatal PHP error at runtime. An error handler may be able to work around it. If not, code execution stops.
			E_RECOVERABLE_ERROR => _t( 'Recoverable Error' ),
		);
		if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
			// @locale A notification from PHP that the code is outdated and may not work in the future
			$error_names[E_DEPRECATED] = _t( 'Deprecated violation' );
			// @locale A notification that the code is outdated and Habari may not work with it in the future
			$error_names[E_USER_DEPRECATED] = _t( 'User deprecated violation' );
		}
		if ( strpos( $errfile, HABARI_PATH ) === 0 ) {
			$errfile = substr( $errfile, strlen( HABARI_PATH ) + 1 );
		}

		if ( ini_get( 'display_errors' ) || DEBUG ) {
			printf(
				"
\n%s: %s in %s line %s\n
",
				$error_names[$errno],
				$errstr,
				$errfile,
				$errline
			);
			if ( DEBUG ) {
				Error::print_backtrace();
			}

			if ( Options::get( 'log_backtraces' ) || DEBUG ) {
				$backtrace = print_r( debug_backtrace(), true );
			}
			else {
				$backtrace = null;
			}
		}

		if ( !isset( $backtrace ) ) {
			$backtrace= null;
		}

		EventLog::log( $errstr . ' in ' . $errfile . ':' . $errline, 'err', 'default', null, $backtrace );

		// throwing an Error make every error fatal!
		//throw new Error($errstr, 0, true);
	}

	/**
	 * Print a backtrace in a format that looks reasonable in both rendered HTML and text
	 *
	 * @param array $trace An optional array of trace data
	 */
	private static function print_backtrace( $trace = null )
	{
		if ( !isset( $trace ) ) {
			$trace = debug_backtrace();
		}
		print "
\n";
		$defaults = array(
			'file' => '[core]',
			'line' => '(eval)',
			'class' => '',
			'type' => '',
			'args' => array(),
		);

		foreach ( $trace as $n => $a ) {
			$a = array_merge( $defaults, $a );

			if ( $a['class'] == 'Error' ) {
				continue;
			}

			if ( strpos( $a['file'], HABARI_PATH ) === 0 ) {
				$a['file'] = substr( $a['file'], strlen( HABARI_PATH ) + 1 );
			}

			if ( defined( 'DEBUG_ARGS' ) ) {
				$args = array();
				foreach ( $a['args'] as $arg ) {
					$args[] = htmlentities( str_replace(
						array( "\n", "\r" ),
						array( "\n   ", '' ),
						var_export( $arg, true )
					) );
				}
				$args = implode( ",    ", $args );
				if ( strlen( $args ) > 1024 ) {
					$args = substr( $args, 0, 1021 ) . '…';
				}
			}
			else {
				$args = count( $a['args'] ) == 0 ? ' ' : sprintf( _n( ' …%d arg… ', ' …%d args… ', count( $a['args'] ) ), $a['args'] );
			}

			printf(
				_t( "%s line %d:\n  %s(%s)\n" ),
				$a['file'],
				$a['line'],
				$a['class'].$a['type'].$a['function'],
				$args
			);
		}
		print "\n
\n";
	}

	/**
	 * function out
	 *
	 * Outputs the error message in plain text
	 */
	public function out()
	{
		if ( is_scalar( $this->message ) ) {
			echo $this->message . "\n";
		}
		else {
			echo var_export( $this->message, true ) . "\n";
		}
	}

	/**
	 * function get
	 *
	 * Returns the error message in plain text
	 */
	public function get()
	{
		return $this->message;
	}

	/**
	 * function raise
	 *
	 * Convenience method to create and return a new Error object
	 */
	public static function raise( $error_message, $severity = E_USER_ERROR )
	{
		return new Error( $error_message, $severity );
	}

	/**
	 * function is_error
	 *
	 * Returns true if the argument is an Error instance
	 */
	public static function is_error( $obj )
	{
		return ($obj instanceof Error);
	}

}

?>


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

/**
 * Habari EventLog class
 *
 * @todo Apply system error handling
 */
class EventLog extends ArrayObject
{
	protected $get_param_cache; // Stores info about the last set of data fetched that was not a single value

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

		return false;
	}

	/**
	 * Adds a logging type to the lookup table
	 *
	 * @param string $type The type of the error
	 * @param string $module The module of the error
	 */
	public static function register_type( $type = 'default', $module = null )
	{
		try {
			DB::query( 'INSERT INTO {log_types} (module, type) VALUES (?,?)', array( self::get_module( $module ), $type ) );
		}
		catch( Exception $e ) {
			// Don't really care if there's a duplicate.
		}
	}

	/**
	 * Removes a logging type from the lookup table
	 *
	 * @param string $type The type of the error
	 * @param string $module The module of the error
	 */
	public static function unregister_type( $type = 'default', $module = null )
	{
		$id = DB::get_value( "SELECT id FROM {log_types} WHERE module = ? and type = ?", array( self::get_module( $module ), $type ) );
		if ( $id ) {
			if ( !DB::exists( '{log}', array( 'type_id' => $id ) ) ) {
				DB::delete( '{log_types}', array( 'id' => $id ) );
			}
		}
	}

	/**
	 * Write an entry to the event log.
	 *
	 * @param string $message The message
	 * @param string $severity The severity
	 * @param string $type The type
	 * @param string $module The module
	 * @param mixed $data The data
	 * @return object LogEntry The inserted LogEntry object
	 */
	public static function log( $message, $severity = 'info', $type = 'default', $module = null, $data = null )
	{
		$module = self::get_module( $module );
		$log = new LogEntry( array(
			'message' => $message,
			'severity' => $severity,
			'module' => $module,
			'type' => $type,
			'data' => $data,
			'ip' => Utils::get_ip(),
		) );
		$user = User::identify();
		if ( $user->loggedin ) {
			$log->user_id = $user->id;
		}
		$log->insert();
		return $log;
	}

	/**
	 * Get the module in which the logged code was executed
	 *
	 * @param integer $level How many backtrace calls to go back through the trace
	 * @return string The classname or .php module in which the log code was called.
	 */
	public static function get_module( $module = null, $level = 2 )
	{
		if ( is_null( $module ) ) {
			$bt = debug_backtrace();
			$last = $bt[$level];
			$module = isset( $last['class'] ) ? $last['class'] : basename( $last['file'], '.php' );
		}
		return $module;
	}

	/**
	 * Returns a LogEntry or EventLog array based on supplied parameters.
	 * By default,fetch as many entries as pagination allows and order them in a descending fashion based on timestamp.
	 *
	 * @todo Cache query results.
	 * @param array $paramarry An associated array of parameters, or a querystring
	 * @return array An array of LogEntry objects, or a single LogEntry object, depending on request
	 */
	public static function get( $paramarray = array() )
	{
		$params = array();
		$fns = array( 'get_results', 'get_row', 'get_value' );
		$select = '';

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

		$select_fields = LogEntry::default_fields();
		if ( !isset( $paramarray['return_data'] ) ) {
			unset( $select_fields['data'] );
		}
		foreach ( $select_fields as $field => $value ) {
			$select .= ( '' == $select )
				? "{log}.$field"
				: ", {log}.$field";
		}
		// Default parameters.
		$orderby = 'ORDER BY timestamp DESC, id DESC';
		$limit = Options::get( 'pagination' );

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

		foreach ( $paramarray as $key => $value ) {
			if ( 'orderby' == $key ) {
				$orderby = ' ORDER BY ' . $value;
				continue;
			}
		}

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

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

				if ( isset( $paramset['id'] ) && is_numeric( $paramset['id'] ) ) {
					$where[] = "id= ?";
					$params[] = $paramset['id'];
				}
				if ( isset( $paramset['user_id'] ) ) {
					$where[] = "user_id= ?";
					$params[] = $paramset['user_id'];
				}
				if ( isset( $paramset['severity'] ) && ( 'any' != LogEntry::severity_name( $paramset['severity'] ) ) ) {
					$where[] = "severity_id= ?";
					$params[] = LogEntry::severity( $paramset['severity'] );
				}
				if ( isset( $paramset['type_id'] ) ) {
					if ( is_array( $paramset['type_id'] ) ) {
						$types = array_filter( $paramset['type_id'], 'is_numeric' );
						if ( count( $types ) ) {
							$where[] = 'type_id IN (' . implode( ',', $types ) . ')';
						}
					}
					else {
						$where[] = 'type_id = ?';
						$params[] = $paramset['type_id'];
					}
				}

				if ( isset( $paramset['module'] ) ) {

					if ( !is_array( $paramset['module'] ) ) {
						$paramset['module'] = array( $paramset['module'] );
					}

					$where[] = 'type_id IN ( SELECT DISTINCT id FROM {log_types} WHERE module IN ( ' . implode( ', ', array_fill( 0, count( $paramset['module'] ), '?' ) ) . ' ) )';
					$params = array_merge( $params, $paramset['module'] );

				}

				if ( isset( $paramset['type'] ) ) {

					if ( !is_array( $paramset['type'] ) ) {
						$paramset['type'] = array( $paramset['type'] );
					}

					$where[] = 'type_id IN ( SELECT DISTINCT id FROM {log_types} WHERE type IN ( ' . implode( ', ', array_fill( 0, count( $paramset['type'] ), '?' ) ) . ' ) )';
					$params = array_merge( $params, $paramset['type'] );

				}

				if ( isset( $paramset['ip'] ) ) {
					$where[] = 'ip = ?';
					$params[] = $paramset['ip'];
				}

				/* do searching */
				if ( isset( $paramset['criteria'] ) ) {
					preg_match_all( '/(?<=")(\w[^"]*)(?=")|([:\w]+)/u', $paramset['criteria'], $matches );
					foreach ( $matches[0] as $word ) {
						if ( preg_match( '%^id:(\d+)$%i', $word, $special_crit ) ) {
							$where[] .= '(id = ?)';
							$params[] = $special_crit[1];
						}
						else {
							$where[] .= "( LOWER( message ) LIKE ? )";
							$params[] = '%' . MultiByte::strtolower( $word ) . '%';
						}
					}
				}

				/**
				 * Build the pubdate
				 * If we've got the day, then get the date.
				 * If we've got the month, but no date, get the month.
				 * If we've only got the year, get the whole year.
				 *
				 * @todo Ensure that we've actually got all the needed parts when we query on them
				 */
				if ( isset( $paramset['day'] ) ) {
					$where[] = 'timestamp BETWEEN ? AND ?';
					$start_date = sprintf( '%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day'] );
					$start_date = HabariDateTime::date_create( $start_date );
					$params[] = $start_date->sql;
					$params[] = $start_date->modify( '+1 day' )->sql;
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], $paramset['day'], $paramset['year'] ) );
					//$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'], $paramset['day'], $paramset['year'] ) );
				}
				elseif ( isset( $paramset['month'] ) ) {
					$where[] = 'timestamp BETWEEN ? AND ?';
					$start_date = sprintf( '%d-%02d-%02d', $paramset['year'], $paramset['month'], 1 );
					$start_date = HabariDateTime::date_create( $start_date );
					$params[] = $start_date->sql;
					$params[] = $start_date->modify( '+1 month' )->sql;
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, $paramset['month'], 1, $paramset['year'] ) );
					//$params[] = date( 'Y-m-d H:i:s', mktime( 23, 59, 59, $paramset['month'] + 1, 0, $paramset['year'] ) );
				}
				elseif ( isset( $paramset['year'] ) ) {
					$where[] = 'timestamp BETWEEN ? AND ?';
					$start_date = sprintf( '%d-%02d-%02d', $paramset['year'], 1, 1 );
					$start_date = HabariDateTime::date_create( $start_date );
					$params[] = $start_date->sql;
					$params[] = $start_date->modify( '+1 year' )->sql;
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, 0, 1, 1, $paramset['year'] ) );
					//$params[] = date( 'Y-m-d H:i:s', mktime( 0, 0, -1, 1, 1, $paramset['year'] + 1 ) );
				}

				$wheres[] = ' (' . implode( ' AND ', $where ) . ') ';
			}
		}

		if ( isset( $index ) && is_numeric( $index ) ) {
			$offset = ( intval( $index ) - 1 ) * intval( $limit );
		}

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

		if ( isset( $count ) ) {
			$select = "COUNT($count)";
			$fetch_fn = 'get_value';
			$orderby = '';
		}
		if ( isset( $limit ) ) {
			$limit = " LIMIT $limit";
			if ( isset( $offset ) ) {
				$limit .= " OFFSET $offset";
			}
		}
		// If the month counts are requested, replace the select clause
		if ( isset( $paramset['month_cts'] ) ) {
			// @todo shouldn't this hand back to habari to convert to DateTime so it reflects the right timezone?
			$select = 'MONTH(FROM_UNIXTIME(timestamp)) AS month, YEAR(FROM_UNIXTIME(timestamp)) AS year, COUNT(*) AS ct';
			$groupby = 'year, month';
			$orderby = ' ORDER BY year, month';
		}
		if ( isset( $nolimit ) || isset( $month_cts ) ) {
			$limit = '';
		}

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

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

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

			// If the fetch callback function is not get_results,
			// return an EventLog ArrayObject filled with the results as LogEntry objects.
		if ( 'get_results' != $fetch_fn ) {
			return $results;
		}
		elseif ( is_array( $results ) ) {
			$c = __CLASS__;
			$return_value = new $c( $results );
			$return_value->get_param_cache = $paramarray;
			return $return_value;
		}
	}

	/*
	 * Trim the EventLog down to the defined number of days to prevent it getting massively large.
	 */
	public static function trim()
	{
		// allow an option to be set to override the log retention - in days
		$retention = Options::get( 'log_retention', 14 );		// default to 14 days

		// make it into the string we'll use
		$retention = '-' . intval( $retention ) . ' days';

		// Trim the log table down
		$date = HabariDateTime::date_create()->modify( $retention );

		return DB::query( 'DELETE FROM {log} WHERE timestamp < ?', array( $date->sql ) );

	}

	public static function purge ()
	{
		$result = DB::query( 'DELETE FROM {log}' );

		if ( $result ) {
			EventLog::log( _t( 'Logs purged.' ), 'info' );
		}

		return $result;

	}

}

?>

repo_name: DaSourcerer/habari


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

/**
 * Contains the FileCache class
 *
 * Stores cache data in local files
 */
class FileCache extends Cache
{
	protected $cache_location;
	protected $enabled = false;
	protected $cache_files = array();
	protected $cache_data = array();
	protected $index_file;

	/**
	 * Constructor for FileCache
	 *
	 * Sets up paths etc. and reads cache index, if it exists.
	 */
	public function __construct()
	{
		if ( !defined( 'FILE_CACHE_LOCATION' ) ) {
			define( 'FILE_CACHE_LOCATION', HABARI_PATH . '/user/cache/' );
		}
		$this->cache_location = FILE_CACHE_LOCATION;
		$this->index_file = $this->cache_location . md5( 'index' . Options::get( 'GUID' ) ) . '.data';
		$this->enabled = is_writeable( $this->cache_location );
		if ( $this->enabled ) {
			if ( file_exists( $this->index_file ) ) {
				$this->cache_files = unserialize( file_get_contents( $this->index_file ) );
			}
		}
		else {
			Session::error( _t( "The cache directory '%s' is not writable - the cache is disabled. The user, or group, which your web server is running as, needs to have read, write, and execute permissions on this directory.", array( $this->cache_location ) ), 'filecache' );
			EventLog::log( _t( "The cache directory '%s' is not writable - the cache is disabled.", array( $this->cache_location ) ), 'notice', 'cache', 'habari' );
		}
	}

	/**
	 * Is record with $name in the cache?
	 *
	 * @param string $name name of the cached item
	 * @return boolean true if item is cached, false if not
	 */
	protected function _has( $name, $group )
	{
		if ( !$this->enabled ) {
			return false;
		}
		$hash = $this->get_name_hash( $name );
		$ghash = $this->get_group_hash( $group );

		return isset( $this->cache_files[$ghash][$hash] ) && ( $this->cache_files[$ghash][$hash]['keep'] || $this->cache_files[$ghash][$hash]['expires'] > time() ) && file_exists( $this->cache_files[$ghash][$hash]['file'] );
	}

	/**
	 * Is group in the cache?
	 *
	 * @param string $name name of the cached item
	 * @return boolean true if item is cached, false if not
	 */
	protected function _has_group( $group )
	{
		if ( !$this->enabled ) {
			return false;
		}
		$ghash = $this->get_group_hash( $group );

		$valid = true;
		$now = time();
		foreach ( $this->cache_files[$ghash] as $hash => $record ) {
			if ( ! file_exists( $record['file'] ) || $record['expires'] <= $now ) {
				$valid = false;
				break;
			}
		}

		return ( isset( $this->cache_files[$ghash] ) && count( $this->cache_files[$ghash] ) > 1 ) && $valid;
	}

	/**
	 * Returns the group from the cache.
	 *
	 * @param string $name The name of the cached item
	 * @return mixed The group or array() if it doesn't exist in cache
	 */
	protected function _get_group( $group )
	{
		if ( !$this->enabled ) {
			return null;
		}
		$ghash = $this->get_group_hash( $group );

		if ( !isset( $this->cache_data[$group] ) ) {
			$this->cache_data[$group] = array();
			if ( isset( $this->cache_files[$ghash] ) ) {
				foreach ( $this->cache_files[$ghash] as $hash => $record ) {
					$this->cache_data[$group][$record['name']] = unserialize(
						file_get_contents( $record['file'] )
					);
				}
			}
		}
		return $this->cache_data[$group];
	}

	/**
	 * Returns the named value from the cache.
	 *
	 * @param string $name The name of the cached item
	 * @return mixed The item value or null if it doesn't exist in cache
	 */
	protected function _get( $name, $group )
	{
		if ( !$this->enabled ) {
			return null;
		}
		$hash = $this->get_name_hash( $name );
		$ghash = $this->get_group_hash( $group );

		if ( !isset( $this->cache_data[$group][$name] ) ) {
			$this->cache_data[$group][$name] = null;
			if ( isset( $this->cache_files[$ghash][$hash] ) && ($this->cache_files[$ghash][$hash]['keep'] || $this->cache_files[$ghash][$hash]['expires'] > time()) && file_exists( $this->cache_files[$ghash][$hash]['file'] ) ) {
				$this->cache_data[$group][$name] = unserialize( file_get_contents( $this->cache_files[$ghash][$hash]['file'] ) );
			}
		}
		return $this->cache_data[$group][$name];
	}

	protected function _set( $name, $value, $expiry, $group, $keep )
	{
		if ( !$this->enabled ) {
			return null;
		}

		Plugins::act( 'cache_set_before', $name, $group, $value, $expiry );

		$hash = $this->get_name_hash( $name );
		$ghash = $this->get_group_hash( $group );

		if ( !isset( $this->cache_data[$group] ) ) {
			// prime our cache so the local version is up-to-date and complete
			$this->_get_group( $group );
		}
		$this->cache_data[$group][$name] = $value;

		file_put_contents( $this->cache_location . $ghash . $hash, serialize( $value ) );
		$this->cache_files[$ghash][$hash] = array( 'file' => $this->cache_location . $ghash . $hash, 'expires' => time() + $expiry, 'name' => $name, 'keep' => $keep );
		$this->clear_expired();
		file_put_contents( $this->index_file, serialize( $this->cache_files ) );

		Plugins::act( 'cache_set_after', $name, $group, $value, $expiry );

		return true;
	}

	/**
	 * Expires the named value from the cache.
	 *
	 * @param string $name The name of the cached item
	 * @param string $group The name of the cache group
	 * @param string $match_mode (optional) how to match bucket names ('strict', 'regex', 'glob') (default 'strict')
	 */
	protected function _expire( $name, $group, $match_mode = 'strict' )
	{
		if ( !$this->enabled ) {
			return null;
		}

		// prime the variable cache.
		// dirty, dirty hack. we should *never* load all the data in, especially when we only care about the expirey
		// alas, this crappy code requires it
		$this->_get_group($group);

		$keys = array();
		switch ( strtolower( $match_mode ) ) {
			case 'glob':
				if ( array_key_exists( $group, $this->cache_data ) ) {
					$keys = preg_grep( Utils::glob_to_regex( $name ), array_keys( $this->cache_data[$group] ) );
				}
				break;
			case 'regex':
				if ( array_key_exists( $group, $this->cache_data ) ) {
					$keys = preg_grep( $name, array_keys( $this->cache_data[$group] ) );
				}
				break;
			case 'strict':
			default:
				$keys = array( $name );
				break;
		}

		$ghash = $this->get_group_hash( $group );
		foreach ( $keys as $key ) {
			Plugins::act( 'cache_expire_before', $name, $group );

			$hash = $this->get_name_hash( $key );

			if ( isset( $this->cache_files[$ghash][$hash] ) && file_exists( $this->cache_files[$ghash][$hash]['file'] ) ) {
				unlink( $this->cache_files[$ghash][$hash]['file'] );
				unset( $this->cache_files[$ghash][$hash] );
			}

			Plugins::act( 'cache_expire_after', $name, $group );
		}

		$this->clear_expired();
		file_put_contents( $this->index_file, serialize( $this->cache_files ) );
	}

	/**
	 * Return whether a named cache value has expired
	 *
	 * @param string $name The name of the cached item
	 * @param string $group The group of the cached item
	 * @return boolean true if the stored value has expired
	 */
	protected function _expired( $name, $group )
	{
		if ( !$this->enabled ) {
			return null;
		}
		$hash = $this->get_name_hash( $name );
		$ghash = $this->get_group_hash( $group );

		// Do not check cached data, since we can return (and cache in this object) data if the cache is set to 'keep'
		if ( isset( $this->cache_files[$ghash][$hash] ) && $this->cache_files[$ghash][$hash]['expires'] > time() && file_exists( $this->cache_files[$ghash][$hash]['file'] ) ) {
			return false;
		}
		else {
			return true;
		}
	}

	/**
	 * Extend the expiration of the named cached value.
	 *
	 * @param string $name The name of the cached item
	 * @param integer $expiry The duration in seconds to extend the cache expiration by
	 */
	protected function _extend( $name, $expiry, $group )
	{
		if ( !$this->enabled ) {
			return null;
		}

		Plugins::act( 'cache_extend_before', $name, $group, $expiry );

		$hash = $this->get_name_hash( $name );
		$ghash = $this->get_group_hash( $group );

		if ( isset( $this->cache_files[$ghash][$hash] ) ) {
			$this->cache_files[$ghash][$hash]['expires'] = time() + $expiry;
			$this->clear_expired();
			file_put_contents( $this->index_file, serialize( $this->cache_files ) );
		}

		Plugins::act( 'cache_extend_after', $name, $group, $expiry );
	}

	/**
	 * Remove all cache files
	 */
	protected function _purge()
	{
		Plugins::act( 'cache_purge_before' );

		$glob = Utils::glob( FILE_CACHE_LOCATION . '*.data' );
		foreach ( $glob as $file ) {
			unlink( $file );
		}
		$glob = Utils::glob( FILE_CACHE_LOCATION . '*.cache' );
		foreach ( $glob as $file ) {
			unlink( $file );
		}

		Plugins::act( 'cache_purge_after' );
	}

	/**
	 * Get the unique hash for a given key.
	 *
	 * @param string $name The name of the cached item.
	 */
	private function get_name_hash( $name )
	{
		return md5( $name . Options::get( 'GUID' ) ) . '.cache';
	}

	/**
	 * Get the unique hash for a given key.
	 *
	 * @param string $name The name of the cached group.
	 */
	private function get_group_hash( $group )
	{
		return md5( $group . Options::get( 'GUID' ) ) . '.';
	}

	/**
	 * Check whether a given record is still fresh (e.g. has not expired).
	 */
	private function record_fresh( $record )
	{
		if ( $record['expires'] > time() || $record['keep'] ) {
			return true;
		}
		elseif ( file_exists( $record['file'] ) ) {
			unlink( $record['file'] );
		}
		return false;
	}

	/**
	 * Purge expired items from the cache.
	 */
	private function clear_expired()
	{
		foreach ( $this->cache_files as $ghash => $records ) {
			$this->cache_files[$ghash] = array_filter( $records, array( $this, 'record_fresh' ) );
		}
	}

}

?>


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

/**
 * Habari Format Class
 *
 * Provides formatting functions for use in themes.  Extendable.
 *
 */
class Format
{
	private static $formatters = null;

	/**
	 * Called to register a format function to a plugin hook, only passing the hook's first parameter to the Format function.
	 * @param string $format A function name that exists in a Format class
	 * @param string $onwhat A plugin hook to apply that Format function to as a filter
	 */
	public static function apply( $format, $onwhat )
	{
		if ( self::$formatters == null ) {
			self::load_all();
		}

		$priority = 8;
		if(preg_match('#^(.+)_(\d+)$#', $onwhat, $matches)) {
			$priority = intval($matches[2]);
			$onwhat = $matches[1];
		}

		$method = false;
		if (is_callable($format)) {
			$method = $format;
		}
		else {
			foreach ( self::$formatters as $formatobj ) {
				if ( method_exists( $formatobj, $format ) ) {
					$method = array($formatobj, $format);
					break;
				}
			}
		}

		if($method) {
			$args = func_get_args();
			$args = array_slice($args, 2);
			$lambda = function() use ($args, $method) {
				$filterargs = func_get_args();
				$filterargs = array_slice($filterargs, 0, 1);
				foreach($args as $arg) {
					$filterargs[] = $arg;
				}
				return call_user_func_array($method, $filterargs);
			};
			Plugins::register( $lambda, 'filter', $onwhat );
		}
	}

	/**
	 * Called to register a format function to a plugin hook, and passes all of the hook's parameters to the Format function.
	 * @param string $format A function name that exists in a Format class
	 * @param string $onwhat A plugin hook to apply that Format function to as a filter
	 */
	public static function apply_with_hook_params( $format, $onwhat )
	{
		if ( self::$formatters == null ) {
			self::load_all();
		}

		$priority = 8;
		if(preg_match('#^(.+)_(\d+)$#', $onwhat, $matches)) {
			$priority = intval($matches[2]);
			$onwhat = $matches[1];
		}

		$method = false;
		if (is_callable($format)) {
			$method = $format;
		}
		else {
			foreach ( self::$formatters as $formatobj ) {
				if ( method_exists( $formatobj, $format ) ) {
					$method = array($formatobj, $format);
					break;
				}
			}
		}

		if($method) {
			$args = func_get_args();
			$args = array_slice($args, 2);
			$lambda = function() use ($args, $method) {
				$filterargs = func_get_args();
				//$filterargs = array_slice($filterargs, 0, 1);
				foreach($args as $arg) {
					$filterargs[] = $arg;
				}
				return call_user_func_array($method, $filterargs);
			};
			Plugins::register( $lambda, 'filter', $onwhat );
		}
	}

	/**
	 * function by_index
	 * Returns an indexed formatter object, for use by lambda functions created
	 * to supply additional parameters to plugin filters.
	 * @param integer $index The index of the formatter object to return.
	 * @return Format The formatter object requested
	 */
	public static function by_index( $index )
	{
		return self::$formatters[$index];
	}

	/**
	 * function load_all
	 * Loads and stores an instance of all declared Format classes for future use
	 */
	public static function load_all()
	{
		self::$formatters = array();
		$classes = get_declared_classes();
		foreach ( $classes as $class ) {
			if ( ( get_parent_class( $class ) == 'Format' ) || ( $class == 'Format' ) ) {
				self::$formatters[] = new $class();
			}
		}
		self::$formatters = array_merge( self::$formatters, Plugins::get_by_interface( 'FormatPlugin' ) );
		self::$formatters = array_reverse( self::$formatters, true );
	}

	/** DEFAULT FORMAT FUNCTIONS **/

	/**
	 * function autop
	 * Converts non-HTML paragraphs separated with 2 or more new lines into HTML paragraphs
	 * while preserving any internal HTML.
	 * New lines within the text of block elements are converted to linebreaks.
	 * New lines before and after tags are stripped.
	 *
	 * If you make changes to this, PLEASE add test cases here:
	 *   http://svn.habariproject.org/habari/trunk/tests/data/autop/
	 *
	 * @param string $value The string to apply the formatting
	 * @returns string The formatted string
	 */
	public static function autop( $value )
	{
		$value = str_replace( "\r\n", "\n", $value );
		$value = trim( $value );
		$ht = new HtmlTokenizer( $value, false );
		$set = $ht->parse();
		$value = '';

		// should never autop ANY content in these items
		$no_auto_p = array(
			'pre','code','ul','h1','h2','h3','h4','h5','h6',
			'table','ul','ol','li','i','b','em','strong','script'
		);

		$block_elements = array(
			'address','blockquote','center','dir','div','dl','fieldset','form',
			'h1','h2','h3','h4','h5','h6','hr','isindex','menu','noframes',
			'noscript','ol','p','pre','table','ul','figure','figcaption'
		);

		$token = $set->current();

		// There are no tokens in the text being formatted
		if ( $token === false ) {
			return $value;
		}

		$open_p = false;
		do {

			if ( $open_p ) {
				if ( ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_EMPTY || $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN || $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE ) && in_array( strtolower( $token['name'] ), $block_elements ) ) {
					if ( strtolower( $token['name'] ) != 'p' || $token['type'] != HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE ) {
						$value .= '
';
					}
					$open_p = false;
				}
			}

			if ( ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN || $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_EMPTY ) && !in_array( strtolower( $token['name'] ), $block_elements ) && !$open_p ) {
				// first element, is not a block element
				$value .= '
';
				$open_p = true;
			}

			// no-autop, pass them through verbatim
			if ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN && in_array( strtolower( $token['name'] ), $no_auto_p ) ) {
				$nested_token = $token;
				do {
					$value .= HtmlTokenSet::token_to_string( $nested_token, false );
					if (
						( $nested_token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE
							&& strtolower( $nested_token['name'] ) == strtolower( $token['name'] ) ) // found closing element
					) {
						break;
					}
				} while ( $nested_token = $set->next() );
				continue;
			}

			// anything that's not a text node should get passed through
			if ( $token['type'] != HTMLTokenizer::NODE_TYPE_TEXT ) {
				$value .= HtmlTokenSet::token_to_string( $token, true );
				// If the token itself is p, we need to set $open_p
				if ( strtolower( $token['name'] ) == 'p' && $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN ) {
					$open_p = true;
				}
				continue;
			}

			// if we get this far, token type is text
			$local_value = $token['value'];
			if ( MultiByte::strlen( $local_value ) ) {
				if ( !$open_p ) {
					$local_value = '
' . ltrim( $local_value );
					$open_p = true;
				}

				$local_value = preg_replace( '/\s*(\n\s*){2,}/u', "

", $local_value ); // at least two \n in a row (allow whitespace in between)
				$local_value = str_replace( "\n", "
", $local_value ); // nl2br
			}
			$value .= $local_value;
		} while ( $token = $set->next() );

		$value = preg_replace( '#\s*

\s*#u', '', $value ); // replace

		$value = preg_replace( '/
<\/p>/', "", $value ); // replace

 around comments
		if ( $open_p ) {
			$value .= '
';
		}

		return $value;
	}

	/**
	 * function and_list
	 * Turns an array of strings into a friendly delimited string separated by commas and an "and"
	 * @param array $array An array of strings
	 * @param string $between Text to put between each element
	 * @param string $between_last Text to put between the next-to-last element and the last element
	 * @reutrn string The constructed string
	 */
	public static function and_list( $array, $between = ', ', $between_last = null )
	{
		if ( ! is_array( $array ) ) {
			$array = array( $array );
		}

		if ( $between_last === null ) {
			$between_last = _t( ' and ' );
		}

		$last = array_pop( $array );
		$out = implode( ', ', $array );
		$out .= ($out == '') ? $last : $between_last . $last;
		return $out;
	}

	/**
	 * function tag_and_list
	 * Formatting function (should be in Format class?)
	 * Turns an array of tag names into an HTML-linked list with commas and an "and".
	 * @param array $array An array of tag names
	 * @param string $between Text to put between each element
	 * @param string $between_last Text to put between the next to last element and the last element
	 * @param boolean $sort_alphabetical Should the tags be sorted alphabetically by `term` first?
	 * @return string HTML links with specified separators.
	 */
	public static function tag_and_list( $terms, $between = ', ', $between_last = null, $sort_alphabetical = false )
	{
		$array = array();
		if ( !$terms instanceof Terms ) {
			$terms = new Terms( $terms );
		}

		foreach ( $terms as $term ) {
			$array[$term->term] = $term->term_display;
		}

		if ( $sort_alphabetical ) {
			ksort( $array );
		}

		if ( $between_last === null ) {
			$between_last = _t( ' and ' );
		}

		$fn = function($a, $b) {
			return " $b) ) . "\" rel=\"tag\">" . $a . "";
		};
		$array = array_map( $fn, $array, array_keys( $array ) );
		$last = array_pop( $array );
		$out = implode( $between, $array );
		$out .= ( $out == '' ) ? $last : $between_last . $last;
		return $out;

	}

	/**
	 * Format a date using a specially formatted string
	 * Useful for using a single string to format multiple date components.
	 * Example:
	 *  If $dt is a HabariDateTime for December 10, 2008...
	 *  echo $dt->format_date('
{F} {j}, {Y}
');
	 *  // Output:
December 10, 2008

	 *
	 * @param HabariDateTime $date The date to format
	 * @param string $format A string with date()-like letters within braces to replace with date components
	 * @return string The formatted string
	 */
	public static function format_date( $date, $format )
	{
		if ( !( $date instanceOf HabariDateTime ) ) {
			$date = HabariDateTime::date_create( $date );
		}
		return $date->text_format( $format );
	}

	/**
	 * function nice_date
	 * Formats a date using a date format string
	 * @param HabariDateTime $date A date as a HabariDateTime object
	 * @param string $dateformat A date format string
	 * @returns string The date formatted as a string
	 */
	public static function nice_date( $date, $dateformat = 'F j, Y' )
	{
		if ( !( $date instanceOf HabariDateTime ) ) {
			$date = HabariDateTime::date_create( $date );
		}
		return $date->format( $dateformat );
	}

	/**
	 * function nice_time
	 * Formats a time using a date format string
	 * @param HabariDateTime $date A date as a HabariDateTime object
	 * @param string $dateformat A date format string
	 * @returns string The time formatted as a string
	 */
	public static function nice_time( $date, $dateformat = 'H:i:s' )
	{
		if ( !( $date instanceOf HabariDateTime ) ) {
			$date = HabariDateTime::date_create( $date );
		}
		return $date->format( $dateformat );
	}

	/**
	 * Returns a shortened version of whatever is passed in.
	 * @param string $value A string to shorten
	 * @param integer $count Maximum words to display [100]
	 * @param integer $max_paragraphs Maximum paragraphs to display [1]
	 * @return string The string, shortened
	 */
	public static function summarize( $text, $count = 100, $max_paragraphs = 1 )
	{
		$ellipsis = '…';

		$showmore = false;

		$ht = new HtmlTokenizer($text, false);
		$set = $ht->parse();

		$stack = array();
		$para = 0;
		$token = $set->current();
		$summary = new HTMLTokenSet();
		$set->rewind();
		$remaining_words = $count;
		// $bail lets the loop end naturally and close all open elements without adding new ones.
		$bail = false;
		for ( $token = $set->current(); $set->valid(); $token = $set->next() ) {
			if ( !$bail && $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN ) {
				$stack[] = $token;
			}
			if ( !$bail ) {
				switch ( $token['type'] ) {
					case HTMLTokenizer::NODE_TYPE_TEXT:
						$words = preg_split( '/(\\s+)/u', $token['value'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
						// word count is doubled because spaces between words are captured as their own array elements via PREG_SPLIT_DELIM_CAPTURE
						$words = array_slice( $words, 0, $remaining_words * 2 );
						$remaining_words -= count( $words ) / 2;
						$token['value'] = implode( '', $words );
						if ( $remaining_words <= 0 ) {
							$token['value'] .= $ellipsis;
							$summary[] = $token;
							$bail = true;
						}
						else {
							$summary[] = $token;
						}
						break;
					case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE;
						// don't handle this case here
						break;
					default:
						$summary[] = $token;
						break;
				}
			}
			if ( $token['type'] == HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE ) {
				do {
					$end = array_pop( $stack );
					$end['type'] = HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE;
					$end['attrs'] = null;
					$end['value'] = null;
					$summary[] = $end;
				} while ( ( $bail || $end['name'] != $token['name'] ) && count( $stack ) > 0 );
				if ( count( $stack ) == 0 ) {
					$para++;
				}
				if ( $bail || $para >= $max_paragraphs ) {
					break;
				}
			}
		}

		return (string) $summary;
	}

	/**
	 * Returns a truncated version of post content when the post isn't being displayed on its own.
	 * Posts are split either at the comment  or at the specified maximums.
	 * Use only after applying autop or other paragrpah styling methods.
	 * Apply to posts using:
	 * Format::apply_with_hook_params( 'more', 'post_content_out' );
	 * @param string $content The post content
	 * @param Post $post The Post object of the post
	 * @param string $more_text The text to use in the "read more" link.
	 * @param integer $max_words null or the maximum number of words to use before showing the more link
	 * @param integer $max_paragraphs null or the maximum number of paragraphs to use before showing the more link
	 * @return string The post content, suitable for display
	 */
	public static function more( $content, $post, $properties = array() )
	{
		// If the post requested is the post under consideration, always return the full post
		if ( $post->slug == Controller::get_var( 'slug' ) ) {
			return $content;
		}
		elseif ( is_string( $properties ) ) {
			$args = func_get_args();
			$more_text = $properties;
			$max_words = ( isset( $args[3] ) ? $args[3] : null );
			$max_paragraphs = ( isset( $args[4] ) ? $args[4] : null );
			$paramstring = "";
		}
		else {
			$paramstring = "";
			$paramarray = Utils::get_params( $properties );

			$more_text = ( isset( $paramarray['more_text'] ) ? $paramarray['more_text'] : 'Read More' );
			$max_words = ( isset( $paramarray['max_words'] ) ? $paramarray['max_words'] : null );
			$max_paragraphs = ( isset( $paramarray['max_paragraphs'] ) ? $paramarray['max_paragraphs'] : null );

			if ( isset( $paramarray['title:before'] ) || isset( $paramarray['title'] ) || isset( $paramarray['title:after'] ) ) {
				$paramstring .= 'title="';

				if ( isset( $paramarray['title:before'] ) ) {
					$paramstring .= $paramarray['title:before'];
				}
				if ( isset( $paramarray['title'] ) ) {
					$paramstring .= $post->title;
				}
				if ( isset( $paramarray['title:after'] ) ) {
					$paramstring .= $paramarray['title:after'];
				}
				$paramstring .= '" ';
			}
			if ( isset( $paramarray['class'] ) ) {
				$paramstring .= 'class="' . $paramarray['class'] . '" ';
			}

		}
		$matches = preg_split( '//isu', $content, 2, PREG_SPLIT_NO_EMPTY );
		if ( count( $matches ) > 1 ) {
			return ( $more_text != '' ) ? reset( $matches ) . ' permalink . '">' . $more_text . '' : reset( $matches );
		}
		elseif ( isset( $max_words ) || isset( $max_paragraphs ) ) {
			$max_words = empty( $max_words ) ? 9999999 : intval( $max_words );
			$max_paragraphs = empty( $max_paragraphs ) ? 9999999 : intval( $max_paragraphs );
			$summary = Format::summarize( $content, $max_words, $max_paragraphs );
			if ( MultiByte::strlen( $summary ) >= MultiByte::strlen( $content ) ) {
				return $content;
			}
			else {
				if ( strlen( $more_text  ) ) {
					// Tokenize the summary and link
					$ht = new HTMLTokenizer( $summary );
					$summary_set = $ht->parse();
					$ht = new HTMLTokenizer( 'permalink . '">' . $more_text . '' );
					$link_set= $ht->parse();
					// Find out where to put the link
					$end = $summary_set->end();
					$key = $summary_set->key();
					// Inject the link
					$summary_set->insert( $link_set, $key );

					return (string)$summary_set;
				}
				else {
					return $summary;
				}
			}
		}

	return $content;
	}

	/**
	 * html_messages
	 * Creates an HTML unordered list of an array of messages
	 * @param array $notices a list of success messages
	 * @param array $errors a list of error messages
	 * @return string HTML output
	 */
	public static function html_messages( $notices, $errors )
	{
		$output = '';
		if ( count( $errors ) ) {
			$output.= '
';
			foreach ( $errors as $error ) {
				$output.= '
' . $error . '
';
			}
			$output.= '
';
		}
		if ( count( $notices ) ) {
			$output.= '
';
			foreach ( $notices as $notice ) {
				$output.= '
' . $notice . '
';
			}
			$output.= '
';
		}

		return $output;
	}

	/**
	 * humane_messages
	 * Creates JS calls to display session messages
	 * @param array $notices a list of success messages
	 * @param array $errors a list of error messages
	 * @return string JS output
	 */
	public static function humane_messages( $notices, $errors )
	{
		$output = '';
		if ( count( $errors ) ) {
			foreach ( $errors as $error ) {
				$error = addslashes( $error );
				$output .= "human_msg.display_msg(\"{$error}\");";
			}
		}
		if ( count( $notices ) ) {
			foreach ( $notices as $notice ) {
				$notice = addslashes( $notice );
				$output .= "human_msg.display_msg(\"{$notice}\");";
			}
		}

		return $output;
	}

	/**
	 * json_messages
	 * Creates a JSON list of session messages
	 * @param array $notices a list of success messages
	 * @param array $errors a list of error messages
	 * @return string JS output
	 */
	public static function json_messages( $notices, $errors )
	{
		$messages = array_merge( $errors, $notices );
		return json_encode( $messages );
	}

	/**
	 * function term_tree
	 * Create nested HTML lists from a hierarchical vocabulary.
	 *
	 * Turns Terms or an array of terms from a hierarchical vocabulary into a ordered HTML list with list items for each term.
	 * @param mixed $terms An array of Term objects or a Terms object.
	 * @param string $tree_name The name of the tree, used for unique node id's
	 * @param array $config an array of values to use to configure the output of this function
	 * @return string The transformed vocabulary.
	 */
	public static function term_tree( $terms, $tree_name, $config = array() )
	{
		$defaults = array(
			'treestart' => '
',
			'treeattr' => array('class' => 'tree', 'id' => Utils::slugify('tree_' . $tree_name)),
			'treeend' => '
',
			'liststart' => '
',
			'listattr' => array(),
			'listend' => '
',
			'itemstart' => '
',
			'itemattr' => array('class' => 'treeitem'),
			'itemend' => '
',
			'wrapper' => '
%s
',
			'linkcallback' => null,
			'itemcallback' => null,
			'listcallback' => null,
		);
		$config = array_merge($defaults, $config);

		$out = sprintf($config['treestart'], Utils::html_attr($config['treeattr']));
		$stack = array();
		$tree_name = Utils::slugify($tree_name);

		if ( !$terms instanceof Terms ) {
			$terms = new Terms( $terms );
		}

		foreach ( $terms as $term ) {
			if(count($stack)) {
				if($term->mptt_left - end($stack)->mptt_left == 1) {
					if(isset($config['listcallback'])) {
						$config = call_user_func($config['listcallback'], $term, $config);
					}
					$out .= sprintf($config['liststart'], Utils::html_attr($config['listattr']));
				}
				while(count($stack) && $term->mptt_left > end($stack)->mptt_right) {
					$out .= $config['listend'] . $config['itemend'] . "\n";
					array_pop($stack);
				}
			}

			$config['itemattr']['id'] = $tree_name . '_' . $term->id;
			if(isset($config['itemcallback'])) {
				$config = call_user_func($config['itemcallback'], $term, $config);
			}
			$out .= sprintf($config['itemstart'], Utils::html_attr($config['itemattr']));
			if(isset($config['linkcallback'])) {
				$display = call_user_func($config['linkcallback'], $term, $config);
			}
			else {
				$display = $term->term_display;
			}
			$out .= sprintf( $config['wrapper'], $display );
			if($term->mptt_right - $term->mptt_left > 1) {
				$stack[] = $term;
			}
			else {
				$out .= $config['itemend'] ."\n";
			}
		}
		while(count($stack)) {
			$out .= $config['listend'] . $config['itemend'] . "\n";
			array_pop($stack);
		}

		$out .= $config['treeend'];
		return $out;
	}
}
?>


code:
<?php

/**
 * interface FormStorage
 *
 * Describes functions that are required to store form data into an object
 */

interface FormStorage
{
	/**
	 * Stores a form value into the object
	 *
	 * @param string $key The name of a form component that will be stored
	 * @param mixed $value The value of the form component to store
	 */
	function field_save( $key, $value );

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

?>