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

/**
 * Habari Options Class
 *
 */
class Options extends Singleton
{
	private $options = null;

	/**
	 * Enables singleton working properly
	 *
	 * @see singleton.php
	 */
	protected static function instance()
	{
		return self::getInstanceOf( __CLASS__ );
	}

	/**
	 * Shortcut to return the value of an option
	 *
	 *
	 * 	 $foo = Options::get('foo'); // returns null if the option 'foo' does not exist
	 *   list($foo, $bar, $baz) = Options::get( array('foo1', 'bar2', 'baz3') ); // useful with array_keys()
	 *   $bar = Options::get('foo', 'bar'); // returns 'bar' if the option 'foo' does not exist. useful for avoiding if/then blocks to detect unset options
	 *
	 *
	 * @param string|array $name The name or an array of names of the option to fetch.
	 * @param mixed $default_value The value to return for an option if it does not exist.
	 * @return mixed The option requested or an array of requested options, $default_value for each if the option does not exist
	 **/
	public static function get( $name, $default_value = null )
	{
		if ( is_array( $name ) ) {
			$results = array();
			foreach ( $name as $key ) {
				$results[ $key ] = Options::get( $key );	// recursively wrap around ourselves!
			}
			return $results;
		}

		if ( self::instance()->$name ) {
			return self::instance()->$name;
		}
		else {
			return $default_value;
		}
	}

	/**
	 * Fetch a group of options with a specific prefix
	 * Useful for plugins that use FormUI to automatically store options with the plugin's prefix.
	 *
	 * @param string $prefix The prefix to fetch
	 * @return array An associative array of all options with that prefix
	 */
	public static function get_group( $prefix )
	{

		if ( substr( $prefix, -2 ) != '__' ) {
			$prefix .= '__';
		}

		$results = array();
		foreach ( array_keys( self::instance()->options ) as $key ) {

			if ( strpos( $key, $prefix ) === 0 ) {
				$results[ substr( $key, strlen( $prefix ) ) ] = Options::get( $key );
			}

		}

		return $results;

	}

	/**
	 * Set a group of options with a specific prefix
	 *
	 *
	 *   Options::set_group( 'foo', array( 'bar' => 'baz', 'qux' => 'quux' ) );
	 *   // results in 2 options: foo__bar == baz and foo__qux == quux
	 *
	 *
	 * @param string $prefix The prefix to set
	 * @param array $values An associative array of all options to be set with that prefix
	 */
	public static function set_group ( $prefix, $values )
	{

		if ( substr( $prefix, -2 ) != '__' ) {
			$prefix .= '__';
		}

		// loop through each option, setting it
		foreach ( $values as $k => $v ) {

			Options::set( $prefix . $k, $v );

		}

	}

	/**
	 * Delete a group of options with a specific prefix
	 *
	 *
	 *   Options::delete_group( 'foo' );
	 *   // would delete all foo__* option names
	 *
	 *
	 * @param string $prefix The prefix to delete
	 */
	public static function delete_group ( $prefix )
	{

		if ( substr( $prefix, -2 ) != '__' ) {
			$prefix .= '__';
		}

		foreach ( array_keys( self::instance()->options ) as $key ) {

			if ( strpos( $key, $prefix ) === 0 ) {
				Options::delete( $key );
			}

		}

	}

	/**
	 * Shortcut to output the value of an option
	 *
	 * Options::out('foo');
	 *
	 * @param string $name Name of the option to output
	 **/
	public static function out( $name )
	{
		echo self::instance()->get( $name );
	}

	/**
	 * function set
	 * Shortcut to set the value of an option
	 *
	 *
	 *   Options::set('foo', 'newvalue');
	 *   Options::set( array( 'foo' => 'bar', 'baz' => 'qux' ) );
	 *
	 *
	 * @param string|array $name Name of the option to set or an array of name => value options to set.
	 * @param mixed $value New value of the option to store. If first parameter is an array, $value is ignored.
	 **/
	public static function set( $name, $value = '' )
	{

		if ( is_array( $name ) ) {
			foreach ( $name as $k => $v ) {
				Options::set( $k, $v );	// recursively wrap around ourselves!
			}
		}
		else {
			self::instance()->$name = $value;
		}

	}

	/**
	 * Shortcut to unset an option in the options table
	 *
	 * @param string|array $name The name of the option or an array of names to delete.
	 */
	public static function delete( $name )
	{

		if ( is_array( $name ) ) {
			foreach ( $name as $key ) {
				Options::delete( $key ); // recursively wrap around ourselves!
			}
		}
		else {
			unset( self::instance()->$name );
		}

	}

	/**
	 * Retrieve an option value
	 * @param string $name Name of the option to get
	 * @return mixed Stored value for specified option
	 **/
	public function __get( $name )
	{
		if ( ! isset( $this->options ) ) {
			$this->get_all_options();
		}
		$option_value = isset( $this->options[$name] ) ? $this->options[$name] : null;
		$option_value = Plugins::filter( 'option_get_value', $option_value, $name );
		return $option_value;
	}

	/**
	 * Fetch all options from the options table into local storage
	 */
	public function get_all_options()
	{
		// Set some defaults here
		$this->options = array(
			'pagination' => 10,
			'comments_require_id' => false,
		);
		if(Config::exists('default_options')) {
			$this->options = array_merge($this->options, Config::get('default_options'));
		}
		$results = DB::get_results( 'SELECT name, value, type FROM {options}', array(), 'QueryRecord' );
		foreach ( $results as $result ) {
			if ( $result->type == 1 ) {
				$this->options[$result->name] = unserialize( $result->value );
			}
			else {
				$this->options[$result->name] = $result->value;
			}
		}
		if(Config::exists('static_options')) {
			$this->options = array_merge($this->options, Config::get('static_options'));
		}
	}

