<?php
# -- BEGIN LICENSE BLOCK ----------------------------------
#
# This file is part of Dotclear 2.
#
# Copyright (c) 2003-2008 Olivier Meunier and contributors
# Licensed under the GPL version 2.0 license.
# See LICENSE file or
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# -- END LICENSE BLOCK ------------------------------------

class dcXmlRpc extends xmlrpcIntrospectionServer
{
	public $core;
	private $blog_id;
	private $debug = false;
	private $debug_file = '/tmp/dotclear-xmlrpc.log';
	private $trace_args = true;
	private $trace_response = true;
	
	public function __construct(&$core,$blog_id)
	{
		parent::__construct();
		
		$this->core =& $core;
		$this->blog_id = $blog_id;
		
		# Blogger methods
		$this->addCallback('blogger.newPost',array($this,'blogger_newPost'),
			array('string','string','string','string','string','string','integer'),
			'New post');
		
		$this->addCallback('blogger.editPost',array($this,'blogger_editPost'),
			array('boolean','string','string','string','string','string','integer'),
			'Edit a post');
		
		$this->addCallback('blogger.getPost',array($this,'blogger_getPost'),
			array('struct','string','integer','string','string'),
			'Return a posts by ID');
		
		$this->addCallback('blogger.deletePost',array($this,'blogger_deletePost'),
			array('string','string','string','string','string','integer'),
			'Delete a post');
		
		$this->addCallback('blogger.getRecentPosts',array($this,'blogger_getRecentPosts'),
			array('array','string','string','string','string','integer'),
			'Return a list of recent posts');
		
		$this->addCallback('blogger.getUsersBlogs',array($this,'blogger_getUserBlogs'),
			array('struct','string','string','string'),
			"Return user's blog");
		
		$this->addCallback('blogger.getUserInfo',array($this,'blogger_getUserInfo'),
			array('struct','string','string','string'),
			'Return User Info');
		
		# Metaweblog methods
		$this->addCallback('metaWeblog.newPost',array($this,'mw_newPost'),
			array('string','string','string','string','struct','boolean'),
			'Creates a new post, and optionnaly publishes it.');
		
		$this->addCallback('metaWeblog.editPost',array($this,'mw_editPost'),
			array('boolean','string','string','string','struct','boolean'),
			'Updates information about an existing entry');
		
		$this->addCallback('metaWeblog.getPost',array($this,'mw_getPost'),
			array('struct','string','string','string'),
			'Returns information about a specific post');
		
		$this->addCallback('metaWeblog.getRecentPosts',array($this,'mw_getRecentPosts'),
			array('array','string','string','string','integer'),
			'List of most recent posts in the system');
		
		$this->addCallback('metaWeblog.newMediaObject',array($this,'mw_newMediaObject'),
			array('string','string','string','string','struct'),
			'Download a file on the web server');
		
		# MovableType methods
		$this->addCallback('mt.getRecentPostTitles',array($this,'mt_getRecentPostTitles'),
			array('array','string','string','string','integer'),
			'List of most recent posts in the system');
		
		$this->addCallback('mt.getCategoryList',array($this,'mt_getCategoryList'),
			array('array','string','string','string'),
			'List of all categories defined in the weblog');
		
		$this->addCallback('mt.getPostCategories',array($this,'mt_getPostCategories'),
			array('array','string','string','string'),
			'List of all categories to which the post is assigned');
		
		$this->addCallback('mt.setPostCategories',array($this,'mt_setPostCategories'),
			array('boolean','string','string','string','array'),
			'Sets the categories for a post');
		
		$this->addCallback('mt.publishPost',array($this,'mt_publishPost'),
			array('boolean','string','string','string'),
			'Retrieve pings list for a post');
		
		$this->addCallback('mt.supportedMethods',array($this,'listMethods'),
			array(),'Retrieve information about the XML-RPC methods supported by the server.');
		
		$this->addCallback('mt.supportedTextFilters',array($this,'mt_supportedTextFilters'),
			array(),'Retrieve information about supported text filters.');
	}
	
	public function serve($data=false,$encoding='UTF-8')
	{
		parent::serve(false,$encoding);
	}
	
