code:
<?php
class flickrAPI
{
	function __construct()
	{
		$this->key = 'cd0ae46b1332aa2bd52ba3063f0db41c';
		$this->secret = '76cf747f70be9029';
		$this->endpoint = 'http://www.flickr.com/services/rest/?';
		$this->authendpoint = 'http://www.flickr.com/services/auth/?';
		$this->uploadendpoint = 'http://api.flickr.com/services/upload/?';
		$this->conntimeout = 20;
	}

	public function sign($args)
	{
		ksort($args);
		unset($args['photo']);
		$a = '';
		foreach($args as $key => $value){
			$a .= $key . $value;
		}
		return md5($this->secret . $a);
	}

	public function encode($args)
	{
		$encoded = array();
		foreach ($args as $key => $value){
			$encoded[] = urlencode($key) . '=' . urlencode($value);
		}
		return $encoded;
	}

	function call($method, $args = array ())
	{
		$args = array_merge(array ('method' => $method,
				'api_key' => $this->key), $args);

		ksort($args);

		$args = array_merge($args, array ('api_sig' => $this->sign($args)));
		ksort($args);

		if($method == 'upload'){
			$req = curl_init();
			$args['api_key'] = $this->key;
			$photo = $args['photo'];
			$args['photo'] = '@' . $photo;
			curl_setopt($req, CURLOPT_URL, $this->uploadendpoint);
			curl_setopt($req, CURLOPT_TIMEOUT, 0);
			// curl_setopt($req, CURLOPT_INFILESIZE, filesize($photo));
			// Sign and build request parameters
			curl_setopt($req, CURLOPT_POSTFIELDS, $args);
			curl_setopt($req, CURLOPT_CONNECTTIMEOUT, $this->conntimeout);
			curl_setopt($req, CURLOPT_FOLLOWLOCATION, 1);
			curl_setopt($req, CURLOPT_HEADER, 0);
			curl_setopt($req, CURLOPT_RETURNTRANSFER, 1);
			$this->_http_body = curl_exec($req);

			if (curl_errno($req)){
				throw new Exception(curl_error($req));
			}

			curl_close($req);
			$xml = simplexml_load_string($this->_http_body);
			$this->xml = $xml;
			return $xml;
		}
		else{
			$url = $this->endpoint . implode('&', $this->encode($args));

			$call = new RemoteRequest($url);

			$call->set_timeout(5);
			$result = $call->execute();
			if (Error::is_error($result)){
				throw $result;
			}

			$response = $call->get_response_body();
			try{
				$xml = new SimpleXMLElement($response);
				return $xml;
			}
			catch(Exception $e) {
				Session::error('Currently unable to connect to Flickr.', 'flickr API');
//				Utils::debug($url, $response);
				return false;
			}
		}
	}
}

class Flickr extends flickrAPI
{
	function __construct($params = array())
	{
		parent::__construct($params);
	}
	// URL building
	function getPhotoURL($p, $size = 'm', $ext = 'jpg')
	{
		return "http://static.flickr.com/{$p['server']}/{$p['id']}_{$p['secret']}_{$size}.{$ext}";
	}
	// authentication and approval
	public function getFrob()
	{
		$xml = $this->call('flickr.auth.getFrob', array());
		return $xml->frob;
	}

	public function authLink($frob)
	{
		$params['api_key'] = $this->key;
		$params['frob'] = $frob;
		$params['perms'] = 'write';
		$params['api_sig'] = md5($this->secret . 'api_key' . $params['api_key'] . 'frob' . $params['frob'] . 'permswrite');
		$link = $this->authendpoint . implode('&', $this->encode($params));
		return $link;
	}