	/**
	 * Applies the option value to the options table
	 * @param string $name Name of the option to set
	 * @param mixed $value Value to set
	 **/
	public function __set( $name, $value )
	{
		if ( ! isset( $this->options ) ) {
			$this->get_all_options();
		}
		$value = Plugins::filter( 'option_set_value', $value, $name, isset( $this->options[$name] ) ? $this->options[$name] : null );
		$this->options[$name] = $value;

		if ( is_array( $value ) || is_object( $value ) ) {
			$result = DB::update( DB::table( 'options' ), array( 'name' => $name, 'value' => serialize( $value ), 'type' => 1 ), array( 'name' => $name ) );
		}
		else {
			$result = DB::update( DB::table( 'options' ), array( 'name' => $name, 'value' => $value, 'type' => 0 ), array( 'name' => $name ) );
		}
		if ( Error::is_error( $result ) ) {
			$result->out();
			die();
		}
	}

	/**
	 * Removes an option from the options table
	 *
	 * @param string $name The name of the option
	 */
	public function __unset( $name )
	{
		unset( $this->options[$name] );
		DB::delete( DB::table( 'options' ), array( 'name' => $name ) );
	}

	/**
	 * Clears memory of cached options
	 **/
	public static function clear_cache()
	{
		self::instance()->options = null;
	}

	/**
	 * Check if an option was set via the config, making it unsettable
	 * @static
	 * @param string $name The name of the option to check
	 * @return bool True if the option is set in the config
	 */
	public static function is_static($name)
	{
		if($static_options = Config::exists('static_options')) {
			if(isset($static_options[$name])) {
				return true;
			}
		}
		return false;
	}

}

?>


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

/**
 * Pluggable class
 * Implements methods that allow descendant classes to register functions to plugin hooks
 *
 * @version $Id$
 * @copyright 2008
 */
abstract class Pluggable
{
	private $_class_name = null;
	public $info;
	public $plugin_id;
	private $_new_rules = array();
	protected $_added_templates = array();

	/**
	 * Pluggable constructor.
	 * This function creates some internal structures that are required for plugin processing
	 * Plugins should not define their own constructors, because they are instantiated
	 * to extract plugin info.  Instead, include a sink for a "init" hook
	 * which is executed immediately after the plugin is loaded during normal execution.
	 */
	public function __construct()
	{
		$this->info = $this->info();
		$this->plugin_id = $this->plugin_id();
	}

	/**
	 * Gets the filename that contains this pluggable class
	 * @return string The filename of the file that contains the pluggable class.
	 */
	final public function get_file()
	{
		if ( empty( $this->_class_name ) ) {
			$class = new ReflectionClass( get_class( $this ) );
			$this->_class_name = $class->getFileName();
		}
		return $this->_class_name;
	}

	/**
	 * Gets a database schema associated with this pluggable
	 * @return string The database schema
	 */
	final public function get_db_schema()
	{
		$db = DB::get_driver_name();
		$schema = dirname( $this->get_file() ) . '/schema/' . $db . '.sql';
		return file_get_contents( $schema );
	}

	/**
	 * Get a fully-qualified URL directory that contains this pluggable class
	 *
	 * @param bool whether to include a trailing slash.  Default: No
	 * @return string URL
	 */
	public function get_url( $trail = false )
	{
		return URL::get_from_filesystem( $this->get_file(), $trail );
	}

	/**
	 * Returns a unique id for this pluggable
	 * @return string A plugin id
	 */
	final public function plugin_id()
	{
		static $id;
		if ( !isset( $id ) ) {
			$id = Plugins::id_from_file( str_replace( '\\', '/', $this->get_file() ) );
		}
		return $id;
	}

	/**
	 * Load a translation domain/file for this pluggable
	 * @return boolean true if data was successfully loaded, false otherwise
	 */
	public function load_text_domain( $domain )
	{
		$base_dir = realpath( dirname( $this->get_file() ) );

		return HabariLocale::load_pluggable_domain( $domain, $base_dir );
	}

	/**
	 * Registers all of this class' action_ and filter_ functions with the Plugins dispatcher
	 * Registers xmlrpc_ functions with the Plugins dispatcher, and turns '__' into '.'
	 *   for the purposes of matching dotted XMLRPC requests.
	 * If the class is an instance of Pluggable, registers the hooks with a plugin id also.
	 * @param mixed $object The object or class name to register the hooks of
	 **/
	public static function load_hooks($object)
	{
		static $registered = array();
		if(is_object($object)) {
			$hash = spl_object_hash($object);
			if(isset($registered[$hash])) {
				return;
			}
			else {
				$registered[$hash] = true;
			}
		}
		else {
			$registered[$object] = true;
		}

		// combine the array so we can have hooks => function
		$methods = get_class_methods( $object );
		$methods = array_combine( $methods, $methods );
		// get the specific priority values for functions, as needed
		if ( method_exists( $object, 'set_priorities' ) ) {
			$priorities = $object->set_priorities();
		}
		// get the aliases.
		if ( method_exists( $object, 'alias' ) ) {
			$methods = array_merge_recursive( $methods, $object->alias() );
		}
		// loop over all the methods in this class
		foreach ( $methods as $fn => $hooks ) {
			// loop hooks and register callback for each
			foreach ( (array) $hooks as $hook ) {
				// make sure the method name is of the form
				// action_foo or filter_foo of xmlrpc_foo or theme_foo
				if ( preg_match( '#^(action|filter|xmlrpc|theme)_#i', $hook ) ) {
					$priority = 8;
					if(isset($priorities[$hook])) {
						$priority = $priorities[$hook];
					}
					elseif(preg_match('#^(.+)_(\d+)$#', $hook, $priority_match)) {
						$hook = $priority_match[1];
						$priority = intval($priority_match[2]);
					}
					elseif(isset( $priorities[$fn])) {
						$priority = $priorities[$fn];
					}
					list( $type, $hook ) = explode( '_', $hook, 2 );
					if ( $type === 'xmlrpc' ) {
						$hook = str_replace( '__', '.', $hook );
					}
					Plugins::register( array( $object, $fn ), $type, $hook, $priority );
					if($object instanceof Pluggable) {
						Plugins::register( array( $object, $fn ), $type, $hook . ':' . $object->plugin_id(), $priority );
					}
				}
			}
		}
	}