	public function call($methodname,$args)
	{
		try {
			$rsp = parent::call($methodname,$args);
			$this->debugTrace($methodname,$args,$rsp);
			return $rsp;
		} catch (Exception $e) {
			$this->debugTrace($methodname,$args,array($e->getMessage(),$e->getCode()));
			throw $e;
		}
		
	}
	
	private function debugTrace($methodname,$args,$rsp)
	{
		if (!$this->debug) {
			return;
		}
		
		if (($fp = @fopen($this->debug_file,'a')) !== false)
		{
			fwrite($fp,'['.date('r').']'.' '.$methodname);
			
			if ($this->trace_args) {
				fwrite($fp,"\n- args ---\n".var_export($args,1));
			}
			
			if ($this->trace_response) {
				fwrite($fp,"\n- response ---\n".var_export($rsp,1));
			}
			fwrite($fp,"\n");
			fclose($fp);
		}
	}
	
	/* Internal methods
	--------------------------------------------------- */
	private function setUser($user_id,$pwd)
	{
		if ($this->core->auth->checkUser($user_id,$pwd) !== true) {
			throw new Exception('Login error');
		}
		
		return true;
	}
	
	private function setBlog()
	{
		if (!$this->blog_id) {
			throw new Exception('No blog ID given.');
		}
		
		$this->core->setBlog($this->blog_id);
		
		if (!$this->core->blog->id) {
			$this->core->blog = null;
			throw new Exception('Blog does not exist.');
		}
		
		if (!$this->core->blog->settings->enable_xmlrpc ||
		!$this->core->auth->check('usage,contentadmin',$this->core->blog->id)) {
			$this->core->blog = null;
			throw new Exception('Not enough permissions on this blog.');
		}
		
		foreach ($this->core->plugins->getModules() as $id => $m) {
			$this->core->plugins->loadNsFile($id,'xmlrpc');
		}
		
		return true;
	}
	
	private function getPostRS($post_id,$user,$pwd)
	{
		$this->setUser($user,$pwd);
		$this->setBlog();
		$rs = $this->core->blog->getPosts(array('post_id' => (integer) $post_id));
		
		if ($rs->isEmpty()) {
			throw new Exception('This entry does not exist');
		}
		
		return $rs;
	}
	
	/* Generic methods
	--------------------------------------------------- */
	private function newPost($blog_id,$user,$pwd,$content,$struct=array(),$publish=true)
	{
		$this->setUser($user,$pwd);
		$this->setBlog();
		
		$title = !empty($struct['title']) ? $struct['title'] : '';
		$excerpt = !empty($struct['mt_excerpt']) ? $struct['mt_excerpt'] : '';
		$description = !empty($struct['description']) ? $struct['description'] : null;
		$dateCreated = !empty($struct['dateCreated']) ? $struct['dateCreated'] : null;
		$open_comment = isset($struct['mt_allow_comments']) ? $struct['mt_allow_comments'] : 1;
		$open_tb = isset($struct['mt_allow_pings']) ? $struct['mt_allow_pings'] : 1;
		
		if ($description !== null) {
			$content = $description;
		}
		
		if (!$title) {
			$title = text::cutString(html::clean($content),25).'...';
		}
		
		$excerpt_xhtml = $this->core->callFormater('xhtml',$excerpt);
		$content_xhtml = $this->core->callFormater('xhtml',$content);
		
		if (empty($content)) {
			throw new Exception('Cannot create an empty entry');
		}
		
		$cur = $this->core->con->openCursor($this->core->prefix.'post');
		
		$cur->user_id = $this->core->auth->userID();
		$cur->post_lang = $this->core->auth->getInfo('user_lang');
		$cur->post_title = trim($title);
		$cur->post_content = $content;
		$cur->post_excerpt = $excerpt;
		$cur->post_content_xhtml = $content_xhtml;
		$cur->post_excerpt_xhtml = $excerpt_xhtml;
		$cur->post_open_comment = (integer) ($open_comment == 1);
		$cur->post_open_tb = (integer) ($open_tb == 1);
		$cur->post_status = (integer) $publish;
		$cur->post_format = 'xhtml';
		
		if ($dateCreated && strtotime($dateCreated)) {
			$cur->post_dt = date('Y-m-d H:i:00',strtotime($dateCreated));
		}
		
		# --BEHAVIOR-- xmlrpcBeforeNewPost
		$this->core->callBehavior('xmlrpcBeforeNewPost',$this,$cur,$content,$struct,$publish);
		
		$post_id = $this->core->blog->addPost($cur);
		
		# --BEHAVIOR-- xmlrpcAfterNewPost
		$this->core->callBehavior('xmlrpcAfterNewPost',$this,$post_id,$cur,$content,$struct,$publish);
		
		return (string) $post_id;
	}
	
