code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }
/**
 * Habari Pingback plugin, enables pingback support between sites.
 * @link http://www.hixie.ch/specs/pingback/pingback The Pingback spec
 *
 * @package Habari
 */

class Pingback extends Plugin
{

	/**
	 * Register the Pingback event type with the event log
	 */
	public function action_plugin_activation( $file )
	{
		if ( realpath( $file ) == __FILE__ ) {
			EventLog::register_type( 'Pingback' );
		}
	}

	/**
	 * Unregister the Pingback event type on deactivation
	 * @todo Should we be doing this?
	 */
	public function action_plugin_deactivation( $file )
	{
		if ( realpath( $file ) == __FILE__ ) {
			EventLog::unregister_type( 'Pingback' );
		}
	}

	/**
	 * Pingback links from the post content when a post is inserted into the database.
	 * @param post The post from which to send pingbacks
	 */
	public function action_post_insert_after( $post )
	{
		// only execute if this is a published post
		if ( Post::status( 'published' ) != $post->status ) {
			return;
		}
		$this->pingback_all_links( $post->content, $post->permalink, $post );
	}

	/**
	 * Pingback mentioned links when a post is updated.
	 * @param Post $post The post is updated
	 * We invoke this function regardless of what might have been updated
	 * in the post because:
	 * 	- this will only execute if the post is published
	 *	- the pingback_all_links function keeps track of links its
	 *		already pinged, so if the content hasnt changed no
	 *		pings will be sent`
	 */
	public function action_post_update_after( $post )
	{
		// only execute if this is a published post
		if ( Post::status( 'published' ) != $post->status) {
			return;
		}
		$this->pingback_all_links( $post->content, $post->permalink, $post );
	}

	/**
	 * Add the Pingback header on single post/page requests
	 * Not to the entire site.  Clever.
	 */
	public function action_add_template_vars()
	{
		$action = Controller::get_action();
		if ( $action == 'display_post' ) {
			header( 'X-Pingback: ' . URL::get( 'xmlrpc' ) );
		}
		else {
			header( 'X-action: ' . $action );
		}
	}

	/**
	 * Receive a Pingback via XMLRPC
	 * @param array $params An array of XMLRPC parameters from the remote call
	 * @return string The success state of the pingback
	 */
	public function xmlrpc_pingback__ping( $params )
	{
		try {
			list( $source_uri, $target_uri )= $params;

			// This should really be done by an Habari core function
			$target_parse = InputFilter::parse_url( $target_uri );
			$target_stub = $target_parse['path'];
			$base_url = Site::get_path( 'base', true );

			if ( '/' != $base_url) {
				$target_stub = str_replace( $base_url, '', $target_stub );
			}

			$target_stub = trim( $target_stub, '/' );

			if ( strpos( $target_stub, '?' ) !== false ) {
				list( $target_stub, $query_string )= explode( '?', $target_stub );
			}

			// Can this be used as a target?
			$target_slug = URL::parse( $target_stub )->named_arg_values['slug'];

			if ( $target_slug === false ) {
				throw new XMLRPCException( 33 );
			}

			// Does the target exist?
			$target_post = Post::get( array( 'slug' => $target_slug ) );

			if ( $target_post === false ) {
				throw new XMLRPCException( 32 );
			}

			// Is comment allowed?
			if ( $target_post->info->comments_disabled ) {
				throw new XMLRPCException( 33 );
			}

			// Is this Pingback already registered?
			if ( Comments::get( array( 'post_id' => $target_post->id, 'url' => $source_uri, 'type' => Comment::PINGBACK ) )->count() > 0 ) {
				throw new XMLRPCException( 48 );
			}

			// Retrieve source contents
			try {
				$rr = new RemoteRequest( $source_uri );
				$rr->execute();
				if ( ! $rr->executed() ) {
					throw new XMLRPCException( 16 );
				}
				$source_contents = $rr->get_response_body();
				$headers = $rr->get_response_headers();
			}
			catch ( XMLRPCException $e ) {
				// catch our special type of exception and re-throw it
				throw $e;
			}
			catch ( Exception $e ) {
				throw new XMLRPCException( -32300 );
			}

			// Encoding is converted into internal encoding.
			// First, detect the source string's encoding
			$habari_encoding = strtoupper( MultiByte::hab_encoding() );
			$source_encoding = 'Windows-1252';
			// Is the charset in the headers?
			if ( isset( $headers['Content-Type'] ) && strpos( $headers['Content-Type'], 'charset' ) !== false ) {
				// This regex should be changed to meet the HTTP spec at some point
				if ( preg_match("/charset[\x09\x0A\x0C\x0D\x20]*=[\x09\x0A\x0C\x0D\x20]*('?)([A-Za-z0-9\-\_]+)\1/i", $headers['Content-Type'], $matches ) ) {
					$source_encoding = strtoupper( $matches[2] );
				}
			}
			// Can we tell the charset from the stream itself?
			else if ( ( $enc = MultiByte::detect_bom_encoding( $source_contents ) ) !== false ) {
				$source_encoding = $enc;
			}
			// Is the charset in a meta tag?
			else if ( preg_match( "/<meta[^>]+charset[\x09\x0A\x0C\x0D\x20]*=[\x09\x0A\x0C\x0D\x20]*([\"']?)([A-Za-z0-9\-\_]+)\1/i", $source_contents, $matches ) ) {
				$source_encoding = strtoupper( $matches[2] );
				if (in_array($source_encoding, array("UTF-16", "UTF-16BE", "UTF-16LE"))) {
					$source_encoding = "UTF-8";
				}
			}
			// Then, convert the string
			$ret = MultiByte::convert_encoding( $source_contents, $habari_encoding, $source_encoding );
			if ( $ret !== false ) {
				$source_contents = $ret;
			}

			// Find the page's title
			preg_match( '/<title>(.*)<\/title>/is', $source_contents, $matches );
			$source_title = $matches[1];

			// Find the reciprocal links and their context
			preg_match( '/<body[^>]*>(.+)<\/body>/is', $source_contents, $matches );
			$source_contents_filtered = preg_replace( '/\s{2,}/is', ' ', strip_tags( $matches[1], '<a>' ) );

			// Get rid of all the non-recriprocal links
			$ht = new HTMLTokenizer( trim( $source_contents_filtered ) );
			$set = $ht->parse();
			$all_links = $set->slice( 'a', array() );
			$keep_links = $set->slice( 'a', array( 'href' => $target_uri ) );
			$bad_links = array_diff( $all_links, $keep_links );
			foreach( $bad_links as $link ) {
				$link->tokenize_replace( '' );
				$set->replace_slice( $link );
			}
			$source_contents_filtered = (string)$set;

			// Get the excerpt
			if ( !preg_match( '%.{0,100}?<a[^>]*?href\\s*=\\s*("|\'|)' . $target_uri . '\\1[^>]*?'.'>(.+?)</a>.{0,100}%s', $source_contents_filtered, $source_excerpt ) ) {
				throw new XMLRPCException( 17 );
			}

			/** Sanitize Data */
			$source_excerpt = '…' . InputFilter::filter( $source_excerpt[0] ) . '…';
			$source_title = InputFilter::filter($source_title);
			$source_uri = InputFilter::filter($source_uri);

			/* Sanitize the URL */
			if (!empty($source_uri)) {
				$parsed = InputFilter::parse_url( $source_uri );
				if ( $parsed['is_relative'] ) {
					// guess if they meant to use an absolute link
					$parsed = InputFilter::parse_url( 'http://' . $source_uri );
					if ( ! $parsed['is_error'] ) {
						$source_uri = InputFilter::glue_url( $parsed );
					}
					else {
						// disallow relative URLs
						$source_uri = '';
					}
				}
				if ( $parsed['is_pseudo'] || ( $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https' ) ) {
					// allow only http(s) URLs
					$source_uri = '';
				}
				else {
					// reconstruct the URL from the error-tolerant parsing
					// http:moeffju.net/blog/ -> http://moeffju.net/blog/
					$source_uri = InputFilter::glue_url( $parsed );
				}
			}

			// Add a new pingback comment
			$pingback = new Comment( array(
				'post_id'	=>	$target_post->id,
				'name'		=>	$source_title,
				'email'		=>	'',
				'url'		=>	$source_uri,
				'ip'		=>	Utils::get_ip(),
				'content'	=>	$source_excerpt,
				'status'	=>	Comment::STATUS_UNAPPROVED,
				'date'		=>	HabariDateTime::date_create(),
				'type' 		=> 	Comment::PINGBACK,
				) );

			$pingback->insert();

			// Respond to the Pingback
			return 'The pingback has been registered';
		}
		catch ( XMLRPCException $e ) {
			$e->output_fault_xml();
		}
	}

	/**
	 * Send a single Pingback
	 * @param string $source_uri The URI source of the ping (here)
	 * @param string $target_uri The URI destination of the ping (there, the site linked to in the content)
	 * @param Post $post The post	object that is initiating the ping, used to track the pings that were sent
	 * @todo If receive error code of already pinged, add to the successful.
	 */
	public function send_pingback( $source_uri, $target_uri, $post = NULL )
	{
		// RemoteRequest makes it easier to retrieve the headers.
		try {
			$rr = new RemoteRequest( $target_uri );
			$rr->execute();
			if ( ! $rr->executed() ) {
				return false;
			}
		}
		catch ( Exception $e ) {
			// log the pingback error
			EventLog::log( _t( 'Unable to retrieve target, can\'t detect pingback endpoint. (Source: %1$s | Target: %2$s)', array( $source_uri, $target_uri ) ), 'err', 'Pingback' );
			return false;
		}

		$headers = $rr->get_response_headers();
		$body = $rr->get_response_body();

		// Find a Pingback endpoint.
		if ( isset( $headers['X-Pingback'] ) ) {
			$pingback_endpoint = $headers['X-Pingback'];
		}
		elseif ( preg_match( '/<link rel="pingback" href="([^"]+)" ?\/?'.'>/is', $body, $matches ) ) {
			$pingback_endpoint = $matches[1];
		}
		else {
			// No Pingback endpoint found.
			return false;
		}

		try {
			$response = XMLRPCClient::open( $pingback_endpoint )->pingback->ping( $source_uri, $target_uri );
		}
		catch ( Exception $e ) {
			EventLog::log( _t( 'Invalid Pingback endpoint - %1$s (Source: %2$s | Target: %3$s)', array( $pingback_endpoint, $source_uri, $target_uri ) ), 'err', 'Pingback' );
			return false;
		}

		if ( isset( $response->faultString ) ) {
			EventLog::log( _t( 'Pingback error: %1$s - %2$s (Source: %3$s | Target: %4$s)', array( $response->faultCode, $response->faultString, $source_uri, $target_uri ) ), 'err', 'Pingback' );
			return false;
		}
		else {
			// The pingback has been registered and is stored as a successful pingback.
			if ( is_object( $post ) ) {
				if ( isset( $post->info->pingbacks_successful ) ) {
					$pingbacks_successful = $post->info->pingbacks_successful;
					$pingbacks_successful[]= $target_uri;
					$post->info->pingbacks_successful = $pingbacks_successful;
				}
				else {
					$post->info->pingbacks_successful = array( $target_uri );
				}
				$post->info->commit();
			}
			return true;
		}
	}

	/**
	 * Scan all links in the content and send them a Pingback.
	 * @param string $content The post content to search
	 * @param string $source_uri The source of the content
	 * @param Post $post The post object of the source of the ping
	 * @param boolean $force If true, force the system to ping all links even if that had been pinged before
	 */
	public function pingback_all_links( $content, $source_uri, $post = NULL, $force = false )
	{

		$tokenizer = new HTMLTokenizer( $content, false );
		$tokens = $tokenizer->parse();

		// slice out only A tags
		$slices = $tokens->slice( array( 'a' ), array( ) );

		$urls = array();
		foreach ( $slices as $slice ) {

			// if there is no href attribute, just skip it, though there is something wrong
			if ( !isset( $slice[0]['attrs']['href'] ) ) {
				continue;
			}
			else {
				$url = $slice[0]['attrs']['href'];
			}

			// make sure it's a valid URL before we waste our time
			$parsed = InputFilter::parse_url( $url );

			if ( $parsed['is_error'] || $parsed['is_pseudo'] || $parsed['is_relative'] ) {
				continue;
			}
			else {
				$urls[] = $url;
			}

		}

		if ( is_object( $post ) && isset( $post->info->pingbacks_successful ) ) {
			$fn = ( $force === true ) ? 'array_merge' : 'array_diff';
			$links = $fn( $urls, $post->info->pingbacks_successful );
		}
		else {
			$links = $urls;
		}

		$links = array_unique( $links );

		foreach ( $links as $target_uri ) {
			if ( $this->send_pingback( $source_uri, $target_uri, $post ) ) {
				EventLog::log( _t( 'Sent pingbacks for "%1$s", target: %2$s', array( $post->title, $target_uri ) ), 'info', 'Pingback' );
			}
		}
	}

	/**
	 * Add the pingback options to the options page
	 * @param array $items The array of option on the options page
	 * @return array The array of options including new options for pingback
	 */
	public function filter_admin_option_items($items)
	{
		$items[_t('Publishing')]['pingback_send'] = array(
			'label' => _t('Send Pingbacks to Links'),
			'type' => 'checkbox',
			'helptext' => '',
		);

		return $items;
	}

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

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

}
?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.habariproject.org/pluggable/0.8">
	<name>Pingback</name>
	<license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>

