JSON API - Version 1.1.2

Version Description

Download this release

Release Info

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

Code changes from version 1.1.1 to 1.1.2

Files changed (48) hide show
  1. controllers/posts.php +12 -12
  2. models/comment.php +2 -2
  3. singletons/api.php +2 -2
  4. singletons/query.php +1 -1
  5. singletons/response.php +5 -5
  6. trunk/controllers/core.php +339 -0
  7. trunk/controllers/posts.php +85 -0
  8. trunk/controllers/respond.php +27 -0
  9. trunk/controllers/widgets.php +108 -0
  10. trunk/json-api.php +83 -0
  11. trunk/library/JSON.php +933 -0
  12. trunk/models/attachment.php +64 -0
  13. trunk/models/author.php +62 -0
  14. trunk/models/category.php +29 -0
  15. trunk/models/comment.php +80 -0
  16. trunk/models/post.php +319 -0
  17. trunk/models/tag.php +26 -0
  18. trunk/readme.txt +1241 -0
  19. trunk/screenshot-1.png +0 -0
  20. trunk/singletons/api.php +394 -0
  21. trunk/singletons/introspector.php +345 -0
  22. trunk/singletons/query.php +194 -0
  23. trunk/singletons/response.php +205 -0
  24. trunk/tests/core.get_author_index-01.phpt +21 -0
  25. trunk/tests/core.get_author_posts-01.phpt +22 -0
  26. trunk/tests/core.get_author_posts-02.phpt +22 -0
  27. trunk/tests/core.get_author_posts-03.phpt +22 -0
  28. trunk/tests/core.get_category_index-01.phpt +21 -0
  29. trunk/tests/core.get_category_posts-01.phpt +21 -0
  30. trunk/tests/core.get_date_index-01.phpt +80 -0
  31. trunk/tests/core.get_date_posts-01.phpt +21 -0
  32. trunk/tests/core.get_date_posts-02.phpt +21 -0
  33. trunk/tests/core.get_date_posts-03.phpt +21 -0
  34. trunk/tests/core.get_page-01.phpt +19 -0
  35. trunk/tests/core.get_page-02.phpt +25 -0
  36. trunk/tests/core.get_post-01.phpt +18 -0
  37. trunk/tests/core.get_posts-01.phpt +21 -0
  38. trunk/tests/core.get_posts-02.phpt +24 -0
  39. trunk/tests/core.get_posts-03.phpt +23 -0
  40. trunk/tests/core.get_recent_posts-01.phpt +21 -0
  41. trunk/tests/core.get_recent_posts-02.phpt +21 -0
  42. trunk/tests/core.get_recent_posts-03.phpt +21 -0
  43. trunk/tests/core.get_search_posts-01.phpt +21 -0
  44. trunk/tests/core.get_tag_index-01.phpt +803 -0
  45. trunk/tests/core.get_tag_posts-01.phpt +21 -0
  46. trunk/tests/core.info-01.phpt +29 -0
  47. trunk/tests/core.info-02.phpt +57 -0
  48. trunk/tests/query-01.phpt +21 -0
controllers/posts.php CHANGED
@@ -9,20 +9,20 @@ 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 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.");
16
  }
17
  $nonce_id = $json_api->get_nonce_id('posts', 'create_post');
18
  if (!wp_verify_nonce($json_api->query->nonce, $nonce_id)) {
19
- $json_api->error("Your 'nonce' value was incorrect. Use the 'get_nonce' API method.");
20
  }
21
  nocache_headers();
22
  $post = new JSON_API_Post();
23
  $id = $post->create($_REQUEST);
24
  if (empty($id)) {
25
- $json_api->error("Could not create post.");
26
  }
27
  return array(
28
  'post' => $post
@@ -36,14 +36,14 @@ class JSON_API_Posts_Controller {
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);
@@ -60,20 +60,20 @@ class JSON_API_Posts_Controller {
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);
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.", 403);
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.", 403);
16
  }
17
  $nonce_id = $json_api->get_nonce_id('posts', 'create_post');
18
  if (!wp_verify_nonce($json_api->query->nonce, $nonce_id)) {
19
+ $json_api->error("Your 'nonce' value was incorrect. Use the 'get_nonce' API method.", 403);
20
  }
21
  nocache_headers();
22
  $post = new JSON_API_Post();
23
  $id = $post->create($_REQUEST);
24
  if (empty($id)) {
25
+ $json_api->error("Could not create post.", 500);
26
  }
27
  return array(
28
  'post' => $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.", 403);
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.", 403);
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.", 403);
47
  }
48
  nocache_headers();
49
  $post = new JSON_API_Post($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.", 403);
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.", 403);
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.", 403);
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.", 403);
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.", 403);
77
  }
78
  nocache_headers();
79
  wp_delete_post($post->ID);