	private function editPost($post_id,$user,$pwd,$content,$struct=array(),$publish=true)
	{
		$post_id = (integer) $post_id;
		
		$post = $this->getPostRS($post_id,$user,$pwd);
		
		$title = (!empty($struct['title'])) ? $struct['title'] : '';
		$excerpt = (!empty($struct['mt_excerpt'])) ? $struct['mt_excerpt'] : '';
		$description = (!empty($struct['description'])) ? $struct['description'] : null;
		$dateCreated = !empty($struct['dateCreated']) ? $struct['dateCreated'] : null;
		$open_comment = (isset($struct['mt_allow_comments'])) ? $struct['mt_allow_comments'] : 1;
		$open_tb = (isset($struct['mt_allow_pings'])) ? $struct['mt_allow_pings'] : 1;
		
		if ($description !== null) {
			$content = $description;
		}
		
		if (!$title) {
			$title = text::cutString(html::clean($content),25).'...';
		}
		
		$excerpt_xhtml = $this->core->callFormater('xhtml',$excerpt);
		$content_xhtml = $this->core->callFormater('xhtml',$content);
		
		if (empty($content)) {
			throw new Exception('Cannot create an empty entry');
		}
		
		$cur = $this->core->con->openCursor($this->core->prefix.'post');
		
		$cur->post_title = trim($title);
		$cur->post_content = $content;
		$cur->post_excerpt = $excerpt;
		$cur->post_content_xhtml = $content_xhtml;
		$cur->post_excerpt_xhtml = $excerpt_xhtml;
		$cur->post_open_comment = (integer) ($open_comment == 1);
		$cur->post_open_tb = (integer) ($open_tb == 1);
		$cur->post_status = (integer) $publish;
		$cur->post_format = 'xhtml';
		$cur->post_url = $post->post_url;
		
		if ($dateCreated && strtotime($dateCreated)) {
			$cur->post_dt = date('Y-m-d H:i:00',strtotime($dateCreated));
		} else {
			$cur->post_dt = $post->post_dt;
		}
		
		# --BEHAVIOR-- xmlrpcBeforeEditPost
		$this->core->callBehavior('xmlrpcBeforeEditPost',$this,$post_id,$cur,$content,$struct,$publish);
		
		$this->core->blog->updPost($post_id,$cur);
		
		# --BEHAVIOR-- xmlrpcAfterEditPost
		$this->core->callBehavior('xmlrpcAfterEditPost',$this,$post_id,$cur,$content,$struct,$publish);
		
		return true;
	}
	
	private function getPost($post_id,$user,$pwd,$type='mw')
	{
		$post_id = (integer) $post_id;
		
		$post = $this->getPostRS($post_id,$user,$pwd);
		
		$res = array();
		
		$res['dateCreated'] = new xmlrpcDate($post->getTS());
		$res['userid'] = $post->user_id;
		$res['postid'] = $post->post_id;
		
		if ($type == 'blogger') {
			$res['content'] = $post->post_content_xhtml;
		}
		
		if ($type == 'mt' || $type == 'mw') {
			$res['title'] = $post->post_title;
		}
		
		if ($type == 'mw') {
			$res['description'] = $post->post_content_xhtml;
			$res['link'] = $res['permaLink'] = $post->getURL();
			$res['mt_excerpt'] = $post->post_excerpt_xhtml;
			$res['mt_text_more'] = '';
			$res['mt_allow_comments'] = (integer) $post->post_open_comment;
			$res['mt_allow_pings'] = (integer) $post->post_open_tb;
			$res['mt_convert_breaks'] = '';
			$res['mt_keywords'] = '';
		}
		
		# --BEHAVIOR-- xmlrpcGetPostInfo
		$this->core->callBehavior('xmlrpcGetPostInfo',$this,$type,array(&$res));
		
		return $res;
	}
	
