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

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

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

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

			// Admin actions
			array( 'name' => 'display_dashboard', 'parse_regex' => '#^admin/?$#', 'build_str' => 'admin', 'handler' => 'AdminDashboardHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Display the admin dashboard' ),
			array( 'name' => 'display_publish', 'parse_regex' => '#^admin/(?P<page>publish)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminPostsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage publishing posts' ),
			array( 'name' => 'display_posts', 'parse_regex' => '#^admin/(?P<page>posts)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminPostsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage posts' ),
			array( 'name' => 'delete_post', 'parse_regex' => '#^admin/(?P<page>delete_post)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminPostsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Delete a post' ),
			array( 'name' => 'user_profile', 'parse_regex' => '#^admin/(?P<page>user)/(?P<user>[^/]+)/?$#', 'build_str' => 'admin/{$page}/{$user}', 'handler' => 'AdminUsersHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'The profile page for a specific user' ),
			array( 'name' => 'display_users', 'parse_regex' => '#^admin/(?P<page>users)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminUsersHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage users' ),
			array( 'name' => 'own_user_profile', 'parse_regex' => '#^admin/(?P<page>user)/?$#', 'build_str' => 'admin/{$page}/{$user}', 'handler' => 'AdminUsersHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'The profile page for a specific user' ),
			array( 'name' => 'display_themes', 'parse_regex' => '#^admin/(?P<page>themes)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminThemesHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage themes' ),
			array( 'name' => 'activate_theme', 'parse_regex' => '#^admin/(?P<page>activate_theme)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminThemesHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Activate a theme' ),
			array( 'name' => 'preview_theme', 'parse_regex' => '#^admin/(?P<page>preview_theme)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminThemesHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Preview a theme' ),
			array( 'name' => 'configure_block', 'parse_regex' => '#^admin/(?P<page>configure_block)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminThemesHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Configure a block in an iframe' ),
			array( 'name' => 'display_plugins', 'parse_regex' => '#^admin/(?P<page>plugins)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminPluginsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage plugins' ),
			array( 'name' => 'plugin_toggle', 'parse_regex' => '#^admin/(?P<page>plugin_toggle)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminPluginsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Activate or deactivate a plugin' ),
			array( 'name' => 'display_options', 'parse_regex' => '#^admin/(?P<page>options)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminOptionsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'The options page for the blog' ),
			array( 'name' => 'display_comments', 'parse_regex' => '#^admin/(?P<page>comments)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminCommentsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage comments' ),
			array( 'name' => 'edit_comment', 'parse_regex' => '#^admin/(?P<page>comment)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminCommentsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Edit a comment' ),
			array( 'name' => 'display_groups', 'parse_regex' => '#^admin/(?P<page>groups)/?$#', 'build_str' => 'admin/{$page}', 'handler' => 'AdminGroupsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage groups' ),
			array( 'name' => 'display_group', 'parse_regex' => '#^admin/(?P<page>group)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminGroupsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage a group' ),
			array( 'name' => 'display_tags', 'parse_regex' => '#^admin/(?P<page>tags)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminTagsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage tags' ),
			array( 'name' => 'display_logs', 'parse_regex' => '#^admin/(?P<page>logs)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminLogsHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage logs' ),
			array( 'name' => 'display_import', 'parse_regex' => '#^admin/(?P<page>import)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminimportHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Manage importing content' ),
			array( 'name' => 'get_locale', 'parse_regex' => '#^admin/(?P<page>locale)/?$#i', 'build_str' => 'admin/{$page}', 'handler' => 'AdminLocaleHandler', 'action' => 'admin', 'priority' => 4, 'description' => 'Fetch the locale data as javascript' ),

			array( 'name' => 'admin', 'parse_regex' => '#^admin(?:/?$|/(?P<page>[^/]*))/?$#i', 'build_str' => 'admin/({$page})', 'handler' => 'AdminHandler', 'action' => 'admin', 'priority' => 6, 'description' => 'An admin action' ),

			// Admin AJAX actions
			array( 'name' => 'admin_ajax_dashboard', 'parse_regex' => '#^admin_ajax/(?P<context>dashboard)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminDashboardHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for the admin dashboard' ),
			array( 'name' => 'admin_ajax_posts', 'parse_regex' => '#^admin_ajax/(?P<context>posts)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminPostsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for the managing posts' ),
			array( 'name' => 'admin_ajax_update_posts', 'parse_regex' => '#^admin_ajax/(?P<context>update_posts)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminPostsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for the updating posts' ),
			array( 'name' => 'admin_ajax_media', 'parse_regex' => '#^admin_ajax/(?P<context>media)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminPostsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling from media silos' ),
			array( 'name' => 'admin_ajax_media_panel', 'parse_regex' => '#^admin_ajax/(?P<context>media_panel)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminPostsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling from media panels' ),
			array( 'name' => 'admin_ajax_media_upload', 'parse_regex' => '#^admin_ajax/(?P<context>media_upload)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminPostsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling from media panel uploads' ),
			array( 'name' => 'admin_ajax_add_block', 'parse_regex' => '#^admin_ajax/(?P<context>add_block)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminThemesHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for adding a block' ),
			array( 'name' => 'admin_ajax_delete_block', 'parse_regex' => '#^admin_ajax/(?P<context>delete_block)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminThemesHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for deleting a block' ),
			array( 'name' => 'admin_ajax_save_areas', 'parse_regex' => '#^admin_ajax/(?P<context>save_areas)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminThemesHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for saving areas' ),
			array( 'name' => 'admin_ajax_comments', 'parse_regex' => '#^admin_ajax/(?P<context>comments)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminCommentsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for managing comments' ),
			array( 'name' => 'admin_ajax_update_comment', 'parse_regex' => '#^admin_ajax/(?P<context>update_comment)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminCommentsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for updating a comment' ),
			array( 'name' => 'admin_ajax_groups', 'parse_regex' => '#^admin_ajax/(?P<context>groups)/?$#i', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminGroupsHandler', 'action' => 'admin_ajax', 'priority' => 8, 'description' => 'Authenticated ajax handling for managing groups' ),
			array( 'name' => 'admin_ajax_update_groups', 'parse_regex' => '#^admin_ajax/(?P<context>update_groups)/?$#', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminGroupsHandler', 'action' => 'admin_ajax', 'priority' => 4, 'description' => 'Authenticated ajax handler for updating a group' ),
			array( 'name' => 'admin_ajax_tags', 'parse_regex' => '#^admin_ajax/(?P<context>tags)/?$#', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminTagsHandler', 'action' => 'admin_ajax', 'priority' => 4, 'description' => 'Authenticated ajax handler for managing tags' ),
			array( 'name' => 'admin_ajax_get_tags', 'parse_regex' => '#^admin_ajax/(?P<context>get_tags)/?$#', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminTagsHandler', 'action' => 'admin_ajax', 'priority' => 4, 'description' => 'Authenticated ajax handler for retrieving tags' ),
			array( 'name' => 'admin_ajax_logs', 'parse_regex' => '#^admin_ajax/(?P<context>logs)/?$#', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminLogsHandler', 'action' => 'admin_ajax', 'priority' => 4, 'description' => 'Authenticated ajax handler for managing logs' ),
			array( 'name' => 'admin_ajax_delete_logs', 'parse_regex' => '#^admin_ajax/(?P<context>delete_logs)/?$#', 'build_str' => 'admin_ajax/{$context}', 'handler' => 'AdminLogsHandler', 'action' => 'admin_ajax', 'priority' => 4, 'description' => 'Authenticated ajax handler for deleting logs' ),

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

			// User actions
			array( 'name' => 'auth', 'parse_regex' => '#^auth/(?P<page>[^/]*)$#i', 'build_str' => 'auth/{$page}', 'handler' => 'UserHandler', 'action' => '{$page}', 'priority' => 7, 'description' => 'A user action or display, for instance the login screen' ),
			array( 'name' => 'user', 'parse_regex' => '#^user/(?P<page>[^/]*)$#i', 'build_str' => 'user/{$page}', 'handler' => 'UserHandler', 'action' => '{$page}', 'priority' => 7, 'description' => 'A user action or display, for instance the login screen' ),

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

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

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

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

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

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

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

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

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

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

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

	/**
	 * Get a RewriteRule by its name
	 *
	 * @param string $name The name of the rule
	 * @return array An array of all matched RewriteRule objects.
	 */
	public static function by_name( $name )
	{
		static $named = null;

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

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

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

?>


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

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

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

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

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

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

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

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

?>

repo_name: DaSourcerer/habari


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

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

	/**
	 * Initialize the session handlers
	 */
	public static function init()
	{

		// the default path for the session cookie is /, but let's make that potentially more restrictive so no one steals our cookehs
		// we also can't use 'null' when we set a secure-only value, because that doesn't mean the same as the default like it should
		$path = Site::get_path( 'base', true );

		// the default is not to require a secure session
		$secure = false;

		// if we want to always require secure
		if ( Config::get( 'force_secure_session' ) == true ) {
			$secure = true;
		}

		// if this is an HTTPS connection by default we will
		// IIS sets HTTPS == 'off', so we have to check the value too
		if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) {
			$secure = true;
		}

		// but if we have explicitly disabled it, don't
		// note the ===. not setting it (ie: null) should not be the same as setting it to false
		if ( Config::get( 'force_secure_session' ) === false ) {
			$secure = false;
		}

		// now we've got a path and secure, so set the cookie values
		session_set_cookie_params( null, $path, null, $secure );

		// figure out the session lifetime and let plugins change it
		$lifetime = ini_get( 'session.gc_maxlifetime' );

		self::$lifetime = Plugins::filter( 'session_lifetime', $lifetime );

		session_set_save_handler(
			array( 'Session', 'open' ),
			array( 'Session', 'close' ),
			array( 'Session', 'read' ),
			array( 'Session', 'write' ),
			array( 'Session', 'destroy' ),
			array( 'Session', 'gc' )
		);
		// session::write gets called after object destruction, so our class isn't available
		// fix that by registering it as a shutdown function, before objects are destroyed
		register_shutdown_function( 'session_write_close' );

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

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

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

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

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

		$dodelete = false;

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

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

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

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

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

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

		// save the initial data we loaded so we can write only if it's changed
		self::$initial_data = $session->data;

		// but if the expiration is close (less than half the session lifetime away), null it out so the session always gets written so we extend the session
		if ( ( $session->expires - HabariDateTime::date_create()->int ) < ( self::$lifetime / 2 ) ) {
			self::$initial_data = null;
		}

		return $session->data;
	}

	/**
	 * Commit $_SESSION data to the database for this user.
	 *
	 * @param string $session_id The PHP-generated session id
	 * @param string $data Data from session stored as a string
	 */
	public static function write( $session_id, $data )
	{

		$remote_address = Utils::get_ip();
		// not always set, even by real browsers
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';

		// default to writing the data only if it's changed
		if ( self::$initial_data !== $data ) {
			$dowrite = true;
		}
		else {
			$dowrite = false;
		}

		// but let a plugin make the final decision. we may want to ignore search spiders, for instance
		$dowrite = Plugins::filter( 'session_write', $dowrite, $session_id, $data );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return $output;
	}

	/**
	 * Output notice and error messages
	 *
	 * @param boolean $clear true to clear the messages from the session upon receipt
	 * @param array $callback a reference to a callback function for formatting the the messages
	 */
	public static function messages_out( $clear = true, $callback = null )
	{
		echo self::messages_get( $clear, $callback );
	}

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

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

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

		// if it's an ipv6 address, we just use that and don't try to determine the subnet
		$is_v6 = filter_var( $remote_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 );

		if ( $is_v6 !== false ) {
			return $remote_address;
		}

		$long_addr = ip2long( $remote_address );

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

	}

}

?>


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

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

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

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

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

?>


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;
	static $config_urldir;

	/**
	 * 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
	 *  'site' returns http://www.habariproject.org/site if
	 *    you are installing with a subdirectory path
	 *	'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.
				$port = Config::get( 'custom_http_port', isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : $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'] == 'on' ) {
					$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 'site':
				$url = Site::get_url( 'host' );
				if( self::$config_type == Site::CONFIG_SUBDIR ) {
					$url .= '/' . self::$config_urldir;
				}
				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 'vendor':
			case 'scripts':
				$url = Site::get_url( 'system' ) . '/vendor';
				break;
			case '3rdparty':
				// this should be removed at a later date as it will cause problems
				// once 'vendor' is adopted, dump the condition!
				if ( file_exists( HABARI_PATH . '/3rdparty' ) ) {
					$url = Site::get_url( 'habari' ) . '/3rdparty';
				}
				else {
					$url = Site::get_url( 'vendor' );
				}
				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':
			case 'habari':
				$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\/(.*)/', '$1', Utils::glob( HABARI_PATH . '/user/sites/*', GLOB_ONLYDIR ) );

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

				$server = InputFilter::parse_url( Site::get_url( 'habari' ) );
				$request = array();
				if ( isset( $server['port'] ) && $server['port'] != '' && $server['port'] != '80' ) {
					$request[] = $server['port'];
				}
				$request = array_merge($request, explode('.', $server['host']));
				$basesegments = count($request);
				$request = array_merge($request, explode( '/', trim( $_SERVER['REQUEST_URI'], '/' ) ) );
				$x = 0;
				do {
					$match = implode('.', $request);
					if ( in_array( $match, $config_dirs ) ) {
						self::$config_dir = $match;
						self::$config_path = HABARI_PATH . '/user/sites/' . self::$config_dir;
						self::$config_type = ( $basesegments > count($request) ) ? Site::CONFIG_SUBDOMAIN : Site::CONFIG_SUBDIR;
						self::$config_urldir = implode('/', array_slice($request, $basesegments));
						break;
					}

					array_pop($request);
					$x--;
					if ( $x < -10 ) {
						echo $x;
						var_dump($request);
						die('too many ');
					}
				} while ( count($request) > 0 );
				$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;
			case 'vendor':
				$path = HABARI_PATH . '/system/vendor';
				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

	class SocketRequestProcessor extends RequestProcessor {

		public function __construct ( ) {

			// see if we can follow Location: headers
			if ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) {
				$this->can_followlocation = false;
			}

			if ( !defined( 'FILE_CACHE_LOCATION' ) ) {
				define( 'FILE_CACHE_LOCATION', HABARI_PATH . '/user/cache/' );
			}

		}

		public function execute ( $method, $url, $headers, $body, $config ) {

			// before we handle headers, see if we should add our compression headers
			if ( $this->can_zlib() ) {
				$headers['Accept-Encoding'] = 'gzip,deflate';
			}

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

			// parse out the URL so we can refer to individual pieces
			$url_pieces = InputFilter::parse_url( $url );

			// set up the options we'll use when creating the request's context
			$options = array(
				'http' => array(
					'method' => $method,
					'header' => implode( "\n", $merged_headers ),
					'timeout' => $config['timeout'],
					'follow_location' => $this->can_followlocation,		// 5.3.4+, should be ignored by others
					'max_redirects' => $config['max_redirects'],

					// and now for our ssl-specific portions, which will be ignored for non-HTTPS requests
					'verify_peer' => $config['ssl']['verify_peer'],
					//'verify_host' => $config['ssl']['verify_host'],	// there doesn't appear to be an equiv of this for sockets - the host is matched by default and you can't just turn that off, only substitute other hostnames
					'cafile' => $config['ssl']['cafile'],
					'capath' => $config['ssl']['capath'],
					'local_cert' => $config['ssl']['local_cert'],
					'passphrase' => $config['ssl']['passphrase'],
				),
			);

			if ( $method == 'POST' ) {
				$options['http']['content'] = $body;
			}

			if ( $config['proxy']['server'] != '' && !in_array( $url_pieces['host'], $config['proxy']['exceptions'] ) ) {
				$proxy = $config['proxy']['server'] . ':' . $config['proxy']['port'];

				if ( $config['proxy']['username'] != '' ) {
					$proxy = $config['proxy']['username'] . ':' . $config['proxy']['password'] . '@' . $proxy;
				}

				$options['http']['proxy'] = 'tcp://' . $proxy;
			}

			// create the context
			$context = stream_context_create( $options );

			// perform the actual request - we use fopen so stream_get_meta_data works
			$fh = @fopen( $url, 'r', false, $context );

			if ( $fh === false ) {
				throw new Exception( _t( 'Unable to connect to %s', array( $url_pieces['host'] ) ) );
			}

			// read in all the contents -- this is the same as file_get_contens, only for a specific stream handle
			$body = stream_get_contents( $fh );

			// get meta data
			$meta = stream_get_meta_data( $fh );

			// close the connection before we do anything else
			fclose( $fh );

			// did we timeout?
			if ( $meta['timed_out'] == true ) {
				throw new RemoteRequest_Timeout( _t( 'Request timed out' ) );
			}

			// $meta['wrapper_data'] should be a list of the headers, the same as is loaded into $http_response_header
			$headers = array();
			foreach ( $meta['wrapper_data'] as $header ) {

				// break the header up into field and value
				$pieces = explode( ': ', $header, 2 );

				if ( count( $pieces ) > 1 ) {
					// if the header was a key: value format, store it keyed in the array
					$headers[ $pieces[0] ] = $pieces[1];
				}
				else {
					// some headers (like the HTTP version in use) aren't keyed, so just store it keyed as itself
					$headers[ $pieces[0] ] = $pieces[0];
				}

			}

			// check to see if the response was compressed
			if ( isset( $headers['Content-Encoding'] ) ) {
				$encoding = trim( $headers['Content-Encoding'] );

				if ( $encoding == 'gzip' ) {
					$body = $this->gzdecode( $body );
				}
				else if ( $encoding == 'deflate' ) {
					$body = gzinflate( $body );
				}
			}

			$this->response_headers = $headers;
			$this->response_body = $body;
			$this->executed = true;

			return true;

		}

		private function can_zlib ( ) {

			// make sure that the zlib extension is loaded
			if ( !extension_loaded( 'zlib' ) ) {
				return false;
			}

			// since we have to write to a temp file to de-gzip, make sure we can do that
			$tmp = tempnam( FILE_CACHE_LOCATION, 'RRS' );		// RRS for RemoteRequestSocket, get it?

			// if creating the temp file failed, that's it
			if ( !$tmp ) {
				return false;
			}

			$fh = @fopen( $tmp, 'w+b' );

			// if actually opening the file for writing failed
			if ( !$fh ) {
				return false;
			}

			fclose( $fh );

			// now we should be good to go, let's clean up after ourselves
			if ( file_exists( $tmp ) ) {
				unlink( $tmp );
			}

			// and we're good
			return true;

		}

		private function gzdecode ( $body ) {

			// create the temp file to write to
			$tmp = tempnam( FILE_CACHE_LOCATION, 'RRS' );

			if ( !$tmp ) {
				throw new Exception( _t( 'Socket Error. Unable to create temporary file name.' ) );
			}

			$result = file_put_contents( $tmp, $body );

			if ( $result === false ) {
				throw new Exception( _t( 'Socket Error. Unable to write to temporary file.' ) );
			}

			// before we read it back in, try to free up as much memory as possible
			unset( $body );

			$zp = gzopen( $tmp, 'rb' );

			$body = '';
			while ( !gzeof( $zp ) ) {
				$body .= gzread( $zp, 1024 );
			}

			gzclose( $zp );

			// clean up the temp file
			if ( file_exists( $tmp ) ) {
				unlink( $tmp );
			}

			return $body;

		}

	}

?>


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 ) {
				self::depend($stack_name, $value_name, $a);
			}
		}
		$stack[$value_name] = $value;
		self::$stacks[$stack_name] = $stack;
		return $stack;
	}

	/**
	 * Add a named stack item to the list of things it depends on
	 * @static
	 * @param string $stack_name The name of the stack
	 * @param string $value_name The item name in the stack
	 * @param string $value_name_on The item name on which this item depends
	 */
	public static function depend( $stack_name, $value_name, $value_name_on )
	{
		if ( !isset( self::$stack_sort[$stack_name] ) ) {
			self::$stack_sort[$stack_name] = array();
		}
		if ( !isset( self::$stack_sort[$stack_name][$value_name] ) ) {
			self::$stack_sort[$stack_name][$value_name] = array();
		}
		self::$stack_sort[$stack_name][$value_name][$value_name_on] = $value_name_on;
	}

	/**
	 * 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 = null )
	{

		if ( $value_name == null ) {
			unset( self::$stacks[ $stack_name ] );
			return array();
		}

		$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 );

		$sorted = array();
		$depdata = self::$stack_sort[ $stack_name ];
		$lastcount = 0;
		$failed = false;
		while(count($sorted) < count($stack)) {
			foreach($stack as $itemkey => $value) {
				if(isset($sorted[$itemkey])) {
					continue;
				}
				if(!$failed && isset($depdata[$itemkey])) {
					$requires = $depdata[$itemkey];
					$requires = array_combine($depdata[$itemkey], $depdata[$itemkey]);
					if(count(array_intersect_key($requires, $sorted)) == count($requires)) {
						$sorted[$itemkey] = $value;
					}
				}
				else {
					$sorted[$itemkey] = $value;
				}
			}
			if($lastcount == count($sorted)) {
				$failed = true;
			}
			$lastcount = count($sorted);
		}

		return $sorted;
	}

	/**
	 * 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
	 * @param mixed $attrib Additional attributes, like 'defer' or 'async' allowed for <script src=...> tags
	 * @param string $wrapper An sprintf formatting string in which to output the script tag, for IE conditional comments
	 * @return string The resulting script tag
	 */
	public static function scripts( $element, $attrib = null, $wrapper = '%s' )
	{
		if(is_array($attrib)) {
			$attrib = $attrib + array('type' => 'text/javascript');
			$attrib = Utils::html_attr($attrib);
		}
		else {
			$attrib .= ' type="text/javascript"';
		}
		if ( self::is_url( $element ) ) {
			$output = sprintf( "<script %s src=\"%s\"></script>\r\n", $attrib, $element );
		}
		else {
			$output = sprintf( "<script %s>%s</script>\r\n", $attrib, $element );
		}
		$output = sprintf($wrapper, $output);
		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
	 * @param string $props Additional properties of the style tag output
	 * @return string The resulting style or link tag
	 */
	public static function styles( $element, $typename = null, $props = array() )
	{
		$props = $props + array('type' => 'text/css');
		if ( !empty( $typename ) ) {
			$props['media'] = $typename;
		}

		if ( self::is_url( $element ) ) {
			$props = $props + array('rel' => 'stylesheet', 'href' => $element);
			$output = sprintf( "<link %s>\r\n", Utils::html_attr($props) );
		}
		else {
			$output = sprintf( "<style %2\$s>%1\$s</style>\r\n", $element, Utils::html_attr($props) );
		}
		return $output;
	}

	/**
	 * Check if the passed string looks like a URL or an absolute path to a file.
	 *
	 * @todo There's a good chance this can be done in a better or more generic
	 * way.
	 *
	 * @param string $url The string to check.
	 * @return boolean TRUE if the passed string looks like a URL.
	 */
	private static function is_url( $url )
	{
		return ( ( strpos( $url, 'http://' ) === 0 || strpos( $url, 'https://' ) === 0 || strpos( $url, '//' ) === 0 || strpos( $url, '/' ) === 0 ) && strpos( $url, "\n" ) === false );
	}
}

?>


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 extends Term
{

	public function __construct( $params = array() )
	{
		if ( is_string( $params ) ) {
			$params = array( 'term_display' => $params );
		}
		$params['vocabulary_id'] = Tags::vocabulary()->id;
		parent::__construct( $params );
	}

	/**
	 * 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( 'Tags', 'get_one' ), $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->term_display;
		}
		return ( $tag instanceOf Term && 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 = Tags::vocabulary()->add_term( $tag );
		return $tag;
	}

	/**
	 * 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 );
	}

}

?>


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

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

	/**
	 * Return a tag based on an id, tag text or slug
	 *
	 * @return Tag The tag object
	 */
	public static function get_one( $tag )
	{
		$term = self::vocabulary()->get_term( $tag );
		if ( !$term instanceOf Term ) {
			return false;
		}
		return $term;
	}

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

	/**
	 * Return a tag based on a tag's text
	 *
	 * @return	A Tag object
	 */
	public static function get_by_slug( $tag )
	{
		return self::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 self::get_one( (int) $tag );
	}

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

	/**
	 * Returns a list of terms from this vocabulary ordered by frequency of use on the post type specified
	 *
	 * @param int $limit If supplied, limits the results to the specified number
	 * @param mixed $post_type If a name or id of a post type is supplied, limits the results to the terms applying to that type
	 * @return Tags A Tags instance containing the terms, each having an additional property of "count" that tells how many times the term was used
	 */
	public static function get_by_frequency($limit = null, $post_type = null)
	{
		$query = '
			SELECT t.*, count(*) as `count`
			FROM {terms} t
			INNER JOIN {object_terms} tp ON t.id=tp.term_id
			INNER JOIN {posts} p ON tp.object_id=p.id
			INNER JOIN {object_types} ot ON tp.object_type_id=ot.id and ot.name="post"
			WHERE t.vocabulary_id = ?';

		$params = array(Tags::vocabulary()->id);

		if ( isset($post_type) ) {
			$query .= ' AND p.content_type = ?';
			$params[] = Post::type($post_type);
		}

		$query .= '
			GROUP BY t.id
			ORDER BY `count` DESC, term_display ASC';

		if ( is_int($limit) ) {
			$query .= ' LIMIT ' . $limit;
		}

		$tags = DB::get_results(
			$query,
			$params,
			'Term'
		);

		return $tags ? new Terms($tags) : false;
	}

	/**
	 * 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( $terms, $object_id, $object_type = 'post' )
	{
		if ( ! $terms instanceof Terms ) {
			$terms = Terms::parse( $terms, 'Tag', Tags::vocabulary() );
		}
		return self::vocabulary()->set_object_terms( $object_type, $object_id, $terms );
	}

	/**
	 * Parse tag parameters from a URL string
	 *
	 * @param String $tags The URL parameter string
	 *
	 * @return Array. Associative array of included and excluded tags
	 */
	public static function parse_url_tags( $tags, $objectify = false )
	{
		$tags = explode( ' ', $tags );
		$exclude_tag = array();
		$include_tag = array();
		foreach ( $tags as $tag ) {
			if ( MultiByte::substr( $tag, 0, 1 ) == '-' ) {
				$tag = MultiByte::substr( $tag, 1 );
				$exclude_tag[] = $objectify ? Tags::get_one(Utils::slugify( $tag )) : Utils::slugify( $tag );
			}
			else {
				$include_tag[] = $objectify ? Tags::get_one(Utils::slugify( $tag )) : Utils::slugify( $tag );
			}
		}

		return compact('include_tag', 'exclude_tag');
	}
}
?>


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

	/**
	 * Add a template to the list of available templates
	 * @abstract
	 * @param string $name Name of the new template
	 * @param string $file File of the template to add
	 * @param boolean $replace If true, replace any existing template with this name
	 */
	abstract function add_template( $name, $file, $replace = false ); // 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.
 *
 * @property int id
 * @property int mptt_left
 * @property int mptt_right
 * @property string term_display
 * @property string term
 * @property int vocabulary_id
 */

class Term extends QueryRecord
{
	protected $inforecords = null;

	public $unsetfields = array(
		'object_id' => 'object_id',
		'type' => 'type',
	);

	/**
	 * 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' );
	}

	/**
	 * Generate a new slug for the post.
	 *
	 * @return string The slug
	 */
	protected function setslug()
	{
		$value = '';
		// 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();

		// Commit the info records
		$this->info->commit( $this->fields['id'] );

		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 ) );
		$this->fields = array_merge( $this->fields, $this->newfields );

		$this->info->commit();

		// 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 );

		// Delete all info records associated with this comment
		$this->info->delete_all();

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

		$result = parent::deleteRecord( '{terms}', array( 'id'=>$this->id ) );
		EventLog::log( _t( 'Term %1$s (%2$s) deleted.', array( $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' );
	}

	/**
	 * Test a Term's lineage.
	 * @return boolean true if $term is an ancestor of $this
	 */
	public function is_descendant_of( Term $term )
	{
		if ( $this->vocabulary_id != $term->vocabulary_id ) {
			return false;
		}
		if ( ( $this->mptt_left > $term->mptt_left ) && ( $this->mptt_right < $term->mptt_right ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Test a Term's ancestry
	 * @return boolean true if $term is a descendant of $this
	 */
	public function is_ancestor_of( Term $term )
	{
		if ( $this->vocabulary_id != $term->vocabulary_id ) {
			return false;
		}
		if ( ( $this->mptt_left < $term->mptt_left ) && ( $this->mptt_right > $term->mptt_right ) ) {
			return true;
		}
		return false;
	}

	/**
	 * 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()
	{
		$parent = $this->parent();
		if ( $parent ) {
			return $parent->children();
		}
		else {
			return $this->vocabulary->get_root_terms();
		}
	}

	/**
	 * 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,
	child.id as 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 mptt_left
SQL;
		return new Terms(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;
	}

	/**
	 * Find the types of objects associated with this Term.
	 *
	 * @return Array of objects, with each object containing an object id and an object type name
	 */
	public function object_types()
	{
		$results = DB::get_results( 'SELECT terms.object_id, types.name as `type` FROM {object_terms} terms, {object_types} types WHERE terms.object_type_id = types.id and term_id = :term_id', array( 'term_id' => $this->id ) );
		return $results;
	}

	/**
	 * Find the count of 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 object_count( $type )
	{
		$type_id = Vocabulary::object_type_id( $type );
		$result = DB::get_value( "SELECT count(object_id) FROM {object_terms} WHERE term_id = ? AND object_type_id = ?", array( $this->id, $type_id ) );
		return $result;
	}

	/**
	 * 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':
				$out = Vocabulary::get_by_id( $this->vocabulary_id );
				break;
			case 'tag_text_searchable':
				// if it's got spaces, then quote it.
				if ( strpos( $this->term_display, ' ' ) !== false || strpos( $this->term_display, ',' ) !== false ) {
					$out = '\'' . str_replace( "'", "\'", $this->term_display ) . '\'';
				}
				else {
					$out = $this->term_display;
				}
				break;
			case 'count':
				$out = (int)$this->count();
				break;
			case 'id':
				return (int)parent::__get( $name );
			case 'info':
				return $this->get_info();
			default:
				$out = parent::__get( $name );
				break;
		}
		return $out;
	}

	/**
	 * Gets the info object for this term, which contains data from the terminfo table
	 * related to this term.
	 * @return TermInfo object
	 */
	protected function get_info()
	{
		if ( ! $this->inforecords ) {
			if ( 0 == $this->id ) {
				$this->inforecords = new TermInfo();
			}
			else {
				$this->inforecords = new TermInfo( $this->id );
			}
		}
		return $this->inforecords;
	}

	/**
	 * Get a count of how many times the tag has been used in a post
	 * @param string $object_type The type of object to count
	 * @return integer The number of times the tag has been used
	 */
	public function count( $object_type = 'post' )
	{
		return $this->object_count( $object_type );
	}

	/**
	 * Handle calls to this Term 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, 'term_call_' . $name, null, $this );
		return call_user_func_array( array( 'Plugins', 'filter' ), $args );
	}

	/**
	 * Gets the term object by criteria.
	 * @param mixed $term A Term object, a string (for a term slug or display), or an integer (for a Term ID).
	 * @param string $term_class The class of the returned term object.
	 * @return Term The Term object requested
	 * @todo improve selective fetching by term slug vs term_display
	 **/
	public static function get( $term, $term_class = 'Term' )
	{
		$query = '';
		if ( $term instanceof Term ) {
			// This seems kind of silly, but by passing a different $term_class, the
			// database values are loaded into a different class instance and fire its constructor
			$params[ 'term_id' ] = $term->id;
			$query = 'SELECT * FROM {terms} WHERE id = ABS(:term_id)';
		}
		elseif ( is_string( $term ) ) {
			$params[ 'term' ] = $term;
			$query = 'SELECT * FROM {terms} WHERE (term = :term OR term_display = :term) ORDER BY term <> :term';
		}
		elseif ( is_int( $term ) ) {
			$params[ 'term_id' ] = $term;
			$query = 'SELECT * FROM {terms} WHERE id = ABS(:term_id)';
		}
		return DB::get_row( $query, $params, $term_class );
	}

}

?>


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

/**
 * Term metadata
 */
class TermInfo extends InfoRecords
{

	function __construct ( $term_id = null )
	{
		parent::__construct( DB::table( 'terminfo' ), 'term_id', $term_id ); // call parent with appropriate  parameters
	}

}

?>