	/**
	 * Called when a pluggable is loaded to register its actions and filters.
	 */
	public function load()
	{
		self::load_hooks($this);
		// look for help with this
		if ( method_exists( $this, 'help' ) ) {
			Plugins::register( array( $this, '_help_plugin_config' ), 'filter', 'plugin_config:' . $this->plugin_id(), 8 );
			Plugins::register( array( $this, '_help_plugin_ui' ), 'action', 'plugin_ui:' . $this->plugin_id(), 8 );
		}
		// look for a basic configure method
		if ( method_exists( $this, 'configure' ) ) {
			Plugins::register( array( $this, '_configure_plugin_config' ), 'filter', 'plugin_config:' . $this->plugin_id(), 8 );
			Plugins::register( array( $this, '_configure_plugin_ui' ), 'action', 'plugin_ui:' . $this->plugin_id(), 8 );
		}
	}

	/**
	 * Registered to the plugin_config hook to supply help via a plugin's help() method
	 *
	 * @param array $actions An array of actions applicable to this plugin
	 * @param string $plugin_id The plugin id to which the actions belong
	 * @return array The modified array of actions
	 */
	public function _help_plugin_config( $actions, $plugin_id )
	{
		if ( $plugin_id == $this->plugin_id() ) {
			$actions['_help'] = _t( '?' );
		}
		return $actions;
	}

	/**
	 * Registered to the plugin_ui hook to supply help via a plugin's help() method
	 *
	 * @param string $plugin_id The id of the plugin whose action was triggered
	 * @param string $action The action triggered
	 */
	public function _help_plugin_ui( $plugin_id, $action )
	{
		if ( $plugin_id == $this->plugin_id() && $action == '_help' ) {
			$output = $this->help();
			if ( $output instanceof FormUI ) {
				$output->out();
			}
			else {
				echo "
{$output}
";
			}
		}
	}

	/**
	 * Registered to the plugin_config hook to supply a config via a plugin's configure() method
	 *
	 * @param array $actions An array of actions applicable to this plugin
	 * @param string $plugin_id The plugin id to which the actions belong
	 * @return array The modified array of actions
	 */
	public function _configure_plugin_config( $actions, $plugin_id )
	{
		if ( $plugin_id == $this->plugin_id() ) {
			$actions['_configure'] = _t( 'Configure' );
		}
		return $actions;
	}

	/**
	 * Registered to the plugin_ui hook to supply a config via a plugin's configure() method
	 *
	 * @param string $plugin_id The id of the plugin whose action was triggered
	 * @param string $action The action triggered
	 */
	public function _configure_plugin_ui( $plugin_id, $action )
	{
		if ( $plugin_id == $this->plugin_id() && $action == '_configure' ) {
			$output = $this->configure();
			if ( $output instanceof FormUI ) {
				$output->out();
			}
			else {
				echo $output;
			}
		}
	}

	/**
	 * Add a rewrite rule that dispatches entirely to a plugin hook
	 *
	 * @param mixed $rule An old-style rewrite rule string, where quoted segments are literals and unquoted segments are variable names, OR a RewriteRule object
	 * @param string $hook The suffix of the hook function: action_plugin_act_{$suffix}
	 * #param Callback $fn A potential function/method to register directly to the newly created hook
	 */
	public function add_rule( $rule, $hook, $fn = null )
	{
		if ( count( $this->_new_rules ) == 0 ) {
			Plugins::register( array( $this, '_filter_rewrite_rules' ), 'filter', 'rewrite_rules', 7 );
		}
		if ( $rule instanceof RewriteRule ) {
			$this->_new_rules[] = $rule;
		}
		else {
			$this->_new_rules[] = RewriteRule::create_url_rule( $rule, 'PluginHandler', $hook );
		}
		if(!is_null($fn)) {
			Plugins::register($fn, 'theme', 'route_' . $hook);
		}
	}

	/**
	 * Add the rewrite rules queued by add_rule() to the full rule set
	 *
	 * @param array $rules The array of current RewriteRules
	 * @return array The appended array of RewriteRules
	 */
	public function _filter_rewrite_rules( $rules )
	{
		$rules = array_merge( $rules, $this->_new_rules );
		return $rules;
	}

	/**
	 * Adds a template to the default theme that is stored in a specified path.
	 * Use this function as a shortcut to make available additional templates to a theme
	 * from within the plugin directory.
	 *
	 * @param string $name The name of the template that will be displayed, sans extension
	 * @param string $filename The full path of the template file used for the specified name
	 * @param boolean $override If false, allow a template with the same name in the active theme directory to override this one.
	 * If true, always override the active theme's template with this one.
	 */
	protected function add_template( $name, $filename, $override = false )
	{
		if ( count( $this->_added_templates ) == 0 ) {
			Plugins::register( array( &$this, '_plugin_available_templates' ), 'filter', 'available_templates' );
			Plugins::register( array( &$this, '_plugin_include_template_file' ), 'filter', 'include_template_file' );
		}

		$this->_added_templates[$name] = array( $filename, $override );
	}

	/**
	 * Add plugin templates to the list of templates that are present in the current theme
	 *
	 * @param array $list List of template names in the current theme
	 * @return array The modified list of template names
	 */
	public function _plugin_available_templates( $list )
	{
		$list = array_merge( $list, array_keys( $this->_added_templates ) );
		return $list;
	}