	<author url="http://habariproject.org/">The Habari Community</author>

	<version>1.1</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Adds Pingback 1.0 methods to the XML-RPC server.]]></description>

	<copyright>2009</copyright>

	<help>
		<value><![CDATA[

When this plugin is active, other sites that link to specific posts can "ping" them with a special request.  This request is registered by Habari as a comment that is processed via the usual system.

When you write a post that links to other sites, Habari will attempt to ping those URLs too.  Depending on the capabilities of the pinged server, your post may appear as a comment on the remote site.

Pingbacks are generally less spam-prone than Trackbacks (which are not implemented in this plugin), because when the ping is received, Habari visits the pinging site to ensure that the provided link to your post does actually exist.

		]]></value>
	</help>
	<provides>
		<feature url="http://wiki.habariproject.org/en/Feature/pingback">pingback</feature>
	</provides>

</pluggable>

repo_name: DaSourcerer/habari


code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }

class SimplePrivatePosts extends Plugin
{

	public function action_plugin_activation()
	{
		ACL::create_token('private', 'Permission to read posts marked as "private"', 'Private Posts');

		// Deny the anonymous group access to the private token, if the group hasn't been removed (why would you remove it ??)
		$anon = UserGroup::get('anonymous');
		if ( false != $anon ) {
			$anon->deny('private');
		}
	}

	public function action_plugin_deactivation( $plugin_file )
	{
		if ( Plugins::id_from_file(__FILE__) == Plugins::id_from_file($plugin_file) ) {
			ACL::destroy_token('private');
		}
	}

	public function action_form_publish($form, $post)
	{
		$form->settings->append('checkbox', 'private_post', 'null:null', _t('Private Post'), 'tabcontrol_checkbox');
		if ( $post->has_tokens('private') ) {
			$form->private_post->value = true;
		}
	}

	public function action_publish_post($post, $form)
	{
		if ( $form->private_post->value == true ) {
			$post->add_tokens('private');
		}
		else {
			$post->remove_tokens('private');
		}
	}

}
?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.habariproject.org/pluggable/0.8">
	<name>Simple Private Posts</name>
	<license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>

	<author url="http://habariproject.org/">The Habari Community</author>

	<version>1.0</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Mark posts as private so that only authenticated users who have read access to the "private" token can read them.]]></description>

	<copyright>2009</copyright>

	<help>
		<value><![CDATA[

The "private" token is automatically set to deny for the "Anonymous" group, visitors to your site who are not logged in, which prevents them from seeing posts with the private token. You can select other groups to deny permission to posts with the "private" token on the groups page.

On the post creation page, a new checkbox labeled "Private Post" will appear in the settings area. Check this box to make the post private.

		]]></value>
	</help>
</pluggable>


code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }

/**
 * SpamChecker Class
 *
 * This class implements first round spam checking.
 *
 **/

class SpamChecker extends Plugin
{

	/**
	 * function act_comment_insert_before
	 * This function is executed when the action "comment_insert_before"
	 * is invoked from a Comment object.
	 * The parent class, Plugin, handles registering the action
	 * and hook name using the name of the function to determine
	 * where it will be applied.
	 * You can still register functions as hooks without using
	 * this method, but boy, is it handy.
	 * @param Comment The comment that will be processed before storing it in the database.
	 **/
	function action_comment_insert_before ( $comment )
	{
		// This plugin ignores non-comments
		if ($comment->type != Comment::COMMENT) {
			return;
		}

		$spamcheck = array();

		//


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.habariproject.org/pluggable/0.8">
	<name>Spam Checker</name>
	<license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>

	<author url="http://habariproject.org/">The Habari Community</author>

	<version>1.0</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Flags as spam obvious comment spam.]]></description>
	<copyright>2009</copyright>

	<help>
	<value><![CDATA[

Applies a number of common sense filters to submitted comments in order to identify the ones which are most probably spam. While not 100% reliable, it eliminates a significant portion of spam based on common elements of these messages.

	]]></value>
	</help>
	<provides>
		<feature url="http://wiki.habariproject.org/en/Feature/spamcheck">spamcheck</feature>
	</provides>
</pluggable>


code:
.posts .item.deleted span, .posts .item.deleted span * {
	color: #999 !important;
}


code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }

/**
 * Undelete Class
 *
 * This class provides undelete functionality for posts and comments, and
 * provides a trashcan interface for restoring items.
 *
 * @todo Document methods
 * @todo Provide an undo popup link like in gmail.
 **/

class Undelete extends Plugin
{
	/**
	 * function action_plugin_activation
	 * adds the "deleted" status type to the poststatus table
	 * when this plugin is activated.
	**/
	public function action_plugin_activation( $file )
	{
		if ( realpath( $file ) == __FILE__ ) {
			Post::add_new_status( 'deleted', true );
			Options::set( 'undelete__style', '#primarycontent .deleted { background-color: #933; text-decoration: line-through; }' );
		}
	}

	/**
	 * This function is executed when the filter "before_post_delete" is
	 * called just before a post is to be deleted.
	 * This filter should return a boolean value to indicate whether
	 * the post should be deleted or not.
	 * @param Boolean Whether to delete the post or not
	 * @param Post The post object to potentially delete
	 * @return Boolean Whether to delete the post or not
	 **/
	function filter_post_delete_allow( $result, $post )
	{
		// all we need to do is set the post status to "deleted"
		// and then return false.  The Post::delete() method will
		// see the false return value, and simply return, leaving
		// the post in the database.
		// If the user_id is invalid (as would happen when the User
		// has just been deleted), return true.  Post::delete()
		// will then delete the (now user_id-less) post.
		if ( $post->status != Post::status( 'deleted' ) && ACL::access_check( $post->get_access(), 'delete' ) && User::get_by_id( $post->user_id ) ) {
			$post->info->prior_status = $post->status;
			$post->status = Post::status( 'deleted' );
			$post->update();
			return false;
		}
		else {
			return true;
		}
	}

	public function filter_post_actions($actions, $post)
	{
		if ( $post->status == Post::status('deleted') && ACL::access_check( $post->get_access(), 'delete' ) ) {
			$actions['remove']['label']= _t('Delete forever');
			$actions['remove']['title']= _t('Permanently delete this post');
			$remove = array_pop($actions);
			$restore = array(
				'url' => 'javascript:unDelete.post('. $post->id . ');',
				'title' => _t('Restore this item'),
				'label' => _t('Restore')
			);
			array_push($actions, $restore, $remove);
		}
		return $actions;
	}

	public function filter_posts_manage_actions( $actions )
	{
		// get all the post types
		$require_any = array( 'own_posts' => 'delete' );
		$types = Post::list_active_post_types();
		foreach ($types as $key => $value ) {
			$require_any['post_' . $key] = 'delete';
		}

		if ( User::identify()->can_any( $require_any ) ) {
			$actions[] = array( 'action' => 'itemManage.update(\'restore\');return false;', 'title' => _t( 'Restore Selected Entries' ), 'label' => _t( 'Restore Selected' ) );
		}
		return $actions;
	}

	public function filter_admin_entries_action( $status_msg, $action, $posts )
	{
		$num = 0;

		switch ( $action ) {
		case 'restore':
			foreach( $posts as $post ) {
				$result = $this->undelete_post( $post->id );
				if ( $result ) {
					$num++;
				}
			}
			if ( $num == count( $posts ) ) {
				$status_msg = sprintf( _n('Restored %d post', 'Restored %d posts', $num ), $num );
			}
			else {
				$status_msg = _t( 'You did not have permission to restore some entries.' );
			}
			break;
		}

		return $status_msg;
	}

	/**
	 * function undelete_post
	 * This function reverts a post's status from 'deleted' to whatever
	 * it previously was.
	**/
	private function undelete_post( $post_id )
	{
		$post = Post::get( array( 'id' => $post_id, 'status' => Post::status('any') ) );
		if ( $post->status == Post::status('deleted') && ACL::access_check( $post->get_access(), 'delete' ) ) {
			$post->status = $post->info->prior_status ? $post->info->prior_status : Post::status( 'draft' );
			unset( $post->info->prior_status );
			$post->update();

			EventLog::log(
				_t('Post %1$s (%2$s) restored.', array( $post->id, $post->slug ) ),
				'info', 'content', 'habari'
			);
			//scheduled post
			if ( $post->status == Post::status( 'scheduled' ) ) {
				Posts::update_scheduled_posts_cronjob();
			}
			return true;
		}
		else {
			return false;
		}
	}

	public function action_auth_ajax_undelete()
	{
		if ( $this->undelete_post($_POST['id']) ) {
			_e('Restored post %d', array($_POST['id']) );
		}
		else {
			_e( 'Could not restore post %d', array($_POST['id']) );
		}
	}

