JSON API - Version 1.0

Version Description

Major release, see changelog for details.

Download this release

Release Info

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

Code changes from version 0.9.6 to 1.0

controllers/core.php ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Name: Core
4
+ Description: Basic introspection methods
5
+ */
6
+
7
+ class JSON_API_Core_Controller {
8
+
9
+ public function info() {
10
+ global $json_api;
11
+ if (!empty($json_api->query->controller)) {
12
+ return $json_api->controller_info($json_api->query->controller);
13
+ } else {
14
+ $dir = dirname(dirname(__FILE__));
15
+ $php = file_get_contents("$dir/json-api.php");
16
+ if (preg_match('/^\s*Version:\s*(.+)$/m', $php, $matches)) {
17
+ $version = $matches[1];
18
+ } else {
19
+ $version = '(Unknown)';
20
+ }
21
+ $active_controllers = explode(',', get_option('json_api_controllers', 'core'));
22
+ $controllers = array_intersect($json_api->get_controllers(), $active_controllers);
23
+ return array(
24
+ 'json_api_version' => $version,
25
+ 'controllers' => $controllers
26
+ );
27
+ }
28
+ }
29
+
30
+ public function get_recent_posts() {
31
+ global $json_api;
32
+ $posts = $json_api->introspector->get_posts();
33
+ return $this->posts_result($posts);
34
+ }
35
+
36
+ public function get_post() {
37
+ global $json_api;
38
+ extract($json_api->query->get(array('id', 'slug', 'post_id', 'post_slug')));
39
+ if ($id || $post_id) {
40
+ if (!$id) {
41
+ $id = $post_id;
42
+ }
43
+ $posts = $json_api->introspector->get_posts(array(
44
+ 'p' => $id
45
+ ));
46
+ } else if ($slug || $post_slug) {
47
+ if (!$slug) {
48
+ $slug = $post_slug;
49
+ }
50
+ $posts = $json_api->introspector->get_posts(array(
51
+ 'name' => $slug
52
+ ));
53
+ } else {
54
+ $json_api->error("Include 'id' or 'slug' var in your request.");
55
+ }
56
+ if (count($posts) == 1) {
57
+ return array(
58
+ 'post' => $posts[0]
59
+ );
60
+ } else {
61
+ $json_api->error("Not found.");
62
+ }
63
+ }
64
+
65
+ public function get_page() {
66
+ global $json_api;
67
+ extract($json_api->query->get(array('id', 'slug', 'page_id', 'page_slug', 'children')));
68
+ if ($id || $page_id) {
69
+ if (!$id) {
70
+ $id = $page_id;
71
+ }
72
+ $posts = $json_api->introspector->get_posts(array(
73
+ 'page_id' => $id
74
+ ));
75
+ } else if ($slug || $page_slug) {
76
+ if (!$slug) {
77
+ $slug = $page_slug;
78
+ }
79
+ $posts = $json_api->introspector->get_posts(array(
80
+ 'pagename' => $slug
81
+ ));
82
+ } else {
83
+ $json_api->error("Include 'id' or 'slug' var in your request.");
84
+ }
85
+
86
+ // Workaround for https://core.trac.wordpress.org/ticket/12647
87
+ if (empty($posts)) {
88
+ $url = $_SERVER['REQUEST_URI'];
89
+ $parsed_url = parse_url($url);
90
+ $path = $parsed_url['path'];
91
+ if (preg_match('#^http://[^/]+(/.+)$#', get_bloginfo('url'), $matches)) {
92
+ $blog_root = $matches[1];
93
+ $path = preg_replace("#^$blog_root#", '', $path);
94
+ }
95
+ if (substr($path, 0, 1) == '/') {
96
+ $path = substr($path, 1);
97
+ }
98
+ $posts = $json_api->introspector->get_posts(array('pagename' => $path));
99
+ }
100
+
101
+ if (count($posts) == 1) {
102
+ if (!empty($children)) {
103
+ $json_api->introspector->attach_child_posts($posts[0]);
104
+ }
105
+ return array(
106
+ 'page' => $posts[0]
107
+ );
108
+ } else {
109
+ $json_api->error("Not found.");
110
+ }
111
+ }
112
+
113
+ public function get_date_posts() {
114
+ global $json_api;
115
+ if ($json_api->query->date) {
116
+ $date = preg_replace('/\D/', '', $json_api->query->date);
117
+ if (!preg_match('/^\d{4}(\d{2})?(\d{2})?$/', $date)) {
118
+ $json_api->error("Specify a date var in one of 'YYYY' or 'YYYY-MM' or 'YYYY-MM-DD' formats.");
119
+ }
120
+ $request = array('year' => substr($date, 0, 4));
121
+ if (strlen($date) > 4) {
122
+ $request['monthnum'] = (int) substr($date, 4, 2);
123
+ }
124
+ if (strlen($date) > 6) {
125
+ $request['day'] = (int) substr($date, 6, 2);
126
+ }
127
+ $posts = $json_api->introspector->get_posts($request);
128
+ } else {
129
+ $json_api->error("Include 'date' var in your request.");
130
+ }
131
+ return $this->posts_result($posts);
132
+ }
133
+
134
+ public function get_category_posts() {
135
+ global $json_api;
136
+ $category = $json_api->introspector->get_current_category();
137
+ if (!$category) {
138
+ $json_api->error("Not found.");
139
+ }
140
+ $posts = $json_api->introspector->get_posts(array(
141
+ 'cat' => $category->id
142
+ ));
143
+ return $this->posts_object_result($posts, $category);
144
+ }
145
+
146
+ public function get_tag_posts() {
147
+ global $json_api;
148
+ $tag = $json_api->introspector->get_current_tag();
149
+ if (!$tag) {
150
+ $json_api->error("Not found.");
151
+ }
152
+ $posts = $json_api->introspector->get_posts(array(
153
+ 'tag_id' => $tag->id
154
+ ));
155
+ return $this->posts_object_result($posts, $tag);
156
+ }
157
+
158
+ public function get_author_posts() {
159
+ global $json_api;
160
+ $author = $json_api->introspector->get_current_author();
161
+ if (!$author) {
162
+ $json_api->error("Not found.");
163
+ }
164
+ $posts = $json_api->introspector->get_posts(array(
165
+ 'author' => $author->id
166
+ ));
167
+ return $this->posts_object_result($posts, $author);
168
+ }
169
+
170
+ public function get_search_results() {
171
+ global $json_api;
172
+ if ($json_api->query->search) {
173
+ $posts = $json_api->introspector->get_posts(array(
174
+ 's' => $json_api->query->search
175
+ ));
176
+ } else {
177
+ $json_api->error("Include 'search' var in your request.");
178
+ }
179
+ return $this->posts_result($posts);
180
+ }
181
+
182
+ public function get_date_index() {
183
+ global $json_api;
184
+ $permalinks = $json_api->introspector->get_date_archive_permalinks();
185
+ $tree = $json_api->introspector->get_date_archive_tree($permalinks);
186
+ return array(
187
+ 'permalinks' => $permalinks,
188
+ 'tree' => $tree
189
+ );
190
+ }
191
+
192
+ public function get_category_index() {
193
+ global $json_api;
194
+ $categories = $json_api->introspector->get_categories();
195
+ return array(
196
+ 'count' => count($categories),
197
+ 'categories' => $categories
198
+ );
199
+ }
200
+
201
+ public function get_tag_index() {
202
+ global $json_api;
203
+ $tags = $json_api->introspector->get_tags();
204
+ return array(
205
+ 'count' => count($tags),
206
+ 'tags' => $tags
207
+ );
208
+ }
209
+
210
+ public function get_author_index() {
211
+ global $json_api;
212
+ $authors = $json_api->introspector->get_authors();
213
+ return array(
214
+ 'count' => count($authors),
215
+ 'authors' => array_values($authors)
216
+ );
217
+ }
218
+
219
+ public function get_page_index() {
220
+ global $json_api;
221
+ $pages = array();
222
+ $wp_posts = get_posts(array(
223
+ 'post_type' => 'page',
224
+ 'post_parent' => 0,
225
+ 'order' => 'ASC',
226
+ 'orderby' => 'menu_order'
227
+ ));
228
+ foreach ($wp_posts as $wp_post) {
229
+ $pages[] = new JSON_API_Post($wp_post);
230
+ }
231
+ foreach ($pages as $page) {
232
+ $json_api->introspector->attach_child_posts($page);
233
+ }
234
+ return array(
235
+ 'pages' => $pages
236
+ );
237
+ }
238
+
239
+ public function get_nonce() {
240
+ global $json_api;
241
+ extract($json_api->query->get(array('controller', 'method')));
242
+ if ($controller && $method) {
243
+ $controller = strtolower($controller);
244
+ if (!in_array($controller, $json_api->get_controllers())) {
245
+ $json_api->error("Unknown controller '$controller'.");
246
+ }
247
+ require_once $json_api->controller_path($controller);
248
+ if (!method_exists($json_api->controller_class($controller), $method)) {
249
+ $json_api->error("Unknown method '$method'.");
250
+ }
251
+ $nonce_id = $json_api->get_nonce_id($controller, $method);
252
+ return array(
253
+ 'controller' => $controller,
254
+ 'method' => $method,
255
+ 'nonce' => wp_create_nonce($nonce_id)
256
+ );
257
+ } else {
258
+ $json_api->error("Include 'controller' and 'method' vars in your request.");
259
+ }
260
+ }
261
+
262
+ protected function get_object_posts($object, $id_var, $slug_var) {
263
+ global $json_api;
264
+ $object_id = "{$type}_id";
265
+ $object_slug = "{$type}_slug";
266
+ extract($json_api->query->get(array('id', 'slug', $object_id, $object_slug)));
267
+ if ($id || $$object_id) {
268
+ if (!$id) {
269
+ $id = $$object_id;
270
+ }
271
+ $posts = $json_api->introspector->get_posts(array(
272
+ $id_var => $id
273
+ ));
274
+ } else if ($slug || $$object_slug) {
275
+ if (!$slug) {
276
+ $slug = $$object_slug;
277
+ }
278
+ $posts = $json_api->introspector->get_posts(array(
279
+ $slug_var => $slug
280
+ ));
281
+ } else {
282
+ $json_api->error("No $type specified. Include 'id' or 'slug' var in your request.");
283
+ }
284
+ return $posts;
285
+ }
286
+
287
+ protected function posts_result($posts) {
288
+ global $wp_query;
289
+ return array(
290
+ 'count' => count($posts),
291
+ 'count_total' => (int) $wp_query->found_posts,
292
+ 'pages' => $wp_query->max_num_pages,
293
+ 'posts' => $posts
294
+ );
295
+ }
296
+
297
+ protected function posts_object_result($posts, $object) {
298
+ global $wp_query;
299
+ // Convert something like "JSON_API_Category" into "category"
300
+ $object_key = strtolower(substr(get_class($object), 9));
301
+ return array(
302
+ 'count' => count($posts),
303
+ 'pages' => (int) $wp_query->max_num_pages,
304
+ $object_key => $object,
305
+ 'posts' => $posts
306
+ );
307
+ }
308
+
309
+ }
310
+
311
+ ?>
controllers/posts.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Name: Posts
4
+ Description: Data manipulation methods for posts
5
+ URL:
6
+ */
7
+
8
+ class JSON_API_Posts_Controller {
9
+
10
+ public function create_post() {
11
+ global $json_api;
12
+ if (!current_user_can('edit_posts')) {
13
+ $json_api->error("You need to login with a user capable of creating posts.");
14
+ }
15
+ if (!$json_api->query->nonce) {
16
+ $json_api->error("You must include a 'nonce' value to create posts. Use the `get_nonce` Core API method.");
17
+ }
18
+ $nonce_id = $json_api->get_nonce_id('posts', 'create_post');
19
+ if (!wp_verify_nonce($json_api->query->nonce, $nonce_id)) {
20
+ $json_api->error("Your 'nonce' value was incorrect. Use the 'get_nonce' API method.");
21
+ }
22
+ nocache_headers();
23
+ $post = new JSON_API_Post();
24
+ $id = $post->create($_REQUEST);
25
+ if (empty($id)) {
26
+ $json_api->error("Could not create post.");
27
+ }
28
+ return array(
29
+ 'post' => $post
30
+ );
31
+ }
32
+
33
+ }
34
+
35
+ ?>
controllers/respond.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Name: Respond
4
+ Description: Comment/trackback submission methods
5
+ URL:
6
+ */
7
+
8
+ class JSON_API_Respond_Controller {
9
+
10
+ function submit_comment() {
11
+ global $json_api;
12
+ nocache_headers();
13
+ if (empty($_REQUEST['post_id'])) {
14
+ $json_api->error("No post specified. Include 'post_id' var in your request.");
15
+ } else if (empty($_REQUEST['name']) ||
16
+ empty($_REQUEST['email']) ||
17
+ empty($_REQUEST['content'])) {
18
+ $json_api->error("Please include all required arguments (name, email, content).");
19
+ } else if (!is_email($_REQUEST['email'])) {
20
+ $json_api->error("Please enter a valid email address.");
21
+ }
22
+ $pending = new JSON_API_Comment();
23
+ return $pending->handle_submission();
24
+ }
25
+
26
+ }
27
+
28
+ ?>
json-api.php CHANGED
@@ -3,21 +3,34 @@
3
  Plugin Name: JSON API
