JSON API - Version 0.6

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 Icon wp plugin JSON API
Version 0.6
Comparing to
See all releases

Version 0.6

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(']]>', ']]&gt;', $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&#039;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
+ ?>