<?php
/**
 * XML-RPC protocol support for bbPress
 *
 * @since 1.0
 * @package bbPress
 */



/**
 * Whether this is an XML-RPC Request
 *
 * @since 1.0
 * @var bool
 */
define( 'XMLRPC_REQUEST', true );

// Get rid of cookies sent by some browser-embedded clients
$_COOKIE = array();

// A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default
if ( !isset( $HTTP_RAW_POST_DATA ) ) {
	$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
}

// Fix for mozBlog and other cases where '<?xml' isn't on the very first line
if ( isset( $HTTP_RAW_POST_DATA ) ) {
	$HTTP_RAW_POST_DATA = trim( $HTTP_RAW_POST_DATA );
}

// Load bbPress
require_once( './bb-load.php' );



// If the service discovery data is requested then return it and exit
if ( isset( $_GET['rsd'] ) ) {
	header( 'Content-Type: text/xml; charset=UTF-8', true );
	echo '<?xml version="1.0" encoding="UTF-8"?'.'>' . "\n";
	echo '<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">' . "\n";
	echo '	<service>' . "\n";
	echo '		<engineName>bbPress</engineName>' . "\n";
	echo '		<engineLink>http://bbpress.org/</engineLink>' . "\n";
	echo '		<homePageLink>' . bb_get_uri() . '</homePageLink>' . "\n";
	echo '		<apis>' . "\n";
	echo '			<api name="bbPress" blogID="1" preferred="true" apiLink="' . bb_get_uri( 'xmlrpc.php' ) . '" />' . "\n";
	echo '		</apis>' . "\n";
	echo '	</service>' . "\n";
	echo '</rsd>' . "\n";
	exit;
}



// Load the XML-RPC server/client classes
require_once( BACKPRESS_PATH . '/class.ixr.php' );



/**
 * XML-RPC server class to allow for remote publishing
 *
 * @since 1.0
 * @package bbPress
 * @subpackage Publishing
 * @uses class IXR_Server
 */
class BB_XMLRPC_Server extends IXR_Server
{
	/**
	 * Stores the last error generated by the class
	 *
	 * @since 1.0
	 * @var object|boolean An instance of the IXR_Error class or false if no error exists
	 */
	var $error = false;

	/**
	 * Site options which can be manipulated using XML-RPC
	 *
	 * @since 1.0
	 * @var array
	 */
	var $site_options = array();

	/**
	 * Whether read-only methods require authentication
	 *
	 * @since 1.0
	 * @var boolean
	 **/
	var $auth_readonly = false;

	/**
	 * Whether user switching is allowed
	 *
	 * @since 1.0
	 * @var boolean
	 **/
	var $allow_user_switching = false;

	/**
	 * Initialises the XML-RPC server
	 *
	 * @since 1.0
	 * @return void
	 */
	function BB_XMLRPC_Server()
	{
		// bbPress publishing API
		if ( bb_get_option( 'enable_xmlrpc' ) ) {
			$this->methods = array(
				// - Demo
				'demo.sayHello'         => 'this:sayHello',
				'demo.addTwoNumbers'    => 'this:addTwoNumbers',
				// - Forums
				'bb.getForumCount'      => 'this:bb_getForumCount',
				'bb.getForums'          => 'this:bb_getForums',
				'bb.getForum'           => 'this:bb_getForum',
				'bb.newForum'           => 'this:bb_newForum',
				'bb.editForum'          => 'this:bb_editForum',
				'bb.deleteForum'        => 'this:bb_deleteForum',
				// - Topics
				'bb.getTopicCount'      => 'this:bb_getTopicCount',
				'bb.getTopics'          => 'this:bb_getTopics',
				'bb.getTopic'           => 'this:bb_getTopic',
				'bb.newTopic'           => 'this:bb_newTopic',
				'bb.editTopic'          => 'this:bb_editTopic',
				'bb.deleteTopic'        => 'this:bb_deleteTopic',        // Also undeletes
				'bb.moveTopic'          => 'this:bb_moveTopic',
				'bb.stickTopic'         => 'this:bb_stickTopic',         // Also unsticks
				'bb.closeTopic'         => 'this:bb_closeTopic',         // Also opens
				'bb.getTopicStatusList' => 'this:bb_getTopicStatusList',
				// - Posts (replies)
				'bb.getPostCount'      => 'this:bb_getPostCount',
				'bb.getPosts'          => 'this:bb_getPosts',
				'bb.getPost'           => 'this:bb_getPost',
				'bb.newPost'           => 'this:bb_newPost',
				'bb.editPost'          => 'this:bb_editPost',
				'bb.deletePost'        => 'this:bb_deletePost',          // Also undeletes
				'bb.getPostStatusList' => 'this:bb_getPostStatusList',
				// - Topic Tags
				'bb.getHotTopicTags'   => 'this:bb_getHotTopicTags',
				'bb.getTopicTagCount'  => 'this:bb_getTopicTagCount',
				'bb.getTopicTags'      => 'this:bb_getTopicTags',
				'bb.getTopicTag'       => 'this:bb_getTopicTag',
				'bb.addTopicTags'      => 'this:bb_addTopicTags',
				'bb.removeTopicTags'   => 'this:bb_removeTopicTags',
				'bb.renameTopicTag'    => 'this:bb_renameTopicTag',
				'bb.mergeTopicTags'    => 'this:bb_mergeTopicTags',
				'bb.destroyTopicTag'   => 'this:bb_destroyTopicTag',
				// - Options
				'bb.getOptions'        => 'this:bb_getOptions',
				'bb.setOptions'        => 'this:bb_setOptions'
			);
		}

		// Pingback
		if ( bb_get_option( 'enable_pingback' ) ) {
			$this->methods = array_merge( (array)$this->methods, array(
				'pingback.ping' => 'this:pingback_ping',
				'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks'
			) );
		}

		// Tells read-only methods whether they require authentication or not
		$this->auth_readonly = apply_filters( 'bb_xmlrpc_auth_readonly', $this->auth_readonly );

		// Whether or not to allow user switching
		$this->allow_user_switching = bb_get_option( 'bb_xmlrpc_allow_user_switching' );

		$this->initialise_site_option_info();
		$this->methods = apply_filters( 'bb_xmlrpc_methods', $this->methods );
		$this->IXR_Server( $this->methods );
	}



	/**
	 * Utility methods
	 */

	/**
	 * Checks the user credentials supplied in the request to make sure they are valid
	 *
	 * @since 1.0
	 * @return integer|boolean The user id if the user is valid, otherwise false
	 * @param string $user_login The users login
	 * @param string $user_pass The users password in plain text
	 * @param string $capability The capability to check (optional)
	 * @param string $message The message to pass back in the error if the capability check fails (optional)
	 */
	function authenticate( $user_login, $user_pass, $capability = 'read', $message = false )
	{
		if ( is_array( $user_login ) ) {
			$auth_user_login = (string) $user_login[0];
			$switch_user_login = (string) $user_login[1];
		} else {
			$auth_user_login = (string) $user_login;
			$switch_user_login = false;
		}
		
		// Check the login
		$user = bb_check_login( $auth_user_login, $user_pass );
		if ( !$user || is_wp_error( $user ) ) {
			$this->error = new IXR_Error( 403, __( 'Authentication failed.' ) );
			return false;
		}

		// Set the current user
		$user = bb_set_current_user( $user->ID );

		// Make sure they are allowed to do this
		if ( !bb_current_user_can( $capability ) ) {
			if ( !$message ) {
				$message = __( 'You do not have permission to read this.' );
			}
			$this->error = new IXR_Error( 403, $message );
			return false;
		}

		// Switch the user if requested and allowed
		if ( $switch_user_login && $this->allow_user_switching && bb_current_user_can( 'edit_users' ) ) {
			$user = $this->switch_user( $switch_user_login, $capability, $message );
		}

		return $user;
	}

	/**
	 * Switches the currently active user for incognito actions
	 *
	 * @since 1.0
	 * @return integer|boolean The user id if the user is valid, otherwise false
	 * @param string $user_login The users login
	 * @param string $capability The capability to check (optional)
	 * @param string $message The message to pass back in the error if the capability check fails (optional)
	 */
	function switch_user( $user_login, $capability = 'read', $message = false )
	{
		// Just get the user, authentication has already been established by the 
		$user = bb_get_user( $user_login, array( 'by' => 'login' ) );
		if ( !$user || is_wp_error( $user ) ) {
			$this->error = new IXR_Error( 400, __( 'User switching failed, the requested user does not exist.' ) );
			return false;
		}

		// Set the current user
		$user = bb_set_current_user( $user->ID );

		// Make sure they are allowed to do this
		if ( !bb_current_user_can( $capability ) ) {
			if ( !$message ) {
				$message = __( 'You do not have permission to read this.' );
			}
			$this->error = new IXR_Error( 403, $message );
			return false;
		}

		return $user;
	}

	/**
	 * Sanitises data from XML-RPC request parameters
	 *
	 * @since 1.0
	 * @return mixed The sanitised variable, should come back with the same type
	 * @param $array mixed The variable to be sanitised
	 * @uses $bbdb BackPress database class instance
	 */
	function escape( &$array )
	{
		global $bbdb;

		if ( !is_array( $array ) ) {
			// Escape it
			$array = $bbdb->escape( $array );
		} elseif ( count( $array ) ) {
			foreach ( (array) $array as $k => $v ) {
				if ( is_array( $v ) ) {
					// Recursively sanitize arrays
					$this->escape( $array[$k] );
				} elseif ( is_object( $v ) ) {
					// Don't sanitise objects - shouldn't happen anyway
				} else {
					// Escape it
					$array[$k] = $bbdb->escape( $v );
				}
			}
		}

		return $array;
	}

	/**
	 * Prepares forum data for return in an XML-RPC object
	 *
	 * @since 1.0
	 * @return array The prepared forum data
	 * @param array|object The unprepared forum data
	 **/
	function prepare_forum( $forum )
	{
		// Cast to an array
		$_forum = (array) $forum;
		// Set the URI
		$_forum['forum_uri'] = get_forum_link( $_forum['forum_id'] );
		// Give this a definite value
		if ( !isset( $_forum['forum_is_category'] ) ) {
			$_forum['forum_is_category'] = 0;
		}
		// Allow plugins to modify the data
		return apply_filters( 'bb_xmlrpc_prepare_forum', $_forum, (array) $forum );
	}