	/**
	 * Potentially serve a different file for the requested template name
	 *
	 * @param string $file The filename of the template the theme will display
	 * @param string $name The name of the template requested
	 * @return string The potentially modified filename to use for the requested template.
	 */
	public function _plugin_include_template_file( $file, $name )
	{
		if ( isset( $this->_added_templates[$name] ) ) {
			if ( $this->_added_templates[$name][1] || !file_exists( $file ) ) {
				$file = $this->_added_templates[$name][0];
			}
		}
		return $file;
	}

	/**
	 * Provide a method to return the version number from a pluggable's info
	 * @return string The version of the pluggable
	 **/
	public abstract function get_version();

	/**
	 * Execute the upgrade action on any pluggable that has a version number change
	 * Update the version number of the pluggable in the database to what is installed
	 */
	public function upgrade()
	{
		if(DB::is_connected() && @ Options::get( 'installed' )) {
			$pluggable_class = get_class($this);
			$versions = Options::get( 'pluggable_versions' );
			if(isset($versions[$pluggable_class])) {
				$old_version = $versions[$pluggable_class];
				if($old_version != $this->get_version()) {
					Plugins::act_id('upgrade', $this->plugin_id(), $old_version);
					$versions[$pluggable_class] = $this->get_version();
					Options::set( 'pluggable_versions', $versions );
				}
			}
			else {
				$versions[$pluggable_class] = $this->get_version();
				Options::set( 'pluggable_versions', $versions );
			}
		}
	}
}

?>

repo_name: DaSourcerer/habari


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

/**
 * Habari Plugin Class
 *
 * Template plugin object which specific plugin objects should extend
 * This object provides the basic constructor used to ensure that
 * plugin actions are registered against the appropriate dispatcher
 *
 */
abstract class Plugin extends Pluggable
{

	/**
	 * Loads a theme's metadata from an XML file in theme's
	 * directory.
	 *
	 */
	final public function info()
	{
		static $info;
		if ( !isset( $info ) ) {
			$info = Plugins::load_info( $this->get_file() );
			if ( isset( $info->help ) ) {
				Plugins::register( array( $this, '_help_plugin_config_plugin' ), 'filter', 'plugin_config:' . $this->plugin_id(), 8 );
				Plugins::register( array( $this, '_help_plugin_ui_plugin' ), 'action', 'plugin_ui:' . $this->plugin_id(), 8 );
			}
		}
		return $info;
	}

	/**
	 * Plugin constructor.
	 * Plugins should not define their own constructors, because they are instantiated
	 * to extract plugin info.  Instead, include a sink for a "init" hook
	 * which is executed immediately after the plugin is loaded during normal execution.
	 **/
	final public function __construct()
	{
		parent::__construct();
	}

	/**
	 * Registered to the plugin_config hook to supply help via a plugin's help in xml
	 *
	 * @param array $actions An array of actions applicable to this plugin
	 * @param string $plugin_id The plugin id to which the actions belong
	 * @return array The modified array of actions
	 */
	public function _help_plugin_config_plugin( $actions, $plugin_id )
	{
		if ( $plugin_id == $this->plugin_id() ) {
			foreach ( $this->info->help as $help ) {
				$name = (string)$help['name'];
				if ( $name == '' ) {
					$name = '_help';
				}
				$actions[$name] = _t( '?' );
			}
		}
		return $actions;
	}

	/**
	 * Registered to the plugin_ui hook to supply help via a plugin's help in xml
	 *
	 * @param string $plugin_id The id of the plugin whose action was triggered
	 * @param string $action The action triggered
	 */
	public function _help_plugin_ui_plugin( $plugin_id, $action )
	{
		if ( $plugin_id == $this->plugin_id() ) {
			foreach ( $this->info->help as $help ) {
				if ( ( $action == (string)$help['name'] && (string)$help['name'] != '' ) || ( $action == '_help' && (string)$help['name'] == '' ) ) {
					echo '
' . ( (string)$help->value ) . '
';
				}
			}
		}
	}

	/**
	 * Provide a method to return the version number from the plugin xml
	 * @return string The plugin version from XML
	 **/
	public function get_version()
	{
		return (string)$this->info->version;
	}
}

?>


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

/**
 * Habari Plugins Class
 *
 * Provides an interface for the code to access plugins
 */
class Plugins
{
	private static $hooks = array();
	private static $plugins = array();
	private static $plugin_files = array();
	private static $plugin_classes = array();

	/**
	 * function __construct
	 * A private constructor method to prevent this class from being instantiated.
	 * Don't ever create this class as an object for any reason.  It is not a singleton.
	 */
	private function __construct()
	{
	}

	/**
	 * Autoload function to load plugin file from classname
	 */
	public static function _autoload( $class )
	{
		if ( isset( self::$plugin_files[$class] ) ) {
			require( self::$plugin_files[$class] );
			if( !class_exists($class, false)) {
				// The classname of a plugin changed.
				$filename = self::$plugin_files[$class];
				EventLog::log(_t('Plugin file "%s" has changed its plugin class name.', array($filename)));
				Session::error(_t('Plugin file "%s" has changed its plugin class name.', array($filename)));

				// Remove the plugin from the active list
				$active_plugins = Options::get('active_plugins');
				unset($active_plugins[$class]);
				self::$plugin_files = array();
				Options::set('active_plugins', $active_plugins);
				self::list_active(true); // Refresh the internal list

				// Reactivate it to try to get the new class loaded
				self::activate_plugin($filename);
				Utils::redirect(null, false);
				exit();
			}
		}
	}

	/**
	 * function register
	 * Registers a plugin action for possible execution
	 * @param mixed A reference to the function to register by string or array(object, string)
	 * @param string Usually either 'filter' or 'action' depending on the hook type.
	 * @param string The plugin hook to register
	 * @param hex An optional execution priority, in hex.  The lower the priority, the earlier the function will execute in the chain.  Default value = 8.
	 */
	public static function register( $fn, $type, $hook, $priority = 8 )
	{
		// add the plugin function to the appropriate array
		$index = array( $type, $hook, $priority );

		$ref =& self::$hooks;

		foreach ( $index as $bit ) {
			if ( !isset( $ref["{$bit}"] ) ) {
				$ref["{$bit}"] = array();
			}
			$ref =& $ref["{$bit}"];
		}

		$ref[] = $fn;
		ksort( self::$hooks[$type][$hook] );
	}