4
  Plugin URI: http://wordpress.org/extend/plugins/json-api/
5
  Description: A RESTful API for WordPress
6
- Version: 0.9.6
7
  Author: Dan Phiffer
8
  Author URI: http://phiffer.org/
9
  */
10
 
11
- global $json_api_dir;
12
- $json_api_dir = WP_PLUGIN_DIR . '/json-api';
 
 
 
 
 
 
 
 
13
 
14
  function json_api_init() {
15
- // Initialize the controller and query inspector
16
- global $json_api, $json_api_dir;
17
- require_once "$json_api_dir/singletons/controller.php";
18
- require_once "$json_api_dir/singletons/query.php";
 
19
  add_filter('rewrite_rules_array', 'json_api_rewrites');
20
- $json_api = new JSON_API_Controller();
 
 
 
 
21
  }
22
 
23
  function json_api_activation() {
@@ -34,16 +47,365 @@ function json_api_deactivation() {
34
  }
35
 
36
  function json_api_rewrites($wp_rules) {
37
- // Register the rewrite rule /api/[method] => ?json=[method]
 
 
 
38
  $json_api_rules = array(
39
- 'api/(.+)$' => 'index.php?json=$matches[1]'
 
40
  );
41
  return array_merge($json_api_rules, $wp_rules);
42
  }
43
 
44
  // Add initialization and activation hooks
45
  add_action('init', 'json_api_init');
46
- register_activation_hook("$json_api_dir/json-api.php", 'json_api_activation');
47
- register_deactivation_hook("$json_api_dir/json-api.php", 'json_api_deactivation');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  ?>
3
  Plugin Name: JSON API
4
  Plugin URI: http://wordpress.org/extend/plugins/json-api/
5
  Description: A RESTful API for WordPress
6
+ Version: 1.0
7
  Author: Dan Phiffer
8
  Author URI: http://phiffer.org/
9
  */
10
 
11
+ $dir = dirname(__FILE__);
12
+ require_once "$dir/singletons/query.php";
13
+ require_once "$dir/singletons/introspector.php";
14
+ require_once "$dir/singletons/response.php";
15
+ require_once "$dir/models/post.php";
16
+ require_once "$dir/models/comment.php";
17
+ require_once "$dir/models/category.php";
18
+ require_once "$dir/models/tag.php";
19
+ require_once "$dir/models/author.php";
20
+ require_once "$dir/models/attachment.php";
21
 
22
  function json_api_init() {
23
+ global $json_api;
24
+ if (phpversion() < 5) {
25
+ add_action('admin_notices', 'json_api_php_version_warning');
26
+ return;
27
+ }
28
  add_filter('rewrite_rules_array', 'json_api_rewrites');
29
+ $json_api = new JSON_API();
30
+ }
31
+
32
+ function json_api_php_version_warning() {
33
+ echo "<div id=\"json-api-warning\" class=\"updated fade\"><p>Sorry, JSON API requires PHP version 5.0 or greater.</p></div>";
34
  }
35
 
36
  function json_api_activation() {
47
  }
48
 
49
  function json_api_rewrites($wp_rules) {
50
+ $base = get_option('json_api_base', 'api');
51
+ if (empty($base)) {
52
+ return $wp_rules;
53
+ }
54
  $json_api_rules = array(
55
+ "$base\$" => 'index.php?json=info',
56
+ "$base/(.+)\$" => 'index.php?json=$matches[1]'
57
  );
58
  return array_merge($json_api_rules, $wp_rules);
59
  }
60
 
61
  // Add initialization and activation hooks
62
  add_action('init', 'json_api_init');
63
+ register_activation_hook("$dir/json-api.php", 'json_api_activation');
64
+ register_deactivation_hook("$dir/json-api.php", 'json_api_deactivation');
65
+
66
+
67
+ class JSON_API {
68
+
69
+ function __construct() {
70
+ $this->query = new JSON_API_Query();
71
+ $this->introspector = new JSON_API_Introspector();
72
+ $this->response = new JSON_API_Response();
73
+ add_action('template_redirect', array(&$this, 'template_redirect'));
74
+ add_action('admin_menu', array(&$this, 'admin_menu'));
75
+ add_action('update_option_json_api_base', array(&$this, 'flush_rewrite_rules'));
76
+ add_action('pre_update_option_json_api_controllers', array(&$this, 'update_controllers'));
77
+ }
78
+
79
+ function template_redirect() {
80
+ // Check to see if there's an appropriate API controller + method
81
+ $controller = strtolower($this->query->get_controller());
82
+ $available_controllers = $this->get_controllers();
83
+ $enabled_controllers = explode(',', get_option('json_api_controllers', 'core'));
84
+ $active_controllers = array_intersect($available_controllers, $enabled_controllers);
85
+
86
+ if ($controller) {
87
+
88
+ if (!in_array($controller, $active_controllers)) {
89
+ $this->error("Unknown controller '$controller'.");
90
+ }
91
+
92
+ $controller_path = $this->controller_path($controller);
93
+ if (file_exists($controller_path)) {
94
+ require_once $controller_path;
95
+ }
96
+ $controller_class = $this->controller_class($controller);
97
+
98
+ if (!class_exists($controller_class)) {
99
+ $this->error("Unknown controller '$controller_class'.");
100
+ }
101
+
102
+ $this->controller = new $controller_class();
103
+ $method = $this->query->get_method($controller);
104
+
105
+ if ($method) {
106
+
107
+ $this->response->setup();
108
+
109
+ // Run action hooks for method
110
+ do_action("json_api-{$controller}-$method");
111
+
112
+ // Error out if nothing is found
113
+ if ($method == '404') {
114
+ $this->error('Not found');
115
+ }
116
+
117
+ // Run the method
118
+ $result = $this->controller->$method();
119
+
120
+ // Handle the result
121
+ $this->response->respond($result);
122
+
123
+ // Done!
124
+ exit;
125
+ }
126
+ }
127
+ }
128
+
129
+ function admin_menu() {
130
+ add_options_page('JSON API Settings', 'JSON API', 'manage_options', 'json-api', array(&$this, 'admin_options'));
131
+ }
132
+
133
+ function admin_options() {
134
+ if (!current_user_can('manage_options')) {
135
+ wp_die( __('You do not have sufficient permissions to access this page.') );
136
+ }
137
+
138
+ $available_controllers = $this->get_controllers();
139
+ $active_controllers = explode(',', get_option('json_api_controllers', 'core'));
140
+
141
+ if (count($active_controllers) == 1 && empty($active_controllers[0])) {
142
+ $active_controllers = array();
143
+ }
144
+
145
+ if (!empty($_REQUEST['_wpnonce']) && wp_verify_nonce($_REQUEST['_wpnonce'], "update-options")) {
146
+ if ((!empty($_REQUEST['action']) || !empty($_REQUEST['action2'])) &&
147
+ (!empty($_REQUEST['controller']) || !empty($_REQUEST['controllers']))) {
148
+ if (!empty($_REQUEST['action'])) {
149
+ $action = $_REQUEST['action'];
150
+ } else {
151
+ $action = $_REQUEST['action2'];
152
+ }
153
+
154
+ if (!empty($_REQUEST['controllers'])) {
155
+ $controllers = $_REQUEST['controllers'];
156
+ } else {
157
+ $controllers = array($_REQUEST['controller']);
158
+ }
159
+
160
+ foreach ($controllers as $controller) {
161
+ if (in_array($controller, $available_controllers)) {
162
+ if ($action == 'activate' && !in_array($controller, $active_controllers)) {
163
+ $active_controllers[] = $controller;
164
+ } else if ($action == 'deactivate') {
165
+ $index = array_search($controller, $active_controllers);
166
+ if ($index !== false) {
167
+ unset($active_controllers[$index]);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ $this->save_option('json_api_controllers', implode(',', $active_controllers));
173
+ }
174
+ if (isset($_REQUEST['json_api_base'])) {
175
+ $this->save_option('json_api_base', $_REQUEST['json_api_base']);
176
+ }
177
+ }
178
+
179
+ ?>
180
+ <div class="wrap">
181
+ <div id="icon-options-general" class="icon32"><br /></div>
182
+ <h2>JSON API Settings</h2>
183
+ <form action="options-general.php?page=json-api" method="post">
184
+ <?php wp_nonce_field('update-options'); ?>
185
+ <h3>Controllers</h3>
186
+ <?php $this->print_controller_actions(); ?>
187
+ <table id="all-plugins-table" class="widefat">
188
+ <thead>
189
+ <tr>
190
+ <th class="manage-column check-column" scope="col"><input type="checkbox" /></th>
191
+ <th class="manage-column" scope="col">Controller</th>
192
+ <th class="manage-column" scope="col">Description</th>
193
+ </tr>
194
+ </thead>
195
+ <tfoot>
196
+ <tr>
197
+ <th class="manage-column check-column" scope="col"><input type="checkbox" /></th>
198
+ <th class="manage-column" scope="col">Controller</th>
199
+ <th class="manage-column" scope="col">Description</th>
200
+ </tr>
201
+ </tfoot>
202
+ <tbody class="plugins">
203
+ <?php
204
+
205
+ foreach ($available_controllers as $controller) {
206
+
207
+ $active = in_array($controller, $active_controllers);
208
+ $info = $this->controller_info($controller);
209
+
210
+ ?>
211
+ <tr class="<?php echo ($active ? 'active' : 'inactive'); ?>">
212
+ <th class="check-column" scope="row">
213
+ <input type="checkbox" name="controllers[]" value="<?php echo $controller; ?>" />
214
+ </th>
215
+ <td class="plugin-title">
216
+ <strong><?php echo $info['name']; ?></strong>
217
+ <div class="row-actions-visible">
218
+ <?php
219
+
220
+ if ($active) {
221
+ 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>';
222
+ } else {
223
+ 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>';
224
+ }
225
+
226
+ if ($info['url']) {
227
+ echo ' | ';
228
+ echo '<a href="' . $info['url'] . '" target="_blank">Docs</a></div>';
229
+ }
230
+
231
+ ?>
232
+ </td>
233
+ <td class="desc">
234
+ <p><?php echo $info['description']; ?></p>
235
+ <p>
236
+ <?php
237
+
238
+ foreach($info['methods'] as $method) {
239
+ $url = $this->get_method_url($controller, $method, array('dev' => 1));
240
+ if ($active) {
241
+ echo "<code><a href=\"$url\">$method</a></code> ";
242
+ } else {
243
+ echo "<code>$method</code> ";
244
+ }
245
+ }
246
+
247
+ ?>
248
+ </p>
249
+ </td>
250
+ </tr>
251
+ <?php } ?>
252
+ </tbody>
253
+ </table>
254
+ <?php $this->print_controller_actions('action2'); ?>
255
+ <h3>Address</h3>
256
+ <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>
257
+ <table class="form-table">
258
+ <tr valign="top">
259
+ <th scope="row">API base</th>
260
+ <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>
261
+ </tr>
262
+ </table>
263
+ <?php if (!get_option('permalink_structure', '')) { ?>
264
+ <br />
265
+ <p><strong>Note:</strong> User-friendly permalinks are not currently enabled. <a target="_blank" class="button" href="options-permalink.php">Change Permalinks</a>
266
+ <?php } ?>
267
+ <p class="submit">
268
+ <input type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" />
269
+ </p>
270
+ </form>
271
+ </div>
272
+ <?php
273
+ }
274
+
275
+ function print_controller_actions($name = 'action') {
276
+ ?>
277
+ <div class="tablenav">
278
+ <div class="alignleft actions">
279
+ <select name="<?php echo $name; ?>">
280
+ <option selected="selected" value="-1">Bulk Actions</option>
281
+ <option value="activate">Activate</option>
282
+ <option value="deactivate">Deactivate</option>
283
+ </select>
284
+ <input type="submit" class="button-secondary action" id="doaction" name="doaction" value="Apply">
285
+ </div>
286
+ <div class="clear"></div>
287
+ </div>
288
+ <div class="clear"></div>
289
+ <?php
290
+ }
291
+
292
+ function get_method_url($controller, $method, $options = '') {
293
+ $url = get_bloginfo('url');
294
+ $base = get_option('json_api_base', 'api');
295
+ $permalink_structure = get_option('permalink_structure', '');
296
+ if (!empty($options) && is_array($options)) {
297
+ $args = array();
298
+ foreach ($options as $key => $value) {
299
+ $args[] = urlencode($key) . '=' . urlencode($value);
300
+ }
301
+ $args = implode('&', $args);
302
+ } else {
303
+ $args = $options;
304
+ }
305
+ if ($controller != 'core') {
306
+ $method = "$controller/$method";
307
+ }
308
+ if (!empty($base) && !empty($permalink_structure)) {
309
+ if (!empty($args)) {
310
+ $args = "?$args";
311
+ }
312
+ return "$url/$base/$method/$args";
313
+ } else {
314
+ return "$url?json=$method&$args";
315
+ }
316
+ }
317
+
318
+ function save_option($id, $value) {
319
+ $option_exists = (get_option($id, null) !== null);
320
+ if ($option_exists) {
321
+ update_option($id, $value);
322
+ } else {
323
+ add_option($id, $value);
324
+ }
325
+ }
326
+
327
+ function get_controllers() {
328
+ $controllers = array();
329
+ $dh = opendir(dirname(__FILE__) . '/controllers');
330
+ while ($file = readdir($dh)) {
331
+ if (preg_match('/(.+)\.php$/', $file, $matches)) {
332
+ $controllers[] = $matches[1];
333
+ }
334
+ }
335
+ return apply_filters('json_api_controllers', $controllers);
336
+ }
337
+
338
+ function controller_is_active($controller) {
339
+ $active_controllers = explode(',', get_option('json_api_controllers', 'core'));
340
+ return (in_array($controller, $active_controllers));
341
+ }
342
+
343
+ function update_controllers($controllers) {
344
+ if (is_array($controllers)) {
345
+ return implode(',', $controllers);
346
+ } else {
347
+ return $controllers;
348
+ }
349
+ }
350
+
351
+ function controller_info($controller) {
352
+ $path = $this->controller_path($controller);
353
+ $class = $this->controller_class($controller);
354
+ $response = array(
355
+ 'name' => $controller,
356
+ 'description' => '(No description available)',
357
+ 'methods' => array()
358
+ );
359
+ if (file_exists($path)) {
360
+ $source = file_get_contents($path);
361
+ if (preg_match('/^\s*Name:(.+)$/im', $source, $matches)) {
362
+ $response['name'] = trim($matches[1]);
363
+ }
364
+ if (preg_match('/^\s*Description:(.+)$/im', $source, $matches)) {
365
+ $response['description'] = trim($matches[1]);
366
+ }
367
+ if (preg_match('/^\s*Docs:(.+)$/im', $source, $matches)) {
368
+ $response['docs'] = trim($matches[1]);
369
+ }
370
+ require_once($path);
371
+ $response['methods'] = get_class_methods($class);
372
+ return $response;
373
+ } else {
374
+ $this->error("Unknown controller '$controller'.");
375
+ }
376
+ }
377
+
378
+ function controller_class($controller) {
379
+ return "json_api_{$controller}_controller";
380
+ }
381
+
382
+ function controller_path($controller) {
383
+ $dir = dirname(__FILE__);
384
+ $controller_class = $this->controller_class($controller);
385
+ return apply_filters("{$controller_class}_path", "$dir/controllers/$controller.php");
386
+ }
387
+
388
+ function get_nonce_id($controller, $method) {
389
+ $controller = strtolower($controller);
390
+ $method = strtolower($method);
391
+ return "json_api-$controller-$method";
392
+ }
393
+
394
+ function flush_rewrite_rules() {
395
+ global $wp_rewrite;
396
+ $wp_rewrite->flush_rules();
397
+ }
398
+
399
+ function error($message = 'Unknown error', $status = 'error') {
400
+ $this->response->respond(array(
401
+ 'error' => $message
402
+ ), $status);
403
+ }
404
+
405
+ function include_value($key) {
406
+ return $this->response->is_value_included($key);
407
+ }
408
+
409
+ }
410
 
411
  ?>
models/attachment.php CHANGED
@@ -37,6 +37,9 @@ class JSON_API_Attachment {
37
 
38
  function query_images() {
39
  $sizes = array('thumbnail', 'medium', 'large', 'full');
 
 
 
40
  $this->images = array();
41
  foreach ($sizes as $size) {
42
  list($url, $width, $height) = wp_get_attachment_image_src($this->id, $size);
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
  foreach ($sizes as $size) {
45
  list($url, $width, $height) = wp_get_attachment_image_src($this->id, $size);
models/post.php CHANGED
@@ -6,6 +6,7 @@ class JSON_API_Post {
6
  // JSON_API_Post objects must be instantiated within The Loop.
7
 
8
  var $id; // Integer
 
9
  var $slug; // String
10
  var $url; // String
11
  var $status; // String ("draft", "published", or "pending")
@@ -56,6 +57,10 @@ class JSON_API_Post {
56
  $wp_values['ID'] = $values['id'];
57
  }
58
 
 
 
 
 
59
  if (!empty($values['status'])) {
60
  $wp_values['post_status'] = $values['status'];
61
  }
@@ -124,11 +129,12 @@ class JSON_API_Post {
124
  $date_format = $json_api->query->date_format;
125
  $this->id = (int) $wp_post->ID;
126
  setup_postdata($wp_post);
 
127
  $this->set_value('slug', $wp_post->post_name);
128
  $this->set_value('url', get_permalink($this->id));
129
  $this->set_value('status', $wp_post->post_status);
130
  $this->set_value('title', get_the_title($this->id));
131
- $this->set_value('title_plain', strip_tags($this->title));
132
  $this->set_content_value();
133
  $this->set_value('excerpt', get_the_excerpt());
134
  $this->set_value('date', get_the_time($date_format));
@@ -160,6 +166,8 @@ class JSON_API_Post {
160
  $content = apply_filters('the_content', $content);
161
  $content = str_replace(']]>', ']]&gt;', $content);
162
  $this->content = $content;
 
 
163
  }
164
  }
165
 
@@ -177,6 +185,8 @@ class JSON_API_Post {
177
  $this->categories[] = $category;
178
  }
179
  }
 
 
180
  }
181
  }
182
 
@@ -189,6 +199,8 @@ class JSON_API_Post {
189
  $this->tags[] = new JSON_API_Tag($wp_tag);
190
  }
191
  }
 
 
192
  }
193
  }
194
 
@@ -196,6 +208,8 @@ class JSON_API_Post {
196
  global $json_api;
197
  if ($json_api->include_value('author')) {
198
  $this->author = new JSON_API_Author($author_id);
 
 
199
  }
200
  }
201
 
@@ -203,6 +217,8 @@ class JSON_API_Post {
203
  global $json_api;
204
  if ($json_api->include_value('comments')) {
205
  $this->comments = $json_api->introspector->get_comments($this->id);
 
 
206
  }
207
  }
208
 
@@ -210,24 +226,26 @@ class JSON_API_Post {
210
  global $json_api;
211
  if ($json_api->include_value('attachments')) {
212
  $this->attachments = $json_api->introspector->get_attachments($this->id);
 
 
213
  }
214
  }
215
 
216
  function set_thumbnail_value() {
217
  global $json_api;
218
- $values = get_post_custom_values('_thumbnail_id', $this->id);
219
- if (empty($values)) {
220
  unset($this->thumbnail);
221
  return;
222
  }
223
- $attachments = $json_api->introspector->get_attachments($this->id);
224
- foreach ($attachments as $attachment) {
225
- if ($attachment->id == $values[0]) {
226
- $image = $attachment->images['thumbnail'];
227
- $this->thumbnail = $image->url;
228
- break;
229
- }
230
  }
 
 
 
231
  }
232
 
233
  function set_custom_fields_value() {
@@ -247,6 +265,16 @@ class JSON_API_Post {
247
  }
248
  }
249
 
 
 
 
 
 
 
 
 
 
 
250
  }
251
 
252
  ?>
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")
57
  $wp_values['ID'] = $values['id'];
58
  }
59
 
60
+ if (!empty($values['type'])) {
61
+ $wp_values['post_type'] = $values['type'];
62
+ }
63
+
64
  if (!empty($values['status'])) {
65
  $wp_values['post_status'] = $values['status'];
66
  }
129
  $date_format = $json_api->query->date_format;
130
  $this->id = (int) $wp_post->ID;
131
  setup_postdata($wp_post);
132
+ $this->set_value('type', $wp_post->post_type);
133
  $this->set_value('slug', $wp_post->post_name);
134
  $this->set_value('url', get_permalink($this->id));
135
  $this->set_value('status', $wp_post->post_status);
136
  $this->set_value('title', get_the_title($this->id));
137
+ $this->set_value('title_plain', strip_tags(@$this->title));
138
  $this->set_content_value();
139
  $this->set_value('excerpt', get_the_excerpt());
140
  $this->set_value('date', get_the_time($date_format));
166
  $content = apply_filters('the_content', $content);
167
  $content = str_replace(']]>', ']]&gt;', $content);
168
  $this->content = $content;
169
+ } else {
170
+ unset($this->content);
171
  }
172
  }