	private function deletePost($post_id,$user,$pwd)
	{
		$post_id = (integer) $post_id;
		
		$this->getPostRS($post_id,$user,$pwd);
		$this->core->blog->delPost($post_id);
		
		return true;
	}
	
	private function getRecentPosts($blog_id,$user,$pwd,$nb_post,$type='mw')
	{
		$this->setUser($user,$pwd);
		$this->setBlog();
		
		$nb_post = (integer) $nb_post;
		
		if ($nb_post > 50) {
			throw new Exception('Cannot retrieve more than 50 entries');
		}
		
		$params = array();
		$params['limit'] = $nb_post;
		
		$posts = $this->core->blog->getPosts($params);
		
		$res = array();
		while ($posts->fetch())
		{
			$tres = array();
			
			$tres['dateCreated'] = new xmlrpcDate($posts->getTS());
			$tres['userid'] = $posts->user_id;
			$tres['postid'] = $posts->post_id;
			
			if ($type == 'blogger') {
				$tres['content'] = $posts->post_content_xhtml;
			}
			
			if ($type == 'mt' || $type == 'mw') {
				$tres['title'] = $posts->post_title;
			}
			
			if ($type == 'mw') {
				$tres['description'] = $posts->post_content_xhtml;
				$tres['link'] = $tres['permaLink'] = $posts->getURL();
				$tres['mt_excerpt'] = $posts->post_excerpt_xhtml;
				$tres['mt_text_more'] = '';
				$tres['mt_allow_comments'] = (integer) $posts->post_open_comment;
				$tres['mt_allow_pings'] = (integer) $posts->post_open_tb;
				$tres['mt_convert_breaks'] = '';
				$tres['mt_keywords'] = '';
			}
			
			# --BEHAVIOR-- xmlrpcGetPostInfo
			$this->core->callBehavior('xmlrpcGetPostInfo',$this,$type,array(&$tres));
			
			$res[] = $tres;
		}
		
		return $res;
	}
	
	private function getUserBlogs($user,$pwd)
	{
		$this->setUser($user,$pwd);
		$this->setBlog();
		
		return array(array(
			'url' => $this->core->blog->url,
			'blogid' => '1',
			'blogName' => $this->core->blog->name
		));
	}
	
	private function getUserInfo($user,$pwd)
	{
		$this->setUser($user,$pwd);
		
		return array(
			'userid' => $this->core->auth->userID(),
			'firstname' => $this->core->auth->getInfo('user_firstname'),
			'lastname' => $this->core->auth->getInfo('user_name'),
			'nickname' => $this->core->auth->getInfo('user_displayname'),
			'email' => $this->core->auth->getInfo('user_email'),
			'url' => $this->core->auth->getInfo('user_url')
		);
	}
	
	private function getCategoryList($blog_id,$user,$pwd)
	{
		$this->setUser($user,$pwd);
		$this->setBlog();
		$rs = $this->core->blog->getCategories();
		
		$res = array();
		while ($rs->fetch()) {
			$res[] = array(
				'categoryId' => (string) $rs->cat_id,
				'categoryName' => $rs->cat_title
			);
		}
		
		return $res;
	}
	
	private function getPostCategories($post_id,$user,$pwd)
	{
		$post_id = (integer) $post_id;
		
		$post = $this->getPostRS($post_id,$user,$pwd);
		
		return array(
			array(
			'categoryName' => $post->cat_title,
			'categoryId' => (string) $post->cat_id,
			'isPrimary' => true
			)
		);
	}
	
	private function setPostCategories($post_id,$user,$pwd,$categories)
	{
		$post_id = (integer) $post_id;
		
		$post = $this->getPostRS($post_id,$user,$pwd);
		
		$cat_id = (!empty($categories[0]['categoryId'])) ? (integer) $categories[0]['categoryId'] : null;
		
		foreach($categories as $v)
		{
			if (isset($v['isPrimary']) && $v['isPrimary']) {
				$cat_id = $v['categoryId'];
				break;
			}
		}
		
		# w.bloggar sends -1 for no category.
		if ($cat_id == -1) {
			$cat_id = null;
		}
		
		$this->core->blog->updPostCategory($post_id,(integer) $cat_id);
		
		return true;
	}
	