	/**
	 * Call to execute a plugin action
	 * @param string The name of the action to execute
	 * @param mixed Optional arguments needed for action
	 */
	public static function act()
	{
		$args = func_get_args();
		$hookname = array_shift( $args );
		if ( ! isset( self::$hooks['action'][$hookname] ) ) {
			return false;
		}
		foreach ( self::$hooks['action'][$hookname] as $priority ) {
			foreach ( $priority as $action ) {
				// $action is an array of object reference
				// and method name
				call_user_func_array( $action, $args );
			}
		}
	}

	/**
	 * Call to execute a plugin action, by id
	 * @param string The name of the action to execute
	 * @param mixed Optional arguments needed for action
	 */
	public static function act_id()
	{
		$args = func_get_args();
		list( $hookname, $id ) = $args;
		$args = array_slice( func_get_args(), 2 );
		$hookname = $hookname . ':' . $id;
		if ( ! isset( self::$hooks['action'][$hookname] ) ) {
			return false;
		}
		foreach ( self::$hooks['action'][$hookname] as $priority ) {
			foreach ( $priority as $action ) {
				// $action is an array of object reference
				// and method name
				call_user_func_array( $action, $args );
			}
		}
	}

	/**
	 * Call to execute a plugin filter
	 * @param string The name of the filter to execute
	 * @param mixed The value to filter.
	 */
	public static function filter()
	{
		list( $hookname, $return ) = func_get_args();
		if ( ! isset( self::$hooks['filter'][$hookname] ) ) {
			return $return;
		}

		$filterargs = array_slice( func_get_args(), 2 );
		foreach ( self::$hooks['filter'][$hookname] as $priority ) {
			foreach ( $priority as $filter ) {
				// $filter is an array of object reference and method name
				$callargs = $filterargs;
				array_unshift( $callargs, $return );
				$return = call_user_func_array( $filter, $callargs );
			}
		}
		return $return;
	}

	/**
	 * Call to execute a plugin filter on a specific plugin, by id
	 * @param string The name of the filter to execute
	 * @param string The id of the only plugin on which to execute
	 * @param mixed The value to filter.
	 */
	public static function filter_id()
	{
		list( $hookname, $id, $return ) = func_get_args();
		$hookname = $hookname . ':' . $id;
		if ( ! isset( self::$hooks['filter'][$hookname] ) ) {
			return $return;
		}

		$filterargs = array_slice( func_get_args(), 3 );
		foreach ( self::$hooks['filter'][$hookname] as $priority ) {
			foreach ( $priority as $filter ) {
				// $filter is an array of object reference and method name
				$callargs = $filterargs;
				array_unshift( $callargs, $return );
				$return = call_user_func_array( $filter, $callargs );
			}
		}
		return $return;
	}

	/**
	 * Call to execute an XMLRPC function
	 * @param string The name of the filter to execute
	 * @param mixed The value to filter.
	 */
	public static function xmlrpc()
	{
		list( $hookname, $return ) = func_get_args();
		if ( ! isset( self::$hooks['xmlrpc'][$hookname] ) ) {
			return false;
		}
		$filterargs = array_slice( func_get_args(), 2 );
		foreach ( self::$hooks['xmlrpc'][$hookname] as $priority ) {
			foreach ( $priority as $filter ) {
				// $filter is an array of object reference and method name
				return call_user_func_array( $filter, $filterargs );
			}
		}
		return false;
	}

	/**
	 * Call to execute a theme function
	 * @param string The name of the filter to execute
	 * @param mixed The value to filter
	 * @return The filtered value
	 */
	public static function theme()
	{
		$filter_args = func_get_args();
		$hookname = array_shift( $filter_args );

		$filtersets = array();
		if ( !isset( self::$hooks['theme'][$hookname] ) ) {
			if ( substr( $hookname, -6 ) != '_empty' ) {
				array_unshift( $filter_args, $hookname . '_empty' );
				return call_user_func_array( array( 'Plugins', 'theme' ), $filter_args );
			}
			return array();
		}

		$return = array();
		foreach ( self::$hooks['theme'][$hookname] as $priority ) {
			foreach ( $priority as $filter ) {
				// $filter is an array of object reference and method name
				$callargs = $filter_args;
				if ( is_array( $filter ) ) {
					if ( is_string( $filter[0] ) ) {
						$module = $filter[0];
					}
					else {
						$module = get_class( $filter[0] );
						if ( $filter[0] instanceof Theme && $module != get_class( $callargs[0] ) ) {
							continue;
						}
					}
				}
				else {
					$module = $filter;
				}
				$return[$module] = call_user_func_array( $filter, $callargs );
			}
		}
		if ( count( $return ) == 0 && substr( $hookname, -6 ) != '_empty' ) {
			array_unshift( $filter_args, $hookname . '_empty' );
			$result = call_user_func_array( array( 'Plugins', 'theme' ), $filter_args );
		}
		array_unshift( $filter_args, 'theme_call_' . $hookname, $return );
		$result = call_user_func_array( array( 'Plugins', 'filter' ), $filter_args );
		return $result;
	}

	/**
	 * Determine if any plugin implements the indicated theme hook
	 *
	 * @param string $hookname The name of the hook to check for
	 * @return boolean True if the hook is implemented
	 */
	public static function theme_implemented( $hookname )
	{
		return isset( self::$hooks['theme'][$hookname] );
	}