	private function get_perms()
	{
		$type_perms = array();
		$types = Post::list_active_post_types();
		foreach( $types as $key => $value ) {
			$perm = array( 'post_' . $key => ACL::get_bitmask( 'delete' ) );
			$types_perms = array_merge( $type_perms, $perm );
		}
		$perms = array( 'own_posts' => ACL::get_bitmask( 'delete' ), 'post_any' => ACL::get_bitmask( 'delete' ) );
		$perms = array_merge( $perms, $type_perms );
		return $perms;
	}

	public function filter_plugin_config( $actions, $plugin_id )
	{
		if ( $plugin_id == $this->plugin_id() ) {
			$actions[]= _t( 'Configure' );
			if ( User::identify()->can_any( $this->get_perms() ) ) {
				$actions[]= _t( 'Empty Trash' );
			}
		}
		return $actions;
	}

	public function action_plugin_ui( $plugin_id, $action )
	{
		if ( $plugin_id == $this->plugin_id() ) {
			switch ( $action ) {
				case _t( 'Configure' ):
					$ui = new FormUI( strtolower( get_class( $this ) ) );
					$ui->append( 'textarea', 'style', 'option:undelete__style', _t( 'Style declaration for deleted content:' ) );
					$ui->append( 'submit', 'save', _t( 'Save' ) );
					$ui->on_success( array( $this, 'updated_config' ) );
					$ui->out();
					break;

				case _t( 'Empty Trash' ):
					$ui = new FormUI( strtolower( get_class( $this ) ) );
					$ui->append( 'static', 'explanation', _t('Pressing this button will permanently delete all posts from the virtual trash can. You cannot undo this.') );
					$ui->append( 'submit', 'delete', _t( 'Delete All' ) );
					$ui->on_success( array( $this, 'deleted_all' ) );
					$ui->out();
					break;
			}

		}
	}

	public function updated_config( $ui )
	{
		$ui->save();
		return false;
	}

	public function deleted_all( $ui )
	{
		$count = self::delete_all();

		Session::notice( _t( 'Permanently deleted %d posts', array( $count ) ) );
		return false;
	}

	// This method will permanently delete all posts stored in the trash can
	private function delete_all()
	{
		$posts = Posts::get(array('status' => Post::status('deleted'), 'nolimit' => true));

		$count = 0;

		foreach($posts as $post) {
			if ( ACL::access_check( $post->get_access(), 'delete' ) ) {
				$post->delete();
				$count++;
			}
		}

		return $count;
	}

	/**
	 * this method will inject some CSS into the <head>
	 * of the public facing theme so that deleted posts
	 * will show up differently
	 */
	public function action_template_header()
	{
		// only show the style to logged in users
		if ( User::identify()->loggedin ) {
			echo '<style type="text/css">';
			Options::out( 'undelete__style' );
			echo '</style>';
		}
	}

	public function action_admin_header( $theme )
	{
		if ( $theme->page == 'posts' ) {
			Stack::add( 'admin_stylesheet', array($this->get_url() . '/undelete.css', 'screen') );
			$url = URL::get( 'auth_ajax', array('context' => 'undelete') );
			$script = <<<JS
var unDelete = {
	post : function(id) {
		spinner.start();
		\$.post(
			'$url',
			{'id':id},
			function( result ) {
				spinner.stop();
				human_msg.display_msg( result );
				if ( $('.timeline').length ) {
					var loupeInfo = timeline.getLoupeInfo();
					itemManage.fetch( 0, loupeInfo.limit, true );
					timeline.updateLoupeInfo();
				}
				else {
					itemManage.fetch( 0, 20, false );
				}
			}
		);
	}
}
JS;
			Stack::add( 'admin_header_javascript', $script, 'undelete', 'admin' );
		}
	}
}

?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.habariproject.org/pluggable/0.8">
	<name>Undelete</name>
	<license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>

	<author url="http://habariproject.org/">The Habari Community</author>

	<version>1.0</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Stores deleted items in a virtual trashcan to support undelete functionality.]]></description>

	<copyright>2009</copyright>
</pluggable>


code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }
/*
	#######################################################
	#  Viddler API / PHP Wrapper
	#  By: Colin Devroe | cdevroe@viddler.com
	#
	#  Docs: http://wiki.developers.viddler.com/index.php/Phpviddler
	#
	#  License(s): Licensed under:
	#  MIT (MIT-LICENSE.txt)
    #
    #  Third-party code:
    #  XML Library by Keith Devens
	#  xmlparser.php
	#
	#  Version 0.3
	########################################################
*/

class Phpviddler {

	var $apiKey = '01172c643b9743485249534a4441564953151';
	var $viddlerREST = 'http://api.viddler.com/rest/v1/'; // REST URL Version 1.0
	var $xml; // Raw XML returned by API
	var $response; // Array of results
	var $parser = false; // Use the included XML parser? Default: true.

	// Optional
	var $uploaddir = false; // Used for temporary upload directory.
/*##########  User functions ########### */
	/* viddler.users.auth
	/ accepts: $userInfo = array
	/ returns: array - sessionid (if not asking for record_token)
	/ returns: array - sessionid and recordtoken (if asking for record token)
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.users.auth
	*/
	function user_authenticate($userInfo) {
		$args = '';
		$args = $this->buildArguments($userInfo); // Arguments as string
		//var_dump( $args ); exit;
		$xml = $this->sendRequest('viddler.users.auth',$args); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				// Return record token too? Or just session id?
				if (!$args['record_token']) {
					return $response['auth']['sessionid'];
				} else {
					return $response['auth'];
				}
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* viddler.users.getProfile
	/ accepts: $username = string
	/ returns: array - user's profile
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.users.getProfile
	*/
	function user_profile($username) {

		$xml = $this->sendRequest('viddler.users.getProfile','user='.$username); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['user'];
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* viddler.users.setOptions
	/ accepts: $options = array
	/ returns: string - number of options updated
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.users.setOptions
	*/
	function user_setoptions($options) {
		$args = '';
		$args = $this->buildArguments($options); // Arguments as string

		$xml = $this->sendRequest('viddler.users.setOptions',$args); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['user'];
			}
		} else {
			return $xml;
		}
	return false;
	}

/*##########  Video functions ########### */

	/* viddler.videos.upload
	/ requires: POST HTTP Method
	/ accepts: $videoInfo = array
	/ returns: array - info about video uploaded
	doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.upload
	*/
	function video_upload($videoInfo) {
		$args = '';
		$args = $this->buildArguments($videoInfo,'array'); // Arguments as array

		$xml = $this->sendRequest('viddler.videos.upload',$args); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			// Deletes the uploaded file from the server
			//if ($this->uploaddir) {
			//	unlink($this->uploaddir.$_FILES['file']['name']);
			//}

			if ($response['error']) {
				return $response;
			} else {
				return $response['video'];
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* viddler.videos.getRecordToken
	/ accepts: $sessionid = string
	/ returns: string - token needed for recording with webcam
	}
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.getRecordToken
	/ Instructions for use: http://wiki.developers.viddler.com/index.php/Record_With_Webcam_API
	*/
	function video_getrecordtoken($sessionid) {

		$xml = $this->sendRequest('viddler.videos.getRecordToken','sessionid='.$sessionid); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['record_token'];
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* Not a method...
	/ accepts: $token = string
	/ returns: string - html for recorder embed with token included.
	}
	/ doc: None.
	*/
	function video_getrecordembed($token) {

		if (!$token) return false;

		$html = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="449" height="400" id="viddler_recorder" align="middle">
			<param name="allowScriptAccess" value="always" />
			<param name="allowNetworking" value="all" />
			<param name="movie" value="http://www.viddler.com/flash/recorder.swf" />
			<param name="quality" value="high" />
			<param name="scale" value="noScale">
			<param name="bgcolor" value="#000000" />
			<param name="flashvars" value="fake=1&recordToken='.$token.'" />
			<embed src="http://www.viddler.com/flash/recorder.swf" quality="high" scale="noScale" bgcolor="#000000" allowScriptAccess="always" allowNetworking="all" width="449" height="400" name="viddler_recorder" flashvars="fake=1&recordToken='.$token.'" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
		</object>';

		return $html;
	}

	/* viddler.videos.getStatus
	/ accepts: $videoID = string
	/ returns: array - info about video's upload status
	/ Method specific responses {
	    1: Waiting in encode queue (failed=0)
		2: Encoding (failed=0)
		3: Encoding process failed (failed=1)
		4: Ready (failed=0)
		5: Deleted (failed=0)
		6: Wrong priviledges (failed=0)
	}
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.getStatus
	*/
	function video_status($videoID) {

		$xml = $this->sendRequest('video.status','video_id='.$videoID); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['video_status'];
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* viddler.videos.getDetails
	/ accepts: $sessionid and $videoID = string
	/ returns: array - info about video's upload status
	/ Method specific responses {
	    1: Waiting in encode queue (failed=0)
		2: Encoding (failed=0)
		3: Encoding process failed (failed=1)
		4: Ready (failed=0)
		5: Deleted (failed=0)
		6: Wrong priviledges (failed=0)
	}
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.getDetails
	*/
	function video_details($sessionid,$videoID) {

		$xml = $this->sendRequest('viddler.videos.getDetails','sessionid='.$sessionid.'&video_id='.$videoID); // Get XML response

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['video'];
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* viddler.videos.getByUser
	/ accepts: $user = string, $page = string, $per_page = string
	/ returns: array - information about the videos matching this user
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.getByUser
	*/
	function videos_listbyuser($user,$page =1,$per_page =5) {

		 // Get XML response
		$xml = $this->sendRequest('viddler.videos.getByUser','user='.$user.'&page='.$page.'&per_page='.$per_page);

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['video_list'];
			}
		} else {
			return $xml;
		}
	}

	/* viddler.videos.getByTag
	/ accepts: $tag = string, $page = string, $per_page = string
	/ returns: array - information about videos matching this tag
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.getByTag
	*/
	function videos_listbytag($tag,$page =1,$per_page =5) {

		 // Get XML response
		$xml = $this->sendRequest('viddler.videos.getByTag','tag='.$tag.'&page='.$page.'&per_page='.$per_page);

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['video_list'];
			}
		} else {
			return $xml;
		}
	return false;
	}

	/* viddler.videos.getFeatured
	/ accepts: nothing.
	/ returns: array - information about videos that are featured
	/ doc: http://wiki.developers.viddler.com/index.php/Viddler.videos.getFeatured
	*/
	function videos_listfeatured() {

		 // Get XML response
		$xml = $this->sendRequest('viddler.videos.getFeatured','');

		// Use included parser?
		if ($this->parser) {
			// Several steps
			// 1. Convert XML into an array
			// 2. Check for errors
			//    i. Return array of error number, short, and long description
			// 3. Return multidimensional array of response.
			$response = $this->checkErrors(XML_unserialize($xml));

			if ($response['error']) {
				return $response;
			} else {
				return $response['video_list'];
			}
		} else {
			return $xml;
		}
	return false;
	}

