# HG changeset patch
# User JcDenis
# Date 1353254036 -3600
# Node ID 634481f5e7376d531c62f204880baefbf8b20f88
# Parent  4a5d59418a795bf5170892851d3c019a9d208be7
JSON REST service. Step 1. PHP part

diff -r 4a5d59418a79 -r 634481f5e737 admin/services_json.php
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/admin/services_json.php	Sun Nov 18 16:53:56 2012 +0100
@@ -0,0 +1,394 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
+# 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 -----------------------------------------
+
+#if (isset($_GET['dcxd'])) {
+#	$_COOKIE['dcxd'] = $_GET['dcxd'];
+#}
+
+require dirname(__FILE__).'/../inc/admin/prepend.php';
+
+$core->jsonrest->addFunction('getPostById',array('dcJsonRestMethods','getPostById'));
+$core->jsonrest->addFunction('getCommentById',array('dcJsonRestMethods','getCommentById'));
+$core->jsonrest->addFunction('quickPost',array('dcJsonRestMethods','quickPost'));
+$core->jsonrest->addFunction('validatePostMarkup',array('dcJsonRestMethods','validatePostMarkup'));
+$core->jsonrest->addFunction('getZipMediaContent',array('dcJsonRestMethods','getZipMediaContent'));
+$core->jsonrest->addFunction('getMeta',array('dcJsonRestMethods','getMeta'));
+$core->jsonrest->addFunction('delMeta',array('dcJsonRestMethods','delMeta'));
+$core->jsonrest->addFunction('setPostMeta',array('dcJsonRestMethods','setPostMeta'));
+$core->jsonrest->addFunction('searchMeta',array('dcJsonRestMethods','searchMeta'));
+
+$core->jsonrest->serve();
+
+/* Common REST methods */
+class dcJsonRestMethods
+{
+	public static function getPostById($core,$get)
+	{
+		if (empty($get['id'])) {
+			throw new Exception('No post ID');
+		}
+		
+		$params = array('post_id' => (integer) $get['id']);
+		
+		if (isset($get['post_type'])) {
+			$params['post_type'] = $get['post_type'];
+		}
+		
+		$rs = $core->blog->getPosts($params);
+		
+		if ($rs->isEmpty()) {
+			throw new Exception('No post for this ID');
+		}
+		
+		$rsp = array(
+			'id' => $rs->post_id,
+		
+			'blog_id' => $rs->blog_id,
+			'user_id' => $rs->user_id,
+			'cat_id' => $rs->cat_id,
+			'post_dt' => $rs->post_dt,
+			'post_creadt' => $rs->post_creadt,
+			'post_upddt' => $rs->post_upddt,
+			'post_format' => $rs->post_format,
+			'post_url' => $rs->post_url,
+			'post_lang' => $rs->post_lang,
+			'post_title' => $rs->post_title,
+			'post_excerpt' => $rs->post_excerpt,
+			'post_excerpt_xhtml' => $rs->post_excerpt_xhtml,
+			'post_content' => $rs->post_content,
+			'post_content_xhtml' => $rs->post_content_xhtml,
+			'post_notes' => $rs->post_notes,
+			'post_status' => $rs->post_status,
+			'post_selected' => $rs->post_selected,
+			'post_open_comment' => $rs->post_open_comment,
+			'post_open_tb' => $rs->post_open_tb,
+			'nb_comment' => $rs->nb_comment,
+			'nb_trackback' => $rs->nb_trackback,
+			'user_name' => $rs->user_name,
+			'user_firstname' => $rs->user_firstname,
+			'user_displayname' => $rs->user_displayname,
+			'user_email' => $rs->user_email,
+			'user_url' => $rs->user_url,
+			'cat_title' => $rs->cat_title,
+			'cat_url' => $rs->cat_url,
+			
+			'post_display_content' => $rs->getContent(true),
+			'post_display_excerpt' => $rs->getExcerpt(true)
+		);
+		
+		if (($meta = @unserialize($rs->post_meta)) !== false)
+		{
+			foreach ($meta as $K => $V)
+			{
+				foreach ($V as $v) {
+					$rsp['post_meta'][$K] = $v;
+				}
+			}
+		}
+		
+		return array('post' => $rsp);
+	}
+	
+	public static function getCommentById($core,$get)
+	{
+		if (empty($get['id'])) {
+			throw new Exception('No comment ID');
+		}
+		
+		$rs = $core->blog->getComments(array('comment_id' => (integer) $get['id']));
+		
+		if ($rs->isEmpty()) {
+			throw new Exception('No comment for this ID');
+		}
+		
+		$rsp = array(
+			'id' => $rs->comment_id,
+		
+			'comment_dt' => $rs->comment_dt,
+			'comment_upddt' => $rs->comment_upddt,
+			'comment_author' => $rs->comment_author,
+			'comment_site' => $rs->comment_site,
+			'comment_content' => $rs->comment_content,
+			'comment_trackback' => $rs->comment_trackback,
+			'comment_status' => $rs->comment_status,
+			'post_title' => $rs->post_title,
+			'post_url' => $rs->post_url,
+			'post_id' => $rs->post_id,
+			'post_dt' => $rs->post_dt,
+			'user_id' => $rs->user_id,
+		
+			'comment_display_content' => $rs->getContent(true)
+		);
+		
+		if ($core->auth->userID()) {
+			$rsp['comment_ip'] = $rs->comment_ip;
+			$rsp['comment_email'] = $rs->comment_email;
+			$rsp['comment_spam_disp'] = dcAntispam::statusMessage($rs);
+		}
+		
+		return array('post' => $rsp);
+	}
+	
+	public static function quickPost($core,$get,$post)
+	{
+		$cur = $core->con->openCursor($core->prefix.'post');
+		
+		$cur->post_title = !empty($post['post_title']) ? $post['post_title'] : '';
+		$cur->user_id = $core->auth->userID();
+		$cur->post_content = !empty($post['post_content']) ? $post['post_content'] : '';
+		$cur->cat_id = !empty($post['cat_id']) ? (integer) $post['cat_id'] : null;
+		$cur->post_format = !empty($post['post_format']) ? $post['post_format'] : 'xhtml';
+		$cur->post_lang = !empty($post['post_lang']) ? $post['post_lang'] : '';
+		$cur->post_status = !empty($post['post_status']) ? (integer) $post['post_status'] : 0;
+		$cur->post_open_comment = (integer) $core->blog->settings->system->allow_comments;
+		$cur->post_open_tb = (integer) $core->blog->settings->system->allow_trackbacks;
+		
+		# --BEHAVIOR-- adminBeforePostCreate
+		$core->callBehavior('adminBeforePostCreate',$cur);
+		
+		$return_id = $core->blog->addPost($cur);
+		
+		# --BEHAVIOR-- adminAfterPostCreate
+		$core->callBehavior('adminAfterPostCreate',$cur,$return_id);
+		
+		$post = $core->blog->getPosts(array('post_id' => $return_id));
+		
+		$rsp = array(
+			'id' => $return_id,
+			'post_status' => $post->post_status,
+			'post_url' => $post->getURL()
+		);
+		
+		return array('post' => $rsp);
+	}
+	
+	public static function validatePostMarkup($core,$get,$post)
+	{
+		if (!isset($post['excerpt'])) {
+			throw new Exception('No entry excerpt');
+		}
+		
+		if (!isset($post['content'])) {
+			throw new Exception('No entry content');
+		}
+		
+		if (empty($post['format'])) {
+			throw new Exception('No entry format');
+		}
+		
+		if (!isset($post['lang'])) {
+			throw new Exception('No entry lang');
+		}
+		
+		$excerpt = $post['excerpt'];
+		$excerpt_xhtml = '';
+		$content = $post['content'];
+		$content_xhtml = '';
+		$format = $post['format'];
+		$lang = $post['lang'];
+		
+		$core->blog->setPostContent(0,$format,$lang,$excerpt,$excerpt_xhtml,$content,$content_xhtml);
+		
+		$v = htmlValidator::validate($excerpt_xhtml.$content_xhtml);
+		
+		$rsp = array(
+			'valid' => $v['valid'],
+			'errors' => $v['errors']
+		);
+		
+		return array('result' => $rsp);
+	}
+	
+	public static function getZipMediaContent($core,$get,$post)
+	{
+		if (empty($get['id'])) {
+			throw new Exception('No media ID');
+		}
+		
+		$id = (integer) $get['id'];
+		
+		if (!$core->auth->check('media,media_admin',$core->blog)) {
+			throw new Exception('Permission denied');
+		}
+		
+		$core->media = new dcMedia($core);
+		$file = $core->media->getFile($id);
+		
+		if ($file === null || $file->type != 'application/zip' || !$file->editable) {
+			throw new Exception('Not a valid file');
+		}
+		
+		$content = $core->media->getZipContent($file);
+		
+		$rsp = array();
+		foreach ($content as $k => $v) {
+			$rsp['file'][]  = $k;
+		}
+		
+		return array('result' => $rsp);
+	}
+	
+	public static function getMeta($core,$get)
+	{
+		$postid = !empty($get['postId']) ? $get['postId'] : null;
+		$limit = !empty($get['limit']) ? $get['limit'] : null;
+		$metaId = !empty($get['metaId']) ? $get['metaId'] : null;
+		$metaType = !empty($get['metaType']) ? $get['metaType'] : null;
+		
+		$sortby = !empty($get['sortby']) ? $get['sortby'] : 'meta_type,asc';
+		
+		$rs = $core->meta->getMetadata(array(
+			'meta_type' => $metaType,
+			'limit' => $limit,
+			'meta_id' => $metaId,
+			'post_id' => $postid));
+		$rs = $core->meta->computeMetaStats($rs);
+		
+		$sortby = explode(',',$sortby);
+		$sort = $sortby[0];
+		$order = isset($sortby[1]) ? $sortby[1] : 'asc';
+		
+		switch ($sort) {
+			case 'metaId':
+				$sort = 'meta_id_lower';
+				break;
+			case 'count':
+				$sort = 'count';
+				break;
+			case 'metaType':
+				$sort = 'meta_type';
+				break;
+			default:
+				$sort = 'meta_type';
+		}
+		
+		$rs->sort($sort,$order);
+		
+		$rsp = array();
+		
+		while ($rs->fetch())
+		{
+			$rsp['meta'][] = array(
+				'type' => $rs->meta_type,
+				'uri' => rawurlencode($rs->meta_id),
+				'count' => $rs->count,
+				'percent' => $rs->percent,
+				'roundpercent' => $rs->roundpercent,
+				'data' => $rs->meta_id
+			);
+		}
+		
+		return $rsp;
+	}
+	
+	public static function setPostMeta($core,$get,$post)
+	{
+		if (empty($post['postId'])) {
+			throw new Exception('No post ID');
+		}
+		
+		if (empty($post['meta']) && $post['meta'] != '0') {
+			throw new Exception('No meta');
+		}
+		
+		if (empty($post['metaType'])) {
+			throw new Exception('No meta type');
+		}
+		
+		# Get previous meta for post
+		$post_meta = $core->meta->getMetadata(array(
+			'meta_type' => $post['metaType'],
+			'post_id' => $post['postId']));
+		$pm = array();
+		while ($post_meta->fetch()) {
+			$pm[] = $post_meta->meta_id;
+		}
+		
+		foreach ($core->meta->splitMetaValues($post['meta']) as $m)
+		{
+			if (!in_array($m,$pm)) {
+				$core->meta->setPostMeta($post['postId'],$post['metaType'],$m);
+			}
+		}
+		
+		return true;
+	}
+	
+	public static function delMeta($core,$get,$post)
+	{
+		if (empty($post['postId'])) {
+			throw new Exception('No post ID');
+		}
+		
+		if (empty($post['metaId']) && $post['metaId'] != '0') {
+			throw new Exception('No meta ID');
+		}
+		
+		if (empty($post['metaType'])) {
+			throw new Exception('No meta type');
+		}
+		
+		$core->meta->delPostMeta($post['postId'],$post['metaType'],$post['metaId']);
+		
+		return true;
+	}
+	
+	public static function searchMeta($core,$get)
+	{
+		$q = !empty($get['q']) ? $get['q'] : null;
+		$metaType = !empty($get['metaType']) ? $get['metaType'] : null;
+		
+		$sortby = !empty($get['sortby']) ? $get['sortby'] : 'meta_type,asc';
+		
+		$rs = $core->meta->getMetadata(array('meta_type' => $metaType));
+		$rs = $core->meta->computeMetaStats($rs);
+		
+		$sortby = explode(',',$sortby);
+		$sort = $sortby[0];
+		$order = isset($sortby[1]) ? $sortby[1] : 'asc';
+		
+		switch ($sort) {
+			case 'metaId':
+				$sort = 'meta_id_lower';
+				break;
+			case 'count':
+				$sort = 'count';
+				break;
+			case 'metaType':
+				$sort = 'meta_type';
+				break;
+			default:
+				$sort = 'meta_type';
+		}
+		
+		$rs->sort($sort,$order);
+		
+		$rsp = array();
+		
+		while ($rs->fetch())
+		{
+			if (preg_match('/'.$q.'/i',$rs->meta_id)) {
+				$rsp['meta'][] = array(
+					'type' => $rs->meta_type,
+					'uri' => rawurlencode($rs->meta_id),
+					'count' => $rs->count,
+					'percent' => $rs->percent,
+					'roundpercent' => $rs->roundpercent,
+					'data'	=> $rs->meta_id
+				);
+			}
+		}
+		
+		return $rsp;
+	}
+}
+?>
\ No newline at end of file
diff -r 4a5d59418a79 -r 634481f5e737 inc/core/class.dc.core.php
--- a/inc/core/class.dc.core.php	Sat Nov 17 18:15:49 2012 +0100
+++ b/inc/core/class.dc.core.php	Sun Nov 18 16:53:56 2012 +0100
@@ -37,6 +37,7 @@
 	public $media;		///< <b>dcMedia</b>			dcMedia object
 	public $postmedia;	///< <b>dcPostMedia</b>		dcPostMedia object
 	public $rest;		///< <b>dcRestServer</b>	dcRestServer object