173
 
185
  $this->categories[] = $category;
186
  }
187
  }
188
+ } else {
189
+ unset($this->categories);
190
  }
191
  }
192
 
199
  $this->tags[] = new JSON_API_Tag($wp_tag);
200
  }
201
  }
202
+ } else {
203
+ unset($this->tags);
204
  }
205
  }
206
 
208
  global $json_api;
209
  if ($json_api->include_value('author')) {
210
  $this->author = new JSON_API_Author($author_id);
211
+ } else {
212
+ unset($this->author);
213
  }
214
  }
215
 
217
  global $json_api;
218
  if ($json_api->include_value('comments')) {
219
  $this->comments = $json_api->introspector->get_comments($this->id);
220
+ } else {
221
+ unset($this->comments);
222
  }
223
  }
224
 
226
  global $json_api;
227
  if ($json_api->include_value('attachments')) {
228
  $this->attachments = $json_api->introspector->get_attachments($this->id);
229
+ } else {
230
+ unset($this->attachments);
231
  }
232
  }
233
 
234
  function set_thumbnail_value() {
235
  global $json_api;
236
+ if (!$json_api->include_value('thumbnail') ||
237
+ !function_exists('get_post_thumbnail_id')) {
238
  unset($this->thumbnail);
239
  return;
240
  }
241
+ $attachment_id = get_post_thumbnail_id($this->id);
242
+ if (!$attachment_id) {
243
+ unset($this->thumbnail);
244
+ return;
 
 
 
245
  }
246
+ $thumbnail_size = $this->get_thumbnail_size();
247
+ list($thumbnail) = wp_get_attachment_image_src($attachment_id, $thumbnail_size);
248
+ $this->thumbnail = $thumbnail;
249
  }
250
 
251
  function set_custom_fields_value() {
265
  }
266
  }
267
 
268
+ function get_thumbnail_size() {
269
+ if (function_exists('get_intermediate_image_sizes')) {
270
+ $sizes = get_intermediate_image_sizes();
271
+ if (in_array('post-thumbnail', $sizes)) {
272
+ return 'post-thumbnail';
273
+ }
274
+ }
275
+ return 'thumbnail';
276
+ }
277
+
278
  }
279
 
280
  ?>
readme.txt CHANGED
@@ -1,44 +1,76 @@
1
  === JSON API ===
2
  Contributors: dphiffer
 
3
  Tags: json, api, ajax, cms, admin, integration, moma
4
  Requires at least: 2.8
5
- Tested up to: 2.9
6
- Stable tag: 0.9.6
7
 
8
  A RESTful API for WordPress
9
 
10
  == Description ==
11
 