/*##########  Misc. Functions ########### */

	// Error checking
	function checkErrors($response) {

		if ($response['error']) {

			switch ($response['error']) {

				case '1':
					$errorshort = 'An internal error has occurred.';
					break;
				case '2':
					$errorshort = 'Bad argument format.';
					break;
				case '3':
					$errorshort = 'Unknown argument specified.';
					break;
				case '4':
					$errorshort = 'Missing required argument for this method.';
					break;
				case '5':
					$errorshort = 'No method specified.';
					break;
				case '6':
					$errorshort = 'Unknown method specified.';
					break;
				case '7':
					$errorshort = 'API key missing.';
					break;
				case '8':
					$errorshort = 'Invalid or unknown API key specified.';
					break;
				case '9':
					$errorshort = 'Invalid or expired sessionid.';
					break;
				case '10':
					$errorshort = 'HTTP method used not allowed on this method.';
					break;
				case '100':
					$errorshort = 'Video could not be found.';
					break;
				case '101':
					$errorshort = 'Username not found.';
					break;
				case '102':
					$errorshort = 'This account has been suspended.';
					break;
				case '103':
					$errorshort = 'Password incorrect for this username.';
					break;
				case '104':
					$errorshort = 'Terms of Service not accepted by user.';
					break;
				case '105':
					$errorshort = 'Username already in use.';
					break;
				case '106':
					$errorshort = 'Email address already in use.';
					break;
				case '200':
					$errorshort = 'The file is too large. Please limit to 500Mb.';
					break;
				default:
					$errorshort = 'An unknown error has occured.';
					break;
			} // End switch

			$error = array('error' => array('number'=>$response['error'],'shortdesc'=>$errorshort));

			return $error;
		}

		return $response;

	}

	/**
	 * Build arguments to use for a request to the API
	 *
	 * @param array $p An associative array of parameters
	 * @param string $return_type Optional return type of parameters ['array'|'string']
	 * @return mixed The filtered arguments in the format requested
	 */
	public function buildArguments( $p, $return_type = 'string') {
		$args = array();
		foreach ($p as $key => $value) {

			// Skip method request name and submit button
			switch ($key) {
				case 'method':
				case 'submit':
				case 'MAX_FILE_SIZE':
					continue;
			}

			$args[$key] = $value;

		} // end foreach

		switch ($return_type) {
			// If array assume uploading
			case 'array':
				$uploadfile = $this->uploaddir . basename($_FILES['file']['name']);

				if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
					$args['file'] = "@{$uploadfile}";
				}
				break;

			// If String, chop off last ampersand
			case 'string':
			default:
				$args = http_build_query($args, null, '&');
				break;
		}

		return $args;
	}

	// Send REST request
	function sendRequest($method,$args) {

		// Build Request URL
		$reqURL = $this->viddlerREST.'?api_key='.$this->apiKey.'&method='.$method;
		if ($method != 'viddler.videos.upload') $reqURL .= '&'.$args;

		// Send request via CURL
		$curl_handle = curl_init();
		curl_setopt ($curl_handle, CURLOPT_URL, $reqURL); // Request URL
		curl_setopt ($curl_handle, CURLOPT_RETURNTRANSFER, 1); // Return as string
		curl_setopt ($curl_handle, CURLOPT_CONNECTTIMEOUT, 1); // Fail if timeout

		// If sending a file, change to POST instead of GET
		if ($method == 'viddler.videos.upload') {
			curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $args);
		}

		$response = curl_exec($curl_handle); // Call!

		if (!$response)	$response = curl_error($curl_handle);

		curl_close($curl_handle); // Close connection

		// When CURL doesn't work, use this.
		//$response = file_get_contents($reqURL);

		// Return XML response as string
		if (!$response) {
			return false; // 'There is no response. CURL might not by allowed?
'.$reqURL.'

';
		}

		return $response;

	} // End sentReq();

}

/**
* Viddler Silo
*/

class ViddlerSilo extends Plugin implements MediaSilo
{
	const SILO_NAME = 'Viddler';

	protected $cache = array();

	/*
	// add a rewrite rule for our auto-generated video page.
	public function filter_rewrite_rules( $rules )	{
		$rules[] = new RewriteRule(array(
			'name' => 'viddlerlist',
			'parse_regex' => '/^sillytv[\/]{0,1}$/i',
			'build_str' => 'viddlerlist',
			'handler' => 'ViddlerAdminHandler',
			'action' => 'display_viddlerlist',
			'priority' => 7,
			'is_active' => 1,
		));

		return $rules;
	}
	*/

	/**
	* Initialize some internal values when plugin initializes
	*/
	public function action_init()
	{
		// add some js to the admin header
		Stack::add( 'admin_header_javascript',  $this->get_url(true) . 'vidfuncs.js', 'viddlerjs', array('media','jquery') );
		$this->viddler = new Phpviddler();
	}

	/**
	* Return basic information about this silo
	*     name- The name of the silo, used as the root directory for media in this silo
	*/
	public function silo_info()
	{
		if ($this->is_auth()) {
			return array('name' => self::SILO_NAME, 'icon' => URL::get_from_filesystem(__FILE__) . '/icon.png');
		}
		else {
			return array();
		}
	}

	/**
	* Return directory contents for the silo path
	*
	* @param string $path The path to retrieve the contents of
	* @return array An array of MediaAssets describing the contents of the directory
	*/
	public function silo_dir($path)
	{
		$user = User::identify()->info->viddler__username;

		if (Cache::has('viddler:videos:' . $user)) {
			$pre = Cache::get('viddler:videos:' . $user);
		}
		else {
			$pre = $this->viddler->videos_listbyuser( $user, '', 20 );
			Cache::set('viddler:videos:' . $user, $pre);
		}
		$xml = new SimpleXMLElement( $pre );
		$results = array();
		foreach( $xml->video as $video ) {

			$props = array();
			foreach($video->children() as $name => $value) {
				$props[$name] = (string)$value;
			}
			$props['filetype'] = 'viddler';

			$asset = new MediaAsset(
				self::SILO_NAME . '/' . $path . ($path == '' ? '' : '/') . $video->id,
				false,
				$props
			);
			$results[] = $asset;

			//echo '
thumbnail_url . '" width="150px">
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="437" height="370" id="viddler_chrisjdavis_' . $video->id . '"><param name="movie" value="http://www.viddler.com/player/' . $video->id . '/" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><embed src="http://www.viddler.com/player/' . $video->id . '/" width="437" height="370" type="application/x-shockwave-flash" allowScriptAccess="always" allowFullScreen="true" name="viddler_chrisjdavis" ></embed></object>

';
		}
		return $results;
	}

	/**
	* Get the file from the specified path
	*
	* @param string $path The path of the file to retrieve
	* @param array $qualities Qualities that specify the version of the file to retrieve.
	* @return MediaAsset The requested asset
	*/
	public function silo_get($path, $qualities = null)
	{
	}

	/**
	* Get the direct URL of the file of the specified path
	*
	* @param string $path The path of the file to retrieve
	* @param array $qualities Qualities that specify the version of the file to retrieve.
	* @return string The requested url
	*/
	public function silo_url( $path, $qualities = null )
	{
		$id = basename($path);
		$video = $this->cache[$id];
		if (isset($qualities['size']) && $qualities['size']=='thumbnail') {
			$url = $video->thumbnail_url;
		}
		else {
			$url = $video->url;
		}
		return $url;
	}

	/**
	* Create a new asset instance for the specified path
	*
	* @param string $path The path of the new file to create
	* @return MediaAsset The requested asset
	*/
	public function silo_new($path)
	{
	}

	/**
	* Store the specified media at the specified path
	*
	* @param string $path The path of the file to retrieve
	* @param MediaAsset $ The asset to store
	*/
	public function silo_put($path, $filedata)
	{
	}

	/**
	* Delete the file at the specified path
	*
	* @param string $path The path of the file to retrieve
	*/
	public function silo_delete($path)
	{
	}

	/**
	* Retrieve a set of highlights from this silo
	* This would include things like recently uploaded assets, or top downloads
	*
	* @return array An array of MediaAssets to highlihgt from this silo
	*/
	public function silo_highlights()
	{
	}

	public function controls()
	{
		echo '

Upload</li
Record

Viddler Stream

';
	}

	public function silo_upload_form()
	{
		echo '<form name="viddler_upload"></form>';
	}

	public function upload_form()
	{
	?>

Upload a Video

		<form name="viddler_upload">

Video

<input type="file" name="viddler_file">

Video Title

<input type="text" name="viddler_title">

Video Tags

<input type="text" name="viddler_tags">

Video Description

<textarea name="viddler_desc"></textarea>

<input type="submit" value="Upload Video" id="execute_viddler">

		</form>

Record a Video

<?php
			$user = User::identify()->info->viddler__username;
			$pass = User::identify()->info->viddler__password;
			$auth = $this->viddler->user_authenticate( array( 'user' => $user, 'password' => $pass, 'get_record_token' => 1 ) );
			$sid = new SimpleXMLElement( $auth );
			$mt = $test->video_getrecordtoken( $sid->sessionid );
			$token = new SimpleXMLElement( $mt );
			$embed = $test->video_getrecordembed( $token );
			echo $embed;
			?>

	<?php
	}

	/**
	* Retrieve the permissions for the current user to access the specified path
	*
	* @param string $path The path to retrieve permissions for
	* @return array An array of permissions constants (MediaSilo::PERM_READ, MediaSilo::PERM_WRITE)
	*/
	public function silo_permissions($path)
	{
	}

	/**
	* Add actions to the plugin page for this plugin
	* The authorization should probably be done per-user.
	*
	* @param array $actions An array of actions that apply to this plugin
	* @param string $plugin_id The string id of a plugin, generated by the system
	* @return array The array of actions to attach to the specified $plugin_id
	*/
	public function filter_plugin_config( $actions, $plugin_id ) {
		if ( $plugin_id == $this->plugin_id() ) {
			$viddler_ok = $this->is_auth();
			if ( $viddler_ok ) {
				$actions[]= 'Log Out';
			} else {
				$actions[]= 'Log In';
			}
		}
		return $actions;
	}