	/**
	 * Prepares topic data for return in an XML-RPC object
	 *
	 * @since 1.0
	 * @return array The prepared topic data
	 * @param array|object The unprepared topic data
	 **/
	function prepare_topic( $topic )
	{
		// Cast to an array
		$_topic = (array) $topic;
		// Set the URI
		$_topic['topic_uri'] = get_topic_link( $_topic['topic_id'] );
		// Set readable times
		$_topic['topic_start_time_since'] = bb_since( $_topic['topic_start_time'] );
		$_topic['topic_time_since'] = bb_since( $_topic['topic_time'] );
		// Set the display names
		$_topic['topic_poster_display_name'] = get_user_display_name( $_topic['topic_poster'] );
		$_topic['topic_last_poster_display_name'] = get_user_display_name( $_topic['topic_last_poster'] );
		// Remove some sensitive user ids
		unset(
			$_topic['topic_poster'],
			$_topic['topic_last_poster']
		);
		// Allow plugins to modify the data
		return apply_filters( 'bb_xmlrpc_prepare_topic', $_topic, (array) $topic );
	}

	/**
	 * Prepares post data for return in an XML-RPC object
	 *
	 * @since 1.0
	 * @return array The prepared post data
	 * @param array|object The unprepared post data
	 **/
	function prepare_post( $post )
	{
		// Cast to an array
		$_post = (array) $post;
		// Set the URI
		$_post['post_uri'] = get_post_link( $_post['post_id'] );
		// Set readable times
		$_post['post_time_since'] = bb_since( $_post['post_time'] );
		// Set the display names
		$_post['poster_display_name'] = get_user_display_name( $_post['poster_id'] );
		// Remove some sensitive data
		unset(
			$_post['poster_id'],
			$_post['poster_ip'],
			$_post['pingback_queued']
		);
		// Allow plugins to modify the data
		return apply_filters( 'bb_xmlrpc_prepare_post', $_post, (array) $post );
	}

	/**
	 * Prepares topic tag data for return in an XML-RPC object
	 *
	 * @since 1.0
	 * @return array The prepared topic tag data
	 * @param array|object The unprepared topic tag data
	 **/
	function prepare_topic_tag( $tag )
	{
		// Cast to an array
		$_tag = (array) $tag;
		// Set the URI
		$_tag['topic_tag_uri'] = bb_get_tag_link( $tag );
		// Consistent nomenclature
		$_tag['topic_tag_name'] = (string) $_tag['name'];
		$_tag['topic_tag_slug'] = (string) $_tag['slug'];
		$_tag['topic_tag_count'] = (int) $_tag['count'];
		// Remove some sensitive data
		unset(
			$_tag['object_id'],
			$_tag['name'],
			$_tag['slug'],
			$_tag['count'],
			$_tag['term_id'],
			$_tag['term_group'],
			$_tag['term_taxonomy_id'],
			$_tag['taxonomy'],
			$_tag['description'],
			$_tag['parent'],
			$_tag['count'],
			$_tag['user_id'],
			$_tag['tag_id'],
			$_tag['tag'],
			$_tag['raw_tag'],
			$_tag['tag_count']
		);
		// Allow plugins to modify the data
		return apply_filters( 'bb_xmlrpc_prepare_topic_tag', $_tag, (array) $tag );
	}



	/**
	 * bbPress publishing API - Demo XML-RPC methods
	 */

	/**
	 * Hello world demo function for XML-RPC
	 *
	 * @since 1.0
	 * @return string The phrase 'Hello!'
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 *
	 * XML-RPC request to get a greeting
	 * <methodCall>
	 *     <methodName>demo.sayHello</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function sayHello( $args )
	{
		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly && !$this->authenticate( $username, $password ) ) {
			return $this->error;
		}

		return 'Hello!';
	}

	/**
	 * Adds two numbers together as a demo of XML-RPC
	 *
	 * @since 1.0
	 * @return integer The sum of the two supplied numbers
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer $args[2] The first number to be added
	 * @param integer $args[3] The second number to be added
	 *
	 * XML-RPC request to get the sum of two numbers
	 * <methodCall>
	 *     <methodName>demo.addTwoNumbers</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>5</int></value></param>
	 *         <param><value><int>102</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function addTwoNumbers( $args )
	{
		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly && !$this->authenticate( $username, $password ) ) {
			return $this->error;
		}

		$number1 = (int) $args[2];
		$number2 = (int) $args[3];

		return ( $number1 + $number2 );
	}



	/**
	 * bbPress publishing API - Forum XML-RPC methods
	 */

	/**
	 * Returns a numerical count of forums
	 *
	 * @since 1.0
	 * @return integer|object The number of forums when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The parent forum's id or slug (optional)
	 * @param integer $args[3] The depth of child forums to retrieve (optional)
	 *
	 * XML-RPC request to get a count of all forums in the bbPress instance
	 * <methodCall>
	 *     <methodName>bb.getForumCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get a count of all child forums in the forum with id number 34
	 * <methodCall>
	 *     <methodName>bb.getForumCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get a count of all child forums in the forum with slug "first-forum"
	 * <methodCall>
	 *     <methodName>bb.getForumCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>first-forum</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get a count of all child forums in the forum with id number 34 no more than 2 forums deep in the hierarchy
	 * <methodCall>
	 *     <methodName>bb.getForumCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *         <param><value><int>2</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getForumCount( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getForumCount' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getForumCount' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Setup an array to store arguments to pass to bb_get_forums() function
		$get_forums_args = array(
			'child_of' => 0,
			'hierarchical' => 0,
			'depth' => 0
		);

		// Can be numeric id or slug
		$forum_id = isset( $args[2] ) ? $args[2] : false;

		if ( $forum_id ) {
			// Check for bad data
			if ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
				return $this->error;
			}
			// Check the requested forum exists
			if ( !$forum = bb_get_forum( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum does not exist.' ) );
				return $this->error;
			}
			// Add the specific forum to the arguments
			$get_forums_args['child_of'] = (int) $forum->forum_id;
		}

		// Can only be an integer
		$depth = (int) $args[3];

		if ( $depth > 0 ) {
			// Add the depth to traverse to the arguments
			$get_forums_args['depth'] = $depth;
			// Only make it hierarchical if the depth > 1
			if ( $depth > 1 ) {
				$get_forums_args['hierarchical'] = 1;
			}
		}

		// Get the forums. Return 0 when no forums exist
		if ( !$forums = bb_get_forums( $get_forums_args ) ) {
			$count = 0;
		} else {
			$count = count( $forums );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getForumCount' );

		// Return a count of the forums
		return $count;
	}

	/**
	 * Returns details of multiple forums
	 *
	 * @since 1.0
	 * @return array|object An array containing details of all returned forums when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The parent forum's id or slug (optional)
	 * @param integer $args[3] The depth of child forums to retrieve (optional)
	 *
	 * XML-RPC request to get all forums in the bbPress instance
	 * <methodCall>
	 *     <methodName>bb.getForums</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get all child forums in the forum with id number 34
	 * <methodCall>
	 *     <methodName>bb.getForums</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get all child forums in the forum with slug "first-forum"
	 * <methodCall>
	 *     <methodName>bb.getForums</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>first-forum</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get all child forums in the forum with id number 34 no more than 2 forums deep in the hierarchy
	 * <methodCall>
	 *     <methodName>bb.getForums</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *         <param><value><int>2</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getForums( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getForums' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getForums' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Setup an array to store arguments to pass to bb_get_forums() function
		$get_forums_args = array(
			'child_of' => 0,
			'hierarchical' => 0,
			'depth' => 0
		);

		// Can be numeric id or slug
		$forum_id = isset( $args[2] ) ? $args[2] : false;

		if ( $forum_id ) {
			// Check for bad data
			if ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
				return $this->error;
			}
			// First check the requested forum exists
			if ( !$forum = bb_get_forum( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum does not exist.' ) );
				return $this->error;
			}
			// Add the specific forum to the arguments
			$get_forums_args['child_of'] = (int) $forum->forum_id;
		}

		// Can only be an integer
		$depth = (int) $args[3];

		if ( $depth > 0 ) {
			// Add the depth to traverse to to the arguments
			$get_forums_args['depth'] = $depth;
			// Only make it hierarchical if the depth > 1
			if ( $depth > 1 ) {
				$get_forums_args['hierarchical'] = 1;
			}
		}

		// Get the forums. Return an error when no forums exist
		if ( !$forums = bb_get_forums( $get_forums_args ) ) {
			$this->error = new IXR_Error( 404, __( 'No forums found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$_forums = array();
		foreach ( $forums as $forum ) {
			$_forums[] = $this->prepare_forum( $forum );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getForums' );

		// Return the forums
		return $_forums;
	}

	/**
	 * Returns details of a forum
	 *
	 * @since 1.0
	 * @return array|object An array containing details of the returned forum when successfully executed or an IXR_Error object on failure
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The forum's id or slug
	 *
	 * XML-RPC request to get the forum with id number 34
	 * <methodCall>
	 *     <methodName>bb.getForum</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get the forum with slug "first-forum"
	 * <methodCall>
	 *     <methodName>bb.getForum</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>first-forum</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getForum( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getForum' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getForum' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$forum_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$forum_id || ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
			return $this->error;
		}

		// Check the requested forum exists
		if ( !$forum = bb_get_forum( $forum_id ) ) {
			$this->error = new IXR_Error( 404, __( 'No forum found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$forum = $this->prepare_forum( $forum );

		do_action( 'bb_xmlrpc_call_return', 'bb.getForum' );

		// Return the forums
		return $forum;
	}

	/**
	 * Creates a new forum
	 *
	 * @since 1.0
	 * @return array|object The forum data when successfully created or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The values for the various settings in the new forum
	 * @param string $args[2]['name'] The name of the forum
	 * @param string $args[2]['description'] The description of the forum (optional)
	 * @param integer|string $args[2]['parent_id'] The unique id of the parent forum for this forum (optional)
	 * @param integer $args[2]['order'] The position of the forum in the forum list (optional)
	 * @param integer $args[2]['is_category'] Whether the forum is simply a container category (optional)
	 *
	 * XML-RPC request to create a new sub-forum called "A new forum" inside the parent forum with id 2
	 * <methodCall>
	 *     <methodName>bb.newForum</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>name</name>
	 *                 <value><string>A new forum</string></value>
	 *             </member>
	 *             <member>
	 *                 <name>parent_id</name>
	 *                 <value><integer>2</integer></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_newForum( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.newForum' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'manage_forums', __( 'You do not have permission to manage forums.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.newForum' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum data is invalid.' ) );
			return $this->error;
		}

		$structure = (array) $args[2];

		// Minimum requirement is a name for the new forum
		if ( !isset( $structure['name'] ) || !$structure['name'] ) {
			$this->error = new IXR_Error( 400, __( 'The forum name is invalid.' ) );
			return $this->error;
		}

		// Inject structure into an array suitable for bb_new_forum()
		$bb_new_forum_args = array(
			'forum_name' => (string) $structure['name'],
			'forum_desc' => (string) $structure['description'],
			'forum_parent' => (int) $structure['parent_id'],
			'forum_order' => (int) $structure['order'],
			'forum_is_category' => (int) $structure['is_category']
		);

		// Remove empty settings so that changes to the defaults in bb_new_forum() are honoured
		$bb_new_forum_args = array_filter( $bb_new_forum_args );

		// Leave the require until the very end
		require_once( BB_PATH . 'bb-admin/includes/functions.bb-admin.php' );

		// Create the forum
		if ( !$forum_id = (int) bb_new_forum( $bb_new_forum_args ) ) {
			$this->error = new IXR_Error( 500, __( 'The forum could not be created.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$forum = $this->prepare_forum( bb_get_forum( $forum_id ) );

		do_action( 'bb_xmlrpc_call_return', 'bb.newForum' );

		return $forum;
	}

	/**
	 * Edits an existing forum
	 *
	 * @since 1.0
	 * @return array|object The forum data when successfully edited or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The values for the various settings in the new forum, at least one must be specified
	 * @param integer|string $args[2]['forum_id'] The unique id of the forum to be edited
	 * @param string $args[2]['name'] The name of the forum (optional)
	 * @param string $args[2]['slug'] The slug for the forum (optional)
	 * @param string $args[2]['description'] The description of the forum (optional)
	 * @param integer $args[2]['parent_id'] The unique id of the parent forum for this forum (optional)
	 * @param integer $args[2]['order'] The position of the forum in the forum list (optional)
	 * @param integer $args[2]['is_category'] Whether the forum is simply a container category (optional)
	 *
	 * XML-RPC request to edit a forum with id 11, changing the description
	 * <methodCall>
	 *     <methodName>bb.editForum</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>forum_id</name>
	 *                 <value><integer>11</integer></value>
	 *             </member>
	 *             <member>
	 *                 <name>description</name>
	 *                 <value><string>This is a great forum for all sorts of reasons.</string></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_editForum( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.editForum' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'manage_forums', __( 'You do not have permission to manage forums.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.editForum' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum data is invalid.' ) );
			return $this->error;
		}

		$structure = (array) $args[2];

		// Can be numeric id or slug
		$forum_id = isset( $structure['forum_id'] ) ? $structure['forum_id'] : false;

		// Check for bad data
		if ( !$forum_id || ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
			return $this->error;
		}

		// Check the requested forum exists
		if ( !$forum = bb_get_forum( $forum_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No forum found.' ) );
			return $this->error;
		}

		// Cast the forum object as an array
		$forum = (array) $forum;
		// The forum id may have been a slug, so make sure it's an integer here
		$forum_id = (int) $forum['forum_id'];

		// Remove some unneeded indexes
		unset( $forum['topics'] );
		unset( $forum['posts'] );

		// Add one if it isn't there
		if ( !isset( $forum['forum_is_category'] ) ) {
			$forum['forum_is_category'] = 0;
		}

		// Validate the name for the forum
		if ( isset( $structure['name'] ) && !$structure['name'] ) {
			$this->error = new IXR_Error( 400, __( 'The forum name is invalid.' ) );
			return $this->error;
		}

		// Inject structure into an array suitable for bb_update_forum()
		$bb_update_forum_args = array(
			'forum_name' => $structure['name']
		);

		// Slug cannot be blank
		if ( isset( $structure['slug'] ) && $structure['slug'] !== '' ) {
			$bb_update_forum_args['forum_slug'] = $structure['slug'];
		}

		// Description can be nothing
		if ( isset( $structure['description'] ) ) {
			$bb_update_forum_args['forum_desc'] = $structure['description'];
		}

		// Parent forum ID must be an integer and it can be 0
		if ( isset( $structure['parent_id'] ) && is_integer( $structure['parent_id'] ) ) {
			$bb_update_forum_args['forum_parent'] = $structure['parent_id'];
		}

		// Order must be an integer and it can be 0
		if ( isset( $structure['order'] ) && is_integer( $structure['order'] ) ) {
			$bb_update_forum_args['forum_order'] = $structure['order'];
		}

		// Category flag must be an integer and it can be 0
		if ( isset( $structure['is_category'] ) && is_integer( $structure['is_category'] ) ) {
			$bb_update_forum_args['forum_is_category'] = $structure['is_category'];
		}

		// Merge the changes into the existing data for the forum
		$bb_update_forum_args = wp_parse_args( $bb_update_forum_args, $forum );

		// Leave the require until the very end
		require_once( BB_PATH . 'bb-admin/includes/functions.bb-admin.php' );

		// Update the forum
		if ( !bb_update_forum( $bb_update_forum_args ) ) {
			$this->error = new IXR_Error( 500, __( 'The forum could not be edited.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$forum = $this->prepare_forum( bb_get_forum( $forum_id ) );

		do_action( 'bb_xmlrpc_call_return', 'bb.editForum' );

		return $forum;
	}

	/**
	 * Deletes a forum
	 *
	 * @since 1.0
	 * @return integer|object 1 when successfully deleted or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The unique id of the forum to be deleted
	 *
	 * XML-RPC request to delete a forum with the slug "naughty-forum"
	 * <methodCall>
	 *     <methodName>bb.deleteForum</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>naughty-forum</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_deleteForum( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.deleteForum' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'delete_forums', __( 'You do not have permission to delete forums.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.deleteForum' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$forum_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$forum_id || ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
			return $this->error;
		}

		// Check the requested forum exists
		if ( !$forum = bb_get_forum( $forum_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No forum found.' ) );
			return $this->error;
		}

		// Cast the forum object as an array
		$forum = (array) $forum;
		// The forum id may have been a slug, so make sure it's an integer here
		$forum_id = (int) $forum['forum_id'];

		// Make sure they are allowed to delete this forum specifically
		if ( !bb_current_user_can( 'delete_forum', $forum_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to delete this forum.' ) );
			return $this->error;
		}

		// Leave the require until the very end
		require_once( BB_PATH . 'bb-admin/includes/functions.bb-admin.php' );

		// Delete the forum
		if ( !bb_delete_forum( $forum_id ) ) {
			$this->error = new IXR_Error( 500, __( 'The forum could not be deleted.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.deleteForum' );

		return $result;
	}



	/**
	 * bbPress publishing API - Topic XML-RPC methods
	 */

