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'); //or
	 * 	 list($foo, $bar, $baz) = Options::get('foo1', 'bar2', 'baz3'); //or
	 * 	 extract(Options::get('foo', 'bar', 'baz')); //or
	 *   list($foo, $bar, $baz) = Options::get(array('foo1', 'bar2', 'baz3')); // useful with array_keys()
	 *
	 *
	 * @param string|array $name... The name(s) of the option(s) to retrieve
	 * @return mixed The option requested or an array of requested options, null if it does not exist
	 **/
	public static function get( $name )
	{
		if ( func_num_args() > 1 ) {
			$name = func_get_args();
		}
		if ( is_array( $name ) ) {
			return array_intersect_key( self::instance()->options, array_flip( $name ) );
		}
		return self::instance()->$name;
	}

	/**
	 * 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 )
	{
		$results = array();
		if( substr( $prefix, -2 ) != '__' ) {
			$prefix .= '__';
		}
		foreach( self::instance()->options as $key => $value ) {
			if( strpos( $key, $prefix ) === 0 ) {
				$results[substr( $key, strlen( $prefix ) )] = $value;
			}
		}
		return $results;
	}

	/**
	 * 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');
	 *
	 * @param string $name Name of the option to set
	 * @param mixed $value New value of the option to store
	 **/
	public static function set( $name, $value = '' )
	{
		self::instance()->$name = $value;
	}

	/**
	 * Shortcut to unset an option in the options table
	 *
	 * @param string $name The name of the option
	 */
	public static function delete( $name )
	{
		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,
		);
		$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;
			}
		}
	}

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

}

?>


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()
	{
		return Plugins::id_from_file( str_replace('\\', '/', $this->get_file() ) );
	}

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

	/**
	 * Called when a pluggable is loaded to register its actions and filters.
	 * Registers all of this pluggables 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.
	 */
	public function load()
	{
		// combine the array so we can have hooks => function
		$methods = get_class_methods($this);
		$methods = array_combine( $methods, $methods );
		// get the specific priority values for functions, as needed
		if ( method_exists( $this, 'set_priorities' ) ) {
			$priorities = $this->set_priorities();
		}
		// get the aliases.
		if ( method_exists( $this, 'alias' ) ) {
			$methods = array_merge_recursive( $methods, $this->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 = isset($priorities[$hook]) ? $priorities[$hook] :
						( isset($priorities[$fn]) ? $priorities[$fn] : 8 );
					list($type, $hook) = explode( '_', $hook, 2 );
					if ( $type === 'xmlrpc' ) {
						$hook = str_replace('__', '.', $hook);
					}
					Plugins::register( array($this, $fn), $type, $hook, $priority );
				}
			}
		}
		// look for help with this
		if ( method_exists( $this, 'help') ) {
			Plugins::register( array($this, '_help_plugin_config'), 'filter', 'plugin_config', 8);
			Plugins::register( array($this, '_help_plugin_ui'), 'action', 'plugin_ui', 8);
		}
		// look for a basic configure method
		if ( method_exists( $this, 'configure') ) {
			Plugins::register( array($this, '_configure_plugin_config'), 'filter', 'plugin_config', 8);
			Plugins::register( array($this, '_configure_plugin_ui'), 'action', 'plugin_ui', 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}
	 */
	public function add_rule($rule, $hook)
	{
		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);
		}
	}

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

}

?>

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');
				Plugins::register(array($this, '_help_plugin_ui_plugin'), 'action', 'plugin_ui');
			}
		}
		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) . '
';
				}
			}
		}
	}

}

?>


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

/**
 * Habari PluginHandler Class
 *
 */
class PluginHandler extends ActionHandler
{

	/**
	 * Constructor for the pluggable theme handler.
	 */
	public function __construct()
	{
		$this->setup_theme();
	}

	/**
	 * All handlers must implement act() to conform to handler API.
	 * This is the default implementation of act(), which attempts
	 * to call a class member method of $this->act_$action().  Any
	 * subclass is welcome to override this default implementation.
	 *
	 * @param string $action the action that was in the URL rule
	 */
	public function act($action)
	{
		if ( null === $this->handler_vars ) {
			$this->handler_vars = new SuperGlobal(array());
		}
		$this->action = $action;

		$this->theme->assign('matched_rule', URL::get_matched_rule());
		$request = new StdClass();
		foreach ( URL::get_active_rules() as $rule ) {
			$request->{$rule->name} = false;
		}
		$request->{$this->theme->matched_rule->name} = true;
		$this->theme->assign('request', $request);

		$action_hook = 'plugin_act_' . $action;
		$before_action_hook = 'before_' . $action_hook;
		$after_action_hook = 'after_' . $action_hook;

		Plugins::act( $before_action_hook, $this );
		Plugins::act( $action_hook, $this );
		Plugins::act( $after_action_hook );
	}

}