	/**
	* Respond to the user selecting an action on the plugin page
	*
	* @param string $plugin_id The string id of the acted-upon plugin
	* @param string $action The action string supplied via the filter_plugin_config hook
	*/
	public function action_plugin_ui( $plugin_id, $action ) {
		switch ( $action ) {
			case 'Log In':
				$ui = new FormUI( strtolower( get_class( $this ) ) );
				$ui->append('text', 'username', 'user:viddler__username', 'Viddler Username:');
				$ui->append('password', 'password', 'user:viddler__password', 'Viddler Password:');
				$ui->append('submit', 'submit', 'Log In');
				$ui->on_success( array( $this, 'updated_config' ) );
				$ui->out();

/*
				if ( $this->is_auth() ) {
					$deauth_url= URL::get('admin', array('page' => 'plugins', 'configure' => $this->plugin_id(), 'action' => 'Log Out')) . '#plugin_options';
					echo "
You have successfully logged into Vidder via Habari.
";
					echo "
Do you want to log out?
";
				}
				else {
					$username = $ui->username->value;
					$password = $ui->password->value;
					if ($username != '' && $password != '') {
						$auth= $this->viddler->user_authenticate( array( 'user' => $username, 'password' => $password ) );

						$xml= new SimpleXMLElement( $auth );
						User::identify()->info->viddler__token = (string) $xml->session_id;
					}
				}
*/
				break;
			case 'Log Out':
				unset(User::identify()->info->viddler__token);
				$reauth_url = URL::get('admin', array('page' => 'plugins', 'configure' => $this->plugin_id(), 'action' => 'Log In')) . '#plugin_options';
				echo '
The Viddler Silo Plugin session has been logged out.
';
				echo "
Do you want to log in again?
";
				break;
		}
	}

	/**
   * Returns true if plugin config form values defined in action_plugin_ui should be stored in options by Habari
   * @return boolean True if options should be stored
   **/
  public function updated_config($ui)
  {
		$username = $ui->username->value;
		$password = $ui->password->value;
		if ($username != '' && $password != '') {
			$auth = $this->viddler->user_authenticate( array( 'user' => $username, 'password' => $password ) );

			$xml = new SimpleXMLElement( $auth );
			$token = (string) $xml->sessionid;
			if ($token == '') {
		  	$ui->set_option('success_message', _t('The username and password you supplied failed to log in to Viddler.'));
			}
			else {
		  	$ui->save();
				User::identify()->info->viddler__token = $token;
				User::identify()->info->commit();
		  	return  '
' . _t('You have logged in to Viddler.') . '
';
			}
		}

    return false;
  }

	private function is_auth()
	{
		$token = User::identify()->info->viddler__token;
		if ( $token != '' ) {
			return true;
		} else {
			return false;
		}
	}

	public function action_admin_header( $theme )
	{
		if (Controller::get_var('page') == 'publish') {
			$header = <<< HEADER
habari.media.output.viddler = {display: function(index, fileobj) {
	habari.editor.insertSelection( ''+
		'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="437" height="370" id="viddler_' + fileobj.basename + '">' +
		'<param name="movie" value="http://www.viddler.com/player/' + fileobj.basename + '/" />' +
		'<param name="allowScriptAccess" value="always" />' +
		'<param name="allowFullScreen" value="true" />' +
		'<embed src="http://www.viddler.com/player/' + fileobj.basename + '/" width="437" height="370" type="application/x-shockwave-flash" allowScriptAccess="always" allowFullScreen="true" ></embed>' +
		'</object>'
	);
}}
HEADER;
		Stack::add( 'admin_header_javascript',  $header, 'viddlerinline', array('viddlerjs', 'media','jquery') );

		}
	}

	/**
	 * Produce a link for the media control bar that causes a specific panel to be displayed
	 *
	 * @param string $path The path to pass
	 * @param string $path The panel to display
	 * @param string $title The text to use for the link in the control bar
	 * @return string The link to create
	 */
	public function link_panel( $path, $panel, $title )
	{
		return '' . $title . '';
	}

	/**
	 * Provide controls for the media control bar
	 *
	 * @param array $controls Incoming controls from other plugins
	 * @param MediaSilo $silo An instance of a MediaSilo
	 * @param string $path The path to get controls for
	 * @param string $panelname The name of the requested panel, if none then emptystring
	 * @return array The altered $controls array with new (or removed) controls
	 *
	 * @todo This should really use FormUI, but FormUI needs a way to submit forms via ajax
	 */
	public function filter_media_controls( $controls, $silo, $path, $panelname )
	{
		$class = __CLASS__;
		if ($silo instanceof $class) {
			if (User::identify()->can('upload_viddler')) {
				$controls[] = $this->link_panel(self::SILO_NAME . '/' . $path, 'upload', 'Upload');
				$controls[] = $this->link_panel(self::SILO_NAME . '/' . $path, 'record', 'Record');
			}
		}
		return $controls;
	}

	/**
	 * Provide requested media panels for this plugin
	 *
	 * @param string $panel The HTML content of the panel to be output in the media bar
	 * @param MediaSilo $silo The silo for which the panel was requested
	 * @param string $path The path within the silo (silo root omitted) for which the panel was requested
	 * @param string $panelname The name of the requested panel
	 * @return string The modified $panel to contain the HTML output for the requested panel
	 *
	 * @todo Move the uploaded file from the temporary location to the location indicated by the path field.
	 */
	public function filter_media_panels( $panel, $silo, $path, $panelname)
	{
		$class = __CLASS__;
		if ($silo instanceof $class) {
			switch ($panelname) {
				case 'record':
					$user = User::identify()->info->viddler__username;
					$pass = User::identify()->info->viddler__password;
					$auth = $this->viddler->user_authenticate( array( 'user' => $user, 'password' => $pass, 'get_record_token' => 1, 'record_token' => 1 ) );
					$sid = new SimpleXMLElement( $auth );
					$rt = $sid->record_token;
					$panel .= "
";
					$panel .= $this->viddler->video_getrecordembed( $rt );
					$panel .= '
';
					break;
				case 'upload':
					if (isset($_FILES['file'])) {
						$size = Utils::human_size($_FILES['file']['size']);
						$panel .= "

File Uploaded: {$_FILES['file']['name']} ($size)
";

						$path = self::SILO_NAME . '/' . preg_replace('%\.{2,}%', '.', $path). '/' . $_FILES['file']['name'];
						$asset = new MediaAsset($path, false);
						$asset->upload($_FILES['file']);

						if ($asset->put()) {
							$panel .= '
File added successfully.
';
						}
						else {
							$panel .= '
File could not be added to the silo.
';
						}

						$panel .= '
Browse the current silo path.

';
					}
					else {

						$fullpath = self::SILO_NAME . '/' . $path;
						$form_action = URL::get('admin_ajax', array('context' => 'media_panel'));
						$panel .= <<< UPLOAD_FORM
<form enctype="multipart/form-data" method="post" id="simple_upload" target="simple_upload_frame" action="{$form_action}" class="span-10" style="margin:0px auto;text-align: center">

Upload to: /{$path}

<input type="file" name="file"><input type="submit" name="upload" value="Upload">
	<input type="hidden" name="path" value="{$fullpath}">
	<input type="hidden" name="panel" value="{$panelname}">

</form>

UPLOAD_FORM;

				}
			}
		}
		return $panel;
	}

}

/*
// This is an auto-generated video page.  There is a rewrite rules array in the plugin
// above that needs to be uncommented as well.  Not sure if we actually want this in
// the plugin, which is why it is commented out.

class ViddlerAdminHandler extends ActionHandler
{
	private $vids;
	private $theme= null;

	public function __construct() {
		$this->theme= Themes::create();
	}

	public function act_display_viddlerlist() {
		$this->load_vids();
		$this->theme->assign( 'vids', $this->vid );
		$this->theme->display( 'vids' );
	}

	public function load_vids() {
		$user= Options::get( 'viddlersilo:username_' . User::identify()->id );
		$viddler= new Phpviddler();
		$pre= $viddler->videos_listbyuser( $user, '', 20, 'view_count' );
		$xml= new SimpleXMLElement( $pre );
			foreach( $xml->video as $video ) {
				$this->vid[]= $video;
			}
		}
}
*/
?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.habariproject.org/pluggable/0.8">
	<name>Viddler Media Silo</name>
	<license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>

	<author url="http://habariproject.org/">The Habari Community</author>

	<version>1.0</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Implements basic Viddler integration.]]></description>

	<copyright>2009</copyright>
</pluggable>


code:
$(document).ready(function() {
	$('#vupload').click(function() {
		$('.media').hide();
		$('#viddlerrecord').hide();
		$('#viddlerform').animate({ "opacity": "toggle" }, "slow");
		return false;
	});

	$('#vrecord').click(function() {
		$('.media').hide();
		$('#viddlerform').hide();
		$('#viddlerrecord').animate({ "opacity": "toggle" }, "slow");
		return false;
	});

	$('#vstream').click(function() {
		$('#viddlerform').hide();
		$('#viddlerrecord').hide();
		$('.media').show();
		return false;
	});
});