	/**
	 * Returns a numerical count of topics
	 *
	 * @since 1.0
	 * @return integer|object The number of topics when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The forum id or slug (optional)
	 *
	 * XML-RPC request to get a count of all topics in the bbPress instance
	 * <methodCall>
	 *     <methodName>bb.getTopicCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get a count of all topics in the forum with id number 34
	 * <methodCall>
	 *     <methodName>bb.getTopicCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get a count of all topics in the forum with slug "first-forum"
	 * <methodCall>
	 *     <methodName>bb.getTopicCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>first-forum</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getTopicCount( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getTopicCount' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getTopicCount' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		if ( isset( $args[2] ) && $forum_id = $args[2] ) {
			// Check for bad data
			if ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
				return $this->error;
			}
			// Check the requested forum exists
			if ( !$forum = bb_get_forum( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum does not exist.' ) );
				return $this->error;
			}

			// OK, let's trust the count in the forum table
			$count = (int) $forum->topics;
		} else {
			// Get all forums
			$forums = bb_get_forums();
	
			// Return an error when no forums exist
			if ( !$forums ) {
				$this->error = new IXR_Error( 400, __( 'No forums found.' ) );
				return $this->error;
			}

			// Count the topics
			$count = 0;
			foreach ( $forums as $forum ) {
				$count += (int) $forum->topics;
			}
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getTopicCount' );

		// Return the count of topics
		return $count;
	}

	/**
	 * Returns details of the latest topics
	 *
	 * @since 1.0
	 * @return array|object The topics when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The forum id or slug (optional)
	 * @param integer $args[3] The number of topics to return (optional)
	 * @param integer $args[4] The number of the page to return (optional)
	 *
	 * XML-RPC request to get all topics in the bbPress instance
	 * <methodCall>
	 *     <methodName>bb.getTopics</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get all topics in the forum with id number 34
	 * <methodCall>
	 *     <methodName>bb.getTopics</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>34</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get topics 6 to 10 in the forum with slug "first-forum"
	 * <methodCall>
	 *     <methodName>bb.getTopics</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>first-forum</string></value></param>
	 *         <param><value><int>5</int></value></param>
	 *         <param><value><int>2</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getTopics( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getTopics' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getTopics' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Setup an array to store arguments to pass to get_topics() function
		$get_topics_args = array(
			'forum' => false,
			'number' => false,
			'page' => false
		);

		// Can be numeric id or slug
		if ( isset( $args[2] ) && $forum_id = $args[2] ) {
			// Check for bad data
			if ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
				return $this->error;
			}
			// Check the requested forum exists
			if ( !$forum = bb_get_forum( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum does not exist.' ) );
				return $this->error;
			}

			// The forum id may have been a slug, so make sure it's an integer here
			$get_topics_args['forum'] = (int) $forum->forum_id;
		}

		// Can only be an integer
		if ( isset( $args[3] ) && $number = (int) $args[3] ) {
			$get_topics_args['number'] = $number;
		}

		// Can only be an integer
		if ( isset( $args[4] ) && $page = (int) $args[4] ) {
			$get_topics_args['page'] = $page;
		}

		// Get the topics
		if ( !$topics = get_latest_topics( $get_topics_args ) ) {
			$this->error = new IXR_Error( 400, __( 'No topics found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$_topics = array();
		foreach ( $topics as $topic ) {
			$_topics[] = $this->prepare_topic( $topic );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getTopics' );

		// Return the topics
		return $_topics;
	}

	/**
	 * Returns details of a topic
	 *
	 * @since 1.0
	 * @return array|object An array containing details of the returned topic when successfully executed or an IXR_Error object on failure
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The topic's id or slug
	 *
	 * XML-RPC request to get the topic with id number 105
	 * <methodCall>
	 *     <methodName>bb.getTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>105</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get the topic with slug "cheesy-biscuits"
	 * <methodCall>
	 *     <methodName>bb.getTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>cheesy-biscuits</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$topic = $this->prepare_topic( $topic );

		do_action( 'bb_xmlrpc_call_return', 'bb.getTopic' );

		// Return the topic
		return $topic;
	}

	/**
	 * Creates a new topic
	 *
	 * @since 1.0
	 * @return array|object The topic data when successfully created or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The values for the various parameters in the new topic
	 * @param string $args[2]['title'] The title of the topic
	 * @param string $args[2]['text'] The text of the topic
	 * @param integer|string $args[2]['forum_id'] The unique id of the forum which will contain this topic, slugs are OK to use too
	 * @param string|array $args[2]['tags'] A comma delimited string or an array of tags to add to the topic (optional)
	 *
	 * XML-RPC request to create a new topic called "Insane monkeys" inside the forum with id 2
	 * <methodCall>
	 *     <methodName>bb.newTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>title</name>
	 *                 <value><string>Insane monkeys</string></value>
	 *             </member>
	 *             <member>
	 *                 <name>text</name>
	 *                 <value><string>I just saw some insane monkeys eating bananas, did anyone else see that?</string></value>
	 *             </member>
	 *             <member>
	 *                 <name>forum_id</name>
	 *                 <value><integer>2</integer></value>
	 *             </member>
	 *             <member>
	 *                 <name>tags</name>
	 *                 <value><string>monkeys, bananas</string></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_newTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.newTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'write_topics', __( 'You do not have permission to write topics.' ) );

		// Additionally they need to be able to write posts
		if ( !$this->error && !bb_current_user_can( 'write_posts' ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to write posts.' ) );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.newTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic data is invalid.' ) );
			return $this->error;
		}

		$structure = (array) $args[2];

		// Can be numeric id or slug
		$forum_id = isset( $structure['forum_id'] ) ? $structure['forum_id'] : false;

		// Check for bad data
		if ( !$forum_id || ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
			return $this->error;
		}

		// Check the requested forum exists
		if ( !$forum = bb_get_forum( $forum_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No forum found.' ) );
			return $this->error;
		}

		// The forum id may have been a slug, so make sure it's an integer here
		$forum_id = (int) $forum->forum_id;

		// Make sure they are allowed to write topics to this forum
		if ( !bb_current_user_can( 'write_topic', $forum_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to write topics to this forum.' ) );
			return $this->error;
		}

		// The topic requires a title
		if ( !isset( $structure['title'] ) || !$structure['title'] ) {
			$this->error = new IXR_Error( 400, __( 'The topic title is invalid.' ) );
			return $this->error;
		}

		// The topic requires text
		if ( !isset( $structure['text'] ) || !$structure['text'] ) {
			$this->error = new IXR_Error( 400, __( 'The topic text is invalid.' ) );
			return $this->error;
		}

		// Inject structure into an array suitable for bb_insert_topic()
		$bb_insert_topic_args = array(
			'topic_title' => (string) $structure['title'],
			'forum_id' => $forum_id,
			'tags' => (string) trim( $structure['tags'] )
		);

		// Remove empty settings so that changes to the defaults in bb_insert_topic() are honoured
		$bb_insert_topic_args = array_filter( $bb_insert_topic_args );

		// Create the topic
		if ( !$topic_id = bb_insert_topic( $bb_insert_topic_args ) ) {
			$this->error = new IXR_Error( 500, __( 'The topic could not be created.' ) );
			return $this->error;
		}

		// Inject structure into an array suitable for bb_insert_post()
		$bb_insert_post_args = array(
			'topic_id' => (int) $topic_id,
			'post_text' => (string) $structure['text']
		);

		// Create the post
		if ( !$post_id = bb_insert_post( $bb_insert_post_args ) ) {
			$this->error = new IXR_Error( 500, __( 'The post could not be created.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$topic = $this->prepare_topic( get_topic( $topic_id ) );

		do_action( 'bb_xmlrpc_call_return', 'bb.newTopic' );

		return $topic;
	}

	/**
	 * Edits an existing topic
	 *
	 * @since 1.0
	 * @return array|object The topic data when successfully edited or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The values for the various parameters in the edited topic
	 * @param integer|string $args[2]['topic_id'] The topic's id or slug
	 * @param string $args[2]['title'] The title of the topic
	 * @param string $args[2]['text'] The text of the topic
	 *
	 * XML-RPC request to edit the title of a topic with the slug "insane-monkeys"
	 * <methodCall>
	 *     <methodName>bb.editTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>topic_id</name>
	 *                 <value><string>insane-monkeys</string></value>
	 *             </member>
	 *             <member>
	 *                 <name>title</name>
	 *                 <value><string>Very insane monkeys</string></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_editTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.editTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'edit_topics', __( 'You do not have permission to edit topics.' ) );

		// Additionally they need to be able to edit posts
		if ( !$this->error && !bb_current_user_can( 'edit_posts' ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to edit posts.' ) );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.editTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic data is invalid.' ) );
			return $this->error;
		}

		$structure = (array) $args[2];

		// Can be numeric id or slug
		$topic_id = isset( $structure['topic_id'] ) ? $structure['topic_id'] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Make sure they are allowed to edit this topic
		if ( !bb_current_user_can( 'edit_topic', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to edit this topic.' ) );
			return $this->error;
		}

		// Get the first post in the topic (that's where the content is)
		if ( !$post = bb_get_first_post( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No posts found.' ) );
			return $this->error;
		}

		$post_id = (int) $post->post_id;

		// Make sure they are allowed to edit this post
		if ( !bb_current_user_can( 'edit_post', $post_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to edit this post.' ) );
			return $this->error;
		}

		// The topic requires a title
		if ( isset( $structure['title'] ) && !$structure['title'] ) {
			$this->error = new IXR_Error( 400, __( 'The topic title is invalid.' ) );
			return $this->error;
		}

		// The topic requires text
		if ( isset( $structure['text'] ) && !$structure['text'] ) {
			$this->error = new IXR_Error( 400, __( 'The topic text is invalid.' ) );
			return $this->error;
		}

		if ( $structure['title'] ) {
			if ( !bb_insert_topic( array( 'topic_title' => (string) $structure['title'], 'topic_id' => $topic_id ) ) ) {
				$this->error = new IXR_Error( 500, __( 'The topic could not be edited.' ) );
				return $this->error;
			}
		}

		if ( $structure['text'] ) {
			if ( !bb_insert_post( array( 'post_text' => (string) $structure['text'], 'post_id' => $post_id, 'topic_id'=> $topic_id ) ) ) {
				$this->error = new IXR_Error( 500, __( 'The post could not be edited.' ) );
				return $this->error;
			}
		}

		// Only include "safe" data in the array
		$topic = $this->prepare_topic( get_topic( $topic_id ) );

		do_action( 'bb_xmlrpc_call_return', 'bb.editTopic' );

		return $topic;
	}

	/**
	 * Deletes a topic
	 *
	 * @since 1.0
	 * @return integer|object 0 if already changed, 1 when successfully changed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The unique id of the topic to be deleted
	 * @param integer $args[3] 1 deletes the topic, 0 undeletes the topic
	 *
	 * XML-RPC request to delete a topic with id of 34
	 * <methodCall>
	 *     <methodName>bb.deleteTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><integer>34</integer></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_deleteTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.deleteTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'delete_topics', __( 'You do not have permission to delete topics.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.deleteTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		$delete = isset( $args[3] ) ? (int) $args[3] : 1;

		// Don't do anything if already set that way
		if ( $delete === (int) $topic->topic_status ) {
			return 0;
		}

		// Make sure they are allowed to delete this topic
		if ( !bb_current_user_can( 'delete_topic', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to delete this topic.' ) );
			return $this->error;
		}

		// Delete the topic
		if ( !bb_delete_topic( $topic_id, $delete ) ) {
			$this->error = new IXR_Error( 500, __( 'The topic could not be deleted.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.deleteTopic' );

		return $result;
	}

	/**
	 * Moves a topic to a different forum
	 *
	 * @since 1.0
	 * @return integer|object the forum id where the topic lives after the method is called or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The unique id of the topic to be moved
	 * @param integer|string $args[3] The unique id of the forum to be moved to
	 *
	 * XML-RPC request to move the topic with id of 34 to forum with slug of "better-forum"
	 * <methodCall>
	 *     <methodName>bb.moveTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><integer>34</integer></value></param>
	 *         <param><value><string>better-forum</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_moveTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.moveTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'move_topics', __( 'You do not have permission to move topics.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.moveTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Can be numeric id or slug
		$forum_id = isset( $args[3] ) ? $args[3] : false;

		// Check for bad data
		if ( !$forum_id || ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$forum = bb_get_forum( $forum_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No forum found.' ) );
			return $this->error;
		}

		// The forum id may have been a slug, so make sure it's an integer here
		$forum_id = (int) $forum->forum_id;

		// Only move it if it isn't already there
		if ( $forum_id !== (int) $topic->forum_id ) {
			// Make sure they are allowed to move this topic specifically to this forum
			if ( !bb_current_user_can( 'move_topic', $topic_id, $forum_id ) ) {
				$this->error = new IXR_Error( 403, __( 'You are not allowed to move this topic to this forum.' ) );
				return $this->error;
			}

			// Move the topic
			if ( !bb_move_topic( $topic_id, $forum_id ) ) {
				$this->error = new IXR_Error( 500, __( 'The topic could not be moved.' ) );
				return $this->error;
			}
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.moveTopic' );

		return $forum_id;
	}

	/**
	 * Sticks a topic to the top of a forum or the front page
	 *
	 * @since 1.0
	 * @return integer|object 0 if it is already stuck to the desired location, 1 when successfully stuck or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The unique id of the topic to be stuck
	 * @param integer $args[3] 0 unsticks, 1 sticks, 2 sticks to front (optional)
	 *
	 * XML-RPC request to stick the topic with id of 34 to the front page
	 * <methodCall>
	 *     <methodName>bb.stickTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><integer>34</integer></value></param>
	 *         <param><value><integer>1</integer></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_stickTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.stickTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'stick_topics', __( 'You do not have permission to stick topics.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.stickTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Make sure they are allowed to stick this topic
		if ( !bb_current_user_can( 'stick_topic', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to stick this topic.' ) );
			return $this->error;
		}

		// Stick to where?
		$where = isset( $args[3] ) ? (int) $args[3] : 1;

		// Forget it if it's already there
		if ( $where === (int) $topic->topic_sticky ) {
			return 0;
		}

		// Stick the topic
		if ( !bb_stick_topic( $topic_id, $where ) ) {
			$this->error = new IXR_Error( 500, __( 'The topic could not be stuck.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.stickTopic' );

		return $result;
	}



	/**
	 * Closes a topic
	 *
	 * @since 1.0
	 * @return integer|object 0 when already changed, 1 when successfully changed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The unique id of the topic to be closed
	 * @param integer $args[2] 0 closes, 1 opens (optional)
	 *
	 * XML-RPC request to close the topic with slug of "really-old-topic"
	 * <methodCall>
	 *     <methodName>bb.closeTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>really-old-topic</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to open the topic with slug of "really-old-topic"
	 * <methodCall>
	 *     <methodName>bb.closeTopic</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>really-old-topic</string></value></param>
	 *         <param><value><integer>1</integer></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_closeTopic( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.closeTopic' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'close_topics', __( 'You do not have permission to close topics.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.closeTopic' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Make sure they are allowed to close this topic
		if ( !bb_current_user_can( 'close_topic', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to close this topic.' ) );
			return $this->error;
		}

		// Open or close?
		$close = isset( $args[3] ) ? (int) $args[3] : 0;

		// Forget it if it's already matching
		if ( $close === (int) $topic->topic_open ) {
			return 0;
		}

		// Close the topic
		if ( !$close && !bb_close_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 500, __( 'The topic could not be closed.' ) );
			return $this->error;
		}

		// Open the topic
		if ( $close && !bb_open_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 500, __( 'The topic could not be opened.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.closeTopic' );

		return $result;
	}



	/**
	 * bbPress publishing API - Post XML-RPC methods
	 */