	/**
	 * Determine if a hook of any type is implemented
	 * @static
	 * @param string $hookname The name of the hook to check for
	 * @param string $searchtype Optional. The type of hook to check for
	 * @return array|bool An array with the types of hook implemented, or false if not implemented
	 */
	public static function implemented( $hookname, $searchtype = null )
	{
		$result = array();
		foreach(self::$hooks as $type => $hooks) {
			if($searchtype && $searchtype != $type) continue;
			if(isset($hooks[$hookname])) {
				$result[$type] = $type;
			}
		}
		return count($result) > 0 ? $result : false;
	}

	/**
	 * function list_active
	 * Gets a list of active plugin filenames to be included
	 * @param boolean Whether to refresh the cached array.  Default false
	 * @return array An array of filenames
	 */
	public static function list_active( $refresh = false )
	{
		if ( empty( self::$plugin_files ) || $refresh ) {
			$plugins = Options::get( 'active_plugins' );
			if ( is_array( $plugins ) ) {
				foreach ( $plugins as $class => $filename ) {
					// add base path to stored path
					if(!preg_match('#^([^:]+://)#i', $filename, $matches)) {
						$filename = HABARI_PATH . $filename;
					}

					// if class is somehow empty we'll throw an error when trying to load it - deactivate the plugin instead
					if ( $class == '' ) {
						self::deactivate_plugin( $filename, true );
						EventLog::log( _t( 'An empty plugin definition pointing to file "%1$s" was removed.', array( $filename ) ), 'err', 'plugin', 'habari' );
						// and skip adding it to the active stack
						continue;
					}

					if ( file_exists( $filename ) ) {
						self::$plugin_files[$class] = $filename;
					}
					else {
						// file does not exist, deactivate plugin
						self::deactivate_plugin( $filename, true );
						EventLog::log( _t( 'Plugin "%1$s" deactivated because it could no longer be found.', array( $class ) ), 'err', 'plugin', 'habari', $filename );
					}
				}
			}
			// make sure things work on Windows
			self::$plugin_files = array_map( function($s) {return str_replace('\\', '/', $s);}, self::$plugin_files );
		}
		return self::$plugin_files;
	}

	/**
	 * Returns the internally stored references to all loaded plugins
	 * @return array An array of plugin objects
	 */
	public static function get_active()
	{
		return self::$plugins;
	}

	/**
	* Get references to plugin objects that implement a specific interface
	* @param string $interface The interface to check for
	* @return array An array of matching plugins
	*/
	public static function get_by_interface( $interface )
	{
		return array_filter( self::$plugins, function( $plugin_obj ) use ($interface) { return $plugin_obj instanceof $interface; } );
	}

	/**
	 * function list_all
	 * Gets a list of all plugin filenames that are available
	 * @return array An array of filenames
	 */
	public static function list_all()
	{
		$plugins = array();
		$plugindirs = array( HABARI_PATH . '/system/plugins/', HABARI_PATH . '/3rdparty/plugins/', HABARI_PATH . '/user/plugins/' );
		if ( Site::CONFIG_LOCAL != Site::$config_type ) {
			// include site-specific plugins
			$plugindirs[] = Site::get_dir( 'config' ) . '/plugins/';
		}
		$dirs = array();
		foreach ( $plugindirs as $plugindir ) {
			if ( file_exists( $plugindir ) ) {
				$dirs = array_merge( $dirs, Utils::glob( $plugindir . '*', GLOB_ONLYDIR | GLOB_MARK ) );
			}
		}
		foreach ( $dirs as $dir ) {
			$dirfiles = Utils::glob( $dir . '*.plugin.php' );
			if ( ! empty( $dirfiles ) ) {
				$dirfiles = array_combine(
					// Use the basename of the file as the index to use the named plugin from the last directory in $dirs
					array_map( 'basename', $dirfiles ),
					// massage the filenames so that this works on Windows
					array_map( function($s) {return str_replace('\\', '/', $s);}, $dirfiles )
				);
				$plugins = array_merge( $plugins, $dirfiles );
			}
			$dirfiles = Utils::glob( $dir . '*.phar' );
			foreach($dirfiles as $filename) {
				if(preg_match('/\.phar$/i', $filename)) {
					$d = new DirectoryIterator('phar://' . $filename);
					foreach ($d as $fileinfo) {
						if ($fileinfo->isFile() && preg_match('/\.plugin.php$/i', $fileinfo->getFilename())) {
							$plugins[$fileinfo->getFilename()] = str_replace('\\', '/', $fileinfo->getPathname());
						}
					}

				}
			}
		}
		ksort( $plugins );
		return $plugins;
	}

	/**
	 * Get classes that extend Plugin.
	 * @param $class string A class name
	 * @return boolean true if the class extends Plugin
	 */
	public static function extends_plugin( $class )
	{
		$parents = class_parents( $class, false );
		return in_array( 'Plugin', $parents );
	}

	/**
	 * function class_from_filename
	 * returns the class name from a plugin's filename
	 * @param string $file the full path to a plugin file
	 * @param bool $check_realpath whether or not to try realpath resolution
	 * @return string the class name
	 */
	public static function class_from_filename( $file, $check_realpath = false )
	{
		if ( $check_realpath ) {
			$file = realpath( $file );
		}
		foreach ( self::get_plugin_classes() as $plugin ) {
			$class = new ReflectionClass( $plugin );
			$classfile = str_replace( '\\', '/', $class->getFileName() );
			if ( $classfile == $file ) {
				return $plugin;
			}
		}
		// if we haven't found the plugin class, try again with realpath resolution:
		if ( $check_realpath ) {
			// really can't find it
			return false;
		}
		else {
			return self::class_from_filename( $file, true );
		}
	}

	public static function get_plugin_classes()
	{
		$classes = get_declared_classes();
		return array_filter( $classes, array( 'Plugins', 'extends_plugin' ) );
	}