?>


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];
		}
	}

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

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

	/**
	 * 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
					$filename = HABARI_PATH . $filename;

					if ( file_exists( $filename ) ) {
						self::$plugin_files[$class] = $filename;
					}
				}
			}
			// make sure things work on Windows
			self::$plugin_files = array_map( create_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, create_function('$a', 'return $a 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( create_function( '$s', 'return str_replace(\'\\\\\', \'/\', $s);' ), $dirfiles )
				);
				$plugins = array_merge( $plugins, $dirfiles );
			}
		}
		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 ) ) {
			$info = new SimpleXMLElement( $xml_content );
			if ( $info->getName() != 'pluggable' ) {
				$info = null;
			}
		}
		return $info;
	}

	public static function load( $class, $activate = true )
	{
		$plugin = new $class;
		if ( $activate ) {
			self::$plugins[$plugin->plugin_id] = $plugin;
			$plugin->load();
		}
		return $plugin;
	}

	/**
	 * Instatiate 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;
		$ok = Plugins::filter('activate_plugin', $ok, $file); // Allow plugins to reject activation
		if ( $ok ) {
			// strip base path from stored path
			$short_file = MultiByte::substr( $file, strlen( HABARI_PATH ) );
			$activated = Options::get( 'active_plugins' );
			if ( !is_array( $activated ) || !in_array( $short_file, $activated ) ) {
				include_once($file);
				$class = Plugins::class_from_filename($file);
				$activated[$class] = $short_file;
				Options::set( 'active_plugins', $activated );

				$plugin = Plugins::load($class);
				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
	 */
	public static function deactivate_plugin( $file )
	{
		$ok = true;
		$name = '';
		$ok = Plugins::filter('deactivate_plugin', $ok, $file);  // Allow plugins to reject deactivation
		if ( $ok ) {
			// 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 ) ) {
				// 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 );
				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' );
			}
		}
		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 = array_map(create_function('$a', 'return $a["file"];'), $old_plugins);
		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 = array_map(create_function('$a', 'return $a["checksum"];'), $old_plugins);
		$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( $file, MultiByte::strlen( HABARI_PATH ) );
		}

		$plugin_data = array_map(create_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 ) ) {
				// TODO: throw log error
				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(sprintf( _t( 'Attempted to load the plugin file "%s", but it failed with syntax errors.
%s
' ), basename( $file ), $error ));
				$failed_plugins[] = $file;
			}
		}

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

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

?>


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

/**
 *
 * Includes an instance of the PostInfo class; for holding inforecords about a Post
 * If the Post object describes an existing post; use the internal info object to
 * get, set, unset and test for existence (isset) of info records.
 *
 * $this->info= new PostInfo( 1 );  // Info records of post with id = 1
 * $this->info->option1= "blah"; // set info record with name "option1" to value "blah"
 * $info_value= $this->info->option1; // get value of info record with name "option1" into variable $info_value
 * if ( isset ( $this->info->option1 ) )  // test for existence of "option1"
 * unset ( $this->info->option1 ); // delete "option1" info record
 *
 *
 */
class Post extends QueryRecord implements IsContent
{
	// static variables to hold post status and post type values
	static $post_status_list = array();
	static $post_type_list_active = array();
	static $post_type_list_all = array();

	private $tags = null;
	private $comments_object = null;
	private $author_object = null;
	private $tokens = null;

	private $inforecords = null;

	protected $url_args;

	/**
	 * returns an associative array of active post types
	 * @param bool whether to force a refresh of the cached values
	 * @return array An array of post type names => integer values
	 */
	public static function list_active_post_types( $refresh = false )
	{
		if ( ( ! $refresh ) && ( ! empty( self::$post_type_list_active ) ) ) {
			return self::$post_type_list_active;
		}
		self::$post_type_list_active['any'] = 0;
		$sql = 'SELECT * FROM {posttype} WHERE active = 1 ORDER BY id ASC';
		$results = DB::get_results( $sql );
		foreach ( $results as $result ) {
			self::$post_type_list_active[$result->name] = $result->id;
		}
		return self::$post_type_list_active;
	}

	/**
	 * returns an associative array of all post types
	 * @param bool whether to force a refresh of the cached values
	 * @return array An array of post type names => (integer values, active values)
	 */
	public static function list_all_post_types( $refresh = false )
	{
		if ( ( ! $refresh ) && ( ! empty( self::$post_type_list_all ) ) ) {
			return self::$post_type_list_all;
		}
		self::$post_type_list_all['any'] = 0;
		$sql = 'SELECT * FROM {posttype} ORDER BY id ASC';
		$results = DB::get_results( $sql );
		foreach ( $results as $result ) {
			self::$post_type_list_all[$result->name] = array(
				'id' => $result->id,
				'active' => $result->active
				);
		}
		return self::$post_type_list_all;
	}

	/**
	 * Activate an existing post type
	 *
	 * @param string The post type to activate
	 */
	public static function activate_post_type( $type )
	{
		$all_post_types = Post::list_all_post_types( true ); // We force a refresh

		// Check if it exists
		if ( array_key_exists( $type, $all_post_types ) ) {
			if ( ! $all_post_types[$type]['active'] == 1 ) {
				// Activate it
				$sql = 'UPDATE {posttype} SET active = 1 WHERE id = ' . $all_post_types[$type]['id'];
				DB::query( $sql );
			}
			return true;
		}
		else {
			return false; // Doesn't exist
		}
	}

	/**
	 * Deactivate a post type
	 *
	 * @param string The post type to deactivate
	 */
	public static function deactivate_post_type( $type )
	{
		$active_post_types = Post::list_active_post_types( false ); // We force a refresh

		if ( array_key_exists( $type, $active_post_types ) ) {
			// $type is active so we'll deactivate it
			$sql = 'UPDATE {posttype} SET active = 0 WHERE id = ' . $active_post_types[$type];
			DB::query( $sql );
			return true;
		}
		return false;
	}

	/**
	 * returns an associative array of post statuses
	 * @param mixed $all true to list all statuses, not just external ones, Post to list external and any that match the Post status
	 * @param boolean $refresh true to force a refresh of the cached values
	 * @return array An array of post statuses names => interger values
	 */
	public static function list_post_statuses( $all = true, $refresh = false )
	{
		$statuses = array();
		$statuses['any'] = 0;
		if ( $refresh || empty( self::$post_status_list ) ) {
			$sql = 'SELECT * FROM {poststatus} ORDER BY id ASC';
			$results = DB::get_results( $sql );
			self::$post_status_list = $results;
		}
		foreach ( self::$post_status_list as $status ) {
			if ( $all instanceof Post ) {
				if( ! $status->internal || $status->id == $all->status ) {
					$statuses[$status->name] = $status->id;
				}
			}
			elseif ( $all ) {
				$statuses[$status->name] = $status->id;
			}
			elseif ( ! $status->internal ) {
				$statuses[$status->name] = $status->id;
			}
		}
		return $statuses;
	}

	/**
	 * returns the integer value of the specified post status, or false
	 * @param mixed a post status name or value
	 * @return mixed an integer or boolean false
	 */
	public static function status( $name )
	{
		$statuses = Post::list_post_statuses();
		if ( is_numeric( $name ) && ( FALSE !== in_array( $name, $statuses ) ) ) {
			return $name;
		}
		if ( isset( $statuses[strtolower( $name )] ) ) {
			return $statuses[strtolower( $name )];
		}
		return false;
	}

	/**
	 * returns the friendly name of a post status, or null
	 * @param mixed a post status value, or name
	 * @return mixed a string of the status name, or null
	 */
	public static function status_name( $status )
	{
		$statuses = array_flip( Post::list_post_statuses() );
		if ( is_numeric( $status ) && isset( $statuses[$status] ) ) {
			return $statuses[$status];
		}
		if ( FALSE !== in_array( $status, $statuses ) ) {
			return $status;
		}
		return '';
	}

	/**
	 * returns the integer value of the specified post type, or false
	 * @param mixed a post type name or number
	 * @return mixed an integer or boolean false
	 */
	public static function type( $name )
	{
		$types = Post::list_active_post_types();
		if ( is_numeric( $name ) && ( FALSE !== in_array( $name, $types ) ) ) {
			return $name;
		}
		if ( isset( $types[strtolower( $name )] ) ) {
			return $types[strtolower( $name )];
		}
		return false;
	}

	/**
	 * returns the friendly name of a post type, or null
	 * @param mixed a post type number, or name
	 * @return mixed a string of the post type, or null
	 */
	public static function type_name( $type )
	{
		$types = array_flip( Post::list_active_post_types() );
		if ( is_numeric( $type ) && isset( $types[$type] ) ) {
			return $types[$type];
		}
		if ( FALSE !== in_array( $type, $types ) ) {
			return $type;
		}
		return '';
	}

	/**
	 * inserts a new post type into the database, if it doesn't exist
	 * @param string The name of the new post type
	 * @param bool Whether the new post type is active or not
	 * @return none
	 */
	public static function add_new_type( $type, $active = true )
	{
		// refresh the cache from the DB, just to be sure
		$types = self::list_all_post_types( true );

		if ( ! array_key_exists( $type, $types ) ) {
			// Doesn't exist in DB.. add it and activate it.
			DB::query( 'INSERT INTO {posttype} (name, active) VALUES (?, ?)', array( $type, $active ) );
		}
		elseif ( $types[$type]['active'] == 0 ) {
			// Isn't active so we activate it
			self::activate_post_type( $type );
		}
		ACL::create_token( 'post_' . Utils::slugify($type), _t('Permissions to posts of type "%s"', array($type) ), _t('Content'), TRUE );

		// now force a refresh of the caches, so the new/activated type
		// is available for immediate use
		$types = self::list_active_post_types( true );
		$types = self::list_all_post_types( true );
	}

	/**
	 * inserts a new post status into the database, if it doesn't exist
	 * @param string The name of the new post status
	 * @param bool Whether this status is for internal use only.  If true, this status will NOT be presented to the user
	 * @return none
	 */
	public static function add_new_status( $status, $internal = false )
	{
		// refresh the cache from the DB, just to be sure
		$statuses = self::list_post_statuses( true );
		if ( ! array_key_exists( $status, $statuses ) ) {
			// let's make sure we only insert an integer
			$internal = intval( $internal );
			DB::query( 'INSERT INTO {poststatus} (name, internal) VALUES (?, ?)', array( $status, $internal ) );
			// force a refresh of the cache, so the new status
			// is available for immediate use
			$statuses = self::list_post_statuses( true, true );
		}
	}

	/**
	 * Return the defined database columns for a Post.
	 * @return array Array of columns in the Post table
	 */
	public static function default_fields()
	{
		return array(
			'id' => 0,
			'slug' => '',
			'title' => '',
			'guid' => '',
			'content' => '',
			'cached_content' => '',
			'user_id' => 0,
			'status' => Post::status( 'draft' ),
			'pubdate' => HabariDateTime::date_create(),
			'updated' => HabariDateTime::date_create(),
			'modified' => HabariDateTime::date_create(),
			'content_type' => Post::type( 'entry' )
		);
	}

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

		parent::__construct( $paramarray );
		if ( isset( $this->fields['tags'] ) ) {
			$this->tags = $this->parsetags( $this->fields['tags'] );
			unset( $this->fields['tags'] );
		}

		$this->exclude_fields( 'id' );
		 /* $this->fields['id'] could be null in case of a new post. If so, the info object is _not_ safe to use till after set_key has been called. Info records can be set immediately in any other case. */
	}

	/**
	 * Return a single requested post.
	 *
	 *
	 * $post= Post::get( array( 'slug' => 'wooga' ) );
	 *
	 *
	 * @param array $paramarray An associative array of parameters, or a querystring
	 * @return Post The first post that matched the given criteria
	 */
	static function get( $paramarray = array() )
	{
		// Defaults
		$defaults = array (
			'where' => array(
				array(
					'status' => Post::status( 'published' ),
				),
			),
			'fetch_fn' => 'get_row',
		);
		foreach ( $defaults['where'] as $index => $where ) {
			$defaults['where'][$index] = array_merge( $where, Utils::get_params( $paramarray ) );
		}
		// make sure we get at most one result
		$defaults['limit'] = 1;

		return Posts::get( $defaults );
	}

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

	/**
	 * Generate a new slug for the post.
	 *
	 * @return string The slug
	 */
	private function setslug()
	{
		// determine the base value from:
		// - the new slug
		if ( isset( $this->newfields['slug']) && $this->newfields['slug'] != '' ) {
			$value = $this->newfields['slug'];
		}
		// - the new empty slug whilst in draft or progressing directly to published or scheduled from draft. Also allow changing of slug whilst in scheduled state
		elseif ( isset( $this->newfields['slug']) && $this->newfields['slug'] == '' ) {
			if ( $this->fields['status'] == Post::status( 'draft' ) || ( $this->fields['status'] != Post::status( 'draft' ) && $this->newfields['status'] != Post::status( 'draft' ) ) ) {
				if ( isset( $this->newfields['title'] ) && $this->newfields['title'] != '' ) {
					$value = $this->newfields['title'];
				}
				else {
					$value = $this->fields['title'];
				}
			}
		}
		// - the existing slug
		elseif ( $this->fields['slug'] != '' ) {
			$value = $this->fields['slug'];
		}
		// - the new post title
		elseif ( isset( $this->newfields['title'] ) && $this->newfields['title'] != '' ) {
			$value = $this->newfields['title'];
		}
		// - the existing post title
		elseif ( $this->fields['title'] != '' ) {
			$value = $this->fields['title'];
		}
		// - default
		else {
			$value = 'Post';
		}

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

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

	/**
	 * Generate the GUID for the new post.
	 */
	private function setguid()
	{
		if ( ! isset( $this->newfields['guid'] )
			|| ( $this->newfields['guid'] == '' )  // GUID is empty
			|| ( $this->newfields['guid'] == '//?p=' ) // GUID created by WP was erroneous (as is too common)
		) {
			$result = 'tag:' . Site::get_url( 'hostname' ) . ',' . date( 'Y' ) . ':' . rawurlencode($this->setslug()) . '/' . time();
			$this->newfields['guid'] = $result;
		}
		return $this->newfields['guid'];
	}

	/**
	 * function setstatus
	 * @param mixed the status to set it to. String or integer.
	 * @return integer the status of the post, or false if the new status isn't valid
	 * Sets the status for a post, given a string or integer.
	 */
	private function setstatus( $value )
	{
		$statuses = Post::list_post_statuses();
		$fieldname = isset( $this->fields['status'] ) ? 'newfields' : 'fields';
		if ( is_numeric( $value ) && in_array( $value, $statuses ) ) {
			return $this->{"$fieldname"}['status'] = $value;
		}
		elseif ( array_key_exists( $value, $statuses ) ) {
			return $this->{"$fieldname"}['status'] = Post::status( $value );
		}

		return false;
	}

	/**
	 * Ensure this is an array of tags.
	 *
	 * @param Mixed A string to parse for tags or an array of tags.
	 * @return Array An array of tags
	 */
	private static function parsetags( $tags )
	{
		if ( is_string( $tags ) ) {
			if ( '' === $tags ) {
				return array();
			}
			// dirrty ;)
			$rez = array( '\\"'=>':__unlikely_quote__:', '\\\''=>':__unlikely_apos__:' );
			$zer = array( ':__unlikely_quote__:'=>'"', ':__unlikely_apos__:'=>"'" );
			// escape
			$tagstr = str_replace( array_keys( $rez ), $rez, $tags );
			// match-o-matic
			preg_match_all( '/((("|((?<= )|^)\')\\S([^\\3]*?)\\3((?=[\\W])|$))|[^,])+/u', $tagstr, $matches );
			// cleanup
			$tags = array_map( 'trim', $matches[0] );
			$tags = preg_replace( array_fill( 0, count( $tags ), '/^(["\'])(((?!").)+)(\\1)$/'), '$2', $tags );
			// unescape
			$tags = str_replace( array_keys( $zer ), $zer, $tags );
			// hooray
			return $tags;
		}
		elseif ( is_array( $tags ) ) {
			return $tags;
		}
	}

	/**
	 * Save the tags associated to this post into the terms and object_terms tables
	 */
	private function save_tags()
	{
		return Tags::save_associations( $this->tags, $this->id );
	}

	/**
	 * function insert
	 * Saves a new post to the posts table
	 */
	public function insert()
	{
		$this->newfields['updated'] = HabariDateTime::date_create();
		$this->newfields['modified'] = $this->newfields['updated'];
		$this->setguid();

		$allow = true;
		$allow = Plugins::filter( 'post_insert_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'post_insert_before', $this );

		// Invoke plugins for all fields, since they're all "changed" when inserted
		foreach ( $this->fields as $fieldname => $value ) {
			Plugins::act( 'post_update_' . $fieldname, $this, ( $this->id == 0 ) ? null : $value, $this->$fieldname );
		}
		// invoke plugins for status changes
		Plugins::act( 'post_status_' . self::status_name( $this->status ), $this, null );

		$result = parent::insertRecord( DB::table( 'posts' ) );
		$this->newfields['id'] = DB::last_insert_id(); // Make sure the id is set in the post object to match the row id
		$this->fields = array_merge( $this->fields, $this->newfields );
		$this->newfields = array();
		$this->info->commit( DB::last_insert_id() );
		$this->save_tags();
		$this->create_default_permissions();
		EventLog::log( sprintf(_t('New post %1$s (%2$s);  Type: %3$s; Status: %4$s'), $this->id, $this->slug, Post::type_name( $this->content_type ), $this->statusname), 'info', 'content', 'habari' );
		Plugins::act( 'post_insert_after', $this );

		//scheduled post
		if( $this->status == Post::status( 'scheduled' ) ) {
			Posts::update_scheduled_posts_cronjob();
		}

		return $result;
	}

	/**
	 * function update
	 * Updates an existing post in the posts table
	 * @param bool $minor Indicates if this is a major or minor update
	 */
	public function update( $minor = true )
	{
		$this->modified = HabariDateTime::date_create();
		if ( ! $minor && $this->status != Post::status( 'draft' ) ) {
			$this->updated = $this->modified;
		}

		if ( isset( $this->fields['guid'] ) ) {
			unset( $this->newfields['guid'] );
		}

		$allow = true;
		$allow = Plugins::filter( 'post_update_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'post_update_before', $this );

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

		// invoke plugins for all fields which have been changed
		// For example, a plugin action "post_update_status" would be
		// triggered if the post has a new status value
		foreach ( $this->newfields as $fieldname => $value ) {
			Plugins::act( 'post_update_' . $fieldname, $this, $this->fields[$fieldname], $value );
		}

		// invoke plugins for status changes
		if ( isset( $this->newfields['status'] ) && $this->fields['status'] != $this->newfields['status'] ) {
		  Plugins::act( 'post_status_' . self::status_name( $this->newfields['status'] ), $this, $this->fields['status'] );
		}

		$result = parent::updateRecord( DB::table( 'posts' ), array( 'id' => $this->id ) );

		//scheduled post
		if ( $this->fields['status'] == Post::status( 'scheduled' ) || $this->status == Post::status( 'scheduled' ) ) {
			Posts::update_scheduled_posts_cronjob();
		}

		$this->fields = array_merge( $this->fields, $this->newfields );
		$this->newfields = array();
		$this->save_tags();
		$this->info->commit();
		Plugins::act( 'post_update_after', $this );
		return $result;
	}

	/**
	 * function delete
	 * Deletes an existing post
	 */
	public function delete()
	{
		$allow = true;
		$allow = Plugins::filter( 'post_delete_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		// invoke plugins
		Plugins::act( 'post_delete_before', $this );

		// delete all the tags associated with this post
		foreach ( $this->get_tags() as $tag_slug => $tag_text ) {
			$tag = Tags::get_by_slug( $tag_slug );
			Tag::detach_from_post( $tag->id, $this->id );
		}

		// Delete all comments associated with this post
		if ( $this->comments->count() > 0 ) {
			$this->comments->delete();
		}
		// Delete all info records associated with this post
			$this->info->delete_all();
		// Delete all post_tokens associated with this post
		$this->delete_tokens();

		$result = parent::deleteRecord( DB::table( 'posts' ), array( 'slug'=>$this->slug ) );
		EventLog::log( sprintf(_t('Post %1$s (%2$s) deleted.'), $this->id, $this->slug), 'info', 'content', 'habari' );

		//scheduled post
		if( $this->status == Post::status( 'scheduled' ) ) {
			Posts::update_scheduled_posts_cronjob();
		}

		// invoke plugins on the after_post_delete action
		Plugins::act( 'post_delete_after', $this );
		return $result;
	}

	/**
	 * function publish
	 * Updates an existing post to published status
	 * @return boolean True on success, false if not
	 */
	public function publish()
	{
		if ( $this->status == Post::status( 'published' ) ) {
			return true;
		}
		$allow = true;
		$allow = Plugins::filter( 'post_publish_allow', $allow, $this );
		if ( ! $allow ) {
			return;
		}
		Plugins::act( 'post_publish_before', $this );

		if ( $this->status != Post::status( 'scheduled' ) )  {
			$this->pubdate = HabariDateTime::date_create();
		}

		if ( $this->status == Post::status( 'scheduled' ) ) {
			$this->get_tags();
			$msg = sprintf(_t('Scheduled Post %1$s (%2$s) published at %3$s.'), $this->id, $this->slug, $this->pubdate->format());
		}
		else {
			$msg = sprintf(_t('Post %1$s (%2$s) published.'), $this->id, $this->slug);
		}

		$this->status = Post::status( 'published' );
		$result = $this->update( false );
		EventLog::log( $msg, 'info', 'content', 'habari' );

		// and call any final plugins
		Plugins::act( 'post_publish_after', $this );
		return $result;
	}

	/**
	 * function __get
	 * Overrides QueryRecord __get to implement custom object properties
	 * @param string Name of property to return
	 * @return mixed The requested field value
	 */
	public function __get( $name )
	{
		$fieldnames = array_merge( array_keys( $this->fields ), array( 'permalink', 'tags', 'comments', 'comment_count', 'approved_comment_count', 'comment_feed_link', 'author', 'editlink' ) );
		if ( !in_array( $name, $fieldnames ) && strpos( $name, '_' ) !== false ) {
			preg_match( '/^(.*)_([^_]+)$/', $name, $matches );
			list( $junk, $name, $filter )= $matches;
		}
		else {
			$filter = false;
		}

		switch ( $name ) {
			case 'statusname':
				$out = self::status_name( $this->status );
				break;
			case 'typename':
				$out = self::type_name( $this->content_type );
				break;
			case 'permalink':
				$out = $this->get_permalink();
				break;
			case 'editlink':
				$out = $this->get_editlink();
				break;
			case 'tags':
				$out = $this->get_tags();
				break;
			case 'comments':
				$out = $this->get_comments();
				break;
			case 'comment_count':
				$out = $this->get_comments()->count();
				break;
			case 'approved_comment_count':
				$out = Comments::count_by_id( $this->id );
				break;
			case 'comment_feed_link':
				$out = $this->get_comment_feed_link();
				break;
			case 'author':
				$out = $this->get_author();
				break;
			case 'info':
				$out = $this->get_info();
				break;
			default:
				$out = parent::__get( $name );
				break;
		}
		$out = Plugins::filter( "post_get", $out, $name, $this );
		$out = Plugins::filter( "post_{$name}", $out, $this );
		if ( $filter ) {
			$out = Plugins::filter( "post_{$name}_{$filter}", $out, $this );
		}
		return $out;
	}

	/**
	 * function __set
	 * Overrides QueryRecord __set to implement custom object properties
	 * @param string Name of property to return
	 * @return mixed The requested field value
	 */
	public function __set( $name, $value )
	{
		switch( $name ) {
			case 'pubdate':
			case 'updated':
			case 'modified':
				if ( !($value instanceOf HabariDateTime) ) {
					$value = HabariDateTime::date_create($value);
				}
				break;
			case 'tags':
				if ( is_array( $value) ) {
					return $this->tags = $value;
				}
				else {
					return $this->tags = $this->parsetags( $value );
				}
			case 'status':
				return $this->setstatus( $value );
		}
		return parent::__set( $name, $value );
	}

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

	/**
	 * Returns a form for editing this post
	 * @param string $context The context the form is being created in, most often 'admin'
	 * @return FormUI A form appropriate for creating and updating this post.
	 */
	public function get_form($context)
	{
		$form = new FormUI('create-content');
		$form->class[] = 'create';

		$newpost = ( 0 === $this->id );

		// If the post has already been saved, add a link to its permalink
		if ( !$newpost ) {
			$post_links = $form->append('wrapper', 'post_links');
			$permalink = ( $this->status != Post::status( 'published' ) ) ? $this->permalink . '?preview=1' : $this->permalink;
			$post_links->append('static', 'post_permalink', ''.( $this->status != Post::status('published') ? _t('Preview Post') : _t('View Post') ).'');
			$post_links->class ='container';
		}

		// Create the Title field
		$form->append('text', 'title', 'null:null', _t('Title'), 'admincontrol_text');
		$form->title->class[] = 'important';
		$form->title->class[] = 'check-change';
		$form->title->tabindex = 1;
		$form->title->value = $this->title;

		// Create the silos
		if ( count( Plugins::get_by_interface( 'MediaSilo' ) ) ) {
			$form->append('silos', 'silos');
			$form->silos->silos = Media::dir();
		}

		// Create the Content field
		$form->append('textarea', 'content', 'null:null', _t('Content'), 'admincontrol_textarea');
		$form->content->class[] = 'resizable';
		$form->content->class[] = 'check-change';
		$form->content->tabindex = 2;
		$form->content->value = $this->content;
		$form->content->raw = true;

		// Create the tags field
		$form->append('text', 'tags', 'null:null', _t('Tags, separated by, commas'), 'admincontrol_text');
		$form->tags->class = 'check-change';
		$form->tags->tabindex = 3;
		$form->tags->value = implode(', ', $this->get_tags());

		// Create the splitter
		$publish_controls = $form->append('tabs', 'publish_controls');

		// Create the publishing controls
		// pass "false" to list_post_statuses() so that we don't include internal post statuses
		$statuses = Post::list_post_statuses( $this );
		unset( $statuses[array_search( 'any', $statuses )] );
		$statuses = Plugins::filter( 'admin_publish_list_post_statuses', $statuses );

		$settings = $publish_controls->append('fieldset', 'settings', _t('Settings'));

		$settings->append('select', 'status', 'null:null', _t('Content State'), array_flip($statuses), 'tabcontrol_select');
		$settings->status->value = $this->status;

		// hide the minor edit checkbox if the post is new
		if ( $newpost ) {
			$settings->append('hidden', 'minor_edit', 'null:null');
			$settings->minor_edit->value = false;
		}
		else {
			$settings->append('checkbox', 'minor_edit', 'null:null', _t('Minor Edit'), 'tabcontrol_checkbox');
			$settings->minor_edit->value = true;
			$form->append('hidden', 'modified', 'null:null')->value = $this->modified;
		}

		$settings->append('checkbox', 'comments_enabled', 'null:null', _t('Comments Allowed'), 'tabcontrol_checkbox');
		$settings->comments_enabled->value = $this->info->comments_disabled ? false : true;

		$settings->append('text', 'pubdate', 'null:null', _t('Publication Time'), 'tabcontrol_text');
		$settings->pubdate->value = $this->pubdate->format('Y-m-d H:i:s');

		$settings->append('hidden', 'updated', 'null:null' );
		$settings->updated->value = $this->updated->int;

		$settings->append('text', 'newslug', 'null:null', _t('Content Address'), 'tabcontrol_text');
		$settings->newslug->value = $this->slug;

		// Create the button area
		$buttons = $form->append('fieldset', 'buttons');
		$buttons->template = 'admincontrol_buttons';
		$buttons->class[] = 'container';
		$buttons->class[] = 'buttons';
		$buttons->class[] = 'publish';

		// Create the Save button
		$require_any = array( 'own_posts' => 'create', 'post_any' => 'create', 'post_' . Post::type_name( $this->content_type ) => 'create' );
		if ( ( $newpost && User::identify()->can_any( $require_any ) ) || ( !$newpost && ACL::access_check( $this->get_access(), 'edit' ) ) ) {
			$buttons->append('submit', 'save', _t('Save'), 'admincontrol_submit');
			$buttons->save->tabindex = 4;
		}

		// Add required hidden controls
		$form->append('hidden', 'content_type', 'null:null');
		$form->content_type->id = 'content_type';
		$form->content_type->value = $this->content_type;
		$form->append('hidden', 'post_id', 'null:null');
		$form->post_id->id = 'id';
		$form->post_id->value = $this->id;
		$form->append('hidden', 'slug', 'null:null');
		$form->slug->value = $this->slug;

		// Let plugins alter this form
		Plugins::act('form_publish', $form, $this, $context);

		// Return the form object
		return $form;
	}

	/**
	 * Manage this post's comment form
	 *
	 * @param String context // What is $context for ?
	 * @return FormUI The comment form for this post
	 */
	public function comment_form($context = 'public')
	{
		// Handle comment submissions and default commenter id values
		$cookie = 'comment_' . Options::get( 'GUID' );
		$commenter_name = '';
		$commenter_email = '';
		$commenter_url = '';
		$commenter_content = '';
		$user = User::identify();
		if ( isset( $_SESSION['comment'] ) ) {
			$details = Session::get_set( 'comment' );
			$commenter_name = $details['name'];
			$commenter_email = $details['email'];
			$commenter_url = $details['url'];
			$commenter_content = $details['content'];
		}
		elseif ( $user->loggedin ) {
			$commenter_name = $user->displayname;
			$commenter_email = $user->email;
			$commenter_url = Site::get_url( 'habari' );
		}
		elseif ( isset( $_COOKIE[$cookie] ) ) {
			list( $commenter_name, $commenter_email, $commenter_url )= explode( '#', $_COOKIE[$cookie] );
		}

		// Now start the form.
		$form = new FormUI('comment-' . $context, 'comment');
		$form->class[] = $context;
		$form->class[] = 'commentform';
		$form->set_option( 'form_action',  URL::get( 'submit_feedback', array( 'id' => $this->id ) ) );

		// Create the Name field
		$form->append(
			'text',
			'cf_commenter',
			'null:null',
			_t('Name *Required'),
			'formcontrol_text'
		)->add_validator('validate_required', _t('The Name field value is required'))
		->id = 'comment_name';
		$form->cf_commenter->tabindex = 1;
		$form->cf_commenter->value = $commenter_name;

		// Create the Email field
		$form->append(
			'text',
			'cf_email',
			'null:null',
			_t('Email'),
			'formcontrol_text'
		)->add_validator('validate_email', _t('The Email field value must be a valid email address'))
		->id = 'comment_email';
		$form->cf_email->tabindex = 2;
		if ( Options::get('comments_require_id') == 1 ) {
			$form->cf_email->caption = _t('Email *Required');
		}
		$form->cf_email->value = $commenter_email;

		// Create the URL field
		$form->append(
			'text',
			'cf_url',
			'null:null',
			_t('Website'),
			'formcontrol_text'
		)->add_validator('validate_url', _t('The Web Site field value must be a valid URL'))
		->id = 'comment_url';
		$form->cf_url->tabindex = 3;
		$form->cf_url->value = $commenter_url;

		// Create the Comment field
		$form->append(
			'text',
			'cf_content',
			'null:null',
			_t('Comment'),
			'formcontrol_textarea'
		)->add_validator('validate_required', _t('The Content field value is required'))
		->id = 'comment_content';
		$form->cf_content->tabindex = 4;
		$form->cf_content->value = $commenter_content;

		// Create the Submit button
		$form->append('submit', 'cf_submit', _t('Submit'), 'formcontrol_submit');
		$form->cf_submit->tabindex = 5;

		// Add required hidden controls
		/*
		$form->append('hidden', 'content_type', 'null:null');
		$form->content_type->value = $this->content_type;
		$form->append('hidden', 'post_id', 'null:null');
		$form->post_id->id = 'id';
		$form->post_id->value = $this->id;
		$form->append('hidden', 'slug', 'null:null');
		$form->slug->value = $this->slug;
		*/

		// Let plugins alter this form
		Plugins::act('form_comment', $form, $this, $context);

		// Return the form object
		return $form;
	}

	/**
	 * Returns a URL for the ->permalink property of this class.
	 * @return string A URL to this post.
	 * @todo separate permalink rule?  (Not sure what this means - OW)
	 */
	private function get_permalink()
	{
		$content_type = Post::type_name( $this->content_type );
		return URL::get(
			array(
				"display_{$content_type}",
				"display_post"
			),
			$this,
			false
		);
	}

	/**
	 * Returns a URL for the ->editlink property of this class.
	 * @return string A url to edit this post in the admin.
	 */
	private function get_editlink()
	{
		return URL::get('admin', 'page=publish&id=' . $this->id);
	}

	/**
	 * function get_tags
	 * Gets the tags for the post
	 * @return array The tags array for this post
	 */
	private function get_tags()
	{
		$result = Tags::get_associations( $this->id );
		if ( $result ) {
			foreach ( $result as $t ) {
				$this->tags[$t->term] = $t->term_display;
			}
		}

		if ( count( $this->tags ) == 0 ) {
			return array();
		}
		return $this->tags;
	}

	/**
	 * function get_comments
	 * Gets the comments for the post
	 * @return &array A reference to the comments array for this post
	 */
	private function &get_comments()
	{
		if ( ! $this->comments_object ) {
			$this->comments_object = Comments::by_post_id( $this->id );
		}
		return $this->comments_object;
	}

	/**
	 * private function get_comment_feed_link
	 * Returns the permalink for this post's comments Atom feed
	 * @return string The permalink of this post's comments Atom feed
	 */
	private function get_comment_feed_link()
	{
		$content_type = Post::type_name( $this->content_type );
		return URL::get( array( "atom_feed_{$content_type}_comments" ), $this, false );
	}

	/**
	 * function get_info
	 * Gets the info object for this post, which contains data from the postinfo table
	 * related to this post.
	 * @return PostInfo object
	 */
	private function get_info()
	{
		if ( ! isset( $this->inforecords ) ) {
			// If this post isn't in the database yet...
			if(  0 == $this->id ) {
				$this->inforecords = new PostInfo();
			}
			else {
				$this->inforecords = new PostInfo( $this->id );
			}
		}
		else {
			$this->inforecords->set_key( $this->id );
		}
		return $this->inforecords;
	}

	/**
	 * private function get_author()
	 * returns a User object for the author of this post
	 * @return User a User object for the author of the current post
	 */
	private function get_author()
	{
		if ( ! isset( $this->author_object ) ) {
			// XXX for some reason, user_id is a string sometimes?
			$this->author_object = User::get_by_id( $this->user_id );
		}
		return $this->author_object;
	}

	/**
	 * Returns a set of properties used by URL::get to create URLs
	 * @return array Properties of this post used to build a URL
	 */
	public function get_url_args()
	{
		if ( !$this->url_args ) {
			$arr = array( 'content_type_name' => Post::type_name( $this->content_type ) );
			$author = URL::extract_args( $this->author, 'author_' );
			$info = URL::extract_args( $this->info, 'info_' );
			$this->url_args = array_merge( $author, $info, $arr, $this->to_array(), $this->pubdate->getdate() );
		}
		return $this->url_args;
	}

	/**
	 * Returns the ascending post, relative to this post, according to params
	 * @params The params by which to work out what is the ascending post
	 * @return Post The ascending post
	 */
	public function ascend($params = null)
	{
		return Posts::ascend($this, $params);
	}

	/**
	 * Returns the descending post, relative to this post, according to params
	 * @params The params by which to work out what is the descending post
	 * @return Post The descending post
	 */
	public function descend($params = null)
	{
		return Posts::descend($this, $params);
	}

	/**
	 * Return the content type of this object
	 *
	 * @return array An array of content types that this object represents, starting with the most specific
	 * @see IsContent
	 */
	public function content_type()
	{
		return array(Post::type_name($this->content_type), 'Post');
	}

	/**
	 * Adds the default tokens to this post when it's saved
	 */
	public function create_default_tokens()
	{
		$tokens = array();
		$tokens = Plugins::filter('post_tokens', $tokens, $this);
		$this->add_tokens( $this->content_type() );
	}

	/**
	 * Checks if this post has one or more tokens
	 *
	 * @param mixed $tokens A single token string or an array of tokens
	 * @return mixed false if no tokens match, an array of matching token ids if any match
	 */
	public function has_tokens( $tokens )
	{
		$this->get_tokens();
		$tokens = Utils::single_array( $tokens );
		$tokens = array_map(array('ACL', 'token_id'), $tokens);
		$tokens = array_intersect($tokens, $this->tokens);
		if ( count($tokens) == 0 ) {
			return false;
		}
		return $tokens;
	}

	/**
	 * Add a token to a post
	 * @param mixed $token The name of the permission to add, or an array of permissions to add
	 */
	public function add_tokens( $tokens )
	{
		$this->get_tokens();
		$tokens = Utils::single_array( $tokens );
		$tokens = array_map(array('ACL', 'token_id'), $tokens);
		$add_tokens = array_diff($tokens, $this->tokens);
		$add_tokens = array_unique($add_tokens);
		foreach ( $add_tokens as $token_id ) {
			DB::insert( '{post_tokens}', array( 'post_id' => $this->id, 'token_id' => $token_id ) );
		}
		$this->tokens = array_merge($this->tokens, $add_tokens);
		$this->tokens = array_unique($this->tokens);
	}

	/**
	 * Deletes all tokens from a post
	 */
	public function delete_tokens()
	{
		DB::delete( '{post_tokens}', array( 'post_id' => $this->id ) );
		$this->tokens = array();
	}

	/**
	 * Deletes tokens from a post
	 * @param mixed $token The name of the permission to remove, or an array of permissions to remove
	 */
	public function remove_tokens( $tokens )
	{
		$this->get_tokens();
		$tokens = Utils::single_array( $tokens );
		$tokens = array_map(array('ACL', 'token_id'), $tokens);
		$remove_tokens = array_intersect($tokens, $this->tokens);
		foreach ( $remove_tokens as $token_id ) {
			DB::delete( '{post_tokens}', array( 'post_id' => $this->id, 'token_id' => $token_id ) );
		}
		$this->tokens = array_diff($this->tokens, $remove_tokens);
	}

	/**
	 * Applies a new set of specific tokens to a post
	 * @param mixed $tokens A string token, or an array of tokens to apply to this post
	 */
	public function set_tokens( $tokens )
	{
		$tokens = Utils::single_array( $tokens );
		$new_tokens = array_map(array('ACL', 'token_id'), $tokens);
		$new_tokens = array_unique($new_tokens);
		DB::delete( '{post_tokens}', array( 'post_id' => $this->id ) );
		foreach ( $new_tokens as $token_id ) {
			DB::insert( '{post_tokens}', array( 'post_id' => $this->id, 'token_id' => $token_id ) );
		}
		$this->tokens = $new_tokens;
	}

	/**
	 * Returns an array of token ids that are associated with this post
	 * Also initializes the internal token array for use by other token operations
	 *
	 * @return array An array of token ids
	 */
	public function get_tokens()
	{
		if ( empty( $this->tokens ) ) {
			$this->tokens = DB::get_column( 'SELECT token_id FROM {post_tokens} WHERE post_id = ?', array($this->id) );
		}
		return $this->tokens;
	}

	/**
	 * Returns an access Bitmask for the given user on this post
	 * @param User $user The user mask to fetch
	 * @return Bitmask
	 */
	public function get_access( $user = null )
	{
		if ( ! $user instanceof User ) {
			$user = User::identify();
		}

		if ( $user->can( 'super_user' ) ) {
			return ACL::get_bitmask( 'full' );
		}

		// Collect a list of applicable tokens
		$tokens = array(
			'post_any',
			'post_' . Post::type_name( $this->content_type ),
		);

		if ( $user->id == $this->user_id) {
			$tokens[] = 'own_posts';
		}

		$tokens = array_merge($tokens, $this->get_tokens());

		// collect all possible token accesses on this post
		$token_accesses = array();
		foreach ( $tokens as $token ) {
			$access = ACL::get_user_token_access( $user, $token );
			if ( $access instanceof Bitmask ) {
				$token_accesses[] = ACL::get_user_token_access( $user, $token )->value;
			}
		}

		// now that we have all the accesses, loop through them to build the access to the particular post
		if ( in_array( 0, $token_accesses ) ) {
			return ACL::get_bitmask( 0 );
		}
		return ACL::get_bitmask( Utils::array_or( $token_accesses ) );
	}

}
?>


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

/**
 * Post metadata
 */
class PostInfo extends InfoRecords
{
	function __construct ( $post_id = NULL )
	{
		parent::__construct ( DB::table('postinfo'), "post_id", $post_id ); // call parent with appropriate  parameters
	}
}
?>