	/**
	 * Returns a numerical count of posts
	 *
	 * @since 1.0
	 * @return integer|object The number of topics when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The topic id or slug
	 *
	 * XML-RPC request to get a count of all posts in the topic with slug "countable-topic"
	 * <methodCall>
	 *     <methodName>bb.getPostCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>countable-topic</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getPostCount( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getPostCount' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getPostCount' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// OK, let's trust the count in the topic table
		$count = $topic->topic_posts;

		do_action( 'bb_xmlrpc_call_return', 'bb.getPostCount' );

		// Return the count of posts
		return $count;
	}

	/**
	 * Returns details of the posts in a given topic
	 *
	 * @since 1.0
	 * @return array|object The posts when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The topic id or slug
	 * @param integer $args[3] The number of posts to return (optional)
	 * @param integer $args[4] The number of the page to return (optional)
	 *
	 * XML-RPC request to get all posts in the topic with id number 53
	 * <methodCall>
	 *     <methodName>bb.getPosts</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>53</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get the latest 5 posts in the topic with id number 341
	 * <methodCall>
	 *     <methodName>bb.getPosts</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>341</int></value></param>
	 *         <param><value><int>5</int></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get posts 11 to 20 in the topic with slug "long-topic"
	 * <methodCall>
	 *     <methodName>bb.getPosts</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>long-topic</string></value></param>
	 *         <param><value><int>10</int></value></param>
	 *         <param><value><int>2</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getPosts( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getPosts' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getPosts' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Setup an array to store arguments to pass to get_thread() function
		$get_thread_args = array();

		// Can only be an integer
		if ( isset( $args[3] ) && $per_page = (int) $args[3] ) {
			$get_thread_args['per_page'] = $per_page;
		}

		// Can only be an integer
		if ( isset( $args[4] ) && $page = (int) $args[4] ) {
			$get_thread_args['page'] = $page;
		}

		// Get the posts
		if ( !$posts = get_thread( $topic_id, $get_thread_args ) ) {
			$this->error = new IXR_Error( 500, __( 'No posts found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$_posts = array();
		foreach ( $posts as $post ) {
			$_posts[] = $this->prepare_post( $post );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getPosts' );

		// Return the posts
		return $_posts;
	}

	/**
	 * Returns details of a post
	 *
	 * @since 1.0
	 * @return array|object An array containing details of the returned post when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer $args[2] The post's id
	 *
	 * XML-RPC request to get the post with id number 32
	 * <methodCall>
	 *     <methodName>bb.getPost</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>32</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getPost( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getPost' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getPost' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$post_id = isset( $args[2] ) ? (int) $args[2] : false;

		// Check for bad data
		if ( !$post_id ) {
			$this->error = new IXR_Error( 400, __( 'The post id is invalid.' ) );
			return $this->error;
		}

		// Check the requested post exists
		if ( !$post = bb_get_post( $post_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No post found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$_post = $this->prepare_post( $post );

		do_action( 'bb_xmlrpc_call_return', 'bb.getPost' );

		// Return the post
		return $_post;
	}

	/**
	 * Creates a new post in a given topic
	 *
	 * @since 1.0
	 * @return array|object The post data when successfully created or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The values for the various parameters in the new topic
	 * @param string $args[2]['text'] The text of the topic
	 * @param integer|string $args[2]['topic_id'] The unique id of the topic which will contain this topic, slugs are OK to use too
	 *
	 * XML-RPC request to create a new post in the topic with slug "totally-worth-it"
	 * <methodCall>
	 *     <methodName>bb.newPost</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>text</name>
	 *                 <value><string>I agree, it is totally worth it.</string></value>
	 *             </member>
	 *             <member>
	 *                 <name>topic_id</name>
	 *                 <value><string>totally-worth-it</string></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_newPost( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.newPost' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'write_posts', __( 'You do not have permission to write posts.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.newPost' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The post data is invalid.' ) );
			return $this->error;
		}

		$structure = (array) $args[2];

		// Can be numeric id or slug
		$topic_id = isset( $structure['topic_id'] ) ? $structure['topic_id'] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Make sure they are allowed to write posts to this topic
		if ( !bb_current_user_can( 'write_post', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to write posts to this topic.' ) );
			return $this->error;
		}

		// The post requires text
		if ( !isset( $structure['text'] ) || !$structure['text'] ) {
			$this->error = new IXR_Error( 400, __( 'The post text is invalid.' ) );
			return $this->error;
		}

		// Inject structure into an array suitable for bb_insert_post()
		$bb_insert_post_args = array(
			'topic_id' => $topic_id,
			'post_text' => (string) $structure['text']
		);

		// Create the post
		if ( !$post_id = bb_insert_post( $bb_insert_post_args ) ) {
			$this->error = new IXR_Error( 500, __( 'The post could not be created.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$post = $this->prepare_forum( bb_get_post( $post_id ) );

		do_action( 'bb_xmlrpc_call_return', 'bb.newPost' );

		return $post;
	}

	/**
	 * Edits an existing post
	 *
	 * @since 1.0
	 * @return array|object The post data when successfully edited or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The values for the various parameters in the new topic
	 * @param integer $args[2]['post_id'] The unique id of the post
	 * @param string $args[2]['text'] The text of the topic
	 *
	 * XML-RPC request to edit the text of the post with an id of 452
	 * <methodCall>
	 *     <methodName>bb.editPost</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>post_id</name>
	 *                 <value><int>452</int></value>
	 *             </member>
	 *             <member>
	 *                 <name>text</name>
	 *                 <value><string>For now I will withhold my opinion.</string></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_editPost( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.editPost' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'edit_posts', __( 'You do not have permission to edit posts.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.editPost' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The post data is invalid.' ) );
			return $this->error;
		}

		$structure = (array) $args[2];

		// Can be numeric id or slug
		$post_id = isset( $structure['post_id'] ) ? (int) $structure['post_id'] : false;

		// Check for bad data
		if ( !$post_id ) {
			$this->error = new IXR_Error( 400, __( 'The post id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$post = bb_get_post( $post_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No post found.' ) );
			return $this->error;
		}

		// Re-assign the post id
		$post_id = (int) $post->post_id;

		// Make sure they are allowed to edit this post
		if ( !bb_current_user_can( 'edit_post', $post_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to edit this post.' ) );
			return $this->error;
		}

		// The post requires text
		if ( !isset( $structure['text'] ) || !$structure['text'] ) {
			$this->error = new IXR_Error( 400, __( 'The post text is invalid.' ) );
			return $this->error;
		}

		// Inject structure into an array suitable for bb_insert_post()
		$bb_insert_post_args = array(
			'post_id' => $post_id,
			'post_text' => (string) $structure['text']
		);

		// Create the post
		if ( !$post_id = bb_insert_post( $bb_insert_post_args ) ) {
			$this->error = new IXR_Error( 500, __( 'The post could not be edited.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$post = $this->prepare_forum( bb_get_post( $post_id ) );

		do_action( 'bb_xmlrpc_call_return', 'bb.editPost' );

		return $post;
	}

	/**
	 * Deletes an existing post
	 *
	 * @since 1.0
	 * @return integer|object 1 when successfully deleted, 0 when already deleted or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The unique id of the post
	 * @param array $args[3] 1 deletes the post, 0 undeletes the post (optional)
	 *
	 * XML-RPC request to delete the post with an id of 4301
	 * <methodCall>
	 *     <methodName>bb.editPost</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>4301</int></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_deletePost( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.deletePost' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'delete_posts', __( 'You do not have permission to delete posts.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.deletePost' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$post_id = isset( $args[2] ) ? (int) $args[2] : false;

		// Check for bad data
		if ( !$post_id ) {
			$this->error = new IXR_Error( 400, __( 'The post id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$post = bb_get_post( $post_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No post found.' ) );
			return $this->error;
		}

		// Re-assign the post id
		$post_id = (int) $post->post_id;

		// Make sure they are allowed to delete this post
		if ( !bb_current_user_can( 'delete_post', $post_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to delete this post.' ) );
			return $this->error;
		}

		$status = isset( $args[3] ) ? (int) $args[3] : 1;

		if ( $status === (int) $post->post_status ) {
			return 0;
		}

		// Delete the post
		if ( !$post_id = bb_delete_post( $post_id, $status ) ) {
			$this->error = new IXR_Error( 500, __( 'The post could not be edited.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.deletePost' );

		return $result;
	}



	/**
	 * bbPress publishing API - Topic Tag XML-RPC methods
	 */