12
- This plugin was created for The Museum of Modern Art, whose weblog [Inside/Out](http://moma.org/explore/inside_out) appears within an existing structure built with Ruby on Rails. Instead of reimplementing the site templates as a WordPress theme, we opted for a Rails front-end that displays content served from a WordPress back-end. JSON API provides the necessary interface for retrieving content and accepting comment submissions.
13
 
14
- The current release implements a mostly-complete set of introspection methods and a method for submitting comments. I plan on offering a complete set of authentication & data manipulation methods, but my current focus is on features we're actually using at MoMA.org.
 
 
15
 
16
- See the Other Notes section for complete API documentation.
 
 
17
 
18
  == Installation ==
19
 
20
  1. Upload the `json-api` folder to the `/wp-content/plugins/` directory or install directly through the plugin installer.
21
- 1. Activate the plugin through the 'Plugins' menu in WordPress or by using the link provided by the plugin installer.
22
 
23
  == Screenshots ==
24
 
25
  1. Our old friend, in JSON format
26
 
27
- == Requests ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  Requests use a simple REST-style HTTP GET or POST. To invoke the API, include a non-empty query value for `json` in the URL.
30
 
31
  JSON API operates in two modes:
32
 
33
  1. *Implicit mode* is triggered by setting the `json` query var to a non-empty value on any WordPress page. The content that would normally appear on that page is returned in JSON format.
34
- 1. *Explicit mode* is triggered by setting `json` to a known method string. See the *API Reference* section below for a complete method listing.
35
 
36
  = Implicit mode examples: =
37
 
38
  * `http://www.example.org/?json=1`
39
  * `http://www.example.org/?p=47&json=1`
40
  * `http://www.example.org/tag/banana/?json=1`
41
-
42
  = Explicit mode examples: =
43
 
44
  * `http://www.example.org/?json=get_recent_posts`
@@ -51,7 +83,37 @@ JSON API operates in two modes:
51
  * `http://www.example.org/api/get_post/?post_id=47`
52
  * `http://www.example.org/api/get_tag_posts/?tag_slug=banana`
53
 
54
- == Responses ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  The standard response format for JSON API is (as you may have guessed) [JSON](http://json.org/).
57
 
@@ -65,6 +127,7 @@ Here is an example response from `http://localhost/wordpress/?json=1` called on
65
  "posts": [
66
  {
67
  "id": 1,
 
68
  "slug": "hello-world",
69
  "url": "http:\/\/localhost\/wordpress\/?p=1",
70
  "title": "Hello world!",
@@ -101,166 +164,44 @@ Here is an example response from `http://localhost/wordpress/?json=1` called on
101
  ]
102
  }
103
 
104
- == API Reference ==
105
-
106
- The JSON API reference is split into four sections:
107
-
108
- 1. Request arguments
109
- 1. Response objects
110
- 1. Plugin hooks
111
- 1. Introspection methods
112
- 1. Data manipulation methods
113
-
114
- __About API changes__
115
- All methods are currently subject to change until the plugin reaches maturity. Please read the the changelog carefully before updating to subsequent releases.
116
-
117
- == 1. Request arguments ==
118
-
119
- The following arguments modify how you get results back from the API. The redirect response styles are intended for use with the data manipulation methods.
120
-
121
- * Setting `callback` to a JavaScript function name will trigger a JSONP-style callback.
122
- * Setting `redirect` to a URL will cause the user's browser to redirect to the specified URL with a `status` value appended to the query vars (see the *Response objects* section below for an explanation of status values).
123
- * Setting `redirect_[status]` allows you to control the resulting browser redirection depending on the `status` value.
124
- * Setting `dev` to a non-empty value formats a plain text response using PHP's `print_r()` function.
125
- * Not setting any of the above argument values will result in a standard JSON response.
126
-
127
- These arguments are available to modify all introspection methods:
128
-
129
- * `date_format` - Changes the format of date values. Uses the same syntax as PHP's date() function. Default value is `Y-m-d H:i:s`.
130
- * `read_more` - Changes the 'read more' link text in post content.
131
- * `include` - Specifies which post data fields to include. Expects a comma-separated list of post fields. Leaving this empty includes *all* fields.
132
- * `exclude` - Specifies which post data fields to exclude. Expects a comma-separated list of post fields.
133
- * `custom_fields` - Includes values from posts' Custom Fields. Expects a comma-separated list of custom field keys.
134
- * `author_meta` - Includes additional author metadata. Should be a comma-separated list of metadata fields.
135
- * `count` - Controls the number of posts to include (defaults to the number specified by WordPress)
136
-
137
- __About `include`/`exclude` arguments__
138
- 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.
139
-
140
- == 2. Response objects ==
141
-
142
- This section describes data objects you can retrieve from WordPress and the optional URL redirects.
143
-
144
- __Status values__
145
- 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.
146
-
147
- __Naming compatibility__
148
- 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.
149
-
150
- = Post response object =
151
-
152
- * `id` - Integer
153
- * `slug` - String
154
- * `url` - String
155
- * `title` - String
156
- * `title_plain` - String
157
- * `content` - String (modified by the `read_more` argument)
158
- * `excerpt` - String
159
- * `date` - String (modified by the `date_format` argument)
160
- * `modified` - String (modified by the `date_format` argument)
161
- * `categories` - Array of category objects
162
- * `tags` - Array of tag objects
163
- * `author` Author object
164
- * `comments` - Array of comment objects
165
- * `attachments` - Array of attachment objects
166
- * `comment_count` - Integer
167
- * `comment_status` - String (`"open"` or `"closed"`)
168
- * `thumbnail` - String (only included if a post thumbnail has been specified)
169
- * `custom_fields` - Object (included by setting the `custom_fields` argument to a comma-separated list of custom field names)
170
 
171
- = Category response object =
172
 
173
- * `id` - Integer
174
- * `slug` - String
175
- * `title` - String
176
- * `description` - String
177
- * `parent` - Integer
178
- * `post_count` - Integer
179
 
180
- = Tag response object =
181
-
182
- * `id` - Integer
183
- * `slug` - String
184
- * `title` - String
185
- * `description` - String
186
- * `post_count` - Integer
187
 
188
- = Author response object =
189
 
190
- * `id` - Integer
191
- * `slug` - String
192
- * `name` - String
193
- * `first_name` - String
194
- * `last_name` - String
195
- * `nickname` - String
196
- * `url` - String
197
- * `description` - String
198
-
199
- Note: You can include additional values by setting the `author_meta` argument to a comma-separated list of metadata fields.
200
 
201
- = Comment response object =
202
 
203
- * `id` - Integer
204
- * `name` - String
205
- * `url` - String
206
- * `date` - String
207
- * `content` - String
208
- * `parent` - Integer
209
- * `author` - Object (only set if the comment author was registered & logged in)
210
-
211
- = Attachment response object =
212
-
213
- * `id` - Integer
214
- * `url` - String
215
- * `slug` - String
216
- * `title` - String
217
- * `description` - String
218
- * `caption` - String
219
- * `parent` - Integer
220
- * `mime_type` - String
221
- * `images` - Object with values `thumbnail`, `medium`, `large`, `full`, each of which are objects with values `url`, `width` and `height` (only set if the attachment is an image)
222
-
223
- == Redirects ==
224
-
225
- 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.
226
-
227
- 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:
228
-
229
- * `http://www.example.com/foo?status=ok`
230
- * `http://www.example.com/foo?status=error`
231
-
232
- 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.
233
-
234
- == 3. Plugin hooks ==
235
 
236
- JSON API currently exposes a single [filter hook](http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters) for you to modify the output.
237
 
238
- == Filter: json_api_encode ==
239
 
240
- 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.
 
 
 
 
 
 
241
 
242
- = Example =
243
 
244
- add_filter('json_api_encode', 'encode_kittens_field');
245
-
246
- encode_kittens_field($response) {
247
- if (isset($response['posts'])) {
248
- array_walk($response['posts'], 'add_kittens_field');
249
- } else if (isset($response['post'])) {
250
- add_kittens_field($response['post']);
251
- }
252
- return $response;
253
  }
254
 
255
- add_kittens_field(&$post) {
256
- $post->kittens = 'Kittens!';
257
- }
258
-
259
-
260
- == 4. Introspection methods ==
261
-
262
- Introspection methods are used to retrieve data from WordPress.
263
-
264
 
265
  == Method: get_recent_posts ==
266
 
@@ -292,8 +233,8 @@ Returns a single post object.
292
  = One of the following is required =
293
 
294
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a post URL
295
- * `post_id` - set to the post's ID
296
- * `post_slug` - set to the post's URL slug
297
 
298
  = Response =
299
 
@@ -310,8 +251,12 @@ Returns a single page object.
310
  = One of the following is required =
311
 
312
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a page URL
313
- * `page_id` - set to the page's ID
314
- * `page_slug` - set to the page's URL slug
 
 
 
 
315
 
316
  = Response =
317
 
@@ -327,7 +272,7 @@ Returns an array of posts/pages in a specific category.
327
  = One of the following is required =
328
 
329
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a date archive page
330
- * `date` - set to a date in the format `YYYY` or `YYYYMM` or `YYYYMMDD`
331
 
332
  = Optional arguments =
333
 
@@ -354,8 +299,8 @@ Returns an array of posts/pages in a specific category.
354
  = One of the following is required =
355
 
356
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a category archive page
357
- * `category_id` - set to the category's ID
358
- * `category_slug` - set to the category's URL slug
359
 
360
  = Optional arguments =
361
 
@@ -384,8 +329,8 @@ Returns an array of posts/pages with a specific tag.
384
  = One of the following is required =
385
 
386
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a tag archive page
387
- * `tag_id` - set to the tag's ID
388
- * `tag_slug` - set to the tag's URL slug
389
 
390
  = Optional arguments =
391
 
@@ -414,8 +359,8 @@ Returns an array of posts/pages written by a specific author.
414
  = One of the following is required =
415
 
416
  * Invoking the JSON API implicitly (i.e., `?json=1`) on an author archive page
417
- * `author_id` - set to the author's ID
418
- * `author_slug` - set to the author's URL slug
419
 
420
  = Optional arguments =
421
 
@@ -540,18 +485,50 @@ Returns an array of active blog authors.
540
  }
541
 
542
 
543
- == 5. Data manipulation methods ==
 
 
 
 
544
 
545
- Data manipulation methods are used for saving content back to WordPress.
 
 
 
 
 
 
 
546
 
547
- __Incomplete__
548
- The data manipulation methods are still very incomplete.
549
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
 
551
  == Method: create_post ==
552
 
553
  Creates a new post.
554
 
 
 
 
 
555
  = Optional arguments =
556
 
557
  * `status` - sets the post status ("draft" or "publish"), default is "draft"
@@ -563,6 +540,9 @@ Creates a new post.
563
 
564
  Note: including a file upload field called `attachment` will cause an attachment to be stored with your new post.
565
 
 
 
 
566
  == Method: submit_comment ==
567
 
568
  Submits a comment to a WordPress post.
@@ -586,8 +566,296 @@ Submits a comment to a WordPress post.
586
  * `pending` - assigned if the comment submission is pending moderation
587
 
588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  == Changelog ==
590
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  = 0.9.6 (2010-05-27): =
592
  * Fixed a bug introduced in 0.9.5
593
 
@@ -641,6 +909,9 @@ Submits a comment to a WordPress post.
641
 
642
  == Upgrade Notice ==
643
 
 
 
 
644
  = 0.9.6 =
645
  Bugfix release for something added in 0.9.5.
646
 
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: 3.0
7
+ Stable tag: 1.0
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
35
+ 1.1. Requests
36
+ 1.2. Controllers
37
+ 1.3. Responses
38
+ 2. Request methods
39
+ 2.1. Core controller methods
40
+ 2.2. Posts controller methods
41
+ 2.3. Respond controller methods
42
+ 3. Request arguments
43
+ 3.1. Output-modifying arguments
44
+ 3.2. Content-modifying arguments
45
+ 3.3. Using query_posts, include/exclude, and redirects
46
+ 4. Response objects
47
+ 4.1. Post response object
48
+ 4.2. Category response object
49
+ 4.3. Tag response object
50
+ 4.4. Author response object
51
+ 4.4. Comment response object
52
+ 4.5. Attachment response object
53
+ 5. Extending JSON API
54
+ 5.1. Plugin hooks
55
+ 5.2. Developing JSON API controllers
56
+
57
+ == 1. General Concepts ==
58
+
59
+ == 1.1. Requests ==
60
 
61
  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.
62
 
63
  JSON API operates in two modes:
64
 
65
  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.
66
+ 2. *Explicit mode* is triggered by setting `json` to a known method string. See the *API Reference* section below for a complete method listing.
67
 
68
  = Implicit mode examples: =
69
 
70
  * `http://www.example.org/?json=1`
71
  * `http://www.example.org/?p=47&json=1`
72
  * `http://www.example.org/tag/banana/?json=1`
73
+
74
  = Explicit mode examples: =
75
 
76
  * `http://www.example.org/?json=get_recent_posts`
83
  * `http://www.example.org/api/get_post/?post_id=47`
84
  * `http://www.example.org/api/get_tag_posts/?tag_slug=banana`
85
 
86
+ __Further reading__
87
+ See Section 3: Request arguments for more information about request arguments to modify the response.
88
+
89
+ == 1.2. Controllers ==
90
+
91
+ 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.
92
+
93
+ = The Core controller =
94
+
95
+ 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).
96
+
97
+ = Specifying a controller =
98
+
99
+ There are a few ways of specifying a controller, depending on how you are calling the API:
100
+
101
+ * `http://www.example.org/?json=get_recent_posts` (`core` controller is implied, method is `get_recent_posts`)
102
+ * `http://www.example.org/api/info/` (`core` controller is implied)
103
+ * `http://www.example.org/api/core/get_category_posts/` (`core` controller can also be explicitly specified)
104
+ * `http://www.example.org/?json=respond.submit_comment` (`respond` controller, `submit_comment` method)
105
+
106
+ __Legacy compatibility__
107
+ 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.
108
+
109
+ = Available controllers =
110
+
111
+ The current release includes three controllers: Core, Posts, and Respond. Developers are encouraged to suggest or submit additional controllers.
112
+
113
+ __Further reading__
114
+ 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.
115
+
116
+ == 1.3. Responses ==
117
 
