Version Description
(2009-11-30): =
* Added count_total
response
* Added json_api_encode
filter
* Fixed bugs in the introspector's get_current_category
and get_current_tag
Download this release
Release Info
Developer | dphiffer |
Plugin | JSON API |
Version | 0.6 |
Comparing to | |
See all releases |
Version 0.6
- json-api.php +48 -0
- library/JSON.php +861 -0
- models/author.php +62 -0
- models/category.php +29 -0
- models/comment.php +80 -0
- models/post.php +127 -0
- models/tag.php +25 -0
- readme.txt +566 -0
- screenshot-1.png +0 -0
- singletons/controller.php +224 -0
- singletons/introspector.php +184 -0
- singletons/query.php +156 -0
- singletons/response.php +139 -0
json-api.php
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Plugin Name: JSON API
|
4 |
+
Plugin URI: http://wordpress.org/extend/plugins/json-api/
|
5 |
+
Description: A RESTful API for WordPress
|
6 |
+
Version: 0.5
|
7 |
+
Author: Dan Phiffer
|
8 |
+
Author URI: http://phiffer.org/
|
9 |
+
*/
|
10 |
+
|
11 |
+
global $json_api_dir;
|
12 |
+
$json_api_dir = WP_PLUGIN_DIR . '/json-api';
|
13 |
+
|
14 |
+
function json_api_init() {
|
15 |
+
// Initialize the controller and query inspector
|
16 |
+
global $json_api, $json_api_dir;
|
17 |
+
require_once "$json_api_dir/singletons/controller.php";
|
18 |
+
require_once "$json_api_dir/singletons/query.php";
|
19 |
+
$json_api = new JSON_API_Controller();
|
20 |
+
}
|
21 |
+
|
22 |
+
function json_api_activation() {
|
23 |
+
// Add the rewrite rule on activation
|
24 |
+
global $wp_rewrite;
|
25 |
+
add_filter('rewrite_rules_array', 'json_api_rewrites');
|
26 |
+
$wp_rewrite->flush_rules();
|
27 |
+
}
|
28 |
+
|
29 |
+
function json_api_deactivation() {
|
30 |
+
// Remove the rewrite rule on deactivation
|
31 |
+
global $wp_rewrite;
|
32 |
+
$wp_rewrite->flush_rules();
|
33 |
+
}
|
34 |
+
|
35 |
+
function json_api_rewrites($wp_rules) {
|
36 |
+
// Register the rewrite rule /api/[method] => ?json=[method]
|
37 |
+
$json_api_rules = array(
|
38 |
+
'api/(.+)$' => 'index.php?json=$matches[1]'
|
39 |
+
);
|
40 |
+
return array_merge($json_api_rules, $wp_rules);
|
41 |
+
}
|
42 |
+
|
43 |
+
// Add initialization and activation hooks
|
44 |
+
add_action('init', 'json_api_init');
|
45 |
+
register_activation_hook("$json_api_dir/json-api.php", 'json_api_activation');
|
46 |
+
register_deactivation_hook("$json_api_dir/json-api.php", 'json_api_deactivation');
|
47 |
+
|
48 |
+
?>
|
library/JSON.php
ADDED
@@ -0,0 +1,861 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
3 |
+
/**
|
4 |
+
* Converts to and from JSON format.
|
5 |
+
*
|
6 |
+
* JSON (JavaScript Object Notation) is a lightweight data-interchange
|
7 |
+
* format. It is easy for humans to read and write. It is easy for machines
|
8 |
+
* to parse and generate. It is based on a subset of the JavaScript
|
9 |
+
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
|
10 |
+
* This feature can also be found in Python. JSON is a text format that is
|
11 |
+
* completely language independent but uses conventions that are familiar
|
12 |
+
* to programmers of the C-family of languages, including C, C++, C#, Java,
|
13 |
+
* JavaScript, Perl, TCL, and many others. These properties make JSON an
|
14 |
+
* ideal data-interchange language.
|
15 |
+
*
|
16 |
+
* This package provides a simple encoder and decoder for JSON notation. It
|
17 |
+
* is intended for use with client-side Javascript applications that make
|
18 |
+
* use of HTTPRequest to perform server communication functions - data can
|
19 |
+
* be encoded into JSON notation for use in a client-side javascript, or
|
20 |
+
* decoded from incoming Javascript requests. JSON format is native to
|
21 |
+
* Javascript, and can be directly eval()'ed with no further parsing
|
22 |
+
* overhead
|
23 |
+
*
|
24 |
+
* All strings should be in ASCII or UTF-8 format!
|
25 |
+
*
|
26 |
+
* LICENSE: Redistribution and use in source and binary forms, with or
|
27 |
+
* without modification, are permitted provided that the following
|
28 |
+
* conditions are met: Redistributions of source code must retain the
|
29 |
+
* above copyright notice, this list of conditions and the following
|
30 |
+
* disclaimer. Redistributions in binary form must reproduce the above
|
31 |
+
* copyright notice, this list of conditions and the following disclaimer
|
32 |
+
* in the documentation and/or other materials provided with the
|
33 |
+
* distribution.
|
34 |
+
*
|
35 |
+
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
36 |
+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
37 |
+
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
38 |
+
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
39 |
+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
40 |
+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
41 |
+
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
42 |
+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
43 |
+
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
44 |
+
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
45 |
+
* DAMAGE.
|
46 |
+
*
|
47 |
+
* @category
|
48 |
+
* @package Services_JSON
|
49 |
+
* @author Michal Migurski <mike-json@teczno.com>
|
50 |
+
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
|
51 |
+
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
|
52 |
+
* @copyright 2005 Michal Migurski
|
53 |
+
* @version CVS: $Id: JSON.php 288200 2009-09-09 15:41:29Z alan_k $
|
54 |
+
* @license http://www.opensource.org/licenses/bsd-license.php
|
55 |
+
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
|
56 |
+
*/
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Marker constant for Services_JSON::decode(), used to flag stack state
|
60 |
+
*/
|
61 |
+
define('SERVICES_JSON_SLICE', 1);
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Marker constant for Services_JSON::decode(), used to flag stack state
|
65 |
+
*/
|
66 |
+
define('SERVICES_JSON_IN_STR', 2);
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Marker constant for Services_JSON::decode(), used to flag stack state
|
70 |
+
*/
|
71 |
+
define('SERVICES_JSON_IN_ARR', 3);
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Marker constant for Services_JSON::decode(), used to flag stack state
|
75 |
+
*/
|
76 |
+
define('SERVICES_JSON_IN_OBJ', 4);
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Marker constant for Services_JSON::decode(), used to flag stack state
|
80 |
+
*/
|
81 |
+
define('SERVICES_JSON_IN_CMT', 5);
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Behavior switch for Services_JSON::decode()
|
85 |
+
*/
|
86 |
+
define('SERVICES_JSON_LOOSE_TYPE', 16);
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Behavior switch for Services_JSON::decode()
|
90 |
+
*/
|
91 |
+
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Converts to and from JSON format.
|
95 |
+
*
|
96 |
+
* Brief example of use:
|
97 |
+
*
|
98 |
+
* <code>
|
99 |
+
* // create a new instance of Services_JSON
|
100 |
+
* $json = new Services_JSON();
|
101 |
+
*
|
102 |
+
* // convert a complexe value to JSON notation, and send it to the browser
|
103 |
+
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
|
104 |
+
* $output = $json->encode($value);
|
105 |
+
*
|
106 |
+
* print($output);
|
107 |
+
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
|
108 |
+
*
|
109 |
+
* // accept incoming POST data, assumed to be in JSON notation
|
110 |
+
* $input = file_get_contents('php://input', 1000000);
|
111 |
+
* $value = $json->decode($input);
|
112 |
+
* </code>
|
113 |
+
*/
|
114 |
+
class Services_JSON
|
115 |
+
{
|
116 |
+
/**
|
117 |
+
* constructs a new JSON instance
|
118 |
+
*
|
119 |
+
* @param int $use object behavior flags; combine with boolean-OR
|
120 |
+
*
|
121 |
+
* possible values:
|
122 |
+
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
|
123 |
+
* "{...}" syntax creates associative arrays
|
124 |
+
* instead of objects in decode().
|
125 |
+
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
|
126 |
+
* Values which can't be encoded (e.g. resources)
|
127 |
+
* appear as NULL instead of throwing errors.
|
128 |
+
* By default, a deeply-nested resource will
|
129 |
+
* bubble up with an error, so all return values
|
130 |
+
* from encode() should be checked with isError()
|
131 |
+
*/
|
132 |
+
function Services_JSON($use = 0)
|
133 |
+
{
|
134 |
+
$this->use = $use;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* convert a string from one UTF-16 char to one UTF-8 char
|
139 |
+
*
|
140 |
+
* Normally should be handled by mb_convert_encoding, but
|
141 |
+
* provides a slower PHP-only method for installations
|
142 |
+
* that lack the multibye string extension.
|
143 |
+
*
|
144 |
+
* @param string $utf16 UTF-16 character
|
145 |
+
* @return string UTF-8 character
|
146 |
+
* @access private
|
147 |
+
*/
|
148 |
+
function utf162utf8($utf16)
|
149 |
+
{
|
150 |
+
// oh please oh please oh please oh please oh please
|
151 |
+
if(function_exists('mb_convert_encoding')) {
|
152 |
+
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
|
153 |
+
}
|
154 |
+
|
155 |
+
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
|
156 |
+
|
157 |
+
switch(true) {
|
158 |
+
case ((0x7F & $bytes) == $bytes):
|
159 |
+
// this case should never be reached, because we are in ASCII range
|
160 |
+
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
161 |
+
return chr(0x7F & $bytes);
|
162 |
+
|
163 |
+
case (0x07FF & $bytes) == $bytes:
|
164 |
+
// return a 2-byte UTF-8 character
|
165 |
+
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
166 |
+
return chr(0xC0 | (($bytes >> 6) & 0x1F))
|
167 |
+
. chr(0x80 | ($bytes & 0x3F));
|
168 |
+
|
169 |
+
case (0xFFFF & $bytes) == $bytes:
|
170 |
+
// return a 3-byte UTF-8 character
|
171 |
+
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
172 |
+
return chr(0xE0 | (($bytes >> 12) & 0x0F))
|
173 |
+
. chr(0x80 | (($bytes >> 6) & 0x3F))
|
174 |
+
. chr(0x80 | ($bytes & 0x3F));
|
175 |
+
}
|
176 |
+
|
177 |
+
// ignoring UTF-32 for now, sorry
|
178 |
+
return '';
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* convert a string from one UTF-8 char to one UTF-16 char
|
183 |
+
*
|
184 |
+
* Normally should be handled by mb_convert_encoding, but
|
185 |
+
* provides a slower PHP-only method for installations
|
186 |
+
* that lack the multibye string extension.
|
187 |
+
*
|
188 |
+
* @param string $utf8 UTF-8 character
|
189 |
+
* @return string UTF-16 character
|
190 |
+
* @access private
|
191 |
+
*/
|
192 |
+
function utf82utf16($utf8)
|
193 |
+
{
|
194 |
+
// oh please oh please oh please oh please oh please
|
195 |
+
if(function_exists('mb_convert_encoding')) {
|
196 |
+
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
|
197 |
+
}
|
198 |
+
|
199 |
+
switch(strlen($utf8)) {
|
200 |
+
case 1:
|
201 |
+
// this case should never be reached, because we are in ASCII range
|
202 |
+
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
203 |
+
return $utf8;
|
204 |
+
|
205 |
+
case 2:
|
206 |
+
// return a UTF-16 character from a 2-byte UTF-8 char
|
207 |
+
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
208 |
+
return chr(0x07 & (ord($utf8{0}) >> 2))
|
209 |
+
. chr((0xC0 & (ord($utf8{0}) << 6))
|
210 |
+
| (0x3F & ord($utf8{1})));
|
211 |
+
|
212 |
+
case 3:
|
213 |
+
// return a UTF-16 character from a 3-byte UTF-8 char
|
214 |
+
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
215 |
+
return chr((0xF0 & (ord($utf8{0}) << 4))
|
216 |
+
| (0x0F & (ord($utf8{1}) >> 2)))
|
217 |
+
. chr((0xC0 & (ord($utf8{1}) << 6))
|
218 |
+
| (0x7F & ord($utf8{2})));
|
219 |
+
}
|
220 |
+
|
221 |
+
// ignoring UTF-32 for now, sorry
|
222 |
+
return '';
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* encodes an arbitrary variable into JSON format (and sends JSON Header)
|
227 |
+
*
|
228 |
+
* @param mixed $var any number, boolean, string, array, or object to be encoded.
|
229 |
+
* see argument 1 to Services_JSON() above for array-parsing behavior.
|
230 |
+
* if var is a strng, note that encode() always expects it
|
231 |
+
* to be in ASCII or UTF-8 format!
|
232 |
+
*
|
233 |
+
* @return mixed JSON string representation of input var or an error if a problem occurs
|
234 |
+
* @access public
|
235 |
+
*/
|
236 |
+
function encode($var)
|
237 |
+
{
|
238 |
+
header('Content-type: application/json');
|
239 |
+
return $this->_encode($var);
|
240 |
+
}
|
241 |
+
/**
|
242 |
+
* encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
|
243 |
+
*
|
244 |
+
* @param mixed $var any number, boolean, string, array, or object to be encoded.
|
245 |
+
* see argument 1 to Services_JSON() above for array-parsing behavior.
|
246 |
+
* if var is a strng, note that encode() always expects it
|
247 |
+
* to be in ASCII or UTF-8 format!
|
248 |
+
*
|
249 |
+
* @return mixed JSON string representation of input var or an error if a problem occurs
|
250 |
+
* @access public
|
251 |
+
*/
|
252 |
+
function encodeUnsafe($var)
|
253 |
+
{
|
254 |
+
return $this->_encode($var);
|
255 |
+
}
|
256 |
+
/**
|
257 |
+
* PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
|
258 |
+
*
|
259 |
+
* @param mixed $var any number, boolean, string, array, or object to be encoded.
|
260 |
+
* see argument 1 to Services_JSON() above for array-parsing behavior.
|
261 |
+
* if var is a strng, note that encode() always expects it
|
262 |
+
* to be in ASCII or UTF-8 format!
|
263 |
+
*
|
264 |
+
* @return mixed JSON string representation of input var or an error if a problem occurs
|
265 |
+
* @access public
|
266 |
+
*/
|
267 |
+
function _encode($var)
|
268 |
+
{
|
269 |
+
|
270 |
+
switch (gettype($var)) {
|
271 |
+
case 'boolean':
|
272 |
+
return $var ? 'true' : 'false';
|
273 |
+
|
274 |
+
case 'NULL':
|
275 |
+
return 'null';
|
276 |
+
|
277 |
+
case 'integer':
|
278 |
+
return (int) $var;
|
279 |
+
|
280 |
+
case 'double':
|
281 |
+
case 'float':
|
282 |
+
return (float) $var;
|
283 |
+
|
284 |
+
case 'string':
|
285 |
+
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
|
286 |
+
$ascii = '';
|
287 |
+
$strlen_var = strlen($var);
|
288 |
+
|
289 |
+
/*
|
290 |
+
* Iterate over every character in the string,
|
291 |
+
* escaping with a slash or encoding to UTF-8 where necessary
|
292 |
+
*/
|
293 |
+
for ($c = 0; $c < $strlen_var; ++$c) {
|
294 |
+
|
295 |
+
$ord_var_c = ord($var{$c});
|
296 |
+
|
297 |
+
switch (true) {
|
298 |
+
case $ord_var_c == 0x08:
|
299 |
+
$ascii .= '\b';
|
300 |
+
break;
|
301 |
+
case $ord_var_c == 0x09:
|
302 |
+
$ascii .= '\t';
|
303 |
+
break;
|
304 |
+
case $ord_var_c == 0x0A:
|
305 |
+
$ascii .= '\n';
|
306 |
+
break;
|
307 |
+
case $ord_var_c == 0x0C:
|
308 |
+
$ascii .= '\f';
|
309 |
+
break;
|
310 |
+
case $ord_var_c == 0x0D:
|
311 |
+
$ascii .= '\r';
|
312 |
+
break;
|
313 |
+
|
314 |
+
case $ord_var_c == 0x22:
|
315 |
+
case $ord_var_c == 0x2F:
|
316 |
+
case $ord_var_c == 0x5C:
|
317 |
+
// double quote, slash, slosh
|
318 |
+
$ascii .= '\\'.$var{$c};
|
319 |
+
break;
|
320 |
+
|
321 |
+
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
|
322 |
+
// characters U-00000000 - U-0000007F (same as ASCII)
|
323 |
+
$ascii .= $var{$c};
|
324 |
+
break;
|
325 |
+
|
326 |
+
case (($ord_var_c & 0xE0) == 0xC0):
|
327 |
+
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
328 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
329 |
+
if ($c+1 >= $strlen_var) {
|
330 |
+
$c += 1;
|
331 |
+
$ascii .= '?';
|
332 |
+
break;
|
333 |
+
}
|
334 |
+
|
335 |
+
$char = pack('C*', $ord_var_c, ord($var{$c + 1}));
|
336 |
+
$c += 1;
|
337 |
+
$utf16 = $this->utf82utf16($char);
|
338 |
+
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
339 |
+
break;
|
340 |
+
|
341 |
+
case (($ord_var_c & 0xF0) == 0xE0):
|
342 |
+
if ($c+2 >= $strlen_var) {
|
343 |
+
$c += 2;
|
344 |
+
$ascii .= '?';
|
345 |
+
break;
|
346 |
+
}
|
347 |
+
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
348 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
349 |
+
$char = pack('C*', $ord_var_c,
|
350 |
+
@ord($var{$c + 1}),
|
351 |
+
@ord($var{$c + 2}));
|
352 |
+
$c += 2;
|
353 |
+
$utf16 = $this->utf82utf16($char);
|
354 |
+
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
355 |
+
break;
|
356 |
+
|
357 |
+
case (($ord_var_c & 0xF8) == 0xF0):
|
358 |
+
if ($c+3 >= $strlen_var) {
|
359 |
+
$c += 3;
|
360 |
+
$ascii .= '?';
|
361 |
+
break;
|
362 |
+
}
|
363 |
+
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
364 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
365 |
+
$char = pack('C*', $ord_var_c,
|
366 |
+
ord($var{$c + 1}),
|
367 |
+
ord($var{$c + 2}),
|
368 |
+
ord($var{$c + 3}));
|
369 |
+
$c += 3;
|
370 |
+
$utf16 = $this->utf82utf16($char);
|
371 |
+
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
372 |
+
break;
|
373 |
+
|
374 |
+
case (($ord_var_c & 0xFC) == 0xF8):
|
375 |
+
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
376 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
377 |
+
if ($c+4 >= $strlen_var) {
|
378 |
+
$c += 4;
|
379 |
+
$ascii .= '?';
|
380 |
+
break;
|
381 |
+
}
|
382 |
+
$char = pack('C*', $ord_var_c,
|
383 |
+
ord($var{$c + 1}),
|
384 |
+
ord($var{$c + 2}),
|
385 |
+
ord($var{$c + 3}),
|
386 |
+
ord($var{$c + 4}));
|
387 |
+
$c += 4;
|
388 |
+
$utf16 = $this->utf82utf16($char);
|
389 |
+
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
390 |
+
break;
|
391 |
+
|
392 |
+
case (($ord_var_c & 0xFE) == 0xFC):
|
393 |
+
if ($c+5 >= $strlen_var) {
|
394 |
+
$c += 5;
|
395 |
+
$ascii .= '?';
|
396 |
+
break;
|
397 |
+
}
|
398 |
+
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
399 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
400 |
+
$char = pack('C*', $ord_var_c,
|
401 |
+
ord($var{$c + 1}),
|
402 |
+
ord($var{$c + 2}),
|
403 |
+
ord($var{$c + 3}),
|
404 |
+
ord($var{$c + 4}),
|
405 |
+
ord($var{$c + 5}));
|
406 |
+
$c += 5;
|
407 |
+
$utf16 = $this->utf82utf16($char);
|
408 |
+
$ascii .= sprintf('\u%04s', bin2hex($utf16));
|
409 |
+
break;
|
410 |
+
}
|
411 |
+
}
|
412 |
+
return '"'.$ascii.'"';
|
413 |
+
|
414 |
+
case 'array':
|
415 |
+
/*
|
416 |
+
* As per JSON spec if any array key is not an integer
|
417 |
+
* we must treat the the whole array as an object. We
|
418 |
+
* also try to catch a sparsely populated associative
|
419 |
+
* array with numeric keys here because some JS engines
|
420 |
+
* will create an array with empty indexes up to
|
421 |
+
* max_index which can cause memory issues and because
|
422 |
+
* the keys, which may be relevant, will be remapped
|
423 |
+
* otherwise.
|
424 |
+
*
|
425 |
+
* As per the ECMA and JSON specification an object may
|
426 |
+
* have any string as a property. Unfortunately due to
|
427 |
+
* a hole in the ECMA specification if the key is a
|
428 |
+
* ECMA reserved word or starts with a digit the
|
429 |
+
* parameter is only accessible using ECMAScript's
|
430 |
+
* bracket notation.
|
431 |
+
*/
|
432 |
+
|
433 |
+
// treat as a JSON object
|
434 |
+
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
|
435 |
+
$properties = array_map(array($this, 'name_value'),
|
436 |
+
array_keys($var),
|
437 |
+
array_values($var));
|
438 |
+
|
439 |
+
foreach($properties as $property) {
|
440 |
+
if(Services_JSON::isError($property)) {
|
441 |
+
return $property;
|
442 |
+
}
|
443 |
+
}
|
444 |
+
|
445 |
+
return '{' . join(',', $properties) . '}';
|
446 |
+
}
|
447 |
+
|
448 |
+
// treat it like a regular array
|
449 |
+
$elements = array_map(array($this, '_encode'), $var);
|
450 |
+
|
451 |
+
foreach($elements as $element) {
|
452 |
+
if(Services_JSON::isError($element)) {
|
453 |
+
return $element;
|
454 |
+
}
|
455 |
+
}
|
456 |
+
|
457 |
+
return '[' . join(',', $elements) . ']';
|
458 |
+
|
459 |
+
case 'object':
|
460 |
+
$vars = get_object_vars($var);
|
461 |
+
|
462 |
+
$properties = array_map(array($this, 'name_value'),
|
463 |
+
array_keys($vars),
|
464 |
+
array_values($vars));
|
465 |
+
|
466 |
+
foreach($properties as $property) {
|
467 |
+
if(Services_JSON::isError($property)) {
|
468 |
+
return $property;
|
469 |
+
}
|
470 |
+
}
|
471 |
+
|
472 |
+
return '{' . join(',', $properties) . '}';
|
473 |
+
|
474 |
+
default:
|
475 |
+
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
|
476 |
+
? 'null'
|
477 |
+
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
|
478 |
+
}
|
479 |
+
}
|
480 |
+
|
481 |
+
/**
|
482 |
+
* array-walking function for use in generating JSON-formatted name-value pairs
|
483 |
+
*
|
484 |
+
* @param string $name name of key to use
|
485 |
+
* @param mixed $value reference to an array element to be encoded
|
486 |
+
*
|
487 |
+
* @return string JSON-formatted name-value pair, like '"name":value'
|
488 |
+
* @access private
|
489 |
+
*/
|
490 |
+
function name_value($name, $value)
|
491 |
+
{
|
492 |
+
$encoded_value = $this->_encode($value);
|
493 |
+
|
494 |
+
if(Services_JSON::isError($encoded_value)) {
|
495 |
+
return $encoded_value;
|
496 |
+
}
|
497 |
+
|
498 |
+
return $this->_encode(strval($name)) . ':' . $encoded_value;
|
499 |
+
}
|
500 |
+
|
501 |
+
/**
|
502 |
+
* reduce a string by removing leading and trailing comments and whitespace
|
503 |
+
*
|
504 |
+
* @param $str string string value to strip of comments and whitespace
|
505 |
+
*
|
506 |
+
* @return string string value stripped of comments and whitespace
|
507 |
+
* @access private
|
508 |
+
*/
|
509 |
+
function reduce_string($str)
|
510 |
+
{
|
511 |
+
$str = preg_replace(array(
|
512 |
+
|
513 |
+
// eliminate single line comments in '// ...' form
|
514 |
+
'#^\s*//(.+)$#m',
|
515 |
+
|
516 |
+
// eliminate multi-line comments in '/* ... */' form, at start of string
|
517 |
+
'#^\s*/\*(.+)\*/#Us',
|
518 |
+
|
519 |
+
// eliminate multi-line comments in '/* ... */' form, at end of string
|
520 |
+
'#/\*(.+)\*/\s*$#Us'
|
521 |
+
|
522 |
+
), '', $str);
|
523 |
+
|
524 |
+
// eliminate extraneous space
|
525 |
+
return trim($str);
|
526 |
+
}
|
527 |
+
|
528 |
+
/**
|
529 |
+
* decodes a JSON string into appropriate variable
|
530 |
+
*
|
531 |
+
* @param string $str JSON-formatted string
|
532 |
+
*
|
533 |
+
* @return mixed number, boolean, string, array, or object
|
534 |
+
* corresponding to given JSON input string.
|
535 |
+
* See argument 1 to Services_JSON() above for object-output behavior.
|
536 |
+
* Note that decode() always returns strings
|
537 |
+
* in ASCII or UTF-8 format!
|
538 |
+
* @access public
|
539 |
+
*/
|
540 |
+
function decode($str)
|
541 |
+
{
|
542 |
+
$str = $this->reduce_string($str);
|
543 |
+
|
544 |
+
switch (strtolower($str)) {
|
545 |
+
case 'true':
|
546 |
+
return true;
|
547 |
+
|
548 |
+
case 'false':
|
549 |
+
return false;
|
550 |
+
|
551 |
+
case 'null':
|
552 |
+
return null;
|
553 |
+
|
554 |
+
default:
|
555 |
+
$m = array();
|
556 |
+
|
557 |
+
if (is_numeric($str)) {
|
558 |
+
// Lookie-loo, it's a number
|
559 |
+
|
560 |
+
// This would work on its own, but I'm trying to be
|
561 |
+
// good about returning integers where appropriate:
|
562 |
+
// return (float)$str;
|
563 |
+
|
564 |
+
// Return float or int, as appropriate
|
565 |
+
return ((float)$str == (integer)$str)
|
566 |
+
? (integer)$str
|
567 |
+
: (float)$str;
|
568 |
+
|
569 |
+
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
|
570 |
+
// STRINGS RETURNED IN UTF-8 FORMAT
|
571 |
+
$delim = substr($str, 0, 1);
|
572 |
+
$chrs = substr($str, 1, -1);
|
573 |
+
$utf8 = '';
|
574 |
+
$strlen_chrs = strlen($chrs);
|
575 |
+
|
576 |
+
for ($c = 0; $c < $strlen_chrs; ++$c) {
|
577 |
+
|
578 |
+
$substr_chrs_c_2 = substr($chrs, $c, 2);
|
579 |
+
$ord_chrs_c = ord($chrs{$c});
|
580 |
+
|
581 |
+
switch (true) {
|
582 |
+
case $substr_chrs_c_2 == '\b':
|
583 |
+
$utf8 .= chr(0x08);
|
584 |
+
++$c;
|
585 |
+
break;
|
586 |
+
case $substr_chrs_c_2 == '\t':
|
587 |
+
$utf8 .= chr(0x09);
|
588 |
+
++$c;
|
589 |
+
break;
|
590 |
+
case $substr_chrs_c_2 == '\n':
|
591 |
+
$utf8 .= chr(0x0A);
|
592 |
+
++$c;
|
593 |
+
break;
|
594 |
+
case $substr_chrs_c_2 == '\f':
|
595 |
+
$utf8 .= chr(0x0C);
|
596 |
+
++$c;
|
597 |
+
break;
|
598 |
+
case $substr_chrs_c_2 == '\r':
|
599 |
+
$utf8 .= chr(0x0D);
|
600 |
+
++$c;
|
601 |
+
break;
|
602 |
+
|
603 |
+
case $substr_chrs_c_2 == '\\"':
|
604 |
+
case $substr_chrs_c_2 == '\\\'':
|
605 |
+
case $substr_chrs_c_2 == '\\\\':
|
606 |
+
case $substr_chrs_c_2 == '\\/':
|
607 |
+
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
|
608 |
+
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
|
609 |
+
$utf8 .= $chrs{++$c};
|
610 |
+
}
|
611 |
+
break;
|
612 |
+
|
613 |
+
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
|
614 |
+
// single, escaped unicode character
|
615 |
+
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
|
616 |
+
. chr(hexdec(substr($chrs, ($c + 4), 2)));
|
617 |
+
$utf8 .= $this->utf162utf8($utf16);
|
618 |
+
$c += 5;
|
619 |
+
break;
|
620 |
+
|
621 |
+
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
|
622 |
+
$utf8 .= $chrs{$c};
|
623 |
+
break;
|
624 |
+
|
625 |
+
case ($ord_chrs_c & 0xE0) == 0xC0:
|
626 |
+
// characters U-00000080 - U-000007FF, mask 110XXXXX
|
627 |
+
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
628 |
+
$utf8 .= substr($chrs, $c, 2);
|
629 |
+
++$c;
|
630 |
+
break;
|
631 |
+
|
632 |
+
case ($ord_chrs_c & 0xF0) == 0xE0:
|
633 |
+
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
|
634 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
635 |
+
$utf8 .= substr($chrs, $c, 3);
|
636 |
+
$c += 2;
|
637 |
+
break;
|
638 |
+
|
639 |
+
case ($ord_chrs_c & 0xF8) == 0xF0:
|
640 |
+
// characters U-00010000 - U-001FFFFF, mask 11110XXX
|
641 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
642 |
+
$utf8 .= substr($chrs, $c, 4);
|
643 |
+
$c += 3;
|
644 |
+
break;
|
645 |
+
|
646 |
+
case ($ord_chrs_c & 0xFC) == 0xF8:
|
647 |
+
// characters U-00200000 - U-03FFFFFF, mask 111110XX
|
648 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
649 |
+
$utf8 .= substr($chrs, $c, 5);
|
650 |
+
$c += 4;
|
651 |
+
break;
|
652 |
+
|
653 |
+
case ($ord_chrs_c & 0xFE) == 0xFC:
|
654 |
+
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
|
655 |
+
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
656 |
+
$utf8 .= substr($chrs, $c, 6);
|
657 |
+
$c += 5;
|
658 |
+
break;
|
659 |
+
|
660 |
+
}
|
661 |
+
|
662 |
+
}
|
663 |
+
|
664 |
+
return $utf8;
|
665 |
+
|
666 |
+
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
|
667 |
+
// array, or object notation
|
668 |
+
|
669 |
+
if ($str{0} == '[') {
|
670 |
+
$stk = array(SERVICES_JSON_IN_ARR);
|
671 |
+
$arr = array();
|
672 |
+
} else {
|
673 |
+
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
674 |
+
$stk = array(SERVICES_JSON_IN_OBJ);
|
675 |
+
$obj = array();
|
676 |
+
} else {
|
677 |
+
$stk = array(SERVICES_JSON_IN_OBJ);
|
678 |
+
$obj = new stdClass();
|
679 |
+
}
|
680 |
+
}
|
681 |
+
|
682 |
+
array_push($stk, array('what' => SERVICES_JSON_SLICE,
|
683 |
+
'where' => 0,
|
684 |
+
'delim' => false));
|
685 |
+
|
686 |
+
$chrs = substr($str, 1, -1);
|
687 |
+
$chrs = $this->reduce_string($chrs);
|
688 |
+
|
689 |
+
if ($chrs == '') {
|
690 |
+
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
691 |
+
return $arr;
|
692 |
+
|
693 |
+
} else {
|
694 |
+
return $obj;
|
695 |
+
|
696 |
+
}
|
697 |
+
}
|
698 |
+
|
699 |
+
//print("\nparsing {$chrs}\n");
|
700 |
+
|
701 |
+
$strlen_chrs = strlen($chrs);
|
702 |
+
|
703 |
+
for ($c = 0; $c <= $strlen_chrs; ++$c) {
|
704 |
+
|
705 |
+
$top = end($stk);
|
706 |
+
$substr_chrs_c_2 = substr($chrs, $c, 2);
|
707 |
+
|
708 |
+
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
|
709 |
+
// found a comma that is not inside a string, array, etc.,
|
710 |
+
// OR we've reached the end of the character list
|
711 |
+
$slice = substr($chrs, $top['where'], ($c - $top['where']));
|
712 |
+
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
|
713 |
+
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
714 |
+
|
715 |
+
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
716 |
+
// we are in an array, so just push an element onto the stack
|
717 |
+
array_push($arr, $this->decode($slice));
|
718 |
+
|
719 |
+
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
|
720 |
+
// we are in an object, so figure
|
721 |
+
// out the property name and set an
|
722 |
+
// element in an associative array,
|
723 |
+
// for now
|
724 |
+
$parts = array();
|
725 |
+
|
726 |
+
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
|
727 |
+
// "name":value pair
|
728 |
+
$key = $this->decode($parts[1]);
|
729 |
+
$val = $this->decode($parts[2]);
|
730 |
+
|
731 |
+
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
732 |
+
$obj[$key] = $val;
|
733 |
+
} else {
|
734 |
+
$obj->$key = $val;
|
735 |
+
}
|
736 |
+
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
|
737 |
+
// name:value pair, where name is unquoted
|
738 |
+
$key = $parts[1];
|
739 |
+
$val = $this->decode($parts[2]);
|
740 |
+
|
741 |
+
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
|
742 |
+
$obj[$key] = $val;
|
743 |
+
} else {
|
744 |
+
$obj->$key = $val;
|
745 |
+
}
|
746 |
+
}
|
747 |
+
|
748 |
+
}
|
749 |
+
|
750 |
+
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
|
751 |
+
// found a quote, and we are not inside a string
|
752 |
+
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
|
753 |
+
//print("Found start of string at {$c}\n");
|
754 |
+
|
755 |
+
} elseif (($chrs{$c} == $top['delim']) &&
|
756 |
+
($top['what'] == SERVICES_JSON_IN_STR) &&
|
757 |
+
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
|
758 |
+
// found a quote, we're in a string, and it's not escaped
|
759 |
+
// we know that it's not escaped becase there is _not_ an
|
760 |
+
// odd number of backslashes at the end of the string so far
|
761 |
+
array_pop($stk);
|
762 |
+
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
|
763 |
+
|
764 |
+
} elseif (($chrs{$c} == '[') &&
|
765 |
+
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
766 |
+
// found a left-bracket, and we are in an array, object, or slice
|
767 |
+
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
|
768 |
+
//print("Found start of array at {$c}\n");
|
769 |
+
|
770 |
+
} elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
|
771 |
+
// found a right-bracket, and we're in an array
|
772 |
+
array_pop($stk);
|
773 |
+
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
774 |
+
|
775 |
+
} elseif (($chrs{$c} == '{') &&
|
776 |
+
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
777 |
+
// found a left-brace, and we are in an array, object, or slice
|
778 |
+
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
|
779 |
+
//print("Found start of object at {$c}\n");
|
780 |
+
|
781 |
+
} elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
|
782 |
+
// found a right-brace, and we're in an object
|
783 |
+
array_pop($stk);
|
784 |
+
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
785 |
+
|
786 |
+
} elseif (($substr_chrs_c_2 == '/*') &&
|
787 |
+
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
|
788 |
+
// found a comment start, and we are in an array, object, or slice
|
789 |
+
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
|
790 |
+
$c++;
|
791 |
+
//print("Found start of comment at {$c}\n");
|
792 |
+
|
793 |
+
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
|
794 |
+
// found a comment end, and we're in one now
|
795 |
+
array_pop($stk);
|
796 |
+
$c++;
|
797 |
+
|
798 |
+
for ($i = $top['where']; $i <= $c; ++$i)
|
799 |
+
$chrs = substr_replace($chrs, ' ', $i, 1);
|
800 |
+
|
801 |
+
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
|
802 |
+
|
803 |
+
}
|
804 |
+
|
805 |
+
}
|
806 |
+
|
807 |
+
if (reset($stk) == SERVICES_JSON_IN_ARR) {
|
808 |
+
return $arr;
|
809 |
+
|
810 |
+
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
|
811 |
+
return $obj;
|
812 |
+
|
813 |
+
}
|
814 |
+
|
815 |
+
}
|
816 |
+
}
|
817 |
+
}
|
818 |
+
|
819 |
+
/**
|
820 |
+
* @todo Ultimately, this should just call PEAR::isError()
|
821 |
+
*/
|
822 |
+
function isError($data, $code = null)
|
823 |
+
{
|
824 |
+
if (class_exists('pear')) {
|
825 |
+
return PEAR::isError($data, $code);
|
826 |
+
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
|
827 |
+
is_subclass_of($data, 'services_json_error'))) {
|
828 |
+
return true;
|
829 |
+
}
|
830 |
+
|
831 |
+
return false;
|
832 |
+
}
|
833 |
+
}
|
834 |
+
|
835 |
+
if (class_exists('PEAR_Error')) {
|
836 |
+
|
837 |
+
class Services_JSON_Error extends PEAR_Error
|
838 |
+
{
|
839 |
+
function Services_JSON_Error($message = 'unknown error', $code = null,
|
840 |
+
$mode = null, $options = null, $userinfo = null)
|
841 |
+
{
|
842 |
+
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
|
843 |
+
}
|
844 |
+
}
|
845 |
+
|
846 |
+
} else {
|
847 |
+
|
848 |
+
/**
|
849 |
+
* @todo Ultimately, this class shall be descended from PEAR_Error
|
850 |
+
*/
|
851 |
+
class Services_JSON_Error
|
852 |
+
{
|
853 |
+
function Services_JSON_Error($message = 'unknown error', $code = null,
|
854 |
+
$mode = null, $options = null, $userinfo = null)
|
855 |
+
{
|
856 |
+
|
857 |
+
}
|
858 |
+
}
|
859 |
+
|
860 |
+
}
|
861 |
+
|
models/author.php
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Author {
|
4 |
+
|
5 |
+
var $id; // Integer
|
6 |
+
var $slug; // String
|
7 |
+
var $name; // String
|
8 |
+
var $first_name; // String
|
9 |
+
var $last_name; // String
|
10 |
+
var $nickname; // String
|
11 |
+
var $url; // String
|
12 |
+
var $description; // String
|
13 |
+
|
14 |
+
// Note:
|
15 |
+
// JSON_API_Author objects can include additional values by using the
|
16 |
+
// author_meta query var.
|
17 |
+
|
18 |
+
function JSON_API_Author($id = null) {
|
19 |
+
if ($id) {
|
20 |
+
$this->id = (int) $id;
|
21 |
+
} else {
|
22 |
+
$this->id = (int) get_the_author_meta('ID');
|
23 |
+
}
|
24 |
+
$this->set_value('slug', 'user_nicename');
|
25 |
+
$this->set_value('name', 'display_name');
|
26 |
+
$this->set_value('first_name', 'first_name');
|
27 |
+
$this->set_value('last_name', 'last_name');
|
28 |
+
$this->set_value('nickname', 'nickname');
|
29 |
+
$this->set_value('url', 'user_url');
|
30 |
+
$this->set_value('description', 'description');
|
31 |
+
$this->set_author_meta();
|
32 |
+
//$this->raw = get_userdata($this->id);
|
33 |
+
}
|
34 |
+
|
35 |
+
function set_value($key, $wp_key = false) {
|
36 |
+
if (!$wp_key) {
|
37 |
+
$wp_key = $key;
|
38 |
+
}
|
39 |
+
$this->$key = get_the_author_meta($wp_key, $this->id);
|
40 |
+
}
|
41 |
+
|
42 |
+
function set_author_meta() {
|
43 |
+
global $json_api;
|
44 |
+
if (!$json_api->query->author_meta) {
|
45 |
+
return;
|
46 |
+
}
|
47 |
+
$protected_vars = array(
|
48 |
+
'user_login',
|
49 |
+
'user_pass',
|
50 |
+
'user_email',
|
51 |
+
'user_activation_key'
|
52 |
+
);
|
53 |
+
$vars = explode(',', $json_api->query->author_meta);
|
54 |
+
$vars = array_diff($vars, $protected_vars);
|
55 |
+
foreach ($vars as $var) {
|
56 |
+
$this->set_value($var);
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
?>
|
models/category.php
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Category {
|
4 |
+
|
5 |
+
var $id; // Integer
|
6 |
+
var $slug; // String
|
7 |
+
var $title; // String
|
8 |
+
var $description; // String
|
9 |
+
var $parent; // Integer
|
10 |
+
var $post_count; // Integer
|
11 |
+
|
12 |
+
function JSON_API_Category($wp_category = null) {
|
13 |
+
if ($wp_category) {
|
14 |
+
$this->import_wp_object($wp_category);
|
15 |
+
}
|
16 |
+
}
|
17 |
+
|
18 |
+
function import_wp_object($wp_category) {
|
19 |
+
$this->id = (int) $wp_category->term_id;
|
20 |
+
$this->slug = $wp_category->slug;
|
21 |
+
$this->title = $wp_category->name;
|
22 |
+
$this->description = $wp_category->description;
|
23 |
+
$this->parent = (int) $wp_category->parent;
|
24 |
+
$this->post_count = (int) $wp_category->count;
|
25 |
+
}
|
26 |
+
|
27 |
+
}
|
28 |
+
|
29 |
+
?>
|
models/comment.php
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Comment {
|
4 |
+
|
5 |
+
var $id; // Integer
|
6 |
+
var $name; // String
|
7 |
+
var $url; // String
|
8 |
+
var $date; // String
|
9 |
+
var $content; // String
|
10 |
+
var $parent; // Integer
|
11 |
+
var $author; // Object (only if the user was registered & logged in)
|
12 |
+
|
13 |
+
function JSON_API_Comment($wp_comment = null) {
|
14 |
+
if ($wp_comment) {
|
15 |
+
$this->import_wp_object($wp_comment);
|
16 |
+
}
|
17 |
+
}
|
18 |
+
|
19 |
+
function import_wp_object($wp_comment) {
|
20 |
+
global $json_api;
|
21 |
+
|
22 |
+
$date_format = $json_api->query->date_format;
|
23 |
+
$content = apply_filters('comment_text', $wp_comment->comment_content);
|
24 |
+
|
25 |
+
$this->id = (int) $wp_comment->comment_ID;
|
26 |
+
$this->name = $wp_comment->comment_author;
|
27 |
+
$this->url = $wp_comment->comment_author_url;
|
28 |
+
$this->date = date($date_format, strtotime($wp_comment->comment_date));
|
29 |
+
$this->content = $content;
|
30 |
+
$this->parent = (int) $wp_comment->comment_parent;
|
31 |
+
//$this->raw = $wp_comment;
|
32 |
+
|
33 |
+
if (!empty($wp_comment->user_id)) {
|
34 |
+
$this->author = new JSON_API_Author($wp_comment->user_id);
|
35 |
+
} else {
|
36 |
+
unset($this->author);
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
function handle_submission() {
|
41 |
+
global $comment, $wpdb;
|
42 |
+
add_action('comment_id_not_found', array(&$this, 'comment_id_not_found'));
|
43 |
+
add_action('comment_closed', array(&$this, 'comment_closed'));
|
44 |
+
add_action('comment_on_draft', array(&$this, 'comment_on_draft'));
|
45 |
+
add_filter('comment_post_redirect', array(&$this, 'comment_post_redirect'));
|
46 |
+
$_SERVER['REQUEST_METHOD'] = 'POST';
|
47 |
+
$_POST['comment_post_ID'] = $_REQUEST['post_id'];
|
48 |
+
$_POST['author'] = $_REQUEST['name'];
|
49 |
+
$_POST['email'] = $_REQUEST['email'];
|
50 |
+
$_POST['url'] = empty($_REQUEST['url']) ? '' : $_REQUEST['url'];
|
51 |
+
$_POST['comment'] = $_REQUEST['content'];
|
52 |
+
$_POST['parent'] = $_REQUEST['parent'];
|
53 |
+
include ABSPATH . 'wp-comments-post.php';
|
54 |
+
}
|
55 |
+
|
56 |
+
function comment_id_not_found() {
|
57 |
+
global $json_api;
|
58 |
+
$json_api->error("Post ID '{$_REQUEST['post_id']}' not found.");
|
59 |
+
}
|
60 |
+
|
61 |
+
function comment_closed() {
|
62 |
+
global $json_api;
|
63 |
+
$json_api->error("Post is closed for comments.");
|
64 |
+
}
|
65 |
+
|
66 |
+
function comment_on_draft() {
|
67 |
+
global $json_api;
|
68 |
+
$json_api->error("You cannot comment on unpublished posts.");
|
69 |
+
}
|
70 |
+
|
71 |
+
function comment_post_redirect() {
|
72 |
+
global $comment, $json_api;
|
73 |
+
$status = ($comment->comment_approved) ? 'ok' : 'pending';
|
74 |
+
$new_comment = new JSON_API_Comment($comment);
|
75 |
+
$json_api->response->respond($new_comment, $status);
|
76 |
+
}
|
77 |
+
|
78 |
+
}
|
79 |
+
|
80 |
+
?>
|
models/post.php
ADDED
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Post {
|
4 |
+
|
5 |
+
// Note:
|
6 |
+
// JSON_API_Post objects must be instantiated within The Loop.
|
7 |
+
|
8 |
+
var $id; // Integer
|
9 |
+
var $slug; // String
|
10 |
+
var $url; // String
|
11 |
+
var $title; // String
|
12 |
+
var $title_plain; // String
|
13 |
+
var $content; // String (modified by read_more query var)
|
14 |
+
var $excerpt; // String
|
15 |
+
var $date; // String (modified by date_format query var)
|
16 |
+
var $modified; // String (modified by date_format query var)
|
17 |
+
var $categories; // Array of objects
|
18 |
+
var $tags; // Array of objects
|
19 |
+
var $author; // Object
|
20 |
+
var $comments; // Array of objects
|
21 |
+
var $comment_count; // Integer
|
22 |
+
var $comment_status; // String ("open" or "closed")
|
23 |
+
var $custom_fields; // Object (included by using custom_fields query var)
|
24 |
+
|
25 |
+
function JSON_API_Post() {
|
26 |
+
global $json_api, $post;
|
27 |
+
$date_format = $json_api->query->date_format;
|
28 |
+
$this->id = (int) get_the_ID();
|
29 |
+
$this->set_value('slug', $post->post_name);
|
30 |
+
$this->set_value('url', get_permalink());
|
31 |
+
$this->set_value('title', get_the_title());
|
32 |
+
$this->set_value('title_plain', strip_tags(get_the_title()));
|
33 |
+
$this->set_content_value();
|
34 |
+
$this->set_value('excerpt', get_the_excerpt());
|
35 |
+
$this->set_value('date', get_the_time($date_format));
|
36 |
+
$this->set_value('modified', date($date_format, strtotime($post->post_modified)));
|
37 |
+
$this->set_categories_value();
|
38 |
+
$this->set_tags_value();
|
39 |
+
$this->set_author_value();
|
40 |
+
$this->set_comments_value();
|
41 |
+
$this->set_value('comment_count', (int) $post->comment_count);
|
42 |
+
$this->set_value('comment_status', $post->comment_status);
|
43 |
+
$this->set_custom_fields_value();
|
44 |
+
}
|
45 |
+
|
46 |
+
function set_value($key, $value) {
|
47 |
+
global $json_api;
|
48 |
+
if ($json_api->include_value($key)) {
|
49 |
+
$this->$key = $value;
|
50 |
+
} else {
|
51 |
+
unset($this->$key);
|
52 |
+
}
|
53 |
+
}
|
54 |
+
|
55 |
+
function set_content_value() {
|
56 |
+
global $json_api;
|
57 |
+
if ($json_api->include_value('content')) {
|
58 |
+
$content = get_the_content($json_api->query->read_more);
|
59 |
+
$content = apply_filters('the_content', $content);
|
60 |
+
$content = str_replace(']]>', ']]>', $content);
|
61 |
+
$this->content = $content;
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
function set_categories_value() {
|
66 |
+
global $json_api;
|
67 |
+
if ($json_api->include_value('categories')) {
|
68 |
+
$this->categories = array();
|
69 |
+
if ($wp_categories = get_the_category()) {
|
70 |
+
foreach ($wp_categories as $wp_category) {
|
71 |
+
$category = new JSON_API_Category($wp_category);
|
72 |
+
if ($category->id == 1 && $category->slug == 'uncategorized') {
|
73 |
+
// Skip the 'uncategorized' category
|
74 |
+
continue;
|
75 |
+
}
|
76 |
+
$this->categories[] = $category;
|
77 |
+
}
|
78 |
+
}
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
function set_tags_value() {
|
83 |
+
global $json_api;
|
84 |
+
if ($json_api->include_value('tags')) {
|
85 |
+
$this->tags = array();
|
86 |
+
if ($wp_tags = get_the_tags()) {
|
87 |
+
foreach ($wp_tags as $wp_tag) {
|
88 |
+
$this->tags[] = new JSON_API_Tag($wp_tag);
|
89 |
+
}
|
90 |
+
}
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
function set_author_value() {
|
95 |
+
global $json_api;
|
96 |
+
if ($json_api->include_value('author')) {
|
97 |
+
$this->author = new JSON_API_Author();
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
function set_comments_value() {
|
102 |
+
global $json_api;
|
103 |
+
if ($json_api->include_value('comments')) {
|
104 |
+
$this->comments = $json_api->introspector->get_comments($this->id);
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
function set_custom_fields_value() {
|
109 |
+
global $json_api;
|
110 |
+
if ($json_api->include_value('custom_fields') &&
|
111 |
+
$json_api->query->custom_fields) {
|
112 |
+
$keys = explode(',', $json_api->query->custom_fields);
|
113 |
+
$wp_custom_fields = get_post_custom($this->id);
|
114 |
+
$this->custom_fields = new stdClass();
|
115 |
+
foreach ($keys as $key) {
|
116 |
+
if (isset($wp_custom_fields[$key])) {
|
117 |
+
$this->custom_fields->$key = $wp_custom_fields[$key];
|
118 |
+
}
|
119 |
+
}
|
120 |
+
} else {
|
121 |
+
unset($this->custom_fields);
|
122 |
+
}
|
123 |
+
}
|
124 |
+
|
125 |
+
}
|
126 |
+
|
127 |
+
?>
|
models/tag.php
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Tag {
|
4 |
+
|
5 |
+
var $id; // Integer
|
6 |
+
var $slug; // String
|
7 |
+
var $title; // String
|
8 |
+
var $description; // String
|
9 |
+
|
10 |
+
function JSON_API_Tag($wp_tag = null) {
|
11 |
+
if ($wp_tag) {
|
12 |
+
$this->import_wp_object($wp_tag);
|
13 |
+
}
|
14 |
+
}
|
15 |
+
|
16 |
+
function import_wp_object($wp_tag) {
|
17 |
+
$this->id = (int) $wp_tag->term_id;
|
18 |
+
$this->slug = $wp_tag->slug;
|
19 |
+
$this->title = $wp_tag->name;
|
20 |
+
$this->description = $wp_tag->description;
|
21 |
+
}
|
22 |
+
|
23 |
+
}
|
24 |
+
|
25 |
+
?>
|
readme.txt
ADDED
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== JSON API ===
|
2 |
+
Contributors: dphiffer
|
3 |
+
Tags: json, api, ajax, cms, admin, integration, moma
|
4 |
+
Requires at least: 2.8
|
5 |
+
Tested up to: 2.8
|
6 |
+
Stable tag: 0.6
|
7 |
+
|
8 |
+
A RESTful API for WordPress
|
9 |
+
|
10 |
+
== Description ==
|
11 |
+
|
12 |
+
This plugin was created for The Museum of Modern Art, whose weblog [Inside/Out](http://moma.org/explore/inside_out) appears within an existing structure built with Ruby on Rails. Instead of reimplementing the site templates as a WordPress theme, we opted for a Rails front-end that displays content served from a WordPress back-end. JSON API provides the necessary interface for retrieving content and accepting comment submissions.
|
13 |
+
|
14 |
+
The current release (0.6) implements a mostly-complete set of introspection methods and a method for submitting comments. I plan on offering a complete set of authentication & data manipulation methods, but my current focus is on features we're actually using at MoMA.org.
|
15 |
+
|
16 |
+
See the Other Notes section for complete API documentation.
|
17 |
+
|
18 |
+
== Installation ==
|
19 |
+
|
20 |
+
1. Upload the `json-api` folder to the `/wp-content/plugins/` directory or install directly through the plugin installer.
|
21 |
+
1. Activate the plugin through the 'Plugins' menu in WordPress or by using the link provided by the plugin installer.
|
22 |
+
|
23 |
+
== Screenshots ==
|
24 |
+
|
25 |
+
1. Our old friend, in JSON format
|
26 |
+
|
27 |
+
== Requests ==
|
28 |
+
|
29 |
+
Requests use a simple REST-style HTTP GET or POST. To invoke the API, include a non-empty query value for `json` in the URL.
|
30 |
+
|
31 |
+
JSON API operates in two modes:
|
32 |
+
|
33 |
+
1. *Implicit mode* is triggered by setting the `json` query var to a non-empty value on any WordPress page. The content that would normally appear on that page is returned in JSON format.
|
34 |
+
1. *Explicit mode* is triggered by setting `json` to a known method string. See the *API Reference* section below for a complete method listing.
|
35 |
+
|
36 |
+
= Implicit mode examples: =
|
37 |
+
|
38 |
+
* `http://www.example.org/?json=1`
|
39 |
+
* `http://www.example.org/?p=47&json=1`
|
40 |
+
* `http://www.example.org/tag/banana/?json=1`
|
41 |
+
|
42 |
+
= Explicit mode examples: =
|
43 |
+
|
44 |
+
* `http://www.example.org/?json=get_recent_posts`
|
45 |
+
* `http://www.example.org/?json=get_post&post_id=47`
|
46 |
+
* `http://www.example.org/?json=get_tag_posts&tag_slug=banana`
|
47 |
+
|
48 |
+
= With user-friendly permalinks configured: =
|
49 |
+
|
50 |
+
* `http://www.example.org/api/get_recent_posts/`
|
51 |
+
* `http://www.example.org/api/get_post/?post_id=47`
|
52 |
+
* `http://www.example.org/api/get_tag_posts/?tag_slug=banana`
|
53 |
+
|
54 |
+
== Responses ==
|
55 |
+
|
56 |
+
The standard response format for JSON API is (as you may have guessed) [JSON](http://json.org/).
|
57 |
+
|
58 |
+
Here is an example response from `http://localhost/wordpress/?json=1` called on a default WordPress installation (formatted for readability):
|
59 |
+
|
60 |
+
{
|
61 |
+
"status": "ok",
|
62 |
+
"count": 1,
|
63 |
+
"count_total": 1,
|
64 |
+
"pages": 1,
|
65 |
+
"posts": [
|
66 |
+
{
|
67 |
+
"id": 1,
|
68 |
+
"slug": "hello-world",
|
69 |
+
"url": "http:\/\/localhost\/wordpress\/?p=1",
|
70 |
+
"title": "Hello world!",
|
71 |
+
"title_plain": "Hello world!",
|
72 |
+
"content": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!<\/p>\n",
|
73 |
+
"excerpt": "Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!\n",
|
74 |
+
"date": "2009-11-11 12:50:19",
|
75 |
+
"modified": "2009-11-11 12:50:19",
|
76 |
+
"categories": [],
|
77 |
+
"tags": [],
|
78 |
+
"author": {
|
79 |
+
"id": 1,
|
80 |
+
"slug": "admin",
|
81 |
+
"name": "admin",
|
82 |
+
"first_name": "",
|
83 |
+
"last_name": "",
|
84 |
+
"nickname": "",
|
85 |
+
"url": "",
|
86 |
+
"description": ""
|
87 |
+
},
|
88 |
+
"comments": [
|
89 |
+
{
|
90 |
+
"id": 1,
|
91 |
+
"name": "Mr WordPress",
|
92 |
+
"url": "http:\/\/wordpress.org\/",
|
93 |
+
"date": "2009-11-11 12:50:19",
|
94 |
+
"content": "<p>Hi, this is a comment.<br \/>To delete a comment, just log in and view the post's comments. There you will have the option to edit or delete them.<\/p>\n",
|
95 |
+
"parent": 0
|
96 |
+
}
|
97 |
+
],
|
98 |
+
"comment_count": 1,
|
99 |
+
"comment_status": "open"
|
100 |
+
}
|
101 |
+
]
|
102 |
+
}
|
103 |
+
|
104 |
+
== API Reference ==
|
105 |
+
|
106 |
+
The JSON API reference is split into four sections:
|
107 |
+
|
108 |
+
1. Request arguments
|
109 |
+
1. Response objects
|
110 |
+
1. Plugin hooks
|
111 |
+
1. Introspection methods
|
112 |
+
1. Data manipulation methods
|
113 |
+
|
114 |
+
__About API changes__
|
115 |
+
All methods are currently subject to change until the plugin reaches maturity. Please read the the changelog carefully before updating to subsequent releases.
|
116 |
+
|
117 |
+
== 1. Request arguments ==
|
118 |
+
|
119 |
+
The following arguments modify how you get results back from the API. The redirect response styles are intended for use with the data manipulation methods.
|
120 |
+
|
121 |
+
* Setting `callback` to a JavaScript function name will trigger a JSONP-style callback.
|
122 |
+
* Setting `redirect` to a URL will cause the user's browser to redirect to the specified URL with a `status` value appended to the query vars (see the *Response objects* section below for an explanation of status values).
|
123 |
+
* Setting `redirect_[status]` allows you to control the resulting browser redirection depending on the `status` value.
|
124 |
+
* Setting `dev` to a non-empty value formats a plain text response using PHP's `print_r()` function.
|
125 |
+
* Not setting any of the above argument values will result in a standard JSON response.
|
126 |
+
|
127 |
+
These arguments are available to modify all introspection methods:
|
128 |
+
|
129 |
+
* `date_format` - Changes the format of date values. Uses the same syntax as PHP's date() function. Default value is `Y-m-d H:i:s`.
|
130 |
+
* `read_more` - Changes the 'read more' link text in post content.
|
131 |
+
* `include` - Specifies which post data fields to include. Expects a comma-separated list of post fields. Leaving this empty includes *all* fields.
|
132 |
+
* `exclude` - Specifies which post data fields to exclude. Expects a comma-separated list of post fields.
|
133 |
+
* `custom_fields` - Includes values from posts' Custom Fields. Expects a comma-separated list of custom field keys.
|
134 |
+
* `author_meta` - Includes additional author metadata. Should be a comma-separated list of metadata fields.
|
135 |
+
|
136 |
+
__About `include`/`exclude` arguments__
|
137 |
+
By default you get all values included with each post object. Specify a list of `include` values will cause the post object to filter out the values absent from the list. Specifying `exclude` causes post objects to include all values except the fields you list. For example, the query `exclude=comments` includes everything *except* the comments.
|
138 |
+
|
139 |
+
== 2. Response objects ==
|
140 |
+
|
141 |
+
This section describes data objects you can retrieve from WordPress and the optional URL redirects.
|
142 |
+
|
143 |
+
__Status values__
|
144 |
+
All JSON API requests result in a status value. The two basic status values are `ok` and `error`. Additional status values are available for certain methods (such as `pending` in the case of the `submit_comment` method). API methods that result in custom status values include a *custom status values* section in their documentation.
|
145 |
+
|
146 |
+
__Naming compatibility__
|
147 |
+
Developers familiar with WordPress may notice that many names for properties and arguments have been changed. This was a stylistic choice that intends to provide more clarity and consistency in the interface.
|
148 |
+
|
149 |
+
== Post response object ==
|
150 |
+
|
151 |
+
* `id` - Integer
|
152 |
+
* `slug` - String
|
153 |
+
* `url` - String
|
154 |
+
* `title` - String
|
155 |
+
* `title_plain` - String
|
156 |
+
* `content` - String (modified by the `read_more` argument)
|
157 |
+
* `excerpt` - String
|
158 |
+
* `date` - String (modified by the `date_format` argument)
|
159 |
+
* `modified` - String (modified by the `date_format` argument)
|
160 |
+
* `categories` - Array of category objects
|
161 |
+
* `tags` - Array of tag objects
|
162 |
+
* `author` Author object
|
163 |
+
* `comments` - Array of comment objects
|
164 |
+
* `comment_count` - Integer
|
165 |
+
* `comment_status` - String (`"open"` or `"closed"`)
|
166 |
+
* `custom_fields` - Object (included by setting the `custom_fields` argument to a comma-separated list of custom field names)
|
167 |
+
|
168 |
+
== Category response object ==
|
169 |
+
|
170 |
+
* `id` - Integer
|
171 |
+
* `slug` - String
|
172 |
+
* `title` - String
|
173 |
+
* `description` - String
|
174 |
+
* `parent` - Integer
|
175 |
+
* `post_count` - Integer
|
176 |
+
|
177 |
+
== Tag response object ==
|
178 |
+
|
179 |
+
* `id` - Integer
|
180 |
+
* `slug` - String
|
181 |
+
* `title` - String
|
182 |
+
* `description` - String
|
183 |
+
|
184 |
+
== Author response object ==
|
185 |
+
|
186 |
+
* `id` - Integer
|
187 |
+
* `slug` - String
|
188 |
+
* `name` - String
|
189 |
+
* `first_name` - String
|
190 |
+
* `last_name` - String
|
191 |
+
* `nickname` - String
|
192 |
+
* `url` - String
|
193 |
+
* `description` - String
|
194 |
+
|
195 |
+
Note: You can include additional values by setting the `author_meta` argument to a comma-separated list of metadata fields.
|
196 |
+
|
197 |
+
== Comment response object ==
|
198 |
+
|
199 |
+
* `id` - Integer
|
200 |
+
* `name` - String
|
201 |
+
* `url` - String
|
202 |
+
* `date` - String
|
203 |
+
* `content` - String
|
204 |
+
* `parent` - Integer
|
205 |
+
* `author` - Object (only set if the comment author was registered & logged in)
|
206 |
+
|
207 |
+
== Redirects ==
|
208 |
+
|
209 |
+
The `redirect` response style is useful for when you need the user's browser to make a request directly rather than making proxy requests using a tool like cURL. Setting a `redirect` argument causes the user's browser to redirect back to the specified URL instead of returning a JSON object. The resulting `status` value is included as an extra query variable.
|
210 |
+
|
211 |
+
For example calling an API method with `redirect` set to `http://www.example.com/foo` will result in a redirection to one of the following:
|
212 |
+
|
213 |
+
* `http://www.example.com/foo?status=ok`
|
214 |
+
* `http://www.example.com/foo?status=error`
|
215 |
+
|
216 |
+
You can also set separate URLs to handle status values differently. You could set `redirect_ok` to `http://www.example.com/handle_ok` and `redirect_error` to `http://www.example.com/handle_error` in order to have more fine-tuned control over the method result.
|
217 |
+
|
218 |
+
== 3. Plugin hooks ==
|
219 |
+
|
220 |
+
JSON API currently exposes a single [filter hook](http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters) for you to modify the output.
|
221 |
+
|
222 |
+
== Filter: json_api_encode ==
|
223 |
+
|
224 |
+
This is called just before the output is encoded into JSON format. The value passed will always be an associative array, according to the format described in each method's documentation. Those items described in the *Response objects* section are passed as PHP objects, not associative arrays.
|
225 |
+
|
226 |
+
= Example =
|
227 |
+
|
228 |
+
add_filter('json_api_encode', 'encode_kittens_field');
|
229 |
+
|
230 |
+
encode_kittens_field($response) {
|
231 |
+
if (isset($response['posts'])) {
|
232 |
+
array_walk($response['posts'], 'add_kittens_field');
|
233 |
+
} else if (isset($response['post'])) {
|
234 |
+
add_kittens_field($response['post']);
|
235 |
+
}
|
236 |
+
return $response;
|
237 |
+
}
|
238 |
+
|
239 |
+
add_kittens_field(&$post) {
|
240 |
+
$post->kittens = 'Kittens!';
|
241 |
+
}
|
242 |
+
|
243 |
+
|
244 |
+
== 4. Introspection methods ==
|
245 |
+
|
246 |
+
Introspection methods are used to retrieve data from WordPress.
|
247 |
+
|
248 |
+
|
249 |
+
== Method: get_recent_posts ==
|
250 |
+
|
251 |
+
Returns an array of recent posts. You can invoke this from the WordPress home page either by setting `json` to a non-empty value (i.e., `json=1`) or from any page by setting `json=get_recent_posts`.
|
252 |
+
|
253 |
+
= Optional arguments =
|
254 |
+
|
255 |
+
* `page` - return a specific page number from the results
|
256 |
+
|
257 |
+
= Response =
|
258 |
+
|
259 |
+
{
|
260 |
+
"status": "ok",
|
261 |
+
"count": 10,
|
262 |
+
"count_total": 79,
|
263 |
+
"pages": 7,
|
264 |
+
"posts": [
|
265 |
+
{ ... },
|
266 |
+
{ ... },
|
267 |
+
...
|
268 |
+
]
|
269 |
+
}
|
270 |
+
|
271 |
+
|
272 |
+
== Method: get_post ==
|
273 |
+
|
274 |
+
Returns a single post object.
|
275 |
+
|
276 |
+
= One of the following is required =
|
277 |
+
|
278 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on a post URL
|
279 |
+
* `post_id` - set to the post's ID
|
280 |
+
* `post_slug` - set to the post's URL slug
|
281 |
+
|
282 |
+
= Response =
|
283 |
+
|
284 |
+
{
|
285 |
+
"status": "ok",
|
286 |
+
"post": { ... }
|
287 |
+
}
|
288 |
+
|
289 |
+
|
290 |
+
== Method: get_page ==
|
291 |
+
|
292 |
+
Returns a single page object.
|
293 |
+
|
294 |
+
= One of the following is required =
|
295 |
+
|
296 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on a page URL
|
297 |
+
* `page_id` - set to the page's ID
|
298 |
+
* `page_slug` - set to the page's URL slug
|
299 |
+
|
300 |
+
= Response =
|
301 |
+
|
302 |
+
{
|
303 |
+
"status": "ok",
|
304 |
+
"page": { ... }
|
305 |
+
}
|
306 |
+
|
307 |
+
== Method: get_date_posts ==
|
308 |
+
|
309 |
+
Returns an array of posts/pages in a specific category.
|
310 |
+
|
311 |
+
= One of the following is required =
|
312 |
+
|
313 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on a date archive page
|
314 |
+
* `date` - set to a date in the format `YYYY` or `YYYYMM` or `YYYYMMDD`
|
315 |
+
|
316 |
+
= Optional arguments =
|
317 |
+
|
318 |
+
* `page` - return a specific page number from the results
|
319 |
+
|
320 |
+
= Response =
|
321 |
+
|
322 |
+
{
|
323 |
+
"status": "ok",
|
324 |
+
"count": 10,
|
325 |
+
"count_total": 79,
|
326 |
+
"pages": 7,
|
327 |
+
"posts": [
|
328 |
+
{ ... },
|
329 |
+
{ ... },
|
330 |
+
...
|
331 |
+
]
|
332 |
+
}
|
333 |
+
|
334 |
+
== Method: get_category_posts ==
|
335 |
+
|
336 |
+
Returns an array of posts/pages in a specific category.
|
337 |
+
|
338 |
+
= One of the following is required =
|
339 |
+
|
340 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on a category archive page
|
341 |
+
* `category_id` - set to the category's ID
|
342 |
+
* `category_slug` - set to the category's URL slug
|
343 |
+
|
344 |
+
= Optional arguments =
|
345 |
+
|
346 |
+
* `page` - return a specific page number from the results
|
347 |
+
|
348 |
+
= Response =
|
349 |
+
|
350 |
+
{
|
351 |
+
"status": "ok",
|
352 |
+
"count": 10,
|
353 |
+
"count_total": 79,
|
354 |
+
"pages": 7,
|
355 |
+
"category": { ... }
|
356 |
+
"posts": [
|
357 |
+
{ ... },
|
358 |
+
{ ... },
|
359 |
+
...
|
360 |
+
]
|
361 |
+
}
|
362 |
+
|
363 |
+
|
364 |
+
== Method: get_tag_posts ==
|
365 |
+
|
366 |
+
Returns an array of posts/pages with a specific tag.
|
367 |
+
|
368 |
+
= One of the following is required =
|
369 |
+
|
370 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on a tag archive page
|
371 |
+
* `tag_id` - set to the tag's ID
|
372 |
+
* `tag_slug` - set to the tag's URL slug
|
373 |
+
|
374 |
+
= Optional arguments =
|
375 |
+
|
376 |
+
* `page` - return a specific page number from the results
|
377 |
+
|
378 |
+
= Response =
|
379 |
+
|
380 |
+
{
|
381 |
+
"status": "ok",
|
382 |
+
"count": 10,
|
383 |
+
"count_total": 79,
|
384 |
+
"pages": 7,
|
385 |
+
"tag": { ... }
|
386 |
+
"posts": [
|
387 |
+
{ ... },
|
388 |
+
{ ... },
|
389 |
+
...
|
390 |
+
]
|
391 |
+
}
|
392 |
+
|
393 |
+
|
394 |
+
== Method: get_author_posts ==
|
395 |
+
|
396 |
+
Returns an array of posts/pages written by a specific author.
|
397 |
+
|
398 |
+
= One of the following is required =
|
399 |
+
|
400 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on an author archive page
|
401 |
+
* `author_id` - set to the author's ID
|
402 |
+
* `author_slug` - set to the author's URL slug
|
403 |
+
|
404 |
+
= Optional arguments =
|
405 |
+
|
406 |
+
* `page` - return a specific page number from the results
|
407 |
+
|
408 |
+
= Response =
|
409 |
+
|
410 |
+
{
|
411 |
+
"status": "ok",
|
412 |
+
"count": 10,
|
413 |
+
"count_total": 79,
|
414 |
+
"pages": 7,
|
415 |
+
"author": { ... }
|
416 |
+
"posts": [
|
417 |
+
{ ... },
|
418 |
+
{ ... },
|
419 |
+
...
|
420 |
+
]
|
421 |
+
}
|
422 |
+
|
423 |
+
|
424 |
+
== Method: get_search_results ==
|
425 |
+
|
426 |
+
Returns an array of posts/pages in response to a search query.
|
427 |
+
|
428 |
+
= One of the following is required =
|
429 |
+
|
430 |
+
* Invoking the JSON API implicitly (i.e., `?json=1`) on a search results page
|
431 |
+
* `search` - set to the desired search query
|
432 |
+
|
433 |
+
= Optional arguments =
|
434 |
+
|
435 |
+
* `page` - return a specific page number from the results
|
436 |
+
|
437 |
+
= Response =
|
438 |
+
|
439 |
+
{
|
440 |
+
"status": "ok",
|
441 |
+
"count": 10,
|
442 |
+
"count_total": 79,
|
443 |
+
"pages": 7,
|
444 |
+
"posts": [
|
445 |
+
{ ... },
|
446 |
+
{ ... },
|
447 |
+
...
|
448 |
+
]
|
449 |
+
}
|
450 |
+
|
451 |
+
|
452 |
+
== Method: get_date_index ==
|
453 |
+
|
454 |
+
Returns both an array of date page permalinks and a tree structure representation of the archive.
|
455 |
+
|
456 |
+
= Response =
|
457 |
+
|
458 |
+
{
|
459 |
+
"status": "ok",
|
460 |
+
"permalinks": [
|
461 |
+
"...",
|
462 |
+
"...",
|
463 |
+
"..."
|
464 |
+
],
|
465 |
+
"tree": {
|
466 |
+
"2009": {
|
467 |
+
"09": 17,
|
468 |
+
"10": 20,
|
469 |
+
"11": 7
|
470 |
+
}
|
471 |
+
}
|
472 |
+
|
473 |
+
Note: the tree is arranged by `response.tree.[year].[month].[number of posts]`.
|
474 |
+
|
475 |
+
|
476 |
+
== Method: get_category_index ==
|
477 |
+
|
478 |
+
Returns an array of active categories.
|
479 |
+
|
480 |
+
= Response =
|
481 |
+
|
482 |
+
{
|
483 |
+
"status": "ok",
|
484 |
+
"count": 3,
|
485 |
+
"categories": [
|
486 |
+
{ ... },
|
487 |
+
{ ... },
|
488 |
+
{ ... }
|
489 |
+
]
|
490 |
+
}
|
491 |
+
|
492 |
+
|
493 |
+
== Method: get_tag_index ==
|
494 |
+
|
495 |
+
Returns an array of active tags.
|
496 |
+
|
497 |
+
= Response =
|
498 |
+
|
499 |
+
{
|
500 |
+
"status": "ok",
|
501 |
+
"count": 3
|
502 |
+
"tags": [
|
503 |
+
{ ... },
|
504 |
+
{ ... },
|
505 |
+
{ ... }
|
506 |
+
]
|
507 |
+
}
|
508 |
+
|
509 |
+
|
510 |
+
== Method: get_author_index ==
|
511 |
+
|
512 |
+
Returns an array of active blog authors.
|
513 |
+
|
514 |
+
= Response =
|
515 |
+
|
516 |
+
{
|
517 |
+
"status": "ok",
|
518 |
+
"count": 3,
|
519 |
+
"authors": [
|
520 |
+
{ ... },
|
521 |
+
{ ... },
|
522 |
+
{ ... }
|
523 |
+
]
|
524 |
+
}
|
525 |
+
|
526 |
+
|
527 |
+
== 5. Data manipulation methods ==
|
528 |
+
|
529 |
+
Data manipulation methods are used for saving content back to WordPress.
|
530 |
+
|
531 |
+
__Incomplete__
|
532 |
+
The data manipulation methods are still very incomplete.
|
533 |
+
|
534 |
+
|
535 |
+
== Method: submit_comment ==
|
536 |
+
|
537 |
+
Submits a comment to a WordPress post.
|
538 |
+
|
539 |
+
= Required arguments =
|
540 |
+
|
541 |
+
* `post_id` - which post to comment on
|
542 |
+
* `name` - the commenter's name
|
543 |
+
* `email` - the commenter's email address
|
544 |
+
* `content` - the comment content
|
545 |
+
|
546 |
+
= Optional arguments =
|
547 |
+
|
548 |
+
* `redirect` - redirect instead of returning a JSON object
|
549 |
+
* `redirect_ok` - redirect to a specific URL when the status value is `ok`
|
550 |
+
* `redirect_error` - redirect to a specific URL when the status value is `error`
|
551 |
+
* `redirect_pending` - redirect to a specific URL when the status value is `pending`
|
552 |
+
|
553 |
+
= Custom status values =
|
554 |
+
|
555 |
+
* `pending` - assigned if the comment submission is pending moderation
|
556 |
+
|
557 |
+
|
558 |
+
== Changelog ==
|
559 |
+
|
560 |
+
= 0.6 (2009-11-30): =
|
561 |
+
* Added `count_total` response
|
562 |
+
* Added `json_api_encode` filter
|
563 |
+
* Fixed bugs in the introspector's `get_current_category` and `get_current_tag`
|
564 |
+
|
565 |
+
= 0.5 (2009-11-17): =
|
566 |
+
* Initial Public Release
|
screenshot-1.png
ADDED
Binary file
|
singletons/controller.php
ADDED
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Controller {
|
4 |
+
|
5 |
+
function JSON_API_Controller() {
|
6 |
+
// The query object determines whether the current request is for the JSON API
|
7 |
+
$this->query = new JSON_API_Query();
|
8 |
+
|
9 |
+
// This action is called from wp-includes/template-loader.php
|
10 |
+
add_action('template_redirect', array(&$this, 'template_redirect'));
|
11 |
+
}
|
12 |
+
|
13 |
+
function template_redirect() {
|
14 |
+
// Check to see if there's an appropriate API method
|
15 |
+
$method = $this->query->get_method();
|
16 |
+
|
17 |
+
if ($method) {
|
18 |
+
// Looks like this is an API request
|
19 |
+
$this->setup();
|
20 |
+
$this->query->setup();
|
21 |
+
|
22 |
+
// Run the method
|
23 |
+
$result = $this->$method();
|
24 |
+
|
25 |
+
// Handle the result
|
26 |
+
$this->response->respond($result);
|
27 |
+
|
28 |
+
// Done!
|
29 |
+
exit;
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
function setup() {
|
34 |
+
global $json_api_dir;
|
35 |
+
|
36 |
+
// Setup additional singletons
|
37 |
+
require_once "$json_api_dir/singletons/response.php";
|
38 |
+
require_once "$json_api_dir/singletons/introspector.php";
|
39 |
+
$this->response = new JSON_API_Response();
|
40 |
+
$this->introspector = new JSON_API_Introspector();
|
41 |
+
|
42 |
+
// Models used by introspection methods
|
43 |
+
require_once "$json_api_dir/models/post.php";
|
44 |
+
require_once "$json_api_dir/models/comment.php";
|
45 |
+
require_once "$json_api_dir/models/category.php";
|
46 |
+
require_once "$json_api_dir/models/tag.php";
|
47 |
+
require_once "$json_api_dir/models/author.php";
|
48 |
+
}
|
49 |
+
|
50 |
+
function error($message, $status = 'error') {
|
51 |
+
$result = $this->response->get_json(array(
|
52 |
+
'error' => $message
|
53 |
+
), $status);
|
54 |
+
$this->response->respond($result);
|
55 |
+
}
|
56 |
+
|
57 |
+
function get_recent_posts() {
|
58 |
+
$posts = $this->introspector->get_posts('');
|
59 |
+
return $this->response->get_posts_json($posts);
|
60 |
+
}
|
61 |
+
|
62 |
+
function get_post() {
|
63 |
+
$query = '';
|
64 |
+
if ($this->query->post_id) {
|
65 |
+
$query = "p={$this->query->post_id}";
|
66 |
+
} else if ($this->query->post_slug) {
|
67 |
+
$query = "name={$this->query->post_slug}";
|
68 |
+
} else {
|
69 |
+
$this->error("No post specified. Include 'post_id' or 'post_slug' var in your request.");
|
70 |
+
}
|
71 |
+
$posts = $this->introspector->get_posts($query);
|
72 |
+
if (count($posts) == 1) {
|
73 |
+
return $this->response->get_json(array(
|
74 |
+
'post' => $posts[0]
|
75 |
+
));
|
76 |
+
} else {
|
77 |
+
$this->error("No post was found.");
|
78 |
+
}
|
79 |
+
}
|
80 |
+
|
81 |
+
function get_page() {
|
82 |
+
$query = '';
|
83 |
+
if ($this->query->page_id) {
|
84 |
+
$query = "page_id={$this->query->page_id}";
|
85 |
+
} else if ($this->query->page_slug) {
|
86 |
+
$query = "pagename={$this->query->page_slug}";
|
87 |
+
} else {
|
88 |
+
$this->error("No page specified. Include 'page_id' or 'page_slug' var in your request.");
|
89 |
+
}
|
90 |
+
$pages = $this->introspector->get_posts($query);
|
91 |
+
if (count($pages) == 1) {
|
92 |
+
return $this->response->get_json(array(
|
93 |
+
'page' => $pages[0]
|
94 |
+
));
|
95 |
+
} else {
|
96 |
+
$this->error("No page was found.");
|
97 |
+
}
|
98 |
+
}
|
99 |
+
|
100 |
+
function get_date_posts() {
|
101 |
+
$query = '';
|
102 |
+
if ($this->query->date) {
|
103 |
+
$query = "m={$this->query->date}";
|
104 |
+
} else {
|
105 |
+
$this->error("No date specified. Include 'date' var in your request.");
|
106 |
+
}
|
107 |
+
$posts = $this->introspector->get_posts($query);
|
108 |
+
return $this->response->get_posts_json($posts);
|
109 |
+
}
|
110 |
+
|
111 |
+
function get_category_posts() {
|
112 |
+
$query = '';
|
113 |
+
if ($this->query->catgegory_id) {
|
114 |
+
$query = "cat={$this->query->category_id}";
|
115 |
+
} else if ($this->query->category_slug) {
|
116 |
+
$query = "category_name={$this->query->category_slug}";
|
117 |
+
} else {
|
118 |
+
$this->error("No category specified. Include 'category_id' or 'category_slug' var in your request.");
|
119 |
+
}
|
120 |
+
$posts = $this->introspector->get_posts($query);
|
121 |
+
$category = $this->introspector->get_current_category();
|
122 |
+
return $this->response->get_posts_object_json($posts, $category);
|
123 |
+
}
|
124 |
+
|
125 |
+
function get_tag_posts() {
|
126 |
+
$query = '';
|
127 |
+
if ($this->query->tag_slug) {
|
128 |
+
$query = "tag={$this->query->tag_slug}";
|
129 |
+
} else if ($this->query->tag_id) {
|
130 |
+
$query = "tag_id={$this->query->tag_id}";
|
131 |
+
} else {
|
132 |
+
$this->error("No tag specified. Include 'tag_id' or 'tag_slug' var in your request.");
|
133 |
+
}
|
134 |
+
$posts = $this->introspector->get_posts($query);
|
135 |
+
$tag = $this->introspector->get_current_tag();
|
136 |
+
return $this->response->get_posts_object_json($posts, $tag);
|
137 |
+
}
|
138 |
+
|
139 |
+
function get_author_posts() {
|
140 |
+
$query = '';
|
141 |
+
if ($this->query->author_id) {
|
142 |
+
$query = "author={$this->query->author_id}";
|
143 |
+
} else if ($this->query->author_slug) {
|
144 |
+
$query = "author_name={$this->query->author_slug}";
|
145 |
+
} else {
|
146 |
+
$this->error("No author specified. Include 'author_id' or 'author_slug' var in your request.");
|
147 |
+
}
|
148 |
+
$posts = $this->introspector->get_posts($query);
|
149 |
+
$author = $this->introspector->get_current_author();
|
150 |
+
return $this->response->get_posts_object_json($posts, $author);
|
151 |
+
}
|
152 |
+
|
153 |
+
function get_search_results() {
|
154 |
+
$query = '';
|
155 |
+
if ($this->query->search) {
|
156 |
+
$query = "s={$this->query->search}";
|
157 |
+
} else {
|
158 |
+
$this->error("No search query specified. Include 'search' var in your request.");
|
159 |
+
}
|
160 |
+
$posts = $this->introspector->get_posts($query);
|
161 |
+
return $this->response->get_posts_json($posts);
|
162 |
+
}
|
163 |
+
|
164 |
+
function get_date_index() {
|
165 |
+
$permalinks = $this->introspector->get_date_archive_permalinks();
|
166 |
+
$tree = $this->introspector->get_date_archive_tree($permalinks);
|
167 |
+
return $this->response->get_json(array(
|
168 |
+
'permalinks' => $permalinks,
|
169 |
+
'tree' => $tree
|
170 |
+
));
|
171 |
+
}
|
172 |
+
|
173 |
+
function get_category_index() {
|
174 |
+
$categories = $this->introspector->get_categories();
|
175 |
+
return $this->response->get_json(array(
|
176 |
+
'count' => count($categories),
|
177 |
+
'categories' => $categories
|
178 |
+
));
|
179 |
+
}
|
180 |
+
|
181 |
+
function get_tag_index() {
|
182 |
+
$tags = $this->introspector->get_tags();
|
183 |
+
return $this->response->get_json(array(
|
184 |
+
'count' => count($tags),
|
185 |
+
'tags' => $tags
|
186 |
+
));
|
187 |
+
}
|
188 |
+
|
189 |
+
function get_author_index() {
|
190 |
+
$authors = $this->introspector->get_authors();
|
191 |
+
return $this->response->get_json(array(
|
192 |
+
'count' => count($authors),
|
193 |
+
'authors' => $authors
|
194 |
+
));
|
195 |
+
}
|
196 |
+
|
197 |
+
function submit_comment() {
|
198 |
+
nocache_headers();
|
199 |
+
$required = array(
|
200 |
+
'post_id',
|
201 |
+
'name',
|
202 |
+
'email',
|
203 |
+
'content'
|
204 |
+
);
|
205 |
+
if (empty($_REQUEST['post_id'])) {
|
206 |
+
$this->error("No post specified. Include 'post_id' var in your request.");
|
207 |
+
} else if (empty($_REQUEST['name']) ||
|
208 |
+
empty($_REQUEST['email']) ||
|
209 |
+
empty($_REQUEST['content'])) {
|
210 |
+
$this->error("Please include all required arguments (name, email, content).");
|
211 |
+
} else if (!is_email($_REQUEST['email'])) {
|
212 |
+
$this->error("Please enter a valid email address.");
|
213 |
+
}
|
214 |
+
$pending = new JSON_API_Comment();
|
215 |
+
return $pending->handle_submission();
|
216 |
+
}
|
217 |
+
|
218 |
+
function include_value($key) {
|
219 |
+
return $this->response->is_value_included($key);
|
220 |
+
}
|
221 |
+
|
222 |
+
}
|
223 |
+
|
224 |
+
?>
|
singletons/introspector.php
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Introspector {
|
4 |
+
|
5 |
+
function get_posts($query = '') {
|
6 |
+
// Returns an array of JSON_API_Post objects
|
7 |
+
$this->set_posts_query($query);
|
8 |
+
$output = array();
|
9 |
+
while (have_posts()) {
|
10 |
+
the_post();
|
11 |
+
$output[] = new JSON_API_Post();
|
12 |
+
}
|
13 |
+
return $output;
|
14 |
+
}
|
15 |
+
|
16 |
+
function get_date_archive_permalinks() {
|
17 |
+
$archives = wp_get_archives('echo=0');
|
18 |
+
preg_match_all("/href='([^']+)'/", $archives, $matches);
|
19 |
+
return $matches[1];
|
20 |
+
}
|
21 |
+
|
22 |
+
function get_date_archive_tree($permalinks) {
|
23 |
+
$tree = array();
|
24 |
+
foreach ($permalinks as $url) {
|
25 |
+
if (preg_match('#(\d{4})/(\d{2})#', $url, $date)) {
|
26 |
+
$year = $date[1];
|
27 |
+
$month = $date[2];
|
28 |
+
} else if (preg_match('/(\d{4})(\d{2})/', $url, $date)) {
|
29 |
+
$year = $date[1];
|
30 |
+
$month = $date[2];
|
31 |
+
} else {
|
32 |
+
continue;
|
33 |
+
}
|
34 |
+
$count = $this->get_date_archive_count($year, $month);
|
35 |
+
if (empty($tree[$year])) {
|
36 |
+
$tree[$year] = array(
|
37 |
+
$month => $count
|
38 |
+
);
|
39 |
+
} else {
|
40 |
+
$tree[$year][$month] = $count;
|
41 |
+
}
|
42 |
+
}
|
43 |
+
return $tree;
|
44 |
+
}
|
45 |
+
|
46 |
+
function get_date_archive_count($year, $month) {
|
47 |
+
if (!isset($this->month_archives)) {
|
48 |
+
global $wpdb;
|
49 |
+
$post_counts = $wpdb->get_results("
|
50 |
+
SELECT DATE_FORMAT(post_date, '%Y%m') AS month,
|
51 |
+
COUNT(ID) AS post_count
|
52 |
+
FROM $wpdb->posts
|
53 |
+
WHERE post_status = 'publish'
|
54 |
+
AND post_type = 'post'
|
55 |
+
GROUP BY month
|
56 |
+
");
|
57 |
+
$this->month_archives = array();
|
58 |
+
foreach ($post_counts as $post_count) {
|
59 |
+
$this->month_archives[$post_count->month] = $post_count->post_count;
|
60 |
+
}
|
61 |
+
}
|
62 |
+
return $this->month_archives["$year$month"];
|
63 |
+
}
|
64 |
+
|
65 |
+
function get_categories() {
|
66 |
+
$wp_categories = get_categories();
|
67 |
+
$categories = array();
|
68 |
+
foreach ($wp_categories as $wp_category) {
|
69 |
+
if ($wp_category->term_id == 1 && $wp_category->slug == 'uncategorized') {
|
70 |
+
continue;
|
71 |
+
}
|
72 |
+
$categories[] = $this->get_category_object($wp_category);
|
73 |
+
}
|
74 |
+
return $categories;
|
75 |
+
}
|
76 |
+
|
77 |
+
function get_current_category() {
|
78 |
+
global $json_api;
|
79 |
+
if (!empty($json_api->query->category_id)) {
|
80 |
+
return $this->get_category_by_id($json_api->query->category_id);
|
81 |
+
} else if (!empty($json_api->query->category_slug)) {
|
82 |
+
return $this->get_category_by_slug($json_api->query->category_slug);
|
83 |
+
}
|
84 |
+
return null;
|
85 |
+
}
|
86 |
+
|
87 |
+
function get_category_object($wp_category) {
|
88 |
+
return new JSON_API_Category($wp_category);
|
89 |
+
}
|
90 |
+
|
91 |
+
function get_category_by_id($category_id) {
|
92 |
+
$wp_category = get_term_by('id', $category_id, 'category');
|
93 |
+
return $this->get_category_object($wp_category);
|
94 |
+
}
|
95 |
+
|
96 |
+
function get_category_by_slug($category_slug) {
|
97 |
+
$wp_category = get_term_by('slug', $category_slug, 'category');
|
98 |
+
return $this->get_category_object($wp_category);
|
99 |
+
}
|
100 |
+
|
101 |
+
function get_tags() {
|
102 |
+
$wp_tags = get_tags();
|
103 |
+
return array_map(array(&$this, 'get_tag_object'), $wp_tags);
|
104 |
+
}
|
105 |
+
|
106 |
+
function get_current_tag() {
|
107 |
+
global $json_api;
|
108 |
+
if (!empty($json_api->query->tag_id)) {
|
109 |
+
return $this->get_tag_by_id($json_api->query->tag_id);
|
110 |
+
} else if (!empty($json_api->query->tag_slug)) {
|
111 |
+
return $this->get_tag_by_slug($json_api->query->tag_slug);
|
112 |
+
}
|
113 |
+
return null;
|
114 |
+
}
|
115 |
+
|
116 |
+
function get_tag_object($wp_tag) {
|
117 |
+
return new JSON_API_Tag($wp_tag);
|
118 |
+
}
|
119 |
+
|
120 |
+
function get_tag_by_id($tag_id) {
|
121 |
+
$wp_tag = get_term_by('id', $tag_id, 'post_tag');
|
122 |
+
return $this->get_tag_object($wp_tag);
|
123 |
+
}
|
124 |
+
|
125 |
+
function get_tag_by_slug($tag_slug) {
|
126 |
+
$wp_tag = get_term_by('slug', $tag_slug, 'post_tag');
|
127 |
+
return $this->get_tag_object($wp_tag);
|
128 |
+
}
|
129 |
+
|
130 |
+
function get_authors() {
|
131 |
+
global $wpdb;
|
132 |
+
$author_ids = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->users"));
|
133 |
+
$all_authors = array_map(array(&$this, 'get_author'), $author_ids);
|
134 |
+
$active_authors = array_filter($all_authors, array(&$this, 'is_active_author'));
|
135 |
+
return $active_authors;
|
136 |
+
}
|
137 |
+
|
138 |
+
function get_author($id) {
|
139 |
+
return new JSON_API_Author($id);
|
140 |
+
}
|
141 |
+
|
142 |
+
function get_current_author() {
|
143 |
+
$author_id = get_query_var('author');
|
144 |
+
return $this->get_author($author_id);
|
145 |
+
}
|
146 |
+
|
147 |
+
function is_active_author($author) {
|
148 |
+
if (!isset($this->active_authors)) {
|
149 |
+
$this->active_authors = explode(',', wp_list_authors('html=0&echo=0'));
|
150 |
+
}
|
151 |
+
return in_array($author->slug, $this->active_authors);
|
152 |
+
}
|
153 |
+
|
154 |
+
function set_posts_query($query = '') {
|
155 |
+
// Returns a query string to pass to WP's query_posts() function
|
156 |
+
if (get_query_var('page')) {
|
157 |
+
$amp = empty($query) ? '' : '&';
|
158 |
+
$query .= "{$amp}paged=" . get_query_var('page');
|
159 |
+
}
|
160 |
+
if (!empty($query)) {
|
161 |
+
query_posts($query);
|
162 |
+
}
|
163 |
+
}
|
164 |
+
|
165 |
+
function get_comments($post_id) {
|
166 |
+
global $wpdb;
|
167 |
+
$wp_comments = $wpdb->get_results($wpdb->prepare("
|
168 |
+
SELECT *
|
169 |
+
FROM $wpdb->comments
|
170 |
+
WHERE comment_post_ID = %d
|
171 |
+
AND comment_approved = 1
|
172 |
+
AND comment_type = ''
|
173 |
+
ORDER BY comment_date
|
174 |
+
", $post_id));
|
175 |
+
$comments = array();
|
176 |
+
foreach ($wp_comments as $wp_comment) {
|
177 |
+
$comments[] = new JSON_API_Comment($wp_comment);
|
178 |
+
}
|
179 |
+
return $comments;
|
180 |
+
}
|
181 |
+
|
182 |
+
}
|
183 |
+
|
184 |
+
?>
|
singletons/query.php
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Query {
|
4 |
+
|
5 |
+
var $vars = array(
|
6 |
+
'json', // Determines which API controller method will be called
|
7 |
+
// Expects one of the following values:
|
8 |
+
// * 'get_recent_posts'
|
9 |
+
// * 'get_post'
|
10 |
+
// * 'get_page'
|
11 |
+
// * 'get_date_posts'
|
12 |
+
// * 'get_category_posts'
|
13 |
+
// * 'get_tag_posts'
|
14 |
+
// * 'get_author_posts'
|
15 |
+
// * 'get_date_index'
|
16 |
+
// * 'get_category_index'
|
17 |
+
// * 'get_tag_index'
|
18 |
+
// * 'get_author_index'
|
19 |
+
// * 'get_search_results'
|
20 |
+
// * 'submit_comment'
|
21 |
+
// * Any non-empty value to trigger the API implicitly
|
22 |
+
'callback', // Triggers a JSONP-style callback with the resulting data
|
23 |
+
'dev', // Triggers a developer-friendly print_r() output
|
24 |
+
'redirect', // Triggers a redirect response to the specified URL
|
25 |
+
'page', // Returns a specific page of results
|
26 |
+
'post_id', // Used by the get_post API method
|
27 |
+
'post_slug', // Used by the get_post API method
|
28 |
+
'page_id', // Used by the get_page API method
|
29 |
+
'page_slug', // Used by the get_page API method
|
30 |
+
'search', // Used by the get_search_results API method
|
31 |
+
'category_id', // Used by get_category_posts API method
|
32 |
+
'category_slug', // Used by get_category_posts API method
|
33 |
+
'tag_id', // Used by get_tag_posts API method
|
34 |
+
'tag_slug', // Used by get_tag_posts API method
|
35 |
+
'author_id', // Used by get_author_posts API method
|
36 |
+
'author_slug', // Used by get_author_posts API method
|
37 |
+
'date', // Used by get_date_posts API method
|
38 |
+
// Expects 'YYYY', 'YYYYMM' or 'YYYYMMDD'
|
39 |
+
'date_format', // Changes the format of date values
|
40 |
+
// Uses the same syntax as PHP's date() function
|
41 |
+
// Default value is Y-m-d H:i:s
|
42 |
+
'read_more', // Changes the 'read more' link text in post content
|
43 |
+
'include', // Specifies which post data fields to include
|
44 |
+
// Expects a comma-separated list of post fields
|
45 |
+
// Leaving this empty includes *all* fields
|
46 |
+
'exclude', // Specifies which post data fields to exclude
|
47 |
+
// Expects a comma-separated list of post fields
|
48 |
+
'custom_fields', // Includes values from posts' Custom Fields
|
49 |
+
// Expects a comma-separated list of custom field keys
|
50 |
+
'author_meta' // Includes additional author metadata
|
51 |
+
// Should be a comma-separated list of metadata fields
|
52 |
+
|
53 |
+
// Note about include/exclude vars:
|
54 |
+
// The default behavior includes all post values. You only need to
|
55 |
+
// specify one of include or exclude -- the former will implicitly leave
|
56 |
+
// out those fields you haven't specified and the latter will implicitly
|
57 |
+
// include them. For example, a query that uses exclude=comments will
|
58 |
+
// include everything *except* the comments, so there's no need to also
|
59 |
+
// specify anything with the include argument.
|
60 |
+
|
61 |
+
);
|
62 |
+
|
63 |
+
// Default values
|
64 |
+
var $date_format = 'Y-m-d H:i:s';
|
65 |
+
var $read_more = 'Read more';
|
66 |
+
|
67 |
+
function JSON_API_Query() {
|
68 |
+
// Register JSON API query vars
|
69 |
+
add_filter('query_vars', array(&$this, 'query_vars'));
|
70 |
+
}
|
71 |
+
|
72 |
+
function query_vars($wp_vars) {
|
73 |
+
// This makes our variables available from the get_query_var WP function
|
74 |
+
return array_merge($wp_vars, $this->vars);
|
75 |
+
}
|
76 |
+
|
77 |
+
function setup() {
|
78 |
+
// Translation between WordPress vars and natively understood vars
|
79 |
+
$wp_translation = array(
|
80 |
+
'p' => 'post_id',
|
81 |
+
'name' => 'post_slug',
|
82 |
+
'page_id' => 'page_id',
|
83 |
+
'pagename' => 'page_slug',
|
84 |
+
'cat' => 'category_id',
|
85 |
+
'category_name' => 'category_slug',
|
86 |
+
'tag' => 'tag_slug',
|
87 |
+
'author' => 'author_id',
|
88 |
+
'author_name' => 'author_slug',
|
89 |
+
'm' => 'date',
|
90 |
+
's' => 'search'
|
91 |
+
);
|
92 |
+
|
93 |
+
foreach ($wp_translation as $wp_var => $var) {
|
94 |
+
// Assign WP query vars to natively understood vars
|
95 |
+
$this->$var = get_query_var($wp_var);
|
96 |
+
}
|
97 |
+
|
98 |
+
foreach ($this->vars as $var) {
|
99 |
+
// Assign query vars as object properties for convenience
|
100 |
+
$value = get_query_var($var);
|
101 |
+
if ($value) {
|
102 |
+
$this->$var = $value;
|
103 |
+
} else if (!isset($this->$var)) {
|
104 |
+
$this->$var = null;
|
105 |
+
}
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
function get_method() {
|
110 |
+
// Returns an appropriate API method name or false. Four possible outcomes:
|
111 |
+
// 1. API isn't being invoked at all (return false)
|
112 |
+
// 2. A specific API method was requested (return method name)
|
113 |
+
// 3. A method is chosen implicitly on a given WordPress page
|
114 |
+
// 4. API invoked incorrectly (return "error" method)
|
115 |
+
//
|
116 |
+
// Note:
|
117 |
+
// The implicit outcome (3) is invoked by setting the json query var to a
|
118 |
+
// non-empty value on any WordPress page:
|
119 |
+
// * http://example.org/2009/11/10/hello-world/?json=1
|
120 |
+
// * http://example.org/2009/11/?json=1
|
121 |
+
// * http://example.org/category/foo?json=1
|
122 |
+
|
123 |
+
$this->method = get_query_var('json');
|
124 |
+
|
125 |
+
if (empty($this->method)) {
|
126 |
+
// Case 1: we're not being invoked (done!)
|
127 |
+
return false;
|
128 |
+
} else if (method_exists('JSON_API_Controller', $this->method)) {
|
129 |
+
// Case 2: an explicit method was specified
|
130 |
+
return $this->method;
|
131 |
+
|
132 |
+
// Case 3: choose the method implicitly based on which page we're on...
|
133 |
+
|
134 |
+
} else if (is_search()) {
|
135 |
+
return 'get_search_results';
|
136 |
+
} else if (is_home()) {
|
137 |
+
return 'get_recent_posts';
|
138 |
+
} else if (is_page()) {
|
139 |
+
return 'get_page';
|
140 |
+
} else if (is_single()) {
|
141 |
+
return 'get_post';
|
142 |
+
} else if (is_category()) {
|
143 |
+
return 'get_category_posts';
|
144 |
+
} else if (is_tag()) {
|
145 |
+
return 'get_tag_posts';
|
146 |
+
} else if (is_author()) {
|
147 |
+
return 'get_author_posts';
|
148 |
+
} else if (is_date()) {
|
149 |
+
return 'get_date_posts';
|
150 |
+
} else {
|
151 |
+
// Case 4: either the method doesn't exist or we don't support this page
|
152 |
+
return 'error';
|
153 |
+
}
|
154 |
+
}
|
155 |
+
|
156 |
+
}
|
singletons/response.php
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class JSON_API_Response {
|
4 |
+
|
5 |
+
function JSON_API_Response() {
|
6 |
+
$this->setup_include_values();
|
7 |
+
}
|
8 |
+
|
9 |
+
function setup_include_values() {
|
10 |
+
$this->include_values = array();
|
11 |
+
if (get_query_var('include')) {
|
12 |
+
$this->include_values = explode(',', get_query_var('include'));
|
13 |
+
}
|
14 |
+
if (get_query_var('exclude')) {
|
15 |
+
$exclude = explode(',', get_query_var('exclude'));
|
16 |
+
$this->include_values = array_diff($this->include_values, $exclude);
|
17 |
+
}
|
18 |
+
}
|
19 |
+
|
20 |
+
function get_posts_json($posts, $status = 'ok') {
|
21 |
+
global $wp_query;
|
22 |
+
return $this->get_json(array(
|
23 |
+
'count' => count($posts),
|
24 |
+
'count_total' => $wp_query->found_posts,
|
25 |
+
'pages' => $wp_query->max_num_pages,
|
26 |
+
'posts' => $posts
|
27 |
+
), $status);
|
28 |
+
}
|
29 |
+
|
30 |
+
function get_posts_object_json($posts, $object, $status = 'ok') {
|
31 |
+
global $wp_query;
|
32 |
+
$object_key = strtolower(substr(get_class($object), 9));
|
33 |
+
return $this->get_json(array(
|
34 |
+
'count' => count($posts),
|
35 |
+
'pages' => $wp_query->max_num_pages,
|
36 |
+
$object_key => $object,
|
37 |
+
'posts' => $posts
|
38 |
+
), $status);
|
39 |
+
}
|
40 |
+
|
41 |
+
function get_json($data, $status = 'ok') {
|
42 |
+
// Include a status value with the response
|
43 |
+
if (is_array($data)) {
|
44 |
+
$data = array_merge(array('status' => $status), $data);
|
45 |
+
} else if (is_object($data)) {
|
46 |
+
$data = get_object_vars($data);
|
47 |
+
$data = array_merge(array('status' => $status), $data);
|
48 |
+
}
|
49 |
+
|
50 |
+
$data = apply_filters('json_api_encode', $data);
|
51 |
+
|
52 |
+
if (!empty($_REQUEST['dev'])) {
|
53 |
+
// Don't JSON encode the data in dev mode
|
54 |
+
return $data;
|
55 |
+
} else if (function_exists('json_encode')) {
|
56 |
+
// Use the built-in json_encode function if it's available
|
57 |
+
return json_encode($data);
|
58 |
+
} else {
|
59 |
+
// Use PEAR's Services_JSON encoder otherwise
|
60 |
+
if (!class_exists('Services_JSON')) {
|
61 |
+
global $json_api_dir;
|
62 |
+
require_once "$json_api_dir/library/JSON.php";
|
63 |
+
}
|
64 |
+
$json = new Services_JSON();
|
65 |
+
return $json->encode($data);
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
function is_value_included($key) {
|
70 |
+
if (empty($this->include_values)) {
|
71 |
+
return true;
|
72 |
+
} else {
|
73 |
+
return in_array($key, $this->include_values);
|
74 |
+
}
|
75 |
+
}
|
76 |
+
|
77 |
+
function respond($result, $status = 'ok') {
|
78 |
+
global $json_api;
|
79 |
+
$status_redirect = "redirect_$status";
|
80 |
+
if ($json_api->query->dev) {
|
81 |
+
// Output the result with print_r
|
82 |
+
if (!headers_sent()) {
|
83 |
+
header('Content-Type: text/plain; charset: UTF-8', true);
|
84 |
+
}
|
85 |
+
print_r($result);
|
86 |
+
} else if (!empty($_REQUEST[$status_redirect])) {
|
87 |
+
wp_redirect($_REQUEST[$status_redirect]);
|
88 |
+
} else if ($json_api->query->redirect) {
|
89 |
+
$url = $this->add_status_query_var($json_api->query->redirect, $status);
|
90 |
+
wp_redirect($url);
|
91 |
+
} else if ($json_api->query->callback) {
|
92 |
+
// Run a JSONP-style callback with the result
|
93 |
+
$this->callback($this->query->callback, $result);
|
94 |
+
} else {
|
95 |
+
// Output the result in JSON format
|
96 |
+
$this->output($result);
|
97 |
+
}
|
98 |
+
exit;
|
99 |
+
}
|
100 |
+
|
101 |
+
function output($result) {
|
102 |
+
$charset = get_option('blog_charset');
|
103 |
+
if (!headers_sent()) {
|
104 |
+
header("Content-Type: application/json; charset=$charset", true);
|
105 |
+
header("Content-Disposition: attachment; filename=\"json_api.json\"", true);
|
106 |
+
}
|
107 |
+
echo $result;
|
108 |
+
}
|
109 |
+
|
110 |
+
function callback($callback, $result) {
|
111 |
+
$charset = get_option('blog_charset');
|
112 |
+
if (!headers_sent()) {
|
113 |
+
header("Content-Type: application/javascript; charset=$charset", true);
|
114 |
+
}
|
115 |
+
echo "$callback($result)";
|
116 |
+
}
|
117 |
+
|
118 |
+
function add_status_query_var($url, $status) {
|
119 |
+
if (strpos($url, '#')) {
|
120 |
+
// Remove the anchor hash for now
|
121 |
+
$pos = strpos($url, '#');
|
122 |
+
$anchor = substr($url, $pos);
|
123 |
+
$url = substr($url, 0, $pos);
|
124 |
+
}
|
125 |
+
if (strpos($url, '?')) {
|
126 |
+
$url .= "&status=$status";
|
127 |
+
} else {
|
128 |
+
$url .= "?status=$status";
|
129 |
+
}
|
130 |
+
if (!empty($anchor)) {
|
131 |
+
// Add the anchor hash back in
|
132 |
+
$url .= $anchor;
|
133 |
+
}
|
134 |
+
return $url;
|
135 |
+
}
|
136 |
+
|
137 |
+
}
|
138 |
+
|
139 |
+
?>
|