	/**
	 * Returns the hot tags in order of hotness in a given forum or all hot tags
	 *
	 * @since 1.0
	 * @return integer|object The tag data when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer $args[2] The number of tags to return (optional)
	 * @param integer|string $args[3] The forum id or slug (optional)
	 *
	 * XML-RPC request to get the 20 hottest tags in the forum with slug "hawtness"
	 * <methodCall>
	 *     <methodName>bb.getTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>20</int></value></param>
	 *         <param><value><string>hawtness</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getHotTopicTags( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getHotTopicTags' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getHotTopicTags' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Must be a number
		$per_page = isset( $args[2] ) ? (integer) $args[2] : false;

		// Can be numeric id or slug
		$forum_id = isset( $args[3] ) ? $args[3] : false;

		if ( $forum_id ) {
			// Check for bad data
			if ( !is_string( $forum_id ) && !is_integer( $forum_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The forum id is invalid.' ) );
				return $this->error;
			}

			// Check the requested forum exists
			if ( !$forum = bb_get_forum( $forum_id ) ) {
				$this->error = new IXR_Error( 404, __( 'No forum found.' ) );
				return $this->error;
			}

			global $bbdb;
			$topic_ids = $bbdb->get_col( $bbdb->prepare( "SELECT topic_id FROM `" . $bbdb->topics . "` WHERE `topic_status` = 0 AND `topic_open` = 1 AND `tag_count` > 0 AND `forum_id` = %s;", $forum_id ) );

			if ( !count( $topic_ids ) ) {
				$this->error = new IXR_Error( 400, __( 'No topics found.' ) );
				return $this->error;
			}

			global $wp_taxonomy_object;
			$tags = $wp_taxonomy_object->get_object_terms( $topic_ids, 'bb_topic_tag', array( 'fields' => 'all_with_object_id', 'orderby' => 'count', 'order' => 'DESC' ) );

			if ( !$tags || is_wp_error( $tags ) ) {
				$this->error = new IXR_Error( 500, __( 'Could not retrieve hot topic tags.' ) );
				return $this->error;
			}
			if ( !count( $tags ) ) {
				$this->error = new IXR_Error( 500, __( 'No hot topic tags found.' ) );
				return $this->error;
			}
			global $bb_log;
			$bb_log->debug($tags);

			for ( $i = 0; isset( $tags[$i] ); $i++ ) {
				_bb_make_tag_compat( $tags[$i] );
			}
			$bb_log->debug($tags);

			// Only include "safe" data in the array
			$_tags = array();
			foreach ( $tags as $tag ) {
				$_tag = $this->prepare_topic_tag( $tag );
				if ( !in_array( $_tag, $_tags ) ) {
					$_tags[] = $_tag;
				}
			}

			if ( $per_page ) {
				$_tags = array_slice( $_tags, 0, $per_page );
			}
		} else {
			if ( !$tags = bb_get_top_tags( array( 'get' => 'all', 'number' => $per_page ) ) ) {
				$this->error = new IXR_Error( 500, __( 'No hot topic tags found.' ) );
				return $this->error;
			}

			// Only include "safe" data in the array
			$_tags = array();
			foreach ( $tags as $tag ) {
				$_tags[] = $this->prepare_topic_tag( $tag );
			}
		}

		do_action( 'bb_xmlrpc_call', 'bb.getHotTopicTags' );

		return $_tags;
	}

	/**
	 * Returns a numerical count of tags in a given topic or all tags
	 *
	 * @since 1.0
	 * @return integer|object The number of topics when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The topic id or slug (optional)
	 *
	 * XML-RPC request to get a count of all tags in the topic with slug "woot-frist-topic"
	 * <methodCall>
	 *     <methodName>bb.getTopicTagCount</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>woot-frist-topic</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getTopicTagCount( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getTopicTagCount' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getTopicTagCount' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( $topic_id ) {
			if ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
				return $this->error;
			}

			// Check the requested topic exists
			if ( !$topic = get_topic( $topic_id ) ) {
				$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
				return $this->error;
			}

			// The topic id may have been a slug, so make sure it's an integer here
			$topic_id = (int) $topic->topic_id;

			// Now get the tags
			if ( !$tags = bb_get_topic_tags( $topic_id ) ) {
				$tags = array();
			}

			// Count the tags
			$count = count( $tags );
		} else {
			global $wp_taxonomy_object;
			$count = $wp_taxonomy_object->count_terms( 'bb_topic_tag' );
			if ( is_wp_error( $count ) ) {
				$this->error = new IXR_Error( 500, __( 'Could not get a count of all topic tags.' ) );
				return $this->error;
			}
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getTopicTagCount' );

		// Return the count of tags
		return $count;
	}

	/**
	 * Returns the tags in a given topic or all tags
	 *
	 * @since 1.0
	 * @return integer|object The tag data when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param integer|string $args[2] The topic id or slug (optional)
	 *
	 * XML-RPC request to get all tags in the topic with slug "woot-frist-topic"
	 * <methodCall>
	 *     <methodName>bb.getTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>woot-frist-topic</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getTopicTags( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getTopicTags' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getTopicTags' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( $topic_id ) {
			if ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) {
				$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
				return $this->error;
			}

			// Check the requested topic exists
			if ( !$topic = get_topic( $topic_id ) ) {
				$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
				return $this->error;
			}

			// The topic id may have been a slug, so make sure it's an integer here
			$topic_id = (int) $topic->topic_id;

			// Now get the tags
			if ( !$tags = bb_get_topic_tags( $topic_id ) ) {
				$this->error = new IXR_Error( 500, __( 'No topic tags found.' ) );
				return $this->error;
			}
		} else {
			global $wp_taxonomy_object;
			$tags = $wp_taxonomy_object->get_terms( 'bb_topic_tag', array( 'get' => 'all' ) );
			if ( is_wp_error( $tags ) ) {
				$this->error = new IXR_Error( 500, __( 'Could not retrieve all topic tags.' ) );
				return $this->error;
			}
			for ( $i = 0; isset( $tags[$i] ); $i++ ) {
				_bb_make_tag_compat( $tags[$i] );
			}
		}

		// Only include "safe" data in the array
		$_tags = array();
		foreach ( $tags as $tag ) {
			$_tags[] = $this->prepare_topic_tag( $tag );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getTopicTags' );

		// Return the tags
		return $_tags;
	}

	/**
	 * Returns the topics which are tagged with the given tag
	 *
	 * @since 1.0
	 * @return integer|object The topic data when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param string $args[2] The tag name or slug
	 * @param integer $args[3] The number of topics to return (optional)
	 * @param integer $args[4] The number of the page to return (optional)
	 *
	 * XML-RPC request to get the latest 10 topics tagged with the tag "apples"
	 * <methodCall>
	 *     <methodName>bb.getTopicTag</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>apples</string></value></param>
	 *         <param><value><string>10</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getTopicTag( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getTopicTag' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getTopicTag' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can only be a string
		$tag_id = isset( $args[2] ) ? (string) $args[2] : false;

		// Check for bad data
		if ( !$tag_id ) {
			$this->error = new IXR_Error( 400, __( 'The tag id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$tag = bb_get_tag( $tag_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No tag found.' ) );
			return $this->error;
		}

		// Get the numeric tag id
		$tag_id = (int) $tag->tag_id;

		// Setup an array to store arguments to pass to get_tagged_topics() function
		$get_topics_args = array(
			'tag_id' => false,
			'number' => false,
			'page' => false
		);

		// Can only be an integer
		if ( isset( $args[3] ) && $number = (int) $args[3] ) {
			$get_topics_args['number'] = $number;
		}

		// Can only be an integer
		if ( isset( $args[4] ) && $page = (int) $args[4] ) {
			$get_topics_args['page'] = $page;
		}

		// Now get the topics
		if ( !$topics = get_tagged_topics( $tag_id ) ) {
			$this->error = new IXR_Error( 500, __( 'No topics found.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$_topics = array();
		foreach ( $topics as $topic ) {
			$_topics[] = $this->prepare_topic( $topic );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getTopicTag' );

		// Return the topics
		return $_topics;
	}

	/**
	 * Adds the specified tags to the specified topic
	 *
	 * @since 1.0
	 * @return array|object The tags which were added when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param string|integer $args[2] The topic id or slug
	 * @param string|array $args[3] The tags to add to the topic
	 *
	 * XML-RPC request to add the tag "banana" to the topic with id 219
	 * <methodCall>
	 *     <methodName>bb.addTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>219</int></value></param>
	 *         <param><value><string>banana</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to add the tags "banana" and "man" to the topic with id 219
	 * <methodCall>
	 *     <methodName>bb.addTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>219</int></value></param>
	 *         <param><value><string>banana, man</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to add the tags "banana" and "man" to the topic with id 219 using an array
	 * <methodCall>
	 *     <methodName>bb.addTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>219</int></value></param>
	 *         <param><value><array>
	 *             <data><value><string>banana</string></value></data>
	 *             <data><value><string>man</string></value></data>
	 *         </array></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_addTopicTags( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.addTopicTags' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'edit_tags', __( 'You do not have permission to edit tags.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.addTopicTags' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Make sure they are allowed to add tags to this topic
		if ( !bb_current_user_can( 'add_tag_to', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to add tags to this topic.' ) );
			return $this->error;
		}

		$tags = isset( $args[3] ) ? $args[3] : false;

		// Check for bad data
		if ( !$tags || ( !is_string( $tags ) && !is_array( $tags ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The tag data is invalid.' ) );
			return $this->error;
		}

		// Add the tags
		if ( !$tag_ids = bb_add_topic_tags( $topic_id, $tags ) ) {
			$this->error = new IXR_Error( 500, __( 'The tags could not be added.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$_tags = array();
		foreach ( $tag_ids as $tag_id ) {
			$_tags[] = $this->prepare_topic_tag( bb_get_tag( $tag_id ) );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.addTopicTags' );

		// Return the tags which were added as an array
		return $_tags;
	}

	/**
	 * Removes the specified tags from the specified topic
	 *
	 * @since 1.0
	 * @return integer|object 1 when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param string|integer $args[2] The topic id or slug
	 * @param string|array $args[3] The tags to remove from the topic
	 *
	 * XML-RPC request to remove the tag "banana" to the topic with id 219
	 * <methodCall>
	 *     <methodName>bb.removeTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>219</int></value></param>
	 *         <param><value><string>banana</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to remove the tags "banana" and "man" to the topic with id 219
	 * <methodCall>
	 *     <methodName>bb.removeTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>219</int></value></param>
	 *         <param><value><string>banana, man</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to remove the tags "banana" and "man" to the topic with id 219 using an array
	 * <methodCall>
	 *     <methodName>bb.removeTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><int>219</int></value></param>
	 *         <param><value><array>
	 *             <data><value><string>banana</string></value></data>
	 *             <data><value><string>man</string></value></data>
	 *         </array></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_removeTopicTags( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.removeTopicTags' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'edit_tags', __( 'You do not have permission to edit tags.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.removeTopicTags' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can be numeric id or slug
		$topic_id = isset( $args[2] ) ? $args[2] : false;

		// Check for bad data
		if ( !$topic_id || ( !is_string( $topic_id ) && !is_integer( $topic_id ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The topic id is invalid.' ) );
			return $this->error;
		}

		// Check the requested topic exists
		if ( !$topic = get_topic( $topic_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No topic found.' ) );
			return $this->error;
		}

		// The topic id may have been a slug, so make sure it's an integer here
		$topic_id = (int) $topic->topic_id;

		// Make sure they are allowed to add tags to this topic
		if ( !bb_current_user_can( 'add_tag_to', $topic_id ) ) {
			$this->error = new IXR_Error( 403, __( 'You do not have permission to remove tags from this topic.' ) );
			return $this->error;
		}

		$tags = isset( $args[3] ) ? $args[3] : false;

		// Check for bad data
		if ( !$tags || ( !is_string( $tags ) && !is_array( $tags ) ) ) {
			$this->error = new IXR_Error( 400, __( 'The tag data is invalid.' ) );
			return $this->error;
		}

		// Add the tags
		if ( !bb_remove_topic_tags( $topic_id, $tags ) ) {
			$this->error = new IXR_Error( 500, __( 'The tags could not be removed.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.removeTopicTags' );

		// Return the result
		return $result;
	}

	/**
	 * Renames the specified tag to a new tag name
	 *
	 * @since 1.0
	 * @return array|object The tag data when successfully renamed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param string $args[2] The tag name or slug
	 * @param string $args[3] The new tag name (slug is auto-generated)
	 *
	 * XML-RPC request to rename the tag "banana" to "bananas"
	 * <methodCall>
	 *     <methodName>bb.renameTopicTag</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>banana</string></value></param>
	 *         <param><value><string>bananas</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_renameTopicTag( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.renameTopicTag' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'manage_tags', __( 'You do not have permission to manage tags.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.renameTopicTag' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can only be a string
		$tag_id = isset( $args[2] ) ? (string) $args[2] : false;

		// Check for bad data
		if ( !$tag_id ) {
			$this->error = new IXR_Error( 400, __( 'The tag id is invalid.' ) );
			return $this->error;
		}

		// Check the requested tag exists
		if ( !$tag = bb_get_tag( $tag_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No tag found.' ) );
			return $this->error;
		}

		// Get the numeric tag id
		$tag_id = (int) $tag->tag_id;

		// Can only be a string
		$tag_name = isset( $args[3] ) ? (string) $args[3] : false;

		// Check for bad data
		if ( !$tag_name || $tag_name == $tag->tag_name ) {
			$this->error = new IXR_Error( 400, __( 'The tag name is invalid.' ) );
			return $this->error;
		}

		// Rename the tag
		if ( !$new_tag = bb_rename_tag( $tag_id, $tag_name ) ) {
			$this->error = new IXR_Error( 500, __( 'The tag could not be renamed.' ) );
			return $this->error;
		}

		// Only include "safe" data in the array
		$new_tag = $this->prepare_topic_tag( $new_tag );

		do_action( 'bb_xmlrpc_call_return', 'bb.renameTopicTag' );

		// Return the tag
		return $new_tag;
	}

	/**
	 * Merges the specified tags
	 *
	 * @since 1.0
	 * @return array|object The tag data when successfully merged or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param string $args[2] The old tag name or slug to be destroyed
	 * @param string $args[3] The new tag name or slug where the old tag will be merged to
	 *
	 * XML-RPC request to merge the tag "banana" into the tag "apple"
	 * <methodCall>
	 *     <methodName>bb.mergeTopicTags</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>banana</string></value></param>
	 *         <param><value><string>apple</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_mergeTopicTags( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.mergeTopicTags' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'manage_tags', __( 'You do not have permission to manage tags.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.mergeTopicTags' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can only be strings
		$old_tag_id = isset( $args[2] ) ? (string) $args[2] : false;
		$new_tag_id = isset( $args[3] ) ? (string) $args[3] : false;

		// Check for bad data
		if ( !$old_tag_id ) {
			$this->error = new IXR_Error( 400, __( 'The old tag id is invalid.' ) );
			return $this->error;
		}
		if ( !$new_tag_id ) {
			$this->error = new IXR_Error( 400, __( 'The new tag id is invalid.' ) );
			return $this->error;
		}

		// Check the requested tags exist
		if ( !$old_tag = bb_get_tag( $old_tag_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No old tag found.' ) );
			return $this->error;
		}
		if ( !$new_tag = bb_get_tag( $new_tag_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No new tag found.' ) );
			return $this->error;
		}

		// Get the numeric tag ids
		$old_tag_id = (int) $old_tag->tag_id;
		$new_tag_id = (int) $new_tag->tag_id;

		// Rename the tag
		if ( !$result = bb_rename_tag( $old_tag_id, $new_tag_id ) ) {
			$this->error = new IXR_Error( 500, __( 'The tags could not be merged.' ) );
			return $this->error;
		}

		// Get the merged tag
		$new_tag = bb_get_tag( $new_tag_id );

		// Only include "safe" data in the array
		$new_tag = $this->prepare_topic_tag( $new_tag );

		do_action( 'bb_xmlrpc_call_return', 'bb.mergeTopicTags' );

		// Return the tag
		return $new_tag;
	}

	/**
	 * Destroys the specified tag
	 *
	 * @since 1.0
	 * @return integer|object 1 when successfully deleted or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param string $args[2] The tag name or slug to be destroyed
	 *
	 * XML-RPC request to destroy the tag "banana"
	 * <methodCall>
	 *     <methodName>bb.destroyTopicTag</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><string>banana</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_destroyTopicTag( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.destroyTopicTag' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'manage_tags', __( 'You do not have permission to manage tags.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.destroyTopicTag' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Can only be a string
		$tag_id = isset( $args[2] ) ? (string) $args[2] : false;

		// Check for bad data
		if ( !$tag_id ) {
			$this->error = new IXR_Error( 400, __( 'The tag id is invalid.' ) );
			return $this->error;
		}

		// Check the requested tag exists
		if ( !$tag = bb_get_tag( $tag_id ) ) {
			$this->error = new IXR_Error( 400, __( 'No tag found.' ) );
			return $this->error;
		}

		// Get the numeric tag id
		$tag_id = (int) $tag->tag_id;

		// Destroy the tag
		if ( !$result = bb_destroy_tag( $tag_id ) ) {
			$this->error = new IXR_Error( 500, __( 'The tag could not be destroyed.' ) );
			return $this->error;
		}

		$result = 1;

		do_action( 'bb_xmlrpc_call_return', 'bb.destroyTopicTag' );

		// Return the tag
		return $result;
	}



	/**
	 * bbPress publishing API - Options XML-RPC methods
	 */