	/**
	 * Initialize all loaded plugins by calling their load() method
	 * @param string $file the class name to load
	 * @param boolean $activate True if the plugin's load() method should be called
	 * @return Plugin The instantiated plugin class
	 */
	public static function load_from_file( $file, $activate = true )
	{
		$class = self::class_from_filename( $file );
		return self::load( $class, $activate );
	}

	/**
	 * Return the info XML for a plugin based on a filename
	 *
	 * @param string $file The filename of the plugin file
	 * @return SimpleXMLElement The info structure for the plugin, or null if no info could be loaded
	 */
	public static function load_info( $file )
	{
		$info = null;
		$xml_file = preg_replace( '%\.plugin\.php$%i', '.plugin.xml', $file );

		if ( file_exists( $xml_file ) && $xml_content = file_get_contents( $xml_file ) ) {

			// tell libxml to throw exceptions and let us check for errors
			$old_error = libxml_use_internal_errors( true );

			try {
				$info = new SimpleXMLElement( $xml_content );

				// if the xml file uses a theme element name instead of pluggable, it's old
				if ( $info->getName() != 'pluggable' ) {
					$info = 'legacy';
				}

				// Translate the plugin description
				HabariLocale::translate_xml( $info, $info->description );

				// Translate the plugin help
				foreach( $info->help as $help ) {
					HabariLocale::translate_xml( $help, $help->value );
				}

			}
			catch ( Exception $e ) {

				EventLog::log( _t( 'Invalid plugin XML file: %1$s', array( $xml_file ) ), 'err', 'plugin' );
				$info = 'broken';

			}

			// restore the old error level
			libxml_use_internal_errors( $old_error );

		}

		return $info;
	}

	/**
	 * Load a pluign into memory by class name
	 *
	 * @param string $class The name of the class of the plugin to load
	 * @param boolean $activate True to run the load routine of the plugin and add it to the loaded plugins list
	 * @return Plugin The instance of the created plugin
	 */
	public static function load( $class, $activate = true )
	{
		$plugin = new $class;
		if(!$plugin instanceof Plugin) {
			EventLog::log(_t('The class "%s" is not a Plugin, but was queued to load as a plugin.', array($class)));
			Session::error(_t('The class "%s" is not a Plugin, but was queued to load as a plugin. It may not currently be active.', array($class)));
			self::deactivate_plugin(self::$plugin_files[$class], true);
			return false;
		}
		if ( $activate ) {
			self::$plugins[$plugin->plugin_id] = $plugin;
			$plugin->load();
			$plugin->upgrade();
		}
		return $plugin;
	}

	/**
	 * Upgrade all loaded plugins
	 */
	public static function upgrade( )
	{
		foreach(self::$plugins as $plugin) {
			$plugin->upgrade();
		}
	}

	/**
	 * Instantiate and load all active plugins
	 */
	public static function load_active()
	{
		foreach ( self::list_active() as $class => $filename ) {
			if ( file_exists( $filename ) ) {
				self::load( $class );
			}
		}
	}

	/**
	 * Returns a plugin id for the filename specified.
	 * Used to unify the way plugin ids are generated, rather than spreading the
	 * calls internal to this function over several files.
	 *
	 * @param string $file The filename to generate an id for
	 * @return string A plugin id.
	 */
	public static function id_from_file( $file )
	{
		$file = str_replace( array( '\\', '/' ), PATH_SEPARATOR, realpath( $file ) );
		return sprintf( '%x', crc32( $file ) );
	}

	/**
	 * Activates a plugin file
	 */
	public static function activate_plugin( $file )
	{
		$ok = true;
		// Keep stream handler and strip base path from stored path
		$stream = '';
		if(preg_match('#^([^:]+://)#i', $file, $matches)) {
			$stream = $matches[1];
			$short_file = $file; //$stream . MultiByte::substr( preg_replace('#^([^:]+://)#i', '', $file), strlen( HABARI_PATH ) );
		}
		else {
			$short_file = MultiByte::substr( $file, strlen( HABARI_PATH ) );
		}
//Utils::debug($stream, $file);die();
		$activated = Options::get( 'active_plugins' );
		if ( !is_array( $activated ) || !in_array( $short_file, $activated ) ) {
			include_once( $file );
			$class = Plugins::class_from_filename( $file );
			$plugin = Plugins::load( $class );
			$ok = Plugins::filter( 'activate_plugin', $ok, $file ); // Allow plugins to reject activation
		}
		else if ( is_array( $activated) && in_array( $short_file, $activated ) ) {
			$ok = false;
		}
		if ( $ok ) {
			$activated[$class] = $short_file;
			Options::set( 'active_plugins', $activated );
			$versions = Options::get( 'pluggable_versions' );
			if(!isset($versions[$class])) {
				$versions[$class] = $plugin->get_version();
				Options::set( 'pluggable_versions', $versions );
			}

			if ( method_exists( $plugin, 'action_plugin_activation' ) ) {
				$plugin->action_plugin_activation( $file ); // For the plugin to install itself
			}
			Plugins::act( 'plugin_activated', $file ); // For other plugins to react to a plugin install
			EventLog::log( _t( 'Activated Plugin: %s', array( $plugin->info->name ) ), 'notice', 'plugin', 'habari' );
		}
		return $ok;
	}