	function getToken($frob)
	{
		$xml = $this->call('flickr.auth.getToken', array('frob' => $frob));
		return $xml;
	}
	// grab the token from our db.
	function cachedToken()
	{
		$token = Options::get('flickr_token_' . User::identify()->id);
		return $token;
	}
	// get publicly available photos
	function getPublicPhotos($nsid, $extras = '', $per_page = '', $page = '')
	{
		$params = array('user_id' => $nsid);
		if($extras){
			$params['extras'] = $extras;
		}
		if($per_page){
			$params['per_page'] = $per_page;
		}
		if($page){
			$params['page'] = $page;
		}

		$xml = $this->call('flickr.people.getPublicPhotos' , $params);
		foreach($xml->photos->attributes() as $key => $value){
			$pic[$key] = (string)$value;
		}
		$i = 0;
		foreach($xml->photos->photo as $photo){
			foreach($photo->attributes() as $key => $value){
				$pic['photos'][(string)$photo['id']][$key] = (string)$value;
			}
			$i++;
		}
		return $pic;
	}
	// Photosets methods
	function photosetsGetList($nsid = '')
	{
		$params = array();

		if($nsid){
			$params['user_id'] = $nsid;
		}

		$xml = $this->call('flickr.photosets.getList', $params);
		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function photosetsGetInfo($photoset_id)
	{
		$params = array('photoset_id' => $photoset_id);
		$xml = $this->call('flickr.photosets.getInfo', $params);
		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function photosetGetPrimary($p, $size = 'm', $ext = '.jpg')
	{
		return 'http://static.flickr.com/' . $p['server'] . '/' . $p['primary'] . '_' . $p['secret'] . '_' . $size . $ext;
	}

	function photosetsGetPhotos($photoset_id)
	{
		$params = array('photoset_id' => $photoset_id);
		$xml = $this->call('flickr.photosets.getPhotos', $params);
		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function photosRecentlyUpdated()
	{
		$params = array();
		if($this->cachedToken()){
			$params['auth_token'] = $this->cachedToken();
		}

		$params['secret'] = $this->secret;
		$params['min_date'] = time() - 31536000;  // Within the last year
		$params['per_page'] = 10;

		$xml = $this->call('flickr.photos.recentlyUpdated', $params);

		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function mediaSearch( $params = array()  )
	{
		if($this->cachedToken()){
			$params['auth_token'] = $this->cachedToken();
		}

		$params['secret'] = $this->secret;
		$params['user_id'] = 'me';
		$params['sort'] = 'date-posted-desc';
		$params['per_page'] = 20;

		$xml = $this->call('flickr.photos.search', $params);

		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function photosSearch( $params = array()  )
	{
		if($this->cachedToken()){
			$params['auth_token'] = $this->cachedToken();
		}

		$defaults = array(
			'secret' => $this->secret,
			'user_id' => 'me',
			'sort' => 'date-posted-desc',
			'per_page' => 20,
			'media' => 'photos',
			'extras' => 'original_format',
		);

		$params = array_merge($defaults, $params);

		$xml = $this->call('flickr.photos.search', $params);

		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function videoSearch( $params = array()  )
	{
		if($this->cachedToken()){
			$params['auth_token'] = $this->cachedToken();
		}

		$params['secret'] = $this->secret;
		$params['user_id'] = 'me';
		$params['sort'] = 'date-posted-desc';
		$params['per_page'] = 20;
		$params['media'] = 'videos';

		$xml = $this->call('flickr.photos.search', $params);

		if (Error::is_error($xml)){
			throw $xml;
		}
		return $xml;
	}

	function tagsGetListUser($userid = null)
	{
		$params = array();
		if(isset($userid)) {
			$params['user_id'] = $userid;
		}
		$xml = $this->call('flickr.tags.getListUser', $params);
		return $xml;
	}

	function photosGetInfo($photo_id)
	{
		$params = array();
		if($this->cachedToken()){
			$params['auth_token'] = $this->cachedToken();
		}

		$params['photo_id'] = $photo_id;
		$params['secret'] = $this->secret;

		$xml = $this->call('flickr.photos.getInfo', $params);

		if (Error::is_error($xml)){
			throw $xml;
		}

		foreach($xml->photo->attributes() as $key => $value){
			$result[(string)$key] = (string)$value;
		}

		foreach($xml->photo->children() as $key => $value){
			foreach($value->attributes() as $kk => $vv) $result[(string)$key][(string)$kk] = (string)$vv;
			$id = -1;
			foreach($value->children() as $kk => $vv){
				$typed = false;
				if(isset($vv['id'])){
					$id = (string)$vv['id'];
				}elseif(isset($vv['type'])){
					$id = (string)$vv['type'];
					$typed = true;
				}else $id++;
				foreach($vv->attributes() as $kkk => $vvv){
					$ret[(string)$key][$id][(string)$kkk] = (string)$vvv;
				}
				if($typed){
					$ret[(string)$key][$id] = (string)$vv;
				}
				else{
					$ret[(string)$key][$id]['text'] = (string)$vv;
				}
			}
			if(!count($ret[(string)$key])) $ret[(string)$key] = (string)$value;
		}
		return $ret;
	}

	function upload($photo, $title = '', $description = '', $tags = '', $perms = '', $async = 1, &$info = null)
	{
		$store = HABARI_PATH . '/' . Site::get_path('user') . '/cache';
		if(!is_dir($store)){
			mkdir($store, 0777);
		}
		$params = array('auth_token' => $this->cachedToken());
		$url = InputFilter::parse_url('file://' . $photo);
		if(isset($url['scheme'])){
			$localphoto = fopen(HABARI_PATH . '/' . $photo, 'r');
			$store = tempnam($store, 'G2F');
			file_put_contents($store, $localphoto);
			fclose($localphoto);
			$params['photo'] = $store;
		}
		else{
			$params['photo'] = $photo;
		}

		$info = filesize($params['photo']);

		if($title){
			$params['title'] = $title;
		}

		if($description){
			$params['description'] = $description;
		}

		if($tags){
			$params['tags'] = $tags;
		}

		if($perms){
			if(isset($perms['is_public'])){
				$params['is_public'] = $perms['is_public'];
			}
			if(isset($perms['is_friend'])){
				$params['is_friend'] = $perms['is_friend'];
			}
			if(isset($perms['is_family'])){
				$params['is_family'] = $perms['is_family'];
			}
		}

		if($async){
			$params['async'] = $async;
		}
		// call the upload method.
		$xml = $this->call('upload', $params);

		if($store){
			unlink($store);
		}

		if (Error::is_error($xml)){
			throw $xml;
		}

		if($async){
			return((string)$xml->ticketid);
		}
		else{
			return((string)$xml->photoid);
		}
	}

	function photosUploadCheckTickets($tickets)
	{
		if(is_array($tickets)){
			foreach($tickets as $key => $value){
				if($key){
					$params['tickets'] .= ' ';
				}
				$params['tickets'] .= $value;
			}
		}
		else{
			$params['tickets'] = $tickets;
		}

		$xml = $this->call('flickr.photos.upload.checkTickets', $params);
		if (Error::is_error($xml)){
			throw $xml;
		}

		foreach($xml->uploader->ticket as $ticket){
			foreach($ticket->attributes() as $key => $value){
				$uptick[(string)$ticket['id']][$key] = (string)$value;
			}
		}
		return $uptick;
	}

	function reflectionGetMethods()
	{
		$params = array();
		$xml = $this->call('flickr.reflection.getMethods', $params);
		if(!$xml){
			return false;
		}
		$ret = (array)$xml->methods->method;
		return $ret;
	}
}

/**
* Flickr Silo
*/

class FlickrSilo extends Plugin implements MediaSilo
{
	const SILO_NAME = 'Flickr';

	static $cache = array();

	/**
	* Initialize some internal values when plugin initializes
	*/
	public function action_init()
	{
	}

	/**
	* Return basic information about this silo
	*     name- The name of the silo, used as the root directory for media in this silo
	*	  icon- An icon to represent the 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)
	{
		$flickr = new Flickr();
		$results = array();
		$size = Options::get('flickrsilo__flickr_size');

		$section = strtok($path, '/');
		switch($section) {
			case 'attrib-sa':
				$xml = $flickr->photosSearch(array('user_id' => '', 'license' => '4,5', 'text'=>$_SESSION['flickrsearch']));
				foreach($xml->photos->photo as $photo) {

					$props = array();
					foreach($photo->attributes() as $name => $value) {
						$props[$name] = (string)$value;
					}
					$props['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}$size.jpg";
					$props['thumbnail_url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}_m.jpg";
					$props['flickr_url'] = "http://www.flickr.com/photos/{$photo['owner']}/{$photo['id']}";
					$props['filetype'] = 'flickr';

					$results[] = new MediaAsset(
						self::SILO_NAME . '/photos/' . $photo['id'],
						false,
						$props
					);
				}
				break;

			case 'search':
				$xml = $flickr->photosSearch(array('text'=>$_SESSION['flickrsearch']));
				foreach($xml->photos->photo as $photo) {

					$props = array();
					foreach($photo->attributes() as $name => $value) {
						$props[$name] = (string)$value;
					}
					$props['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}$size.jpg";
					$props['thumbnail_url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}_m.jpg";
					$props['flickr_url'] = "http://www.flickr.com/photos/{$_SESSION['nsid']}/{$photo['id']}";
					$props['filetype'] = 'flickr';

					$results[] = new MediaAsset(
						self::SILO_NAME . '/photos/' . $photo['id'],
						false,
						$props
					);
				}
				break;

			case 'photos':
				$xml = $flickr->photosSearch();
				foreach($xml->photos->photo as $photo) {

					$props = array();
					foreach($photo->attributes() as $name => $value) {
						$props[$name] = (string)$value;
					}
					$props['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}$size.jpg";
					$props['thumbnail_url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}_m.jpg";
					$props['flickr_url'] = "http://www.flickr.com/photos/{$_SESSION['nsid']}/{$photo['id']}";
					$props['filetype'] = 'flickr';

					$results[] = new MediaAsset(
						self::SILO_NAME . '/photos/' . $photo['id'],
						false,
						$props
					);
				}
				break;
			case 'videos':
				$xml = $flickr->videoSearch();
				foreach($xml->photos->photo as $photo) {

					$props = array();
					foreach($photo->attributes() as $name => $value) {
						$props[$name] = (string)$value;
					}
					$props['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}$size.jpg";
					$props['thumbnail_url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}_m.jpg";
					$props['flickr_url'] = "http://www.flickr.com/photos/{$_SESSION['nsid']}/{$photo['id']}";
					$props['filetype'] = 'flickrvideo';

					$results[] = new MediaAsset(
						self::SILO_NAME . '/photos/' . $photo['id'],
						false,
						$props
					);
				}
				break;
			case 'tags':
				$selected_tag = strtok('/');
				if($selected_tag) {
					$xml = $flickr->photosSearch(array('tags'=>$selected_tag));
					foreach($xml->photos->photo as $photo) {

						$props = array();
						foreach($photo->attributes() as $name => $value) {
							$props[$name] = (string)$value;
						}
						$props['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}.jpg";
						$props['thumbnail_url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}_m.jpg";
						$props['flickr_url'] = "http://www.flickr.com/photos/{$_SESSION['nsid']}/{$photo['id']}";
						$props['filetype'] = 'flickr';

						$results[] = new MediaAsset(
							self::SILO_NAME . '/photos/' . $photo['id'],
							false,
							$props
						);
					}
				}
				else {
					$xml = $flickr->tagsGetListUser($_SESSION['nsid']);
					foreach($xml->who->tags->tag as $tag) {
						$results[] = new MediaAsset(
							self::SILO_NAME . '/tags/' . (string)$tag,
							true,
							array('title' => (string)$tag)
						);
					}
				}
				break;
			case 'sets':
				$selected_set = strtok('/');
				if($selected_set) {
					$xml = $flickr->photosetsGetPhotos($selected_set);
					foreach($xml->photoset->photo as $photo) {

						$props = array();
						foreach($photo->attributes() as $name => $value) {
							$props[$name] = (string)$value;
						}
						$props['url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}.jpg";
						$props['thumbnail_url'] = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}_m.jpg";
						$props['flickr_url'] = "http://www.flickr.com/photos/{$_SESSION['nsid']}/{$photo['id']}";
						$props['filetype'] = 'flickr';

						$results[] = new MediaAsset(
							self::SILO_NAME . '/photos/' . $photo['id'],
							false,
							$props
						);
					}
				}
				else {
					$xml = $flickr->photosetsGetList($_SESSION['nsid']);
					foreach($xml->photosets->photoset as $set) {
						$results[] = new MediaAsset(
							self::SILO_NAME . '/sets/' . (string)$set['id'],
							true,
							array('title' => (string)$set->title)
						);
					}
				}
				break;

			case '$search':
				$path = strtok('/');
				$dosearch = Utils::slugify($path);
				$_SESSION['flickrsearch'] = $path;
				$section = $path;

			case '':
				if(isset($_SESSION['flickrsearch'])) {
					$results[] = new MediaAsset(
						self::SILO_NAME . '/search',
						true,
						array('title' => 'Search')
					);
					$results[] = new MediaAsset(
						self::SILO_NAME . '/attrib-sa',
						true,
						array('title' => 'Search CC')
					);
				}
				$results[] = new MediaAsset(
					self::SILO_NAME . '/photos',
					true,
					array('title' => 'Photos')
				);
				$results[] = new MediaAsset(
					self::SILO_NAME . '/videos',
					true,
					array('title' => 'Videos')
				);
				$results[] = new MediaAsset(
					self::SILO_NAME . '/tags',
					true,
					array('title' => 'Tags')
				);
				$results[] = new MediaAsset(
					self::SILO_NAME . '/sets',
					true,
					array('title' => 'Sets')
				);
				break;
		}
		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)
	{
		$photo = false;
		if(preg_match('%^photos/(.+)$%', $path, $matches)) {
			$id = $matches[1];
			$photo = self::$cache[$id];
		}

		$size = '';
		if(isset($qualities['size']) && $qualities['size'] == 'thumbnail') {
			$size = '_m';
		}
		$url = "http://farm{$photo['farm']}.static.flickr.com/{$photo['server']}/{$photo['id']}_{$photo['secret']}{$size}.jpg";
		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()
	{
	}

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

	/**
	* 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_contents()
	{
		$flickr = new Flickr();
		$token = Options::get('flickr_token_' . User::identify()->id);
		$result = $flickr->call('flickr.auth.checkToken',
			array('api_key' => $flickr->key,
				'auth_token' => $token));
		$photos = $flickr->GetPublicPhotos($result->auth->user['nsid'], null, 5);
		foreach($photos['photos'] as $photo){
			$url = $flickr->getPhotoURL($photo);
			echo '<img src="' . $url . '" width="150px" alt="' . ( isset( $photo['title'] ) ? $photo['title'] : _t('This photo has no title') ) . '">';
		}
	}

	/**
	* 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()){
			$flickr_ok = $this->is_auth();

			if($flickr_ok){
				$actions[] = 'De-Authorize';
			}
			else{
				$actions[] = 'Authorize';
			}
			$actions[] = 'Configure';
		}

		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)
	{
		if ($plugin_id == $this->plugin_id()){
			switch ($action){
				case 'Authorize':
					if($this->is_auth()){
						$deauth_url = URL::get('admin', array('page' => 'plugins', 'configure' => $this->plugin_id(), 'configaction' => 'De-Authorize')) . '#plugin_options';
						echo "<p>You have already successfully authorized Habari to access your Flickr account.</p>";
						echo "<p>Do you want to <a href=\"\">revoke authorization</a>?</p>";
					}
					else{
						$flickr = new Flickr();
						$_SESSION['flickr_frob'] = '' . $flickr->getFrob();
						$auth_url = $flickr->authLink($_SESSION['flickr_frob']);
						$confirm_url = URL::get('admin', array('page' => 'plugins', 'configure' => $this->plugin_id(), 'configaction' => 'confirm')) . '#plugin_options';
						echo <<< END_AUTH

<p>To use this plugin, you must <a href="{$auth_url}" target="_blank">authorize Habari to have access to your Flickr account</a>.
<p>When you have completed the authorization on Flickr, return here and <a href="$confirm_url">confirm that the authorization was successful</a>.

END_AUTH;
					}
					break;

				case 'confirm':
					$flickr = new Flickr();
					if(!isset($_SESSION['flickr_frob'])){
						$auth_url = URL::get('admin', array('page' => 'plugins', 'configure' => $this->plugin_id(), 'configaction' => 'Authorize')) . '#plugin_options';
						echo '<p>Either you have already authorized Habari to access your flickr account, or you have not yet done so.  Please <a href="' . $auth_url . '">try again</a>.</p>';
					}
					else{
						$token = $flickr->getToken($_SESSION['flickr_frob']);
						if(isset($token->auth->perms)){
							Options::set('flickr_token_' . User::identify()->id, '' . $token->auth->token);
							echo '<p>Your authorization was set successfully.</p>';
						}
						else{
							echo '<p>There was a problem with your authorization:</p>';
							echo Utils::htmlspecialchars($token->asXML());
						}
						unset($_SESSION['flickr_frob']);
					}
					break;
				case 'De-Authorize':
					Options::set('flickr_token_' . User::identify()->id);
					$reauth_url = URL::get('admin', array('page' => 'plugins', 'configure' => $this->plugin_id(), 'configaction' => 'Authorize')) . '#plugin_options';
					echo '<p>The Flickr Silo Plugin authorization has been deleted.<p>';
					echo "<p>Do you want to <a href=\"{$reauth_url}\">re-authorize this plugin</a>?<p>";
					break;
				case 'Configure' :
					$ui = new FormUI( strtolower( get_class( $this ) ) );
					$ui->append( 'select', 'flickr_size','option:flickrsilo__flickr_size', _t( 'Default size for images in Posts:' ) );
					$ui->flickr_size->options = array( '_s' => 'Square (75x75)', '_t' => 'Thumbnail (100px)', '_m' => 'Small (240px)', '' => 'Medium (500px)', '_b' => 'Large (1024px)', '_o' => 'Original Size' );
					$ui->append('submit', 'save', _t( 'Save' ) );
					$ui->set_option('success_message', _t('Options saved'));
					$ui->out();
					break;
			}
		}
	}
	public function action_admin_footer( $theme ) {
		if(Controller::get_var('page') == 'publish') {
			$size = Options::get('flickrsilo__flickr_size');
			switch($size) {
				case '_s':
					$vsizex = 75;
					break;
				case '_t':
					$vsizex = 100;
					break;
				case '_m':
					$vsizex = 240;
					break;
				case '':
					$vsizex = 500;
					break;
				case '_b':
					$vsizex = 1024;
					break;
				case '_o':
					$vsizex = 400;
					break;
			}
			$vsizey = intval($vsizex/4*3);

			echo <<< FLICKR
			<script type="text/javascript">
				habari.media.output.flickr = {
					embed_photo: function(fileindex, fileobj) {
						habari.editor.insertSelection('<a href="' + fileobj.flickr_url + '"><img src="' + fileobj.url + '"></a>');
					}
				}
				habari.media.output.flickrvideo = {
					embed_video: function(fileindex, fileobj) {
						habari.editor.insertSelection('<object type="application/x-shockwave-flash" width="{$vsizex}" height="{$vsizey}" data="http://www.flickr.com/apps/video/stewart.swf?v=49235" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"> <param name="flashvars" value="intl_lang=en-us&photo_secret=' + fileobj.secret + '&photo_id=' + fileobj.id + '&show_info_box=true"></param> <param name="movie" value="http://www.flickr.com/apps/video/stewart.swf?v=49235"></param> <param name="bgcolor" value="#000000"></param> <param name="allowFullScreen" value="true"></param><embed type="application/x-shockwave-flash" src="http://www.flickr.com/apps/video/stewart.swf?v=49235" bgcolor="#000000" allowfullscreen="true" flashvars="intl_lang=en-us&photo_secret=' + fileobj.secret + '&photo_id=' + fileobj.id + '&flickr_show_info_box=true" height="{$vsizey}" width="{$vsizex}"></embed></object>');
					},
					thumbnail: function(fileindex, fileobj) {
						habari.editor.insertSelection('<a href="' + fileobj.flickr_url + '"><img src="' + fileobj.url + '"></a>');
					}
				}
				habari.media.preview.flickr = function(fileindex, fileobj) {
					var stats = '';
					return '<div class="mediatitle"><a href="' + fileobj.flickr_url + '" class="medialink" onclick="$(this).attr(\'target\',\'_blank\');" title="Open in new window">media</a>' + fileobj.title + '</div><img src="' + fileobj.thumbnail_url + '"><div class="mediastats"> ' + stats + '</div>';
				}
				habari.media.preview.flickrvideo = function(fileindex, fileobj) {
					var stats = '';
					return '<div class="mediatitle"><a href="' + fileobj.flickr_url + '" class="medialink" onclick="$(this).attr(\'target\',\'_blank\');"title="Open in new window" >media</a>' + fileobj.title + '</div><img src="' + fileobj.thumbnail_url + '"><div class="mediastats"> ' + stats + '</div>';
				}
			</script>
FLICKR;
		}
	}

	private function is_auth()
	{
		static $flickr_ok = null;
		if(isset($flickr_ok)){
			return $flickr_ok;
		}

		$flickr_ok = false;
		$token = Options::get('flickr_token_' . User::identify()->id);
		if($token != ''){
			$flickr = new Flickr();
			$result = $flickr->call('flickr.auth.checkToken', array('api_key' => $flickr->key, 'auth_token' => $token));
			if(isset($result->auth->perms)){
				$flickr_ok = true;
				$_SESSION['nsid'] = (string)$result->auth->user['nsid'];
			}
			else{
				Options::set('flickr_token_' . User::identify()->id);
				unset($_SESSION['flickr_token']);
			}
		}
		return $flickr_ok;
	}

	/**
	 * 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) {
			unset($controls['root']);
			$search_criteria = isset($_SESSION['flickrsearch']) ? htmlentities($_SESSION['flickrsearch']) : '';
			$controls['search']= '<label for="flickrsearch" class="incontent">Search</label><input type="search" id="flickrsearch" placeholder="'. _t('Search for photos') .'" value="'.$search_criteria.'">
					<script type="text/javascript">
					$(\'#flickrsearch\').keypress(function(e){
						if(e.which == 13){
							habari.media.fullReload();
							habari.media.showdir(\''.FlickrSilo::SILO_NAME.'/$search/\' + $(this).val());
							return false;
						}
					});
					</script>';
		}
		return $controls;
	}

}

?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin">
	<name>Flickr 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 Flickr integration.]]></description>

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

repo_name: DaSourcerer/habari


code:
<?php

/**
 * Simple file access silo
 *
 * @todo Create some helper functions in a superclass to display panel controls more easily, so that you don't need to include 80 lines of code to build a simple upload form every time.
 */

class HabariSilo extends Plugin implements MediaSilo
{
	protected $root = null;
	protected $url = null;

	const SILO_NAME = 'Habari';

	const DERIV_DIR = '.deriv';

	/**
	 * Initialize some internal values when plugin initializes
	 */
	public function action_init()
	{
		$user_path = HABARI_PATH . '/' . Site::get_path('user', true);
		$this->root = $user_path . 'files'; //Options::get('simple_file_root');
		$this->url = Site::get_url('user', true) . 'files';  //Options::get('simple_file_url');

		if ( !$this->check_files() ) {
			Session::error( _t( "Habari Silo activation failed. The web server does not have permission to create the 'files' directory for the Habari Media Silo." ) );
			Plugins::deactivate_plugin( __FILE__ ); //Deactivate plugin
			Utils::redirect(); //Refresh page. Unfortunately, if not done so then results don't appear
		}
	}

	/**
	* Don't bother loading if the gd library isn't active
	*/
	public function action_plugin_activation( $file )
	{
		if ( !function_exists( 'imagecreatefromjpeg' ) ) {
			Session::error( _t( "Habari Silo activation failed. PHP has not loaded the gd imaging library." ) );
			Plugins::deactivate_plugin( __FILE__ );
		}
		// Create required tokens
		ACL::create_token( 'create_directories', _t( 'Create media silo directories' ), 'Administration' );
		ACL::create_token( 'delete_directories', _t( 'Delete media silo directories' ), 'Administration' );
		ACL::create_token( 'upload_media', _t( 'Upload files to media silos' ), 'Administration' );
		ACL::create_token( 'delete_media', _t( 'Delete files from media silos' ), 'Administration' );
	}

	/**
	*
	* @param string $file. The name of the plugin file
	*
	* Delete the special silo permissions if they're no longer
	* being used.
	*/
	public function action_plugin_deactivation( $file ) {
		$silos = Plugins::get_by_interface( 'MediaSilo' );
		if ( count( $silos ) <= 1 ) {
			ACL::destroy_token( 'upload_media' );
			ACL::destroy_token( 'delete_media' );
			ACL::destroy_token( 'create_directories' );
			ACL::destroy_token( 'delete_directories' );
		}
	}

	/**
	 *
	 * Checks if files directory is usable
	 */
	private function check_files() {
		$user_path = HABARI_PATH . '/' . Site::get_path('user', true);
		$this->root = $user_path . 'files'; //Options::get('simple_file_root');
		$this->url = Site::get_url('user', true) . 'files';  //Options::get('simple_file_url');

		if ( !is_dir( $this->root ) ) {
			if ( is_writable( $user_path ) ) {
				mkdir( $this->root, 0755 );
			}
			else {
				return false;
			}
		}

		return true;
	}

	/**
	 * 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()
	{
		return array(
			'name' => self::SILO_NAME,
			'icon' => URL::get_from_filesystem(__FILE__) . '/icon.png'
		);
	}

	/**
	 * 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 )
	{
		if ( !isset( $this->root ) ) {
			return array();
		}

		$path = preg_replace('%\.{2,}%', '.', $path);
		$results = array();

		$dir = Utils::glob( $this->root . ( $path == '' ? '' : '/' ) . $path . '/*' );

		foreach ( $dir as $item ) {
			if ( substr( basename( $item ), 0, 1 ) == '.' ) {
				continue;
			}
			if ( basename( $item ) == 'desktop.ini' ) {
				continue;
			}

			$file = basename( $item );
			$props = array(
				'title' => basename( $item ),
			);
			if ( !is_dir( $item ) ) {
				$thumbnail_suffix = HabariSilo::DERIV_DIR . '/' . $file . '.thumbnail.jpg';
				$thumbnail_url = $this->url . '/' . $path . ($path == '' ? '' : '/') . $thumbnail_suffix;
				$mimetype = preg_replace('%[^a-z_0-9]%', '_', Utils::mimetype($item));
				$mtime = '';

				if ( !file_exists( dirname( $item ) . '/' . $thumbnail_suffix ) ) {
					switch(strtolower(substr($item, strrpos($item, '.') + 1))) {
						case 'jpg':
						case 'png':
						case 'gif':
							if ( !$this->create_thumbnail( $item ) ) {
								// there is no thumbnail so use icon based on mimetype.
								$icon_path = Plugins::filter( 'habarisilo_icon_base_path', dirname($this->get_file()) . '/icons' );
								$icon_url = Plugins::filter( 'habarisilo_icon_base_url', $this->get_url() . '/icons' );

								if ( ( $icons = Utils::glob($icon_path . '/*.{png,jpg,gif,svg}', GLOB_BRACE) ) && $mimetype ) {
									$icon_keys = array_map( create_function('$a', 'return pathinfo($a, PATHINFO_FILENAME);'), $icons );
									$icons = array_combine($icon_keys, $icons);
									$icon_filter = create_function('$a, $b', "\$mime = '$mimetype';".'return (((strpos($mime, $a)===0) ? (strlen($a) / strlen($mime)) : 0) >= (((strpos($mime, $b)===0)) ? (strlen($b) / strlen($mime)) : 0)) ? $a : $b;');
									$icon_key = array_reduce($icon_keys, $icon_filter);
									if ($icon_key) {
										$icon = basename($icons[$icon_key]);
										$thumbnail_url = $icon_url .'/'. $icon;
									}
									else {
										// couldn't find an icon so use default
										$thumbnail_url = $icon_url .'/default.png';
									}
								}
							}
						break;
					}
				}

				// If the asset is an image, obtain the image dimensions
				if ( in_array( $mimetype, array( 'image_jpeg', 'image_png', 'image_gif' ) ) ) {
					list( $props['width'], $props['height'] ) = getimagesize( $item );
					$mtime = '?' . filemtime( $item );
				}
				$props = array_merge(
					$props,
					array(
						'url' => $this->url . '/' . $path . ($path == '' ? '' : '/') . $file,
						'thumbnail_url' => $thumbnail_url . $mtime,
						'filetype' => $mimetype,
					)
				);
			}

			$results[] = new MediaAsset(
				self::SILO_NAME . '/' . $path . ($path == '' ? '' : '/') . basename( $item ),
				is_dir( $item ),
				$props
			);
		}
		//print_r($results);
		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 )
	{
		if ( ! isset( $this->root ) ) {
			return false;
		}

		$path = preg_replace('%\.{2,}%', '.', $path);

		$file = $this->root . '/' . $path;

		if ( file_exists( $file ) && is_file( $file ) ) {
			$asset = new MediaAsset( self::SILO_NAME . '/' . $path, false );
			$asset->filetype = preg_replace('%[^a-z_0-9]%', '_', Utils::mimetype($file));
			$asset->content = file_get_contents( $file );
			return $asset;
		}
		return false;
	}

	/**
	 * Create a thumbnail in the derivative directory
	 *
	 * @param string $src_filename The source filename
	 * @param integer $max_width The maximum width of the output image
	 * @param integer $max_height The maximum height of the output image
	 * @return boolean true if the thumbnail creation succeeded
	 */
	private function create_thumbnail($src_filename, $max_width = Media::THUMBNAIL_WIDTH, $max_height = Media::THUMBNAIL_HEIGHT)
	{
		// Does derivative directory not exist?
		$thumbdir = dirname( $src_filename ) . '/' . HabariSilo::DERIV_DIR . '';
		if ( !is_dir( $thumbdir ) ) {
			// Create the derivative driectory
			if ( !mkdir( $thumbdir, 0755 ) ) {
				// Couldn't make derivative directory
				return false;
			}
		}

		// Get information about the image
		list( $src_width, $src_height, $type, $attr )= getimagesize( $src_filename );

		// Load the image based on filetype
		switch( $type ) {
		case IMAGETYPE_JPEG:
			$src_img = imagecreatefromjpeg( $src_filename );
			break;
		case IMAGETYPE_PNG:
			$src_img = imagecreatefrompng( $src_filename );
			break;
		case IMAGETYPE_GIF:
			$src_img = imagecreatefromgif( $src_filename );
			break;
		default:
			return false;
		}
		// Did the image fail to load?
		if ( !$src_img ) {
			return false;
		}

		// Calculate the output size based on the original's aspect ratio
		$y_displacement = 0;
		if ( $src_width / $src_height > $max_width / $max_height ) {
			$thumb_w = $max_width;
			$thumb_h = $src_height * $max_width / $src_width;

		// thumbnail is not full height, position it down so that it will be padded on the
		// top and bottom with black
		$y_displacement = ($max_height - $thumb_h) / 2;
		}
		else {
			$thumb_w = $src_width * $max_height / $src_height;
			$thumb_h = $max_height;
		}

		// Create the output image and copy to source to it
		$dst_img = ImageCreateTrueColor( $thumb_w, $max_height );
		imagecopyresampled( $dst_img, $src_img, 0, $y_displacement, 0, 0, $thumb_w, $thumb_h, $src_width, $src_height );

		/* Sharpen before save?
		$sharpenMatrix= array( array(-1, -1, -1), array(-1, 16, -1), array(-1, -1, -1) );
		$divisor= 8;
		$offset= 0;
		imageconvolution( $dst_img, $sharpenMatrix, $divisor, $offset );
		//*/

		// Define the thumbnail filename
		$dst_filename = $thumbdir . '/' . basename($src_filename) . ".thumbnail.jpg";

		// Save the thumbnail as a JPEG
		imagejpeg( $dst_img, $dst_filename );

		// Clean up memory
		imagedestroy( $dst_img );
		imagedestroy( $src_img );

		return true;
	}

	/**
	 * 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 $filedata The MediaAsset to store
	 * @return boolean True on success
	 **/
	public function silo_put( $path, $filedata )
	{
		$path = preg_replace('%\.{2,}%', '.', $path);
		$file = $this->root . '/' . $path;

		$result = $filedata->save( $file );
		if ( $result ) {
			$this->create_thumbnail( $file );
		}

		return $result;
	}

	/**
	 * Delete the file at the specified path
	 * @param string $path The path of the file to retrieve
	 **/
	public function silo_delete( $path )
	{
		$file = $this->root . '/' . $path;

		// Delete the file
		$result = unlink( $file );

		// If it's an image, remove the file in .deriv too
		$thumbdir = dirname( $file ) . '/' . HabariSilo::DERIV_DIR . '';
		$thumb = $thumbdir . '/' . basename( $file ) . ".thumbnail.jpg";

		if ( file_exists( $thumbdir ) && file_exists( $thumb ) ) {
			unlink( $thumb );
			// if this is the last thumb, delete the .deriv dir too
			if ( self::isEmptyDir( $thumbdir ) ) {
				rmdir( $thumbdir );
			}
		}
		return $result;
	}

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

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

	/**
	 * Produce a link for the media control bar that causes a specific path to be displayed
	 *
	 * @param string $path The path 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_path( $path, $title = '' )
	{
		if ( $title == '' ) {
			$title = basename( $path );
		}
		return '' . $title . '';
	}

	/**
	 * 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 ) {
			$controls[] = $this->link_path( self::SILO_NAME . '/' . $path, _t( 'Browse' ) );
			if ( User::identify()->can( 'upload_media' ) ) {
				$controls[] = $this->link_panel(self::SILO_NAME . '/' . $path, 'upload', _t( 'Upload' ) );
			}
			if ( User::identify()->can( 'create_directories' ) ) {
				$controls[] = $this->link_panel(self::SILO_NAME . '/' . $path, 'mkdir', _t( 'Create Directory' ) );
			}
			if ( User::identify()->can( 'delete_directories' ) && ( $path && self::isEmptyDir( $this->root . '/' . $path ) ) ) {
				$controls[] = $this->link_panel(self::SILO_NAME . '/' . $path, 'rmdir', _t( 'Delete Directory' ) );
			}
		}
		return $controls;
	}

	/**
	 * Provide requested media panels for this plugin
	 *
	 * Regarding Uploading:
	 * A panel is returned to the media bar that contains a form, an iframe, and a javascript function.
	 * The form allows the user to select a file, and is submitted back to the same URL that produced this panel in the first place.
	 * This has the result of submitting the uploaded file to here when the form is submitted.
	 * To prevent the panel form from reloading the whole publishing page, the form is submitted into the iframe.
	 * An onload event attached to the iframe calls the function.
	 * The function accesses the content of the iframe when it loads, which should contain the results of the request to obtain this panel, which are in JSON format.
	 * The JSON data is passed to the habari.media.jsonpanel() function in media.js to process the data and display the results, just like when displaying a panel normally.
	 *
	 * @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 'mkdir':

					$fullpath = self::SILO_NAME . '/' . $path;

					$form = new FormUI( 'habarisilomkdir' );
					$form->append( 'static', 'ParentDirectory', '
' . _t('Parent Directory:') . " /{$path}
" );

					// add the parent directory as a hidden input for later validation
					$form->append( 'hidden', 'path', 'null:unused' )->value = $path;
					$form->append( 'hidden', 'action', 'null:unused')->value = $panelname;
					$dir_text_control = $form->append( 'text', 'directory', 'null:unused', _t('What would you like to call the new directory?') );
					$dir_text_control->add_validator( array( $this, 'mkdir_validator' ) );
					$form->append( 'submit', 'submit', _t('Submit') );
					$form->media_panel($fullpath, $panelname, 'habari.media.forceReload();');
					$form->on_success( array( $this, 'dir_success' ) );
					$panel = $form->get(); /* form submission magicallly happens here */

					return $panel;

					break;
				case 'rmdir':
					$fullpath = self::SILO_NAME . '/' . $path;

					$form = new FormUI( 'habarisilormdir' );
					$form->append( 'static', 'RmDirectory', '
' . _t('Directory:') . " /{$path}
" );

					// add the parent directory as a hidden input for later validation
					$form->append( 'hidden', 'path', 'null:unused' )->value = $path;
					$form->append( 'hidden', 'action', 'null:unused')->value = $panelname;
					$dir_text_control = $form->append( 'static', 'directory', _t('Are you sure you want to delete this directory?') );
					$form->append( 'submit', 'submit', _t('Delete') );
					$form->media_panel($fullpath, $panelname, 'habari.media.forceReload();');
					$form->on_success( array( $this, 'dir_success' ) );
					$panel = $form->get(); /* form submission magicallly happens here */

					return $panel;

					break;
				case 'delete':
					$fullpath = self::SILO_NAME . '/' . $path;

					$form = new FormUI( 'habarisilodelete' );
					$form->append( 'static', 'RmFile', '
' . _t('File:') . " /{$path}
" );

					// add the parent directory as a hidden input for later validation
					$form->append( 'hidden', 'path', 'null:unused' )->value = $path;
					$dir_text_control = $form->append( 'static', 'directory', '
' . _t('Are you sure you want to delete this file?') . '
');
					$form->append( 'submit', 'submit', _t('Delete') );
					$form->media_panel($fullpath, $panelname, 'habari.media.forceReload();');
					$form->on_success( array( $this, 'do_delete' ) );
					$panel = $form->get();

					return $panel;
					break;
				case 'upload':
					if( isset( $_FILES['file'] ) ) {
						$size = Utils::human_size($_FILES['file']['size']);
						$panel .= "

" . _t( "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 .= '
' . _t( 'File added successfully.' ) . '
';
						}
						else {
							$panel .= '
' . _t( 'File could not be added to the silo.' ) . '
';
						}

						$panel .= '
' . _t( '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">

%s /{$path}

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

</form>

UPLOAD_FORM;

					$panel = sprintf( $panel, _t( "Upload to:" ), _t( "Upload" ) );
				}
			}
		}
		return $panel;
	}

	/**
	 * A validator for the mkdir form created with FormUI. Checks to see if the
	 * webserver can write to the parent directory and that the directory does
	 * not already exist.
	 * @param $dir The input from the form
	 * @param $control The FormControl object
	 * @param $form The FormUI object
	 */
	public function mkdir_validator( $dir, $control, $form )
	{
		if ( strpos($dir, '*') !== false || preg_match('%(?:^|/)\.%', $dir) ) {
		    return array(_t("The directory name contains invalid characters: %s.", array($dir)));
		}

		$path = preg_replace( '%\.{2,}%', '.', $form->path->value );
		$dir = $this->root . ( $path == '' ? '' : '/' ) . $path . '/'. $dir;

		if ( !is_writable( $this->root . '/' . $path ) ) {
			return array(_t("Webserver does not have permission to create directory: %s.", array( $dir ) ) );
		}
		if ( is_dir( $dir ) ) {
			return array( _t( "Directory: %s already exists.", array( $dir ) ) );
		}

		return array();
	}
	/**
	 * This function performs the mkdir and rmdir actions on submission of the form.
	 * It is called by FormUI's success() method.
	 * @param FormUI $form
	 */
	public function dir_success ( $form )
	{
		$dir = preg_replace( '%\.{2,}%', '.', $form->directory->value );
		$path = preg_replace( '%\.{2,}%', '.', $form->path->value );

		switch ( $form->action->value ) {
			case 'rmdir':
				$dir = $this->root . ( $path == '' ? '' : '/' ) . $path;
				rmdir( $dir );
				$msg = 'Directory Deleted:';
				$what = $path;
				break;
			case 'mkdir':
				$dir = $this->root . ( $path == '' ? '' : '/' ) . $path . '/'. $dir;
				mkdir( $dir, 0755 );
				$msg = 'Directory Created:';
				$what = $path . '/' . $form->directory->value;
				break;
		}

		return '

' . _t( $msg ) . ' ' . $what . '

';
	}

	/**
	 * This function takes the path passed from the form and passes it to silo_delete
	 * to delete the file and it's thumbnail if it's an image.
	 *
	 * @param FormUI $form
	 */
	public function do_delete ( $form )
	{
		$path = preg_replace( '%\.{2,}%', '.', $form->path->value );
		$result = $this->silo_delete($path);
		$panel = '
';
		if ( $result ) {
			$panel .= '
' . _t( 'File deleted successfully.' ) . '
';
		} else {
			$panel .= '
' . _t( 'Failed to delete file.' ) . '
';
		}

		$panel .= '
' . _t( 'Browse the current silo path.' ) . '

';

		return $panel;
	}

	/**
	 * This function is used to check if a directory is empty.
	 *
	 * @param string $dir
	 * @return boolean
	 */
	private static function isEmptyDir( $dir )
	{
		return ( ( $files = @scandir( $dir ) ) && count( $files ) <= 2 );
	}

}

?>


code:
<?xml version="1.0" encoding="utf-8" ?>
<pluggable type="plugin">
	<name>Habari 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.1</version>
	<url>http://habariproject.org/</url>
	<description><![CDATA[Provides simple file uploading and embedding functionality.]]></description>

	<copyright>2009</copyright>

	<help>
		<value><![CDATA[

This plugin provides basic media functionality by allowing users to insert into posts images that exist on the server.

Depending on server capabilities, users may also be able to upload new images and create directories for new images on the server.

Upon initialization, this plugin will attempt to create a /user/files directory under your Habari installation. You may need to alter permissions to allow this action.

		]]></value>
	</help>

</pluggable>


code:
<?php
/**
 * 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
			$rr = new RemoteRequest( $source_uri );
			$rr->execute();
			if ( ! $rr->executed() ) {
				throw new XMLRPCException( 16 );
			}
			$source_contents = $rr->get_response_body();

			// encoding is converted into internal encoding.
			// @todo check BOM at beginning of file before checking for a charset attribute
			$habari_encoding = MultiByte::hab_encoding();
			if ( preg_match( "/<meta[^>]+charset=([A-Za-z0-9\-\_]+)/i", $source_contents, $matches ) && strtolower( $habari_encoding ) != strtolower( $matches[1] ) ) {
				$ret = MultiByte::convert_encoding( $source_contents, $habari_encoding, $matches[1] );
				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>' ) );

			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'		=>	sprintf( "%u", ip2long( $_SERVER['REMOTE_ADDR'] ) ),
				'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.
		$rr = new RemoteRequest( $target_uri );
		$rr->execute();
		if ( ! $rr->executed() ) {
			return false;
		}

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

		// Find a Pingback endpoint.
		if ( preg_match( '/^X-Pingback: (\S*)/im', $headers, $matches ) ) {
			$pingback_endpoint = $matches[1];
		}
		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( 'Invalid Pingback endpoint - ' . $pingback_endpoint . '  (Source: ' . $source_uri . ' | Target: ' . $target_uri . ')', 'info', 'Pingback' );
			return false;
		}

		if ( isset( $response->faultString ) ) {
			EventLog::log( $response->faultCode . ' - ' . $response->faultString . ' (Source: ' . $source_uri . ' | Target: ' . $target_uri . ')', 'info', '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 )
	{
		preg_match_all( '/<a[^>]+href=(?:"|\')((?=https?\:\/\/)[^>"\']+)(?:"|\')[^>]*>[^>]+<\/a>/is', $content, $matches );

		if ( is_object( $post ) && isset( $post->info->pingbacks_successful ) ) {
			$fn = ( $force === TRUE ) ? 'array_merge' : 'array_diff';
			$links = $fn( $matches[1], $post->info->pingbacks_successful );
		}
		else {
			$links = $matches[1];
		}

		$links = array_unique( $links );

		foreach ( $links as $target_uri ) {
			if ( $this->send_pingback( $source_uri, $target_uri, $post ) ) {
				EventLog::log( sprintf( _t( 'Sent pingbacks for "%1$s", target: %2$s' ), $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 Pingabcks' );
		}

		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">
	<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.0</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>

</pluggable>


code:
<?php

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)
	{
		if ( $post->content_type == Post::type('entry') ) {
			$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 ( $post->content_type == Post::type('entry') ) {
			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">
	<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

/**
 * 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">
	<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>
</pluggable>


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


code:
<?php

/**
 * 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( $post->status != Post::status( 'deleted' ) && ACL::access_check( $post->get_access(), 'delete' ) ) {
			$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(
				sprintf(_t('Post %1$s (%2$s) restored.'), $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(sprintf( _t('Permanently deleted %d posts'), $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();
				humanMsg.displayMsg( 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">
	<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>