	/**
	 * Initialises site options which can be manipulated using XML-RPC
	 *
	 * @since 1.0
	 * @return void
	 */
	function initialise_site_option_info()
	{
		$this->site_options = array(
			// Read only options
			'software_name'		=> array(
				'desc'			=> __( 'Software Name' ),
				'readonly'		=> true,
				'value'			=> 'bbPress'
			),
			'software_version'	=> array(
				'desc'			=> __( 'Software Version' ),
				'readonly'		=> true,
				'option'		=> 'version'
			),
			'site_url'			=> array(
				'desc'			=> __( 'Site URL' ),
				'readonly'		=> true,
				'option'		=> 'uri'
			),

			// Updatable options
			'site_name'		=> array(
				'desc'			=> __( 'Site Name' ),
				'readonly'		=> false,
				'option'		=> 'name'
			),
			'site_description'	=> array(
				'desc'			=> __( 'Site Description' ),
				'readonly'		=> false,
				'option'		=> 'description'
			),
			'time_zone'			=> array(
				'desc'			=> __( 'Time Zone' ),
				'readonly'		=> false,
				'option'		=> 'gmt_offset'
			),
			'datetime_format'	=> array(
				'desc'			=> __( 'Date/Time Format' ),
				'readonly'		=> false,
				'option'		=> 'datetime_format'
			),
			'date_format'		=> array(
				'desc'			=> __( 'Date Format' ),
				'readonly'		=> false,
				'option'		=> 'date_format'
			)
		);

		$this->site_options = apply_filters( 'xmlrpc_site_options', $this->site_options );
	}