code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }

	// define IMPORT_BATCH in your config.php to limit each batch of DB results
	if ( !defined('IMPORT_BATCH') ) {
		define('IMPORT_BATCH', 100);
	}

	class WPImport extends Plugin implements Importer {

		private $supported_importers = array();

		private $default_values = array(
			'db_name' => '',
			'db_host' => 'localhost',
			'db_user' => '',
			'db_pass' => '',
			'db_prefix' => 'wp_',
			'category_import' => true,
			'import_index' => 0,
			'error' => '',
		);

		public function action_init ( ) {

			$this->supported_importers[] = _t('WordPress Database');

		}

		public function filter_import_names ( $import_names ) {

			return array_merge( $import_names, $this->supported_importers );

		}

		public function filter_import_stage ( $stage_output, $import_name, $stage, $step ) {

			// only act on this filter if the import_name is one we handle
			if ( !in_array( $import_name, $this->supported_importers ) ) {
				// it's a filter, always return the output another plugin might have generated
				return $stage_output;
			}

			// the values we'll hand to each stage for processing
			$inputs = array();

			// validate input and figure out which stage we're at
			switch ( $stage ) {

				case 1:

					if ( isset( $_POST['wpimport'] ) ) {

						$inputs = $_POST->filter_keys( array( 'db_name', 'db_user', 'db_host', 'db_pass', 'db_prefix', 'category_import', 'import_index' ) );
						$inputs = $inputs->getArrayCopy();

						// try to connect to the db with the given values
						if ( $this->wp_connect( $inputs['db_host'], $inputs['db_name'], $inputs['db_user'], $inputs['db_pass'] ) ) {

							// we've got good connection info, bump to stage 2
							$stage = 2;

						}
						else {

							// add a warning to the stack
							$inputs['error'] = _t('Could not connect to the WordPress database using the values supplied. Please correct them and try again.');

						}

					}

					break;

			}

			// now dispatch the right stage
			switch ( $stage ) {

				case 1:
				default:
					$output = $this->stage1( $inputs );
					break;

				case 2:
					$output = $this->stage2( $inputs );
					break;

			}

			// return the output for the importer to display
			return $output;

		}

		private function stage1 ( $inputs ) {

			$inputs = array_merge( $this->default_values, $inputs );

			// if there is a error, display it
			if ( $inputs['error'] != '' ) {
				$error = '
' . $inputs['error'] . '
';
			}
			else {
				// blank it out just so we can use the value in output
				$error = '';
			}

			$output = '
' . _t( 'Habari will attempt to import from a WordPress database.') . '
';
			$output .= $error;

			// get the FormUI form
			//$form = $this->get_form( $inputs );

			// append the output of the form
			//$output .= $form->get();

			$output .= '
';
			$output .= 	'' . _t( 'Database Name' ) . '<input type="text" name="db_name" id="db_name" value="' . $inputs['db_name'] . '">';
			$output .= '
';

			$output .= '
';
			$output .= 	'' . _t( 'Database Host' ) . '<input type="text" name="db_host" id="db_host" value="' . $inputs['db_host'] . '">';
			$output .= '
';

			$output .= '
';
			$output .= 	'' . _t( 'Database User' ) . '<input type="text" name="db_user" id="db_user" value="' . $inputs['db_user'] . '">';
			$output .= '
';

			$output .= '
';
			$output .= 	'' . _t( 'Database Password' ) . '<input type="text" name="db_pass" id="db_pass" value="' . $inputs['db_pass'] . '">';
			$output .= '
';

			$output .= '
';
			$output .= 	'' . _t( 'Table Prefix' ) . '<input type="text" name="db_prefix" id="db_prefix" value="' . $inputs['db_prefix'] . '">';
			$output .= '
';

			$output .= '
';
			$output .= 	'' . _t( 'Import Categories as Tags' ) . '<input type="checkbox" name="category_import" id="category_import" value="true" ' . ( ( $inputs['category_import'] == true ) ? 'checked="checked"' : '' ) . '>';
			$output .= '
';

			$output .= '
';
			$output .=	'<input type="submit" class="button" name="wpimport" value="' . _t( 'Import' ) . '">';
			$output .= '
';

			return $output;

		}

		private function stage2 ( $inputs ) {

			// make sure we have all our default values
			$inputs = array_merge( $this->default_values, $inputs );

			// the first thing we import are users, so get that URL to kick off the ajax process
			$ajax_url = URL::get( 'auth_ajax', array( 'context' => 'wp_import_users' ) );

			// the variables we'll hand to the ajax call are all the input values
			$vars = $inputs;

			EventLog::log( _t('Starting import from "%s"', array( $inputs['db_name'] ) ) );

			$output = '
' . _t('Import in Progress') . '
';
			$output .= '
' . _t( 'Starting Import…' ) . '
';
			$output .= $this->get_ajax( $ajax_url, $vars );

			return $output;

		}

		private function get_ajax ( $url, $vars = array() ) {

			// generate the vars we'll use
			$ajax_vars = array();
			foreach ( $vars as $k => $v ) {
				$ajax_vars[] = $k . ': "' . $v . '"';
			}
			$ajax_vars = implode( ',', $ajax_vars );

			$output = <<<WP_IMPORT_AJAX

WP_IMPORT_AJAX;

			return $output;

		}

		private function get_form ( $inputs ) {
			// this isn't used right now because we can't use formui in an importer, there's already a form
			$form = new FormUI('wp_importer');

			$db_name = $form->append( 'text', 'db_name', 'null:null', _t( 'Database Name') );
			$db_name->value = $inputs['db_name'];

			$db_host = $form->append( 'text', 'db_host', 'null:null', _t( 'Database Host' ) );
			$db_host->value = $inputs['db_host'];

			$db_user = $form->append( 'text', 'db_user', 'null:null', _t( 'Database User' ) );
			$db_user->value = $inputs['db_user'];

			$db_pass = $form->append( 'text', 'db_pass', 'null:null', _t( 'Database Password' ) );
			$db_pass->value = $inputs['db_pass'];

			$db_prefix = $form->append( 'text', 'db_prefix', 'null:null', _t( 'Table Prefix' ) );
			$db_prefix->value = $inputs['db_prefix'];

			$category_import = $form->append( 'checkbox', 'category_import', 'null:null', _t( 'Import Categories as Tags' ) );
			$category_import->value = ( $inputs['category_import'] ) ? true : false;

			$submit = $form->append( 'submit', 'submit', _t( 'Import' ) );

			return $form;

		}

		private function wp_connect ( $db_host, $db_name, $db_user, $db_pass ) {

			// build the connection string, since we stupidly have to use it twice
			$connection_string = 'mysql:host=' . $db_host . ';dbname=' . $db_name;

			try {
				$wpdb = DatabaseConnection::ConnectionFactory( $connection_string );
				$wpdb->connect( $connection_string, $db_user, $db_pass );

				// @todo make sure preifx_* tables exist?

				return $wpdb;
			}
			catch ( Exception $e ) {
				// just hide connection errors, it's enough that we errored out
				return false;
			}

		}

		public function action_auth_ajax_wp_import_users ( ) {

			// get the values post'd in
			$inputs = $_POST->filter_keys( array( 'db_name', 'db_host', 'db_user', 'db_pass', 'db_prefix', 'category_import', 'import_index' ) );
			$inputs = $inputs->getArrayCopy();

			// make sure we have all our default values
			$inputs = array_merge( $this->default_values, $inputs );

			// get the wpdb
			$wpdb = $this->wp_connect( $inputs['db_host'], $inputs['db_name'], $inputs['db_user'], $inputs['db_pass'] );

			// if we couldn't connect, error out
			if ( !$wpdb ) {
				EventLog::log( _t( 'Failed to import from "%s"', array( $inputs['db_name'] ) ) );
				Session::error( _t( 'Failed to import from "%s"', array( $inputs['db_name'] ) ) );
				echo '
' . _t( 'Failed to connect using the given database connection details.' ) . '
';
			}

			// we connected just fine, let's get moving!

			// begin a transaction. if we error out at any point, we want to roll back to before import began
			DB::begin_transaction();

			// fetch all the users from the wordpress database
			$wp_users = $wpdb->get_results( 'select id, user_login, user_pass, user_email, user_url, display_name from ' . $inputs['db_prefix'] . 'users' );

			echo '
' . _t( 'Importing Users…' ) . '
';

			foreach ( $wp_users as $wp_user ) {

				// see if a user with this username already exists
				$user = User::get_by_name( $wp_user->user_login );

				if ( $user !== false ) {

					// if the user exists, save their old ID into an info attribute
					$user->info->wp_id = intval( $wp_user->id );

					// and update
					$user->update();

					echo '
' . _t( 'Associated imported user %1$s with existing user %2$s', array( $wp_user->user_login, $user->username ) ) . '
';

					EventLog::log( _t( 'Associated imported user %1$s with existing user %2$s', array( $wp_user->user_login, $user->username ) ) );

				}
				else {

					// no user exists, we need to create one
					try {

						$u = new User();
						$u->username = $wp_user->user_login;
						$u->email = $wp_user->user_email;

						// set their password so the user will be able to login. they're auto-added to the 'authenticated' ACL group
						$u->password = Utils::crypt( $wp_user->user_pass );

						$u->info->wp_id = intval( $wp_user->id );
						$u->info->displayname = $wp_user->display_name;

						if ( $wp_user->user_url != '' ) {
							$u->info->url = $wp_user->user_url;
						}

						// and save it
						$u->insert();

						echo '
' . _t( 'Created new user %1$s. Their old ID was %2$d.', array( $u->username, $wp_user->id ) ) . '
';

						EventLog::log( _t( 'Created new user %1$s. Their old ID was %2$d.', array( $u->username, $wp_user->id ) ) );

					}
					catch ( Exception $e ) {

						// no idea why we might error out, but catch it if we do
						EventLog::log( $e->getMessage, 'err' );

						echo '
' . _t( 'There was an error importing user %s. See the EventLog for the error message. ', array( $wp_user->user_login ) ) . '
';

						echo '
' . _t( 'Rolling back changes…' ) . '
';

						// rollback all changes before we return so the import hasn't changed anything yet
						DB::rollback();

						// and return so they don't get AJAX to send them on to the next step
						return false;

					}

				}

			}

			// if we've finished without an error, commit the import
			DB::commit();

			// get the next ajax url
			$ajax_url = URL::get( 'auth_ajax', array( 'context' => 'wp_import_posts' ) );

			// and spit out ajax to send them to the next step - posts!
			echo $this->get_ajax( $ajax_url, $inputs );

		}

		public function action_auth_ajax_wp_import_posts ( ) {

			// get the values post'd in
			$inputs = $_POST->filter_keys( array( 'db_name', 'db_host', 'db_user', 'db_pass', 'db_prefix', 'category_import', 'import_index' ) );
			$inputs = $inputs->getArrayCopy();

			// make sure we have all our default values
			$inputs = array_merge( $this->default_values, $inputs );

			// get the wpdb
			$wpdb = $this->wp_connect( $inputs['db_host'], $inputs['db_name'], $inputs['db_user'], $inputs['db_pass'] );

			// if we couldn't connect, error out
			if ( !$wpdb ) {
				EventLog::log( _t( 'Failed to import from "%s"', array( $inputs['db_name'] ) ) );
				Session::error( _t( 'Failed to import from "%s"', array( $inputs['db_name'] ) ) );
				echo '
' . _t( 'Failed to connect using the given database connection details.' ) . '
';
			}

			// we connected just fine, let's get moving!

			// begin a transaction. if we error out at any point, we want to roll back to before import began
			DB::begin_transaction();

			// fetch the number of posts from the wordpress database so we can batch things up
			$num_posts = $wpdb->get_value( 'select count(id) from ' . $inputs['db_prefix'] . 'posts' );

			// figure out the LIMIT we're at
			$min = $inputs['import_index'] * IMPORT_BATCH;
			$max = min( $min + IMPORT_BATCH, $num_posts );		// for display only

			echo '
' . _t( 'Importing posts %1$d - %2$d of %3$d.', array( $min, $max, $num_posts ) ) . '
';

			// get all the imported users so we can link old post authors to new post authors
			$users = DB::get_results( 'select user_id, value from {userinfo} where name = :name', array( ':name' => 'wp_id' ) );

			// create an easy user map of old ID -> new ID
			$user_map = array();
			foreach ( $users as $info ) {
				$user_map[ $info->value ] = $info->user_id;
			}

			// get all the post IDs we've imported so far to make sure we don't duplicate any
			$post_map = DB::get_column( 'select value from {postinfo} where name = :name', array( ':name' => 'wp_id' ) );

			// now we're ready to start importing posts
			$posts = $wpdb->get_results( 'select id, post_author, post_date, post_content, post_title, post_status, comment_status, post_name, post_modified, guid, post_type from ' . $inputs['db_prefix'] . 'posts order by id asc limit ' . $min . ', ' . IMPORT_BATCH );

			foreach ( $posts as $post ) {

				// if this post is already in the list we've imported, skip it
				if ( in_array( $post->id, $post_map ) ) {
					continue;
				}

				// set up the big taxonomy sql query
				// if this turns out to be incredibly slow we should refactor it into a big join, but they're all keys so it seems zippy enough for me
				$taxonomy_query = 'select name, slug from ' . $inputs['db_prefix'] . 'terms where term_id in ( select term_id from ' . $inputs['db_prefix'] . 'term_taxonomy where taxonomy = :taxonomy and term_taxonomy_id in ( select term_taxonomy_id from ' . $inputs['db_prefix'] . 'term_relationships where object_id = :object_id ) )';

				// get all the textual tag names for this post
				$tags = $wpdb->get_results( $taxonomy_query, array( ':taxonomy' => 'post_tag', ':object_id' => $post->id ) );

				// should we import categories as tags too?
				if ( $inputs['category_import'] ) {
					// then do the same as above for the category taxonomy
					$categories = $wpdb->get_results( $taxonomy_query, array( ':taxonomy' => 'category', ':object_id' => $post->id ) );
				}

				// create the new post
				$p = new Post( array(
					'title' => MultiByte::convert_encoding( $post->post_title ),
					'content' => MultiByte::convert_encoding( $post->post_content ),
					'user_id' => $user_map[ $post->post_author ],
					'pubdate' => HabariDateTime::date_create( $post->post_date ),
					'updated' => HabariDateTime::date_create( $post->post_modified ),
					'slug' => MultiByte::convert_encoding( $post->post_name ),
				) );

				// figure out the post type
				switch ( $post->post_type ) {

					case 'post':
						$p->content_type = Post::type( 'entry' );
						break;

					case 'page':
						$p->content_type = Post::type( 'page' );
						break;

					default:
						// we're not importing other types - continue 2 to break out of the switch and the loop and continue to the next post
						continue 2;

				}

				// figure out the post status
				switch ( $post->post_status ) {

					case 'publish':
						$p->status = Post::status( 'published' );
						break;

					case 'future':
						$p->status = Post::status( 'scheduled' );
						break;

					case 'pending':		// means pending-review, not pending as in scheduled
					case 'draft':
						$p->status = Post::status( 'draft' );
						break;

					default:
						// Post::status() returns false if it doesn't recognize the status type
						$status = Post::status( $post->post_status );		// store in a temp value because if you try and set ->status to an invalid value the Post class freaks

						if ( $status == false ) {
							// we're not importing statuses we don't recognize - continue 2 to break out of the switch and the loop and continue to the next post
							continue 2;
						}
						else {
							$p->status = $status;
						}

						break;

				}

				// if comments are closed, disable them on the new post
				if ( $post->comment_status == 'closed' ) {
					$p->info->comments_disabled = true;
				}

				// save the old post ID in info
				$p->info->wp_id = $post->id;

				// since we're not using it, save the old GUID too
				$p->info->wp_guid = $post->guid;

				// now that we've got all the pieces in place, save the post
				try {
					$p->insert();

					// now that the post is in the db we can add tags to it

					// first, if we want to import categories as tags, add them to the array
					if ( $inputs['category_import'] ) {
						$tags = array_merge( $tags, $categories );
					}

					// now for the tags!
					foreach ( $tags as $tag ) {

						// try to get the tag by slug, which is the key and therefore the most unique
						$t = Tags::get_by_slug( $tag->slug );

						// if we didn't get back a tag, create a new one
						if ( $t == false ) {
							$t = Tag::create( array(
								'term' => $tag->slug,
								'term_display' => $tag->name
							) );
						}

						// now that we have a tag (one way or the other), associate this post with it
						$t->associate( 'post', $p->id );

					}

				}
				catch ( Exception $e ) {

					EventLog::log( $e->getMessage(), 'err' );

					echo '
' . _t( 'There was an error importing post %s. See the EventLog for the error message.', array( $post->post_title ) );

					echo '
' . _t( 'Rolling back changes…' ) . '
';

					// rollback all changes before we return so the import hasn't changed anything yet
					DB::rollback();

					// and return so they don't get AJAX to send them on to the next step
					return false;

				}

			}

			// if we've finished without an error, commit the import
			DB::commit();

			if ( $max < $num_posts ) {

				// if there are more posts to import

				// get the next ajax url
				$ajax_url = URL::get( 'auth_ajax', array( 'context' => 'wp_import_posts' ) );

				// bump the import index by one so we get a new batch next time
				$inputs['import_index']++;

			}
			else {

				// move on to importing comments

				// get the next ajax url
				$ajax_url = URL::get( 'auth_ajax', array( 'context' => 'wp_import_comments' ) );

				// reset the import index so we start at the first comment
				$inputs['import_index'] = 0;

			}

			// and spit out ajax to send them to the next step - posts!
			echo $this->get_ajax( $ajax_url, $inputs );

		}

		public function action_auth_ajax_wp_import_comments ( ) {

			// get the values post'd in
			$inputs = $_POST->filter_keys( array( 'db_name', 'db_host', 'db_user', 'db_pass', 'db_prefix', 'category_import', 'import_index' ) );
			$inputs = $inputs->getArrayCopy();

			// make sure we have all our default values
			$inputs = array_merge( $this->default_values, $inputs );

			// get the wpdb
			$wpdb = $this->wp_connect( $inputs['db_host'], $inputs['db_name'], $inputs['db_user'], $inputs['db_pass'] );

			// if we couldn't connect, error out
			if ( !$wpdb ) {
				EventLog::log( _t( 'Failed to import from "%s"', array( $inputs['db_name'] ) ) );
				Session::error( _t( 'Failed to import from "%s"', array( $inputs['db_name'] ) ) );
				echo '
' . _t( 'Failed to connect using the given database connection details.' ) . '
';
			}

			// we connected just fine, let's get moving!

			// begin a transaction. if we error out at any point, we want to roll back to before import began
			DB::begin_transaction();

			// fetch the number of comments from the wordpress database so we can batch things up
			$num_comments = $wpdb->get_value( 'select count(comment_id) from ' . $inputs['db_prefix'] . 'comments' );

			// figure out the LIMIT we're at
			$min = $inputs['import_index'] * IMPORT_BATCH;
			$max = min( $min + IMPORT_BATCH, $num_comments );		// for display only

			echo '
' . _t( 'Importing comments %1$d - %2$d of %3$d.', array( $min, $max, $num_comments ) ) . '
';

			// get all the imported users so we can link old comment authors to new comment authors
			$users = DB::get_results( 'select user_id, value from {userinfo} where name = :name', array( ':name' => 'wp_id' ) );

			// create an easy user map of old ID -> new ID
			$user_map = array();
			foreach ( $users as $info ) {
				$user_map[ $info->value ] = $info->user_id;
			}

			// get all the imported posts so we can link old post IDs to new post IDs
			$posts = DB::get_results( 'select post_id, value from {postinfo} where name = :name', array( ':name' => 'wp_id' ) );

			// create an easy post map of old ID -> new ID
			$post_map = array();
			foreach ( $posts as $info ) {
				$post_map[ $info->value ] = $info->post_id;
			}

			// get all the comment IDs we've imported so far to make sure we don't duplicate any
			$comment_map = DB::get_column( 'select value from {commentinfo} where name = :name', array( ':name' => 'wp_id' ) );

			// now we're ready to start importing comments
			$comments = $wpdb->get_results( 'select comment_id, comment_post_id, comment_author, comment_author_email, comment_author_url, comment_author_ip, comment_date, comment_content, comment_karma, comment_approved, comment_agent, comment_type, comment_parent, user_id from ' . $inputs['db_prefix'] . 'comments order by comment_id asc limit ' . $min . ', ' . IMPORT_BATCH );

			foreach ( $comments as $comment ) {

				// if this post is already in the list we've imported, skip it
				if ( in_array( $comment->id, $comment_map ) ) {
					continue;
				}

				// if the post this comment belongs to is not in the list of imported posts, skip it
				if ( !isset( $post_map[ $comment->comment_post_id ] ) ) {
					continue;
				}

				// create the new comment
				$c = new Comment( array(
					'content' => MultiByte::convert_encoding( $comment->comment_content ),
					'name' => MultiByte::convert_encoding( $comment->comment_author ),
					'email' => MultiByte::convert_encoding( $comment->comment_author_email ),
					'url' => MultiByte::convert_encoding( $comment->comment_author_url ),
					'date' => HabariDateTime::date_create( $comment->comment_date ),
					'post_id' => $post_map[ $comment->comment_post_id ],
				) );

				// figure out the comment type
				switch ( $comment->comment_type ) {

					case 'pingback':
						$c->type = Comment::type( 'pingback' );
						break;

					case 'trackback':
						$c->type = Comment::type( 'trackback' );
						break;

					default:
					case 'comment':
						$c->type = Comment::type( 'comment' );
						break;

				}

				// figure out the comment status
				switch ( $comment->comment_approved ) {

					case '1':
						$c->status = Comment::status( 'approved' );
						break;

					case '':
					case '0':
						$c->status = Comment::status( 'unapproved' );
						break;

					case 'spam':
						$c->status = Comment::status( 'spam' );
						break;

					default:
						// Comment::status() returns false if it doesn't recognize the status type
						$status = Comment::status( $comment->comment_status );		// store in a temp value because if you try and set ->status to an invalid value the Comment class freaks

						if ( $status == false ) {
							// we're not importing statuses we don't recognize - continue 2 to break out of the switch and the loop and continue to the next comment
							continue 2;
						}
						else {
							$c->status = $status;
						}

						break;

				}

				// save the old comment ID in info
				$c->info->wp_id = $comment->comment_id;

				// save the old post ID in info
				$c->info->wp_post_id = $comment->comment_post_id;

				// save the old comment karma - but only if it is something
				if ( $comment->comment_karma != '0' ) {
					$c->info->wp_karma = $comment->comment_karma;
				}

				// save the old comment user agent - but only if it is something
				if ( $comment->comment_agent != '' ) {
					$c->info->wp_agent = $comment->comment_agent;
				}

				// now that we've got all the pieces in place, save the comment
				try {
					$c->insert();
				}
				catch ( Exception $e ) {

					EventLog::log( $e->getMessage(), 'err' );

					echo '
' . _t( 'There was an error importing comment ID %d. See the EventLog for the error message.', array( $comment->comment_id ) );

					echo '
' . _t( 'Rolling back changes…' ) . '
';

					// rollback all changes before we return so the import hasn't changed anything yet
					DB::rollback();

					// and return so they don't get AJAX to send them on to the next step
					return false;

				}

			}

			// if we've finished without an error, commit the import
			DB::commit();

			if ( $max < $num_comments ) {

				// if there are more posts to import

				// get the next ajax url
				$ajax_url = URL::get( 'auth_ajax', array( 'context' => 'wp_import_comments' ) );

				// bump the import index by one so we get a new batch next time
				$inputs['import_index']++;

			}
			else {

				// display the completed message!

				EventLog::log( _t( 'Import completed from "%s"', array( $inputs['db_name'] ) ) );
				echo '
' . _t( 'Import is complete.' ) . '
';

				return;

			}

			// and spit out ajax to send them to the next step - posts!
			echo $this->get_ajax( $ajax_url, $inputs );

		}

	}

