JSON API - Version 1.0.9

Version Description

Update/delete post methods and some other bugfixes and improvements

Download this release

Release Info

Developer dphiffer
Plugin Icon wp plugin JSON API
Version 1.0.9
Comparing to
See all releases

Code changes from version 1.0.8 to 1.0.9

controllers/core.php CHANGED
@@ -60,31 +60,12 @@ class JSON_API_Core_Controller {
60
 
61
  public function get_post() {
62
  global $json_api, $post;
63
- extract($json_api->query->get(array('id', 'slug', 'post_id', 'post_slug')));
64
- if ($id || $post_id) {
65
- if (!$id) {
66
- $id = $post_id;
67
- }
68
- $posts = $json_api->introspector->get_posts(array(
69
- 'p' => $id
70
- ), true);
71
- } else if ($slug || $post_slug) {
72
- if (!$slug) {
73
- $slug = $post_slug;
74
- }
75
- $posts = $json_api->introspector->get_posts(array(
76
- 'name' => $slug
77
- ), true);
78
- } else {
79
- $json_api->error("Include 'id' or 'slug' var in your request.");
80
- }
81
- if (count($posts) == 1) {
82
- $post = $posts[0];
83
  $previous = get_adjacent_post(false, '', true);
84
  $next = get_adjacent_post(false, '', false);
85
- $post = new JSON_API_Post($post);
86
  $response = array(
87
- 'post' => $post
88
  );
89
  if ($previous) {
90
  $response['previous_url'] = get_permalink($previous->ID);
@@ -227,7 +208,13 @@ class JSON_API_Core_Controller {
227
 
228
  public function get_category_index() {
229
  global $json_api;
230
- $categories = $json_api->introspector->get_categories();
 
 
 
 
 
 
231
  return array(
232
  'count' => count($categories),
233
  'categories' => $categories
60
 
61
  public function get_post() {
62
  global $json_api, $post;
63
+ $post = $json_api->introspector->get_current_post();
64
+ if ($post) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  $previous = get_adjacent_post(false, '', true);
66
  $next = get_adjacent_post(false, '', false);
 
67
  $response = array(
68
+ 'post' => new JSON_API_Post($post)
69
  );
70
  if ($previous) {
71
  $response['previous_url'] = get_permalink($previous->ID);
208
 
209
  public function get_category_index() {
210
  global $json_api;
211
+ $args = null;
212
+ if (!empty($json_api->query->parent)) {
213
+ $args = array(
214
+ 'parent' => $json_api->query->parent
215
+ );
216
+ }
217
+ $categories = $json_api->introspector->get_categories($args);
218
  return array(
219
  'count' => count($categories),
220
  'categories' => $categories
controllers/posts.php CHANGED
@@ -9,7 +9,7 @@ class JSON_API_Posts_Controller {
9
  public function create_post() {
10
  global $json_api;
11
  if (!current_user_can('edit_posts')) {
12
- $json_api->error("You need to login with a user capable of creating posts.");
13
  }
14
  if (!$json_api->query->nonce) {
15
  $json_api->error("You must include a 'nonce' value to create posts. Use the `get_nonce` Core API method.");
@@ -29,6 +29,57 @@ class JSON_API_Posts_Controller {
29
  );
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  ?>
9
  public function create_post() {
10
  global $json_api;
11
  if (!current_user_can('edit_posts')) {
12
+ $json_api->error("You need to login with a user that has 'edit_posts' capacity.");
13
  }
14
  if (!$json_api->query->nonce) {
15
  $json_api->error("You must include a 'nonce' value to create posts. Use the `get_nonce` Core API method.");
29
  );
30
  }
31
 
32
+ public function update_post() {
33
+ global $json_api;
34
+ $post = $json_api->introspector->get_current_post();
35
+ if (empty($post)) {
36
+ $json_api->error("Post not found.");
37
+ }
38
+ if (!current_user_can('edit_post', $post->ID)) {
39
+ $json_api->error("You need to login with a user that has the 'edit_post' capacity for that post.");
40
+ }
41
+ if (!$json_api->query->nonce) {
42
+ $json_api->error("You must include a 'nonce' value to update posts. Use the `get_nonce` Core API method.");
43
+ }
44
+ $nonce_id = $json_api->get_nonce_id('posts', 'update_post');
45
+ if (!wp_verify_nonce($json_api->query->nonce, $nonce_id)) {
46
+ $json_api->error("Your 'nonce' value was incorrect. Use the 'get_nonce' API method.");
47
+ }
48
+ nocache_headers();
49
+ $post = new JSON_API_Post($post);
50
+ $post->update($_REQUEST);
51
+ return array(
52
+ 'post' => $post
53
+ );
54
+ }
55
+
56
+ public function delete_post() {
57
+ global $json_api;
58
+ $post = $json_api->introspector->get_current_post();
59
+ if (empty($post)) {
60
+ $json_api->error("Post not found.");
61
+ }
62
+ if (!current_user_can('edit_post', $post->ID)) {
63
+ $json_api->error("You need to login with a user that has the 'edit_post' capacity for that post.");
64
+ }
65
+ if (!current_user_can('delete_posts')) {
66
+ $json_api->error("You need to login with a user that has the 'delete_posts' capacity.");
67
+ }
68
+ if ($post->post_author != get_current_user_id() && !current_user_can('delete_other_posts')) {
69
+ $json_api->error("You need to login with a user that has the 'delete_other_posts' capacity.");
70
+ }
71
+ if (!$json_api->query->nonce) {
72
+ $json_api->error("You must include a 'nonce' value to update posts. Use the `get_nonce` Core API method.");
73
+ }
74
+ $nonce_id = $json_api->get_nonce_id('posts', 'delete_post');
75
+ if (!wp_verify_nonce($json_api->query->nonce, $nonce_id)) {
76
+ $json_api->error("Your 'nonce' value was incorrect. Use the 'get_nonce' API method.");
77
+ }
78
+ nocache_headers();
79
+ wp_delete_post($post->ID);
80
+ return array();
81
+ }
82
+
83
  }
84
 
85
  ?>
json-api.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: JSON API
4
  Plugin URI: http://wordpress.org/plugins/json-api/
5
  Description: A RESTful API for WordPress
6
- Version: 1.0.8
7
  Author: Dan Phiffer
8
  Author URI: http://phiffer.org/
9
  */
3
  Plugin Name: JSON API
4
  Plugin URI: http://wordpress.org/plugins/json-api/
5
  Description: A RESTful API for WordPress
6
+ Version: 1.0.9
7
  Author: Dan Phiffer
8
  Author URI: http://phiffer.org/
9
  */
library/JSON.php CHANGED
@@ -50,7 +50,7 @@
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
  */
@@ -90,6 +90,11 @@ define('SERVICES_JSON_LOOSE_TYPE', 16);
90
  */
91
  define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
92
 
 
 
 
 
 
93
  /**
94
  * Converts to and from JSON format.
95
  *
@@ -128,12 +133,24 @@ class Services_JSON
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
  *
@@ -148,7 +165,7 @@ class Services_JSON
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
 
@@ -192,11 +209,11 @@ class Services_JSON
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
@@ -236,10 +253,10 @@ class Services_JSON
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.
@@ -251,7 +268,13 @@ class Services_JSON
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
@@ -279,12 +302,12 @@ class Services_JSON
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,
@@ -457,8 +480,27 @@ class Services_JSON
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));
@@ -568,14 +610,14 @@ class Services_JSON
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) {
@@ -610,10 +652,10 @@ class Services_JSON
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;
@@ -625,35 +667,35 @@ class Services_JSON
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
 
@@ -683,7 +725,7 @@ class Services_JSON
683
  'where' => 0,
684
  'delim' => false));
685
 
686
- $chrs = substr($str, 1, -1);
687
  $chrs = $this->reduce_string($chrs);
688
 
689
  if ($chrs == '') {
@@ -698,19 +740,19 @@ class Services_JSON
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
@@ -723,20 +765,19 @@ class Services_JSON
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;
@@ -754,12 +795,12 @@ class Services_JSON
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))) {
@@ -770,7 +811,7 @@ class Services_JSON
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))) {
@@ -781,7 +822,7 @@ class Services_JSON
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))) {
@@ -798,7 +839,7 @@ class Services_JSON
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
 
@@ -830,6 +871,38 @@ class Services_JSON
830
 
831
  return false;
832
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  }
834
 
835
  if (class_exists('PEAR_Error')) {
@@ -856,6 +929,5 @@ if (class_exists('PEAR_Error')) {
856
 
857
  }
858
  }
859
-
860
  }
861
-
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 305040 2010-11-02 23:19:03Z 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
  */
90
  */
91
  define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
92
 
93
+ /**
94
+ * Behavior switch for Services_JSON::decode()
95
+ */
96
+ define('SERVICES_JSON_USE_TO_JSON', 64);
97
+
98
  /**
99
  * Converts to and from JSON format.
100
  *
133
  * By default, a deeply-nested resource will
134
  * bubble up with an error, so all return values
135
  * from encode() should be checked with isError()
136
+ * - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects
137
+ * It serializes the return value from the toJSON call rather
138
+ * than the object it'self, toJSON can return associative arrays,
139
+ * strings or numbers, if you return an object, make sure it does
140
+ * not have a toJSON method, otherwise an error will occur.
141
  */
142
  function Services_JSON($use = 0)
143
  {
144
  $this->use = $use;
145
+ $this->_mb_strlen = function_exists('mb_strlen');
146
+ $this->_mb_convert_encoding = function_exists('mb_convert_encoding');
147
+ $this->_mb_substr = function_exists('mb_substr');
148
  }
149
+ // private - cache the mbstring lookup results..
150
+ var $_mb_strlen = false;
151
+ var $_mb_substr = false;
152
+ var $_mb_convert_encoding = false;
153
+
154
  /**
155
  * convert a string from one UTF-16 char to one UTF-8 char
156
  *
165
  function utf162utf8($utf16)
166
  {
167
  // oh please oh please oh please oh please oh please
168
+ if($this->_mb_convert_encoding) {
169
  return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
170
  }
171
 
209
  function utf82utf16($utf8)
210
  {
211
  // oh please oh please oh please oh please oh please
212
+ if($this->_mb_convert_encoding) {
213
  return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
214
  }
215
 
216
+ switch($this->strlen8($utf8)) {
217
  case 1:
218
  // this case should never be reached, because we are in ASCII range
219
  // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
253
  function encode($var)
254
  {
255
  header('Content-type: application/json');
256
+ return $this->encodeUnsafe($var);
257
  }
258
  /**
259
+ * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
260
  *
261
  * @param mixed $var any number, boolean, string, array, or object to be encoded.
262
  * see argument 1 to Services_JSON() above for array-parsing behavior.
268
  */
269
  function encodeUnsafe($var)
270
  {
271
+ // see bug #16908 - regarding numeric locale printing
272
+ $lc = setlocale(LC_NUMERIC, 0);
273
+ setlocale(LC_NUMERIC, 'C');
274
+ $ret = $this->_encode($var);
275
+ setlocale(LC_NUMERIC, $lc);
276
+ return $ret;
277
+
278
  }
279
  /**
280
  * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
302
 
303
  case 'double':
304
  case 'float':
305
+ return (float) $var;
306
 
307
  case 'string':
308
  // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
309
  $ascii = '';
310
+ $strlen_var = $this->strlen8($var);
311
 
312
  /*
313
  * Iterate over every character in the string,
480
  return '[' . join(',', $elements) . ']';
481
 
482
  case 'object':
483
+
484
+ // support toJSON methods.
485
+ if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
486
+ // this may end up allowing unlimited recursion
487
+ // so we check the return value to make sure it's not got the same method.
488
+ $recode = $var->toJSON();
489
+
490
+ if (method_exists($recode, 'toJSON')) {
491
+
492
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
493
+ ? 'null'
494
+ : new Services_JSON_Error(class_name($var).
495
+ " toJSON returned an object with a toJSON method.");
496
+
497
+ }
498
+
499
+ return $this->_encode( $recode );
500
+ }
501
+
502
  $vars = get_object_vars($var);
503
+
504
  $properties = array_map(array($this, 'name_value'),
505
  array_keys($vars),
506
  array_values($vars));
610
 
611
  } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
612
  // STRINGS RETURNED IN UTF-8 FORMAT
613
+ $delim = $this->substr8($str, 0, 1);
614
+ $chrs = $this->substr8($str, 1, -1);
615
  $utf8 = '';
616
+ $strlen_chrs = $this->strlen8($chrs);
617
 
618
  for ($c = 0; $c < $strlen_chrs; ++$c) {
619
 
620
+ $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
621
  $ord_chrs_c = ord($chrs{$c});
622
 
623
  switch (true) {
652
  }
653
  break;
654
 
655
+ case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)):
656
  // single, escaped unicode character
657
+ $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2)))
658
+ . chr(hexdec($this->substr8($chrs, ($c + 4), 2)));
659
  $utf8 .= $this->utf162utf8($utf16);
660
  $c += 5;
661
  break;
667
  case ($ord_chrs_c & 0xE0) == 0xC0:
668
  // characters U-00000080 - U-000007FF, mask 110XXXXX
669
  //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
670
+ $utf8 .= $this->substr8($chrs, $c, 2);
671
  ++$c;
672
  break;
673
 
674
  case ($ord_chrs_c & 0xF0) == 0xE0:
675
  // characters U-00000800 - U-0000FFFF, mask 1110XXXX
676
  // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
677
+ $utf8 .= $this->substr8($chrs, $c, 3);
678
  $c += 2;
679
  break;
680
 
681
  case ($ord_chrs_c & 0xF8) == 0xF0:
682
  // characters U-00010000 - U-001FFFFF, mask 11110XXX
683
  // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
684
+ $utf8 .= $this->substr8($chrs, $c, 4);
685
  $c += 3;
686
  break;
687
 
688
  case ($ord_chrs_c & 0xFC) == 0xF8:
689
  // characters U-00200000 - U-03FFFFFF, mask 111110XX
690
  // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
691
+ $utf8 .= $this->substr8($chrs, $c, 5);
692
  $c += 4;
693
  break;
694
 
695
  case ($ord_chrs_c & 0xFE) == 0xFC:
696
  // characters U-04000000 - U-7FFFFFFF, mask 1111110X
697
  // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
698
+ $utf8 .= $this->substr8($chrs, $c, 6);
699
  $c += 5;
700
  break;
701
 
725
  'where' => 0,
726
  'delim' => false));
727
 
728
+ $chrs = $this->substr8($str, 1, -1);
729
  $chrs = $this->reduce_string($chrs);
730
 
731
  if ($chrs == '') {
740
 
741
  //print("\nparsing {$chrs}\n");
742
 
743
+ $strlen_chrs = $this->strlen8($chrs);
744
 
745
  for ($c = 0; $c <= $strlen_chrs; ++$c) {
746
 
747
  $top = end($stk);
748
+ $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
749
 
750
  if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
751
  // found a comma that is not inside a string, array, etc.,
752
  // OR we've reached the end of the character list
753
+ $slice = $this->substr8($chrs, $top['where'], ($c - $top['where']));
754
  array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
755
+ //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
756
 
757
  if (reset($stk) == SERVICES_JSON_IN_ARR) {
758
  // we are in an array, so just push an element onto the stack
765
  // for now
766
  $parts = array();
767
 
768
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
769
+ // "name":value pair
770
  $key = $this->decode($parts[1]);
771
+ $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
 
772
  if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
773
  $obj[$key] = $val;
774
  } else {
775
  $obj->$key = $val;
776
  }
777
+ } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) {
778
  // name:value pair, where name is unquoted
779
  $key = $parts[1];
780
+ $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
781
 
782
  if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
783
  $obj[$key] = $val;
795
 
796
  } elseif (($chrs{$c} == $top['delim']) &&
797
  ($top['what'] == SERVICES_JSON_IN_STR) &&
798
+ (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
799
  // found a quote, we're in a string, and it's not escaped
800
  // we know that it's not escaped becase there is _not_ an
801
  // odd number of backslashes at the end of the string so far
802
  array_pop($stk);
803
+ //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
804
 
805
  } elseif (($chrs{$c} == '[') &&
806
  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
811
  } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
812
  // found a right-bracket, and we're in an array
813
  array_pop($stk);
814
+ //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
815
 
816
  } elseif (($chrs{$c} == '{') &&
817
  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
822
  } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
823
  // found a right-brace, and we're in an object
824
  array_pop($stk);
825
+ //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
826
 
827
  } elseif (($substr_chrs_c_2 == '/*') &&
828
  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
839
  for ($i = $top['where']; $i <= $c; ++$i)
840
  $chrs = substr_replace($chrs, ' ', $i, 1);
841
 
842
+ //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
843
 
844
  }
845
 
871
 
872
  return false;
873
  }
874
+
875
+ /**
876
+ * Calculates length of string in bytes
877
+ * @param string
878
+ * @return integer length
879
+ */
880
+ function strlen8( $str )
881
+ {
882
+ if ( $this->_mb_strlen ) {
883
+ return mb_strlen( $str, "8bit" );
884
+ }
885
+ return strlen( $str );
886
+ }
887
+
888
+ /**
889
+ * Returns part of a string, interpreting $start and $length as number of bytes.
890
+ * @param string
891
+ * @param integer start
892
+ * @param integer length
893
+ * @return integer length
894
+ */
895
+ function substr8( $string, $start, $length=false )
896
+ {
897
+ if ( $length === false ) {
898
+ $length = $this->strlen8( $string ) - $start;
899
+ }
900
+ if ( $this->_mb_substr ) {
901
+ return mb_substr( $string, $start, $length, "8bit" );
902
+ }
903
+ return substr( $string, $start, $length );
904
+ }
905
+
906
  }
907
 
908
  if (class_exists('PEAR_Error')) {
929
 
930
  }
931
  }
932
+
933
  }
 
models/attachment.php CHANGED
@@ -41,7 +41,7 @@ class JSON_API_Attachment {
41
  $sizes = array_merge(array('full'), get_intermediate_image_sizes());
42
  }
43
  $this->images = array();
44
- $home = get_bloginfo('home');
45
  foreach ($sizes as $size) {
46
  list($url, $width, $height) = wp_get_attachment_image_src($this->id, $size);
47
  $filename = ABSPATH . substr($url, strlen($home) + 1);
41
  $sizes = array_merge(array('full'), get_intermediate_image_sizes());
42
  }
43
  $this->images = array();
44
+ $home = get_bloginfo('url');
45
  foreach ($sizes as $size) {
46
  list($url, $width, $height) = wp_get_attachment_image_src($this->id, $size);
47
  $filename = ABSPATH . substr($url, strlen($home) + 1);
readme.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_i
4
  Tags: json, api, ajax, cms, admin, integration, moma
5
  Requires at least: 2.8
6
  Tested up to: 3.5.1
7
- Stable tag: 1.0.8
8
 
9
  A RESTful API for WordPress
10
 
@@ -54,10 +54,10 @@ See the [Other Notes](http://wordpress.org/extend/plugins/json-api/other_notes/)
54
  5. [Extending JSON API](#5.-Extending-JSON-API)
55
  5.1. [Plugin hooks](#5.1.-Plugin-hooks)
56
  5.2. [Developing JSON API controllers](#5.2.-Developing-JSON-API-controllers)
57
- 5.3. [Configuration options](#5.3.-Configuration-options)
58
- 6. [Unit tests](#6.-Unit-tests)
59
- 6.1. [Preparing a WordPress test site](#6.1.-Preparing-a-WordPress-test-site)
60
- 6.2. [Running the tests](#6.2.-Running-the-tests)
61
 
62
  == 1. General Concepts ==
63
 
@@ -191,7 +191,7 @@ Returns information about JSON API.
191
 
192
  * `controller` - returns detailed information about a specific controller
193
 
194
- = Response to `?json=core.info` =
195
 
196
  {
197
  "status": "ok",
@@ -202,7 +202,7 @@ Returns information about JSON API.
202
  }
203
 
204
 
205
- = Response to `?json=core.info&controller=core` =
206
 
207
  {
208
  "status": "ok",
@@ -224,7 +224,7 @@ Returns an array of recent posts. You can invoke this from the WordPress home pa
224
  * `page` - return a specific page number from the results
225
  * `post_type` - used to retrieve custom post types
226
 
227
- = Response to `?json=core.get_recent_posts` =
228
 
229
  {
230
  "status": "ok",
@@ -252,7 +252,7 @@ Returns posts according to WordPress's [`WP_Query` parameters](http://codex.word
252
  __Further reading__
253
  See the [`WP_Query` documentation](http://codex.wordpress.org/Class_Reference/WP_Query#Parameters) for a full list of supported parameters. The `post_status` parameter is currently ignored.
254
 
255
- = Response to `?json=get_posts&meta_key=enclosure` =
256
 
257
  {
258
  "status": "ok",
@@ -490,6 +490,10 @@ Note: the tree is arranged by `response.tree.[year].[month].[number of posts]`.
490
 
491
  Returns an array of active categories.
492
 
 
 
 
 
493
  = Response =
494
 
495
  {
@@ -594,6 +598,43 @@ Creates a new post.
594
 
595
  Note: including a file upload field called `attachment` will cause an attachment to be stored with your new post.
596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
  == 2.3. Respond controller methods ==
599
 
@@ -648,6 +689,8 @@ The following arguments modify how you get results back from the API. The redire
648
  * 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).
649
  * Setting `redirect_[status]` allows you to control the resulting browser redirection depending on the `status` value.
650
  * Setting `dev` to a non-empty value adds whitespace for readability and responds with `text/plain`
 
 
651
  * Omitting all of the above arguments will result in a standard JSON response.
652
 
653
  == 3.2. Content-modifying arguments ==
@@ -991,6 +1034,13 @@ You should see the test results print out culminating in a summary:
991
 
992
  == Changelog ==
993
 
 
 
 
 
 
 
 
994
  = 1.0.8 (2013-06-12): =
995
  * Added `widgets` controller
996
  * Added a generic `get_posts` method to the core controller
@@ -1108,6 +1158,9 @@ You should see the test results print out culminating in a summary:
1108
 
1109
  == Upgrade Notice ==
1110
 
 
 
 
1111
  = 1.0.8 =
1112
  Long overdue bugfix/improvement release
1113
 
4
  Tags: json, api, ajax, cms, admin, integration, moma
5
  Requires at least: 2.8
6
  Tested up to: 3.5.1
7
+ Stable tag: 1.0.9
8
 
9
  A RESTful API for WordPress
10
 
54
  5. [Extending JSON API](#5.-Extending-JSON-API)
55
  5.1. [Plugin hooks](#5.1.-Plugin-hooks)
56
  5.2. [Developing JSON API controllers](#5.2.-Developing-JSON-API-controllers)
57
+ 5.3. [Configuration options](#5.3.-Configuration-options)
58
+ 6. [Unit tests](#6.-Unit-tests)
59
+ 6.1. [Preparing a WordPress test site](#6.1.-Preparing-a-WordPress-test-site)
60
+ 6.2. [Running the tests](#6.2.-Running-the-tests)
61
 
62
  == 1. General Concepts ==
63
 
191
 
192
  * `controller` - returns detailed information about a specific controller
193
 
194
+ = Response =
195
 
196
  {
197
  "status": "ok",
202
  }
203
 
204
 
205
+ = Response with controller=core =
206
 
207
  {
208
  "status": "ok",
224
  * `page` - return a specific page number from the results
225
  * `post_type` - used to retrieve custom post types
226
 
227
+ = Response =
228
 
229
  {
230
  "status": "ok",
252
  __Further reading__
253
  See the [`WP_Query` documentation](http://codex.wordpress.org/Class_Reference/WP_Query#Parameters) for a full list of supported parameters. The `post_status` parameter is currently ignored.
254
 
255
+ = Response =
256
 
257
  {
258
  "status": "ok",
490
 
491
  Returns an array of active categories.
492
 
493
+ = Optional argument =
494
+
495
+ * `parent` - returns categories that are direct children of the parent ID
496
+
497
  = Response =
498
 
499
  {
598
 
599
  Note: including a file upload field called `attachment` will cause an attachment to be stored with your new post.
600
 
601
+ == Method: update_post ==
602
+
603
+ Updates a post.
604
+
605
+ = Required argument =
606
+
607
+ * `nonce` - available from the `get_nonce` method (call with vars `controller=posts` and `method=update_post`)
608
+
609
+ = One of the following is required =
610
+
611
+ * `id` or `post_id` - set to the post's ID
612
+ * `slug` or `post_slug` - set to the post's URL slug
613
+
614
+ = Optional arguments =
615
+
616
+ * `status` - sets the post status ("draft" or "publish"), default is "draft"
617
+ * `title` - the post title
618
+ * `content` - the post content
619
+ * `author` - the post's author (login name), default is the current logged in user
620
+ * `categories` - a comma-separated list of categories (URL slugs)
621
+ * `tags` - a comma-separated list of tags (URL slugs)
622
+
623
+ Note: including a file upload field called `attachment` will cause an attachment to be stored with your post.
624
+
625
+ == Method: delete_post ==
626
+
627
+ Deletes a post.
628
+
629
+ = Required argument =
630
+
631
+ * `nonce` - available from the `get_nonce` method (call with vars `controller=posts` and `method=delete_post`)
632
+
633
+ = One of the following is required =
634
+
635
+ * `id` or `post_id` - set to the post's ID
636
+ * `slug` or `post_slug` - set to the post's URL slug
637
+
638
 
639
  == 2.3. Respond controller methods ==
640
 
689
  * 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).
690
  * Setting `redirect_[status]` allows you to control the resulting browser redirection depending on the `status` value.
691
  * Setting `dev` to a non-empty value adds whitespace for readability and responds with `text/plain`
692
+ * Setting `json_encode_options` will let you specify an integer bitmask to modify the behavior of [PHP's `json_encode`](http://php.net/manual/en/function.json-encode.php)
693
+ * Setting `json_unescaped_unicode` will replace unicode-escaped characters with their unescaped equivalents (e.g., `\u00e1` becomes á)
694
  * Omitting all of the above arguments will result in a standard JSON response.
695
 
696
  == 3.2. Content-modifying arguments ==
1034
 
1035
  == Changelog ==
1036
 
1037
+ = 1.0.9 (2013-06-21): =
1038
+ * Added `update_post` and `delete_post` methods to Post controller
1039
+ * Added two JSON encoding arguments: `json_encode_options` and `json_unescaped_unicode`
1040
+ * Added a `parent` argument to `get_category_index`
1041
+ * Fixed a couple places where the code was generating PHP notifications
1042
+ * Updated bundled Services_JSON library (only used if `json_encode` is unavailable)
1043
+
1044
  = 1.0.8 (2013-06-12): =
1045
  * Added `widgets` controller
1046
  * Added a generic `get_posts` method to the core controller
1158
 
1159
  == Upgrade Notice ==
1160
 
1161
+ = 1.0.9 =
1162
+ Update/delete post methods and some other bugfixes and improvements
1163
+
1164
  = 1.0.8 =
1165
  Long overdue bugfix/improvement release
1166
 
singletons/api.php CHANGED
@@ -172,7 +172,7 @@ class JSON_API {
172
  echo '<a href="' . wp_nonce_url('options-general.php?page=json-api&amp;action=activate&amp;controller=' . $controller, 'update-options') . '" title="' . __('Activate this controller') . '" class="edit">' . __('Activate') . '</a>';
173
  }
174
 
175
- if ($info['url']) {
176
  echo ' | ';
177
  echo '<a href="' . $info['url'] . '" target="_blank">Docs</a></div>';
178
  }
172
  echo '<a href="' . wp_nonce_url('options-general.php?page=json-api&amp;action=activate&amp;controller=' . $controller, 'update-options') . '" title="' . __('Activate this controller') . '" class="edit">' . __('Activate') . '</a>';
173
  }
174
 
175
+ if (!empty($info['url'])) {
176
  echo ' | ';
177
  echo '<a href="' . $info['url'] . '" target="_blank">Docs</a></div>';
178
  }
singletons/introspector.php CHANGED
@@ -67,8 +67,8 @@ class JSON_API_Introspector {
67
  return $this->month_archives["$year$month"];
68
  }
69
 
70
- public function get_categories() {
71
- $wp_categories = get_categories();
72
  $categories = array();
73
  foreach ($wp_categories as $wp_category) {
74
  if ($wp_category->term_id == 1 && $wp_category->slug == 'uncategorized') {
@@ -79,6 +79,33 @@ class JSON_API_Introspector {
79
  return $categories;
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  public function get_current_category() {
83
  global $json_api;
84
  extract($json_api->query->get(array('id', 'slug', 'category_id', 'category_slug')));
67
  return $this->month_archives["$year$month"];
68
  }
69
 
70
+ public function get_categories($args = null) {
71
+ $wp_categories = get_categories($args);
72
  $categories = array();
73
  foreach ($wp_categories as $wp_category) {
74
  if ($wp_category->term_id == 1 && $wp_category->slug == 'uncategorized') {
79
  return $categories;
80
  }
81
 
82
+ public function get_current_post() {
83
+ global $json_api;
84
+ extract($json_api->query->get(array('id', 'slug', 'post_id', 'post_slug')));
85
+ if ($id || $post_id) {
86
+ if (!$id) {
87
+ $id = $post_id;
88
+ }
89
+ $posts = $this->get_posts(array(
90
+ 'p' => $id
91
+ ), true);
92
+ } else if ($slug || $post_slug) {
93
+ if (!$slug) {
94
+ $slug = $post_slug;
95
+ }
96
+ $posts = $this->get_posts(array(
97
+ 'name' => $slug
98
+ ), true);
99
+ } else {
100
+ $json_api->error("Include 'id' or 'slug' var in your request.");
101
+ }
102
+ if (!empty($posts)) {
103
+ return $posts[0];
104
+ } else {
105
+ return null;
106
+ }
107
+ }
108
+
109
  public function get_current_category() {
110
  global $json_api;
111
  extract($json_api->query->get(array('id', 'slug', 'category_id', 'category_slug')));
singletons/response.php CHANGED
@@ -20,6 +20,7 @@ class JSON_API_Response {
20
  }
21
 
22
  function get_json($data, $status = 'ok') {
 
23
  // Include a status value with the response
24
  if (is_array($data)) {
25
  $data = array_merge(array('status' => $status), $data);
@@ -32,16 +33,27 @@ class JSON_API_Response {
32
 
33
  if (function_exists('json_encode')) {
34
  // Use the built-in json_encode function if it's available
35
- return json_encode($data);
 
 
 
 
36
  } else {
37
  // Use PEAR's Services_JSON encoder otherwise
38
  if (!class_exists('Services_JSON')) {
39
  $dir = json_api_dir();
40
  require_once "$dir/library/JSON.php";
41
  }
42
- $json = new Services_JSON();
43
- return $json->encode($data);
 
 
 
 
 
44
  }
 
 
45
  }
46
 
47
  function is_value_included($key) {
@@ -179,6 +191,10 @@ class JSON_API_Response {
179
  return $pretty;
180
  }
181
 
 
 
 
 
182
  }
183
 
184
  ?>
20
  }
21
 
22
  function get_json($data, $status = 'ok') {
23
+ global $json_api;
24
  // Include a status value with the response
25
  if (is_array($data)) {
26
  $data = array_merge(array('status' => $status), $data);
33
 
34
  if (function_exists('json_encode')) {
35
  // Use the built-in json_encode function if it's available
36
+ $json_encode_options = 0;
37
+ if ($json_api->query->json_encode_options) {
38
+ $json_encode_options = $json_api->query->json_encode_options;
39
+ }
40
+ $json = json_encode($data, $json_encode_options);
41
  } else {
42
  // Use PEAR's Services_JSON encoder otherwise
43
  if (!class_exists('Services_JSON')) {
44
  $dir = json_api_dir();
45
  require_once "$dir/library/JSON.php";
46
  }
47
+ $json_service = new Services_JSON();
48
+ $json = $json_service->encode($data);
49
+ }
50
+
51
+ if ($json_api->query->json_unescaped_unicode) {
52
+ $callback = array($this, 'replace_unicode_escape_sequence');
53
+ $json = preg_replace_callback('/\\\\u([0-9a-f]{4})/i', $callback, $json);
54
  }
55
+
56
+ return $json;
57
  }
58
 
59
  function is_value_included($key) {
191
  return $pretty;
192
  }
193
 
194
+ function replace_unicode_escape_sequence($match) {
195
+ return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
196
+ }
197
+
198
  }
199
 
200
  ?>