118
  The standard response format for JSON API is (as you may have guessed) [JSON](http://json.org/).
119
 
127
  "posts": [
128
  {
129
  "id": 1,
130
+ "type": "post",
131
  "slug": "hello-world",
132
  "url": "http:\/\/localhost\/wordpress\/?p=1",
133
  "title": "Hello world!",
164
  ]
165
  }
166
 
167
+ == 2. Request methods ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ The JSON API reference is split into the following sections:
170
 
171
+ == 2.1. Core controller methods ==
 
 
 
 
 
172
 
173
+ The Core controller offers a mostly-complete set of introspection methods for retrieving content from WordPress.
 
 
 
 
 
 
174
 
 
175
 
176
+ == Method: info ==
 
 
 
 
 
 
 
 
 
177
 
178
+ Returns information about JSON API.
179
 
180
+ = Optional arguments =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ * `controller` - returns detailed information about a specific controller
183
 
184
+ = Response =
185
 
186
+ {
187
+ "status": "ok",
188
+ "json_api_version": "1.0",
189
+ "controllers": [
190
+ "core"
191
+ ]
192
+ }
193
 
194
+ = Response =
195
 
196
+ {
197
+ "status": "ok",
198
+ "name": "Core",
199
+ "description": "Basic introspection methods",
200
+ "methods": [
201
+ ...
202
+ ]
 
 
203
  }
204
 
 
 
 
 
 
 
 
 
 
205
 
206
  == Method: get_recent_posts ==
207
 
233
  = One of the following is required =
234
 
235
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a post URL
236
+ * `id` or `post_id` - set to the post's ID
237
+ * `slug` or `post_slug` - set to the post's URL slug
238
 
239
  = Response =
240
 
251
  = One of the following is required =
252
 
253
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a page URL
254
+ * `id` or `page_id` - set to the page's ID
255
+ * `slug` or `page_slug` - set to the page's URL slug
256
+
257
+ = Optional arguments =
258
+
259
+ * `children` - set to a non-empty value to include a recursive hierarchy of child pages
260
 
261
  = Response =
262
 
272
  = One of the following is required =
273
 
274
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a date archive page
275
+ * `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)
276
 
277
  = Optional arguments =
278
 
299
  = One of the following is required =
300
 
301
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a category archive page
302
+ * `id` or `category_id` - set to the category's ID
303
+ * `slug` or `category_slug` - set to the category's URL slug
304
 
305
  = Optional arguments =
306
 
329
  = One of the following is required =
330
 
331
  * Invoking the JSON API implicitly (i.e., `?json=1`) on a tag archive page
332
+ * `id` or `tag_id` - set to the tag's ID
333
+ * `slug` or `tag_slug` - set to the tag's URL slug
334
 
335
  = Optional arguments =
336
 
359
  = One of the following is required =
360
 
361
  * Invoking the JSON API implicitly (i.e., `?json=1`) on an author archive page
362
+ * `id` or `author_id` - set to the author's ID
363
+ * `slug` or `author_slug` - set to the author's URL slug
364
 
365
  = Optional arguments =
366
 
485
  }
486
 
487
 
488
+ == Method: get_page_index ==
489
+
490
+ Returns a hierarchical tree of `page` posts.
491
+
492
+ = Response =
493
 
494
+ {
495
+ "status": "ok",
496
+ "pages": [
497
+ { ... },
498
+ { ... },
499
+ { ... }
500
+ ]
501
+ }
502
 
503
+ == Method: get_nonce ==
 
504
 
505
+ Returns a WordPress nonce value, required to call some data manipulation methods.
506
+
507
+ = Required arguments =
508
+
509
+ * `controller` - the JSON API controller
510
+ * `method` - the JSON API method
511
+
512
+ = Response =
513
+
514
+ {
515
+ "status": "ok",
516
+ "controller": "posts",
517
+ "method": "create_post",
518
+ "nonce": "cefe01efd4"
519
+ }
520
+
521
+
522
+ = 2.2. Pages controller methods =
523
 
524
  == Method: create_post ==
525
 
526
  Creates a new post.
527
 
528
+ = Required argument =
529
+
530
+ * `nonce` - a security check value, available from the `get_nonce` method
531
+
532
  = Optional arguments =
533
 
534
  * `status` - sets the post status ("draft" or "publish"), default is "draft"
540
 
541
  Note: including a file upload field called `attachment` will cause an attachment to be stored with your new post.
542
 
543
+
544
+ = 2.3. Respond controller methods =
545
+
546
  == Method: submit_comment ==
547
 
548
  Submits a comment to a WordPress post.
566
  * `pending` - assigned if the comment submission is pending moderation
567
 
568
 
569
+ == 3. Request arguments ==
570
+
571
+ API requests can be controlled by specifying one of the following arguments as URL query vars.
572
+
573
+ = Examples =
574
+
575
+ * Debug the response: `http://www.example.org/api/get_page_index/?dev=1`
576
+ * Widget-style JSONP output: `http://www.example.org/api/get_recent_posts/?callback=show_posts_widget&read_more=More&count=3`
577
+ * Redirect on error: `http://www.example.org/api/posts/create_post/?callback_error=http%3A%2F%2Fwww.example.org%2Fhelp.html`
578
+
579
+ == 3.1. Output-modifying arguments ==
580
+
581
+ 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.
582
+
583
+ * Setting `callback` to a JavaScript function name will trigger a JSONP-style callback.
584
+ * 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).
585
+ * Setting `redirect_[status]` allows you to control the resulting browser redirection depending on the `status` value.
586
+ * Setting `dev` to a non-empty value adds whitespace for readability and responds with `text/plain`
587
+ * Omitting all of the above arguments will result in a standard JSON response.
588
+
589
+ == 3.2. Content-modifying arguments ==
590
+
591
+ These arguments are available to modify all introspection methods:
592
+
593
+ * `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`.
594
+ * `read_more` - Changes the 'read more' link text in post content.
595
+ * `include` - Specifies which post data fields to include. Expects a comma-separated list of post fields. Leaving this empty includes *all* fields.
596
+ * `exclude` - Specifies which post data fields to exclude. Expects a comma-separated list of post fields.
597
+ * `custom_fields` - Includes values from posts' Custom Fields. Expects a comma-separated list of custom field keys.
598
+ * `author_meta` - Includes additional author metadata. Should be a comma-separated list of metadata fields.
599
+ * `count` - Controls the number of posts to include (defaults to the number specified by WordPress)
600
+ * `order` - Controls the order of post results ('DESC' or 'ASC'). Default value is 'DESC'.
601
+ * `order_by` - Controls which field to order results by. Expects one of the following values:
602
+ * `author`
603
+ * `date` (default value)
604
+ * `title`
605
+ * `modified`
606
+ * `menu_order` (only works with Pages)
607
+ * `parent`
608
+ * `ID`
609
+ * `rand`
610
+ * `meta_value` (`meta_key` must also be set)
611
+ * `none`
612
+ * `comment_count`
613
+ * `meta_key`, `meta_value`, `meta_compare` - Retrieve posts (or Pages) based on a custom field key or value.
614
+
615
+ == 3.3. Using query_posts, include/exclude, and redirects ==
616
+
617
+ __Additional arguments__
618
+ JSON API is based on the same rules as the [`query_posts` template tag](http://codex.wordpress.org/Template_Tags/query_posts). Any query arguments that can be used to augment a call to `query_posts` can also be passed as a URL query variable to achieve the same results.
619
+
620
+ __About `include`/`exclude` arguments__
621
+ 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.
622
+
623
+ __About the `redirect` argument__
624
+ 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.
625
+
626
+ 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:
627
+
628
+ * `http://www.example.com/foo?status=ok`
629
+ * `http://www.example.com/foo?status=error`
630
+
631
+ 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.
632
+
633
+
634
+ == 4. Response objects ==
635
+
636
+ This section describes data objects you can retrieve from WordPress and the optional URL redirects.
637
+
638
+ __Status values__
639
+ 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.
640
+
641
+ __Naming compatibility__
642
+ 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.
643
+
644
+ == 4.1. Post response object ==
645
+
646
+ * `id` - Integer
647
+ * `type` - String (e.g., `post` or `page`)
648
+ * `slug` - String
649
+ * `url` - String
650
+ * `title` - String
651
+ * `title_plain` - String
652
+ * `content` - String (modified by the `read_more` argument)
653
+ * `excerpt` - String
654
+ * `date` - String (modified by the `date_format` argument)
655
+ * `modified` - String (modified by the `date_format` argument)
656
+ * `categories` - Array of category objects
657
+ * `tags` - Array of tag objects
658
+ * `author` Author object
659
+ * `comments` - Array of comment objects
660
+ * `attachments` - Array of attachment objects
661
+ * `comment_count` - Integer
662
+ * `comment_status` - String (`"open"` or `"closed"`)
663
+ * `thumbnail` - String (only included if a post thumbnail has been specified)
664
+ * `custom_fields` - Object (included by setting the `custom_fields` argument to a comma-separated list of custom field names)
665
+
666
+ == 4.2. Category response object ==
667
+
668
+ * `id` - Integer
669
+ * `slug` - String
670
+ * `title` - String
671
+ * `description` - String
672
+ * `parent` - Integer
673
+ * `post_count` - Integer
674
+
675
+ == 4.3. Tag response object ==
676
+
677
+ * `id` - Integer
678
+ * `slug` - String
679
+ * `title` - String
680
+ * `description` - String
681
+ * `post_count` - Integer
682
+
683
+ == 4.4. Author response object ==
684
+
685
+ * `id` - Integer
686
+ * `slug` - String
687
+ * `name` - String
688
+ * `first_name` - String
689
+ * `last_name` - String
690
+ * `nickname` - String
691
+ * `url` - String
692
+ * `description` - String
693
+
694
+ Note: You can include additional values by setting the `author_meta` argument to a comma-separated list of metadata fields.
695
+
696
+ == 4.5. Comment response object ==
697
+
698
+ * `id` - Integer
699
+ * `name` - String
700
+ * `url` - String
701
+ * `date` - String
702
+ * `content` - String
703
+ * `parent` - Integer
704
+ * `author` - Object (only set if the comment author was registered & logged in)
705
+
706
+ == 4.6. Attachment response object ==
707
+
708
+ * `id` - Integer
709
+ * `url` - String
710
+ * `slug` - String
711
+ * `title` - String
712
+ * `description` - String
713
+ * `caption` - String
714
+ * `parent` - Integer
715
+ * `mime_type` - String
716
+ * `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)
717
+
718
+
719
+ == 5. Extending JSON API ==
720
+
721
+ JSON API exposes several WordPress action and filter hooks as well as a modular controller system for adding new API methods.
722
+
723
+ == 5.1. Plugin hooks ==
724
+
725
+ JSON API exposes several [action and filter hooks](http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters) to augment its behavior.
726
+
727
+ == Filter: json_api_controllers ==
728
+
729
+ This filter controls the array of controllers available to JSON API. The callback function is passed a single argument, an array of strings.
730
+
731
+ = Example =
732
+
733
+ // Add a custom controller
734
+ add_filter('json_api_controllers', 'add_my_controller');
735
+
736
+ function add_my_controller($controllers) {
737
+ // Corresponds to the class JSON_API_MyController_Controller
738
+ $controllers[] = 'MyController';
739
+ return $controllers;
740
+ }
741
+
742
+
743
+ == Filter: json_api_[controller]_controller_path
744
+
745
+ Specifies the PHP source file for a given controller, overriding the default location `wp-content/plugins/json_api/controllers`.
746
+
747
+ __Note__
748
+ If you your controller file in the `json-api/controllers` folder JSON API will find it automatically.
749
+
750
+ = Example =
751
+
752
+ // Add a custom controller file for MyController
753
+ add_filter('json_api_mycontroller_controller', 'my_controller_path');
754
+
755
+ function my_controller_path($default_path) {
756
+ return '/path/to/mycontroller.php';
757
+ }
758
+
759
+ == Filter: json_api_encode ==
760
+
761
+ 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.
762
+
763
+ = Example =
764
+
765
+ add_filter('json_api_encode', 'my_encode_kittens');
766
+
767
+ function my_encode_kittens($response) {
768
+ if (isset($response['posts'])) {
769
+ foreach ($response['posts'] as $post) {
770
+ my_add_kittens($post); // Add kittens to each post
771
+ }
772
+ } else if (isset($response['post'])) {
773
+ my_add_kittens($response['post']); // Add a kittens property
774
+ }
775
+ return $response;
776
+ }
777
+
778
+ function my_add_kittens(&$post) {
779
+ $post->kittens = 'Kittens!';
780
+ }
781
+
782
+ == Action: json_api-[controller]-[method] ==
783
+
784
+ Each JSON API method invokes an action when called.
785
+
786
+ = Example =
787
+
788
+ // Disable get_author_index method (e.g., for security reasons)
789
+ add_action('json_api-core-get_author_index', 'my_disable_author_index');
790
+
791
+ function my_disable_author_index() {
792
+ // Stop execution
793
+ exit;
794
+ }
795
+
796
+ == 5.2. Developing JSON API controllers ==
797
+
798
+ = Creating a controller =
799
+
800
+ 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:
801
+
802
+ <?php
803
+
804
+ class JSON_API_Hello_Controller {
805
+
806
+ public function hello_world() {
807
+ return array(
808
+ "message" => "Hello, world"
809
+ );
810
+ }
811
+
812
+ }
813
+
814
+ ?>
815
+
816
+ Your controller is now available as `hello`, and exposes one `hello_world` method.
817
+
818
+ 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:
819
+
820
+ {
821
+ "status": "ok",
822
+ "message": "Hello, world"
823
+ }
824
+
825
+ = Using query vars =
826
+
827
+ 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:
828
+
829
+ public function hello_person() {
830
+ global $json_api;
831
+ $name = $json_api->query->name;
832
+ return array(
833
+ "message" => "Hello, $name."
834
+ );
835
+ }
836
+
837
+ 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`.
838
+
839
+ {
840
+ "status": "ok",
841
+ "message": "Hello, Alice"
842
+ }
843
+
844
+
845
  == Changelog ==
846
 
847
+ = 1.0 (2010-06-29): =
848
+ * JSON API officially drops support for PHP 4 (it was already broken)
849
+ * Added JSON API Settings page to WP admin
850
+ * Broke apart `JSON_API_Controller` into a modular controller system
851
+ * Refactored `JSON_API_Query` to depend less on WordPress's `get_query_var` mechanism
852
+ * Developer mode now shows response in JSON format
853
+ * The `create_post` method now requires a nonce
854
+ * Improved support for complex post queries (props zibitt)
855
+ * Fixed a bug with `get_author_by_login` (props Krzysztof Sobolewski)
856
+ * Made image attachments more robust with `get_intermediate_image_sizes` (props mimecine)
857
+ * Improved post thumbnail support (props nyamsprod)
858
+
859
  = 0.9.6 (2010-05-27): =
860
  * Fixed a bug introduced in 0.9.5
861
 
909
 
910
  == Upgrade Notice ==
911
 
912
+ = 1.0 =
913
+ Major release, see changelog for details.
914
+
915
  = 0.9.6 =
916
  Bugfix release for something added in 0.9.5.
917
 
singletons/controller.php DELETED
@@ -1,253 +0,0 @@
1
- <?php
2
-
3
- class JSON_API_Controller {
4
-
5
- function JSON_API_Controller() {
6
- // The query object determines whether the current request is for the JSON API
7
- $this->query = new JSON_API_Query();
8
-
9
- // This action is called from wp-includes/template-loader.php
10
- add_action('template_redirect', array(&$this, 'template_redirect'));
11
- }
12
-
13
- function template_redirect() {
14
- // Check to see if there's an appropriate API method
15
- $method = $this->query->get_method();
16
-
17
- if ($method) {
18
- // Looks like this is an API request
19
- $this->setup();
20
- $this->query->setup();
21
-
22
- // Run Plugin hooks for method
23
- do_action("json_api_$method");
24
-
25
- // Run the method
26
- $result = $this->$method();
27
-
28
- // Handle the result
29
- $this->response->respond($result);
30
-
31
- // Done!
32
- exit;
33
- }
34
- }
35
-
36
- function setup() {
37
- global $json_api_dir;
38
-
39
- // Setup additional singletons
40
- require_once "$json_api_dir/singletons/response.php";
41
- require_once "$json_api_dir/singletons/introspector.php";
42
- $this->response = new JSON_API_Response();
43
- $this->introspector = new JSON_API_Introspector();
44
-
45
- // Models used by introspection methods
46
- require_once "$json_api_dir/models/post.php";
47
- require_once "$json_api_dir/models/comment.php";
48
- require_once "$json_api_dir/models/category.php";
49
- require_once "$json_api_dir/models/tag.php";
50
- require_once "$json_api_dir/models/author.php";
51
- require_once "$json_api_dir/models/attachment.php";
52
- }
53
-
54
- function error($message, $status = 'error') {
55
- $result = $this->response->get_json(array(
56
- 'error' => $message
57
- ), $status);
58
- $this->response->respond($result);
59
- }
60
-
61
- function get_recent_posts() {
62
- $posts = $this->introspector->get_posts('');
63
- return $this->response->get_posts_json($posts);
64
- }
65
-
66
- function get_post() {
67
- $query = '';
68
- if ($this->query->post_id) {
69
- $query = "p={$this->query->post_id}";
70
- } else if ($this->query->post_slug) {
71
- $query = "name={$this->query->post_slug}";
72
- } else {
73
- $this->error("No post specified. Include 'post_id' or 'post_slug' var in your request.");
74
- }
75
- $posts = $this->introspector->get_posts($query);
76
- if (count($posts) == 1) {
77
- return $this->response->get_json(array(
78
- 'post' => $posts[0]
79
- ));
80
- } else {
81
- $this->error("No post was found.");
82
- }
83
- }
84
-
85
- function get_page() {
86
- $query = '';
87
- if ($this->query->page_id) {
88
- $query = "page_id={$this->query->page_id}";
89
- } else if ($this->query->page_slug) {
90
- $query = "pagename={$this->query->page_slug}";
91
- } else {
92
- $this->error("No page specified. Include 'page_id' or 'page_slug' var in your request.");
93
- }
94
- $pages = $this->introspector->get_posts($query);
95
-
96
- // Workaround for https://core.trac.wordpress.org/ticket/12647
97
- if (empty($pages)) {
98
- $url = $_SERVER['REQUEST_URI'];
99
- $parsed_url = parse_url($url);
100
- $path = $parsed_url['path'];
101
- if (preg_match('#^http://[^/]+(/.+)$#', get_bloginfo('url'), $matches)) {
102
- $blog_root = $matches[1];
103
- $path = preg_replace("#^$blog_root#", '', $path);
104
- }
105
- if (substr($path, 0, 1) == '/') {
106
- $path = substr($path, 1);
107
- }
108
- $pages = $this->introspector->get_posts("pagename=$path");
109
- }
110
-
111
- if (count($pages) == 1) {
112
- return $this->response->get_json(array(
113
- 'page' => $pages[0]
114
- ));
115
- } else {
116
- $this->error("No page was found.");
117
- }
118
- }
119
-
120
- function get_date_posts() {
121
- $query = '';
122
- if ($this->query->date) {
123
- $query = "m={$this->query->date}";
124
- } else {
125
- $this->error("No date specified. Include 'date' var in your request.");
126
- }
127
- $posts = $this->introspector->get_posts($query);
128
- return $this->response->get_posts_json($posts);
129
- }
130
-
131
- function get_category_posts() {
132
- $query = '';
133
- if ($this->query->category_id) {
134
- $query = "cat={$this->query->category_id}";
135
- } else if ($this->query->category_slug) {
136
- $query = "category_name={$this->query->category_slug}";
137
- } else {
138
- $this->error("No category specified. Include 'category_id' or 'category_slug' var in your request.");
139
- }
140
- $posts = $this->introspector->get_posts($query);
141
- $category = $this->introspector->get_current_category();
142
- return $this->response->get_posts_object_json($posts, $category);
143
- }
144
-
145
- function get_tag_posts() {
146
- $query = '';
147
- if ($this->query->tag_slug) {
148
- $query = "tag={$this->query->tag_slug}";
149
- } else if ($this->query->tag_id) {
150
- $query = "tag_id={$this->query->tag_id}";
151
- } else {
152
- $this->error("No tag specified. Include 'tag_id' or 'tag_slug' var in your request.");
153
- }
154
- $posts = $this->introspector->get_posts($query);
155
- $tag = $this->introspector->get_current_tag();
156
- return $this->response->get_posts_object_json($posts, $tag);
157
- }
158
-
159
- function get_author_posts() {
160
- $query = '';
161
- if ($this->query->author_id) {
162
- $query = "author={$this->query->author_id}";
163
- } else if ($this->query->author_slug) {
164
- $query = "author_name={$this->query->author_slug}";
165
- } else {
166
- $this->error("No author specified. Include 'author_id' or 'author_slug' var in your request.");
167
- }
168
- $posts = $this->introspector->get_posts($query);
169
- $author = $this->introspector->get_current_author();
170
- return $this->response->get_posts_object_json($posts, $author);
171
- }
172
-
173
- function get_search_results() {
174
- $query = '';
175
- if ($this->query->search) {
176
- $query = "s={$this->query->search}";
177
- } else {
178
- $this->error("No search query specified. Include 'search' var in your request.");
179
- }
180
- $posts = $this->introspector->get_posts($query);
181
- return $this->response->get_posts_json($posts);
182
- }
183
-
184
- function get_date_index() {
185
- $permalinks = $this->introspector->get_date_archive_permalinks();
186
- $tree = $this->introspector->get_date_archive_tree($permalinks);
187
- return $this->response->get_json(array(
188
- 'permalinks' => $permalinks,
189
- 'tree' => $tree
190
- ));
191
- }
192
-
193
- function get_category_index() {
194
- $categories = $this->introspector->get_categories();
195
- return $this->response->get_json(array(
196
- 'count' => count($categories),
197
- 'categories' => $categories
198
- ));
199
- }
200
-
201
- function get_tag_index() {
202
- $tags = $this->introspector->get_tags();
203
- return $this->response->get_json(array(
204
- 'count' => count($tags),
205
- 'tags' => $tags
206
- ));
207
- }
208
-
209
- function get_author_index() {
210
- $authors = $this->introspector->get_authors();
211
- return $this->response->get_json(array(
212
- 'count' => count($authors),
213
- 'authors' => array_values($authors)
214
- ));
215
- }
216
-
217
- function create_post() {
218
- if (!current_user_can('edit_posts')) {
219
- $this->error("You need to login with a user capable of creating posts.");
220
- }
221
- nocache_headers();
222
- $post = new JSON_API_Post();
223
- $id = $post->create($_REQUEST);
224
- if (empty($id)) {
225
- $this->error("Could not create post.");
226
- }
227
- return $this->response->get_json(array(
228
- 'post' => $post
229
- ));
230
- }
231
-
232
- function submit_comment() {
233
- nocache_headers();
234
- if (empty($_REQUEST['post_id'])) {
235
- $this->error("No post specified. Include 'post_id' var in your request.");
236
- } else if (empty($_REQUEST['name']) ||
237
- empty($_REQUEST['email']) ||
238
- empty($_REQUEST['content'])) {
239
- $this->error("Please include all required arguments (name, email, content).");
240
- } else if (!is_email($_REQUEST['email'])) {
241
- $this->error("Please enter a valid email address.");
242
- }
243
- $pending = new JSON_API_Comment();
244
- return $pending->handle_submission();
245
- }
246
-
247
- function include_value($key) {
248
- return $this->response->is_value_included($key);
249
- }
250
-
251
- }
252
-
253
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
singletons/introspector.php CHANGED
@@ -2,9 +2,8 @@
2
 
3
  class JSON_API_Introspector {
4
 
5
- function get_posts($query = '') {
6
  global $post;
7
- // Returns an array of JSON_API_Post objects
8
  $this->set_posts_query($query);
9
  $output = array();
10
  while (have_posts()) {
@@ -14,13 +13,13 @@ class JSON_API_Introspector {
14
  return $output;
15
  }
16
 
17
- function get_date_archive_permalinks() {
18
  $archives = wp_get_archives('echo=0');
19
  preg_match_all("/href='([^']+)'/", $archives, $matches);
20
  return $matches[1];
21
  }
22
 
23
- function get_date_archive_tree($permalinks) {
24
  $tree = array();
25
  foreach ($permalinks as $url) {
26
  if (preg_match('#(\d{4})/(\d{2})#', $url, $date)) {
@@ -44,7 +43,7 @@ class JSON_API_Introspector {
44
  return $tree;
45
  }
46
 
47
- function get_date_archive_count($year, $month) {
48
  if (!isset($this->month_archives)) {
49
  global $wpdb;
50
  $post_counts = $wpdb->get_results("
@@ -63,7 +62,7 @@ class JSON_API_Introspector {
63
  return $this->month_archives["$year$month"];
64
  }
65
 
66
- function get_categories() {
67
  $wp_categories = get_categories();
68
  $categories = array();
69
  foreach ($wp_categories as $wp_category) {
@@ -75,60 +74,70 @@ class JSON_API_Introspector {
75
  return $categories;
76
  }
77
 
78
- function get_current_category() {
79
  global $json_api;
80
- if (!empty($json_api->query->category_id)) {
81
- return $this->get_category_by_id($json_api->query->category_id);
82
- } else if (!empty($json_api->query->category_slug)) {
83
- return $this->get_category_by_slug($json_api->query->category_slug);
 
 
 
 
 
 
 
 
 
84
  }
85
  return null;
86
  }
87
 
88
- function get_category_object($wp_category) {
89
- return new JSON_API_Category($wp_category);
90
- }
91
-
92
- function get_category_by_id($category_id) {
93
  $wp_category = get_term_by('id', $category_id, 'category');
94
  return $this->get_category_object($wp_category);
95
  }
96
 
97
- function get_category_by_slug($category_slug) {
98
  $wp_category = get_term_by('slug', $category_slug, 'category');
99
  return $this->get_category_object($wp_category);
100
  }
101
 
102
- function get_tags() {
103
  $wp_tags = get_tags();
104
  return array_map(array(&$this, 'get_tag_object'), $wp_tags);
105
  }
106
 
107
- function get_current_tag() {
108
  global $json_api;
109
- if (!empty($json_api->query->tag_id)) {
110
- return $this->get_tag_by_id($json_api->query->tag_id);
111
- } else if (!empty($json_api->query->tag_slug)) {
112
- return $this->get_tag_by_slug($json_api->query->tag_slug);
 
 
 
 
 
 
 
 
 
113
  }
114
  return null;
115
  }
116
 
117
- function get_tag_object($wp_tag) {
118
- return new JSON_API_Tag($wp_tag);
119
- }
120
-
121
- function get_tag_by_id($tag_id) {
122
  $wp_tag = get_term_by('id', $tag_id, 'post_tag');
123
  return $this->get_tag_object($wp_tag);
124
  }
125
 
126
- function get_tag_by_slug($tag_slug) {
127
  $wp_tag = get_term_by('slug', $tag_slug, 'post_tag');
128
  return $this->get_tag_object($wp_tag);
129
  }
130
 
131
- function get_authors() {
132
  global $wpdb;
133
  $author_ids = $wpdb->get_col($wpdb->prepare("
134
  SELECT u.ID, m.meta_value AS last_name
@@ -138,20 +147,40 @@ class JSON_API_Introspector {
138
  AND m.meta_key = 'last_name'
139
  ORDER BY last_name
140
  "));
141
- $all_authors = array_map(array(&$this, 'get_author'), $author_ids);
142
  $active_authors = array_filter($all_authors, array(&$this, 'is_active_author'));
143
  return $active_authors;
144
  }
145
 
146
- function get_author($id) {
147
- return $this->get_author_by_id($id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  }
149
 
150
- function get_author_by_id($id) {
 
 
 
 
151
  return new JSON_API_Author($id);
152
  }
153
 
154
- function get_author_by_login($login) {
 
155
  $id = $wpdb->get_var($wpdb->prepare("
156
  SELECT ID
157
  FROM $wpdb->users
@@ -160,35 +189,7 @@ class JSON_API_Introspector {
160
  return $this->get_author_by_id($id);
161
  }
162
 
163
- function get_current_author() {
164
- $author_id = get_query_var('author');
165
- return $this->get_author($author_id);
166
- }
167
-
168
- function is_active_author($author) {
169
- if (!isset($this->active_authors)) {
170
- $this->active_authors = explode(',', wp_list_authors('html=0&echo=0'));
171
- $this->active_authors = array_map('trim', $this->active_authors);
172
- }
173
- return in_array($author->name, $this->active_authors);
174
- }
175
-
176
- function set_posts_query($query = '') {
177
- // Returns a query string to pass to WP's query_posts() function
178
- if (get_query_var('page')) {
179
- $amp = empty($query) ? '' : '&';
180
- $query .= "{$amp}paged=" . get_query_var('page');
181
- }
182
- if (get_query_var('count')) {
183
- $amp = empty($query) ? '' : '&';
184
- $query .= "{$amp}posts_per_page=" . get_query_var('count');
185
- }
186
- if (!empty($query)) {
187
- query_posts($query);
188
- }
189
- }
190
-
191
- function get_comments($post_id) {
192
  global $wpdb;
193
  $wp_comments = $wpdb->get_results($wpdb->prepare("
194
  SELECT *
@@ -205,7 +206,7 @@ class JSON_API_Introspector {
205
  return $comments;
206
  }
207
 
208
- function get_attachments($post_id) {
209
  $wp_attachments = get_children("post_type=attachment&post_parent=$post_id");
210
  $attachments = array();
211
  if (!empty($wp_attachments)) {
@@ -216,6 +217,70 @@ class JSON_API_Introspector {
216
  return $attachments;
217
  }
218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  }
220
 
221
  ?>
2
 
3
  class JSON_API_Introspector {
4
 
5
+ public function get_posts($query = false) {
6
  global $post;
 
7
  $this->set_posts_query($query);
8
  $output = array();
9
  while (have_posts()) {
13
  return $output;
14
  }
15
 
16
+ public function get_date_archive_permalinks() {
17
  $archives = wp_get_archives('echo=0');
18
  preg_match_all("/href='([^']+)'/", $archives, $matches);
19
  return $matches[1];
20
  }
21
 
22
+ public function get_date_archive_tree($permalinks) {
23
  $tree = array();
24
  foreach ($permalinks as $url) {
25
  if (preg_match('#(\d{4})/(\d{2})#', $url, $date)) {
43
  return $tree;
44
  }
45
 
46
+ public function get_date_archive_count($year, $month) {
47
  if (!isset($this->month_archives)) {
48
  global $wpdb;
49
  $post_counts = $wpdb->get_results("
62
  return $this->month_archives["$year$month"];
63
  }
64
 
65
+ public function get_categories() {
66
  $wp_categories = get_categories();
67
  $categories = array();
68
  foreach ($wp_categories as $wp_category) {
74
  return $categories;
75
  }
76
 
77
+ public function get_current_category() {
78
  global $json_api;
79
+ extract($json_api->query->get(array('id', 'slug', 'category_id', 'category_slug')));
80
+ if ($id || $category_id) {
81
+ if (!$id) {
82
+ $id = $category_id;
83
+ }
84
+ return $this->get_category_by_id($id);
85
+ } else if ($slug || $category_slug) {
86
+ if (!$slug) {
87
+ $slug = $category_slug;
88
+ }
89
+ return $this->get_category_by_slug($slug);
90
+ } else {
91
+ $json_api->error("Include 'id' or 'slug' var in your request.");
92
  }
93
  return null;
94
  }
95
 
96
+ public function get_category_by_id($category_id) {
 
 
 
 
97
  $wp_category = get_term_by('id', $category_id, 'category');
98
  return $this->get_category_object($wp_category);
99
  }
100
 
101
+ public function get_category_by_slug($category_slug) {
102
  $wp_category = get_term_by('slug', $category_slug, 'category');
103
  return $this->get_category_object($wp_category);
104
  }
105
 
106
+ public function get_tags() {
107
  $wp_tags = get_tags();
108
  return array_map(array(&$this, 'get_tag_object'), $wp_tags);
109
  }
110
 
111
+ public function get_current_tag() {
112
  global $json_api;
113
+ extract($json_api->query->get(array('id', 'slug', 'tag_id', 'tag_slug')));
114
+ if ($id || $tag_id) {
115
+ if (!$id) {
116
+ $id = $tag_id;
117
+ }
118
+ return $this->get_tag_by_id($id);
119
+ } else if ($slug || $tag_slug) {
120
+ if (!$slug) {
121
+ $slug = $tag_slug;
122
+ }
123
+ return $this->get_tag_by_slug($slug);
124
+ } else {
125
+ $json_api->error("Include 'id' or 'slug' var in your request.");
126
  }
127
  return null;
128
  }
129
 
130
+ public function get_tag_by_id($tag_id) {
 
 
 
 
131
  $wp_tag = get_term_by('id', $tag_id, 'post_tag');
132
  return $this->get_tag_object($wp_tag);
133
  }
134
 
135
+ public function get_tag_by_slug($tag_slug) {
136
  $wp_tag = get_term_by('slug', $tag_slug, 'post_tag');
137
  return $this->get_tag_object($wp_tag);
138
  }
139
 
140
+ public function get_authors() {
141
  global $wpdb;
142
  $author_ids = $wpdb->get_col($wpdb->prepare("
143
  SELECT u.ID, m.meta_value AS last_name
147
  AND m.meta_key = 'last_name'
148
  ORDER BY last_name
149
  "));
150
+ $all_authors = array_map(array(&$this, 'get_author_by_id'), $author_ids);
151
  $active_authors = array_filter($all_authors, array(&$this, 'is_active_author'));
152
  return $active_authors;
153
  }
154
 
155
+ public function get_current_author() {
156
+ global $json_api;
157
+ extract($json_api->query->get(array('id', 'slug', 'author_id', 'author_slug')));
158
+ if ($id || $author_id) {
159
+ if (!$id) {
160
+ $id = $author_id;
161
+ }
162
+ return $this->get_author_by_id($id);
163
+ } else if ($slug || $author_slug) {
164
+ if (!$slug) {
165
+ $slug = $author_slug;
166
+ }
167
+ return $this->get_author_by_login($slug);
168
+ } else {
169
+ $json_api->error("Include 'id' or 'slug' var in your request.");
170
+ }
171
+ return null;
172
  }
173
 
174
+ public function get_author_by_id($id) {
175
+ $id = get_the_author_meta('ID', $id);
176
+ if (!$id) {
177
+ return null;
178
+ }
179
  return new JSON_API_Author($id);
180
  }
181
 
182
+ public function get_author_by_login($login) {
183
+ global $wpdb;
184
  $id = $wpdb->get_var($wpdb->prepare("
185
  SELECT ID
186
  FROM $wpdb->users
189
  return $this->get_author_by_id($id);
190
  }
191
 
192
+ public function get_comments($post_id) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  global $wpdb;
194
  $wp_comments = $wpdb->get_results($wpdb->prepare("
195
  SELECT *
206
  return $comments;
207
  }
208
 
209
+ public function get_attachments($post_id) {
210
  $wp_attachments = get_children("post_type=attachment&post_parent=$post_id");
211
  $attachments = array();
212
  if (!empty($wp_attachments)) {
217
  return $attachments;
218
  }
219
 
220
+ public function attach_child_posts(&$post) {
221
+ $post->children = array();
222
+ $wp_children = get_posts(array(
223
+ 'post_type' => $post->type,
224
+ 'post_parent' => $post->id,
225
+ 'order' => 'ASC',
226
+ 'orderby' => 'menu_order'
227
+ ));
228
+ foreach ($wp_children as $wp_post) {
229
+ $post->children[] = new JSON_API_Post($wp_post);
230
+ }
231
+ foreach ($post->children as $child) {
232
+ $this->attach_child_posts($child);
233
+ }
234
+ }
235
+
236
+ protected function get_category_object($wp_category) {
237
+ if (!$wp_category) {
238
+ return null;
239
+ }
240
+ return new JSON_API_Category($wp_category);
241
+ }
242
+
243
+ protected function get_tag_object($wp_tag) {
244
+ if (!$wp_tag) {
245
+ return null;
246
+ }
247
+ return new JSON_API_Tag($wp_tag);
248
+ }
249
+
250
+ protected function is_active_author($author) {
251
+ if (!isset($this->active_authors)) {
252
+ $this->active_authors = explode(',', wp_list_authors(array(
253
+ 'html' => false,
254
+ 'echo' => false,
255
+ 'exclude_admin' => false
256
+ )));
257
+ $this->active_authors = array_map('trim', $this->active_authors);
258
+ }
259
+ return in_array($author->name, $this->active_authors);
260
+ }
261
+
262
+ protected function set_posts_query($query = false) {
263
+ global $json_api, $wp_query;
264
+
265
+ if (!$query) {
266
+ $query = array();
267
+ }
268
+
269
+ $query = array_merge($query, $wp_query->query);
270
+
271
+ if ($json_api->query->page) {
272
+ $query['paged'] = $json_api->query->page;
273
+ }
274
+
275
+ if ($json_api->query->count) {
276
+ $query['posts_per_page'] = $json_api->query->count;
277
+ }
278
+
279
+ if (!empty($query)) {
280
+ query_posts($query);
281
+ }
282
+ }
283
+
284
  }
285
 
286
  ?>
singletons/query.php CHANGED
@@ -2,112 +2,140 @@
2
 
3
  class JSON_API_Query {
4
 
5
- var $vars = array(
6
- 'json', // Determines which API controller method will be called
7
- // Expects one of the following values:
8
- // * 'get_recent_posts'
9
- // * 'get_post'
10
- // * 'get_page'
11
- // * 'get_date_posts'
12
- // * 'get_category_posts'
13
- // * 'get_tag_posts'
14
- // * 'get_author_posts'
15
- // * 'get_date_index'
16
- // * 'get_category_index'
17
- // * 'get_tag_index'
18
- // * 'get_author_index'
19
- // * 'get_search_results'
20
- // * 'submit_comment'
21
- // * Any non-empty value to trigger the API implicitly
22
- 'callback', // Triggers a JSONP-style callback with the resulting data
23
- 'dev', // Triggers a developer-friendly print_r() output
24
- 'redirect', // Triggers a redirect response to the specified URL
25
- 'page', // Returns a specific page of results
26
- 'count', // Controls the number of posts returned
27
- 'post_id', // Used by the get_post API method
28
- 'post_slug', // Used by the get_post API method
29
- 'page_id', // Used by the get_page API method
30
- 'page_slug', // Used by the get_page API method
31
- 'search', // Used by the get_search_results API method
32
- 'category_id', // Used by get_category_posts API method
33
- 'category_slug', // Used by get_category_posts API method
34
- 'tag_id', // Used by get_tag_posts API method
35
- 'tag_slug', // Used by get_tag_posts API method
36
- 'author_id', // Used by get_author_posts API method
37
- 'author_slug', // Used by get_author_posts API method
38
- 'date', // Used by get_date_posts API method
39
- // Expects 'YYYY', 'YYYYMM' or 'YYYYMMDD'
40
- 'date_format', // Changes the format of date values
41
- // Uses the same syntax as PHP's date() function
42
- // Default value is Y-m-d H:i:s
43
- 'read_more', // Changes the 'read more' link text in post content
44
- 'include', // Specifies which post data fields to include
45
- // Expects a comma-separated list of post fields
46
- // Leaving this empty includes *all* fields
47
- 'exclude', // Specifies which post data fields to exclude
48
- // Expects a comma-separated list of post fields
49
- 'custom_fields', // Includes values from posts' Custom Fields
50
- // Expects a comma-separated list of custom field keys
51
- 'author_meta' // Includes additional author metadata
52
- // Should be a comma-separated list of metadata fields
53
-
54
- // Note about include/exclude vars:
55
- // The default behavior includes all post values. You only need to
56
- // specify one of include or exclude -- the former will implicitly leave
57
- // out those fields you haven't specified and the latter will implicitly
58
- // include them. For example, a query that uses exclude=comments will
59
- // include everything *except* the comments, so there's no need to also
60
- // specify anything with the include argument.
61
-
62
- );
63
-
64
  // Default values
65
- var $date_format = 'Y-m-d H:i:s';
66
- var $read_more = 'Read more';
 
 
67
 
68
- function JSON_API_Query() {
69
  // Register JSON API query vars
70
  add_filter('query_vars', array(&$this, 'query_vars'));
71
  }
72
 
73
- function query_vars($wp_vars) {
74
- // This makes our variables available from the get_query_var WP function
75
- return array_merge($wp_vars, $this->vars);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
 
78
- function setup() {
79
- // Translation between WordPress vars and natively understood vars
80
  $wp_translation = array(
81
- 'p' => 'post_id',
82
- 'name' => 'post_slug',
 
83
  'page_id' => 'page_id',
84
- 'pagename' => 'page_slug',
85
- 'cat' => 'category_id',
86
- 'category_name' => 'category_slug',
87
- 'tag' => 'tag_slug',
88
- 'author' => 'author_id',
89
- 'author_name' => 'author_slug',
90
- 'm' => 'date',
91
- 's' => 'search'
 
 
92
  );
93
-
94
- foreach ($wp_translation as $wp_var => $var) {
95
- // Assign WP query vars to natively understood vars
96
- $this->$var = get_query_var($wp_var);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
-
99
- foreach ($this->vars as $var) {
100
- // Assign query vars as object properties for convenience
101
- $value = get_query_var($var);
102
- if ($value) {
103
- $this->$var = $value;
104
- } else if (!isset($this->$var)) {
105
- $this->$var = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
 
 
107
  }
108
  }
109
 
110
- function get_method() {
 
 
 
111
  // Returns an appropriate API method name or false. Four possible outcomes:
112
  // 1. API isn't being invoked at all (return false)
113
  // 2. A specific API method was requested (return method name)
@@ -117,41 +145,50 @@ class JSON_API_Query {
117
  // Note:
118
  // The implicit outcome (3) is invoked by setting the json query var to a
119
  // non-empty value on any WordPress page:
120
- // * http://example.org/2009/11/10/hello-world/?json=1
121
- // * http://example.org/2009/11/?json=1
122
- // * http://example.org/category/foo?json=1
123
 
124
- $this->method = get_query_var('json');
 
 
 
 
 
125
 
126
- if (empty($this->method)) {
127
  // Case 1: we're not being invoked (done!)
128
  return false;
129
- } else if (method_exists('JSON_API_Controller', $this->method)) {
130
  // Case 2: an explicit method was specified
131
- return $this->method;
132
-
133
- // Case 3: choose the method implicitly based on which page we're on...
134
-
135
- } else if (is_search()) {
136
- return 'get_search_results';
137
- } else if (is_home()) {
138
- return 'get_recent_posts';
139
- } else if (is_page()) {
140
- return 'get_page';
141
- } else if (is_single()) {
142
- return 'get_post';
143
- } else if (is_category()) {
144
- return 'get_category_posts';
145
- } else if (is_tag()) {
146
- return 'get_tag_posts';
147
- } else if (is_author()) {
148
- return 'get_author_posts';
149
- } else if (is_date()) {
150
- return 'get_date_posts';
151
- } else {
152
- // Case 4: either the method doesn't exist or we don't support this page
153
- return 'error';
 
 
154
  }
 
 
155
  }
156
 
157
  }
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-Z_]+)(\/|\.)[a-zA-Z_]+$/', $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)
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("Uknown 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
  }
singletons/response.php CHANGED
@@ -2,42 +2,18 @@
2
 
3
  class JSON_API_Response {
4
 
5
- function JSON_API_Response() {
6
- $this->setup_include_values();
7
- }
8
-
9
- function setup_include_values() {
10
  $this->include_values = array();
11
- if (get_query_var('include')) {
12
- $this->include_values = explode(',', get_query_var('include'));
13
  }
14
- if (get_query_var('exclude')) {
15
- $exclude = explode(',', get_query_var('exclude'));
16
  $this->include_values = array_diff($this->include_values, $exclude);
17
  }
18
  }
19
 
20
- function get_posts_json($posts, $status = 'ok') {
21
- global $wp_query;
22
- return $this->get_json(array(
23
- 'count' => count($posts),
24
- 'count_total' => $wp_query->found_posts,
25
- 'pages' => $wp_query->max_num_pages,
26
- 'posts' => $posts
27
- ), $status);
28
- }
29
-
30
- function get_posts_object_json($posts, $object, $status = 'ok') {
31
- global $wp_query;
32
- $object_key = strtolower(substr(get_class($object), 9));
33
- return $this->get_json(array(
34
- 'count' => count($posts),
35
- 'pages' => $wp_query->max_num_pages,
36
- $object_key => $object,
37
- 'posts' => $posts
38
- ), $status);
39
- }
40
-
41
  function get_json($data, $status = 'ok') {
42
  // Include a status value with the response
43
  if (is_array($data)) {
@@ -49,17 +25,14 @@ class JSON_API_Response {
49
 
50
  $data = apply_filters('json_api_encode', $data);
51
 
52
- if (!empty($_REQUEST['dev'])) {
53
- // Don't JSON encode the data in dev mode
54
- return $data;
55
- } else if (function_exists('json_encode')) {
56
  // Use the built-in json_encode function if it's available
57
  return json_encode($data);
58
  } else {
59
  // Use PEAR's Services_JSON encoder otherwise
60
  if (!class_exists('Services_JSON')) {
61
- global $json_api_dir;
62
- require_once "$json_api_dir/library/JSON.php";
63
  }
64
  $json = new Services_JSON();
65
  return $json->encode($data);
@@ -76,13 +49,16 @@ class JSON_API_Response {
76
 
77
  function respond($result, $status = 'ok') {
78
  global $json_api;
 
79
  $status_redirect = "redirect_$status";
80
- if ($json_api->query->dev) {
81
- // Output the result with print_r
82
  if (!headers_sent()) {
83
  header('Content-Type: text/plain; charset: UTF-8', true);
 
 
84
  }
85
- print_r($result);
86
  } else if (!empty($_REQUEST[$status_redirect])) {
87
  wp_redirect($_REQUEST[$status_redirect]);
88
  } else if ($json_api->query->redirect) {
@@ -90,10 +66,10 @@ class JSON_API_Response {
90
  wp_redirect($url);
91
  } else if ($json_api->query->callback) {
92
  // Run a JSONP-style callback with the result
93
- $this->callback($json_api->query->callback, $result);
94
  } else {
95
- // Output the result in JSON format
96
- $this->output($result);
97
  }
98
  exit;
99
  }
@@ -104,7 +80,7 @@ class JSON_API_Response {
104
  header("Content-Type: application/json; charset=$charset", true);
105
  header("Content-Disposition: attachment; filename=\"json_api.json\"", true);
106
  }
107
- echo $result;
108
  }
109
 
110
  function callback($callback, $result) {
@@ -134,6 +110,63 @@ class JSON_API_Response {
134
  return $url;
135
  }
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
 
139
  ?>
2
 
3
  class JSON_API_Response {
4
 
5
+ function setup() {
6
+ global $json_api;
 
 
 
7
  $this->include_values = array();
8
+ if ($json_api->query->include) {
9
+ $this->include_values = explode(',', $json_api->query->include);
10
  }
11
+ if ($json_api->query->exclude) {
12
+ $exclude = explode(',', $json_api->query->exclude);
13
  $this->include_values = array_diff($this->include_values, $exclude);
14
  }
15
  }
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  function get_json($data, $status = 'ok') {
18
  // Include a status value with the response
19
  if (is_array($data)) {
25
 
26
  $data = apply_filters('json_api_encode', $data);
27
 
28
+ if (function_exists('json_encode')) {
 
 
 
29
  // Use the built-in json_encode function if it's available
30
  return json_encode($data);
31
  } else {
32
  // Use PEAR's Services_JSON encoder otherwise
33
  if (!class_exists('Services_JSON')) {
34
+ $dir = dirname(dirname(__FILE__));
35
+ require_once "$dir/library/JSON.php";
36
  }
37
  $json = new Services_JSON();
38
  return $json->encode($data);
49
 
50
  function respond($result, $status = 'ok') {
51
  global $json_api;
52
+ $json = $this->get_json($result, $status);
53
  $status_redirect = "redirect_$status";
54
+ if ($json_api->query->dev || !empty($_REQUEST['dev'])) {
55
+ // Output the result in a human-redable format
56
  if (!headers_sent()) {
57
  header('Content-Type: text/plain; charset: UTF-8', true);
58
+ } else {
59
+ echo '<pre>';
60
  }
61
+ echo $this->prettify($json);
62
  } else if (!empty($_REQUEST[$status_redirect])) {
63
  wp_redirect($_REQUEST[$status_redirect]);
64
  } else if ($json_api->query->redirect) {
66
  wp_redirect($url);
67
  } else if ($json_api->query->callback) {
68
  // Run a JSONP-style callback with the result
69
+ $this->callback($json_api->query->callback, $json);
70
  } else {
71
+ // Output the result
72
+ $this->output($json);
73
  }
74
  exit;
75
  }
80
  header("Content-Type: application/json; charset=$charset", true);
81
  header("Content-Disposition: attachment; filename=\"json_api.json\"", true);
82
  }
83
+ echo $result;
84
  }
85
 
86
  function callback($callback, $result) {
110
  return $url;
111
  }
112
 
113
+ function prettify($ugly) {
114
+ $pretty = "";
115
+ $indent = "";
116
+ $last = '';
117
+ $pos = 0;
118
+ $level = 0;
119
+ $string = false;
120
+ while ($pos < strlen($ugly)) {
121
+ $char = substr($ugly, $pos++, 1);
122
+ if (!$string) {
123
+ if ($char == '{' || $char == '[') {
124
+ if ($char == '[' && substr($ugly, $pos, 1) == ']') {
125
+ $pretty .= "[]";
126
+ $pos++;
127
+ } else if ($char == '{' && substr($ugly, $pos, 1) == '}') {
128
+ $pretty .= "{}";
129
+ $pos++;
130
+ } else {
131
+ $pretty .= "$char\n";
132
+ $indent = str_repeat(' ', ++$level);
133
+ $pretty .= "$indent";
134
+ }
135
+ } else if ($char == '}' || $char == ']') {
136
+ $indent = str_repeat(' ', --$level);
137
+ if ($last != '}' && $last != ']') {
138
+ $pretty .= "\n$indent";
139
+ } else if (substr($pretty, -2, 2) == ' ') {
140
+ $pretty = substr($pretty, 0, -2);
141
+ }
142
+ $pretty .= $char;
143
+ if (substr($ugly, $pos, 1) == ',') {
144
+ $pretty .= ",";
145
+ $last = ',';
146
+ $pos++;
147
+ }
148
+ $pretty .= "\n$indent";
149
+ } else if ($char == ':') {
150
+ $pretty .= ": ";
151
+ } else if ($char == ',') {
152
+ $pretty .= ",\n$indent";
153
+ } else if ($char == '"') {
154
+ $pretty .= '"';
155
+ $string = true;
156
+ } else {
157
+ $pretty .= $char;
158
+ }
159
+ } else {
160
+ if ($last != '\\' && $char == '"') {
161
+ $string = false;
162
+ }
163
+ $pretty .= $char;
164
+ }
165
+ $last = $char;
166
+ }
167
+ return $pretty;
168
+ }
169
+
170
  }
171
 
172
  ?>