	/**
	 * Deactivates a plugin file
	 * @param string $file the Filename of the plugin to deactivate
	 * @param boolean $force If true, deactivate this plugin regardless of what filters may say about it.
	 */
	public static function deactivate_plugin( $file, $force = false )
	{
		$ok = true;
		$name = '';
		$ok = Plugins::filter( 'deactivate_plugin', $ok, $file );  // Allow plugins to reject deactivation
		if ( $ok || $force == true ) {
			// normalize directory separator
			$file = str_replace( '\\', '/', $file );
			// strip base path from stored path
			$short_file = MultiByte::substr( $file, MultiByte::strlen( HABARI_PATH ) );

			$activated = Options::get( 'active_plugins' );
			$index = array_search( $short_file, $activated );
			if ( is_array( $activated ) && ( false !== $index ) ) {

				if ( $force != true ) {
					// Get plugin name for logging
					$name = self::$plugins[Plugins::id_from_file( $file )]->info->name;
					if ( method_exists( self::$plugins[Plugins::id_from_file( $file )], 'action_plugin_deactivation' ) ) {
						self::$plugins[Plugins::id_from_file( $file )]->action_plugin_deactivation( $file ); // For the plugin to uninstall itself
					}
				}

				unset( $activated[$index] );
				Options::set( 'active_plugins', $activated );

				if ( $force != true ) {
					Plugins::act( 'plugin_deactivated', $file );  // For other plugins to react to a plugin uninstallation
					EventLog::log( _t( 'Deactivated Plugin: %s', array( $name ) ), 'notice', 'plugin', 'habari' );
				}
			}
		}

		if ( $force == true ) {
			// always return true for forced deactivations
			return true;
		}
		else {
			return $ok;
		}
	}

	/**
	 * Detects whether the plugins that exist have changed since they were last
	 * activated.
	 * @return boolean true if the plugins have changed, false if not.
	 */
	public static function changed_since_last_activation()
	{
		$old_plugins = Options::get( 'plugins_present' );
		//self::set_present();
		// If the plugin list was never stored, then they've changed.
		if ( !is_array( $old_plugins ) ) {
			return true;
		}
		// add base path onto stored path
		foreach ( $old_plugins as $old_plugin ) {
			$old_plugin = HABARI_PATH . $old_plugin;
		}
		// If the file list is not identical, then they've changed.
		$new_plugin_files = Plugins::list_all();
		$old_plugin_files = Utils::array_map_field($old_plugins, 'file');
		if ( count( array_intersect( $new_plugin_files, $old_plugin_files ) ) != count( $new_plugin_files ) ) {
			return true;
		}
		// If the files are not identical, then they've changed.
		$old_plugin_checksums = Utils::array_map_field($old_plugins, 'checksum');
		$new_plugin_checksums = array_map( 'md5_file', $new_plugin_files );
		if ( count( array_intersect( $old_plugin_checksums, $new_plugin_checksums ) ) != count( $new_plugin_checksums ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Stores the list of plugins that are present (not necessarily active) in
	 * the Options table for future comparison.
	 */
	public static function set_present()
	{
		$plugin_files = Plugins::list_all();
		// strip base path
		foreach ( $plugin_files as $plugin_file ) {
			$plugin_file = MultiByte::substr( $plugin_file, MultiByte::strlen( HABARI_PATH ) );
		}

		$plugin_data = array_map( function($a) {return array( 'file' => $a, 'checksum' => md5_file( $a ) ); }, $plugin_files );
		Options::set( 'plugins_present', $plugin_data );
	}

	/**
	 * Verify if a plugin is loaded.
	 * You may supply an optional argument $version as a minimum version requirement.
	 *
	 * @param string $name Name or class name of the plugin to find.
	 * @param string $version Optional minimal version of the plugin.
	 * @return bool Returns true if name is found and version is equal or higher than required.
	 */
	public static function is_loaded( $name, $version = null )
	{
		foreach ( self::$plugins as $plugin ) {
			if ( is_null( $plugin->info ) || $plugin->info == 'broken' || $plugin->info == 'invalid' ) {
				continue;
			}
			if ( MultiByte::strtolower( $plugin->info->name ) == MultiByte::strtolower( $name ) || $plugin instanceof $name || ( isset( $plugin->info->guid ) && MultiByte::strtolower( $plugin->info->guid ) == MultiByte::strtolower( $name ) ) ) {
				if ( isset( $version ) ) {
					if ( isset( $plugin->info->version ) ) {
						return version_compare( $plugin->info->version, $version, '>=' );
					}
					else {
						return $version == null;
					}
				}
				else {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Check the PHP syntax of every plugin available, activated or not.
	 *
	 * @see Utils::php_check_file_syntax()
	 * @return bool Returns true if all plugins were valid, return false if a plugin (or more) failed.
	 */
	public static function check_every_plugin_syntax()
	{
		$failed_plugins = array();
		$all_plugins = self::list_all();

		foreach ( $all_plugins as $file ) {
			$error = '';
			if ( !Utils::php_check_file_syntax( $file, $error ) ) {
				Session::error( _t( 'Attempted to load the plugin file "%s", but it failed with syntax errors. <div class="reveal">%s</div>', array( basename( $file ), $error ) ) );
				$failed_plugins[] = $file;
			}
		}

		Options::set( 'failed_plugins', $failed_plugins );
		Plugins::set_present();

		return ( count( $failed_plugins ) > 0 ) ? false : true;
	}

	/**
	 * Produce the UI for a plugin based on the user's selected config option
	 *
	 * @param string $configure The id of the configured plugin
	 * @param string $configuration The selected configuration option
	 **/
	public static function plugin_ui( $configure, $configaction )
	{
		Plugins::act_id( 'plugin_ui_' . $configaction, $configure, $configure, $configaction );
		Plugins::act( 'plugin_ui_any_' . $configaction, $configure, $configaction );
		Plugins::act_id( 'plugin_ui', $configure, $configure, $configaction );
		Plugins::act( 'plugin_ui_any', $configure, $configaction );
	}

	public static function provided($exclude = null)
	{
		$active_plugins = Plugins::get_active();

		$provided = array();
		foreach($active_plugins as $plugin_id => $plugin) {
			if($plugin->info->name == $exclude || $plugin_id == $exclude) {
				continue;
			}
			if(isset($plugin->info->provides)) {
				foreach($plugin->info->provides->feature as $provide) {
					$provided[(string)$provide][] = (string)$plugin->info->name;
				}
			}
		}

		return $provided;
	}
}

?>