	/**
	 * Compiles site options into an array suitable to be passed back through the XML-RPC server
	 *
	 * @since 1.0
	 * @return array The site options in an array
	 * @param array $options An array of options to fetch and return
	 */
	function _getOptions( $options )
	{
		$data = array();
		foreach ( $options as $option ) {
			if ( array_key_exists( $option, $this->site_options ) ) {
				$data[$option] = $this->site_options[$option];

				// Is the value static or dynamic?
				if ( isset( $data[$option]['option'] ) ) {
					$data[$option]['value'] = bb_get_option( $data[$option]['option'] );
					unset( $data[$option]['option'] );
				}
			}
		}

		return $data;
	}

	/**
	 * Gets the specified site options
	 *
	 * @since 1.0
	 * @return array|object An array containing the specified options when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The options to be retrieved, when omitted the method returns all options (optional)
	 *
	 * XML-RPC request to get all site options
	 * <methodCall>
	 *     <methodName>bb.getOptions</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *     </params>
	 * </methodCall>
	 *
	 * XML-RPC request to get the site name and site description
	 * <methodCall>
	 *     <methodName>bb.getOptions</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><array>
	 *             <data><value><string>site_name</string></value></data>
	 *             <data><value><string>site_description</string></value></data>
	 *         </array></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_getOptions( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.getOptions' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		if ( $this->auth_readonly ) {
			$user = $this->authenticate( $username, $password );
		}

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.getOptions' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// If there are parameters supplied then make sure they are in an array
		$options = isset( $args[2] ) ? (array) $args[2] : false;

		// If no specific options where asked for, return all of them
		if ( !$options || !count( $options ) ) {
			$options = array_keys( $this->site_options );
		}

		do_action( 'bb_xmlrpc_call_return', 'bb.getOptions' );

		return $this->_getOptions( $options );
	}

	/**
	 * Sets the specified site options to the specified values
	 *
	 * @since 1.0
	 * @return array|object An array containing the specified options when successfully executed or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The username for authentication
	 * @param string $args[1] The password for authentication
	 * @param array $args[2] The options to be updated along with the new value of the option
	 *
	 * XML-RPC request to set the site name and site description
	 * <methodCall>
	 *     <methodName>bb.setOptions</methodName>
	 *     <params>
	 *         <param><value><string>joeblow</string></value></param>
	 *         <param><value><string>123password</string></value></param>
	 *         <param><value><struct>
	 *             <member>
	 *                 <name>site_name</name>
	 *                 <value><string>Awesome forums</string></value>
	 *             </member>
	 *             <member>
	 *                 <name>site_description</name>
	 *                 <value><string>My totally awesome forums will kick your butt</string></value>
	 *             </member>
	 *         </struct></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function bb_setOptions( $args )
	{
		do_action( 'bb_xmlrpc_call', 'bb.setOptions' );

		// Escape args
		$this->escape( $args );

		// Get the login credentials
		$username = $args[0];
		$password = (string) $args[1];

		// Check the user is valid
		$user = $this->authenticate( $username, $password, 'manage_options', __( 'You are not allowed to manage options.' ) );

		do_action( 'bb_xmlrpc_call_authenticated', 'bb.setOptions' );

		// If an error was raised by authentication or by an action then return it
		if ( $this->error ) {
			return $this->error;
		}

		// Make sure there is something for us to do
		if ( !$args[2] || !is_array( $args[2] ) || !count( $args[2] ) ) {
			$this->error = new IXR_Error( 400, __( 'The options data is invalid.' ) );
			return $this->error;
		}

		$options = (array) $args[2];

		// Update the requested options
		foreach( $options as $o_name => $o_value ) {
			$option_names[] = $o_name;

			// If there is no value set skip it
			if ( empty( $o_value ) ) {
				continue;
			}

			// If the option doesn't exist skip it
			if ( !array_key_exists( $o_name, $this->site_options ) ) {
				continue;
			}

			// If the option is readonly skip it
			if ( $this->site_options[$o_name]['readonly'] == true ) {
				continue;
			}

			// Everything is good, update the option
			bb_update_option( $this->site_options[$o_name]['option'], $o_value );
		}

		$_options = $this->_getOptions( $option_names );

		do_action( 'bb_xmlrpc_call_return', 'bb.setOptions' );

		// Now return the updated values
		return $_options;
	}



	/**
	 * Pingback XML-RPC methods
	 */