+	public $jsonrest;	///< <b>dcJsonRestServer</b>	dcJsonRestServer object
 	public $log;		///< <b>dcLog</b>			dcLog object
 	
 	private $versions = null;
@@ -87,6 +88,7 @@
 		$this->plugins = new dcModules($this);
 		
 		$this->rest = new dcRestServer($this);
+		$this->jsonrest = new dcJsonRestServer($this);
 		
 		$this->meta = new dcMeta($this);
 		
diff -r 4a5d59418a79 -r 634481f5e737 inc/core/class.dc.jsonrest.php
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/inc/core/class.dc.jsonrest.php	Sun Nov 18 16:53:56 2012 +0100
@@ -0,0 +1,182 @@
+<?php
+# -- BEGIN LICENSE BLOCK ---------------------------------------
+#
+# This file is part of Dotclear 2.
+#
+# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
+# 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 -----------------------------------------
+if (!defined('DC_RC_PATH')) { return; }
+
+/**
+@ingroup DC_CORE
+@brief Dotclear JSON REST server extension
+
+This class handles dcCore instance in each rest method call.
+Instance of this class is provided by dcCore $jsonrest.
+*/
+class dcJsonRestServer
+{
+	public $core;		///< dcCore instance
+	public $rsp;		///< Server response (array)
+	public $functions = array();	///< array of Server's functions
+	
+	/**
+	Object constructor.
+	
+	@param	core		<b>dcCore</b>		dcCore instance
+	*/
+	public function __construct($core)
+	{
+		$this->core =& $core;
+		$this->rsp = new jsonContent();
+	}
+	
+	/**
+	This adds a new function to the server. <var>$callback</var> should be
+	a valid PHP callback. Callback function takes three arguments: 
+	dcCore instance and GET values and POST values.
+	
+	@param string	$name		Function name
+	@param callback	$callback		Callback function
+	*/
+	public function addFunction($name, $callback)
+	{
+		if (is_callable($callback)) {
+			$this->functions[$name] = $callback;
+		}
+	}
+	
+	/**
+	Rest method call.
+	
+	@param	name		<b>string</b>		Method name
+	@param	get		<b>array</b>		GET parameters copy
+	@param	post		<b>array</b>		POST parameters copy
+	@return	<b>mixed</b>	Rest method result
+	*/
+	protected function callFunction($name,$get,$post)
+	{
+		if (isset($this->functions[$name])) {
+			return call_user_func($this->functions[$name],$this->core,$get,$post);
+		}
+	}
+	
+	/**
+	This method creates the main server.
+	
+	@param string	$encoding		Server charset
+	*/
+	public function serve()
+	{
+		$get = array();
+		if (isset($_GET)) {
+			$get = $_GET;
+		}
+		
+		$post = array();
+		if (isset($_POST)) {
+			$post = $_POST;
+		}
+		
+		if (!isset($_REQUEST['f'])) {
+			$this->rsp->setCode(2);
+			$this->rsp->setMessage('No function given');
+			$this->getJSON();
+			return false;
+		}
+		
+		if (!isset($this->functions[$_REQUEST['f']])) {
+			$this->rsp->setCode(3);
+			$this->rsp->setMessage('Function does not exist');
+			$this->getJSON();
+			return false;
+		}
+		
+		try {
+			$res = $this->callFunction($_REQUEST['f'],$get,$post);
+		} catch (Exception $e) {
+			$this->rsp->setCode(4);
+			$this->rsp->setMessage($e->getMessage());
+			$this->getJSON();
+			return false;
+		}
+		
+		$this->rsp->setCode(0);
+		$this->rsp->setMessage("No error");
+		$this->rsp->setData($res);
+		$this->getJSON();
+		
+		return true;
+	}
+	
+	private function getJSON()
+	{
+		if (!function_exists('json_encode')) {
+			header('Content-Type: text/plain; charset=UTF-8');
+			http::head(500);
+			exit();
+		}
+		
+		header('Content-Type: text/javascript; charset=UTF-8');
+		http::head($this->rsp->getStatus() == 'ok' ? 200 : 400);
+		
+		echo $this->rsp->toJSON();
+	}
+}
+
+class jsonContent
+{
+	public $code;
+	public $message;
+	public $data;
+	
+	public function __construct()
+	{
+		$this->code = 1;
+		$this->message = 'no data';
+		$this->data = array();
+	}
+	
+	public function setCode($code)
+	{
+		$this->code = abs((integer) $code);
+	}
+	
+	public function setMessage($message)
+	{
+		$this->message = (string) $message;
+	}
+	
+	public function setData($data)
+	{
+		$this->data = $data;
+	}
+	
+	public function getStatus()
+	{
+		return $this->code > 0 ? 'failed' : 'ok';
+	}
+	
+	/**
+	Return a json string like this:
+	{"error":{"status":"failed","code":2,"message":"No function given"},"data":[]}
+	*/
+	public function toJSON()
+	{
+		$res = array(
+			'error' => array(
+				'status' => $this->getStatus(),
+				'code' => $this->code,
+				'message' => $this->message
+			),
+			'data' => $this->data
+		);
+		
+		return json_encode($res);
+	}
+}
+?>
\ No newline at end of file
diff -r 4a5d59418a79 -r 634481f5e737 inc/prepend.php
--- a/inc/prepend.php	Sat Nov 17 18:15:49 2012 +0100
+++ b/inc/prepend.php	Sun Nov 18 16:53:56 2012 +0100
@@ -36,6 +36,7 @@
 $__autoload['dcModules']				= dirname(__FILE__).'/core/class.dc.modules.php';
 $__autoload['dcThemes']				= dirname(__FILE__).'/core/class.dc.themes.php';
 $__autoload['dcRestServer']			= dirname(__FILE__).'/core/class.dc.rest.php';
+$__autoload['dcJsonRestServer']			= dirname(__FILE__).'/core/class.dc.jsonrest.php';
 $__autoload['dcNamespace']			= dirname(__FILE__).'/core/class.dc.namespace.php';
 $__autoload['dcSettings']			= dirname(__FILE__).'/core/class.dc.settings.php';
 $__autoload['dcTrackback']			= dirname(__FILE__).'/core/class.dc.trackback.php';