models/comment.php CHANGED
@@ -60,12 +60,12 @@ class JSON_API_Comment {
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() {
60
 
61
  function comment_closed() {
62
  global $json_api;
63
+ $json_api->error("Post is closed for comments.", 403);
64
  }
65
 
66
  function comment_on_draft() {
67
  global $json_api;
68
+ $json_api->error("You cannot comment on unpublished posts.", 403);
69
  }
70
 
71
  function comment_post_redirect() {
singletons/api.php CHANGED
@@ -379,10 +379,10 @@ class JSON_API {
379
  $wp_rewrite->flush_rules();
380
  }
381
 
382
- function error($message = 'Unknown error', $status = 'error') {
383
  $this->response->respond(array(
384
  'error' => $message
385
- ), $status);
386
  }
387
 
388
  function include_value($key) {
379
  $wp_rewrite->flush_rules();
380
  }
381
 
382
+ function error($message = 'Unknown error', $http_status = 404) {
383
  $this->response->respond(array(
384
  'error' => $message
385
+ ), 'error', $http_status);
386
  }
387
 
388
  function include_value($key) {
singletons/query.php CHANGED
@@ -168,7 +168,7 @@ class JSON_API_Query {
168
  return 'get_search_results';
169
  } else if (is_home()) {
170
  if (empty($_GET['json'])) {
171
- $json_api->error("Uknown method '$method'.");
172
  }
173
  return 'get_recent_posts';
174
  } else if (is_page()) {
168
  return 'get_search_results';
169
  } else if (is_home()) {
170
  if (empty($_GET['json'])) {
171
+ $json_api->error("Unknown method '$method'.");
172
  }
173
  return 'get_recent_posts';
174
  } else if (is_page()) {
singletons/response.php CHANGED
@@ -74,7 +74,7 @@ class JSON_API_Response {
74
  }
75
  }
76
 
77
- function respond($result, $status = 'ok') {
78
  global $json_api;
79
  $json = $this->get_json($result, $status);
80
  $status_redirect = "redirect_$status";
@@ -97,15 +97,15 @@ class JSON_API_Response {
97
  $this->callback($json_api->query->callback, $json);
98
  } else {
99
  // Output the result
100
- $this->output($json);
101
  }
102
  exit;
103
  }
104
 
105
- function output($result) {
106
  $charset = get_option('blog_charset');
107
  if (!headers_sent()) {
108
- header('HTTP/1.1 200 OK', true);
109
  header("Content-Type: application/json; charset=$charset", true);
110
  }
111
  echo $result;
@@ -114,7 +114,7 @@ class JSON_API_Response {
114
  function callback($callback, $result) {
115
  $charset = get_option('blog_charset');
116
  if (!headers_sent()) {
117
- header('HTTP/1.1 200 OK', true);
118
  header("Content-Type: application/javascript; charset=$charset", true);
119
  }
120
  echo "$callback($result)";
74
  }
75
  }
76
 
77
+ function respond($result, $status = 'ok', $http_status = 200) {
78
  global $json_api;
79
  $json = $this->get_json($result, $status);
80
  $status_redirect = "redirect_$status";
97
  $this->callback($json_api->query->callback, $json);
98
  } else {
99
  // Output the result
100
+ $this->output($json, $http_status);
101
  }
102
  exit;
103
  }
104
 
105
+ function output($result, $http_status = 200) {
106
  $charset = get_option('blog_charset');
107
  if (!headers_sent()) {
108
+ status_header($http_status);
109
  header("Content-Type: application/json; charset=$charset", true);
110
  }
111
  echo $result;
114
  function callback($callback, $result) {
115
  $charset = get_option('blog_charset');
116
  if (!headers_sent()) {
117
+ status_header(200);
118
  header("Content-Type: application/javascript; charset=$charset", true);
119
  }
120
  echo "$callback($result)";
trunk/controllers/core.php ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Controller name: Core
4
+ Controller description: Basic introspection methods
5
+ */
6
+
7
+ class JSON_API_Core_Controller {
8
+
9
+ public function info() {
10
+ global $json_api;
11
+ $php = '';
12
+ if (!empty($json_api->query->controller)) {
13
+ return $json_api->controller_info($json_api->query->controller);
14
+ } else {
15
+ $dir = json_api_dir();
16
+ if (file_exists("$dir/json-api.php")) {
17
+ $php = file_get_contents("$dir/json-api.php");
18
+ } else {
19
+ // Check one directory up, in case json-api.php was moved
20
+ $dir = dirname($dir);
21
+ if (file_exists("$dir/json-api.php")) {
22
+ $php = file_get_contents("$dir/json-api.php");
23
+ }
24
+ }
25
+ if (preg_match('/^\s*Version:\s*(.+)$/m', $php, $matches)) {
26
+ $version = $matches[1];
27
+ } else {
28
+ $version = '(Unknown)';
29
+ }
30
+ $active_controllers = explode(',', get_option('json_api_controllers', 'core'));
31
+ $controllers = array_intersect($json_api->get_controllers(), $active_controllers);
32
+ return array(
33
+ 'json_api_version' => $version,
34
+ 'controllers' => array_values($controllers)
35
+ );
36
+ }
37
+ }
38
+
39
+ public function get_recent_posts() {
40
+ global $json_api;
41
+ $posts = $json_api->introspector->get_posts();
42
+ return $this->posts_result($posts);
43
+ }
44
+
45
+ public function get_posts() {
46
+ global $json_api;
47
+ $url = parse_url($_SERVER['REQUEST_URI']);
48
+ $defaults = array(
49
+ 'ignore_sticky_posts' => true
50
+ );
51
+ $query = wp_parse_args($url['query']);
52
+ unset($query['json']);
53
+ unset($query['post_status']);
54
+ $query = array_merge($defaults, $query);
55
+ $posts = $json_api->introspector->get_posts($query);
56
+ $result = $this->posts_result($posts);
57
+ $result['query'] = $query;
58
+ return $result;
59
+ }
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);
72
+ }
73
+ if ($next) {
74
+ $response['next_url'] = get_permalink($next->ID);
75
+ }
76
+ return $response;
77
+ } else {
78
+ $json_api->error("Not found.");
79
+ }
80
+ }
81
+
82
+ public function get_page() {
83
+ global $json_api;
84
+ extract($json_api->query->get(array('id', 'slug', 'page_id', 'page_slug', 'children')));
85
+ if ($id || $page_id) {
86
+ if (!$id) {
87
+ $id = $page_id;
88
+ }
89
+ $posts = $json_api->introspector->get_posts(array(
90
+ 'page_id' => $id
91
+ ));
92
+ } else if ($slug || $page_slug) {
93
+ if (!$slug) {
94
+ $slug = $page_slug;
95
+ }
96
+ $posts = $json_api->introspector->get_posts(array(
97
+ 'pagename' => $slug
98
+ ));
99
+ } else {
100
+ $json_api->error("Include 'id' or 'slug' var in your request.");
101
+ }
102
+
103
+ // Workaround for https://core.trac.wordpress.org/ticket/12647
104
+ if (empty($posts)) {
105
+ $url = $_SERVER['REQUEST_URI'];
106
+ $parsed_url = parse_url($url);
107
+ $path = $parsed_url['path'];
108
+ if (preg_match('#^http://[^/]+(/.+)$#', get_bloginfo('url'), $matches)) {
109
+ $blog_root = $matches[1];
110
+ $path = preg_replace("#^$blog_root#", '', $path);
111
+ }
112
+ if (substr($path, 0, 1) == '/') {
113
+ $path = substr($path, 1);
114
+ }
115
+ $posts = $json_api->introspector->get_posts(array('pagename' => $path));
116
+ }
117
+
118
+ if (count($posts) == 1) {
119
+ if (!empty($children)) {
120
+ $json_api->introspector->attach_child_posts($posts[0]);
121
+ }
122
+ return array(
123
+ 'page' => $posts[0]
124
+ );
125
+ } else {
126
+ $json_api->error("Not found.");
127
+ }
128
+ }
129
+
130
+ public function get_date_posts() {
131
+ global $json_api;
132
+ if ($json_api->query->date) {
133
+ $date = preg_replace('/\D/', '', $json_api->query->date);
134
+ if (!preg_match('/^\d{4}(\d{2})?(\d{2})?$/', $date)) {
135
+ $json_api->error("Specify a date var in one of 'YYYY' or 'YYYY-MM' or 'YYYY-MM-DD' formats.");
136
+ }
137
+ $request = array('year' => substr($date, 0, 4));
138
+ if (strlen($date) > 4) {
139
+ $request['monthnum'] = (int) substr($date, 4, 2);
140
+ }
141
+ if (strlen($date) > 6) {
142
+ $request['day'] = (int) substr($date, 6, 2);
143
+ }
144
+ $posts = $json_api->introspector->get_posts($request);
145
+ } else {
146
+ $json_api->error("Include 'date' var in your request.");
147
+ }
148
+ return $this->posts_result($posts);
149
+ }
150
+
151
+ public function get_category_posts() {
152
+ global $json_api;
153
+ $category = $json_api->introspector->get_current_category();
154
+ if (!$category) {
155
+ $json_api->error("Not found.");
156
+ }
157
+ $posts = $json_api->introspector->get_posts(array(
158
+ 'cat' => $category->id
159
+ ));
160
+ return $this->posts_object_result($posts, $category);
161
+ }
162
+
163
+ public function get_tag_posts() {
164
+ global $json_api;
165
+ $tag = $json_api->introspector->get_current_tag();
166
+ if (!$tag) {
167
+ $json_api->error("Not found.");
168
+ }
169
+ $posts = $json_api->introspector->get_posts(array(
170
+ 'tag' => $tag->slug
171
+ ));
172
+ return $this->posts_object_result($posts, $tag);
173
+ }
174
+
175
+ public function get_author_posts() {
176
+ global $json_api;
177
+ $author = $json_api->introspector->get_current_author();
178
+ if (!$author) {
179
+ $json_api->error("Not found.");
180
+ }
181
+ $posts = $json_api->introspector->get_posts(array(
182
+ 'author' => $author->id
183
+ ));
184
+ return $this->posts_object_result($posts, $author);
185
+ }
186
+
187
+ public function get_search_results() {
188
+ global $json_api;
189
+ if ($json_api->query->search) {
190
+ $posts = $json_api->introspector->get_posts(array(
191
+ 's' => $json_api->query->search
192
+ ));
193
+ } else {
194
+ $json_api->error("Include 'search' var in your request.");
195
+ }
196
+ return $this->posts_result($posts);
197
+ }
198
+
199
+ public function get_date_index() {
200
+ global $json_api;
201
+ $permalinks = $json_api->introspector->get_date_archive_permalinks();
202
+ $tree = $json_api->introspector->get_date_archive_tree($permalinks);
203
+ return array(
204
+ 'permalinks' => $permalinks,
205
+ 'tree' => $tree
206
+ );
207
+ }
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
221
+ );
222
+ }
223
+
224
+ public function get_tag_index() {
225
+ global $json_api;
226
+ $tags = $json_api->introspector->get_tags();
227
+ return array(
228
+ 'count' => count($tags),
229
+ 'tags' => $tags
230
+ );
231
+ }
232
+
233
+ public function get_author_index() {
234
+ global $json_api;
235
+ $authors = $json_api->introspector->get_authors();
236
+ return array(
237
+ 'count' => count($authors),
238
+ 'authors' => array_values($authors)
239
+ );
240
+ }
241
+
242
+ public function get_page_index() {
243
+ global $json_api;
244
+ $pages = array();
245
+ $post_type = $json_api->query->post_type ? $json_api->query->post_type : 'page';
246
+
247
+ // Thanks to blinder for the fix!
248
+ $numberposts = empty($json_api->query->count) ? -1 : $json_api->query->count;
249
+ $wp_posts = get_posts(array(
250
+ 'post_type' => $post_type,
251
+ 'post_parent' => 0,
252
+ 'order' => 'ASC',
253
+ 'orderby' => 'menu_order',
254
+ 'numberposts' => $numberposts
255
+ ));
256
+ foreach ($wp_posts as $wp_post) {
257
+ $pages[] = new JSON_API_Post($wp_post);
258
+ }
259
+ foreach ($pages as $page) {
260
+ $json_api->introspector->attach_child_posts($page);
261
+ }
262
+ return array(
263
+ 'pages' => $pages
264
+ );
265
+ }
266
+
267
+ public function get_nonce() {
268
+ global $json_api;
269
+ extract($json_api->query->get(array('controller', 'method')));
270
+ if ($controller && $method) {
271
+ $controller = strtolower($controller);
272
+ if (!in_array($controller, $json_api->get_controllers())) {
273
+ $json_api->error("Unknown controller '$controller'.");
274
+ }
275
+ require_once $json_api->controller_path($controller);
276
+ if (!method_exists($json_api->controller_class($controller), $method)) {
277
+ $json_api->error("Unknown method '$method'.");
278
+ }
279
+ $nonce_id = $json_api->get_nonce_id($controller, $method);
280
+ return array(
281
+ 'controller' => $controller,
282
+ 'method' => $method,
283
+ 'nonce' => wp_create_nonce($nonce_id)
284
+ );
285
+ } else {
286
+ $json_api->error("Include 'controller' and 'method' vars in your request.");
287
+ }
288
+ }
289
+
290
+ protected function get_object_posts($object, $id_var, $slug_var) {
291
+ global $json_api;
292
+ $object_id = "{$type}_id";
293
+ $object_slug = "{$type}_slug";
294
+ extract($json_api->query->get(array('id', 'slug', $object_id, $object_slug)));
295
+ if ($id || $$object_id) {
296
+ if (!$id) {
297
+ $id = $$object_id;
298
+ }
299
+ $posts = $json_api->introspector->get_posts(array(
300
+ $id_var => $id
301
+ ));
302
+ } else if ($slug || $$object_slug) {
303
+ if (!$slug) {
304
+ $slug = $$object_slug;
305
+ }
306
+ $posts = $json_api->introspector->get_posts(array(
307
+ $slug_var => $slug
308
+ ));
309
+ } else {
310
+ $json_api->error("No $type specified. Include 'id' or 'slug' var in your request.");
311
+ }
312
+ return $posts;
313
+ }
314
+
315
+ protected function posts_result($posts) {
316
+ global $wp_query;
317
+ return array(
318
+ 'count' => count($posts),
319
+ 'count_total' => (int) $wp_query->found_posts,
320
+ 'pages' => $wp_query->max_num_pages,
321
+ 'posts' => $posts
322
+ );
323
+ }
324
+
325
+ protected function posts_object_result($posts, $object) {
326
+ global $wp_query;
327
+ // Convert something like "JSON_API_Category" into "category"
328
+ $object_key = strtolower(substr(get_class($object), 9));
329
+ return array(
330
+ 'count' => count($posts),
331
+ 'pages' => (int) $wp_query->max_num_pages,
332
+ $object_key => $object,
333
+ 'posts' => $posts
334
+ );
335
+ }
336
+
337
+ }
338
+
339
+ ?>
trunk/controllers/posts.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Controller name: Posts
4
+ Controller description: Data manipulation methods for posts
5
+ */
6
+
7
+ class JSON_API_Posts_Controller {
8
+
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.", 403);
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.", 403);
16
+ }
17
+ $nonce_id = $json_api->get_nonce_id('posts', 'create_post');
18
+ if (!wp_verify_nonce($json_api->query->nonce, $nonce_id)) {
19
+ $json_api->error("Your 'nonce' value was incorrect. Use the 'get_nonce' API method.", 403);
20
+ }
21
+ nocache_headers();
22
+ $post = new JSON_API_Post();
23
+ $id = $post->create($_REQUEST);
24
+ if (empty($id)) {
25
+ $json_api->error("Could not create post.", 500);
26
+ }
27
+ return array(
28
+ 'post' => $post
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.", 403);
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.", 403);
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.", 403);
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.", 403);
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.", 403);
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.", 403);
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.", 403);
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.", 403);
77
+ }
78
+ nocache_headers();
79
+ wp_delete_post($post->ID);
80
+ return array();
81
+ }
82
+
83
+ }
84
+
85
+ ?>
trunk/controllers/respond.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Controller name: Respond
4
+ Controller description: Comment/trackback submission methods
5
+ */
6
+
7
+ class JSON_API_Respond_Controller {
8
+
9
+ function submit_comment() {
10
+ global $json_api;
11
+ nocache_headers();
12
+ if (empty($_REQUEST['post_id'])) {
13
+ $json_api->error("No post specified. Include 'post_id' var in your request.");
14
+ } else if (empty($_REQUEST['name']) ||
15
+ empty($_REQUEST['email']) ||
16
+ empty($_REQUEST['content'])) {
17
+ $json_api->error("Please include all required arguments (name, email, content).");
18
+ } else if (!is_email($_REQUEST['email'])) {
19
+ $json_api->error("Please enter a valid email address.");
20
+ }
21
+ $pending = new JSON_API_Comment();
22
+ return $pending->handle_submission();
23
+ }
24
+
25
+ }
26
+
27
+ ?>
trunk/controllers/widgets.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Controller name: Widgets
4
+ Controller description: Retrieve sidebar widgets
5
+ */
6
+
7
+ class JSON_API_Widgets_Controller {
8
+
9
+ function get_sidebar() {
10
+ global $json_api;
11
+ $index = @$_REQUEST['sidebar_id'];
12
+ if (empty($_REQUEST['sidebar_id'])) {
13
+ $json_api->error("No sidebar specified. Include 'sidebar_id' var in your request.");
14
+ } else if (!is_active_sidebar($index)) {
15
+ $json_api->error("Sidebar '$index' is not active.");
16
+ }
17
+
18
+ $widget_params = array(
19
+ 'before_widget',
20
+ 'after_widget',
21
+ 'before_title',
22
+ 'after_title'
23
+ );
24
+ $json_api_params = array();
25
+ foreach ($widget_params as $param) {
26
+ if (isset($_REQUEST[$param])) {
27
+ $json_api_params[$param] = $_REQUEST[$param];
28
+ }
29
+ }
30
+
31
+ $widgets = array();
32
+
33
+ global $wp_registered_sidebars, $wp_registered_widgets;
34
+
35
+ if ( is_int($index) ) {
36
+ $index = "sidebar-$index";
37
+ } else {
38
+ $index = sanitize_title($index);
39
+ foreach ( (array) $wp_registered_sidebars as $key => $value ) {
40
+ if ( sanitize_title($value['name']) == $index ) {
41
+ $index = $key;
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ $sidebars_widgets = wp_get_sidebars_widgets();
48
+
49
+ if ( empty($wp_registered_sidebars[$index]) || !array_key_exists($index, $sidebars_widgets) || !is_array($sidebars_widgets[$index]) || empty($sidebars_widgets[$index]) )
50
+ return false;
51
+
52
+ $sidebar = $wp_registered_sidebars[$index];
53
+
54
+ $did_one = false;
55
+ foreach ( (array) $sidebars_widgets[$index] as $id ) {
56
+
57
+ if ( !isset($wp_registered_widgets[$id]) ) continue;
58
+
59
+ $params = array_merge(
60
+ array( array_merge( $sidebar, array('widget_id' => $id, 'widget_name' => $wp_registered_widgets[$id]['name']), $json_api_params ) ),
61
+ (array) $wp_registered_widgets[$id]['params']
62
+ );
63
+
64
+
65
+ // Substitute HTML id and class attributes into before_widget
66
+ $classname_ = '';
67
+ foreach ( (array) $wp_registered_widgets[$id]['classname'] as $cn ) {
68
+ if ( is_string($cn) )
69
+ $classname_ .= '_' . $cn;
70
+ elseif ( is_object($cn) )
71
+ $classname_ .= '_' . get_class($cn);
72
+ }
73
+ $classname_ = ltrim($classname_, '_');
74
+ $params[0]['before_widget'] = sprintf($params[0]['before_widget'], $id, $classname_);
75
+
76
+ $params = apply_filters( 'dynamic_sidebar_params', $params );
77
+
78
+ $callback = $wp_registered_widgets[$id]['callback'];
79
+
80
+ do_action( 'dynamic_sidebar', $wp_registered_widgets[$id] );
81
+
82
+ if ( is_callable($callback) ) {
83
+ ob_start();
84
+ $object = $callback[0];
85
+ $settings = $object->get_settings();
86
+ $widget_params = $wp_registered_widgets[$id]['params'];
87
+ $number = $widget_params[0]['number'];
88
+ $instance = $settings[$number];
89
+ call_user_func_array($callback, $params);
90
+ $widgets[] = array(
91
+ 'id' => $id,
92
+ 'widget' => trim(ob_get_contents()),
93
+ 'params' => $params[0],
94
+ 'instance' => $instance
95
+ );
96
+ ob_end_clean();
97
+ }
98
+ }
99
+
100
+ return array(
101
+ 'sidebar_id' => $index,
102
+ 'widgets' => $widgets
103
+ );
104
+ }
105
+
106
+ }
107
+
108
+ ?>
trunk/json-api.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: JSON API
4
+ Plugin URI: http://wordpress.org/plugins/json-api/
5
+ Description: A RESTful API for WordPress
6
+ Version: 1.1.1
7
+ Author: Dan Phiffer
8
+ Author URI: http://phiffer.org/
9
+ */
10
+
11
+ $dir = json_api_dir();
12
+ @include_once "$dir/singletons/api.php";
13
+ @include_once "$dir/singletons/query.php";
14
+ @include_once "$dir/singletons/introspector.php";
15
+ @include_once "$dir/singletons/response.php";
16
+ @include_once "$dir/models/post.php";
17
+ @include_once "$dir/models/comment.php";
18
+ @include_once "$dir/models/category.php";
19
+ @include_once "$dir/models/tag.php";
20
+ @include_once "$dir/models/author.php";
21
+ @include_once "$dir/models/attachment.php";
22
+
23
+ function json_api_init() {
24
+ global $json_api;
25
+ if (phpversion() < 5) {
26
+ add_action('admin_notices', 'json_api_php_version_warning');
27
+ return;
28
+ }
29
+ if (!class_exists('JSON_API')) {
30
+ add_action('admin_notices', 'json_api_class_warning');
31
+ return;
32
+ }
33
+ add_filter('rewrite_rules_array', 'json_api_rewrites');
34
+ $json_api = new JSON_API();
35
+ }
36
+
37
+ function json_api_php_version_warning() {
38
+ echo "<div id=\"json-api-warning\" class=\"updated fade\"><p>Sorry, JSON API requires PHP version 5.0 or greater.</p></div>";
39
+ }
40
+
41
+ function json_api_class_warning() {
42
+ echo "<div id=\"json-api-warning\" class=\"updated fade\"><p>Oops, JSON_API class not found. If you've defined a JSON_API_DIR constant, double check that the path is correct.</p></div>";
43
+ }
44
+
45
+ function json_api_activation() {
46
+ // Add the rewrite rule on activation
47
+ global $wp_rewrite;
48
+ add_filter('rewrite_rules_array', 'json_api_rewrites');
49
+ $wp_rewrite->flush_rules();
50
+ }
51
+
52
+ function json_api_deactivation() {
53
+ // Remove the rewrite rule on deactivation
54
+ global $wp_rewrite;
55
+ $wp_rewrite->flush_rules();
56
+ }
57
+
58
+ function json_api_rewrites($wp_rules) {
59
+ $base = get_option('json_api_base', 'api');
60
+ if (empty($base)) {
61
+ return $wp_rules;
62
+ }
63
+ $json_api_rules = array(
64
+ "$base\$" => 'index.php?json=info',
65
+ "$base/(.+)\$" => 'index.php?json=$matches[1]'
66
+ );
67
+ return array_merge($json_api_rules, $wp_rules);
68
+ }
69
+
70
+ function json_api_dir() {
71
+ if (defined('JSON_API_DIR') && file_exists(JSON_API_DIR)) {
72
+ return JSON_API_DIR;
73
+ } else {
74
+ return dirname(__FILE__);
75
+ }
76
+ }
77
+
78
+ // Add initialization and activation hooks
79
+ add_action('init', 'json_api_init');
80
+ register_activation_hook("$dir/json-api.php", 'json_api_activation');
81
+ register_deactivation_hook("$dir/json-api.php", 'json_api_deactivation');
82
+
83
+ ?>
trunk/library/JSON.php ADDED
@@ -0,0 +1,933 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 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
+ */
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
+ * 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
+ *
101
+ * Brief example of use:
102
+ *
103
+ * <code>
104
+ * // create a new instance of Services_JSON
105
+ * $json = new Services_JSON();
106
+ *
107
+ * // convert a complexe value to JSON notation, and send it to the browser
108
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
109
+ * $output = $json->encode($value);
110
+ *
111
+ * print($output);
112
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
113
+ *
114
+ * // accept incoming POST data, assumed to be in JSON notation
115
+ * $input = file_get_contents('php://input', 1000000);
116
+ * $value = $json->decode($input);
117
+ * </code>
118
+ */
119
+ class Services_JSON
120
+ {
121
+ /**
122
+ * constructs a new JSON instance
123
+ *
124
+ * @param int $use object behavior flags; combine with boolean-OR
125
+ *
126
+ * possible values:
127
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
128
+ * "{...}" syntax creates associative arrays
129
+ * instead of objects in decode().
130
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
131
+ * Values which can't be encoded (e.g. resources)
132
+ * appear as NULL instead of throwing errors.
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
+ *
157
+ * Normally should be handled by mb_convert_encoding, but
158
+ * provides a slower PHP-only method for installations
159
+ * that lack the multibye string extension.
160
+ *
161
+ * @param string $utf16 UTF-16 character
162
+ * @return string UTF-8 character
163
+ * @access private
164
+ */
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
+
172
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
173
+
174
+ switch(true) {
175
+ case ((0x7F & $bytes) == $bytes):
176
+ // this case should never be reached, because we are in ASCII range
177
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
178
+ return chr(0x7F & $bytes);
179
+
180
+ case (0x07FF & $bytes) == $bytes:
181
+ // return a 2-byte UTF-8 character
182
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
183
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
184
+ . chr(0x80 | ($bytes & 0x3F));
185
+
186
+ case (0xFFFF & $bytes) == $bytes:
187
+ // return a 3-byte UTF-8 character
188
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
189
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
190
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
191
+ . chr(0x80 | ($bytes & 0x3F));
192
+ }
193
+
194
+ // ignoring UTF-32 for now, sorry
195
+ return '';
196
+ }
197
+
198
+ /**
199
+ * convert a string from one UTF-8 char to one UTF-16 char
200
+ *
201
+ * Normally should be handled by mb_convert_encoding, but
202
+ * provides a slower PHP-only method for installations
203
+ * that lack the multibye string extension.
204
+ *
205
+ * @param string $utf8 UTF-8 character
206
+ * @return string UTF-16 character
207
+ * @access private
208
+ */
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
220
+ return $utf8;
221
+
222
+ case 2:
223
+ // return a UTF-16 character from a 2-byte UTF-8 char
224
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
225
+ return chr(0x07 & (ord($utf8{0}) >> 2))
226
+ . chr((0xC0 & (ord($utf8{0}) << 6))
227
+ | (0x3F & ord($utf8{1})));
228
+
229
+ case 3:
230
+ // return a UTF-16 character from a 3-byte UTF-8 char
231
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
232
+ return chr((0xF0 & (ord($utf8{0}) << 4))
233
+ | (0x0F & (ord($utf8{1}) >> 2)))
234
+ . chr((0xC0 & (ord($utf8{1}) << 6))
235
+ | (0x7F & ord($utf8{2})));
236
+ }
237
+
238
+ // ignoring UTF-32 for now, sorry
239
+ return '';
240
+ }
241
+
242
+ /**
243
+ * encodes an arbitrary variable into JSON format (and sends JSON Header)
244
+ *
245
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
246
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
247
+ * if var is a strng, note that encode() always expects it
248
+ * to be in ASCII or UTF-8 format!
249
+ *
250
+ * @return mixed JSON string representation of input var or an error if a problem occurs
251
+ * @access public
252
+ */
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.
263
+ * if var is a strng, note that encode() always expects it
264
+ * to be in ASCII or UTF-8 format!
265
+ *
266
+ * @return mixed JSON string representation of input var or an error if a problem occurs
267
+ * @access public
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
281
+ *
282
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
283
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
284
+ * if var is a strng, note that encode() always expects it
285
+ * to be in ASCII or UTF-8 format!
286
+ *
287
+ * @return mixed JSON string representation of input var or an error if a problem occurs
288
+ * @access public
289
+ */
290
+ function _encode($var)
291
+ {
292
+
293
+ switch (gettype($var)) {
294
+ case 'boolean':
295
+ return $var ? 'true' : 'false';
296
+
297
+ case 'NULL':
298
+ return 'null';
299
+
300
+ case 'integer':
301
+ return (int) $var;
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,
314
+ * escaping with a slash or encoding to UTF-8 where necessary
315
+ */
316
+ for ($c = 0; $c < $strlen_var; ++$c) {
317
+
318
+ $ord_var_c = ord($var{$c});
319
+
320
+ switch (true) {
321
+ case $ord_var_c == 0x08:
322
+ $ascii .= '\b';
323
+ break;
324
+ case $ord_var_c == 0x09:
325
+ $ascii .= '\t';
326
+ break;
327
+ case $ord_var_c == 0x0A:
328
+ $ascii .= '\n';
329
+ break;
330
+ case $ord_var_c == 0x0C:
331
+ $ascii .= '\f';
332
+ break;
333
+ case $ord_var_c == 0x0D:
334
+ $ascii .= '\r';
335
+ break;
336
+
337
+ case $ord_var_c == 0x22:
338
+ case $ord_var_c == 0x2F:
339
+ case $ord_var_c == 0x5C:
340
+ // double quote, slash, slosh
341
+ $ascii .= '\\'.$var{$c};
342
+ break;
343
+
344
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
345
+ // characters U-00000000 - U-0000007F (same as ASCII)
346
+ $ascii .= $var{$c};
347
+ break;
348
+
349
+ case (($ord_var_c & 0xE0) == 0xC0):
350
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
351
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
352
+ if ($c+1 >= $strlen_var) {
353
+ $c += 1;
354
+ $ascii .= '?';
355
+ break;
356
+ }
357
+
358
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
359
+ $c += 1;
360
+ $utf16 = $this->utf82utf16($char);
361
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
362
+ break;
363
+
364
+ case (($ord_var_c & 0xF0) == 0xE0):
365
+ if ($c+2 >= $strlen_var) {
366
+ $c += 2;
367
+ $ascii .= '?';
368
+ break;
369
+ }
370
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
371
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
372
+ $char = pack('C*', $ord_var_c,
373
+ @ord($var{$c + 1}),
374
+ @ord($var{$c + 2}));
375
+ $c += 2;
376
+ $utf16 = $this->utf82utf16($char);
377
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
378
+ break;
379
+
380
+ case (($ord_var_c & 0xF8) == 0xF0):
381
+ if ($c+3 >= $strlen_var) {
382
+ $c += 3;
383
+ $ascii .= '?';
384
+ break;
385
+ }
386
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
387
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
388
+ $char = pack('C*', $ord_var_c,
389
+ ord($var{$c + 1}),
390
+ ord($var{$c + 2}),
391
+ ord($var{$c + 3}));
392
+ $c += 3;
393
+ $utf16 = $this->utf82utf16($char);
394
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
395
+ break;
396
+
397
+ case (($ord_var_c & 0xFC) == 0xF8):
398
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
399
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
400
+ if ($c+4 >= $strlen_var) {
401
+ $c += 4;
402
+ $ascii .= '?';
403
+ break;
404
+ }
405
+ $char = pack('C*', $ord_var_c,
406
+ ord($var{$c + 1}),
407
+ ord($var{$c + 2}),
408
+ ord($var{$c + 3}),
409
+ ord($var{$c + 4}));
410
+ $c += 4;
411
+ $utf16 = $this->utf82utf16($char);
412
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
413
+ break;
414
+
415
+ case (($ord_var_c & 0xFE) == 0xFC):
416
+ if ($c+5 >= $strlen_var) {
417
+ $c += 5;
418
+ $ascii .= '?';
419
+ break;
420
+ }
421
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
422
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
423
+ $char = pack('C*', $ord_var_c,
424
+ ord($var{$c + 1}),
425
+ ord($var{$c + 2}),
426
+ ord($var{$c + 3}),
427
+ ord($var{$c + 4}),
428
+ ord($var{$c + 5}));
429
+ $c += 5;
430
+ $utf16 = $this->utf82utf16($char);
431
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
432
+ break;
433
+ }
434
+ }
435
+ return '"'.$ascii.'"';
436
+
437
+ case 'array':
438
+ /*
439
+ * As per JSON spec if any array key is not an integer
440
+ * we must treat the the whole array as an object. We
441
+ * also try to catch a sparsely populated associative
442
+ * array with numeric keys here because some JS engines
443
+ * will create an array with empty indexes up to
444
+ * max_index which can cause memory issues and because
445
+ * the keys, which may be relevant, will be remapped
446
+ * otherwise.
447
+ *
448
+ * As per the ECMA and JSON specification an object may
449
+ * have any string as a property. Unfortunately due to
450
+ * a hole in the ECMA specification if the key is a
451
+ * ECMA reserved word or starts with a digit the
452
+ * parameter is only accessible using ECMAScript's
453
+ * bracket notation.
454
+ */
455
+
456
+ // treat as a JSON object
457
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
458
+ $properties = array_map(array($this, 'name_value'),
459
+ array_keys($var),
460
+ array_values($var));
461
+
462
+ foreach($properties as $property) {
463
+ if(Services_JSON::isError($property)) {
464
+ return $property;
465
+ }
466
+ }
467
+
468
+ return '{' . join(',', $properties) . '}';
469
+ }
470
+
471
+ // treat it like a regular array
472
+ $elements = array_map(array($this, '_encode'), $var);
473
+
474
+ foreach($elements as $element) {
475
+ if(Services_JSON::isError($element)) {
476
+ return $element;
477
+ }
478
+ }
479
+
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));
507
+
508
+ foreach($properties as $property) {
509
+ if(Services_JSON::isError($property)) {
510
+ return $property;
511
+ }
512
+ }
513
+
514
+ return '{' . join(',', $properties) . '}';
515
+
516
+ default:
517
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
518
+ ? 'null'
519
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
520
+ }
521
+ }
522
+
523
+ /**
524
+ * array-walking function for use in generating JSON-formatted name-value pairs
525
+ *
526
+ * @param string $name name of key to use
527
+ * @param mixed $value reference to an array element to be encoded
528
+ *
529
+ * @return string JSON-formatted name-value pair, like '"name":value'
530
+ * @access private
531
+ */
532
+ function name_value($name, $value)
533
+ {
534
+ $encoded_value = $this->_encode($value);
535
+
536
+ if(Services_JSON::isError($encoded_value)) {
537
+ return $encoded_value;
538
+ }
539
+
540
+ return $this->_encode(strval($name)) . ':' . $encoded_value;
541
+ }
542
+
543
+ /**
544
+ * reduce a string by removing leading and trailing comments and whitespace
545
+ *
546
+ * @param $str string string value to strip of comments and whitespace
547
+ *
548
+ * @return string string value stripped of comments and whitespace
549
+ * @access private
550
+ */
551
+ function reduce_string($str)
552
+ {
553
+ $str = preg_replace(array(
554
+
555
+ // eliminate single line comments in '// ...' form
556
+ '#^\s*//(.+)$#m',
557
+
558
+ // eliminate multi-line comments in '/* ... */' form, at start of string
559
+ '#^\s*/\*(.+)\*/#Us',
560
+
561
+ // eliminate multi-line comments in '/* ... */' form, at end of string
562
+ '#/\*(.+)\*/\s*$#Us'
563
+
564
+ ), '', $str);
565
+
566
+ // eliminate extraneous space
567
+ return trim($str);
568
+ }
569
+
570
+ /**
571
+ * decodes a JSON string into appropriate variable
572
+ *
573
+ * @param string $str JSON-formatted string
574
+ *
575
+ * @return mixed number, boolean, string, array, or object
576
+ * corresponding to given JSON input string.
577
+ * See argument 1 to Services_JSON() above for object-output behavior.
578
+ * Note that decode() always returns strings
579
+ * in ASCII or UTF-8 format!
580
+ * @access public
581
+ */
582
+ function decode($str)
583
+ {
584
+ $str = $this->reduce_string($str);
585
+
586
+ switch (strtolower($str)) {
587
+ case 'true':
588
+ return true;
589
+
590
+ case 'false':
591
+ return false;
592
+
593
+ case 'null':
594
+ return null;
595
+
596
+ default:
597
+ $m = array();
598
+
599
+ if (is_numeric($str)) {
600
+ // Lookie-loo, it's a number
601
+
602
+ // This would work on its own, but I'm trying to be
603
+ // good about returning integers where appropriate:
604
+ // return (float)$str;
605
+
606
+ // Return float or int, as appropriate
607
+ return ((float)$str == (integer)$str)
608
+ ? (integer)$str
609
+ : (float)$str;
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) {
624
+ case $substr_chrs_c_2 == '\b':
625
+ $utf8 .= chr(0x08);
626
+ ++$c;
627
+ break;
628
+ case $substr_chrs_c_2 == '\t':
629
+ $utf8 .= chr(0x09);
630
+ ++$c;
631
+ break;
632
+ case $substr_chrs_c_2 == '\n':
633
+ $utf8 .= chr(0x0A);
634
+ ++$c;
635
+ break;
636
+ case $substr_chrs_c_2 == '\f':
637
+ $utf8 .= chr(0x0C);
638
+ ++$c;
639
+ break;
640
+ case $substr_chrs_c_2 == '\r':
641
+ $utf8 .= chr(0x0D);
642
+ ++$c;
643
+ break;
644
+
645
+ case $substr_chrs_c_2 == '\\"':
646
+ case $substr_chrs_c_2 == '\\\'':
647
+ case $substr_chrs_c_2 == '\\\\':
648
+ case $substr_chrs_c_2 == '\\/':
649
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
650
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
651
+ $utf8 .= $chrs{++$c};
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;
662
+
663
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
664
+ $utf8 .= $chrs{$c};
665
+ break;
666
+
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
+
702
+ }
703
+
704
+ }
705
+
706
+ return $utf8;
707
+
708
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
709
+ // array, or object notation
710
+
711
+ if ($str{0} == '[') {
712
+ $stk = array(SERVICES_JSON_IN_ARR);
713
+ $arr = array();
714
+ } else {
715
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
716
+ $stk = array(SERVICES_JSON_IN_OBJ);
717
+ $obj = array();
718
+ } else {
719
+ $stk = array(SERVICES_JSON_IN_OBJ);
720
+ $obj = new stdClass();
721
+ }
722
+ }
723
+
724
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
725
+ 'where' => 0,
726
+ 'delim' => false));
727
+
728
+ $chrs = $this->substr8($str, 1, -1);
729
+ $chrs = $this->reduce_string($chrs);
730
+
731
+ if ($chrs == '') {
732
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
733
+ return $arr;
734
+
735
+ } else {
736
+ return $obj;
737
+
738
+ }
739
+ }
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
759
+ array_push($arr, $this->decode($slice));
760
+
761
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
762
+ // we are in an object, so figure
763
+ // out the property name and set an
764
+ // element in an associative array,
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;
784
+ } else {
785
+ $obj->$key = $val;
786
+ }
787
+ }
788
+
789
+ }
790
+
791
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
792
+ // found a quote, and we are not inside a string
793
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
794
+ //print("Found start of string at {$c}\n");
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))) {
807
+ // found a left-bracket, and we are in an array, object, or slice
808
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
809
+ //print("Found start of array at {$c}\n");
810
+
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))) {
818
+ // found a left-brace, and we are in an array, object, or slice
819
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
820
+ //print("Found start of object at {$c}\n");
821
+
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))) {
829
+ // found a comment start, and we are in an array, object, or slice
830
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
831
+ $c++;
832
+ //print("Found start of comment at {$c}\n");
833
+
834
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
835
+ // found a comment end, and we're in one now
836
+ array_pop($stk);
837
+ $c++;
838
+
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
+
846
+ }
847
+
848
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
849
+ return $arr;
850
+
851
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
852
+ return $obj;
853
+
854
+ }
855
+
856
+ }
857
+ }
858
+ }
859
+
860
+ /**
861
+ * @todo Ultimately, this should just call PEAR::isError()
862
+ */
863
+ function isError($data, $code = null)
864
+ {
865
+ if (class_exists('pear')) {
866
+ return PEAR::isError($data, $code);
867
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
868
+ is_subclass_of($data, 'services_json_error'))) {
869
+ return true;
870
+ }
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')) {
909
+
910
+ class Services_JSON_Error extends PEAR_Error
911
+ {
912
+ function Services_JSON_Error($message = 'unknown error', $code = null,
913
+ $mode = null, $options = null, $userinfo = null)
914
+ {
915
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
916
+ }
917
+ }
918
+
919
+ } else {
920
+
921
+ /**
922
+ * @todo Ultimately, this class shall be descended from PEAR_Error
923
+ */
924
+ class Services_JSON_Error
925
+ {
926
+ function Services_JSON_Error($message = 'unknown error', $code = null,
927
+ $mode = null, $options = null, $userinfo = null)
928
+ {
929
+
930
+ }
931
+ }
932
+
933
+ }
trunk/models/attachment.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class JSON_API_Attachment {
4
+
5
+ var $id; // Integer
6
+ var $url; // String
7
+ var $slug; // String
8
+ var $title; // String
9
+ var $description; // String
10
+ var $caption; // String
11
+ var $parent; // Integer
12
+ var $mime_type; // String
13
+
14
+ function JSON_API_Attachment($wp_attachment = null) {
15
+ if ($wp_attachment) {
16
+ $this->import_wp_object($wp_attachment);
17
+ if ($this->is_image()) {
18
+ $this->query_images();
19
+ }
20
+ }
21
+ }
22
+
23
+ function import_wp_object($wp_attachment) {
24
+ $this->id = (int) $wp_attachment->ID;
25
+ $this->url = $wp_attachment->guid;
26
+ $this->slug = $wp_attachment->post_name;
27
+ $this->title = $wp_attachment->post_title;
28
+ $this->description = $wp_attachment->post_content;
29
+ $this->caption = $wp_attachment->post_excerpt;
30
+ $this->parent = (int) $wp_attachment->post_parent;
31
+ $this->mime_type = $wp_attachment->post_mime_type;
32
+ }
33
+
34
+ function is_image() {
35
+ return (substr($this->mime_type, 0, 5) == 'image');
36
+ }
37
+
38
+ function query_images() {
39
+ $sizes = array('thumbnail', 'medium', 'large', 'full');
40
+ if (function_exists('get_intermediate_image_sizes')) {
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);
48
+ if (file_exists($filename)) {
49
+ list($measured_width, $measured_height) = getimagesize($filename);
50
+ if ($measured_width == $width &&
51
+ $measured_height == $height) {
52
+ $this->images[$size] = (object) array(
53
+ 'url' => $url,
54
+ 'width' => $width,
55
+ 'height' => $height
56
+ );
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ }
63
+
64
+ ?>
trunk/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
+ ?>
trunk/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
+ ?>
trunk/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.", 403);
64
+ }
65
+
66
+ function comment_on_draft() {
67
+ global $json_api;
68
+ $json_api->error("You cannot comment on unpublished posts.", 403);
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
+ ?>
trunk/models/post.php ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 $type; // String
10
+ var $slug; // String
11
+ var $url; // String
12
+ var $status; // String ("draft", "published", or "pending")
13
+ var $title; // String
14
+ var $title_plain; // String
15
+ var $content; // String (modified by read_more query var)
16
+ var $excerpt; // String
17
+ var $date; // String (modified by date_format query var)
18
+ var $modified; // String (modified by date_format query var)
19
+ var $categories; // Array of objects
20
+ var $tags; // Array of objects
21
+ var $author; // Object
22
+ var $comments; // Array of objects
23
+ var $attachments; // Array of objects
24
+ var $comment_count; // Integer
25
+ var $comment_status; // String ("open" or "closed")
26
+ var $thumbnail; // String
27
+ var $custom_fields; // Object (included by using custom_fields query var)
28
+
29
+ function JSON_API_Post($wp_post = null) {
30
+ if (!empty($wp_post)) {
31
+ $this->import_wp_object($wp_post);
32
+ }
33
+ do_action("json_api_{$this->type}_constructor", $this);
34
+ }
35
+
36
+ function create($values = null) {
37
+ unset($values['id']);
38
+ if (empty($values) || empty($values['title'])) {
39
+ $values = array(
40
+ 'title' => 'Untitled',
41
+ 'content' => ''
42
+ );
43
+ }
44
+ return $this->save($values);
45
+ }
46
+
47
+ function update($values) {
48
+ $values['id'] = $this->id;
49
+ return $this->save($values);
50
+ }
51
+
52
+ function save($values = null) {
53
+ global $json_api, $user_ID;
54
+
55
+ $wp_values = array();
56
+
57
+ if (!empty($values['id'])) {
58
+ $wp_values['ID'] = $values['id'];
59
+ }
60
+
61
+ if (!empty($values['type'])) {
62
+ $wp_values['post_type'] = $values['type'];
63
+ }
64
+
65
+ if (!empty($values['status'])) {
66
+ $wp_values['post_status'] = $values['status'];
67
+ }
68
+
69
+ if (!empty($values['title'])) {
70
+ $wp_values['post_title'] = $values['title'];
71
+ }
72
+
73
+ if (!empty($values['content'])) {
74
+ $wp_values['post_content'] = $values['content'];
75
+ }
76
+
77
+ if (!empty($values['author'])) {
78
+ $author = $json_api->introspector->get_author_by_login($values['author']);
79
+ $wp_values['post_author'] = $author->id;
80
+ }
81
+
82
+ if (isset($values['categories'])) {
83
+ $categories = explode(',', $values['categories']);
84
+ foreach ($categories as $category_slug) {
85
+ $category_slug = trim($category_slug);
86
+ $category = $json_api->introspector->get_category_by_slug($category_slug);
87
+ if (empty($wp_values['post_category'])) {
88
+ $wp_values['post_category'] = array($category->id);
89
+ } else {
90
+ array_push($wp_values['post_category'], $category->id);
91
+ }
92
+ }
93
+ }
94
+
95
+ if (isset($values['tags'])) {
96
+ $tags = explode(',', $values['tags']);
97
+ foreach ($tags as $tag_slug) {
98
+ $tag_slug = trim($tag_slug);
99
+ if (empty($wp_values['tags_input'])) {
100
+ $wp_values['tags_input'] = array($tag_slug);
101
+ } else {
102
+ array_push($wp_values['tags_input'], $tag_slug);
103
+ }
104
+ }
105
+ }
106
+
107
+ if (isset($wp_values['ID'])) {
108
+ $this->id = wp_update_post($wp_values);
109
+ } else {
110
+ $this->id = wp_insert_post($wp_values);
111
+ }
112
+
113
+ if (!empty($_FILES['attachment'])) {
114
+ include_once ABSPATH . '/wp-admin/includes/file.php';
115
+ include_once ABSPATH . '/wp-admin/includes/media.php';
116
+ include_once ABSPATH . '/wp-admin/includes/image.php';
117
+ $attachment_id = media_handle_upload('attachment', $this->id);
118
+ $this->attachments[] = new JSON_API_Attachment($attachment_id);
119
+ unset($_FILES['attachment']);
120
+ }
121
+
122
+ $wp_post = get_post($this->id);
123
+ $this->import_wp_object($wp_post);
124
+
125
+ return $this->id;
126
+ }
127
+
128
+ function import_wp_object($wp_post) {
129
+ global $json_api, $post;
130
+ $date_format = $json_api->query->date_format;
131
+ $this->id = (int) $wp_post->ID;
132
+ setup_postdata($wp_post);
133
+ $this->set_value('type', $wp_post->post_type);
134
+ $this->set_value('slug', $wp_post->post_name);
135
+ $this->set_value('url', get_permalink($this->id));
136
+ $this->set_value('status', $wp_post->post_status);
137
+ $this->set_value('title', get_the_title($this->id));
138
+ $this->set_value('title_plain', strip_tags(@$this->title));
139
+ $this->set_content_value();
140
+ $this->set_value('excerpt', apply_filters('the_excerpt', get_the_excerpt()));
141
+ $this->set_value('date', get_the_time($date_format));
142
+ $this->set_value('modified', date($date_format, strtotime($wp_post->post_modified)));
143
+ $this->set_categories_value();
144
+ $this->set_tags_value();
145
+ $this->set_author_value($wp_post->post_author);
146
+ $this->set_comments_value();
147
+ $this->set_attachments_value();
148
+ $this->set_value('comment_count', (int) $wp_post->comment_count);
149
+ $this->set_value('comment_status', $wp_post->comment_status);
150
+ $this->set_thumbnail_value();
151
+ $this->set_custom_fields_value();
152
+ $this->set_custom_taxonomies($wp_post->post_type);
153
+ do_action("json_api_import_wp_post", $this, $wp_post);
154
+ }
155
+
156
+ function set_value($key, $value) {
157
+ global $json_api;
158
+ if ($json_api->include_value($key)) {
159
+ $this->$key = $value;
160
+ } else {
161
+ unset($this->$key);
162
+ }
163
+ }
164
+
165
+ function set_content_value() {
166
+ global $json_api;
167
+ if ($json_api->include_value('content')) {
168
+ $content = get_the_content($json_api->query->read_more);
169
+ $content = apply_filters('the_content', $content);
170
+ $content = str_replace(']]>', ']]&gt;', $content);
171
+ $this->content = $content;
172
+ } else {
173
+ unset($this->content);
174
+ }
175
+ }
176
+
177
+ function set_categories_value() {
178
+ global $json_api;
179
+ if ($json_api->include_value('categories')) {
180
+ $this->categories = array();
181
+ if ($wp_categories = get_the_category($this->id)) {
182
+ foreach ($wp_categories as $wp_category) {
183
+ $category = new JSON_API_Category($wp_category);
184
+ if ($category->id == 1 && $category->slug == 'uncategorized') {
185
+ // Skip the 'uncategorized' category
186
+ continue;
187
+ }
188
+ $this->categories[] = $category;
189
+ }
190
+ }
191
+ } else {
192
+ unset($this->categories);
193
+ }
194
+ }
195
+
196
+ function set_tags_value() {
197
+ global $json_api;
198
+ if ($json_api->include_value('tags')) {
199
+ $this->tags = array();
200
+ if ($wp_tags = get_the_tags($this->id)) {
201
+ foreach ($wp_tags as $wp_tag) {
202
+ $this->tags[] = new JSON_API_Tag($wp_tag);
203
+ }
204
+ }
205
+ } else {
206
+ unset($this->tags);
207
+ }
208
+ }
209
+
210
+ function set_author_value($author_id) {
211
+ global $json_api;
212
+ if ($json_api->include_value('author')) {
213
+ $this->author = new JSON_API_Author($author_id);
214
+ } else {
215
+ unset($this->author);
216
+ }
217
+ }
218
+
219
+ function set_comments_value() {
220
+ global $json_api;
221
+ if ($json_api->include_value('comments')) {
222
+ $this->comments = $json_api->introspector->get_comments($this->id);
223
+ } else {
224
+ unset($this->comments);
225
+ }
226
+ }
227
+
228
+ function set_attachments_value() {
229
+ global $json_api;
230
+ if ($json_api->include_value('attachments')) {
231
+ $this->attachments = $json_api->introspector->get_attachments($this->id);
232
+ } else {
233
+ unset($this->attachments);
234
+ }
235
+ }
236
+
237
+ function set_thumbnail_value() {
238
+ global $json_api;
239
+ if (!$json_api->include_value('thumbnail') ||
240
+ !function_exists('get_post_thumbnail_id')) {
241
+ unset($this->thumbnail);
242
+ return;
243
+ }
244
+ $attachment_id = get_post_thumbnail_id($this->id);
245
+ if (!$attachment_id) {
246
+ unset($this->thumbnail);
247
+ return;
248
+ }
249
+ $thumbnail_size = $this->get_thumbnail_size();
250
+ $this->thumbnail_size = $thumbnail_size;
251
+ $attachment = $json_api->introspector->get_attachment($attachment_id);
252
+ $image = $attachment->images[$thumbnail_size];
253
+ $this->thumbnail = $image->url;
254
+ $this->thumbnail_images = $attachment->images;
255
+ }
256
+
257
+ function set_custom_fields_value() {
258
+ global $json_api;
259
+ if ($json_api->include_value('custom_fields')) {
260
+ $wp_custom_fields = get_post_custom($this->id);
261
+ $this->custom_fields = new stdClass();
262
+ if ($json_api->query->custom_fields) {
263
+ $keys = explode(',', $json_api->query->custom_fields);
264
+ }
265
+ foreach ($wp_custom_fields as $key => $value) {
266
+ if ($json_api->query->custom_fields) {
267
+ if (in_array($key, $keys)) {
268
+ $this->custom_fields->$key = $wp_custom_fields[$key];
269
+ }
270
+ } else if (substr($key, 0, 1) != '_') {
271
+ $this->custom_fields->$key = $wp_custom_fields[$key];
272
+ }
273
+ }
274
+ } else {
275
+ unset($this->custom_fields);
276
+ }
277
+ }
278
+
279
+ function set_custom_taxonomies($type) {
280
+ global $json_api;
281
+ $taxonomies = get_taxonomies(array(
282
+ 'object_type' => array($type),
283
+ 'public' => true,
284
+ '_builtin' => false
285
+ ), 'objects');
286
+ foreach ($taxonomies as $taxonomy_id => $taxonomy) {
287
+ $taxonomy_key = "taxonomy_$taxonomy_id";
288
+ if (!$json_api->include_value($taxonomy_key)) {
289
+ continue;
290
+ }
291
+ $taxonomy_class = $taxonomy->hierarchical ? 'JSON_API_Category' : 'JSON_API_Tag';
292
+ $terms = get_the_terms($this->id, $taxonomy_id);
293
+ $this->$taxonomy_key = array();
294
+ if (!empty($terms)) {
295
+ $taxonomy_terms = array();
296
+ foreach ($terms as $term) {
297
+ $taxonomy_terms[] = new $taxonomy_class($term);
298
+ }
299
+ $this->$taxonomy_key = $taxonomy_terms;
300
+ }
301
+ }
302
+ }
303
+
304
+ function get_thumbnail_size() {
305
+ global $json_api;
306
+ if ($json_api->query->thumbnail_size) {
307
+ return $json_api->query->thumbnail_size;
308
+ } else if (function_exists('get_intermediate_image_sizes')) {
309
+ $sizes = get_intermediate_image_sizes();
310
+ if (in_array('post-thumbnail', $sizes)) {
311
+ return 'post-thumbnail';
312
+ }
313
+ }
314
+ return 'thumbnail';
315
+ }
316
+
317
+ }
318
+
319
+ ?>
trunk/models/tag.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ $this->post_count = (int) $wp_tag->count;
22
+ }
23
+
24
+ }
25
+
26
+ ?>
trunk/readme.txt ADDED
@@ -0,0 +1,1241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === JSON API ===
2
+ Contributors: dphiffer
3
+ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DH4MEG99JR2WE
4
+ Tags: json, api, ajax, cms, admin, integration, moma
5
+ Requires at least: 2.8
6
+ Tested up to: 4.3
7
+ Stable tag: 1.1.2
8
+
9
+ A RESTful API for WordPress
10
+
11
+ == Description ==
12
+
13
+ JSON API allows you to retrieve and manipulate WordPress content using HTTP requests. There are three main goals:
14
+
15
+ 1. Provide a simple, consistent external interface
16
+ 2. Create a stable, understandable internal implementation
17
+ 3. Enable new types of extensions for WordPress
18
+
19
+ This plugin was created at [The Museum of Modern Art](http://moma.org/) for the weblog [Inside/Out](http://moma.org/explore/inside_out), which is served from 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.
20
+
21
+ See the [Other Notes](http://wordpress.org/extend/plugins/json-api/other_notes/) section for the complete documentation.
22
+
23
+ == Installation ==
24
+
25
+ 1. Upload the `json-api` folder to the `/wp-content/plugins/` directory or install directly through the plugin installer.
26
+ 2. Activate the plugin through the 'Plugins' menu in WordPress or by using the link provided by the plugin installer.
27
+
28
+ == Screenshots ==
29
+
30
+ 1. Our old friend, in JSON format
31
+
32
+ == Documentation ==
33
+
34
+ 1. [General concepts](#1.-General-Concepts)
35
+ 1.1. [Requests](#1.1.-Requests)
36
+ 1.2. [Controllers](#1.2.-Controllers)
37
+ 1.3. [Responses](#1.3.-Responses)
38
+ 2. [Request methods](#2.-Request-methods)
39
+ 2.1. [Core controller methods](#2.1.-Core-controller-methods)
40
+ 2.2. [Posts controller methods](#2.2.-Pages-controller-methods)
41
+ 2.3. [Respond controller methods](#2.3.-Respond-controller-methods)
42
+ 2.4. [Widgets controller methods](#2.4.-Widgets-controller-methods)
43
+ 3. [Request arguments](#3.-Request-arguments)
44
+ 3.1. [Output-modifying arguments](#3.1.-Output-modifying-arguments)
45
+ 3.2. [Content-modifying arguments](#3.2.-Content-modifying-arguments)
46
+ 3.3. [Using include/exclude and redirects](#3.3.-Using-include/exclude-and-redirects)
47
+ 4. [Response objects](#4.-Response-objects)
48
+ 4.1. [Post response object](#4.1.-Post-response-object)
49
+ 4.2. [Category response object](#4.2.-Category-response-object)
50
+ 4.3. [Tag response object](#4.3.-Tag-response-object)
51
+ 4.4. [Author response object](#4.4.-Author-response-object)
52
+ 4.5. [Comment response object](#4.5.-Comment-response-object)
53
+ 4.6. [Attachment response object](#4.6.-Attachment-response-object)
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
+
64
+ == 1.1. Requests ==
65
+
66
+ 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.
67
+
68
+ JSON API operates in two modes:
69
+
70
+ 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.
71
+ 2. *Explicit mode* is triggered by setting `json` to a known method string. See *Section 2: Request methods* for a complete method listing.
72
+
73
+ = Implicit mode examples: =
74
+
75
+ * `http://www.example.org/?json=1`
76
+ * `http://www.example.org/?p=47&json=1`
77
+ * `http://www.example.org/tag/banana/?json=1`
78
+
79
+ = Explicit mode examples: =
80
+
81
+ * `http://www.example.org/?json=get_recent_posts`
82
+ * `http://www.example.org/?json=get_post&post_id=47`
83
+ * `http://www.example.org/?json=get_tag_posts&tag_slug=banana`
84
+
85
+ = With user-friendly permalinks configured: =
86
+
87
+ * `http://www.example.org/api/get_recent_posts/`
88
+ * `http://www.example.org/api/get_post/?post_id=47`
89
+ * `http://www.example.org/api/get_tag_posts/?tag_slug=banana`
90
+
91
+ __Further reading__
92
+ See *Section 3: Request arguments* for more information about request arguments to modify the response.
93
+
94
+ == 1.2. Controllers ==
95
+
96
+ The 1.0 release of JSON API introduced a modular controller system. This allows developers to flexibly add features to the API and give users more control over which methods they have enabled.
97
+
98
+ = The Core controller =
99
+
100
+ Most of the methods available prior to version 1.0 have been moved to the Core controller. The two exceptions are `submit_comment` and `create_post` which are now available from the Respond and Posts controllers, respectively. The Core controller is the only one enabled by default. All other functionality must be enabled from the JSON API Settings page (under Settings in the WordPress admin menu).
101
+
102
+ = Specifying a controller =
103
+
104
+ There are a few ways of specifying a controller, depending on how you are calling the API:
105
+
106
+ * `http://www.example.org/?json=get_recent_posts` (`core` controller is implied, method is `get_recent_posts`)
107
+ * `http://www.example.org/api/info/` (`core` controller is implied)
108
+ * `http://www.example.org/api/core/get_category_posts/` (`core` controller can also be explicitly specified)
109
+ * `http://www.example.org/?json=respond.submit_comment` (`respond` controller, `submit_comment` method)
110
+
111
+ __Legacy compatibility__
112
+ JSON API retains support for its pre-1.0 methods. For example, if you invoke the method `create_post` without a controller specified, the Posts controller is chosen instead of Core.
113
+
114
+ = Available controllers =
115
+
116
+ The current release includes three controllers: Core, Posts, and Respond. Developers are encouraged to suggest or submit additional controllers.
117
+
118
+ __Further reading__
119
+ See *Section 2: Request methods* for a complete reference of available controllers and methods. For documentation on extending JSON API with new controllers see *Section 5.2: Developing JSON API controllers*.
120
+
121
+ == 1.3. Responses ==
122
+
123
+ The standard response format for JSON API is (as you may have guessed) [JSON](http://json.org/).
124
+
125
+ Here is an example response from `http://localhost/wordpress/?json=1` called on a default WordPress installation (formatted for readability):
126
+
127
+ {
128
+ "status": "ok",
129
+ "count": 1,
130
+ "count_total": 1,
131
+ "pages": 1,
132
+ "posts": [
133
+ {
134
+ "id": 1,
135
+ "type": "post",
136
+ "slug": "hello-world",
137
+ "url": "http:\/\/localhost\/wordpress\/?p=1",
138
+ "title": "Hello world!",
139
+ "title_plain": "Hello world!",
140
+ "content": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!<\/p>\n",
141
+ "excerpt": "Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!\n",
142
+ "date": "2009-11-11 12:50:19",
143
+ "modified": "2009-11-11 12:50:19",
144
+ "categories": [],
145
+ "tags": [],
146
+ "author": {
147
+ "id": 1,
148
+ "slug": "admin",
149
+ "name": "admin",
150
+ "first_name": "",
151
+ "last_name": "",
152
+ "nickname": "",
153
+ "url": "",
154
+ "description": ""
155
+ },
156
+ "comments": [
157
+ {
158
+ "id": 1,
159
+ "name": "Mr WordPress",
160
+ "url": "http:\/\/wordpress.org\/",
161
+ "date": "2009-11-11 12:50:19",
162
+ "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",
163
+ "parent": 0
164
+ }
165
+ ],
166
+ "comment_count": 1,
167
+ "comment_status": "open"
168
+ }
169
+ ]
170
+ }
171
+
172
+ == 2. Request methods ==
173
+
174
+ Request methods are available from the following controllers:
175
+
176
+ * Core controller - basic introspection methods
177
+ * Posts controller - data manipulation methods for posts
178
+ * Respond controller - comment/trackback submission methods
179
+ * Widgets controller - retrieve sidebar widgets
180
+
181
+ == 2.1. Core controller methods ==
182
+
183
+ The Core controller offers a mostly-complete set of introspection methods for retrieving content from WordPress.
184
+
185
+
186
+ == Method: info ==
187
+
188
+ Returns information about JSON API.
189
+
190
+ = Optional arguments =
191
+
192
+ * `controller` - returns detailed information about a specific controller
193
+
194
+ = Response =
195
+
196
+ {
197
+ "status": "ok",
198
+ "json_api_version": "1.0",
199
+ "controllers": [
200
+ "core"
201
+ ]
202
+ }
203
+
204
+
205
+ = Response with “controller=core” =
206
+
207
+ {
208
+ "status": "ok",
209
+ "name": "Core",
210
+ "description": "Basic introspection methods",
211
+ "methods": [
212
+ ...
213
+ ]
214
+ }
215
+
216
+
217
+ == Method: get_recent_posts ==
218
+
219
+ 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`.
220
+
221
+ = Optional arguments =
222
+
223
+ * `count` - determines how many posts per page are returned (default value is 10)
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",
231
+ "count": 10,
232
+ "count_total": 79,
233
+ "pages": 7,
234
+ "posts": [
235
+ { ... },
236
+ { ... },
237
+ ...
238
+ ]
239
+ }
240
+
241
+
242
+ == Method: get_posts ==
243
+
244
+ Returns posts according to WordPress's [`WP_Query` parameters](http://codex.wordpress.org/Class_Reference/WP_Query#Parameters). The one default parameter is `ignore_sticky_posts=1` (this can be overridden).
245
+
246
+ = Optional arguments =
247
+
248
+ * `count` - determines how many posts per page are returned (default value is 10)
249
+ * `page` - return a specific page number from the results
250
+ * `post_type` - used to retrieve custom post types
251
+
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",
259
+ "count": 1,
260
+ "posts": [
261
+ { ... }
262
+ ]
263
+ }
264
+
265
+
266
+ == Method: get_post ==
267
+
268
+ Returns a single post object.
269
+
270
+ = One of the following is required =
271
+
272
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on a post URL
273
+ * `id` or `post_id` - set to the post's ID
274
+ * `slug` or `post_slug` - set to the post's URL slug
275
+
276
+ = Optional arguments =
277
+
278
+ * `post_type` - used to retrieve custom post types
279
+
280
+ = Response =
281
+
282
+ {
283
+ "status": "ok",
284
+ "post": { ... }
285
+ }
286
+
287
+
288
+ == Method: get_page ==
289
+
290
+ Returns a single page object.
291
+
292
+ = One of the following is required =
293
+
294
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on a page URL
295
+ * `id` or `page_id` - set to the page's ID
296
+ * `slug` or `page_slug` - set to the page's URL slug
297
+
298
+ = Optional arguments =
299
+
300
+ * `children` - set to a non-empty value to include a recursive hierarchy of child pages
301
+ * `post_type` - used to retrieve custom post types
302
+
303
+ = Response =
304
+
305
+ {
306
+ "status": "ok",
307
+ "page": { ... }
308
+ }
309
+
310
+ == Method: get_date_posts ==
311
+
312
+ Returns an array of posts/pages in a specific date archive (by day, month, or year).
313
+
314
+ = One of the following is required =
315
+
316
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on a date archive page
317
+ * `date` - set to a date in the format `YYYY` or `YYYY-MM` or `YYYY-MM-DD` (non-numeric characters are stripped from the var, so `YYYYMMDD` or `YYYY/MM/DD` are also valid)
318
+
319
+ = Optional arguments =
320
+
321
+ * `count` - determines how many posts per page are returned (default value is 10)
322
+ * `page` - return a specific page number from the results
323
+ * `post_type` - used to retrieve custom post types
324
+
325
+ = Response =
326
+
327
+ {
328
+ "status": "ok",
329
+ "count": 10,
330
+ "count_total": 79,
331
+ "pages": 7,
332
+ "posts": [
333
+ { ... },
334
+ { ... },
335
+ ...
336
+ ]
337
+ }
338
+
339
+ == Method: get_category_posts ==
340
+
341
+ Returns an array of posts/pages in a specific category.
342
+
343
+ = One of the following is required =
344
+
345
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on a category archive page
346
+ * `id` or `category_id` - set to the category's ID
347
+ * `slug` or `category_slug` - set to the category's URL slug
348
+
349
+ = Optional arguments =
350
+
351
+ * `count` - determines how many posts per page are returned (default value is 10)
352
+ * `page` - return a specific page number from the results
353
+ * `post_type` - used to retrieve custom post types
354
+
355
+ = Response =
356
+
357
+ {
358
+ "status": "ok",
359
+ "count": 10,
360
+ "count_total": 79,
361
+ "pages": 7,
362
+ "category": { ... }
363
+ "posts": [
364
+ { ... },
365
+ { ... },
366
+ ...
367
+ ]
368
+ }
369
+
370
+
371
+ == Method: get_tag_posts ==
372
+
373
+ Returns an array of posts/pages with a specific tag.
374
+
375
+ = One of the following is required =
376
+
377
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on a tag archive page
378
+ * `id` or `tag_id` - set to the tag's ID
379
+ * `slug` or `tag_slug` - set to the tag's URL slug
380
+
381
+ = Optional arguments =
382
+
383
+ * `count` - determines how many posts per page are returned (default value is 10)
384
+ * `page` - return a specific page number from the results
385
+ * `post_type` - used to retrieve custom post types
386
+
387
+ = Response =
388
+
389
+ {
390
+ "status": "ok",
391
+ "count": 10,
392
+ "count_total": 79,
393
+ "pages": 7,
394
+ "tag": { ... }
395
+ "posts": [
396
+ { ... },
397
+ { ... },
398
+ ...
399
+ ]
400
+ }
401
+
402
+
403
+ == Method: get_author_posts ==
404
+
405
+ Returns an array of posts/pages written by a specific author.
406
+
407
+ = One of the following is required =
408
+
409
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on an author archive page
410
+ * `id` or `author_id` - set to the author's ID
411
+ * `slug` or `author_slug` - set to the author's URL slug
412
+
413
+ = Optional arguments =
414
+
415
+ * `count` - determines how many posts per page are returned (default value is 10)
416
+ * `page` - return a specific page number from the results
417
+ * `post_type` - used to retrieve custom post types
418
+
419
+ = Response =
420
+
421
+ {
422
+ "status": "ok",
423
+ "count": 10,
424
+ "count_total": 79,
425
+ "pages": 7,
426
+ "author": { ... }
427
+ "posts": [
428
+ { ... },
429
+ { ... },
430
+ ...
431
+ ]
432
+ }
433
+
434
+
435
+ == Method: get_search_results ==
436
+
437
+ Returns an array of posts/pages in response to a search query.
438
+
439
+ = One of the following is required =
440
+
441
+ * Invoking the JSON API implicitly (i.e., `?json=1`) on a search results page
442
+ * `search` - set to the desired search query
443
+
444
+ = Optional arguments =
445
+
446
+ * `count` - determines how many posts per page are returned (default value is 10)
447
+ * `page` - return a specific page number from the results
448
+ * `post_type` - used to retrieve custom post types
449
+
450
+ = Response =
451
+
452
+ {
453
+ "status": "ok",
454
+ "count": 10,
455
+ "count_total": 79,
456
+ "pages": 7,
457
+ "posts": [
458
+ { ... },
459
+ { ... },
460
+ ...
461
+ ]
462
+ }
463
+
464
+
465
+ == Method: get_date_index ==
466
+
467
+ Returns both an array of date page permalinks and a tree structure representation of the archive.
468
+
469
+ = Response =
470
+
471
+ {
472
+ "status": "ok",
473
+ "permalinks": [
474
+ "...",
475
+ "...",
476
+ "..."
477
+ ],
478
+ "tree": {
479
+ "2009": {
480
+ "09": 17,
481
+ "10": 20,
482
+ "11": 7
483
+ }
484
+ }
485
+
486
+ Note: the tree is arranged by `response.tree.[year].[month].[number of posts]`.
487
+
488
+
489
+ == Method: get_category_index ==
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
+ {
500
+ "status": "ok",
501
+ "count": 3,
502
+ "categories": [
503
+ { ... },
504
+ { ... },
505
+ { ... }
506
+ ]
507
+ }
508
+
509
+
510
+ == Method: get_tag_index ==
511
+
512
+ Returns an array of active tags.
513
+
514
+ = Response =
515
+
516
+ {
517
+ "status": "ok",
518
+ "count": 3
519
+ "tags": [
520
+ { ... },
521
+ { ... },
522
+ { ... }
523
+ ]
524
+ }
525
+
526
+
527
+ == Method: get_author_index ==
528
+
529
+ Returns an array of active blog authors.
530
+
531
+ = Response =
532
+
533
+ {
534
+ "status": "ok",
535
+ "count": 3,
536
+ "authors": [
537
+ { ... },
538
+ { ... },
539
+ { ... }
540
+ ]
541
+ }
542
+
543
+
544
+ == Method: get_page_index ==
545
+
546
+ Returns a hierarchical tree of `page` posts.
547
+
548
+ = Response =
549
+
550
+ {
551
+ "status": "ok",
552
+ "pages": [
553
+ { ... },
554
+ { ... },
555
+ { ... }
556
+ ]
557
+ }
558
+
559
+ == Method: get_nonce ==
560
+
561
+ Returns a WordPress nonce value, required to call some data manipulation methods.
562
+
563
+ = Required arguments =
564
+
565
+ * `controller` - the JSON API controller for the method you will use the nonce for
566
+ * `method` - the method you wish to call (currently `create_post` is the only method that requires a nonce)
567
+
568
+ = Response =
569
+
570
+ {
571
+ "status": "ok",
572
+ "controller": "posts",
573
+ "method": "create_post",
574
+ "nonce": "cefe01efd4"
575
+ }
576
+
577
+ __Further reading__
578
+ To learn more about how nonces are used in WordPress, see [Mark Jaquith's article on the subject](http://markjaquith.wordpress.com/2006/06/02/wordpress-203-nonces/).
579
+
580
+ == 2.2. Pages controller methods ==
581
+
582
+ == Method: create_post ==
583
+
584
+ Creates a new post.
585
+
586
+ = Required argument =
587
+
588
+ * `nonce` - available from the `get_nonce` method (call with vars `controller=posts` and `method=create_post`)
589
+
590
+ = Optional arguments =
591
+
592
+ * `status` - sets the post status ("draft" or "publish"), default is "draft"
593
+ * `title` - the post title
594
+ * `content` - the post content
595
+ * `author` - the post's author (login name), default is the current logged in user
596
+ * `categories` - a comma-separated list of categories (URL slugs)
597
+ * `tags` - a comma-separated list of tags (URL slugs)
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
+
641
+ == Method: submit_comment ==
642
+
643
+ Submits a comment to a WordPress post.
644
+
645
+ = Required arguments =
646
+
647
+ * `post_id` - which post to comment on
648
+ * `name` - the commenter's name
649
+ * `email` - the commenter's email address
650
+ * `content` - the comment content
651
+
652
+ = Optional arguments =
653
+
654
+ * `redirect` - redirect instead of returning a JSON object
655
+ * `redirect_ok` - redirect to a specific URL when the status value is `ok`
656
+ * `redirect_error` - redirect to a specific URL when the status value is `error`
657
+ * `redirect_pending` - redirect to a specific URL when the status value is `pending`
658
+
659
+ = Custom status values =
660
+
661
+ * `pending` - assigned if the comment submission is pending moderation
662
+
663
+ == 2.4. Widgets controller methods ==
664
+
665
+ == Method: get_sidebar ==
666
+
667
+ Retrieves widgets assigned to a sidebar.
668
+
669
+ = Required arguments =
670
+
671
+ * `sidebar_id` - the name or number of the sidebar to retrieve
672
+
673
+
674
+ == 3. Request arguments ==
675
+
676
+ API requests can be controlled by specifying one of the following arguments as URL query vars.
677
+
678
+ = Examples =
679
+
680
+ * Debug the response: `http://www.example.org/api/get_page_index/?dev=1`
681
+ * Widget-style JSONP output: `http://www.example.org/api/get_recent_posts/?callback=show_posts_widget&read_more=More&count=3`
682
+ * Redirect on error: `http://www.example.org/api/posts/create_post/?callback_error=http%3A%2F%2Fwww.example.org%2Fhelp.html`
683
+
684
+ == 3.1. Output-modifying arguments ==
685
+
686
+ 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.
687
+
688
+ * Setting `callback` to a JavaScript function name will trigger a JSONP-style callback.
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
+ * Errors are suppressed unless `dev` is set to a non-empty value
693
+ * 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) (Note: this option is only recognized in PHP version 5.3+)
694
+ * Setting `json_unescaped_unicode` will replace unicode-escaped characters with their unescaped equivalents (e.g., `\u00e1` becomes á)
695
+ * Omitting all of the above arguments will result in a standard JSON response.
696
+
697
+ == 3.2. Content-modifying arguments ==
698
+
699
+ These arguments are available to modify all introspection methods:
700
+
701
+ * `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`.
702
+ * `read_more` - Changes the 'read more' link text in post content.
703
+ * `include` - Specifies which post data fields to include. Expects a comma-separated list of post fields. Leaving this empty includes *all* fields.
704
+ * `exclude` - Specifies which post data fields to exclude. Expects a comma-separated list of post fields.
705
+ * `custom_fields` - Includes values from posts' Custom Fields. Expects a comma-separated list of custom field keys.
706
+ * `author_meta` - Includes additional author metadata. Should be a comma-separated list of metadata fields.
707
+ * `count` - Controls the number of posts to include (defaults to the number specified by WordPress)
708
+ * `order` - Controls the order of post results ('DESC' or 'ASC'). Default value is 'DESC'.
709
+ * `order_by` - Controls which field to order results by. Expects one of the following values:
710
+ * `author`
711
+ * `date` (default value)
712
+ * `title`
713
+ * `modified`
714
+ * `menu_order` (only works with Pages)
715
+ * `parent`
716
+ * `ID`
717
+ * `rand`
718
+ * `meta_value` (`meta_key` must also be set)
719
+ * `none`
720
+ * `comment_count`
721
+ * `meta_key`, `meta_value`, `meta_compare` - Retrieve posts (or Pages) based on a custom field key or value.
722
+
723
+ == 3.3. Using include/exclude and redirects ==
724
+
725
+ __About `include`/`exclude` arguments__
726
+ 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.
727
+
728
+ __About the `redirect` argument__
729
+ 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.
730
+
731
+ 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:
732
+
733
+ * `http://www.example.com/foo?status=ok`
734
+ * `http://www.example.com/foo?status=error`
735
+
736
+ 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.
737
+
738
+
739
+ == 4. Response objects ==
740
+
741
+ This section describes data objects you can retrieve from WordPress and the optional URL redirects.
742
+
743
+ __Status values__
744
+ 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.
745
+
746
+ __Naming compatibility__
747
+ 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.
748
+
749
+ == 4.1. Post response object ==
750
+
751
+ * `id` - Integer
752
+ * `type` - String (e.g., `post` or `page`)
753
+ * `slug` - String
754
+ * `url` - String
755
+ * `title` - String
756
+ * `title_plain` - String
757
+ * `content` - String (modified by the `read_more` argument)
758
+ * `excerpt` - String
759
+ * `date` - String (modified by the `date_format` argument)
760
+ * `modified` - String (modified by the `date_format` argument)
761
+ * `categories` - Array of category objects
762
+ * `tags` - Array of tag objects
763
+ * `author` Author object
764
+ * `comments` - Array of comment objects
765
+ * `attachments` - Array of attachment objects
766
+ * `comment_count` - Integer
767
+ * `comment_status` - String (`"open"` or `"closed"`)
768
+ * `thumbnail` - String (only included if a post thumbnail has been specified)
769
+ * `custom_fields` - Object (included by setting the `custom_fields` argument to a comma-separated list of custom field names)
770
+ * `taxonomy_(taxonomy)` - Array of custom taxonomy objects (these resemble Category or Tag response objects, depending on whether the taxonomy is hierarchical)
771
+
772
+ __Note__
773
+ The `thumbnail` attribute returns a URL to the image size specified by the optional `thumbnail_size` request argument. By default this will use the `thumbnail` or `post-thumbnail` sizes, depending on your version of WordPress. See [Mark Jaquith's post on the topic](http://markjaquith.wordpress.com/2009/12/23/new-in-wordpress-2-9-post-thumbnail-images/) for more information.
774
+
775
+ == 4.2. Category response object ==
776
+
777
+ * `id` - Integer
778
+ * `slug` - String
779
+ * `title` - String
780
+ * `description` - String
781
+ * `parent` - Integer
782
+ * `post_count` - Integer
783
+
784
+ == 4.3. Tag response object ==
785
+
786
+ * `id` - Integer
787
+ * `slug` - String
788
+ * `title` - String
789
+ * `description` - String
790
+ * `post_count` - Integer
791
+
792
+ == 4.4. Author response object ==
793
+
794
+ * `id` - Integer
795
+ * `slug` - String
796
+ * `name` - String
797
+ * `first_name` - String
798
+ * `last_name` - String
799
+ * `nickname` - String
800
+ * `url` - String
801
+ * `description` - String
802
+
803
+ Note: You can include additional values by setting the `author_meta` argument to a comma-separated list of metadata fields.
804
+
805
+ == 4.5. Comment response object ==
806
+
807
+ * `id` - Integer
808
+ * `name` - String
809
+ * `url` - String
810
+ * `date` - String
811
+ * `content` - String
812
+ * `parent` - Integer
813
+ * `author` - Object (only set if the comment author was registered & logged in)
814
+
815
+ == 4.6. Attachment response object ==
816
+
817
+ * `id` - Integer
818
+ * `url` - String
819
+ * `slug` - String
820
+ * `title` - String
821
+ * `description` - String
822
+ * `caption` - String
823
+ * `parent` - Integer
824
+ * `mime_type` - String
825
+ * `images` - Object with values including `thumbnail`, `medium`, `large`, `full`, each of which are objects with values `url`, `width` and `height` (only set if the attachment is an image)
826
+
827
+
828
+ == 5. Extending JSON API ==
829
+
830
+ JSON API exposes several WordPress action and filter hooks as well as a modular controller system for adding new API methods.
831
+
832
+ == 5.1. Plugin hooks ==
833
+
834
+ JSON API exposes several [action and filter hooks](http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters) to augment its behavior.
835
+
836
+ == Filter: json_api_controllers ==
837
+
838
+ This filter controls the array of controllers available to JSON API. The callback function is passed a single argument, an array of strings.
839
+
840
+ = Example =
841
+
842
+ // Add a custom controller
843
+ add_filter('json_api_controllers', 'add_my_controller');
844
+
845
+ function add_my_controller($controllers) {
846
+ // Corresponds to the class JSON_API_MyController_Controller
847
+ $controllers[] = 'MyController';
848
+ return $controllers;
849
+ }
850
+
851
+
852
+ == Filter: json_api_[controller]_controller_path ==
853
+
854
+ Specifies the PHP source file for a given controller, overriding the default location `wp-content/plugins/json_api/controllers`.
855
+
856
+ __Note__
857
+ If you your controller file in the `json-api/controllers` folder JSON API will find it automatically.
858
+
859
+ = Example =
860
+
861
+ // Register the source file for JSON_API_Widgets_Controller
862
+ add_filter('json_api_widgets_controller_path', 'widgets_controller_path');
863
+
864
+ function widgets_controller_path($default_path) {
865
+ return '/path/to/widgets.php';
866
+ }
867
+
868
+ __Capitalization__
869
+ Your filter hook must be all-lowercase to work correctly. The above example would fail with the filter `json_api_Widgets_Controller_path`, even if that's how the class is capitalized in the PHP source.
870
+
871
+ == Filter: json_api_encode ==
872
+
873
+ 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.
874
+
875
+ = Example =
876
+
877
+ add_filter('json_api_encode', 'my_encode_kittens');
878
+
879
+ function my_encode_kittens($response) {
880
+ if (isset($response['posts'])) {
881
+ foreach ($response['posts'] as $post) {
882
+ my_add_kittens($post); // Add kittens to each post
883
+ }
884
+ } else if (isset($response['post'])) {
885
+ my_add_kittens($response['post']); // Add a kittens property
886
+ }
887
+ return $response;
888
+ }
889
+
890
+ function my_add_kittens(&$post) {
891
+ $post->kittens = 'Kittens!';
892
+ }
893
+
894
+ == Action: json_api-[controller]-[method] ==
895
+
896
+ Each JSON API method invokes an action when called.
897
+
898
+ = Example =
899
+
900
+ // Disable get_author_index method (e.g., for security reasons)
901
+ add_action('json_api-core-get_author_index', 'my_disable_author_index');
902
+
903
+ function my_disable_author_index() {
904
+ // Stop execution
905
+ exit;
906
+ }
907
+
908
+ == 5.2. Developing JSON API controllers ==
909
+
910
+ = Creating a controller =
911
+
912
+ To start a new JSON API controller, create a file called `hello.php` inside `wp-content/plugins/json-api/controllers`. Add the following class definition:
913
+
914
+ <?php
915
+
916
+ class JSON_API_Hello_Controller {
917
+
918
+ public function hello_world() {
919
+ return array(
920
+ "message" => "Hello, world"
921
+ );
922
+ }
923
+
924
+ }
925
+
926
+ ?>
927
+
928
+ Your controller is now available as `hello`, and exposes one `hello_world` method.
929
+
930
+ Next, activate your controller from the WordPress admin interface, available from the menu under Settings > JSON API. You can either click on the link to your `hello_world` method from the admin interface or enter it manually. It should have the form: `http://www.example.org/api/hello/hello_world/?dev=1` or `http://www.example.org/?json=hello.hello_world&dev=1` (note the use of the `dev` argument to enable human-readable output). You should get the following output:
931
+
932
+ {
933
+ "status": "ok",
934
+ "message": "Hello, world"
935
+ }
936
+
937
+ = Using query vars =
938
+
939
+ To customize the behavior of your controller, you will want to make use of the global `$json_api->query` object. Add the following method to your controller:
940
+
941
+ public function hello_person() {
942
+ global $json_api;
943
+ $name = $json_api->query->name;
944
+ return array(
945
+ "message" => "Hello, $name."
946
+ );
947
+ }
948
+
949
+ Now append the `name` query var to the method call: `http://www.example.org/api/hello/hello_world/?dev=1&name=Alice` or `http://www.example.org/?json=hello.hello_world&dev=1&name=Alice`.
950
+
951
+ {
952
+ "status": "ok",
953
+ "message": "Hello, Alice"
954
+ }
955
+
956
+ = Introspector and data models =
957
+
958
+ Your controller can use any of the [existing WordPress functions](http://codex.wordpress.org/Function_Reference) to collect data, but JSON API also includes an introspector that wraps data in objects defined in the `json-api/models` directory. These are the same data models described in *Section 4: Response objects*.
959
+
960
+ Here is an example of how you might use the introspector:
961
+
962
+ // Retrieve posts based on custom field key/value pair
963
+ public function get_custom_posts() {
964
+ global $json_api;
965
+
966
+ // Make sure we have key/value query vars
967
+ if (!$json_api->query->key || !$json_api->query->value) {
968
+ $json_api->error("Include a 'key' and 'value' query var.");
969
+ }
970
+
971
+ // See also: http://codex.wordpress.org/Template_Tags/query_posts
972
+ $posts = $json_api->introspector->get_posts(array(
973
+ 'meta_key' => $json_api->query->key,
974
+ 'meta_value' => $json_api->query->value
975
+ ));
976
+
977
+ return array(
978
+ 'key' => $key,
979
+ 'value' => $value,
980
+ 'posts' => $posts
981
+ );
982
+ }
983
+
984
+ = External controllers =
985
+
986
+ It is recommended that custom controllers are kept outside of `json-api/controllers` in order to avoid accidental deletion during upgrades or site migrations. To make your controller visible from an external plugin or theme directory you will need to use two filters: `json_api_controllers` and `json_api_[controller]_controller_path`. Move the `hello.php` file from the steps above into your theme's directory. Then add the following to your theme's `functions.php` file (if your theme doesn't have a file called `functions.php` you can create one).
987
+
988
+ function add_hello_controller($controllers) {
989
+ $controllers[] = 'hello';
990
+ return $controllers;
991
+ }
992
+ add_filter('json_api_controllers', 'add_hello_controller');
993
+
994
+ function set_hello_controller_path() {
995
+ return "/path/to/theme/hello.php";
996
+ }
997
+ add_filter('json_api_hello_controller_path', 'set_hello_controller_path');
998
+
999
+ == 5.3. Configuration options ==
1000
+
1001
+ The following are constants you can define in your `wp-config.php` folder:
1002
+
1003
+ * `JSON_API_DIR` - set to the directory where JSON API plugin lives (in some cases this can be useful for `mu-plugins` with WordPress MU)
1004
+ * `JSON_API_CONTROLLERS` - a comma-separated list of default controllers to enable (this is overridden by the JSON API settings page)
1005
+
1006
+ == 6. Unit tests ==
1007
+
1008
+ JSON API comes with a set of tests that should make it easier to maintain and reveal incompatibilities when they might occur. This is an ongoing process, I hope to improve the test coverage going forward.
1009
+
1010
+ == 6.1. Preparing a WordPress test site ==
1011
+
1012
+ There are a few necessary steps that need to be carried out before the test suite will run properly.
1013
+
1014
+ 1. WordPress should generate a new set of tables before you start, so if you're testing with a `wp_` table prefix make sure the database has no existing tables of this kind
1015
+ 2. Configure and install a new copy of WordPress
1016
+ 3. Delete the Hello World post and Sample Page (titled "About" in some versions of WordPress)
1017
+ 4. Enable user-friendly URLs from Settings > Permalinks, use the "Day and name" format
1018
+ 5. Install + Activate the JSON API plugin and enable all bundled controllers from Settings > JSON API
1019
+ 6. Import the [Theme Unit Test](http://codex.wordpress.org/Theme_Unit_Test) test data XML file from Settings > Import > WordPress (you will need to install the WordPress Importer plugin)
1020
+
1021
+ == 6.2. Running the tests ==
1022
+
1023
+ From the command line, make sure you have the HTTP_Client PEAR package installed:
1024
+
1025
+ `pear install HTTP_Client`
1026
+
1027
+ Change directory to `tests` and run the following:
1028
+
1029
+ `pear run-tests`
1030
+
1031
+ You should see the test results print out culminating in a summary:
1032
+
1033
+ TOTAL TIME: 00:04
1034
+ 23 PASSED TESTS
1035
+ 0 SKIPPED TESTS
1036
+
1037
+ == Changelog ==
1038
+
1039
+ = 1.1.2 (2015-08-20): =
1040
+ * Don't always respond with HTTP 200
1041
+
1042
+ = 1.1.1 (2013-06-23): =
1043
+ * Added support for custom taxonomies
1044
+ * Errors are now suppressed unless you include a non-empty `dev` argument
1045
+
1046
+ = 1.1.0 (2013-06-22): =
1047
+ * Bugfix for `json_encode` compatibility with PHP < 5.3
1048
+ * Bugfix for `get_author_index` warnings in WordPress > 3.5
1049
+
1050
+ = 1.0.9 (2013-06-21): =
1051
+ * Added `update_post` and `delete_post` methods to Post controller
1052
+ * Added two JSON encoding arguments: `json_encode_options` and `json_unescaped_unicode`
1053
+ * Added a `parent` argument to `get_category_index`
1054
+ * Fixed a couple places where the code was generating PHP notifications
1055
+ * Updated bundled Services_JSON library (only used if `json_encode` is unavailable)
1056
+
1057
+ = 1.0.8 (2013-06-12): =
1058
+ * Added `widgets` controller
1059
+ * Added a generic `get_posts` method to the core controller
1060
+ * Added a `thumbnail_images` object property to complement `thumbnail` URL
1061
+ * Attachment image files are now checked to exist and match the expected width/height
1062
+ * Fixed a bug where `the_excerpt` filter wasn't being applied to the `excerpt` property
1063
+ * Fixed a bug where the number of child pages was being limited to 5
1064
+ * Fixed a bug where custom controller class names couldn't include numerics
1065
+ * Theme directory check for custom controllers
1066
+
1067
+ = 1.0.7 (2011-01-27): =
1068
+ * Created some basic unit tests
1069
+ * Fixed a bug where `get_author_posts` was unable to find users by `slug`
1070
+ * Added missing `post_type` argument to documentation for `get_post` and `get_page` (props Koshirosan)
1071
+ * Added `previous_url` and `next_url` properties to the `get_post` response object (props mlcy44)
1072
+
1073
+ = 1.0.6 (2011-01-13): =
1074
+ * Fixed a bug in `exclude` query parameter (big props to ikesyo and archon810)
1075
+ * Fix for `get_page_index` that where it only returned 5 pages -- it now responds to `count` query param (props to npavkovic and blinder)
1076
+ * Removed `Content-Disposition` header from response (props mimecine, kjwierenga)
1077
+ * Fixed an incompatibility issue with Disqus plugin (props joshcanhelp)
1078
+ * Fixed a bug where `submit_comment` was resulting in a HTTP 404 status (props @tdweston)
1079
+ * Fixed an error in the documentation, external controller example (props jli)
1080
+
1081
+ = 1.0.5 (2010-07-08): =
1082
+ * Added an check so that `json-api.php` can be moved one level above the `json-api` directory
1083
+ * Added more documentation about using nonces
1084
+
1085
+ = 1.0.4 (2010-07-07): =
1086
+ * Fixed a bug where the order of attachments didn't match the gallery
1087
+ * Added a section to the developer documentation for externalizing custom controllers
1088
+ * Moved JSON_API class to its own file: `singletons/api.php`
1089
+ * Created a new top-level function: `json_api_dir()`
1090
+ * Improvements for WordPress MU: `JSON_API_DIR` and `JSON_API_CONTROLLERS` constants (props Jim McQuillan)
1091
+
1092
+ = 1.0.3 (2010-07-07): =
1093
+ * Added request argument `thumbnail_size` to support different sizes of featured images (see also: `add_image_size` WordPress function)
1094
+ * Added request argument `post_type` to support custom post types (props Mark Harris)
1095
+
1096
+ = 1.0.2 (2010-07-02): =
1097
+ * Removed an inaccurate section from readme.txt about supporting `query_posts` arguments
1098
+ * Changed controller info block format to use "Controller name" and "Controller description"
1099
+ * Made admin page more robust about handling errors loading controllers
1100
+ * Changed `JSON_API::get_controllers` method to lowercase all entries
1101
+ * Added introspector section to developer documentation
1102
+ * Fixed incorrect example for `json_api_[controller]_controller_path`
1103
+ * Thanks to Tim Nash for early feedback on writing external controllers
1104
+
1105
+ = 1.0.1 (2010-07-01): =
1106
+ * Fixed some typos in readme.txt
1107
+ * Switched `get_tag_posts` to query on tag instead of tag_id (maybe a WordPress issue?)
1108
+
1109
+ = 1.0 (2010-06-29): =
1110
+ * JSON API officially drops support for PHP 4 (it was already broken)
1111
+ * Added JSON API Settings page to WP admin
1112
+ * Broke apart `JSON_API_Controller` into a modular controller system
1113
+ * Refactored `JSON_API_Query` to depend less on WordPress's `get_query_var` mechanism
1114
+ * Developer mode now shows response in JSON format
1115
+ * The `create_post` method now requires a nonce
1116
+ * Improved support for complex post queries (props zibitt)
1117
+ * Fixed a bug with `get_author_by_login` (props Krzysztof Sobolewski)
1118
+ * Made image attachments more robust with `get_intermediate_image_sizes` (props mimecine)
1119
+ * Improved post thumbnail support (props nyamsprod)
1120
+
1121
+ = 0.9.6 (2010-05-27): =
1122
+ * Fixed a bug introduced in 0.9.5
1123
+
1124
+ = 0.9.5 (2010-05-27): =
1125
+ * Added a `thumbnail` property to Post response objects
1126
+
1127
+ = 0.9.4 (2010-04-28): =
1128
+ * Fixed a bug where any non-authenticated user could create a draft blog post through `create_post`. Thanks to user futtta for posting about this.
1129
+
1130
+ = 0.9.3 (2010-03-19): =
1131
+ * Fixed a bug where child pages were being ignored by the API. See also: https://core.trac.wordpress.org/ticket/12647
1132
+
1133
+ = 0.9.2 (2010-03-18): =
1134
+ * Fixed a bug where the /api/ rewrite rules would be lost
1135
+
1136
+ = 0.9 (2010-02-04): =
1137
+ * Added a `create_post` method
1138
+
1139
+ = 0.8.3 (2010-01-27): =
1140
+ * Fixed the stable tag version
1141
+
1142
+ = 0.8.2 (2010-01-27): =
1143
+ * Fixed a typo in the changelog
1144
+
1145
+ = 0.8.1 (2010-01-27): =
1146
+ * Fixed a bug that was making JSONP support non-functional
1147
+
1148
+ = 0.8 (2010-01-18): =
1149
+ * Added an attachment model and instance variable for post objects
1150
+
1151
+ = 0.7.3 (2010-01-15): =
1152
+ * Added a `count` request parameter to control the number of posts returned
1153
+
1154
+ = 0.7.2 (2010-01-14): =
1155
+ * Removed the version number from the description text
1156
+
1157
+ = 0.7.1 (2010-01-14): =
1158
+ * Fixed another subtle bug with `get_author_index`
1159
+
1160
+ = 0.7 (2010-01-08): =
1161
+ * Added a `post_count` response to tag objects
1162
+ * Fixed a bug with `get_author_index`
1163
+
1164
+ = 0.6 (2009-11-30): =
1165
+ * Added `count_total` response
1166
+ * Added `json_api_encode` filter
1167
+ * Fixed bugs in the introspector's `get_current_category` and `get_current_tag`
1168
+
1169
+ = 0.5 (2009-11-17): =
1170
+ * Initial Public Release
1171
+
1172
+ == Upgrade Notice ==
1173
+
1174
+ = 1.1.2 =
1175
+ Don't always respond with HTTP 200
1176
+
1177
+ = 1.1.1 =
1178
+ Added support for custom taxonomies
1179
+
1180
+ = 1.1.0 =
1181
+ Minor bugfixes
1182
+
1183
+ = 1.0.9 =
1184
+ Update/delete post methods and some other bugfixes and improvements
1185
+
1186
+ = 1.0.8 =
1187
+ Long overdue bugfix/improvement release
1188
+
1189
+ = 1.0.7 =
1190
+ Minor bugfix/improvement release
1191
+
1192
+ = 1.0.6 =
1193
+ Minor bugfix/improvement release
1194
+
1195
+ = 1.0.5 =
1196
+ Minor improvement release
1197
+
1198
+ = 1.0.4 =
1199
+ Minor bugfix/refactor release
1200
+
1201
+ = 1.0.3 =
1202
+ Two new request arguments added: `thumbnail_size` and `post_type`
1203
+
1204
+ = 1.0.2 =
1205
+ Minor bugfix release
1206
+
1207
+ = 1.0.1 =
1208
+ Bugfix release, possibly stemming from a bug in WordPress 3.0
1209
+
1210
+ = 1.0 =
1211
+ Major release, see changelog for details.
1212
+
1213
+ = 0.9.6 =
1214
+ Bugfix release for something added in 0.9.5.
1215
+
1216
+ = 0.9.5 =
1217
+ Feature addition: post thumbnails now included in response objects.
1218
+
1219
+ = 0.9.4 =
1220
+ Security fix: all users are strongly encouraged to upgrade. (See Changelog.)
1221
+
1222
+ = 0.9.3 =
1223
+ Fixed a bug where child pages could not be introspected by the API.
1224
+
1225
+ = 0.9.2 =
1226
+ Fixed a bug where the /api/ path would stop working upon publishing new pages.
1227
+
1228
+ = 0.9 =
1229
+ Added a new data manipulation method: `create_post`.
1230
+
1231
+ = 0.8.3 =
1232
+ Oh dear, I didn't tag 0.8.2 in the stable tags thing.
1233
+
1234
+ = 0.8.2 =
1235
+ Just fixing a mislabeled 0.8.1 release in the changelog.
1236
+
1237
+ = 0.8.1 =
1238
+ This is a bug fix release for JSONP support. Thanks to Ben Wilson for reporting it!
1239
+
1240
+ = 0.8 =
1241
+ Added what may be the last introspection feature: post attachments. You can now see images and other media that have been added to posts.
trunk/screenshot-1.png ADDED
Binary file
trunk/singletons/api.php ADDED
@@ -0,0 +1,394 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class JSON_API {
4
+
5
+ function __construct() {
6
+ $this->query = new JSON_API_Query();
7
+ $this->introspector = new JSON_API_Introspector();
8
+ $this->response = new JSON_API_Response();
9
+ add_action('template_redirect', array(&$this, 'template_redirect'));
10
+ add_action('admin_menu', array(&$this, 'admin_menu'));
11
+ add_action('update_option_json_api_base', array(&$this, 'flush_rewrite_rules'));
12
+ add_action('pre_update_option_json_api_controllers', array(&$this, 'update_controllers'));
13
+ }
14
+
15
+ function template_redirect() {
16
+ // Check to see if there's an appropriate API controller + method
17
+ $controller = strtolower($this->query->get_controller());
18
+ $available_controllers = $this->get_controllers();
19
+ $enabled_controllers = explode(',', get_option('json_api_controllers', 'core'));
20
+ $active_controllers = array_intersect($available_controllers, $enabled_controllers);
21
+
22
+ if ($controller) {
23
+
24
+ if (empty($this->query->dev)) {
25
+ error_reporting(0);
26
+ }
27
+
28
+ if (!in_array($controller, $active_controllers)) {
29
+ $this->error("Unknown controller '$controller'.");
30
+ }
31
+
32
+ $controller_path = $this->controller_path($controller);
33
+ if (file_exists($controller_path)) {
34
+ require_once $controller_path;
35
+ }
36
+ $controller_class = $this->controller_class($controller);
37
+
38
+ if (!class_exists($controller_class)) {
39
+ $this->error("Unknown controller '$controller_class'.");
40
+ }
41
+
42
+ $this->controller = new $controller_class();
43
+ $method = $this->query->get_method($controller);
44
+
45
+ if ($method) {
46
+
47
+ $this->response->setup();
48
+
49
+ // Run action hooks for method
50
+ do_action("json_api", $controller, $method);
51
+ do_action("json_api-{$controller}-$method");
52
+
53
+ // Error out if nothing is found
54
+ if ($method == '404') {
55
+ $this->error('Not found');
56
+ }
57
+
58
+ // Run the method
59
+ $result = $this->controller->$method();
60
+
61
+ // Handle the result
62
+ $this->response->respond($result);
63
+
64
+ // Done!
65
+ exit;
66
+ }
67
+ }
68
+ }
69
+
70
+ function admin_menu() {
71
+ add_options_page('JSON API Settings', 'JSON API', 'manage_options', 'json-api', array(&$this, 'admin_options'));
72
+ }
73
+
74
+ function admin_options() {
75
+ if (!current_user_can('manage_options')) {
76
+ wp_die( __('You do not have sufficient permissions to access this page.') );
77
+ }
78
+
79
+ $available_controllers = $this->get_controllers();
80
+ $active_controllers = explode(',', get_option('json_api_controllers', 'core'));
81
+
82
+ if (count($active_controllers) == 1 && empty($active_controllers[0])) {
83
+ $active_controllers = array();
84
+ }
85
+
86
+ if (!empty($_REQUEST['_wpnonce']) && wp_verify_nonce($_REQUEST['_wpnonce'], "update-options")) {
87
+ if ((!empty($_REQUEST['action']) || !empty($_REQUEST['action2'])) &&
88
+ (!empty($_REQUEST['controller']) || !empty($_REQUEST['controllers']))) {
89
+ if (!empty($_REQUEST['action'])) {
90
+ $action = $_REQUEST['action'];
91
+ } else {
92
+ $action = $_REQUEST['action2'];
93
+ }
94
+
95
+ if (!empty($_REQUEST['controllers'])) {
96
+ $controllers = $_REQUEST['controllers'];
97
+ } else {
98
+ $controllers = array($_REQUEST['controller']);
99
+ }
100
+
101
+ foreach ($controllers as $controller) {
102
+ if (in_array($controller, $available_controllers)) {
103
+ if ($action == 'activate' && !in_array($controller, $active_controllers)) {
104
+ $active_controllers[] = $controller;
105
+ } else if ($action == 'deactivate') {
106
+ $index = array_search($controller, $active_controllers);
107
+ if ($index !== false) {
108
+ unset($active_controllers[$index]);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ $this->save_option('json_api_controllers', implode(',', $active_controllers));
114
+ }
115
+ if (isset($_REQUEST['json_api_base'])) {
116
+ $this->save_option('json_api_base', $_REQUEST['json_api_base']);
117
+ }
118
+ }
119
+
120
+ ?>
121
+ <div class="wrap">
122
+ <div id="icon-options-general" class="icon32"><br /></div>
123
+ <h2>JSON API Settings</h2>
124
+ <form action="options-general.php?page=json-api" method="post">
125
+ <?php wp_nonce_field('update-options'); ?>
126
+ <h3>Controllers</h3>
127
+ <?php $this->print_controller_actions(); ?>
128
+ <table id="all-plugins-table" class="widefat">
129
+ <thead>
130
+ <tr>
131
+ <th class="manage-column check-column" scope="col"><input type="checkbox" /></th>
132
+ <th class="manage-column" scope="col">Controller</th>
133
+ <th class="manage-column" scope="col">Description</th>
134
+ </tr>
135
+ </thead>
136
+ <tfoot>
137
+ <tr>
138
+ <th class="manage-column check-column" scope="col"><input type="checkbox" /></th>
139
+ <th class="manage-column" scope="col">Controller</th>
140
+ <th class="manage-column" scope="col">Description</th>
141
+ </tr>
142
+ </tfoot>
143
+ <tbody class="plugins">
144
+ <?php
145
+
146
+ foreach ($available_controllers as $controller) {
147
+
148
+ $error = false;
149
+ $active = in_array($controller, $active_controllers);
150
+ $info = $this->controller_info($controller);
151
+
152
+ if (is_string($info)) {
153
+ $active = false;
154
+ $error = true;
155
+ $info = array(
156
+ 'name' => $controller,
157
+ 'description' => "<p><strong>Error</strong>: $info</p>",
158
+ 'methods' => array(),
159
+ 'url' => null
160
+ );
161
+ }
162
+
163
+ ?>
164
+ <tr class="<?php echo ($active ? 'active' : 'inactive'); ?>">
165
+ <th class="check-column" scope="row">
166
+ <input type="checkbox" name="controllers[]" value="<?php echo $controller; ?>" />
167
+ </th>
168
+ <td class="plugin-title">
169
+ <strong><?php echo $info['name']; ?></strong>
170
+ <div class="row-actions-visible">
171
+ <?php
172
+
173
+ if ($active) {
174
+ echo '<a href="' . wp_nonce_url('options-general.php?page=json-api&amp;action=deactivate&amp;controller=' . $controller, 'update-options') . '" title="' . __('Deactivate this controller') . '" class="edit">' . __('Deactivate') . '</a>';
175
+ } else if (!$error) {
176
+ 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>';
177
+ }
178
+
179
+ if (!empty($info['url'])) {
180
+ echo ' | ';
181
+ echo '<a href="' . $info['url'] . '" target="_blank">Docs</a></div>';
182
+ }
183
+
184
+ ?>
185
+ </td>
186
+ <td class="desc">
187
+ <p><?php echo $info['description']; ?></p>
188
+ <p>
189
+ <?php
190
+
191
+ foreach($info['methods'] as $method) {
192
+ $url = $this->get_method_url($controller, $method);
193
+ if ($active) {
194
+ echo "<code><a href=\"$url\">$method</a></code> ";
195
+ } else {
196
+ echo "<code>$method</code> ";
197
+ }
198
+ }
199
+
200
+ ?>
201
+ </p>
202
+ </td>
203
+ </tr>
204
+ <?php } ?>
205
+ </tbody>
206
+ </table>
207
+ <?php $this->print_controller_actions('action2'); ?>
208
+ <h3>Address</h3>
209
+ <p>Specify a base URL for JSON API. For example, using <code>api</code> as your API base URL would enable the following <code><?php bloginfo('url'); ?>/api/get_recent_posts/</code>. If you assign a blank value the API will only be available by setting a <code>json</code> query variable.</p>
210
+ <table class="form-table">
211
+ <tr valign="top">
212
+ <th scope="row">API base</th>
213
+ <td><code><?php bloginfo('url'); ?>/</code><input type="text" name="json_api_base" value="<?php echo get_option('json_api_base', 'api'); ?>" size="15" /></td>
214
+ </tr>
215
+ </table>
216
+ <?php if (!get_option('permalink_structure', '')) { ?>
217
+ <br />
218
+ <p><strong>Note:</strong> User-friendly permalinks are not currently enabled. <a target="_blank" class="button" href="options-permalink.php">Change Permalinks</a>
219
+ <?php } ?>
220
+ <p class="submit">
221
+ <input type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" />
222
+ </p>
223
+ </form>
224
+ </div>
225
+ <?php
226
+ }
227
+
228
+ function print_controller_actions($name = 'action') {
229
+ ?>
230
+ <div class="tablenav">
231
+ <div class="alignleft actions">
232
+ <select name="<?php echo $name; ?>">
233
+ <option selected="selected" value="-1">Bulk Actions</option>
234
+ <option value="activate">Activate</option>
235
+ <option value="deactivate">Deactivate</option>
236
+ </select>
237
+ <input type="submit" class="button-secondary action" id="doaction" name="doaction" value="Apply">
238
+ </div>
239
+ <div class="clear"></div>
240
+ </div>
241
+ <div class="clear"></div>
242
+ <?php
243
+ }
244
+
245
+ function get_method_url($controller, $method, $options = '') {
246
+ $url = get_bloginfo('url');
247
+ $base = get_option('json_api_base', 'api');
248
+ $permalink_structure = get_option('permalink_structure', '');
249
+ if (!empty($options) && is_array($options)) {
250
+ $args = array();
251
+ foreach ($options as $key => $value) {
252
+ $args[] = urlencode($key) . '=' . urlencode($value);
253
+ }
254
+ $args = implode('&', $args);
255
+ } else {
256
+ $args = $options;
257
+ }
258
+ if ($controller != 'core') {
259
+ $method = "$controller/$method";
260
+ }
261
+ if (!empty($base) && !empty($permalink_structure)) {
262
+ if (!empty($args)) {
263
+ $args = "?$args";
264
+ }
265
+ return "$url/$base/$method/$args";
266
+ } else {
267
+ return "$url?json=$method&$args";
268
+ }
269
+ }
270
+
271
+ function save_option($id, $value) {
272
+ $option_exists = (get_option($id, null) !== null);
273
+ if ($option_exists) {
274
+ update_option($id, $value);
275
+ } else {
276
+ add_option($id, $value);
277
+ }
278
+ }
279
+
280
+ function get_controllers() {
281
+ $controllers = array();
282
+ $dir = json_api_dir();
283
+ $this->check_directory_for_controllers("$dir/controllers", $controllers);
284
+ $this->check_directory_for_controllers(get_stylesheet_directory(), $controllers);
285
+ $controllers = apply_filters('json_api_controllers', $controllers);
286
+ return array_map('strtolower', $controllers);
287
+ }
288
+
289
+ function check_directory_for_controllers($dir, &$controllers) {
290
+ $dh = opendir($dir);
291
+ while ($file = readdir($dh)) {
292
+ if (preg_match('/(.+)\.php$/i', $file, $matches)) {
293
+ $src = file_get_contents("$dir/$file");
294
+ if (preg_match("/class\s+JSON_API_{$matches[1]}_Controller/i", $src)) {
295
+ $controllers[] = $matches[1];
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ function controller_is_active($controller) {
302
+ if (defined('JSON_API_CONTROLLERS')) {
303
+ $default = JSON_API_CONTROLLERS;
304
+ } else {
305
+ $default = 'core';
306
+ }
307
+ $active_controllers = explode(',', get_option('json_api_controllers', $default));
308
+ return (in_array($controller, $active_controllers));
309
+ }
310
+
311
+ function update_controllers($controllers) {
312
+ if (is_array($controllers)) {
313
+ return implode(',', $controllers);
314
+ } else {
315
+ return $controllers;
316
+ }
317
+ }
318
+
319
+ function controller_info($controller) {
320
+ $path = $this->controller_path($controller);
321
+ $class = $this->controller_class($controller);
322
+ $response = array(
323
+ 'name' => $controller,
324
+ 'description' => '(No description available)',
325
+ 'methods' => array()
326
+ );
327
+ if (file_exists($path)) {
328
+ $source = file_get_contents($path);
329
+ if (preg_match('/^\s*Controller name:(.+)$/im', $source, $matches)) {
330
+ $response['name'] = trim($matches[1]);
331
+ }
332
+ if (preg_match('/^\s*Controller description:(.+)$/im', $source, $matches)) {
333
+ $response['description'] = trim($matches[1]);
334
+ }
335
+ if (preg_match('/^\s*Controller URI:(.+)$/im', $source, $matches)) {
336
+ $response['docs'] = trim($matches[1]);
337
+ }
338
+ if (!class_exists($class)) {
339
+ require_once($path);
340
+ }
341
+ $response['methods'] = get_class_methods($class);
342
+ return $response;
343
+ } else if (is_admin()) {
344
+ return "Cannot find controller class '$class' (filtered path: $path).";
345
+ } else {
346
+ $this->error("Unknown controller '$controller'.");
347
+ }
348
+ return $response;
349
+ }
350
+
351
+ function controller_class($controller) {
352
+ return "json_api_{$controller}_controller";
353
+ }
354
+
355
+ function controller_path($controller) {
356
+ $json_api_dir = json_api_dir();
357
+ $json_api_path = "$json_api_dir/controllers/$controller.php";
358
+ $theme_dir = get_stylesheet_directory();
359
+ $theme_path = "$theme_dir/$controller.php";
360
+ if (file_exists($theme_path)) {
361
+ $path = $theme_path;
362
+ } else if (file_exists($json_api_path)) {
363
+ $path = $json_api_path;
364
+ } else {
365
+ $path = null;
366
+ }
367
+ $controller_class = $this->controller_class($controller);
368
+ return apply_filters("{$controller_class}_path", $path);
369
+ }
370
+
371
+ function get_nonce_id($controller, $method) {
372
+ $controller = strtolower($controller);
373
+ $method = strtolower($method);
374
+ return "json_api-$controller-$method";
375
+ }
376
+
377
+ function flush_rewrite_rules() {
378
+ global $wp_rewrite;
379
+ $wp_rewrite->flush_rules();
380
+ }
381
+
382
+ function error($message = 'Unknown error', $http_status = 404) {
383
+ $this->response->respond(array(
384
+ 'error' => $message
385
+ ), 'error', $http_status);
386
+ }
387
+
388
+ function include_value($key) {
389
+ return $this->response->is_value_included($key);
390
+ }
391
+
392
+ }
393
+
394
+ ?>
trunk/singletons/introspector.php ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class JSON_API_Introspector {
4
+
5
+ public function get_posts($query = false, $wp_posts = false) {
6
+ global $post, $wp_query;
7
+ $this->set_posts_query($query);
8
+ $output = array();
9
+ while (have_posts()) {
10
+ the_post();
11
+ if ($wp_posts) {
12
+ $new_post = $post;
13
+ } else {
14
+ $new_post = new JSON_API_Post($post);
15
+ }
16
+ $output[] = $new_post;
17
+ }
18
+ return $output;
19
+ }
20
+
21
+ public function get_date_archive_permalinks() {
22
+ $archives = wp_get_archives('echo=0');
23
+ preg_match_all("/href='([^']+)'/", $archives, $matches);
24
+ return $matches[1];
25
+ }
26
+
27
+ public function get_date_archive_tree($permalinks) {
28
+ $tree = array();
29
+ foreach ($permalinks as $url) {
30
+ if (preg_match('#(\d{4})/(\d{2})#', $url, $date)) {
31
+ $year = $date[1];
32
+ $month = $date[2];
33
+ } else if (preg_match('/(\d{4})(\d{2})/', $url, $date)) {
34
+ $year = $date[1];
35
+ $month = $date[2];
36
+ } else {
37
+ continue;
38
+ }
39
+ $count = $this->get_date_archive_count($year, $month);
40
+ if (empty($tree[$year])) {
41
+ $tree[$year] = array(
42
+ $month => $count
43
+ );
44
+ } else {
45
+ $tree[$year][$month] = $count;
46
+ }
47
+ }
48
+ return $tree;
49
+ }
50
+
51
+ public function get_date_archive_count($year, $month) {
52
+ if (!isset($this->month_archives)) {
53
+ global $wpdb;
54
+ $post_counts = $wpdb->get_results("
55
+ SELECT DATE_FORMAT(post_date, '%Y%m') AS month,
56
+ COUNT(ID) AS post_count
57
+ FROM $wpdb->posts
58
+ WHERE post_status = 'publish'
59
+ AND post_type = 'post'
60
+ GROUP BY month
61
+ ");
62
+ $this->month_archives = array();
63
+ foreach ($post_counts as $post_count) {
64
+ $this->month_archives[$post_count->month] = $post_count->post_count;
65
+ }
66
+ }
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') {
75
+ continue;
76
+ }
77
+ $categories[] = $this->get_category_object($wp_category);
78
+ }
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')));
112
+ if ($id || $category_id) {
113
+ if (!$id) {
114
+ $id = $category_id;
115
+ }
116
+ return $this->get_category_by_id($id);
117
+ } else if ($slug || $category_slug) {
118
+ if (!$slug) {
119
+ $slug = $category_slug;
120
+ }
121
+ return $this->get_category_by_slug($slug);
122
+ } else {
123
+ $json_api->error("Include 'id' or 'slug' var in your request.");
124
+ }
125
+ return null;
126
+ }
127
+
128
+ public function get_category_by_id($category_id) {
129
+ $wp_category = get_term_by('id', $category_id, 'category');
130
+ return $this->get_category_object($wp_category);
131
+ }
132
+
133
+ public function get_category_by_slug($category_slug) {
134
+ $wp_category = get_term_by('slug', $category_slug, 'category');
135
+ return $this->get_category_object($wp_category);
136
+ }
137
+
138
+ public function get_tags() {
139
+ $wp_tags = get_tags();
140
+ return array_map(array(&$this, 'get_tag_object'), $wp_tags);
141
+ }
142
+
143
+ public function get_current_tag() {
144
+ global $json_api;
145
+ extract($json_api->query->get(array('id', 'slug', 'tag_id', 'tag_slug')));
146
+ if ($id || $tag_id) {
147
+ if (!$id) {
148
+ $id = $tag_id;
149
+ }
150
+ return $this->get_tag_by_id($id);
151
+ } else if ($slug || $tag_slug) {
152
+ if (!$slug) {
153
+ $slug = $tag_slug;
154
+ }
155
+ return $this->get_tag_by_slug($slug);
156
+ } else {
157
+ $json_api->error("Include 'id' or 'slug' var in your request.");
158
+ }
159
+ return null;
160
+ }
161
+
162
+ public function get_tag_by_id($tag_id) {
163
+ $wp_tag = get_term_by('id', $tag_id, 'post_tag');
164
+ return $this->get_tag_object($wp_tag);
165
+ }
166
+
167
+ public function get_tag_by_slug($tag_slug) {
168
+ $wp_tag = get_term_by('slug', $tag_slug, 'post_tag');
169
+ return $this->get_tag_object($wp_tag);
170
+ }
171
+
172
+ public function get_authors() {
173
+ global $wpdb;
174
+ $author_ids = $wpdb->get_col("
175
+ SELECT u.ID, m.meta_value AS last_name
176
+ FROM $wpdb->users AS u,
177
+ $wpdb->usermeta AS m
178
+ WHERE m.user_id = u.ID
179
+ AND m.meta_key = 'last_name'
180
+ ORDER BY last_name
181
+ ");
182
+ $all_authors = array_map(array(&$this, 'get_author_by_id'), $author_ids);
183
+ $active_authors = array_filter($all_authors, array(&$this, 'is_active_author'));
184
+ return $active_authors;
185
+ }
186
+
187
+ public function get_current_author() {
188
+ global $json_api;
189
+ extract($json_api->query->get(array('id', 'slug', 'author_id', 'author_slug')));
190
+ if ($id || $author_id) {
191
+ if (!$id) {
192
+ $id = $author_id;
193
+ }
194
+ return $this->get_author_by_id($id);
195
+ } else if ($slug || $author_slug) {
196
+ if (!$slug) {
197
+ $slug = $author_slug;
198
+ }
199
+ return $this->get_author_by_login($slug);
200
+ } else {
201
+ $json_api->error("Include 'id' or 'slug' var in your request.");
202
+ }
203
+ return null;
204
+ }
205
+
206
+ public function get_author_by_id($id) {
207
+ $id = get_the_author_meta('ID', $id);
208
+ if (!$id) {
209
+ return null;
210
+ }
211
+ return new JSON_API_Author($id);
212
+ }
213
+
214
+ public function get_author_by_login($login) {
215
+ global $wpdb;
216
+ $id = $wpdb->get_var($wpdb->prepare("
217
+ SELECT ID
218
+ FROM $wpdb->users
219
+ WHERE user_nicename = %s
220
+ ", $login));
221
+ return $this->get_author_by_id($id);
222
+ }
223
+
224
+ public function get_comments($post_id) {
225
+ global $wpdb;
226
+ $wp_comments = $wpdb->get_results($wpdb->prepare("
227
+ SELECT *
228
+ FROM $wpdb->comments
229
+ WHERE comment_post_ID = %d
230
+ AND comment_approved = 1
231
+ AND comment_type = ''
232
+ ORDER BY comment_date
233
+ ", $post_id));
234
+ $comments = array();
235
+ foreach ($wp_comments as $wp_comment) {
236
+ $comments[] = new JSON_API_Comment($wp_comment);
237
+ }
238
+ return $comments;
239
+ }
240
+
241
+ public function get_attachments($post_id) {
242
+ $wp_attachments = get_children(array(
243
+ 'post_type' => 'attachment',
244
+ 'post_parent' => $post_id,
245
+ 'orderby' => 'menu_order',
246
+ 'order' => 'ASC',
247
+ 'suppress_filters' => false
248
+ ));
249
+ $attachments = array();
250
+ if (!empty($wp_attachments)) {
251
+ foreach ($wp_attachments as $wp_attachment) {
252
+ $attachments[] = new JSON_API_Attachment($wp_attachment);
253
+ }
254
+ }
255
+ return $attachments;
256
+ }
257
+
258
+ public function get_attachment($attachment_id) {
259
+ global $wpdb;
260
+ $wp_attachment = $wpdb->get_row(
261
+ $wpdb->prepare("
262
+ SELECT *
263
+ FROM $wpdb->posts
264
+ WHERE ID = %d
265
+ ", $attachment_id)
266
+ );
267
+ return new JSON_API_Attachment($wp_attachment);
268
+ }
269
+
270
+ public function attach_child_posts(&$post) {
271
+ $post->children = array();
272
+ $wp_children = get_posts(array(
273
+ 'post_type' => $post->type,
274
+ 'post_parent' => $post->id,
275
+ 'order' => 'ASC',
276
+ 'orderby' => 'menu_order',
277
+ 'numberposts' => -1,
278
+ 'suppress_filters' => false
279
+ ));
280
+ foreach ($wp_children as $wp_post) {
281
+ $new_post = new JSON_API_Post($wp_post);
282
+ $new_post->parent = $post->id;
283
+ $post->children[] = $new_post;
284
+ }
285
+ foreach ($post->children as $child) {
286
+ $this->attach_child_posts($child);
287
+ }
288
+ }
289
+
290
+ protected function get_category_object($wp_category) {
291
+ if (!$wp_category) {
292
+ return null;
293
+ }
294
+ return new JSON_API_Category($wp_category);
295
+ }
296
+
297
+ protected function get_tag_object($wp_tag) {
298
+ if (!$wp_tag) {
299
+ return null;
300
+ }
301
+ return new JSON_API_Tag($wp_tag);
302
+ }
303
+
304
+ protected function is_active_author($author) {
305
+ if (!isset($this->active_authors)) {
306
+ $this->active_authors = explode(',', wp_list_authors(array(
307
+ 'html' => false,
308
+ 'echo' => false,
309
+ 'exclude_admin' => false
310
+ )));
311
+ $this->active_authors = array_map('trim', $this->active_authors);
312
+ }
313
+ return in_array($author->name, $this->active_authors);
314
+ }
315
+
316
+ protected function set_posts_query($query = false) {
317
+ global $json_api, $wp_query;
318
+
319
+ if (!$query) {
320
+ $query = array();
321
+ }
322
+
323
+ $query = array_merge($query, $wp_query->query);
324
+
325
+ if ($json_api->query->page) {
326
+ $query['paged'] = $json_api->query->page;
327
+ }
328
+
329
+ if ($json_api->query->count) {
330
+ $query['posts_per_page'] = $json_api->query->count;
331
+ }
332
+
333
+ if ($json_api->query->post_type) {
334
+ $query['post_type'] = $json_api->query->post_type;
335
+ }
336
+
337
+ if (!empty($query)) {
338
+ query_posts($query);
339
+ do_action('json_api_query', $wp_query);
340
+ }
341
+ }
342
+
343
+ }
344
+
345
+ ?>
trunk/singletons/query.php ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class JSON_API_Query {
4
+
5
+ // Default values
6
+ protected $defaults = array(
7
+ 'date_format' => 'Y-m-d H:i:s',
8
+ 'read_more' => 'Read more'
9
+ );
10
+
11
+ function __construct() {
12
+ // Register JSON API query vars
13
+ add_filter('query_vars', array(&$this, 'query_vars'));
14
+ }
15
+
16
+ function get($key) {
17
+ if (is_array($key)) {
18
+ $result = array();
19
+ foreach ($key as $k) {
20
+ $result[$k] = $this->get($k);
21
+ }
22
+ return $result;
23
+ }
24
+ $query_var = (isset($_REQUEST[$key])) ? $_REQUEST[$key] : null;
25
+ $wp_query_var = $this->wp_query_var($key);
26
+ if ($wp_query_var) {
27
+ return $wp_query_var;
28
+ } else if ($query_var) {
29
+ return $this->strip_magic_quotes($query_var);
30
+ } else if (isset($this->defaults[$key])) {
31
+ return $this->defaults[$key];
32
+ } else {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function __get($key) {
38
+ return $this->get($key);
39
+ }
40
+
41
+ function __isset($key) {
42
+ return ($this->get($key) !== null);
43
+ }
44
+
45
+ function wp_query_var($key) {
46
+ $wp_translation = array(
47
+ 'json' => 'json',
48
+ 'post_id' => 'p',
49
+ 'post_slug' => 'name',
50
+ 'page_id' => 'page_id',
51
+ 'page_slug' => 'name',
52
+ 'category_id' => 'cat',
53
+ 'category_slug' => 'category_name',
54
+ 'tag_id' => 'tag_id',
55
+ 'tag_slug' => 'tag',
56
+ 'author_id' => 'author',
57
+ 'author_slug' => 'author_name',
58
+ 'search' => 's',
59
+ 'order' => 'order',
60
+ 'order_by' => 'orderby'
61
+ );
62
+ if ($key == 'date') {
63
+ $date = null;
64
+ if (get_query_var('year')) {
65
+ $date = get_query_var('year');
66
+ }
67
+ if (get_query_var('monthnum')) {
68
+ $month = get_query_var('monthnum');
69
+ if ($month < 10) {
70
+ $month = "0$month";
71
+ }
72
+ $date .= $month;
73
+ }
74
+ if (get_query_var('day')) {
75
+ $day = get_query_var('day');
76
+ if ($day < 10) {
77
+ $day = "0$day";
78
+ }
79
+ $date .= $day;
80
+ }
81
+ return $date;
82
+ } else if (isset($wp_translation[$key])) {
83
+ return get_query_var($wp_translation[$key]);
84
+ } else {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ function strip_magic_quotes($value) {
90
+ if (get_magic_quotes_gpc()) {
91
+ return stripslashes($value);
92
+ } else {
93
+ return $value;
94
+ }
95
+ }
96
+
97
+ function query_vars($wp_vars) {
98
+ $wp_vars[] = 'json';
99
+ return $wp_vars;
100
+ }
101
+
102
+ function get_controller() {
103
+ $json = $this->get('json');
104
+ if (empty($json)) {
105
+ return false;
106
+ }
107
+ if (preg_match('/^[a-zA-Z_]+$/', $json)) {
108
+ return $this->get_legacy_controller($json);
109
+ } else if (preg_match('/^([a-zA-Z0-9_]+)(\/|\.)[a-zA-Z0-9_]+$/', $json, $matches)) {
110
+ return $matches[1];
111
+ } else {
112
+ return 'core';
113
+ }
114
+ }
115
+
116
+ function get_legacy_controller($json) {
117
+ global $json_api;
118
+ if ($json == 'submit_comment') {
119
+ if ($json_api->controller_is_active('respond')) {
120
+ return 'respond';
121
+ } else {
122
+ $json_api->error("The 'submit_comment' method has been removed from the Core controller. To use this method you must enable the Respond controller from WP Admin > Settings > JSON API.");
123
+ }
124
+ } else if ($json == 'create_post') {
125
+ if ($json_api->controller_is_active('posts')) {
126
+ return 'posts';
127
+ } else {
128
+ $json_api->error("The 'create_post' method has been removed from the Core controller. To use this method you must enable the Posts controller from WP Admin > Settings > JSON API.");
129
+ }
130
+ } else {
131
+ return 'core';
132
+ }
133
+ }
134
+
135
+ function get_method($controller) {
136
+
137
+ global $json_api;
138
+
139
+ // Returns an appropriate API method name or false. Four possible outcomes:
140
+ // 1. API isn't being invoked at all (return false)
141
+ // 2. A specific API method was requested (return method name)
142
+ // 3. A method is chosen implicitly on a given WordPress page
143
+ // 4. API invoked incorrectly (return "error" method)
144
+ //
145
+ // Note:
146
+ // The implicit outcome (3) is invoked by setting the json query var to a
147
+ // non-empty value on any WordPress page:
148
+ // * http://example.org/2009/11/10/hello-world/?json=1 (get_post)
149
+ // * http://example.org/2009/11/?json=1 (get_date_posts)
150
+ // * http://example.org/category/foo?json=1 (get_category_posts)
151
+
152
+ $method = $this->get('json');
153
+ if (strpos($method, '/') !== false) {
154
+ $method = substr($method, strpos($method, '/') + 1);
155
+ } else if (strpos($method, '.') !== false) {
156
+ $method = substr($method, strpos($method, '.') + 1);
157
+ }
158
+
159
+ if (empty($method)) {
160
+ // Case 1: we're not being invoked (done!)
161
+ return false;
162
+ } else if (method_exists("JSON_API_{$controller}_Controller", $method)) {
163
+ // Case 2: an explicit method was specified
164
+ return $method;
165
+ } else if ($controller == 'core') {
166
+ // Case 3: choose the method implicitly based on which page we're on...
167
+ if (is_search()) {
168
+ return 'get_search_results';
169
+ } else if (is_home()) {
170
+ if (empty($_GET['json'])) {
171
+ $json_api->error("Unknown method '$method'.");
172
+ }
173
+ return 'get_recent_posts';
174
+ } else if (is_page()) {
175
+ return 'get_page';
176
+ } else if (is_single()) {
177
+ return 'get_post';
178
+ } else if (is_category()) {
179
+ return 'get_category_posts';
180
+ } else if (is_tag()) {
181
+ return 'get_tag_posts';
182
+ } else if (is_author()) {
183
+ return 'get_author_posts';
184
+ } else if (is_date()) {
185
+ return 'get_date_posts';
186
+ } else if (is_404()) {
187
+ return '404';
188
+ }
189
+ }
190
+ // Case 4: either the method doesn't exist or we don't support the page implicitly
191
+ return 'error';
192
+ }
193
+
194
+ }
trunk/singletons/response.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class JSON_API_Response {
4
+
5
+ function setup() {
6
+ global $json_api;
7
+ $this->include_values = array();
8
+ $this->exclude_values = array();
9
+ if ($json_api->query->include) {
10
+ $this->include_values = explode(',', $json_api->query->include);
11
+ }
12
+ // Props to ikesyo for submitting a fix!
13
+ if ($json_api->query->exclude) {
14
+ $this->exclude_values = explode(',', $json_api->query->exclude);
15
+ $this->include_values = array_diff($this->include_values, $this->exclude_values);
16
+ }
17
+
18
+ // Compatibility with Disqus plugin
19
+ remove_action('loop_end', 'dsq_loop_end');
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);
27
+ } else if (is_object($data)) {
28
+ $data = get_object_vars($data);
29
+ $data = array_merge(array('status' => $status), $data);
30
+ }
31
+
32
+ $data = apply_filters('json_api_encode', $data);
33
+
34
+ if (function_exists('json_encode')) {
35
+ // Use the built-in json_encode function if it's available
36
+ if (version_compare(PHP_VERSION, '5.3') < 0) {
37
+ $json = json_encode($data);
38
+ } else {
39
+ $json_encode_options = 0;
40
+ if ($json_api->query->json_encode_options) {
41
+ $json_encode_options = $json_api->query->json_encode_options;
42
+ }
43
+ $json = json_encode($data, $json_encode_options);
44
+ }
45
+ } else {
46
+ // Use PEAR's Services_JSON encoder otherwise
47
+ if (!class_exists('Services_JSON')) {
48
+ $dir = json_api_dir();
49
+ require_once "$dir/library/JSON.php";
50
+ }
51
+ $json_service = new Services_JSON();
52
+ $json = $json_service->encode($data);
53
+ }
54
+
55
+ // Thanks to Stack Overflow user Gumbo stackoverflow.com/questions/2934563
56
+ if ($json_api->query->json_unescaped_unicode) {
57
+ $callback = array($this, 'replace_unicode_escape_sequence');
58
+ $json = preg_replace_callback('/\\\\u([0-9a-f]{4})/i', $callback, $json);
59
+ }
60
+
61
+ return $json;
62
+ }
63
+
64
+ function is_value_included($key) {
65
+ // Props to ikesyo for submitting a fix!
66
+ if (empty($this->include_values) && empty($this->exclude_values)) {
67
+ return true;
68
+ } else {
69
+ if (empty($this->exclude_values)) {
70
+ return in_array($key, $this->include_values);
71
+ } else {
72
+ return !in_array($key, $this->exclude_values);
73
+ }
74
+ }
75
+ }
76
+
77
+ function respond($result, $status = 'ok', $http_status = 200) {
78
+ global $json_api;
79
+ $json = $this->get_json($result, $status);
80
+ $status_redirect = "redirect_$status";
81
+ if ($json_api->query->dev || !empty($_REQUEST['dev'])) {
82
+ // Output the result in a human-redable format
83
+ if (!headers_sent()) {
84
+ header('HTTP/1.1 200 OK');
85
+ header('Content-Type: text/plain; charset: UTF-8', true);
86
+ } else {
87
+ echo '<pre>';
88
+ }
89
+ echo $this->prettify($json);
90
+ } else if (!empty($_REQUEST[$status_redirect])) {
91
+ wp_redirect($_REQUEST[$status_redirect]);
92
+ } else if ($json_api->query->redirect) {
93
+ $url = $this->add_status_query_var($json_api->query->redirect, $status);
94
+ wp_redirect($url);
95
+ } else if ($json_api->query->callback) {
96
+ // Run a JSONP-style callback with the result
97
+ $this->callback($json_api->query->callback, $json);
98
+ } else {
99
+ // Output the result
100
+ $this->output($json, $http_status);
101
+ }
102
+ exit;
103
+ }
104
+
105
+ function output($result, $http_status = 200) {
106
+ $charset = get_option('blog_charset');
107
+ if (!headers_sent()) {
108
+ status_header($http_status);
109
+ header("Content-Type: application/json; charset=$charset", true);
110
+ }
111
+ echo $result;
112
+ }
113
+
114
+ function callback($callback, $result) {
115
+ $charset = get_option('blog_charset');
116
+ if (!headers_sent()) {
117
+ status_header(200);
118
+ header("Content-Type: application/javascript; charset=$charset", true);
119
+ }
120
+ echo "$callback($result)";
121
+ }
122
+
123
+ function add_status_query_var($url, $status) {
124
+ if (strpos($url, '#')) {
125
+ // Remove the anchor hash for now
126
+ $pos = strpos($url, '#');
127
+ $anchor = substr($url, $pos);
128
+ $url = substr($url, 0, $pos);
129
+ }
130
+ if (strpos($url, '?')) {
131
+ $url .= "&status=$status";
132
+ } else {
133
+ $url .= "?status=$status";
134
+ }
135
+ if (!empty($anchor)) {
136
+ // Add the anchor hash back in
137
+ $url .= $anchor;
138
+ }
139
+ return $url;
140
+ }
141
+
142
+ function prettify($ugly) {
143
+ $pretty = "";
144
+ $indent = "";
145
+ $last = '';
146
+ $pos = 0;
147
+ $level = 0;
148
+ $string = false;
149
+ while ($pos < strlen($ugly)) {
150
+ $char = substr($ugly, $pos++, 1);
151
+ if (!$string) {
152
+ if ($char == '{' || $char == '[') {
153
+ if ($char == '[' && substr($ugly, $pos, 1) == ']') {
154
+ $pretty .= "[]";
155
+ $pos++;
156
+ } else if ($char == '{' && substr($ugly, $pos, 1) == '}') {
157
+ $pretty .= "{}";
158
+ $pos++;
159
+ } else {
160
+ $pretty .= "$char\n";
161
+ $indent = str_repeat(' ', ++$level);
162
+ $pretty .= "$indent";
163
+ }
164
+ } else if ($char == '}' || $char == ']') {
165
+ $indent = str_repeat(' ', --$level);
166
+ if ($last != '}' && $last != ']') {
167
+ $pretty .= "\n$indent";
168
+ } else if (substr($pretty, -2, 2) == ' ') {
169
+ $pretty = substr($pretty, 0, -2);
170
+ }
171
+ $pretty .= $char;
172
+ if (substr($ugly, $pos, 1) == ',') {
173
+ $pretty .= ",";
174
+ $last = ',';
175
+ $pos++;
176
+ }
177
+ $pretty .= "\n$indent";
178
+ } else if ($char == ':') {
179
+ $pretty .= ": ";
180
+ } else if ($char == ',') {
181
+ $pretty .= ",\n$indent";
182
+ } else if ($char == '"') {
183
+ $pretty .= '"';
184
+ $string = true;
185
+ } else {
186
+ $pretty .= $char;
187
+ }
188
+ } else {
189
+ if ($last != '\\' && $char == '"') {
190
+ $string = false;
191
+ }
192
+ $pretty .= $char;
193
+ }
194
+ $last = $char;
195
+ }
196
+ return $pretty;
197
+ }
198
+
199
+ function replace_unicode_escape_sequence($match) {
200
+ return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
201
+ }
202
+
203
+ }
204
+
205
+ ?>
trunk/tests/core.get_author_index-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_author_index
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_author_index&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $author = $response->authors[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Author count: $response->count\n";
15
+ echo "Author name: $author->name\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Author count: 1
21
+ Author name: themedemos
trunk/tests/core.get_author_posts-01.phpt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_author_posts by slug
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_author_posts&slug=themedemos&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $author = $response->author;
12
+ $post = $response->posts[0];
13
+
14
+ echo "Response status: $response->status\n";
15
+ echo "Post count: $response->count\n";
16
+ echo "First post title: $post->title\n";
17
+
18
+ ?>
19
+ --EXPECT--
20
+ Response status: ok
21
+ Post count: 10
22
+ First post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_author_posts-02.phpt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_author_posts by author_slug
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_author_posts&author_slug=themedemos&page=2&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $author = $response->author;
12
+ $post = $response->posts[0];
13
+
14
+ echo "Response status: $response->status\n";
15
+ echo "Post count: $response->count\n";
16
+ echo "First post title: $post->title\n";
17
+
18
+ ?>
19
+ --EXPECT--
20
+ Response status: ok
21
+ Post count: 10
22
+ First post title: Template: Paginated
trunk/tests/core.get_author_posts-03.phpt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_author_posts by id
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_author_posts&id=2&count=20&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $author = $response->author;
12
+ $post = $response->posts[0];
13
+
14
+ echo "Response status: $response->status\n";
15
+ echo "Post count: $response->count\n";
16
+ echo "First post title: $post->title\n";
17
+
18
+ ?>
19
+ --EXPECT--
20
+ Response status: ok
21
+ Post count: 20
22
+ First post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_category_index-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_category_index
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_category_index&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $category = $response->categories[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Category count: $response->count\n";
15
+ echo "Category name: $category->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Category count: 63
21
+ Category name: aciform
trunk/tests/core.get_category_posts-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_category_posts default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_category_posts&slug=markup&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 6
21
+ Post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_date_index-01.phpt ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_search_posts default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_date_index&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $count = count($response->permalinks);
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Permalink count: $count\n";
15
+ echo "Tree:\n";
16
+ var_dump($response->tree);
17
+
18
+
19
+ ?>
20
+ --EXPECT--
21
+ Response status: ok
22
+ Permalink count: 20
23
+ Tree:
24
+ object(stdClass)#5 (5) {
25
+ ["2013"]=>
26
+ object(stdClass)#6 (1) {
27
+ ["01"]=>
28
+ string(1) "5"
29
+ }
30
+ ["2012"]=>
31
+ object(stdClass)#4 (2) {
32
+ ["03"]=>
33
+ string(1) "5"
34
+ ["01"]=>
35
+ string(1) "6"
36
+ }
37
+ ["2011"]=>
38
+ object(stdClass)#7 (1) {
39
+ ["03"]=>
40
+ string(1) "1"
41
+ }
42
+ ["2010"]=>
43
+ object(stdClass)#8 (10) {
44
+ ["10"]=>
45
+ string(1) "1"
46
+ ["09"]=>
47
+ string(1) "2"
48
+ ["08"]=>
49
+ string(1) "3"
50
+ ["07"]=>
51
+ string(1) "1"
52
+ ["06"]=>
53
+ string(1) "3"
54
+ ["05"]=>
55
+ string(1) "1"
56
+ ["04"]=>
57
+ string(1) "1"
58
+ ["03"]=>
59
+ string(1) "1"
60
+ ["02"]=>
61
+ string(1) "1"
62
+ ["01"]=>
63
+ string(1) "1"
64
+ }
65
+ ["2009"]=>
66
+ object(stdClass)#9 (6) {
67
+ ["10"]=>
68
+ string(1) "1"
69
+ ["09"]=>
70
+ string(1) "1"
71
+ ["08"]=>
72
+ string(1) "1"
73
+ ["07"]=>
74
+ string(1) "1"
75
+ ["06"]=>
76
+ string(1) "1"
77
+ ["05"]=>
78
+ string(1) "1"
79
+ }
80
+ }
trunk/tests/core.get_date_posts-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_date_posts by day
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_date_posts&date=2013-01&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 5
21
+ Post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_date_posts-02.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_date_posts by month
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_date_posts&date=2013-01&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 5
21
+ Post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_date_posts-03.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_date_posts by year
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_date_posts&date=2012&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 10
21
+ Post title: Template: Featured Image (Vertical)
trunk/tests/core.get_page-01.phpt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_page default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_page&slug=about&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+
12
+ echo "Response status: $response->status\n";
13
+ echo "Page title: {$response->page->title}\n";
14
+
15
+
16
+ ?>
17
+ --EXPECT--
18
+ Response status: ok
19
+ Page title: About The Tests
trunk/tests/core.get_page-02.phpt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_page children argument
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_page&slug=level-1&children=1&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $page = $response->page;
12
+ $child = $page->children[0];
13
+ $grandchild = $child->children[0];
14
+
15
+ echo "Response status: $response->status\n";
16
+ echo "Page title: $page->title\n";
17
+ echo "Child title: $child->title\n";
18
+ echo "Grandchild title: $grandchild->title\n";
19
+
20
+ ?>
21
+ --EXPECT--
22
+ Response status: ok
23
+ Page title: Level 1
24
+ Child title: Level 2
25
+ Grandchild title: Level 3
trunk/tests/core.get_post-01.phpt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_post default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_post&slug=markup-html-tags-and-formatting&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+
12
+ echo "Response status: $response->status\n";
13
+ echo "post title: {$response->post->title}\n";
14
+
15
+ ?>
16
+ --EXPECT--
17
+ Response status: ok
18
+ post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_posts-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_posts default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_posts&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 10
21
+ Post title: Markup: HTML Tags and Formatting
trunk/tests/core.get_posts-02.phpt ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_posts by meta key
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_posts&meta_key=enclosure&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+ $attachment = $post->attachments[0];
13
+
14
+ echo "Response status: $response->status\n";
15
+ echo "Post count: $response->count\n";
16
+ echo "Post title: $post->title\n";
17
+ echo "Attachment title: $attachment->title\n";
18
+
19
+ ?>
20
+ --EXPECT--
21
+ Response status: ok
22
+ Post count: 1
23
+ Post title: Post Format: Audio
24
+ Attachment title: St. Louis Blues
trunk/tests/core.get_posts-03.phpt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_posts by meta value
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_posts&meta_key=_wp_old_slug&meta_value=excerpt&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+ echo "Post slug: $post->slug\n";
17
+
18
+ ?>
19
+ --EXPECT--
20
+ Response status: ok
21
+ Post count: 1
22
+ Post title: Template: Excerpt (Defined)
23
+ Post slug: template-excerpt-defined
trunk/tests/core.get_recent_posts-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_recent_posts default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_recent_posts&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 11
21
+ Post title: Template: Sticky
trunk/tests/core.get_recent_posts-02.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_recent_posts count argument
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_recent_posts&count=3&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[3];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 4
21
+ Post title: Markup: Text Alignment
trunk/tests/core.get_recent_posts-03.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_recent_posts page argument
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_recent_posts&page=2&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 10
21
+ Post title: Template: Paginated
trunk/tests/core.get_search_posts-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_search_posts default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_search_results&search=foolish&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 2
21
+ Post title: Page Markup And Formatting
trunk/tests/core.get_tag_index-01.phpt ADDED
@@ -0,0 +1,803 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_tag_index
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_tag_index&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+
12
+ echo "Response status: $response->status\n";
13
+ echo "Tag count: $response->count\n";
14
+ echo "Tags:\n";
15
+ var_dump($response->tags);
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Tag count: 60
21
+ Tags:
22
+ array(60) {
23
+ [0]=>
24
+ object(stdClass)#5 (5) {
25
+ ["id"]=>
26
+ int(66)
27
+ ["slug"]=>
28
+ string(4) "8bit"
29
+ ["title"]=>
30
+ string(4) "8BIT"
31
+ ["description"]=>
32
+ string(22) "Tags posts about 8BIT."
33
+ ["post_count"]=>
34
+ int(1)
35
+ }
36
+ [1]=>
37
+ object(stdClass)#6 (5) {
38
+ ["id"]=>
39
+ int(67)
40
+ ["slug"]=>
41
+ string(11) "alignment-2"
42
+ ["title"]=>
43
+ string(9) "alignment"
44
+ ["description"]=>
45
+ string(0) ""
46
+ ["post_count"]=>
47
+ int(3)
48
+ }
49
+ [2]=>
50
+ object(stdClass)#4 (5) {
51
+ ["id"]=>
52
+ int(68)
53
+ ["slug"]=>
54
+ string(8) "articles"
55
+ ["title"]=>
56
+ string(8) "Articles"
57
+ ["description"]=>
58
+ string(26) "Tags posts about Articles."
59
+ ["post_count"]=>
60
+ int(1)
61
+ }
62
+ [3]=>
63
+ object(stdClass)#7 (5) {
64
+ ["id"]=>
65
+ int(69)
66
+ ["slug"]=>
67
+ string(5) "aside"
68
+ ["title"]=>
69
+ string(5) "aside"
70
+ ["description"]=>
71
+ string(0) ""
72
+ ["post_count"]=>
73
+ int(1)
74
+ }
75
+ [4]=>
76
+ object(stdClass)#8 (5) {
77
+ ["id"]=>
78
+ int(70)
79
+ ["slug"]=>
80
+ string(5) "audio"
81
+ ["title"]=>
82
+ string(5) "audio"
83
+ ["description"]=>
84
+ string(0) ""
85
+ ["post_count"]=>
86
+ int(1)
87
+ }
88
+ [5]=>
89
+ object(stdClass)#9 (5) {
90
+ ["id"]=>
91
+ int(71)
92
+ ["slug"]=>
93
+ string(10) "captions-2"
94
+ ["title"]=>
95
+ string(8) "captions"
96
+ ["description"]=>
97
+ string(0) ""
98
+ ["post_count"]=>
99
+ int(2)
100
+ }
101
+ [6]=>
102
+ object(stdClass)#10 (5) {
103
+ ["id"]=>
104
+ int(72)
105
+ ["slug"]=>
106
+ string(10) "categories"
107
+ ["title"]=>
108
+ string(10) "categories"
109
+ ["description"]=>
110
+ string(0) ""
111
+ ["post_count"]=>
112
+ int(2)
113
+ }
114
+ [7]=>
115
+ object(stdClass)#11 (5) {
116
+ ["id"]=>
117
+ int(73)
118
+ ["slug"]=>
119
+ string(4) "chat"
120
+ ["title"]=>
121
+ string(4) "chat"
122
+ ["description"]=>
123
+ string(0) ""
124
+ ["post_count"]=>
125
+ int(2)
126
+ }
127
+ [8]=>
128
+ object(stdClass)#12 (5) {
129
+ ["id"]=>
130
+ int(77)
131
+ ["slug"]=>
132
+ string(5) "codex"
133
+ ["title"]=>
134
+ string(5) "Codex"
135
+ ["description"]=>
136
+ string(0) ""
137
+ ["post_count"]=>
138
+ int(3)
139
+ }
140
+ [9]=>
141
+ object(stdClass)#13 (5) {
142
+ ["id"]=>
143
+ int(78)
144
+ ["slug"]=>
145
+ string(10) "comments-2"
146
+ ["title"]=>
147
+ string(8) "comments"
148
+ ["description"]=>
149
+ string(0) ""
150
+ ["post_count"]=>
151
+ int(4)
152
+ }
153
+ [10]=>
154
+ object(stdClass)#14 (5) {
155
+ ["id"]=>
156
+ int(79)
157
+ ["slug"]=>
158
+ string(9) "content-2"
159
+ ["title"]=>
160
+ string(7) "content"
161
+ ["description"]=>
162
+ string(0) ""
163
+ ["post_count"]=>
164
+ int(12)
165
+ }
166
+ [11]=>
167
+ object(stdClass)#15 (5) {
168
+ ["id"]=>
169
+ int(81)
170
+ ["slug"]=>
171
+ string(3) "css"
172
+ ["title"]=>
173
+ string(3) "css"
174
+ ["description"]=>
175
+ string(0) ""
176
+ ["post_count"]=>
177
+ int(7)
178
+ }
179
+ [12]=>
180
+ object(stdClass)#16 (5) {
181
+ ["id"]=>
182
+ int(85)
183
+ ["slug"]=>
184
+ string(6) "dowork"
185
+ ["title"]=>
186
+ string(6) "dowork"
187
+ ["description"]=>
188
+ string(25) "Tags posts about #dowork."
189
+ ["post_count"]=>
190
+ int(1)
191
+ }
192
+ [13]=>
193
+ object(stdClass)#17 (5) {
194
+ ["id"]=>
195
+ int(86)
196
+ ["slug"]=>
197
+ string(9) "edge-case"
198
+ ["title"]=>
199
+ string(9) "edge case"
200
+ ["description"]=>
201
+ string(0) ""
202
+ ["post_count"]=>
203
+ int(8)
204
+ }
205
+ [14]=>
206
+ object(stdClass)#18 (5) {
207
+ ["id"]=>
208
+ int(87)
209
+ ["slug"]=>
210
+ string(8) "embeds-2"
211
+ ["title"]=>
212
+ string(6) "embeds"
213
+ ["description"]=>
214
+ string(0) ""
215
+ ["post_count"]=>
216
+ int(4)
217
+ }
218
+ [15]=>
219
+ object(stdClass)#19 (5) {
220
+ ["id"]=>
221
+ int(91)
222
+ ["slug"]=>
223
+ string(9) "excerpt-2"
224
+ ["title"]=>
225
+ string(7) "excerpt"
226
+ ["description"]=>
227
+ string(0) ""
228
+ ["post_count"]=>
229
+ int(3)
230
+ }
231
+ [16]=>
232
+ object(stdClass)#20 (5) {
233
+ ["id"]=>
234
+ int(92)
235
+ ["slug"]=>
236
+ string(4) "fail"
237
+ ["title"]=>
238
+ string(4) "Fail"
239
+ ["description"]=>
240
+ string(22) "Tags posts about fail."
241
+ ["post_count"]=>
242
+ int(1)
243
+ }
244
+ [17]=>
245
+ object(stdClass)#21 (5) {
246
+ ["id"]=>
247
+ int(93)
248
+ ["slug"]=>
249
+ string(14) "featured-image"
250
+ ["title"]=>
251
+ string(14) "featured image"
252
+ ["description"]=>
253
+ string(0) ""
254
+ ["post_count"]=>
255
+ int(3)
256
+ }
257
+ [18]=>
258
+ object(stdClass)#22 (5) {
259
+ ["id"]=>
260
+ int(96)
261
+ ["slug"]=>
262
+ string(12) "formatting-2"
263
+ ["title"]=>
264
+ string(10) "formatting"
265
+ ["description"]=>
266
+ string(0) ""
267
+ ["post_count"]=>
268
+ int(1)
269
+ }
270
+ [19]=>
271
+ object(stdClass)#23 (5) {
272
+ ["id"]=>
273
+ int(97)
274
+ ["slug"]=>
275
+ string(3) "ftw"
276
+ ["title"]=>
277
+ string(3) "FTW"
278
+ ["description"]=>
279
+ string(0) ""
280
+ ["post_count"]=>
281
+ int(1)
282
+ }
283
+ [20]=>
284
+ object(stdClass)#24 (5) {
285
+ ["id"]=>
286
+ int(98)
287
+ ["slug"]=>
288
+ string(3) "fun"
289
+ ["title"]=>
290
+ string(3) "Fun"
291
+ ["description"]=>
292
+ string(21) "Tags posts about fun."
293
+ ["post_count"]=>
294
+ int(1)
295
+ }
296
+ [21]=>
297
+ object(stdClass)#25 (5) {
298
+ ["id"]=>
299
+ int(99)
300
+ ["slug"]=>
301
+ string(7) "gallery"
302
+ ["title"]=>
303
+ string(7) "gallery"
304
+ ["description"]=>
305
+ string(0) ""
306
+ ["post_count"]=>
307
+ int(3)
308
+ }
309
+ [22]=>
310
+ object(stdClass)#26 (5) {
311
+ ["id"]=>
312
+ int(105)
313
+ ["slug"]=>
314
+ string(4) "html"
315
+ ["title"]=>
316
+ string(4) "html"
317
+ ["description"]=>
318
+ string(0) ""
319
+ ["post_count"]=>
320
+ int(5)
321
+ }
322
+ [23]=>
323
+ object(stdClass)#27 (5) {
324
+ ["id"]=>
325
+ int(106)
326
+ ["slug"]=>
327
+ string(5) "image"
328
+ ["title"]=>
329
+ string(5) "image"
330
+ ["description"]=>
331
+ string(0) ""
332
+ ["post_count"]=>
333
+ int(7)
334
+ }
335
+ [24]=>
336
+ object(stdClass)#28 (5) {
337
+ ["id"]=>
338
+ int(109)
339
+ ["slug"]=>
340
+ string(9) "jetpack-2"
341
+ ["title"]=>
342
+ string(7) "jetpack"
343
+ ["description"]=>
344
+ string(0) ""
345
+ ["post_count"]=>
346
+ int(3)
347
+ }
348
+ [25]=>
349
+ object(stdClass)#29 (5) {
350
+ ["id"]=>
351
+ int(111)
352
+ ["slug"]=>
353
+ string(6) "layout"
354
+ ["title"]=>
355
+ string(6) "layout"
356
+ ["description"]=>
357
+ string(0) ""
358
+ ["post_count"]=>
359
+ int(4)
360
+ }
361
+ [26]=>
362
+ object(stdClass)#30 (5) {
363
+ ["id"]=>
364
+ int(112)
365
+ ["slug"]=>
366
+ string(4) "link"
367
+ ["title"]=>
368
+ string(4) "link"
369
+ ["description"]=>
370
+ string(0) ""
371
+ ["post_count"]=>
372
+ int(2)
373
+ }
374
+ [27]=>
375
+ object(stdClass)#31 (5) {
376
+ ["id"]=>
377
+ int(113)
378
+ ["slug"]=>
379
+ string(7) "lists-2"
380
+ ["title"]=>
381
+ string(5) "lists"
382
+ ["description"]=>
383
+ string(0) ""
384
+ ["post_count"]=>
385
+ int(1)
386
+ }
387
+ [28]=>
388
+ object(stdClass)#32 (5) {
389
+ ["id"]=>
390
+ int(115)
391
+ ["slug"]=>
392
+ string(4) "love"
393
+ ["title"]=>
394
+ string(4) "Love"
395
+ ["description"]=>
396
+ string(22) "Tags posts about love."
397
+ ["post_count"]=>
398
+ int(1)
399
+ }
400
+ [29]=>
401
+ object(stdClass)#33 (5) {
402
+ ["id"]=>
403
+ int(116)
404
+ ["slug"]=>
405
+ string(8) "markup-2"
406
+ ["title"]=>
407
+ string(6) "markup"
408
+ ["description"]=>
409
+ string(0) ""
410
+ ["post_count"]=>
411
+ int(6)
412
+ }
413
+ [30]=>
414
+ object(stdClass)#34 (5) {
415
+ ["id"]=>
416
+ int(117)
417
+ ["slug"]=>
418
+ string(5) "media"
419
+ ["title"]=>
420
+ string(5) "media"
421
+ ["description"]=>
422
+ string(0) ""
423
+ ["post_count"]=>
424
+ int(1)
425
+ }
426
+ [31]=>
427
+ object(stdClass)#35 (5) {
428
+ ["id"]=>
429
+ int(122)
430
+ ["slug"]=>
431
+ string(10) "mothership"
432
+ ["title"]=>
433
+ string(10) "Mothership"
434
+ ["description"]=>
435
+ string(29) "Tags posts about motherships."
436
+ ["post_count"]=>
437
+ int(1)
438
+ }
439
+ [32]=>
440
+ object(stdClass)#36 (5) {
441
+ ["id"]=>
442
+ int(123)
443
+ ["slug"]=>
444
+ string(8) "mustread"
445
+ ["title"]=>
446
+ string(9) "Must Read"
447
+ ["description"]=>
448
+ string(40) "Tags posts about articles you must read."
449
+ ["post_count"]=>
450
+ int(1)
451
+ }
452
+ [33]=>
453
+ object(stdClass)#37 (5) {
454
+ ["id"]=>
455
+ int(124)
456
+ ["slug"]=>
457
+ string(8) "nailedit"
458
+ ["title"]=>
459
+ string(9) "Nailed It"
460
+ ["description"]=>
461
+ string(32) "Tags posts about that nailed it."
462
+ ["post_count"]=>
463
+ int(1)
464
+ }
465
+ [34]=>
466
+ object(stdClass)#38 (5) {
467
+ ["id"]=>
468
+ int(126)
469
+ ["slug"]=>
470
+ string(10) "pagination"
471
+ ["title"]=>
472
+ string(10) "pagination"
473
+ ["description"]=>
474
+ string(0) ""
475
+ ["post_count"]=>
476
+ int(1)
477
+ }
478
+ [35]=>
479
+ object(stdClass)#39 (5) {
480
+ ["id"]=>
481
+ int(128)
482
+ ["slug"]=>
483
+ string(10) "password-2"
484
+ ["title"]=>
485
+ string(8) "password"
486
+ ["description"]=>
487
+ string(0) ""
488
+ ["post_count"]=>
489
+ int(1)
490
+ }
491
+ [36]=>
492
+ object(stdClass)#40 (5) {
493
+ ["id"]=>
494
+ int(129)
495
+ ["slug"]=>
496
+ string(8) "pictures"
497
+ ["title"]=>
498
+ string(8) "Pictures"
499
+ ["description"]=>
500
+ string(0) ""
501
+ ["post_count"]=>
502
+ int(1)
503
+ }
504
+ [37]=>
505
+ object(stdClass)#41 (5) {
506
+ ["id"]=>
507
+ int(130)
508
+ ["slug"]=>
509
+ string(11) "pingbacks-2"
510
+ ["title"]=>
511
+ string(9) "pingbacks"
512
+ ["description"]=>
513
+ string(0) ""
514
+ ["post_count"]=>
515
+ int(1)
516
+ }
517
+ [38]=>
518
+ object(stdClass)#42 (5) {
519
+ ["id"]=>
520
+ int(133)
521
+ ["slug"]=>
522
+ string(4) "post"
523
+ ["title"]=>
524
+ string(4) "post"
525
+ ["description"]=>
526
+ string(0) ""
527
+ ["post_count"]=>
528
+ int(1)
529
+ }
530
+ [39]=>
531
+ object(stdClass)#43 (5) {
532
+ ["id"]=>
533
+ int(38)
534
+ ["slug"]=>
535
+ string(12) "post-formats"
536
+ ["title"]=>
537
+ string(12) "Post Formats"
538
+ ["description"]=>
539
+ string(0) ""
540
+ ["post_count"]=>
541
+ int(16)
542
+ }
543
+ [40]=>
544
+ object(stdClass)#44 (5) {
545
+ ["id"]=>
546
+ int(139)
547
+ ["slug"]=>
548
+ string(5) "quote"
549
+ ["title"]=>
550
+ string(5) "quote"
551
+ ["description"]=>
552
+ string(0) ""
553
+ ["post_count"]=>
554
+ int(2)
555
+ }
556
+ [41]=>
557
+ object(stdClass)#45 (5) {
558
+ ["id"]=>
559
+ int(141)
560
+ ["slug"]=>
561
+ string(9) "read-more"
562
+ ["title"]=>
563
+ string(9) "read more"
564
+ ["description"]=>
565
+ string(0) ""
566
+ ["post_count"]=>
567
+ int(1)
568
+ }
569
+ [42]=>
570
+ object(stdClass)#46 (5) {
571
+ ["id"]=>
572
+ int(142)
573
+ ["slug"]=>
574
+ string(11) "readability"
575
+ ["title"]=>
576
+ string(11) "readability"
577
+ ["description"]=>
578
+ string(0) ""
579
+ ["post_count"]=>
580
+ int(1)
581
+ }
582
+ [43]=>
583
+ object(stdClass)#47 (5) {
584
+ ["id"]=>
585
+ int(145)
586
+ ["slug"]=>
587
+ string(9) "shortcode"
588
+ ["title"]=>
589
+ string(9) "shortcode"
590
+ ["description"]=>
591
+ string(0) ""
592
+ ["post_count"]=>
593
+ int(6)
594
+ }
595
+ [44]=>
596
+ object(stdClass)#48 (5) {
597
+ ["id"]=>
598
+ int(147)
599
+ ["slug"]=>
600
+ string(10) "standard-2"
601
+ ["title"]=>
602
+ string(8) "standard"
603
+ ["description"]=>
604
+ string(0) ""
605
+ ["post_count"]=>
606
+ int(2)
607
+ }
608
+ [45]=>
609
+ object(stdClass)#49 (5) {
610
+ ["id"]=>
611
+ int(148)
612
+ ["slug"]=>
613
+ string(6) "status"
614
+ ["title"]=>
615
+ string(6) "status"
616
+ ["description"]=>
617
+ string(0) ""
618
+ ["post_count"]=>
619
+ int(1)
620
+ }
621
+ [46]=>
622
+ object(stdClass)#50 (5) {
623
+ ["id"]=>
624
+ int(149)
625
+ ["slug"]=>
626
+ string(8) "sticky-2"
627
+ ["title"]=>
628
+ string(6) "sticky"
629
+ ["description"]=>
630
+ string(0) ""
631
+ ["post_count"]=>
632
+ int(1)
633
+ }
634
+ [47]=>
635
+ object(stdClass)#51 (5) {
636
+ ["id"]=>
637
+ int(150)
638
+ ["slug"]=>
639
+ string(7) "success"
640
+ ["title"]=>
641
+ string(7) "Success"
642
+ ["description"]=>
643
+ string(25) "Tags posts about success."
644
+ ["post_count"]=>
645
+ int(1)
646
+ }
647
+ [48]=>
648
+ object(stdClass)#52 (5) {
649
+ ["id"]=>
650
+ int(151)
651
+ ["slug"]=>
652
+ string(7) "swagger"
653
+ ["title"]=>
654
+ string(7) "Swagger"
655
+ ["description"]=>
656
+ string(25) "Tags posts about swagger."
657
+ ["post_count"]=>
658
+ int(1)
659
+ }
660
+ [49]=>
661
+ object(stdClass)#53 (5) {
662
+ ["id"]=>
663
+ int(158)
664
+ ["slug"]=>
665
+ string(4) "tags"
666
+ ["title"]=>
667
+ string(4) "Tags"
668
+ ["description"]=>
669
+ string(33) "Tags posts about tags. #inception"
670
+ ["post_count"]=>
671
+ int(1)
672
+ }
673
+ [50]=>
674
+ object(stdClass)#54 (5) {
675
+ ["id"]=>
676
+ int(159)
677
+ ["slug"]=>
678
+ string(8) "template"
679
+ ["title"]=>
680
+ string(8) "template"
681
+ ["description"]=>
682
+ string(0) ""
683
+ ["post_count"]=>
684
+ int(12)
685
+ }
686
+ [51]=>
687
+ object(stdClass)#55 (5) {
688
+ ["id"]=>
689
+ int(163)
690
+ ["slug"]=>
691
+ string(5) "tiled"
692
+ ["title"]=>
693
+ string(5) "tiled"
694
+ ["description"]=>
695
+ string(0) ""
696
+ ["post_count"]=>
697
+ int(1)
698
+ }
699
+ [52]=>
700
+ object(stdClass)#56 (5) {
701
+ ["id"]=>
702
+ int(164)
703
+ ["slug"]=>
704
+ string(5) "title"
705
+ ["title"]=>
706
+ string(5) "title"
707
+ ["description"]=>
708
+ string(0) ""
709
+ ["post_count"]=>
710
+ int(5)
711
+ }
712
+ [53]=>
713
+ object(stdClass)#57 (5) {
714
+ ["id"]=>
715
+ int(165)
716
+ ["slug"]=>
717
+ string(12) "trackbacks-2"
718
+ ["title"]=>
719
+ string(10) "trackbacks"
720
+ ["description"]=>
721
+ string(0) ""
722
+ ["post_count"]=>
723
+ int(1)
724
+ }
725
+ [54]=>
726
+ object(stdClass)#58 (5) {
727
+ ["id"]=>
728
+ int(166)
729
+ ["slug"]=>
730
+ string(9) "twitter-2"
731
+ ["title"]=>
732
+ string(7) "twitter"
733
+ ["description"]=>
734
+ string(0) ""
735
+ ["post_count"]=>
736
+ int(2)
737
+ }
738
+ [55]=>
739
+ object(stdClass)#59 (5) {
740
+ ["id"]=>
741
+ int(168)
742
+ ["slug"]=>
743
+ string(6) "unseen"
744
+ ["title"]=>
745
+ string(6) "Unseen"
746
+ ["description"]=>
747
+ string(46) "Tags posts about things that cannot be unseen."
748
+ ["post_count"]=>
749
+ int(1)
750
+ }
751
+ [56]=>
752
+ object(stdClass)#60 (5) {
753
+ ["id"]=>
754
+ int(169)
755
+ ["slug"]=>
756
+ string(5) "video"
757
+ ["title"]=>
758
+ string(5) "video"
759
+ ["description"]=>
760
+ string(0) ""
761
+ ["post_count"]=>
762
+ int(3)
763
+ }
764
+ [57]=>
765
+ object(stdClass)#61 (5) {
766
+ ["id"]=>
767
+ int(170)
768
+ ["slug"]=>
769
+ string(10) "videopress"
770
+ ["title"]=>
771
+ string(10) "videopress"
772
+ ["description"]=>
773
+ string(0) ""
774
+ ["post_count"]=>
775
+ int(2)
776
+ }
777
+ [58]=>
778
+ object(stdClass)#62 (5) {
779
+ ["id"]=>
780
+ int(172)
781
+ ["slug"]=>
782
+ string(9) "wordpress"
783
+ ["title"]=>
784
+ string(9) "WordPress"
785
+ ["description"]=>
786
+ string(27) "Tags posts about WordPress."
787
+ ["post_count"]=>
788
+ int(1)
789
+ }
790
+ [59]=>
791
+ object(stdClass)#63 (5) {
792
+ ["id"]=>
793
+ int(173)
794
+ ["slug"]=>
795
+ string(12) "wordpress-tv"
796
+ ["title"]=>
797
+ string(12) "wordpress.tv"
798
+ ["description"]=>
799
+ string(0) ""
800
+ ["post_count"]=>
801
+ int(2)
802
+ }
803
+ }
trunk/tests/core.get_tag_posts-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_tag_posts default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.get_tag_posts&slug=css&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $post = $response->posts[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Post count: $response->count\n";
15
+ echo "Post title: $post->title\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Post count: 7
21
+ Post title: Markup: HTML Tags and Formatting
trunk/tests/core.info-01.phpt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.info default
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.info&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+
12
+ echo "Response status: $response->status\n";
13
+ echo "Controllers:\n";
14
+ var_dump($response->controllers);
15
+
16
+ ?>
17
+ --EXPECT--
18
+ Response status: ok
19
+ Controllers:
20
+ array(4) {
21
+ [0]=>
22
+ string(4) "core"
23
+ [1]=>
24
+ string(5) "posts"
25
+ [2]=>
26
+ string(7) "respond"
27
+ [3]=>
28
+ string(7) "widgets"
29
+ }
trunk/tests/core.info-02.phpt ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.info controller detail
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/?json=core.info&controller=core&dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+
12
+ echo "Response status: $response->status\n";
13
+ echo "Name: $response->name\n";
14
+ echo "Description: $response->description\n";
15
+ echo "Methods:\n";
16
+ var_dump($response->methods);
17
+
18
+ ?>
19
+ --EXPECT--
20
+ Response status: ok
21
+ Name: Core
22
+ Description: Basic introspection methods
23
+ Methods:
24
+ array(16) {
25
+ [0]=>
26
+ string(4) "info"
27
+ [1]=>
28
+ string(16) "get_recent_posts"
29
+ [2]=>
30
+ string(9) "get_posts"
31
+ [3]=>
32
+ string(8) "get_post"
33
+ [4]=>
34
+ string(8) "get_page"
35
+ [5]=>
36
+ string(14) "get_date_posts"
37
+ [6]=>
38
+ string(18) "get_category_posts"
39
+ [7]=>
40
+ string(13) "get_tag_posts"
41
+ [8]=>
42
+ string(16) "get_author_posts"
43
+ [9]=>
44
+ string(18) "get_search_results"
45
+ [10]=>
46
+ string(14) "get_date_index"
47
+ [11]=>
48
+ string(18) "get_category_index"
49
+ [12]=>
50
+ string(13) "get_tag_index"
51
+ [13]=>
52
+ string(16) "get_author_index"
53
+ [14]=>
54
+ string(14) "get_page_index"
55
+ [15]=>
56
+ string(9) "get_nonce"
57
+ }
trunk/tests/query-01.phpt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ core.get_author_index with user-friendly URLs
3
+ --FILE--
4
+ <?php
5
+
6
+ require_once 'HTTP/Client.php';
7
+ $http = new HTTP_Client();
8
+ $http->get('http://wordpress.test/api/core/get_author_index?dev=1');
9
+ $response = $http->currentResponse();
10
+ $response = json_decode($response['body']);
11
+ $author = $response->authors[0];
12
+
13
+ echo "Response status: $response->status\n";
14
+ echo "Author count: $response->count\n";
15
+ echo "Author name: $author->name\n";
16
+
17
+ ?>
18
+ --EXPECT--
19
+ Response status: ok
20
+ Author count: 1
21
+ Author name: themedemos