?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schemas.habariproject.org/pluggable/0.8">
	<name>WordPress Importer</name>
	<license url="http://www.apache.org/licenses/LICENSE-2.0.html">Apache Software License 2.0</license>
	<url>http://habariproject.org</url>
	<author url="http://habariproject.org/">The Habari Community</author>

	<version>2.0</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Import a Wordpress 2.3+ database.]]></description>

</pluggable>


code:
<?php if ( !defined( 'HABARI_PATH' ) ) { die( 'No direct access' ); }
Config::set( 'db_connection', array(
	'connection_string'=>'mysql:host={$db_host};dbname={$db_schema}',
	'username'=>'{$db_user}',
	'password'=>'{$db_pass}',
	'prefix'=>'{$table_prefix}'
));

// Config::set('locale', '{$locale}');
?>


code:
<?php
/**
 * Habari database specific connection class
 *
 * @package Habari
 */

class MySQLConnection extends DatabaseConnection
{
	/**
	 * Extends default connection method. It will be useful in order to
	 * allow accents and other DB-centric global commands.
	 *
	 * @param string $connect_string a PDO connection string
	 * @param string $db_user the database user name
	 * @param string $db_pass the database user password
	 * @return boolean true on success, false on error
	 */
	public function connect ( $connect_string, $db_user, $db_pass )
	{
		// If something went wrong, we don't need to exec the specific commands.
		if ( !parent::connect( $connect_string, $db_user, $db_pass ) ) {
			return false;
		}
		$this->pdo->setAttribute( PDO::ATTR_EMULATE_PREPARES, true );

		// Everything is OK. Let's update the charset!
		if ( !defined('MYSQL_CHAR_SET') ) {
			define('MYSQL_CHAR_SET', 'UTF8');
		}

		// SET NAMES defines character_set_client, character_set_results, and character_set_connection (which implicitly sets collation_connection) and therefore covers everything SET CHARACTER SET does, but uses the character set we tell it to, ignoring what the database is configured to use
		// 	http://dev.mysql.com/doc/refman/5.0/en/charset-connection.html
		$this->exec('SET NAMES ' . MYSQL_CHAR_SET);

		return true;
	}