	/**
	 * Processes pingback requests
	 *
	 * @since 1.0
	 * @link http://www.hixie.ch/specs/pingback/pingback
	 * @return string|object A message of success or an IXR_Error object on failure
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The full URI of the post where the pingback is being sent from
	 * @param string $args[1] The full URI of the post where the pingback is being sent to
	 *
	 * XML-RPC request to register a pingback
	 * <methodCall>
	 *     <methodName>pingback.ping</methodName>
	 *     <params>
	 *         <param><value><string>http://example.org/2008/09/post-containing-a-link/</string></value></param>
	 *         <param><value><string>http://example.com/2008/08/post-being-linked-to/</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function pingback_ping( $args )
	{
		do_action( 'bb_xmlrpc_call', 'pingback.ping' );

		$this->escape( $args );

		// No particular need to sanitise
		$link_from = (string) $args[0];
		$link_to   = (string) $args[1];

		// Tidy up ampersands in the URLs
		$link_from = str_replace( '&amp;', '&', $link_from );
		$link_to   = str_replace( '&amp;', '&', $link_to );
		$link_to   = str_replace( '&', '&amp;', $link_to );

		// Check if the topic linked to is in our site - a little more strict than WordPress, doesn't pull out the www if added
		if ( !bb_match_domains( $link_to, bb_get_uri() ) ) {
			// These are not the droids you are looking for
			$this->error = new IXR_Error( 0, __( 'This is not the site you are trying to pingback.' ) );
			return $this->error;
		}

		// Get the topic
		if ( $topic_to = bb_get_topic_from_uri( $link_to ) ) {
			// Topics shouldn't ping themselves
			if ( $topic_from = bb_get_topic_from_uri( $link_from ) ) {
				if ( $topic_from->topic_id === $topic_to->topic_id ) {
					$this->error = new IXR_Error( 0, __( 'The source URL and the target URL cannot both point to the same resource.' ) );
					return $this->error;
				}
			}
		} else {
			$this->error = new IXR_Error ( 33, __( 'The specified target URL cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.' ) );
			return $this->error;
		}

		// Let's check that the remote site didn't already pingback this entry
		$query = new BB_Query( 'post', array( 'topic_id' => $topic_to->topic_id, 'append_meta' => true ), 'get_thread' );
		$posts_to = $query->results;
		unset( $query );

		// Make sure we have some posts in the topic, this error should never happen really
		if ( !$posts_to || !is_array( $posts_to ) || !count( $posts_to ) ) {
			$this->error = new IXR_Error( 0, __( 'The specified target topic does not contain any posts.' ) );
			return $this->error;
		}

		// Check if we already have a pingback from this URL
		foreach ( $posts_to as $post ) {
			if ( isset( $post->pingback_uri ) && trim( $post->pingback_uri ) === trim( $link_from ) ) {
				$this->error = new IXR_Error( 48, __( 'The pingback has already been registered.' ) );
				return $this->error;
			}
		}
		unset( $posts_to, $post );

		// Give time for the server sending the pingback to finish publishing it's post
		sleep(1);

		// Let's check the remote site for valid URL and content
		$link_from_source = wp_remote_fopen( $link_from );
		if ( !$link_from_source ) {
			$this->error = new IXR_Error( 16, __( 'The source URL does not exist.' ) );
			return $this->error;
		}

		// Allow plugins to filter here
		$link_from_source = apply_filters( 'bb_pre_remote_source', $link_from_source, $link_to );

		// Work around bug in strip_tags()
		$link_from_source = str_replace( '<!DOC', '<DOC', $link_from_source );

		// Normalize spaces
		$link_from_source = preg_replace( '/[\s\r\n\t]+/', ' ', $link_from_source );

		// Turn certain elements to double line returns
		$link_from_source = preg_replace( "/ <(h1|h2|h3|h4|h5|h6|p|th|td|li|dt|dd|pre|caption|input|textarea|button|body)[^>]*>/", "\n\n", $link_from_source );

		// Find the title of the page
		preg_match( '|<title>([^<]*?)</title>|is', $link_from_source, $link_from_title );
		$link_from_title = $link_from_title[1];
		if ( empty( $link_from_title ) ) {
			$this->error = new IXR_Error( 32, __( 'We cannot find a title on that page.' ) );
			return $this->error;
		}

		// Strip out all tags except anchors
		$link_from_source = strip_tags( $link_from_source, '<a>' ); // just keep the tag we need

		// Split the source into paragraphs
		$link_from_paragraphs = explode( "\n\n", $link_from_source );

		// Prepare the link to search for in preg_match() once here
		$preg_target = preg_quote( $link_to );

		// Loop through the paragraphs looking for the context for the url
		foreach ( $link_from_paragraphs as $link_from_paragraph ) {
			// The url exists
			if ( strpos( $link_from_paragraph, $link_to ) !== false ) {
				// But is it in an anchor tag
				preg_match(
					"|<a[^>]+?" . $preg_target . "[^>]*>([^>]+?)</a>|",
					$link_from_paragraph,
					$context
				);
				// If the URL isn't in an anchor tag, keep looking
				if ( empty( $context ) ) {
					continue;
				}

				// We're going to use this fake tag to mark the context in a bit
				// the marker is needed in case the link text appears more than once in the paragraph
				$excerpt = preg_replace( '|\</?wpcontext\>|', '', $link_from_paragraph );

				// Prevent really long link text
				if ( strlen( $context[1] ) > 100 ) {
					$context[1] = substr( $context[1], 0, 100 ) . '...';
				}

				// Set up the marker around the context
				$marker = '<wpcontext>' . $context[1] . '</wpcontext>';
				// Swap out the link for our marker
				$excerpt = str_replace( $context[0], $marker, $excerpt );
				// Strip all tags except for our context marker
				$excerpt = trim( strip_tags( $excerpt, '<wpcontext>' ) );
				// Make the marker safe for use in regexp
				$preg_marker = preg_quote( $marker );
				// Reduce the excerpt to only include 100 characters on either side of the link
				$excerpt = preg_replace( "|.*?\s(.{0,100}" . $preg_marker . "{0,100})\s.*|s", '$1', $excerpt );
				// Strip tags again, to remove the marker wrapper
				$excerpt = strip_tags( $excerpt );
				break;
			}
		}

		 // Make sure the link to the target was found in the excerpt
		if ( empty( $context ) ) {
			$this->error = new IXR_Error( 17, __( 'The source URL does not contain a link to the target URL, and so cannot be used as a source.' ) );
			return $this->error;
		}

		// Add whacky prefix and suffix to the excerpt and sanitize
		$excerpt = '[...] ' . esc_html( $excerpt ) . ' [...]';
		$this->escape( $excerpt );

		// Build an array of post data to insert then insert a new post
		$postdata = array(
			'topic_id' => $topic_to->topic_id,
			'post_text' => $excerpt,
			'poster_id' => 0,
		);
		if ( !$post_ID = bb_insert_post( $postdata ) ) {
			$this->error = new IXR_Error( 0, __( 'The pingback could not be added.' ) );
			return $this->error;
		}

		// Add meta to let us know where the pingback came from
		$link_from = str_replace( '&', '&amp;', $link_from );
		$this->escape( $link_from );
		bb_update_postmeta( $post_ID, 'pingback_uri', $link_from );

		// Add the title to meta
		$this->escape( $link_from_title );
		bb_update_postmeta( $post_ID, 'pingback_title', $link_from_title );

		// Action for plugins and what not
		do_action( 'bb_pingback_post', $post_ID );

		// Return success message, complete with emoticon
		return sprintf( __( 'Pingback from %1$s to %2$s registered. Keep the web talking! :-)' ), $link_from, $link_to );
	}



	/**
	 * Returns an array of URLs that pingbacked the given URL
	 *
	 * @since 1.0
	 * @link http://www.aquarionics.com/misc/archives/blogite/0198.html
	 * @return array The array of URLs that pingbacked the given topic
	 * @param array $args Arguments passed by the XML-RPC call
	 * @param string $args[0] The full URI of the post where the pingback is being sent from
	 * @param string $args[1] The full URI of the post where the pingback is being sent to
	 *
	 * XML-RPC request to get all pingbacks on a topic
	 * <methodCall>
	 *     <methodName>pingback.ping</methodName>
	 *     <params>
	 *         <param><value><string>http://example.com/2008/08/post-tobe-queried/</string></value></param>
	 *     </params>
	 * </methodCall>
	 */
	function pingback_extensions_getPingbacks( $args )
	{
		do_action( 'bb_xmlrpc_call', 'pingback.extensions.getPingbacks' );

		$this->escape( $args );

		// Don't accept arrays of arguments
		if ( is_array( $args ) ) {
			$this->error = new IXR_Error( 404, __( 'The requested method only accepts one parameter.' ) );
			return $this->error;
		} else {
			$url = (string) $args;
		}

		// Tidy up ampersands in the URI
		$url = str_replace( '&amp;', '&', $url );
		$url = str_replace( '&', '&amp;', $url );

		// Check if the URI is in our site
		if ( !bb_match_domains( $url, bb_get_uri() ) ) {
			// These are not the droids you are looking for
			$this->error = new IXR_Error( 0, __( 'The specified target URL is not on this domain.' ) );
			return $this->error;
		}

		// Make sure the specified URI is in fact associated with a topic
		if ( !$topic = bb_get_topic_from_uri( $url ) ) {
			$this->error = new IXR_Error( 33, __( 'The specified target URL cannot be used as a target. It either doesn\'t exist, or it is not a pingback-enabled resource.' ) );
			return $this->error;
		}

		// Grab the posts from the topic
		$query = new BB_Query( 'post', array( 'topic_id' => $topic_to->topic_id, 'append_meta' => true ), 'get_thread' );
		$posts_to = $query->results;
		unset( $query );

		// Check for pingbacks in the post meta data
		$pingbacks = array();
		foreach ( $posts_to as $post ) {
			if ( isset( $post->pingback_uri ) ) {
				$pingbacks[] = $post->pingback_uri;
			}
		}
		unset( $post );

		// This will return an empty array on failure
		return $pingbacks;
	}
}



/**
 * Initialises the XML-RPC server
 *
 * @since 1.0
 * @var object The instance of the XML-RPC server class
 */
$bb_xmlrpc_server = new BB_XMLRPC_Server();