	private function publishPost($post_id,$user,$pwd)
	{
		$post_id = (integer) $post_id;
		
		$this->getPostRS($post_id,$user,$pwd);
		
		# --BEHAVIOR-- xmlrpcBeforePublishPost
		$this->core->callBehavior('xmlrpcBeforePublishPost',$this,$post_id);
		
		$this->core->blog->updPostStatus($post_id,1);
		
		# --BEHAVIOR-- xmlrpcAfterPublishPost
		$this->core->callBehavior('xmlrpcAfterPublishPost',$this,$post_id);
		
		return true;
	}
	
	private function newMediaObject($blog_id,$user,$pwd,$file)
	{
		if (empty($file['name'])) {
			throw new Exception('No file name');
		}
		
		if (empty($file['bits'])) {
			throw new Exception('No file content');
		}
		
		$file_name = $file['name'];
		$file_bits = $file['bits'];
		
		$this->setUser($user,$pwd);
		$this->setBlog();
		
		$media = new dcMedia($this->core);
		$media_id = $media->uploadBits($file_name,$file_bits);
		
		$f = $media->getFile($media_id);
		return array('url' => $f->file_url);
	}
	
	/* Blogger methods
	--------------------------------------------------- */
	public function blogger_newPost($appkey,$blogid,$username,$password,$content,$publish)
	{
		return $this->newPost($blogid,$username,$password,$content,array(),$publish);
	}
	
	public function blogger_editPost($appkey,$postid,$username,$password,$content,$publish)
	{
		return $this->editPost($postid,$username,$password,$content,array(),$publish);
	}
	
	public function blogger_getPost($appkey,$postid,$username,$password)
	{
		return $this->getPost($postid,$username,$password,'blogger');
	}
	
	public function blogger_deletePost($appkey,$postid,$username,$password,$publish)
	{
		return $this->deletePost($postid,$username,$password);
	}
	
	public function blogger_getRecentPosts($appkey,$blogid,$username,$password,$numberOfPosts)
	{
		return $this->getRecentPosts($blogid,$username,$password,$numberOfPosts,'blogger');
	}
	
	public function blogger_getUserBlogs($appkey,$username,$password)
	{
		return $this->getUserBlogs($username,$password);
	}
	
	public function blogger_getUserInfo($appkey,$username,$password)
	{
		return $this->getUserInfo($username,$password);
	}
	
	
	/* Metaweblog methods
	------------------------------------------------------- */
	public function mw_newPost($blogid,$username,$password,$content,$publish)
	{
		return $this->newPost($blogid,$username,$password,'',$content,$publish);
	}
	
	public function mw_editPost($postid,$username,$password,$content,$publish)
	{
		return $this->editPost($postid,$username,$password,'',$content,$publish);
	}
	
	public function mw_getPost($postid,$username,$password)
	{
		return $this->getPost($postid,$username,$password,'mw');
	}
	
	public function mw_getRecentPosts($blogid,$username,$password,$numberOfPosts)
	{
		return $this->getRecentPosts($blogid,$username,$password,$numberOfPosts,'mw');
	}
	
	public function mw_newMediaObject($blogid,$username,$password,$file)
	{
		return $this->newMediaObject($blogid,$username,$password,$file);
	}
	
	/* MovableType methods
	--------------------------------------------------- */
	public function mt_getRecentPostTitles($blogid,$username,$password,$numberOfPosts)
	{
		return $this->getRecentPosts($blogid,$username,$password,$numberOfPosts,'mt');
	}
	
	public function mt_getCategoryList($blogid,$username,$password)
	{
		return $this->getCategoryList($blogid,$username,$password);
	}
	
	public function mt_getPostCategories($postid,$username,$password)
	{
		return $this->getPostCategories($postid,$username,$password);
	}
	
	public function mt_setPostCategories($postid,$username,$password,$categories)
	{
		return $this->setPostCategories($postid,$username,$password,$categories);
	}
	
	public function mt_publishPost($postid,$username,$password)
	{
		return $this->publishPost($postid,$username,$password);
	}
	
	public function mt_supportedTextFilters()
	{
		return array();
	}
}
?>