	/**
	 * Database specific SQL translation function, loosely modelled on the
	 * internationalization _t() function.
	 * Call with a database independent SQL string and it will be translated
	 * to a MySQL specific SQL string.
	 *
	 * @param $sql database independent SQL
	 * @return string translated SQL string
	 * @todo Actually implement this.
	 */
	function sql_t( $sql )
	{
		return $sql;
	}

	/**
	 * automatic diffing function - used for determining required database upgrades
	 * based on Owen Winkler's microwiki upgrade function
	 *
	 * @param queries array of create table and insert statements which constitute a fresh install
	 * @param (optional)  execute should the queries be executed against the database or just simulated. default = true
	 * @param (optional) silent silent running with no messages printed? default = true
	 * @param boolean $doinserts (optional) Execute all insert queries found, default=false
	 * @return  array list of updates made
	 */
	function dbdelta( $queries, $execute = true, $silent = true, $doinserts = false )
	{
		$queries = str_replace('{$prefix}', $this->prefix, $queries);	//Converts {$prefix}table_name to prefix__table_name
		$queries = $this->filter_tables( $queries );	//Converts {table_name} to prefix__table_name

		if ( !is_array($queries) ) {
			$queries = explode( ';', $queries );
			if ( '' == $queries[count($queries) - 1] ) {
				array_pop($queries);
			}
		}

		$cqueries = array();
		$iqueries = array();
		$for_update = array();
		$indices = array();

		foreach ( $queries as $qry ) {
			if ( preg_match("|CREATE TABLE\s+(\w*)|", $qry, $matches) ) {
				$cqueries[strtolower($matches[1])] = $qry;
				$for_update[$matches[1]] = 'Created table '.$matches[1];
			}
			else if ( preg_match("|CREATE DATABASE ([^ ]*)|", $qry, $matches) ) {
				array_unshift($cqueries, $qry);
			}
			else if ( preg_match("|INSERT INTO ([^ ]*)|", $qry, $matches) ) {
				$iqueries[] = $qry;
			}
			else if ( preg_match("|UPDATE ([^ ]*)|", $qry, $matches) ) {
				$iqueries[] = $qry;
			}
			else {
				// Unrecognized query type
			}
		}

		if ( $tables = $this->get_column('SHOW TABLES;') ) {
			foreach ( $tables as $table ) {
				if ( array_key_exists(strtolower($table), $cqueries) ) {
					unset($cfields);
					$cfields = array();
					unset($indices);
					$indices = array();
					preg_match("|\((.*)\)|ms", $cqueries[strtolower($table)], $match2);
					$qryline = trim($match2[1]);

					$flds = explode("\n", $qryline);
					foreach ( $flds as $fld ) {
						preg_match("|^([^ ]*)|", trim($fld), $fvals);
						$fieldname = $fvals[1];
						$validfield = true;
						switch ( strtolower($fieldname) ) {
							case '':
							case 'primary':
							case 'index':
							case 'fulltext':
							case 'unique':
							case 'key':
								$validfield = false;
								$indices[] = trim(trim($fld), ", \n");
								break;
						}
						$fld = trim($fld);
						if ( $validfield ) {
							$cfields[strtolower($fieldname)] = trim($fld, ", \n");
						}
					}
					$tablefields = $this->get_results("DESCRIBE {$table};");
					foreach ( (array)$tablefields as $tablefield ) {
						if ( array_key_exists(strtolower($tablefield->Field), $cfields) ) {
							preg_match("|".$tablefield->Field." ([^ ]*( unsigned)?)|i", $cfields[strtolower($tablefield->Field)], $matches);
							$fieldtype = $matches[1];
							// Use default field sizes
							$field_default_names = array('/(?'.'>\bint\s*)(?!\(.*$)/i','/(?'.'>smallint\s*)(?!\(.*$)/i','/(?'.'>tinyint\s*)(?!\(.*$)/i','/(?'.'>bigint\s*)(?!\(.*$)/i');
							$field_sized_names = array('int(10) ','smallint(5) ','tinyint(3) ','bigint(20) ');
							$fieldtype = preg_replace($field_default_names, $field_sized_names, $fieldtype);
							if ( strtolower($tablefield->Type) != strtolower($fieldtype) ) {
								$cqueries[] = "ALTER TABLE {$table} CHANGE COLUMN {$tablefield->Field} " . $cfields[strtolower($tablefield->Field)];
								$for_update[$table.'.'.$tablefield->Field] = "Changed type of {$table}.{$tablefield->Field} from {$tablefield->Type} to {$fieldtype}";
							}
							if ( preg_match("| DEFAULT ([^ ]*)|i", $cfields[strtolower($tablefield->Field)], $matches) ) {
								$default_value = $matches[1];
								if ( $tablefield->Default != $default_value && !(is_null($tablefield->Default) && strtoupper($default_value) == 'NULL')) {
									$cqueries[] = "ALTER TABLE {$table} ALTER COLUMN {$tablefield->Field} SET DEFAULT {$default_value}";
									$for_update[$table.'.'.$tablefield->Field] = "Changed default value of {$table}.{$tablefield->Field} from {$tablefield->Default} to {$default_value}";
								}
							}
							elseif ( strlen( $tablefield->Default) > 0 ) {
								$cqueries[] = "ALTER TABLE {$table} ALTER COLUMN {$tablefield->Field} DROP DEFAULT";
								$for_update[$table.'.'.$tablefield->Field] = "Dropped default value of {$table}.{$tablefield->Field}";
							}
							unset($cfields[strtolower($tablefield->Field)]);
						}
						else {
							// This field exists in the table, but not in the creation queries?
						}
					}
					foreach ( $cfields as $fieldname => $fielddef ) {
						$cqueries[] = "ALTER TABLE {$table} ADD COLUMN $fielddef";
						$for_update[$table.'.'.$fieldname] = 'Added column '.$table.'.'.$fieldname;
					}
					$tableindices = $this->get_results("SHOW INDEX FROM {$table};");

					if ( $tableindices ) {
						unset($index_ary);
						$index_ary = array();
						foreach ( $tableindices as $tableindex ) {
							$keyname = $tableindex->Key_name;
							$index_ary[$keyname]['columns'][] = array('fieldname' => $tableindex->Column_name, 'subpart' => $tableindex->Sub_part);
							$index_ary[$keyname]['unique'] = ($tableindex->Non_unique == 0)?true:false;
						}
						foreach ( $index_ary as $index_name => $index_data ) {
							$index_string = '';
							if ( $index_name == 'PRIMARY' ) {
								$index_string .= 'PRIMARY ';
							}
							else if ( $index_data['unique'] ) {
								$index_string .= 'UNIQUE ';
							}
							$index_string .= 'KEY ';
							if ( $index_name != 'PRIMARY' ) {
								$index_string .= $index_name;
							}
							$index_columns = '';
							foreach ( $index_data['columns'] as $column_data ) {
								if ( $index_columns != '' ) {
								 	$index_columns .= ',';
								}
								$index_columns .= $column_data['fieldname'];
								if ( $column_data['subpart'] != '' ) {
									$index_columns .= '('.$column_data['subpart'].')';
								}
							}
							$index_string = rtrim($index_string, ' ');
							$index_string .= ' ('.$index_columns.')';
							if ( !(($aindex = array_search($index_string, $indices)) === false) ) {
								unset($indices[$aindex]);
							}
							else {
								preg_match( '|(^.*)\((.*)\)|', $index_string, $matches );
								$tindextype = $matches[1];
								if ( preg_match( '/^KEY|UNIQUE KEY/i', $tindextype ) > 0 ) {
									$cqueries[] = "ALTER TABLE {$table} DROP INDEX {$index_name}";
								}
								else {
									$cqueries[] = "ALTER TABLE {$table} DROP PRIMARY KEY";
								}
							}
						}
					}
					foreach ( $indices as $index ) {
						$cqueries[] = "ALTER TABLE {$table} ADD $index";
						$for_update[$table.'.'.$fieldname] = 'Added index '.$table.' '.$index;
					}
					unset($cqueries[strtolower($table)]);
					unset($for_update[strtolower($table)]);
				}
				else {
				}
			}
		}

		$allqueries = $cqueries;
		if ( $doinserts ) {
			$allqueries = array_merge($allqueries, $iqueries);
		}
		if ( $execute ) {
			foreach ( $allqueries as $query ) {
				if ( !$this->exec($query) ) {
					$this->get_errors();
					return false;
				}
			}
		}

		if ( !$silent ) {
			if ( count($for_update) > 0) {
				echo "
\n";
				foreach ( $for_update as $upgrade ) {
					echo "
{$upgrade}
\n";
				}
				echo "
\n";
			}
			else {
				echo "

" . _t('No Upgrades') . "

";
			}
		}
		return $for_update;
	}

	/**
	 * Run all of the upgrades slated for pre-dbdelta since the last database revision.
	 *
	 * @param integer $old_version The current version of the database that is being upgraded
	 * @return boolean True on success
	 */
	public function upgrade_pre( $old_version, $upgrade_path = '' )
	{
		return parent::upgrade( $old_version, dirname(__FILE__) . '/upgrades/pre');
	}

	/**
	 * Run all of the upgrades slated for post-dbdelta since the last database revision.
	 *
	 * @param integer $old_version The current version of the database that is being upgraded
	 * @return boolean True on success
	 */
	public function upgrade_post( $old_version, $upgrade_path = '' )
	{
		return parent::upgrade( $old_version, dirname(__FILE__) . '/upgrades/post');
	}
}
?>