From 2aa8a390d7fba9ac5c0faf945ba824826e4ce607 Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Sun, 8 Dec 2013 18:24:51 -0600 Subject: [PATCH 01/42] Updated plugin activation and deactivation functions to be multisite compatible. Check if the plugin is network activated in Multisite, and if so run activation or deactivation function for each public blog in the network. Initial pass at issue #48. --- plugin.php | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/plugin.php b/plugin.php index cca3f1cd90..ac6931a166 100644 --- a/plugin.php +++ b/plugin.php @@ -115,19 +115,50 @@ function json_api_loaded() { add_action( 'template_redirect', 'json_api_loaded', -100 ); /** - * Flush the rewrite rules on activation + * Register routes and flush the rewrite rules on activation. */ -function json_api_activation() { - json_api_register_rewrites(); - flush_rewrite_rules(); +function json_api_activation( $network_wide ) { + if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) { + + $mu_blogs = wp_get_sites(); + + foreach ( $mu_blogs as $mu_blog ) { + + switch_to_blog( $mu_blog['blog_id'] ); + json_api_register_rewrites(); + flush_rewrite_rules(); + } + + restore_current_blog(); + + } else { + + json_api_register_rewrites(); + flush_rewrite_rules(); + } } register_activation_hook( __FILE__, 'json_api_activation' ); /** - * Also flush the rewrite rules on deactivation + * Flush the rewrite rules on deactivation */ -function json_api_deactivation() { - flush_rewrite_rules(); +function json_api_deactivation( $network_wide ) { + if ( function_exists( 'is_multisite' ) && is_multisite() && $network_wide ) { + + $mu_blogs = wp_get_sites(); + + foreach ( $mu_blogs as $mu_blog ) { + + switch_to_blog( $mu_blog['blog_id'] ); + flush_rewrite_rules(); + } + + restore_current_blog(); + + } else { + + flush_rewrite_rules(); + } } register_deactivation_hook( __FILE__, 'json_api_deactivation' ); From cdca94afbc14dcd8808113c7d2773146da1ca39e Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Wed, 11 Dec 2013 14:38:21 -0600 Subject: [PATCH 02/42] Added conditional to WP_JSON_Posts->prepare_author() to verify the $user object is set. --- lib/class-wp-json-posts.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index 0bef968f22..210a5f0713 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -632,8 +632,10 @@ protected function prepare_meta( $post_id ) { protected function prepare_author( $author ) { $user = get_user_by( 'id', $author ); - if (!$author) + if (! $author || ! is_object( $user ) ) { return null; + } + $author = array( 'ID' => $user->ID, From 18040fe24222407635b7dc3e7d084e17a86d45b0 Mon Sep 17 00:00:00 2001 From: Anders Lisspers Date: Thu, 12 Dec 2013 16:26:24 +0100 Subject: [PATCH 03/42] Fixes bugs with attaching featured images to posts This commit fixes two bugs in `WP_JSON_Media::attachThubmnail()`. - `attachThumbnail()` should dp nothing if `$update` is false since the post ID is needed. - The post ID must be fetched from the `$post` array. --- lib/class-wp-json-media.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-json-media.php b/lib/class-wp-json-media.php index 9573d208a2..2630897cf2 100644 --- a/lib/class-wp-json-media.php +++ b/lib/class-wp-json-media.php @@ -386,10 +386,14 @@ public function preinsertCheck( $status, $post, $data ) { * @param boolean $update Is this an update? */ public function attachThumbnail( $post, $data, $update ) { + if ( ! $update ) { + return; + } + if ( ! empty( $data['featured_image'] ) ) { // Already verified in preinsertCheck() $thumbnail = $this->getPost( $data['featured_image'], 'child' ); - set_post_thumbnail( $post_ID, $thumbnail['ID'] ); + set_post_thumbnail( $post['ID'], $thumbnail['ID'] ); } } From 3337ee29b0ddefdba6610c040e6c6178c39d22e4 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 17 Dec 2013 07:16:14 +1000 Subject: [PATCH 04/42] Add RSD and Link headers for discovery Fixes #40 --- plugin.php | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/plugin.php b/plugin.php index 78afccd940..c72fe5bdba 100644 --- a/plugin.php +++ b/plugin.php @@ -142,6 +142,45 @@ function json_register_scripts() { } add_action( 'wp_enqueue_scripts', 'json_register_scripts', -100 ); +/** + * Add the API URL to the WP RSD endpoint + */ +function json_output_rsd() { +?> + +\n"; +} +add_action( 'wp_head', 'json_output_link_wp_head', 10, 0 ); + +/** + * Send a Link header for the API + */ +function json_output_link_header() { + if ( headers_sent() ) + return; + + $api_root = get_json_url(); + + if ( empty($api_root) ) + return; + + header('Link: <' . $api_root . '>; rel="https://github.com/WP-API/WP-API"', false); +} +add_action( 'template_redirect', 'json_output_link_header', 11, 0 ); + /** * Get URL to a JSON endpoint on a site * From c21fb3c9cbc0f65f6bd6b60ca6384e07489c04e6 Mon Sep 17 00:00:00 2001 From: Aaron Jorbin Date: Thu, 26 Dec 2013 11:57:03 -0500 Subject: [PATCH 05/42] Update link in README due to project owner change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c105cc24c..cce3bbc1fa 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ run by Ryan McCue and is part of the WordPress 2013 GSoC projects. ## Documentation Read the [plugin's documentation][docs]. -[docs]: https://github.com/rmccue/WP-API/tree/master/docs +[docs]: https://github.com/WP-API/WP-API/tree/master/docs ## Installation From 80e9fcbdb650fa31c32cdba2d9230fefd6bf1e64 Mon Sep 17 00:00:00 2001 From: Bryan Petty Date: Mon, 30 Dec 2013 15:39:34 -0700 Subject: [PATCH 06/42] Added unit testing skeleton with mostly stubbed server tests. --- .travis.yml | 50 +++++++++++++ phpunit.xml.dist | 8 +++ plugin.php | 5 ++ tests/bootstrap.php | 22 ++++++ tests/test_json_plugin.php | 59 ++++++++++++++++ tests/test_json_server.php | 141 +++++++++++++++++++++++++++++++++++++ 6 files changed, 285 insertions(+) create mode 100644 .travis.yml create mode 100644 phpunit.xml.dist create mode 100644 tests/bootstrap.php create mode 100644 tests/test_json_plugin.php create mode 100644 tests/test_json_server.php diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..1c5bdaaf76 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,50 @@ +# Travis CI Configuration File + +# Tell Travis CI we're using PHP +language: php + +# PHP version used in first build configuration. +php: + - "5.5" + +# WordPress version used in first build configuration. +env: + - WP_VERSION=master + +# Next we define our matrix of additional build configurations to test against. +# The versions listed above will automatically create our first configuration, +# so it doesn't need to be re-defined below. + +# WP_VERSION specifies the tag to use. The way these tests are configured to run +# requires at least WordPress 3.8. Specify "master" to test against SVN trunk. + +# Note that Travis CI supports listing these above to automatically build a +# matrix of configurations, but we're being nice here by manually building a +# total of four configurations even though we're testing 4 versions of PHP +# along with 2 versions of WordPress (which would build 8 configs otherwise). +# This takes half as long to run while still providing adequate coverage. + +matrix: + include: + - php: "5.3" + env: WP_VERSION=master + - php: "5.4" + env: WP_VERSION=3.8 + - php: "5.2" + env: WP_VERSION=3.8 + +# Clones WordPress and configures our testing environment. +before_script: + - export PLUGIN_SLUG=$(basename $(pwd)) + - git clone https://github.com/tierra/wordpress.git /tmp/wordpress + - git clone . "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" + - cd /tmp/wordpress + - git checkout $WP_VERSION + - mysql -e "CREATE DATABASE wordpress_tests;" -uroot + - cp wp-tests-config-sample.php wp-tests-config.php + - sed -i "s/youremptytestdbnamehere/wordpress_tests/" wp-tests-config.php + - sed -i "s/yourusernamehere/travis/" wp-tests-config.php + - sed -i "s/yourpasswordhere//" wp-tests-config.php + - cd "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" + +script: phpunit diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000000..66723a23d1 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,8 @@ + + + + + tests + + + \ No newline at end of file diff --git a/plugin.php b/plugin.php index 4adf7400d1..d0a0bc1e21 100644 --- a/plugin.php +++ b/plugin.php @@ -67,6 +67,11 @@ function json_api_default_filters($server) { /** * Load the JSON API + * + * @todo Extract code that should be unit tested into isolated methods such as + * the wp_json_server_class filter and serving requests. This would also + * help for code re-use by wp-json.php endpoint. Note that we can't unit + * test any method that calls die(). */ function json_api_loaded() { if ( empty( $GLOBALS['wp']->query_vars['json_route'] ) ) diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000000..42cd9587a1 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,22 @@ + array( 'WP-API/plugin.php' ), +); + +// If the develop repo location is defined (as WP_DEVELOP_DIR), use that +// location. Otherwise, we'll just assume that this plugin is installed in a +// WordPress develop SVN checkout. + +if( false !== getenv( 'WP_DEVELOP_DIR' ) ) { + require getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit/includes/bootstrap.php'; +} else { + require '../../../../tests/phpunit/includes/bootstrap.php'; +} diff --git a/tests/test_json_plugin.php b/tests/test_json_plugin.php new file mode 100644 index 0000000000..a45a0cfc4e --- /dev/null +++ b/tests/test_json_plugin.php @@ -0,0 +1,59 @@ +markTestSkipped( 'Travis CI was not detected.' ); + + $requested_version = getenv( 'WP_VERSION' ) . '-src'; + + // The "master" version requires special handling. + if ( $requested_version == 'master-src' ) { + $file = file_get_contents( 'https://raw.github.com/tierra/wordpress/master/src/wp-includes/version.php' ); + preg_match( '#\$wp_version = \'([^\']+)\';#', $file, $matches ); + $requested_version = $matches[1]; + } + + $this->assertEquals( get_bloginfo( 'version' ), $requested_version ); + } + + /** + * The plugin should be installed and activated. + */ + function test_plugin_activated() { + $this->assertTrue( is_plugin_active( 'WP-API/plugin.php' ) ); + } + + /** + * The json_api_init hook should have been registered with init, and should + * have a default priority of 10. + */ + function test_init_action_added() { + $this->assertEquals( 10, has_action( 'init', 'json_api_init' ) ); + } + + /** + * The json_route query variable should be registered. + */ + function test_json_route_query_var() { + global $wp; + $this->assertTrue( in_array( 'json_route', $wp->public_query_vars ) ); + } + +} diff --git a/tests/test_json_server.php b/tests/test_json_server.php new file mode 100644 index 0000000000..e60fd99d9f --- /dev/null +++ b/tests/test_json_server.php @@ -0,0 +1,141 @@ +factory->user->create( array( + 'user_login' => 'basic_auth', + 'user_pass' => 'basic_auth' + ) ); + + $_SERVER['PHP_AUTH_USER'] = 'basic_auth'; + $_SERVER['PHP_AUTH_PW'] = 'basic_auth'; + + $result = $wp_json_server->check_authentication(); + $this->assertTrue( $result instanceof WP_User ); + + unset( $_SERVER['PHP_AUTH_USER'] ); + unset( $_SERVER['PHP_AUTH_PW'] ); + } + + /** + * Errors should convert to arrays cleanly. + */ + function test_error_to_array() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Test the format of errors encoded to json. + */ + function test_json_error() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * The default routes should contain all valid callbacks. This test mostly + * ensures that a set of valid routes have been properly defined. + */ + function test_get_routes() { + // NB: I'd mostly iterate over all endpoints, checking for is_callable(), + // and dispatch() does this check, but that's only at runtime, but + // you could use that as a template for this test. + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Ensure the dispatcher calls valid routes with the appropriate method. + */ + function test_dispatch() { + // NB: The dispatcher makes use of get_raw_data() which may not work + // properly with unit tests, so that might need a workaround. + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Test sort_callback_params(). + * + * @todo This should probably be broken out into a few unique tests with + * various methods with different reflection properties. + */ + function test_sort_callback_params() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Test for valid link header format. + * + * @todo This will likely require some changes to $server->header() so it's + * possible to actually write unit tests for headers. + */ + function test_link_header() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Ensure pagination link headers work properly with valid page counts. + */ + function test_query_navigation_headers() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Objects passed through prepare_response() should be expanded to arrays. + */ + function test_prepare_response() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * JsonSerializable data passed through prepare_response() should be + * expanded properly. + */ + function test_json_serializable() { + $this->markTestIncomplete('Missing test implementation.'); + } + + /** + * Test if local RFC3339 dates are converted to MySQL datetimes with the + * appropriate GMT timezone. + */ + function test_get_date_with_gmt() { + $this->markTestIncomplete('Missing test implementation.'); + } + +} From f0e5a245925e6a0db6ebfddb3e36ca80092913d5 Mon Sep 17 00:00:00 2001 From: "K.Adam White" Date: Mon, 10 Feb 2014 22:49:46 -0500 Subject: [PATCH 07/42] Link collection filtering docs to URL formatting guide As noted in the compatibility document, URLs do not provide any standard convention for passing query parameters as an array. Linking the collection filtering guide to the article explaining the URL array syntax used in this API will reduce confusion for new users. --- docs/guides/working-with-posts.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guides/working-with-posts.md b/docs/guides/working-with-posts.md index cfc5a3e859..a696b199e3 100644 --- a/docs/guides/working-with-posts.md +++ b/docs/guides/working-with-posts.md @@ -60,7 +60,8 @@ The last parameter is the `filter` parameter. This gives you full access to the level of access you have, not all parameters will be available, so check the [schema][] for the available parameters. A good assumption to make is that anything you can put in a query on the site itself (such as `?s=...` for -searches) will be available. +searches) will be available. You can specify filter parameters in a request +using [array-style URL formatting][]. Creating and Editing Posts @@ -283,3 +284,4 @@ take a look at the other APIs, or look at documentation on the specifics. [Extending the API]: extending.md [schema]: ../schema.md [WP_Query]: http://codex.wordpress.org/Class_Reference/WP_Query +[array-style URL formatting]: ../compatibility.md#inputting-data-as-an-array From 635e60847bca3384d82fb291f452cdb98e5e07af Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 8 Dec 2013 19:14:47 +1000 Subject: [PATCH 08/42] Introduce Response/ResponseInterface --- lib/class-wp-json-media.php | 5 +- lib/class-wp-json-posts.php | 34 +++--- lib/class-wp-json-response.php | 155 ++++++++++++++++++++++++ lib/class-wp-json-responseinterface.php | 35 ++++++ lib/class-wp-json-server.php | 99 +++++++++++++-- plugin.php | 3 + 6 files changed, 303 insertions(+), 28 deletions(-) create mode 100644 lib/class-wp-json-response.php create mode 100644 lib/class-wp-json-responseinterface.php diff --git a/lib/class-wp-json-media.php b/lib/class-wp-json-media.php index 9573d208a2..62b4c8e9a6 100644 --- a/lib/class-wp-json-media.php +++ b/lib/class-wp-json-media.php @@ -236,9 +236,8 @@ public function uploadAttachment( $_files, $_headers ) { wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) ); } - $this->server->send_status( 201 ); - $this->server->header( 'Location', json_url( '/media/' . $id ) ); - return $this->getPost( $id, 'edit' ); + $headers = array( 'Location' => json_url( '/media/' . $id ) ); + return new WP_JSON_Response( $this->getPost( $id, 'edit' ), 201, $headers ); } /** diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index e440b25356..63c02fb244 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -123,15 +123,18 @@ public function getPosts( $filter = array(), $context = 'view', $type = 'post', $post_query = new WP_Query(); $posts_list = $post_query->query( $query ); - $this->server->query_navigation_headers( $post_query ); + $response = new WP_JSON_Response(); + $response->query_navigation_headers( $post_query ); - if ( ! $posts_list ) - return array(); + if ( ! $posts_list ) { + $response->set_data( array() ); + return $response; + } // holds all the posts data $struct = array(); - $this->server->header( 'Last-Modified', mysql2date( 'D, d M Y H:i:s', get_lastpostmodified( 'GMT' ), 0 ).' GMT' ); + $response->header( 'Last-Modified', mysql2date( 'D, d M Y H:i:s', get_lastpostmodified( 'GMT' ), 0 ).' GMT' ); foreach ( $posts_list as $post ) { $post = get_object_vars( $post ); @@ -140,11 +143,12 @@ public function getPosts( $filter = array(), $context = 'view', $type = 'post', if ( ! $this->checkReadPermission( $post ) ) continue; - $this->server->link_header( 'item', json_url( '/posts/' . $post['ID'] ), array( 'title' => $post['post_title'] ) ); + $response->link_header( 'item', json_url( '/posts/' . $post['ID'] ), array( 'title' => $post['post_title'] ) ); $struct[] = $this->prepare_post( $post, $context ); } + $response->set_data( $struct ); - return $struct; + return $response; } /** @@ -211,10 +215,10 @@ function newPost( $data ) { $result = $this->insert_post( $data ); if ( is_string( $result ) || is_int( $result ) ) { - $this->server->send_status( 201 ); - $this->server->header( 'Location', json_url( '/posts/' . $result ) ); - - return $this->getPost( $result ); + $response = $this->getPost( $result ); + $response->set_status( 201 ); + $response->header( 'Location', json_url( '/posts/' . $result ) ); + return $response; } elseif ( $result instanceof IXR_Error ) { return new WP_Error( 'json_insert_error', $result->message, array( 'status' => $result->code ) ); @@ -249,18 +253,20 @@ public function getPost( $id, $context = 'view' ) { // Link headers (see RFC 5988) - $this->server->header( 'Last-Modified', mysql2date( 'D, d M Y H:i:s', $post['post_modified_gmt'] ) . 'GMT' ); + $response = new WP_JSON_Response(); + $response->header( 'Last-Modified', mysql2date( 'D, d M Y H:i:s', $post['post_modified_gmt'] ) . 'GMT' ); $post = $this->prepare_post( $post, $context ); if ( is_wp_error( $post ) ) return $post; foreach ( $post['meta']['links'] as $rel => $url ) { - $this->server->link_header( $rel, $url ); + $response->link_header( $rel, $url ); } - $this->server->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) ); + $response->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) ); - return $post; + $response->set_data( $post ); + return $response; } /** diff --git a/lib/class-wp-json-response.php b/lib/class-wp-json-response.php new file mode 100644 index 0000000000..f3e9f7182c --- /dev/null +++ b/lib/class-wp-json-response.php @@ -0,0 +1,155 @@ +data = $data; + $this->set_status( $status ); + $this->set_headers( $headers ); + } + + /** + * Get headers associated with the response + * + * @return array Map of header name to header value + */ + public function get_headers() { + return $this->headers; + } + + /** + * Set all header values + * + * @param array $headers Map of header name to header value + */ + public function set_headers($headers) { + $this->headers = $headers; + } + + /** + * Set a single HTTP header + * + * @param string $key Header name + * @param string $value Header value + * @param boolean $replace Replace an existing header of the same name? + */ + public function header($key, $value, $replace = true) { + if ( $replace || ! isset( $this->headers[ $key ] ) ) { + $this->headers[ $key ] = $value; + } + else { + $this->headers[ $key ] .= ', ' . $value; + } + } + + /** + * Set a single link header + * + * @internal The $rel parameter is first, as this looks nicer when sending multiple + * + * @link http://tools.ietf.org/html/rfc5988 + * @link http://www.iana.org/assignments/link-relations/link-relations.xml + * + * @param string $rel Link relation. Either an IANA registered type, or an absolute URL + * @param string $link Target IRI for the link + * @param array $other Other parameters to send, as an assocative array + */ + public function link_header( $rel, $link, $other = array() ) { + $header = '<' . $link . '>; rel="' . $rel . '"'; + foreach ( $other as $key => $value ) { + if ( 'title' == $key ) + $value = '"' . $value . '"'; + $header .= '; ' . $key . '=' . $value; + } + return $this->header( 'Link', $header, false ); + } + + /** + * Send navigation-related headers for post collections + * + * @param WP_Query $query + */ + public function query_navigation_headers( $query ) { + $max_page = $query->max_num_pages; + $paged = $query->get('paged'); + + if ( !$paged ) + $paged = 1; + + $nextpage = intval($paged) + 1; + + if ( ! $query->is_single() ) { + if ( $paged > 1 ) { + $request = remove_query_arg( 'page' ); + $request = add_query_arg( 'page', $paged - 1, $request ); + $this->link_header( 'prev', $request ); + } + + if ( $nextpage <= $max_page ) { + $request = remove_query_arg( 'page' ); + $request = add_query_arg( 'page', $nextpage, $request ); + $this->link_header( 'next', $request ); + } + } + + $this->header( 'X-WP-Total', $query->found_posts ); + $this->header( 'X-WP-TotalPages', $max_page ); + + do_action('json_query_navigation_headers', $this, $query); + } + + /** + * Get the HTTP return code for the response + * + * @return integer 3-digit HTTP status code + */ + public function get_status() { + return $this->status; + } + + /** + * Set the HTTP status code + * + * @param int $code HTTP status + */ + public function set_status( $code ) { + $this->status = absint( $code ); + } + + /** + * Get the response data + * + * @return mixed + */ + public function get_data() { + return $this->data; + } + + /** + * Set the response data + * + * @param mixed $data + */ + public function set_data( $data ) { + $this->data = $data; + } + + /** + * Get the response data for JSON serialization + * + * It is expected that in most implementations, this will return the same as + * {@see get_data()}, however this may be different if you want to do custom + * JSON data handling. + * + * @return mixed Any JSON-serializable value + */ + public function jsonSerialize() { + return $this->get_data(); + } +} diff --git a/lib/class-wp-json-responseinterface.php b/lib/class-wp-json-responseinterface.php new file mode 100644 index 0000000000..9e224c1c62 --- /dev/null +++ b/lib/class-wp-json-responseinterface.php @@ -0,0 +1,35 @@ +errors as $code => $messages ) { foreach ( (array) $messages as $message ) { - $errors[] = array( 'code' => $code, 'message' => $message ); + $data[] = array( 'code' => $code, 'message' => $message ); } } - return $errors; + $response = new WP_JSON_Response( $data, $status ); + + return $response; + } + + /** + * Convert an error to an array + * + * This iterates over all error codes and messages to change it into a flat + * array. This enables simpler client behaviour, as it is represented as a + * list in JSON rather than an object/map + * + * @param WP_Error $error + * @return array List of associative arrays with code and message keys + */ + protected function error_to_array( $error ) { + _deprecated_function( 'WP_JSON_Server::error_to_array', 'WPAPI-0.8', 'WP_JSON_Server::error_to_response' ); + + $response = $this->error_to_response( $error ); + return $errors->get_data(); } /** @@ -166,7 +192,7 @@ protected function json_error( $code, $message, $status = null ) { * @uses WP_JSON_Server::dispatch() */ public function serve_request( $path = null ) { - $this->header( 'Content-Type', 'application/json; charset=' . get_option( 'blog_charset' ), true ); + $this->send_header( 'Content-Type', 'application/json; charset=' . get_option( 'blog_charset' ), true ); // Proper filter for turning off the JSON API. It is on by default. $enabled = apply_filters( 'json_enabled', true ); @@ -214,13 +240,18 @@ public function serve_request( $path = null ) { $result = $this->dispatch(); } + // Normalize errors to response objects if ( is_wp_error( $result ) ) { - $data = $result->get_error_data(); - if ( is_array( $data ) && isset( $data['status'] ) ) { - $this->send_status( $data['status'] ); - } + $result = $this->error_to_response( $result ); + } + + // Send extra data from response objects + if ( $result instanceof WP_JSON_ResponseInterface ) { + $headers = $result->get_headers(); + $this->send_headers( $headers ); - $result = $this->error_to_array( $result ); + $code = $result->get_status(); + $this->set_status( $code ); } // This is a filter rather than an action, since this is designed to be @@ -467,6 +498,17 @@ public function getIndex() { * @param int $code HTTP status */ public function send_status( $code ) { + _deprecated_function( 'WP_JSON_Server::send_status', 'WPAPI-0.8', 'WP_JSON_Response' ); + + status_header( $code ); + } + + /** + * Send a HTTP status code + * + * @param int $code HTTP status + */ + protected function set_status( $code ) { status_header( $code ); } @@ -478,6 +520,8 @@ public function send_status( $code ) { * @param boolean $replace Should we replace the existing header? */ public function header( $key, $value, $replace = true ) { + _deprecated_function( 'WP_JSON_Server::header', 'WPAPI-0.8', 'WP_JSON_Response' ); + // Sanitize as per RFC2616 (Section 4.2): // Any LWS that occurs between field-content MAY be replaced with a // single SP before interpreting the field value or forwarding the @@ -486,6 +530,35 @@ public function header( $key, $value, $replace = true ) { header( sprintf( '%s: %s', $key, $value ), $replace ); } + /** + * Send a HTTP header + * + * This is set via the response object, unlike {@see self::header()}, which + * was called directly before response objects were added. + * + * @param string $key Header key + * @param string $value Header value + */ + protected function send_header( $key, $value ) { + // Sanitize as per RFC2616 (Section 4.2): + // Any LWS that occurs between field-content MAY be replaced with a + // single SP before interpreting the field value or forwarding the + // message downstream. + $value = preg_replace( '/\s+/', ' ', $value ); + header( sprintf( '%s: %s', $key, $value ) ); + } + + /** + * Send multiple HTTP headers + * + * @param array Map of header name to header value + */ + protected function send_headers( $headers ) { + foreach ( $headers as $key => $value ) { + $this->send_header( $key, $value ); + } + } + /** * Send a Link header * @@ -500,6 +573,8 @@ public function header( $key, $value, $replace = true ) { * @param array $other Other parameters to send, as an assocative array */ public function link_header( $rel, $link, $other = array() ) { + _deprecated_function( 'WP_JSON_Server::link_header', 'WPAPI-0.8', 'WP_JSON_Response' ); + $header = '<' . $link . '>; rel="' . $rel . '"'; foreach ( $other as $key => $value ) { if ( 'title' == $key ) @@ -515,6 +590,8 @@ public function link_header( $rel, $link, $other = array() ) { * @param WP_Query $query */ public function query_navigation_headers( $query ) { + _deprecated_function( 'WP_JSON_Server::query_navigation_headers', 'WPAPI-0.8', 'WP_JSON_Response' ); + $max_page = $query->max_num_pages; $paged = $query->get('paged'); diff --git a/plugin.php b/plugin.php index 4adf7400d1..072ca85f6a 100644 --- a/plugin.php +++ b/plugin.php @@ -10,6 +10,9 @@ include_once( dirname( __FILE__ ) . '/lib/class-jsonserializable.php' ); include_once( dirname( __FILE__ ) . '/lib/class-wp-json-responsehandler.php' ); +include_once( dirname( __FILE__ ) . '/lib/class-wp-json-responseinterface.php' ); +include_once( dirname( __FILE__ ) . '/lib/class-wp-json-response.php' ); + include_once( dirname( __FILE__ ) . '/lib/class-wp-json-posts.php' ); include_once( dirname( __FILE__ ) . '/lib/class-wp-json-customposttype.php' ); include_once( dirname( __FILE__ ) . '/lib/class-wp-json-pages.php' ); From fa5ac578b31b91ab7b90f8ac4f297ffec7c40085 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 17 Feb 2014 23:15:27 +1000 Subject: [PATCH 09/42] Test all versions of PHP, with WP trunk --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c5bdaaf76..a5f1d380b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,12 +26,10 @@ env: matrix: include: + - php: "5.2" - php: "5.3" - env: WP_VERSION=master - php: "5.4" - env: WP_VERSION=3.8 - - php: "5.2" - env: WP_VERSION=3.8 + - php: "5.4" # Clones WordPress and configures our testing environment. before_script: From e3a4ca03be155f94b8caf032772f57937cc222d3 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 17 Feb 2014 23:32:16 +1000 Subject: [PATCH 10/42] Add documentation on testing/developing --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README.md b/README.md index cce3bbc1fa..d0664f22b6 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,58 @@ working `PATH_INFO` on your server, but you don't need pretty permalinks enabled. +## Quick Setup +Want to test out WP-API and work on it? Here's how you can set up your own +testing environment in a few easy steps: + +1. Install [Vagrant](http://vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/). +2. Clone [Chassis](https://github.com/sennza/Chassis): + + ```bash + git clone git@github.com:sennza/Chassis.git api-tester + ``` + +3. Grab a copy of WP API: + + ```bash + mkdir -p content/plugins + git clone git@github.com:WP-API/WP-API.git content/plugins/json-rest-api + ``` + +4. Start the virtual machine: + + ```bash + vagrant up + ``` + +5. Browse to http://vagrant.local/wp/wp-admin/ and activate the WP API plugin +6. Browse to http://vagrant.local/wp-json.php/ + + +### Testing +For testing, you'll need a little bit more: + +1. Install PHPUnit: + + ```bash + wget https://phar.phpunit.de/phpunit.phar + ``` + +2. Clone WordPress development (including tests): + + ```bash + git clone https://github.com/tierra/wordpress.git /tmp/wordpress + export WP_DEVELOP_DIR=/tmp/wordpress + ``` + +3. Run the testing suite: + + ```bash + cd /vagrant/content/plugins/json-rest-api/tests + phpunit + ``` + + ## Issue Tracking All tickets for the project are being tracked on the [GSoC Trac][]. Make sure you use the JSON REST API component. From 13893c643d42bc1ccc72a8d186f9e4268870f0b6 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 17 Feb 2014 23:40:31 +1000 Subject: [PATCH 11/42] Don't check for IXR_Error --- lib/class-wp-json-posts.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index 8f5806b698..6b6b141ec7 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -214,18 +214,14 @@ function newPost( $data ) { unset( $data['ID'] ); $result = $this->insert_post( $data ); - if ( is_string( $result ) || is_int( $result ) ) { - $response = $this->getPost( $result ); - $response->set_status( 201 ); - $response->header( 'Location', json_url( '/posts/' . $result ) ); - return $response; - } - elseif ( $result instanceof IXR_Error ) { - return new WP_Error( 'json_insert_error', $result->message, array( 'status' => $result->code ) ); - } - else { - return new WP_Error( 'json_insert_error', __( 'An unknown error occurred while creating the post' ), array( 'status' => 500 ) ); + if ( $result instanceof WP_Error ) { + return $result; } + + $response = $this->getPost( $result ); + $response->set_status( 201 ); + $response->header( 'Location', json_url( '/posts/' . $result ) ); + return $response; } /** From 5aacbab79d022015416763a78583871eb3df62c3 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 17 Feb 2014 23:41:28 +1000 Subject: [PATCH 12/42] Remove IXR includes --- lib/wp-json.php | 2 -- plugin.php | 2 -- tests/test_json_server.php | 2 -- 3 files changed, 6 deletions(-) diff --git a/lib/wp-json.php b/lib/wp-json.php index 6e6dfde013..6e4058da3f 100644 --- a/lib/wp-json.php +++ b/lib/wp-json.php @@ -24,8 +24,6 @@ include('./wp-load.php'); include_once(ABSPATH . 'wp-admin/includes/admin.php'); -include_once(ABSPATH . WPINC . '/class-IXR.php'); -include_once(ABSPATH . WPINC . '/class-wp-xmlrpc-server.php'); include_once(ABSPATH . WPINC . '/class-wp-json-server.php'); // Allow for a plugin to insert a different class to handle requests. diff --git a/plugin.php b/plugin.php index fcd8256c74..4521df8d8c 100644 --- a/plugin.php +++ b/plugin.php @@ -80,8 +80,6 @@ function json_api_loaded() { if ( empty( $GLOBALS['wp']->query_vars['json_route'] ) ) return; - include_once( ABSPATH . WPINC . '/class-IXR.php' ); - include_once( ABSPATH . WPINC . '/class-wp-xmlrpc-server.php' ); include_once( dirname( __FILE__ ) . '/lib/class-wp-json-server.php' ); /** diff --git a/tests/test_json_server.php b/tests/test_json_server.php index e60fd99d9f..d67256ceea 100644 --- a/tests/test_json_server.php +++ b/tests/test_json_server.php @@ -24,8 +24,6 @@ function setUp() { parent::setUp(); - include_once( ABSPATH . WPINC . '/class-IXR.php' ); - include_once( ABSPATH . WPINC . '/class-wp-xmlrpc-server.php' ); include_once( plugin_dir_path( dirname( __FILE__ ) ) . 'lib/class-wp-json-server.php' ); // Allow for a plugin to insert a different class to handle requests. From aeec6bca60f35c1bce1a062e4b60483a00151ee5 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Mon, 17 Feb 2014 23:48:22 +1000 Subject: [PATCH 13/42] Remove hardcoded /pages references --- lib/class-wp-json-pages.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-json-pages.php b/lib/class-wp-json-pages.php index 5000c0925e..48742aa073 100644 --- a/lib/class-wp-json-pages.php +++ b/lib/class-wp-json-pages.php @@ -107,10 +107,10 @@ protected function prepare_post( $post, $context = 'view' ) { $_post = parent::prepare_post( $post, $context ); // Override entity meta keys with the correct links - $_post['meta']['links']['self'] = json_url( '/pages/' . get_page_uri( $post['ID'] ) ); + $_post['meta']['links']['self'] = json_url( $this->base . '/' . get_page_uri( $post['ID'] ) ); if ( ! empty( $post['post_parent'] ) ) - $_post['meta']['links']['up'] = json_url( '/pages/' . get_page_uri( (int) $post['post_parent'] ) ); + $_post['meta']['links']['up'] = json_url( $this->base . '/' . get_page_uri( (int) $post['post_parent'] ) ); return apply_filters( 'json_prepare_page', $_post, $post, $context ); } From d7e732f26894c301e4e051bce6e3d9a406ebd1cc Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Mon, 17 Feb 2014 17:14:01 -0600 Subject: [PATCH 14/42] Workaround for createFromFormat on PHP > 5.2 --- lib/class-wp-json-datetime.php | 21 +++++++++++++++++++++ lib/class-wp-json-posts.php | 10 +++++----- lib/class-wp-json-server.php | 2 +- lib/wp-json.php | 1 + plugin.php | 2 ++ 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 lib/class-wp-json-datetime.php diff --git a/lib/class-wp-json-datetime.php b/lib/class-wp-json-datetime.php new file mode 100644 index 0000000000..a87b86ab96 --- /dev/null +++ b/lib/class-wp-json-datetime.php @@ -0,0 +1,21 @@ + 5.2 + * Found on http://stackoverflow.com/a/17084893/717643 + * + * @param string $format The format that the passed in string should be in. + * @param string $string String representing the time. + * @param DateTimeZone $timezone A DateTimeZone object representing the desired time zone. + * @return Datetime + */ + public static function createFromFormat($format, $time, $timezone = null) + { + if ( method_exists('DateTime', 'createFromFormat') ) { + return parent::createFromFormat($format, $time, $timezone); + } + + return new DateTime(date($format, strtotime($time)), $timezone); + } +} \ No newline at end of file diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index 8f5806b698..acf545b7fe 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -299,7 +299,7 @@ function editPost( $id, $data, $_headers = array() ) { // and C's asctime() format (and ignore invalid headers) $formats = array( DateTime::RFC1123, DateTime::RFC1036, 'D M j H:i:s Y' ); foreach ( $formats as $format ) { - $check = DateTime::createFromFormat( $format, $_headers['IF_UNMODIFIED_SINCE'] ); + $check = WP_JSON_DateTime::createFromFormat( $format, $_headers['IF_UNMODIFIED_SINCE'] ); if ( $check !== false ) break; @@ -533,12 +533,12 @@ protected function prepare_post( $post, $context = 'view' ) { // Dates $timezone = $this->server->get_timezone(); - $date = DateTime::createFromFormat( 'Y-m-d H:i:s', $post['post_date'], $timezone ); + $date = WP_JSON_DateTime::createFromFormat( 'Y-m-d H:i:s', $post['post_date'], $timezone ); $post_fields['date'] = $date->format( 'c' ); $post_fields_extended['date_tz'] = $date->format( 'e' ); $post_fields_extended['date_gmt'] = date( 'c', strtotime( $post['post_date_gmt'] ) ); - $modified = DateTime::createFromFormat( 'Y-m-d H:i:s', $post['post_modified'], $timezone ); + $modified = WP_JSON_DateTime::createFromFormat( 'Y-m-d H:i:s', $post['post_modified'], $timezone ); $post_fields['modified'] = $modified->format( 'c' ); $post_fields_extended['modified_tz'] = $modified->format( 'e' ); $post_fields_extended['modified_gmt'] = date( 'c', strtotime( $post['post_modified_gmt'] ) ); @@ -895,7 +895,7 @@ protected function parse_date( $date, $force_utc = false ) { if ( strpos( $date, '.' ) !== false ) { $date = preg_replace( '/\.\d+/', '', $date ); } - $datetime = DateTime::createFromFormat( DateTime::RFC3339, $date ); + $datetime = WP_JSON_DateTime::createFromFormat( DateTime::RFC3339, $date ); return $datetime; } @@ -997,7 +997,7 @@ protected function prepare_comment( $comment, $requested_fields = array( 'commen // Date $timezone = $this->server->get_timezone(); - $date = DateTime::createFromFormat( 'Y-m-d H:i:s', $comment->comment_date, $timezone ); + $date = WP_JSON_DateTime::createFromFormat( 'Y-m-d H:i:s', $comment->comment_date, $timezone ); $fields['date'] = $date->format( 'c' ); $fields['date_tz'] = $date->format( 'e' ); $fields['date_gmt'] = date( 'c', strtotime( $comment->comment_date_gmt ) ); diff --git a/lib/class-wp-json-server.php b/lib/class-wp-json-server.php index e2e4f7bce1..fbe070520c 100644 --- a/lib/class-wp-json-server.php +++ b/lib/class-wp-json-server.php @@ -700,7 +700,7 @@ public function parse_date( $date, $force_utc = false ) { if ( strpos( $date, '.' ) !== false ) { $date = preg_replace( '/\.\d+/', '', $date ); } - $datetime = DateTime::createFromFormat( DateTime::RFC3339, $date ); + $datetime = WP_JSON_DateTime::createFromFormat( DateTime::RFC3339, $date ); return $datetime; } diff --git a/lib/wp-json.php b/lib/wp-json.php index 6e6dfde013..c3ffe47e66 100644 --- a/lib/wp-json.php +++ b/lib/wp-json.php @@ -26,6 +26,7 @@ include_once(ABSPATH . 'wp-admin/includes/admin.php'); include_once(ABSPATH . WPINC . '/class-IXR.php'); include_once(ABSPATH . WPINC . '/class-wp-xmlrpc-server.php'); +include_once(ABSPATH . WPINC . '/class-wp-json-datetime.php'); include_once(ABSPATH . WPINC . '/class-wp-json-server.php'); // Allow for a plugin to insert a different class to handle requests. diff --git a/plugin.php b/plugin.php index fcd8256c74..4b528d92cf 100644 --- a/plugin.php +++ b/plugin.php @@ -9,6 +9,8 @@ */ include_once( dirname( __FILE__ ) . '/lib/class-jsonserializable.php' ); +include_once( dirname( __FILE__ ) . '/lib/class-wp-json-datetime.php' ); + include_once( dirname( __FILE__ ) . '/lib/class-wp-json-responsehandler.php' ); include_once( dirname( __FILE__ ) . '/lib/class-wp-json-responseinterface.php' ); include_once( dirname( __FILE__ ) . '/lib/class-wp-json-response.php' ); From 13a07376986bba3c7a7d72deec69cf1cee49d3a8 Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Mon, 17 Feb 2014 17:48:58 -0600 Subject: [PATCH 15/42] Change to WP official git mirror --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1c5bdaaf76..e65c04dbaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,8 @@ matrix: # Clones WordPress and configures our testing environment. before_script: - export PLUGIN_SLUG=$(basename $(pwd)) - - git clone https://github.com/tierra/wordpress.git /tmp/wordpress + - git clone git://develop.git.wordpress.org/ /tmp/wordpress + - mkdir "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" - git clone . "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" - cd /tmp/wordpress - git checkout $WP_VERSION From c1705d4be2f8cdec67b206e02799659029236c2e Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Mon, 17 Feb 2014 18:01:15 -0600 Subject: [PATCH 16/42] Just copy the files instead of cloning the repo again (this way it even works on branches tests, I think) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e65c04dbaa..4887be23d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ before_script: - export PLUGIN_SLUG=$(basename $(pwd)) - git clone git://develop.git.wordpress.org/ /tmp/wordpress - mkdir "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" - - git clone . "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" + - cp -r . "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG/" - cd /tmp/wordpress - git checkout $WP_VERSION - mysql -e "CREATE DATABASE wordpress_tests;" -uroot From 36b59457b7d4758a51382fba5f01c721a2457f29 Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Wed, 19 Feb 2014 04:29:37 -0600 Subject: [PATCH 17/42] Change function semiCamelCase names to underscore_case on lib/ --- lib/class-wp-json-customposttype.php | 46 ++++++++-------- lib/class-wp-json-media.php | 62 +++++++++++----------- lib/class-wp-json-pages.php | 26 ++++----- lib/class-wp-json-posts.php | 79 +++++++++++++++++----------- lib/class-wp-json-server.php | 10 ++-- lib/class-wp-json-taxonomies.php | 41 ++++++++++----- 6 files changed, 147 insertions(+), 117 deletions(-) diff --git a/lib/class-wp-json-customposttype.php b/lib/class-wp-json-customposttype.php index c72966ab48..80c8f15e01 100644 --- a/lib/class-wp-json-customposttype.php +++ b/lib/class-wp-json-customposttype.php @@ -38,7 +38,7 @@ public function __construct(WP_JSON_ResponseHandler $server) { return; } - add_filter( 'json_endpoints', array( $this, 'registerRoutes' ) ); + add_filter( 'json_endpoints', array( $this, 'register_routes' ) ); add_filter( 'json_post_type_data', array( $this, 'type_archive_link' ), 10, 2 ); parent::__construct($server); @@ -50,16 +50,16 @@ public function __construct(WP_JSON_ResponseHandler $server) { * @param array $routes Routes for the post type * @return array Modified routes */ - public function registerRoutes( $routes ) { + public function register_routes( $routes ) { $routes[ $this->base ] = array( - array( array( $this, 'getPosts' ), WP_JSON_Server::READABLE ), - array( array( $this, 'newPost' ), WP_JSON_Server::CREATABLE ), + array( array( $this, 'get_posts' ), WP_JSON_Server::READABLE ), + array( array( $this, 'new_post' ), WP_JSON_Server::CREATABLE ), ); $routes[ $this->base . '/(?P\d+)' ] = array( - array( array( $this, 'getPost' ), WP_JSON_Server::READABLE ), - array( array( $this, 'editPost' ), WP_JSON_Server::EDITABLE ), - array( array( $this, 'deletePost' ), WP_JSON_Server::DELETABLE ), + array( array( $this, 'get_post' ), WP_JSON_Server::READABLE ), + array( array( $this, 'edit_post' ), WP_JSON_Server::EDITABLE ), + array( array( $this, 'delete_post' ), WP_JSON_Server::DELETABLE ), ); return $routes; } @@ -70,7 +70,7 @@ public function registerRoutes( $routes ) { * @param array $routes Routes for the post type * @return array Modified routes */ - public function registerRevisionRoutes( $routes ) { + public function register_revision_routes( $routes ) { $routes[ $this->base . '/(?P\d+)/revisions' ] = array( array( '__return_null', WP_JSON_Server::READABLE ), ); @@ -83,13 +83,13 @@ public function registerRevisionRoutes( $routes ) { * @param array $routes Routes for the post type * @return array Modified routes */ - public function registerCommentRoutes( $routes ) { + public function register_comment_routes( $routes ) { $routes[ $this->base . '/(?P\d+)/comments'] = array( - array( array( $this, 'getComments' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_comments' ), WP_JSON_Server::READABLE ), array( '__return_null', WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), ); $routes[ $this->base . '/(?P\d+)/comments/(?P\d+)' ] = array( - array( array( $this, 'getComment' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_comment' ), WP_JSON_Server::READABLE ), array( '__return_null', WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), array( '__return_null', WP_JSON_Server::DELETABLE ), ); @@ -102,21 +102,21 @@ public function registerCommentRoutes( $routes ) { * Overrides the $type to set to $this->type, then passes through to the * post endpoints. * - * @see WP_JSON_Posts::getPosts() + * @see WP_JSON_Posts::get_posts() */ - public function getPosts( $filter = array(), $context = 'view', $type = null, $page = 1 ) { + public function get_posts( $filter = array(), $context = 'view', $type = null, $page = 1 ) { if ( !empty( $type ) && $type !== $this->type ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::getPosts( $filter, $context, $this->type, $page ); + return parent::get_posts( $filter, $context, $this->type, $page ); } /** * Retrieve a post * - * @see WP_JSON_Posts::getPost() + * @see WP_JSON_Posts::get_post() */ - public function getPost( $id, $context = 'view' ) { + public function get_post( $id, $context = 'view' ) { $id = (int) $id; if ( empty( $id ) ) @@ -127,15 +127,15 @@ public function getPost( $id, $context = 'view' ) { if ( $post['post_type'] !== $this->type ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::getPost( $id, $context ); + return parent::get_post( $id, $context ); } /** * Edit a post * - * @see WP_JSON_Posts::editPost() + * @see WP_JSON_Posts::edit_post() */ - function editPost( $id, $data, $_headers = array() ) { + function edit_post( $id, $data, $_headers = array() ) { $id = (int) $id; if ( empty( $id ) ) return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); @@ -148,15 +148,15 @@ function editPost( $id, $data, $_headers = array() ) { if ( $post['post_type'] !== $this->type ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::editPost( $id, $data, $_headers ); + return parent::edit_post( $id, $data, $_headers ); } /** * Delete a post * - * @see WP_JSON_Posts::deletePost() + * @see WP_JSON_Posts::delete_post() */ - public function deletePost( $id, $force = false ) { + public function delete_post( $id, $force = false ) { $id = (int) $id; if ( empty( $id ) ) @@ -167,7 +167,7 @@ public function deletePost( $id, $force = false ) { if ( $post['post_type'] !== $this->type ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::deletePost( $id, $force ); + return parent::delete_post( $id, $force ); } /** diff --git a/lib/class-wp-json-media.php b/lib/class-wp-json-media.php index 62b4c8e9a6..c9dbcb8c06 100644 --- a/lib/class-wp-json-media.php +++ b/lib/class-wp-json-media.php @@ -7,17 +7,17 @@ class WP_JSON_Media extends WP_JSON_Posts { * @param array $routes Existing routes * @return array Modified routes */ - public function registerRoutes( $routes ) { + public function register_routes( $routes ) { $media_routes = array( '/media' => array( - array( array( $this, 'getPosts' ), WP_JSON_Server::READABLE ), - array( array( $this, 'uploadAttachment' ), WP_JSON_Server::CREATABLE ), + array( array( $this, 'get_posts' ), WP_JSON_Server::READABLE ), + array( array( $this, 'upload_attachment' ), WP_JSON_Server::CREATABLE ), ), '/media/(?P\d+)' => array( - array( array( $this, 'getPost' ), WP_JSON_Server::READABLE ), - array( array( $this, 'editPost' ), WP_JSON_Server::EDITABLE ), - array( array( $this, 'deletePost' ), WP_JSON_Server::DELETABLE ), + array( array( $this, 'get_post' ), WP_JSON_Server::READABLE ), + array( array( $this, 'edit_post' ), WP_JSON_Server::EDITABLE ), + array( array( $this, 'delete_post' ), WP_JSON_Server::DELETABLE ), ), ); return array_merge( $routes, $media_routes ); @@ -29,9 +29,9 @@ public function registerRoutes( $routes ) { * Overrides the $type to set to 'attachment', then passes through to the post * endpoints. * - * @see WP_JSON_Posts::getPosts() + * @see WP_JSON_Posts::get_posts() */ - public function getPosts( $filter = array(), $context = 'view', $type = 'attachment', $page = 1 ) { + public function get_posts( $filter = array(), $context = 'view', $type = 'attachment', $page = 1 ) { if ( $type !== 'attachment' ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); @@ -39,10 +39,10 @@ public function getPosts( $filter = array(), $context = 'view', $type = 'attachm $filter['post_status'] = array( 'publish', 'inherit' ); // Always allow status queries for attachments - add_filter( 'query_vars', array( $this, 'allowStatusQuery' ) ); + add_filter( 'query_vars', array( $this, 'allow_status_query' ) ); } - $posts = parent::getPosts( $filter, $context, 'attachment', $page ); + $posts = parent::get_posts( $filter, $context, 'attachment', $page ); return $posts; } @@ -53,8 +53,8 @@ public function getPosts( $filter = array(), $context = 'view', $type = 'attachm * @param array $vars Query variables * @return array Filtered query variables */ - public function allowStatusQuery( $vars ) { - remove_filter( 'query_vars', array( $this, 'allowStatusQuery' ) ); + public function allow_status_query( $vars ) { + remove_filter( 'query_vars', array( $this, 'allow_status_query' ) ); $vars[] = 'post_status'; return $vars; @@ -63,9 +63,9 @@ public function allowStatusQuery( $vars ) { /** * Retrieve a attachment * - * @see WP_JSON_Posts::getPost() + * @see WP_JSON_Posts::get_post() */ - public function getPost( $id, $context = 'view' ) { + public function get_post( $id, $context = 'view' ) { $id = (int) $id; if ( empty( $id ) ) @@ -76,7 +76,7 @@ public function getPost( $id, $context = 'view' ) { if ( $post['post_type'] !== 'attachment' ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::getPost( $id, $context ); + return parent::get_post( $id, $context ); } /** @@ -130,9 +130,9 @@ protected function prepare_post( $post, $context = 'single' ) { /** * Edit a attachment * - * @see WP_JSON_Posts::editPost() + * @see WP_JSON_Posts::edit_post() */ - public function editPost( $id, $data, $_headers = array() ) { + public function edit_post( $id, $data, $_headers = array() ) { $id = (int) $id; if ( empty( $id ) ) return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); @@ -145,15 +145,15 @@ public function editPost( $id, $data, $_headers = array() ) { if ( $post['post_type'] !== 'attachment' ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::editPost( $id, $data, $_headers ); + return parent::edit_post( $id, $data, $_headers ); } /** * Delete a attachment * - * @see WP_JSON_Posts::deletePost() + * @see WP_JSON_Posts::delete_post() */ - public function deletePost( $id, $force = false ) { + public function delete_post( $id, $force = false ) { $id = (int) $id; if ( empty( $id ) ) @@ -164,7 +164,7 @@ public function deletePost( $id, $force = false ) { if ( $post['post_type'] !== 'attachment' ) return new WP_Error( 'json_post_invalid_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); - return parent::deletePost( $id, $force ); + return parent::delete_post( $id, $force ); } /** @@ -178,7 +178,7 @@ public function deletePost( $id, $force = false ) { * @param array $_headers HTTP headers from the request * @return array|WP_Error Attachment data or error */ - public function uploadAttachment( $_files, $_headers ) { + public function upload_attachment( $_files, $_headers ) { $post_type = get_post_type_object( 'attachment' ); if ( ! $post_type ) return new WP_Error( 'json_invalid_post_type', __( 'Invalid post type' ), array( 'status' => 400 ) ); @@ -189,10 +189,10 @@ public function uploadAttachment( $_files, $_headers ) { // Get the file via $_FILES or raw data if ( empty( $_files ) ) { - $file = $this->uploadFromData( $_files, $_headers ); + $file = $this->upload_from_data( $_files, $_headers ); } else { - $file = $this->uploadFromFile( $_files, $_headers ); + $file = $this->upload_from_file( $_files, $_headers ); } if ( is_wp_error( $file ) ) @@ -247,7 +247,7 @@ public function uploadAttachment( $_files, $_headers ) { * @param array $_headers HTTP headers from the request * @return array|WP_Error Data from {@see wp_handle_sideload()} */ - protected function uploadFromData( $_files, $_headers ) { + protected function upload_from_data( $_files, $_headers ) { $data = $this->server->get_raw_data(); if ( empty( $data ) ) { @@ -328,7 +328,7 @@ protected function uploadFromData( $_files, $_headers ) { * @param array $_headers HTTP headers from the request * @return array|WP_Error Data from {@see wp_handle_upload()} */ - protected function uploadFromFile( $_files, $_headers ) { + protected function upload_from_file( $_files, $_headers ) { if ( empty( $_files['file'] ) ) return new WP_Error( 'json_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) ); @@ -361,7 +361,7 @@ protected function uploadFromFile( $_files, $_headers ) { * @param array $data Supplied post data * @return bool|WP_Error Success or error object */ - public function preinsertCheck( $status, $post, $data ) { + public function preinsert_check( $status, $post, $data ) { if ( is_wp_error( $status ) ) { return $status; } @@ -384,10 +384,10 @@ public function preinsertCheck( $status, $post, $data ) { * @param array $data Supplied post data * @param boolean $update Is this an update? */ - public function attachThumbnail( $post, $data, $update ) { + public function attach_thumbnail( $post, $data, $update ) { if ( ! empty( $data['featured_image'] ) ) { // Already verified in preinsertCheck() - $thumbnail = $this->getPost( $data['featured_image'], 'child' ); + $thumbnail = $this->get_post( $data['featured_image'], 'child' ); set_post_thumbnail( $post_ID, $thumbnail['ID'] ); } } @@ -400,7 +400,7 @@ public function attachThumbnail( $post, $data, $update ) { * @param string $context Display context * @return array Filtered post data */ - public function addThumbnailData( $data, $post, $context ) { + public function add_thumbnail_data( $data, $post, $context ) { if( !post_type_supports( $post['post_type'], 'thumbnail' ) ) { return $data; } @@ -409,7 +409,7 @@ public function addThumbnailData( $data, $post, $context ) { $data['featured_image'] = null; $thumbnail_id = get_post_thumbnail_id( $post['ID'] ); if ( $thumbnail_id ) { - $data['featured_image'] = $this->getPost( $thumbnail_id, 'child' ); + $data['featured_image'] = $this->get_post( $thumbnail_id, 'child' ); } return $data; diff --git a/lib/class-wp-json-pages.php b/lib/class-wp-json-pages.php index 48742aa073..50d3ff2ab7 100644 --- a/lib/class-wp-json-pages.php +++ b/lib/class-wp-json-pages.php @@ -39,16 +39,16 @@ class WP_JSON_Pages extends WP_JSON_CustomPostType { * @param array $routes Existing routes * @return array Modified routes */ - public function registerRoutes( $routes ) { - $routes = parent::registerRoutes( $routes ); - $routes = parent::registerRevisionRoutes( $routes ); - $routes = parent::registerCommentRoutes( $routes ); + public function register_routes( $routes ) { + $routes = parent::register_routes( $routes ); + $routes = parent::register_revision_routes( $routes ); + $routes = parent::register_comment_routes( $routes ); // Add post-by-path routes $routes[ $this->base . '/(?P.+)'] = array( - array( array( $this, 'getPostByPath' ), WP_JSON_Server::READABLE ), - array( array( $this, 'editPostByPath' ), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), - array( array( $this, 'deletePostByPath' ), WP_JSON_Server::DELETABLE ), + array( array( $this, 'get_post_by_path' ), WP_JSON_Server::READABLE ), + array( array( $this, 'edit_post_by_path' ), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), + array( array( $this, 'delete_post_by_path' ), WP_JSON_Server::DELETABLE ), ); return $routes; @@ -59,13 +59,13 @@ public function registerRoutes( $routes ) { * * @param string $path */ - public function getPostByPath( $path, $context = 'view' ) { + public function get_post_by_path( $path, $context = 'view' ) { $post = get_page_by_path( $path, ARRAY_A ); if ( empty( $post ) ) return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); - return $this->getPost( $post['ID'], $context ); + return $this->get_post( $post['ID'], $context ); } /** @@ -73,13 +73,13 @@ public function getPostByPath( $path, $context = 'view' ) { * * @param string $path */ - public function editPostByPath( $path, $data, $_headers = array() ) { + public function edit_post_by_path( $path, $data, $_headers = array() ) { $post = get_page_by_path( $path, ARRAY_A ); if ( empty( $post ) ) return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); - return $this->editPost( $post['ID'], $data, $_headers ); + return $this->edit_post( $post['ID'], $data, $_headers ); } /** @@ -87,13 +87,13 @@ public function editPostByPath( $path, $data, $_headers = array() ) { * * @param string $path */ - public function deletePostByPath( $path, $force = false ) { + public function delete_post_by_path( $path, $force = false ) { $post = get_page_by_path( $path, ARRAY_A ); if ( empty( $post ) ) return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); - return $this->deletePost( $post['ID'], $force ); + return $this->delete_post( $post['ID'], $force ); } /** diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index acf545b7fe..0f39873872 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -23,36 +23,36 @@ public function __construct(WP_JSON_ResponseHandler $server) { * @param array $routes Existing routes * @return array Modified routes */ - public function registerRoutes( $routes ) { + public function register_routes( $routes ) { $post_routes = array( // Post endpoints '/posts' => array( - array( array( $this, 'getPosts' ), WP_JSON_Server::READABLE ), - array( array( $this, 'newPost' ), WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), + array( array( $this, 'get_posts' ), WP_JSON_Server::READABLE ), + array( array( $this, 'new_post' ), WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), ), '/posts/(?P\d+)' => array( - array( array( $this, 'getPost' ), WP_JSON_Server::READABLE ), - array( array( $this, 'editPost' ), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), - array( array( $this, 'deletePost' ), WP_JSON_Server::DELETABLE ), + array( array( $this, 'get_post' ), WP_JSON_Server::READABLE ), + array( array( $this, 'edit_post' ), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), + array( array( $this, 'delete_post' ), WP_JSON_Server::DELETABLE ), ), '/posts/(?P\d+)/revisions' => array( '__return_null', WP_JSON_Server::READABLE ), // Comments '/posts/(?P\d+)/comments' => array( - array( array( $this, 'getComments' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_comments' ), WP_JSON_Server::READABLE ), array( '__return_null', WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), ), '/posts/(?P\d+)/comments/(?P\d+)' => array( - array( array( $this, 'getComment' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_comment' ), WP_JSON_Server::READABLE ), array( '__return_null', WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), array( '__return_null', WP_JSON_Server::DELETABLE ), ), // Meta-post endpoints - '/posts/types' => array( array( $this, 'getPostTypes' ), WP_JSON_Server::READABLE ), - '/posts/types/(?P\w+)' => array( array( $this, 'getPostType' ), WP_JSON_Server::READABLE ), - '/posts/statuses' => array( array( $this, 'getPostStatuses' ), WP_JSON_Server::READABLE ), + '/posts/types' => array( array( $this, 'get_post_types' ), WP_JSON_Server::READABLE ), + '/posts/types/(?P\w+)' => array( array( $this, 'get_post_type' ), WP_JSON_Server::READABLE ), + '/posts/statuses' => array( array( $this, 'get_post_statuses' ), WP_JSON_Server::READABLE ), ); return array_merge( $routes, $post_routes ); } @@ -70,14 +70,14 @@ public function registerRoutes( $routes ) { * in the response array. * * @uses wp_get_recent_posts() - * @see WP_JSON_Posts::getPost() for more on $fields + * @see WP_JSON_Posts::get_post() for more on $fields * @see get_posts() for more on $filter values * * @param array $filter optional * @param array $fields optional * @return array contains a collection of Post entities. */ - public function getPosts( $filter = array(), $context = 'view', $type = 'post', $page = 1 ) { + public function get_posts( $filter = array(), $context = 'view', $type = 'post', $page = 1 ) { $query = array(); $post_type = get_post_type_object( $type ); @@ -140,7 +140,7 @@ public function getPosts( $filter = array(), $context = 'view', $type = 'post', $post = get_object_vars( $post ); // Do we have permission to read this post? - if ( ! $this->checkReadPermission( $post ) ) + if ( ! $this->check_read_permission( $post ) ) continue; $response->link_header( 'item', json_url( '/posts/' . $post['ID'] ), array( 'title' => $post['post_title'] ) ); @@ -158,7 +158,7 @@ public function getPosts( $filter = array(), $context = 'view', $type = 'post', * @param array $post Post data * @return boolean Can we read it? */ - protected function checkReadPermission( $post ) { + protected function check_read_permission( $post ) { // Can we read the post? $post_type = get_post_type_object( $post['post_type'] ); if ( 'publish' === $post['post_status'] || current_user_can( $post_type->cap->read_post, $post['ID'] ) ) { @@ -169,7 +169,7 @@ protected function checkReadPermission( $post ) { if ( 'inherit' === $post['post_status'] && $post['post_parent'] > 0 ) { $parent = get_post( $post['post_parent'], ARRAY_A ); - if ( $this->checkReadPermission( $parent ) ) { + if ( $this->check_read_permission( $parent ) ) { return true; } } @@ -208,14 +208,14 @@ protected function checkReadPermission( $post ) { * - terms_names - array, with taxonomy names as keys and arrays of term names as values * - enclosure * - any other fields supported by wp_insert_post() - * @return array Post data (see {@see WP_JSON_Posts::getPost}) + * @return array Post data (see {@see WP_JSON_Posts::get_post}) */ - function newPost( $data ) { + function new_post( $data ) { unset( $data['ID'] ); $result = $this->insert_post( $data ); if ( is_string( $result ) || is_int( $result ) ) { - $response = $this->getPost( $result ); + $response = $this->get_post( $result ); $response->set_status( 201 ); $response->header( 'Location', json_url( '/posts/' . $result ) ); return $response; @@ -236,7 +236,7 @@ function newPost( $data ) { * @param array $fields Post fields to return (optional) * @return array Post entity */ - public function getPost( $id, $context = 'view' ) { + public function get_post( $id, $context = 'view' ) { $id = (int) $id; if ( empty( $id ) ) @@ -248,7 +248,7 @@ public function getPost( $id, $context = 'view' ) { return new WP_Error( 'json_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); $post_type = get_post_type_object( $post['post_type'] ); - if ( ! $this->checkReadPermission( $post ) ) + if ( ! $this->check_read_permission( $post ) ) return new WP_Error( 'json_user_cannot_read', __( 'Sorry, you cannot read this post.' ), array( 'status' => 401 ) ); // Link headers (see RFC 5988) @@ -279,11 +279,11 @@ public function getPost( $id, $context = 'view' ) { * @internal 'data' is used here rather than 'content', as get_default_post_to_edit uses $_REQUEST['content'] * * @param int $id Post ID to edit - * @param array $data Data construct, see {@see WP_JSON_Posts::newPost} + * @param array $data Data construct, see {@see WP_JSON_Posts::new_post} * @param array $_headers Header data * @return true on success */ - function editPost( $id, $data, $_headers = array() ) { + function edit_post( $id, $data, $_headers = array() ) { $id = (int) $id; if ( empty( $id ) ) @@ -328,7 +328,7 @@ function editPost( $id, $data, $_headers = array() ) { * @param int $id * @return true on success */ - public function deletePost( $id, $force = false ) { + public function delete_post( $id, $force = false ) { $id = (int) $id; if ( empty( $id ) ) @@ -363,7 +363,7 @@ public function deletePost( $id, $force = false ) { * @param int $id Post ID to retrieve comments for * @return array List of Comment entities */ - public function getComments( $id ) { + public function get_comments( $id ) { //$args = array('status' => $status, 'post_id' => $id, 'offset' => $offset, 'number' => $number )l $comments = get_comments( array('post_id' => $id) ); @@ -380,7 +380,7 @@ public function getComments( $id ) { * @param int $comment Comment ID * @return array Comment entity */ - public function getComment( $comment ) { + public function get_comment( $comment ) { $comment = get_comment( $comment ); $data = $this->prepare_comment( $comment ); return $data; @@ -389,15 +389,15 @@ public function getComment( $comment ) { /** * Get all public post types * - * @uses self::getPostType() + * @uses self::get_post_type() * @return array List of post type data */ - public function getPostTypes() { + public function get_post_types() { $data = get_post_types( array(), 'objects' ); $types = array(); foreach ($data as $name => $type) { - $type = $this->getPostType( $type, true ); + $type = $this->get_post_type( $type, true ); if ( is_wp_error( $type ) ) continue; @@ -414,7 +414,7 @@ public function getPostTypes() { * @param boolean $_in_collection Is this in a collection? (internal use) * @return array Post type data */ - public function getPostType( $type, $_in_collection = false ) { + public function get_post_type( $type, $_in_collection = false ) { if ( ! is_object( $type ) ) $type = get_post_type_object($type); @@ -454,7 +454,7 @@ public function getPostType( $type, $_in_collection = false ) { * * @return array List of post status data */ - public function getPostStatuses() { + public function get_post_statuses() { $statuses = get_post_stati(array(), 'objects'); $data = array(); @@ -501,7 +501,7 @@ protected function prepare_post( $post, $context = 'view' ) { ); $post_type = get_post_type_object( $post['post_type'] ); - if ( ! $this->checkReadPermission( $post ) ) + if ( ! $this->check_read_permission( $post ) ) return new WP_Error( 'json_user_cannot_read', __( 'Sorry, you cannot read this post.' ), array( 'status' => 401 ) ); // prepare common post fields @@ -1025,4 +1025,19 @@ protected function prepare_comment( $comment, $requested_fields = array( 'commen return $data; } + + /** + * Magic method used to temporaly deprecate camelcase functions + * + * @param string $name Function name + * @param array $arguments Function arguments + * @return mixed + */ + public function __call($name, $arguments) { + $underscored = strtolower(preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '_$0', $name)); + if ( method_exists( $this, $underscored ) ) { + _deprecated_function( __CLASS__ . '->' . $name, '0.9', __CLASS__ . '->' . $underscored ); + return call_user_func_array( array( $this, $underscored ), $arguments ); + } + } } diff --git a/lib/class-wp-json-server.php b/lib/class-wp-json-server.php index fbe070520c..6cdb0bed8a 100644 --- a/lib/class-wp-json-server.php +++ b/lib/class-wp-json-server.php @@ -288,10 +288,10 @@ public function serve_request( $path = null ) { * * @return array `'/path/regex' => array( $callback, $bitmask )` or `'/path/regex' => array( array( $callback, $bitmask ), ...)` */ - public function getRoutes() { + public function get_routes() { $endpoints = array( // Meta endpoints - '/' => array( array( $this, 'getIndex' ), self::READABLE ), + '/' => array( array( $this, 'get_index' ), self::READABLE ), // Users '/users' => array( @@ -349,7 +349,7 @@ public function dispatch() { default: return new WP_Error( 'json_unsupported_method', __( 'Unsupported request method' ), array( 'status' => 400 ) ); } - foreach ( $this->getRoutes() as $route => $handlers ) { + foreach ( $this->get_routes() as $route => $handlers ) { foreach ( $handlers as $handler ) { $callback = $handler[0]; $supported = isset( $handler[1] ) ? $handler[1] : self::METHOD_GET; @@ -446,7 +446,7 @@ protected function sort_callback_params( $callback, $provided ) { * * @return array Index entity */ - public function getIndex() { + public function get_index() { // General site data $available = array( 'name' => get_option( 'blogname' ), @@ -462,7 +462,7 @@ public function getIndex() { ); // Find the available routes - foreach ( $this->getRoutes() as $route => $callbacks ) { + foreach ( $this->get_routes() as $route => $callbacks ) { $data = array(); $route = preg_replace( '#\(\?P(<\w+?>).*?\)#', '$1', $route ); diff --git a/lib/class-wp-json-taxonomies.php b/lib/class-wp-json-taxonomies.php index 9ba54bbbad..ea14a1f565 100644 --- a/lib/class-wp-json-taxonomies.php +++ b/lib/class-wp-json-taxonomies.php @@ -7,20 +7,20 @@ class WP_JSON_Taxonomies { * @param array $routes Existing routes * @return array Modified routes */ - public function registerRoutes( $routes ) { + public function register_routes( $routes ) { $tax_routes = array( '/posts/types/(?P\w+)/taxonomies' => array( - array( array( $this, 'getTaxonomies' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_taxonomies' ), WP_JSON_Server::READABLE ), ), '/posts/types/(?P\w+)/taxonomies/(?P\w+)' => array( - array( array( $this, 'getTaxonomy' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_taxonomy' ), WP_JSON_Server::READABLE ), ), '/posts/types/(?P\w+)/taxonomies/(?P\w+)/terms' => array( - array( array( $this, 'getTerms' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_terms' ), WP_JSON_Server::READABLE ), array( '__return_null', WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), ), '/posts/types/(?P\w+)/taxonomies/(?P\w+)/terms/(?P\w+)' => array( - array( array( $this, 'getTerm' ), WP_JSON_Server::READABLE ), + array( array( $this, 'get_term' ), WP_JSON_Server::READABLE ), array( '__return_null', WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), array( '__return_null', WP_JSON_Server::DELETABLE ), ), @@ -30,11 +30,11 @@ public function registerRoutes( $routes ) { /** * Get taxonomies - * + * * @param string $type Post type to get taxonomies for * @return array Taxonomy data */ - public function getTaxonomies( $type ) { + public function get_taxonomies( $type ) { $taxonomies = get_object_taxonomies( $type, 'objects' ); $data = array(); @@ -51,11 +51,11 @@ public function getTaxonomies( $type ) { /** * Get taxonomies - * + * * @param string $type Post type to get taxonomies for * @return array Taxonomy data */ - public function getTaxonomy( $type, $taxonomy ) { + public function get_taxonomy( $type, $taxonomy ) { $tax = get_taxonomy( $taxonomy ); if ( empty( $tax ) ) return new WP_Error( 'json_taxonomy_invalid_id', __( 'Invalid taxonomy ID.' ), array( 'status' => 404 ) ); @@ -108,7 +108,7 @@ protected function prepare_taxonomy( $taxonomy, $type, $_in_collection = false ) * @return array Filtered data */ public function add_taxonomy_data( $data, $type ) { - $data['taxonomies'] = $this->getTaxonomies( $type->name ); + $data['taxonomies'] = $this->get_taxonomies( $type->name ); return $data; } @@ -120,7 +120,7 @@ public function add_taxonomy_data( $data, $type ) { * @param string $taxonomy Taxonomy slug * @return array Term collection */ - public function getTerms( $type, $taxonomy ) { + public function get_terms( $type, $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) return new WP_Error( 'json_taxonomy_invalid_id', __( 'Invalid taxonomy ID.' ), array( 'status' => 404 ) ); @@ -147,7 +147,7 @@ public function getTerms( $type, $taxonomy ) { * @param string $context Context (view/view-parent) * @return array Term entity */ - public function getTerm( $type, $taxonomy, $term, $context = 'view' ) { + public function get_term( $type, $taxonomy, $term, $context = 'view' ) { if ( ! taxonomy_exists( $taxonomy ) ) return new WP_Error( 'json_taxonomy_invalid_id', __( 'Invalid taxonomy ID.' ), array( 'status' => 404 ) ); @@ -201,7 +201,7 @@ protected function prepare_term( $term, $type, $context = 'view' ) { ); if ( ! empty( $data['parent'] ) && $context === 'view' ) { - $data['parent'] = $this->getTerm( $type, $term->taxonomy, $data['parent'], 'view-parent' ); + $data['parent'] = $this->get_term( $type, $term->taxonomy, $data['parent'], 'view-parent' ); } elseif ( empty( $data['parent'] ) ) { $data['parent'] = null; @@ -209,4 +209,19 @@ protected function prepare_term( $term, $type, $context = 'view' ) { return apply_filters( 'json_prepare_term', $data, $term ); } + + /** + * Magic method used to temporaly deprecate camelcase functions + * + * @param string $name Function name + * @param array $arguments Function arguments + * @return mixed + */ + public function __call($name, $arguments) { + $underscored = strtolower(preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '_$0', $name)); + if ( method_exists( $this, $underscored ) ) { + _deprecated_function( __CLASS__ . '->' . $name, '0.9', __CLASS__ . '->' . $underscored ); + return call_user_func_array( array( $this, $underscored ), $arguments ); + } + } } From a77486ffcae94a197e041c50dd505eb826d8f299 Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Wed, 19 Feb 2014 04:29:46 -0600 Subject: [PATCH 18/42] Change function semiCamelCase names to underscore_case on docs/ --- docs/guides/extending.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/guides/extending.md b/docs/guides/extending.md index 347f533a67..c8e0b20c86 100644 --- a/docs/guides/extending.md +++ b/docs/guides/extending.md @@ -130,9 +130,9 @@ preparation and request handling. This will automatically register all the post methods for their endpoints. Along these lines, keep your methods named as generically as possible; while -`MyPlugin_API_MyType::getMyTypeItems()` might seem like a good name, it makes it +`MyPlugin_API_MyType::get_my_type_items()` might seem like a good name, it makes it harder for other plugins to use; standardising on -`MyPlugin_API_MyType::getPosts()` with similar arguments to the parent is a +`MyPlugin_API_MyType::get_posts()` with similar arguments to the parent is a better idea and allows a nicer fall-through. You should also aim to keep these related endpoints modular, and make liberal @@ -159,7 +159,7 @@ errors. For example, an endpoint that takes a required `context` parameter, an optional `type` parameter and uses the `X-WP-Example` header would look like this: - function getMyData( $context, $_headers, $type = 'my-default-value' ) { + function get_my_data( $context, $_headers, $type = 'my-default-value' ) { if ( isset( $_headers['X-WP-EXAMPLE'] ) ) { $my_header_value = $_headers['X-WP-EXAMPLE']; } @@ -233,20 +233,20 @@ built-in types, your registration code should look something like this: global $myplugin_api_mytype; $myplugin_api_mytype = new MyPlugin_API_MyType(); - add_filter( 'json_endpoints', array( $myplugin_api_mytype, 'registerRoutes' ) ); + add_filter( 'json_endpoints', array( $myplugin_api_mytype, 'register_routes' ) ); } add_action( 'wp_json_server_before_serve', 'myplugin_api_init' ); class MyPlugin_API_MyType { - public function registerRoutes( $routes ) { + public function register_routes( $routes ) { $routes['/myplugin/mytypeitems'] = array( - array( array( $this, 'getPosts'), WP_JSON_Server::READABLE ), - array( array( $this, 'newPost'), WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), + array( array( $this, 'get_posts'), WP_JSON_Server::READABLE ), + array( array( $this, 'new_post'), WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), ); $routes['/myplugin/mytypeitems/(?P\d+)'] = array( - array( array( $this, 'getPost'), WP_JSON_Server::READABLE ), - array( array( $this, 'editPost'), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), - array( array( $this, 'deletePost'), WP_JSON_Server::DELETABLE ), + array( array( $this, 'get_post'), WP_JSON_Server::READABLE ), + array( array( $this, 'edit_post'), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), + array( array( $this, 'delete_post'), WP_JSON_Server::DELETABLE ), ); // Add more custom routes here @@ -274,10 +274,10 @@ hooking and more for you: protected $base = '/myplugin/mytypeitems'; protected $type = 'myplugin-mytype'; - public function registerRoutes( $routes ) { - $routes = parent::registerRoutes( $routes ); - // $routes = parent::registerRevisionRoutes( $routes ); - // $routes = parent::registerCommentRoutes( $routes ); + public function register_routes( $routes ) { + $routes = parent::register_routes( $routes ); + // $routes = parent::register_revision_routes( $routes ); + // $routes = parent::register_comment_routes( $routes ); // Add more custom routes here From 3a5413b7823b4af30a706735e8bf1761583ae57c Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Wed, 19 Feb 2014 04:30:07 -0600 Subject: [PATCH 19/42] Change function semiCamelCase names to underscore_case on plugin files. --- plugin.php | 14 +++++++------- testhelper.php | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugin.php b/plugin.php index 4b528d92cf..fe51104e13 100644 --- a/plugin.php +++ b/plugin.php @@ -47,24 +47,24 @@ function json_api_default_filters($server) { // Posts $wp_json_posts = new WP_JSON_Posts($server); - add_filter( 'json_endpoints', array( $wp_json_posts, 'registerRoutes' ), 0 ); + add_filter( 'json_endpoints', array( $wp_json_posts, 'register_routes' ), 0 ); // Pages $wp_json_pages = new WP_JSON_Pages($server); - add_filter( 'json_endpoints', array( $wp_json_pages, 'registerRoutes' ), 1 ); + add_filter( 'json_endpoints', array( $wp_json_pages, 'register_routes' ), 1 ); add_filter( 'json_post_type_data', array( $wp_json_pages, 'type_archive_link' ), 10, 2 ); // Media $wp_json_media = new WP_JSON_Media($server); - add_filter( 'json_endpoints', array( $wp_json_media, 'registerRoutes' ), 1 ); - add_filter( 'json_prepare_post', array( $wp_json_media, 'addThumbnailData' ), 10, 3 ); - add_filter( 'json_pre_insert_post', array( $wp_json_media, 'preinsertCheck' ), 10, 3 ); - add_filter( 'json_insert_post', array( $wp_json_media, 'attachThumbnail' ), 10, 3 ); + add_filter( 'json_endpoints', array( $wp_json_media, 'register_routes' ), 1 ); + add_filter( 'json_prepare_post', array( $wp_json_media, 'add_thumbnail_data' ), 10, 3 ); + add_filter( 'json_pre_insert_post', array( $wp_json_media, 'preinsert_check' ), 10, 3 ); + add_filter( 'json_insert_post', array( $wp_json_media, 'attach_thumbnail' ), 10, 3 ); add_filter( 'json_post_type_data', array( $wp_json_media, 'type_archive_link' ), 10, 2 ); // Posts $wp_json_taxonomies = new WP_JSON_Taxonomies($server); - add_filter( 'json_endpoints', array( $wp_json_taxonomies, 'registerRoutes' ), 2 ); + add_filter( 'json_endpoints', array( $wp_json_taxonomies, 'register_routes' ), 2 ); add_filter( 'json_post_type_data', array( $wp_json_taxonomies, 'add_taxonomy_data' ), 10, 2 ); add_filter( 'json_prepare_post', array( $wp_json_taxonomies, 'add_term_data' ), 10, 3 ); } diff --git a/testhelper.php b/testhelper.php index 41cb1dff1a..44d72cb7ef 100644 --- a/testhelper.php +++ b/testhelper.php @@ -16,11 +16,11 @@ class WP_JSON_Server_TestHelper { protected $reports = array(); public function __construct() { - add_action('init', array($this, 'startCoverage')); - add_filter('json_endpoints', array($this, 'addEndpoints')); + add_action('init', array($this, 'start_coverage')); + add_filter('json_endpoints', array($this, 'add_endpoints')); } - public function startCoverage() { + public function start_coverage() { if ( ! isset( $_REQUEST['_jsoncurrenttest'] ) ) { return; } @@ -38,7 +38,7 @@ public function startCoverage() { $this->coverage->start( $current_test ); } - public function endCoverage() { + public function end_coverage() { if ( ! $this->coverage ) { return; } @@ -48,14 +48,14 @@ public function endCoverage() { set_transient('json_testhelper_coverage', $this->reports, 30 * MINUTE_IN_SECONDS); } - public function addEndpoints($routes) { + public function add_endpoints($routes) { $routes['/testhelper/report'] = array( - array( array( $this, 'getReports' ), WP_JSON_Server::METHOD_POST ), + array( array( $this, 'get_reports' ), WP_JSON_Server::METHOD_POST ), ); return $routes; } - public function getReports() { + public function get_reports() { $this->reports = get_transient('json_testhelper_coverage'); if (empty($this->reports)) { return new WP_Error('json_testhelper_no_report', __('No report data available', 'json_testhelper'), array('status' => 400)); From f9a1b71be05784ed27b902390a1bc32b489f370b Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Wed, 19 Feb 2014 05:10:33 -0600 Subject: [PATCH 20/42] Use name of plugin on deprecated notice. --- lib/class-wp-json-posts.php | 2 +- lib/class-wp-json-taxonomies.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index 0f39873872..5d374d9426 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -1036,7 +1036,7 @@ protected function prepare_comment( $comment, $requested_fields = array( 'commen public function __call($name, $arguments) { $underscored = strtolower(preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '_$0', $name)); if ( method_exists( $this, $underscored ) ) { - _deprecated_function( __CLASS__ . '->' . $name, '0.9', __CLASS__ . '->' . $underscored ); + _deprecated_function( __CLASS__ . '->' . $name, 'WPAPI-0.9', __CLASS__ . '->' . $underscored ); return call_user_func_array( array( $this, $underscored ), $arguments ); } } diff --git a/lib/class-wp-json-taxonomies.php b/lib/class-wp-json-taxonomies.php index ea14a1f565..a4424113dd 100644 --- a/lib/class-wp-json-taxonomies.php +++ b/lib/class-wp-json-taxonomies.php @@ -220,7 +220,7 @@ protected function prepare_term( $term, $type, $context = 'view' ) { public function __call($name, $arguments) { $underscored = strtolower(preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '_$0', $name)); if ( method_exists( $this, $underscored ) ) { - _deprecated_function( __CLASS__ . '->' . $name, '0.9', __CLASS__ . '->' . $underscored ); + _deprecated_function( __CLASS__ . '->' . $name, 'WPAPI-0.9', __CLASS__ . '->' . $underscored ); return call_user_func_array( array( $this, $underscored ), $arguments ); } } From 24afd8a9c8b571680160ca8909ecfdd84b4d17b7 Mon Sep 17 00:00:00 2001 From: Bryan Petty Date: Thu, 20 Feb 2014 15:47:43 -0700 Subject: [PATCH 21/42] Removed test support for multiple WP versions. --- .travis.yml | 29 +++-------------------------- tests/test_json_plugin.php | 22 ---------------------- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5f1d380b8..f247ef3e56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,41 +3,18 @@ # Tell Travis CI we're using PHP language: php -# PHP version used in first build configuration. php: + - "5.2" + - "5.3" + - "5.4" - "5.5" -# WordPress version used in first build configuration. -env: - - WP_VERSION=master - -# Next we define our matrix of additional build configurations to test against. -# The versions listed above will automatically create our first configuration, -# so it doesn't need to be re-defined below. - -# WP_VERSION specifies the tag to use. The way these tests are configured to run -# requires at least WordPress 3.8. Specify "master" to test against SVN trunk. - -# Note that Travis CI supports listing these above to automatically build a -# matrix of configurations, but we're being nice here by manually building a -# total of four configurations even though we're testing 4 versions of PHP -# along with 2 versions of WordPress (which would build 8 configs otherwise). -# This takes half as long to run while still providing adequate coverage. - -matrix: - include: - - php: "5.2" - - php: "5.3" - - php: "5.4" - - php: "5.4" - # Clones WordPress and configures our testing environment. before_script: - export PLUGIN_SLUG=$(basename $(pwd)) - git clone https://github.com/tierra/wordpress.git /tmp/wordpress - git clone . "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" - cd /tmp/wordpress - - git checkout $WP_VERSION - mysql -e "CREATE DATABASE wordpress_tests;" -uroot - cp wp-tests-config-sample.php wp-tests-config.php - sed -i "s/youremptytestdbnamehere/wordpress_tests/" wp-tests-config.php diff --git a/tests/test_json_plugin.php b/tests/test_json_plugin.php index a45a0cfc4e..b60eb98377 100644 --- a/tests/test_json_plugin.php +++ b/tests/test_json_plugin.php @@ -11,28 +11,6 @@ */ class WP_Test_JSON_Plugin extends WP_UnitTestCase { - /** - * If these tests are being run on Travis CI, verify that the version of - * WordPress installed is the version that we requested. - * - * @requires PHP 5.3 - */ - function test_wp_version() { - if ( !getenv( 'TRAVIS' ) ) - $this->markTestSkipped( 'Travis CI was not detected.' ); - - $requested_version = getenv( 'WP_VERSION' ) . '-src'; - - // The "master" version requires special handling. - if ( $requested_version == 'master-src' ) { - $file = file_get_contents( 'https://raw.github.com/tierra/wordpress/master/src/wp-includes/version.php' ); - preg_match( '#\$wp_version = \'([^\']+)\';#', $file, $matches ); - $requested_version = $matches[1]; - } - - $this->assertEquals( get_bloginfo( 'version' ), $requested_version ); - } - /** * The plugin should be installed and activated. */ From 959dfac17b8467a127aa16ae6ea52ca4ac25e177 Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Fri, 21 Feb 2014 22:06:15 -0600 Subject: [PATCH 22/42] #85 - added timezone parameter to WP_JSON_DateTime::createFromFormat() in parse_date method. --- lib/class-wp-json-server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-json-server.php b/lib/class-wp-json-server.php index fbe070520c..bfcc0503e2 100644 --- a/lib/class-wp-json-server.php +++ b/lib/class-wp-json-server.php @@ -700,7 +700,7 @@ public function parse_date( $date, $force_utc = false ) { if ( strpos( $date, '.' ) !== false ) { $date = preg_replace( '/\.\d+/', '', $date ); } - $datetime = WP_JSON_DateTime::createFromFormat( DateTime::RFC3339, $date ); + $datetime = WP_JSON_DateTime::createFromFormat( DateTime::RFC3339, $date, $timezone ); return $datetime; } From e196c6d965373f9b072aff361d6d650d12f53b66 Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Sat, 22 Feb 2014 11:00:48 -0600 Subject: [PATCH 23/42] Provide default timezone fallback in WP_JSON_DateTime::createFromFormat(). --- lib/class-wp-json-datetime.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/class-wp-json-datetime.php b/lib/class-wp-json-datetime.php index a87b86ab96..4061a3ba71 100644 --- a/lib/class-wp-json-datetime.php +++ b/lib/class-wp-json-datetime.php @@ -12,6 +12,9 @@ class WP_JSON_DateTime extends DateTime */ public static function createFromFormat($format, $time, $timezone = null) { + if ( is_null( $timezone ) ) { + $timezone = new DateTimeZone( date_default_timezone_get() ); + } if ( method_exists('DateTime', 'createFromFormat') ) { return parent::createFromFormat($format, $time, $timezone); } From a1b23c952485d4f96ef9287765999ed4c2f8664e Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Sat, 22 Feb 2014 11:11:38 -0600 Subject: [PATCH 24/42] Added class comment and code formatting fixes to WP_JSON_DateTime class. --- lib/class-wp-json-datetime.php | 42 +++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/class-wp-json-datetime.php b/lib/class-wp-json-datetime.php index 4061a3ba71..d2e93c15cc 100644 --- a/lib/class-wp-json-datetime.php +++ b/lib/class-wp-json-datetime.php @@ -1,24 +1,30 @@ 5.2 - * Found on http://stackoverflow.com/a/17084893/717643 - * - * @param string $format The format that the passed in string should be in. - * @param string $string String representing the time. - * @param DateTimeZone $timezone A DateTimeZone object representing the desired time zone. - * @return Datetime - */ - public static function createFromFormat($format, $time, $timezone = null) - { +/** + * DateTime compatibility class + * + * @package WordPress + * @subpackage JSON API + * @version 0.9 + */ +class WP_JSON_DateTime extends DateTime { + /** + * Workaround for DateTime::createFromFormat on PHP > 5.2 + * + * @link http://stackoverflow.com/a/17084893/717643 + * + * @param string $format The format that the passed in string should be in. + * @param string $string String representing the time. + * @param DateTimeZone $timezone A DateTimeZone object representing the desired time zone. + * @return Datetime + */ + public static function createFromFormat($format, $time, $timezone = null ) { if ( is_null( $timezone ) ) { $timezone = new DateTimeZone( date_default_timezone_get() ); } - if ( method_exists('DateTime', 'createFromFormat') ) { - return parent::createFromFormat($format, $time, $timezone); - } + if ( method_exists( 'DateTime', 'createFromFormat' ) ) { + return parent::createFromFormat( $format, $time, $timezone ); + } - return new DateTime(date($format, strtotime($time)), $timezone); - } + return new DateTime( date( $format, strtotime( $time ) ), $timezone ); + } } \ No newline at end of file From c0dc49e8d1c06c214189431e2237866c578c1287 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 24 Feb 2014 14:55:05 -0500 Subject: [PATCH 25/42] WP_JSON_CustomPostType __construct() requires a param of type WP_JSON_ResponseHandler. This needs to be documented. It's not clear how to write getPost, editPost, getPosts, and newPost methods mentioned in the MyPlugin_API_MyType class: I added a sentence explaining where you could start with these. --- docs/guides/extending.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/guides/extending.md b/docs/guides/extending.md index 347f533a67..57b7a0b738 100644 --- a/docs/guides/extending.md +++ b/docs/guides/extending.md @@ -257,15 +257,17 @@ built-in types, your registration code should look something like this: // ... } +You will need to implement the getPost, editPost, getPosts, and newPost methods within your new class. Take a look at the WP_JSON_Posts class to see examples of how these methods can be written. + Alternatively, use the custom post type base class, which will handle the hooking and more for you: // main.php - function myplugin_api_init() { + function myplugin_api_init( $server ) { global $myplugin_api_mytype; require_once dirname( __FILE__ ) . '/class-myplugin-api-mytype.php'; - $myplugin_api_mytype = new MyPlugin_API_MyType(); + $myplugin_api_mytype = new MyPlugin_API_MyType( $server ); } add_action( 'wp_json_server_before_serve', 'myplugin_api_init' ); From af6f4631169499236977653f5b623f05de0d0106 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 4 Mar 2014 22:44:40 +1000 Subject: [PATCH 26/42] Move from wp-json.php/ to wp-json/ --- plugin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin.php b/plugin.php index 4b528d92cf..a59ae7ac36 100644 --- a/plugin.php +++ b/plugin.php @@ -33,8 +33,8 @@ function json_api_init() { add_action( 'init', 'json_api_init' ); function json_api_register_rewrites() { - add_rewrite_rule( '^wp-json\.php/?$','index.php?json_route=/','top' ); - add_rewrite_rule( '^wp-json\.php(.*)?','index.php?json_route=$matches[1]','top' ); + add_rewrite_rule( '^wp-json/?$','index.php?json_route=/','top' ); + add_rewrite_rule( '^wp-json(.*)?','index.php?json_route=$matches[1]','top' ); } /** @@ -232,7 +232,7 @@ function json_output_link_header() { * @return string Full URL to the endpoint */ function get_json_url( $blog_id = null, $path = '', $scheme = 'json' ) { - $url = get_home_url( $blog_id, 'wp-json.php', $scheme ); + $url = get_home_url( $blog_id, 'wp-json', $scheme ); if ( !empty( $path ) && is_string( $path ) && strpos( $path, '..' ) === false ) $url .= '/' . ltrim( $path, '/' ); From 20e35cac3ab4f9c6edd826017d851d4c7a0f3810 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 4 Mar 2014 22:48:08 +1000 Subject: [PATCH 27/42] Use `wp-json/` in documentation --- docs/guides/getting-started.md | 56 +++++++++++++++---------------- docs/guides/working-with-posts.md | 18 +++++----- docs/schema.md | 48 +++++++++++++------------- lib/class-wp-json-server.php | 9 ++--- plugin.php | 2 +- 5 files changed, 67 insertions(+), 66 deletions(-) diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index e771d8c299..8eb83e070f 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -25,15 +25,15 @@ Checking for the API As our first command, let's go ahead and check the API index. The index tells us what routes are available, and a short summary about the site. -The index is always available at the site's address with `/wp-json.php/` on the +The index is always available at the site's address with `/wp-json/` on the end. Note the trailing slash there; it indicates that we want to access the `/` route. My test site is set up at `http://example.com/`, so all my routes will -start with `http://example.com/wp-json.php` followed by the route (which here is +start with `http://example.com/wp-json` followed by the route (which here is just `/`). Let's fire off the request: - curl -i http://example.com/wp-json.php/ + curl -i http://example.com/wp-json/ (By the way, `-i` tells cURL that we want to see the headers as well. I'll strip some irrelevant ones for this documentation.) @@ -54,7 +54,7 @@ And here's what we get back: "GET" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/" + "self": "http:\/\/example.com\/wp-json\/" } }, "\/posts": { @@ -64,7 +64,7 @@ And here's what we get back: "POST" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/posts" + "self": "http:\/\/example.com\/wp-json\/posts" }, "accepts_json": true }, @@ -110,7 +110,7 @@ And here's what we get back: "GET" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/types" + "self": "http:\/\/example.com\/wp-json\/posts\/types" } }, "\/posts\/types\/": { @@ -125,7 +125,7 @@ And here's what we get back: "GET" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/statuses" + "self": "http:\/\/example.com\/wp-json\/posts\/statuses" } }, "\/taxonomies": { @@ -134,7 +134,7 @@ And here's what we get back: "GET" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/taxonomies" + "self": "http:\/\/example.com\/wp-json\/taxonomies" } }, "\/taxonomies\/": { @@ -174,7 +174,7 @@ And here's what we get back: "POST" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/users" + "self": "http:\/\/example.com\/wp-json\/users" }, "accepts_json": true }, @@ -188,7 +188,7 @@ And here's what we get back: "DELETE" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/users\/me" + "self": "http:\/\/example.com\/wp-json\/users\/me" } }, "\/users\/": { @@ -273,13 +273,13 @@ Getting Posts Now that we understand some of the basics, let's have a look at the posts route. All we need to do is send a GET request to the posts endpoint. - curl -i http://example.com/wp-json.php/posts + curl -i http://example.com/wp-json/posts And this time, we get (again trimming headers, you'll have more than this): HTTP/1.1 200 OK Last-Modified: Wed, 31 Oct 2012 18:26:17 GMT - Link: ; rel="item"; title="Hello world!" + Link: ; rel="item"; title="Hello world!" [ { @@ -295,8 +295,8 @@ And this time, we get (again trimming headers, you'll have more than this): "avatar": "http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G", "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/users\/1", - "archives": "http:\/\/example.com\/wp-json.php\/users\/1\/posts" + "self": "http:\/\/example.com\/wp-json\/users\/1", + "archives": "http:\/\/example.com\/wp-json\/users\/1\/posts" } } }, @@ -316,19 +316,19 @@ And this time, we get (again trimming headers, you'll have more than this): "count": 1, "meta": { "links": { - "collection": "http:\/\/example.com\/wp-json.php\/taxonomy\/category", - "self": "http:\/\/example.com\/wp-json.php\/taxonomy\/category\/terms\/1" + "collection": "http:\/\/example.com\/wp-json\/taxonomy\/category", + "self": "http:\/\/example.com\/wp-json\/taxonomy\/category\/terms\/1" } } } }, "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/1", - "author": "http:\/\/example.com\/wp-json.php\/users\/1", - "collection": "http:\/\/example.com\/wp-json.php\/posts", - "replies": "http:\/\/example.com\/wp-json.php\/posts\/1\/comments", - "version-history": "http:\/\/example.com\/wp-json.php\/posts\/1\/revisions" + "self": "http:\/\/example.com\/wp-json\/posts\/1", + "author": "http:\/\/example.com\/wp-json\/users\/1", + "collection": "http:\/\/example.com\/wp-json\/posts", + "replies": "http:\/\/example.com\/wp-json\/posts\/1\/comments", + "version-history": "http:\/\/example.com\/wp-json\/posts\/1\/revisions" } } } @@ -353,14 +353,14 @@ Example of pagination headers: X-WP-Total: 492 X-WP-TotalPages: 50 - Link: ; rel="next", - ; rel="prev" + Link: ; rel="next", + ; rel="prev" If you want to grab a single post, you can instead send a GET request to the post itself. You can grab the URL for this from the `meta.links.self` field, or construct it yourself (`/posts/`): - curl -i http://example.com/wp-json.php/posts/1 + curl -i http://example.com/wp-json/posts/1 Editing and Creating Posts @@ -381,7 +381,7 @@ the correct headers and authentication. The API uses HTTP Basic authentication: curl --data-binary="@updated-post.json" \ -H "Content-Type: application/javascript" \ --user admin:password \ - http://example.com/wp-json.php/posts/1 + http://example.com/wp-json/posts/1 And we should get back a 200 status code, indicating that the post has been updated, plus the updated Post in the body. @@ -396,18 +396,18 @@ before, but this time, we POST it to the main posts route. curl --data-binary="@updated-post.json" \ -H "Content-Type: application/javascript" \ --user admin:password \ - http://example.com/wp-json.php/posts + http://example.com/wp-json/posts We should get a similar response to the editing endpoint, but this time we get a 201 Created status code, with a Location header telling us where to access the post in future: HTTP/1.1 201 Created - Location: http://example.com/wp-json.php/posts/2 + Location: http://example.com/wp-json/posts/2 Finally, we can clean this post up and delete it by sending a DELETE request: - curl -X DELETE --user admin:password http://example.com/wp-json.php/posts/2 + curl -X DELETE --user admin:password http://example.com/wp-json/posts/2 In general, routes follow the same pattern: diff --git a/docs/guides/working-with-posts.md b/docs/guides/working-with-posts.md index a696b199e3..0d16603469 100644 --- a/docs/guides/working-with-posts.md +++ b/docs/guides/working-with-posts.md @@ -15,12 +15,12 @@ This guide also assumes that you know how to send requests given how to use them, so the examples will be HTTP requests. I recommend reading the cURL manual or using a higher level tool if you don't know how to wrangle cURL. -The examples also pretend that your JSON base URL (`wp-json.php` in the main WP +The examples also pretend that your JSON base URL (`wp-json` in the main WP directory) is located at `/`, which is probably not the case. For example, if -your base URL is `http://example.com/wp-json.php` and the example request is +your base URL is `http://example.com/wp-json` and the example request is `GET /posts`, you should should actually send the following: - GET /wp-json.php/posts HTTP/1.1 + GET /wp-json/posts HTTP/1.1 Host: example.com Higher level HTTP clients can usually handle this for you. @@ -137,8 +137,8 @@ This should return a list of the available types: "hierarchical": false, "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/types\/post", - "archives": "http:\/\/example.com\/wp-json.php\/posts" + "self": "http:\/\/example.com\/wp-json\/posts\/types\/post", + "archives": "http:\/\/example.com\/wp-json\/posts" } } }, @@ -167,7 +167,7 @@ This should return a list of the available types: "hierarchical": true, "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/types\/page" + "self": "http:\/\/example.com\/wp-json\/posts\/types\/page" } } }, @@ -196,8 +196,8 @@ This should return a list of the available types: "hierarchical": false, "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/types\/attachment", - "archives": "http:\/\/example.com\/wp-json.php\/posts?type=attachment" + "self": "http:\/\/example.com\/wp-json\/posts\/types\/attachment", + "archives": "http:\/\/example.com\/wp-json\/posts?type=attachment" } } } @@ -221,7 +221,7 @@ A similar API exists for post statuses at `/posts/statuses`: "queryable": true, "show_in_list": true, "meta": { - "archives": "http:\/\/example.com\/wp-json.php\/posts" + "archives": "http:\/\/example.com\/wp-json\/posts" } }, "future": { diff --git a/docs/schema.md b/docs/schema.md index bd486b9df0..1b0d922074 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -102,7 +102,7 @@ value indicating a human-readable documentation page about the API. "GET" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/" + "self": "http:\/\/example.com\/wp-json\/" } }, "\/posts": { @@ -112,7 +112,7 @@ value indicating a human-readable documentation page about the API. "POST" ], "meta": { - "self": "http:\/\/example.com\/wp-json.php\/posts" + "self": "http:\/\/example.com\/wp-json\/posts" }, "accepts_json": true }, @@ -365,8 +365,8 @@ representation. "avatar": "http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G", "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/users\/1", - "archives": "http:\/\/example.com\/wp-json.php\/users\/1\/posts" + "self": "http:\/\/example.com\/wp-json\/users\/1", + "archives": "http:\/\/example.com\/wp-json\/users\/1\/posts" } }, "first_name": "", @@ -394,11 +394,11 @@ representation. ], "meta": { "links": { - "self": "http:\/\/example.com\/wp-json.php\/posts\/1", - "author": "http:\/\/example.com\/wp-json.php\/users\/1", - "collection": "http:\/\/example.com\/wp-json.php\/posts", - "replies": "http:\/\/example.com\/wp-json.php\/posts\/1\/comments", - "version-history": "http:\/\/example.com\/wp-json.php\/posts\/1\/revisions" + "self": "http:\/\/example.com\/wp-json\/posts\/1", + "author": "http:\/\/example.com\/wp-json\/users\/1", + "collection": "http:\/\/example.com\/wp-json\/posts", + "replies": "http:\/\/example.com\/wp-json\/posts\/1\/comments", + "version-history": "http:\/\/example.com\/wp-json\/posts\/1\/revisions" } }, "featured_image": null, @@ -411,8 +411,8 @@ representation. "count": 7, "meta": { "links": { - "collection": "http:\/\/example.com\/wp-json.php\/posts\/types\/post\/taxonomies\/category\/terms", - "self": "http:\/\/example.com\/wp-json.php\/posts\/types\/post\/taxonomies\/category\/terms\/1" + "collection": "http:\/\/example.com\/wp-json\/posts\/types\/post\/taxonomies\/category\/terms", + "self": "http:\/\/example.com\/wp-json\/posts\/types\/post\/taxonomies\/category\/terms\/1" } } } @@ -724,10 +724,10 @@ The body of a Post document is a Post entity. Date: Mon, 07 Jan 2013 03:35:14 GMT Last-Modified: Mon, 07 Jan 2013 03:35:14 GMT Link: ; rel="alternate"; type=text/html - Link: ; rel="author" - Link: ; rel="collection" - Link: ; rel="replies" - Link: ; rel="version-history" + Link: ; rel="author" + Link: ; rel="collection" + Link: ; rel="replies" + Link: ; rel="version-history" Content-Type: application/json; charset=UTF-8 { @@ -743,8 +743,8 @@ The body of a Post document is a Post entity. "avatar":"http:\/\/0.gravatar.com\/avatar\/c57c8945079831fa3c19caef02e44614&d=404&r=G", "meta":{ "links":{ - "self":"http:\/\/localhost\/wptrunk\/wp-json.php\/users\/1", - "archives":"http:\/\/localhost\/wptrunk\/wp-json.php\/users\/1\/posts" + "self":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1", + "archives":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1\/posts" } } }, @@ -776,8 +776,8 @@ The body of a Post document is a Post entity. "count":4, "meta":{ "links":{ - "collection":"http:\/\/localhost\/wptrunk\/wp-json.php\/taxonomy\/category", - "self":"http:\/\/localhost\/wptrunk\/wp-json.php\/taxonomy\/category\/terms\/1" + "collection":"http:\/\/localhost\/wptrunk\/wp-json\/taxonomy\/category", + "self":"http:\/\/localhost\/wptrunk\/wp-json\/taxonomy\/category\/terms\/1" } } } @@ -785,11 +785,11 @@ The body of a Post document is a Post entity. "post_meta":[], "meta":{ "links":{ - "self":"http:\/\/localhost\/wptrunk\/wp-json.php\/posts\/158", - "author":"http:\/\/localhost\/wptrunk\/wp-json.php\/users\/1", - "collection":"http:\/\/localhost\/wptrunk\/wp-json.php\/posts", - "replies":"http:\/\/localhost\/wptrunk\/wp-json.php\/posts\/158\/comments", - "version-history":"http:\/\/localhost\/wptrunk\/wp-json.php\/posts\/158\/revisions" + "self":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158", + "author":"http:\/\/localhost\/wptrunk\/wp-json\/users\/1", + "collection":"http:\/\/localhost\/wptrunk\/wp-json\/posts", + "replies":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158\/comments", + "version-history":"http:\/\/localhost\/wptrunk\/wp-json\/posts\/158\/revisions" } } } diff --git a/lib/class-wp-json-server.php b/lib/class-wp-json-server.php index bfcc0503e2..25ce804212 100644 --- a/lib/class-wp-json-server.php +++ b/lib/class-wp-json-server.php @@ -53,7 +53,7 @@ class WP_JSON_Server implements WP_JSON_ResponseHandler { ); /** - * Requested path (relative to the API root, wp-json.php) + * Requested path (relative to the API root, `wp-json`) * * @var string */ @@ -126,8 +126,9 @@ public function check_authentication() { * @return array List of associative arrays with code and message keys */ protected function error_to_response( $error ) { - if ( is_array( $data ) && isset( $data['status'] ) ) { - $status = $data['status']; + $error_data = $error->get_error_data(); + if ( is_array( $error_data ) && isset( $error_data['status'] ) ) { + $status = $error_data['status']; } else { $status = 500; @@ -158,7 +159,7 @@ protected function error_to_array( $error ) { _deprecated_function( 'WP_JSON_Server::error_to_array', 'WPAPI-0.8', 'WP_JSON_Server::error_to_response' ); $response = $this->error_to_response( $error ); - return $errors->get_data(); + return $response->get_data(); } /** diff --git a/plugin.php b/plugin.php index a59ae7ac36..2134db138c 100644 --- a/plugin.php +++ b/plugin.php @@ -75,7 +75,7 @@ function json_api_default_filters($server) { * * @todo Extract code that should be unit tested into isolated methods such as * the wp_json_server_class filter and serving requests. This would also - * help for code re-use by wp-json.php endpoint. Note that we can't unit + * help for code re-use by `wp-json` endpoint. Note that we can't unit * test any method that calls die(). */ function json_api_loaded() { From a541687db4bd84e50b5aa2671f92c445b9577c21 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 4 Mar 2014 23:20:17 +1000 Subject: [PATCH 28/42] Shallow-clone WP for testing Props @osiux --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 97c039008a..05aff999e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ php: # Clones WordPress and configures our testing environment. before_script: - export PLUGIN_SLUG=$(basename $(pwd)) - - git clone git://develop.git.wordpress.org/ /tmp/wordpress + - git clone --depth=1 git://develop.git.wordpress.org/ /tmp/wordpress - mkdir "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" - cp -r . "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG/" - cd /tmp/wordpress From ed71619babf1b175bb619381c63b593c07c542e9 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 4 Mar 2014 23:22:32 +1000 Subject: [PATCH 29/42] Don't declare jsonSerialize on ResponseInterface For PHP <5.3.9, duplicate abstract methods can't be declared (5.3.9+ allows them with the same signature). This comments it out to leave the documentation intact. --- lib/class-wp-json-responseinterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-json-responseinterface.php b/lib/class-wp-json-responseinterface.php index 9e224c1c62..001377127d 100644 --- a/lib/class-wp-json-responseinterface.php +++ b/lib/class-wp-json-responseinterface.php @@ -31,5 +31,5 @@ public function get_data(); * * @return mixed Any JSON-serializable value */ - public function jsonSerialize(); + // public function jsonSerialize(); } From 20485c9f49471d080ac03807014e530e3b23ec62 Mon Sep 17 00:00:00 2001 From: Bryan Petty Date: Tue, 4 Mar 2014 08:18:35 -0700 Subject: [PATCH 30/42] Link to Travis CI build page with status image. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d0664f22b6..babd4fea03 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This is a project to create a JSON-based REST API for WordPress. This project is run by Ryan McCue and is part of the WordPress 2013 GSoC projects. +[![Build Status](https://travis-ci.org/WP-API/WP-API.png?branch=master)](https://travis-ci.org/WP-API/WP-API) + ## Documentation Read the [plugin's documentation][docs]. From 227351bc2b4afe0298864a2155988376ab90d7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dobros=C5=82aw=20=C5=BBybort?= Date: Tue, 4 Mar 2014 16:28:57 +0100 Subject: [PATCH 31/42] Drop php suffix from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index babd4fea03..e5a4de8b97 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ testing environment in a few easy steps: ``` 5. Browse to http://vagrant.local/wp/wp-admin/ and activate the WP API plugin -6. Browse to http://vagrant.local/wp-json.php/ +6. Browse to http://vagrant.local/wp-json/ ### Testing From 319898fd7a669c5df15f3c96385728b9758b4919 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 26 Feb 2014 00:32:00 -0500 Subject: [PATCH 32/42] Force WP_JSON_CustomPostType to accept JSON for post editing/creation (closes #90) --- lib/class-wp-json-customposttype.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-json-customposttype.php b/lib/class-wp-json-customposttype.php index c72966ab48..9193538e92 100644 --- a/lib/class-wp-json-customposttype.php +++ b/lib/class-wp-json-customposttype.php @@ -53,12 +53,12 @@ public function __construct(WP_JSON_ResponseHandler $server) { public function registerRoutes( $routes ) { $routes[ $this->base ] = array( array( array( $this, 'getPosts' ), WP_JSON_Server::READABLE ), - array( array( $this, 'newPost' ), WP_JSON_Server::CREATABLE ), + array( array( $this, 'newPost' ), WP_JSON_Server::CREATABLE | WP_JSON_Server::ACCEPT_JSON ), ); $routes[ $this->base . '/(?P\d+)' ] = array( array( array( $this, 'getPost' ), WP_JSON_Server::READABLE ), - array( array( $this, 'editPost' ), WP_JSON_Server::EDITABLE ), + array( array( $this, 'editPost' ), WP_JSON_Server::EDITABLE | WP_JSON_Server::ACCEPT_JSON ), array( array( $this, 'deletePost' ), WP_JSON_Server::DELETABLE ), ); return $routes; From 06feaafe46a06e642a3cdb59feb5b5bce1e2479a Mon Sep 17 00:00:00 2001 From: Bryan Petty Date: Mon, 10 Mar 2014 14:18:24 -0600 Subject: [PATCH 33/42] Made some corrections to the local testing instructions. --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e5a4de8b97..3eebe6111d 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ run by Ryan McCue and is part of the WordPress 2013 GSoC projects. ## Documentation + Read the [plugin's documentation][docs]. [docs]: https://github.com/WP-API/WP-API/tree/master/docs ## Installation + ### As a Plugin Drop this directory in and activate it. You need to be using pretty permalinks to use the plugin, as it uses custom rewrite rules to power the API. @@ -27,6 +29,7 @@ enabled. ## Quick Setup + Want to test out WP-API and work on it? Here's how you can set up your own testing environment in a few easy steps: @@ -34,12 +37,14 @@ testing environment in a few easy steps: 2. Clone [Chassis](https://github.com/sennza/Chassis): ```bash - git clone git@github.com:sennza/Chassis.git api-tester + git clone --recursive git@github.com:sennza/Chassis.git api-tester + vagrant plugin install vagrant-hostsupdater ``` 3. Grab a copy of WP API: ```bash + cd api-tester mkdir -p content/plugins git clone git@github.com:WP-API/WP-API.git content/plugins/json-rest-api ``` @@ -51,34 +56,43 @@ testing environment in a few easy steps: ``` 5. Browse to http://vagrant.local/wp/wp-admin/ and activate the WP API plugin + + * Username: admin + * Password: password + 6. Browse to http://vagrant.local/wp-json/ ### Testing + For testing, you'll need a little bit more: -1. Install PHPUnit: +1. SSH into your Vagrant box, and install PHPUnit: ```bash - wget https://phar.phpunit.de/phpunit.phar + vagrant ssh + sudo apt-get install php-pear + sudo pear config-set auto_discover 1 + sudo pear install pear.phpunit.de/PHPUnit ``` 2. Clone WordPress development (including tests): ```bash - git clone https://github.com/tierra/wordpress.git /tmp/wordpress + git clone git://develop.git.wordpress.org/ /tmp/wordpress export WP_DEVELOP_DIR=/tmp/wordpress ``` 3. Run the testing suite: ```bash - cd /vagrant/content/plugins/json-rest-api/tests + cd /vagrant/content/plugins/json-rest-api phpunit ``` ## Issue Tracking + All tickets for the project are being tracked on the [GSoC Trac][]. Make sure you use the JSON REST API component. From 595d601d2ddb6255cd3c88c9baaeb2bdc2c47047 Mon Sep 17 00:00:00 2001 From: Bryan Petty Date: Mon, 10 Mar 2014 14:41:40 -0600 Subject: [PATCH 34/42] Fix step numbers in setup instructions. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3eebe6111d..d969d464f0 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,9 @@ testing environment in a few easy steps: 5. Browse to http://vagrant.local/wp/wp-admin/ and activate the WP API plugin - * Username: admin - * Password: password + ```Username: admin + Password: password + ``` 6. Browse to http://vagrant.local/wp-json/ From a633757730e0071e51e9a1c4189de6ecb433cdb8 Mon Sep 17 00:00:00 2001 From: Bryan Petty Date: Mon, 10 Mar 2014 14:45:41 -0600 Subject: [PATCH 35/42] Fix fenced code block in instructions. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d969d464f0..d9e2c97579 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ testing environment in a few easy steps: 5. Browse to http://vagrant.local/wp/wp-admin/ and activate the WP API plugin - ```Username: admin + ``` + Username: admin Password: password ``` From 83e45c4540af62c3caa7ae13ec017c56a30891a3 Mon Sep 17 00:00:00 2001 From: Rachel Baker Date: Fri, 21 Mar 2014 15:49:56 -0500 Subject: [PATCH 36/42] Return null if post doesn't have an excerpt. Fixes #72. --- lib/class-wp-json-posts.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-json-posts.php b/lib/class-wp-json-posts.php index 583d464141..e371210ea6 100644 --- a/lib/class-wp-json-posts.php +++ b/lib/class-wp-json-posts.php @@ -606,7 +606,11 @@ protected function prepare_excerpt( $excerpt ) { return __( 'There is no excerpt because this is a protected post.' ); } - return apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt ) ); + $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt ) ); + if ( empty( $excerpt ) ) { + return null; + } + return $excerpt; } /** From f3885659b43d8bbe993791652206c3dd4480ca1c Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 6 Apr 2014 14:17:33 +1000 Subject: [PATCH 37/42] Note that Chassis requires local-config.php --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d9e2c97579..e585f07561 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ testing environment in a few easy steps: ```bash cd api-tester + cp local-config-sample.php local-config.php mkdir -p content/plugins git clone git@github.com:WP-API/WP-API.git content/plugins/json-rest-api ``` From 8cc8b8a16f03a2faca33050d0fce420014c1bcc9 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 6 Apr 2014 14:27:27 +1000 Subject: [PATCH 38/42] Clarify that content/themes/ needs to be created Also removes local-config.php instructions, as newer Chassis versions don't require it. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e585f07561..e6d39bff1b 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ testing environment in a few easy steps: ```bash cd api-tester - cp local-config-sample.php local-config.php - mkdir -p content/plugins + mkdir -p content/plugins content/themes + cp -r wp/wp-content/themes/* content/themes git clone git@github.com:WP-API/WP-API.git content/plugins/json-rest-api ``` From 2ddef509d2c06d5c8a38bc31af7dafb26fdb9337 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 6 Apr 2014 14:32:43 +1000 Subject: [PATCH 39/42] Fix link to issue tracker We've been using GitHub for a while now. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6d39bff1b..9a2fdc2c5c 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,9 @@ For testing, you'll need a little bit more: ## Issue Tracking -All tickets for the project are being tracked on the [GSoC Trac][]. Make sure -you use the JSON REST API component. +All tickets for the project are being tracked on [GitHub][]. Previous issues can +be found on the [GSOC Trac][] issue tracker, however new issues should not be +filed there. -[GSoC Trac]: https://gsoc.trac.wordpress.org/query?component=JSON+REST+API +[GitHub]: https://github.com/WP-API/WP-API +[GSOC Trac]: https://gsoc.trac.wordpress.org/query?component=JSON+REST+API From c909611ee480b5af85501ab87f962daf29bc5a22 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 6 Apr 2014 14:45:41 +1000 Subject: [PATCH 40/42] Move filter registration out of CPT constructor --- lib/class-wp-json-customposttype.php | 12 ++++++++++-- plugin.php | 3 +-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-json-customposttype.php b/lib/class-wp-json-customposttype.php index 8d7dabb55f..b3c942cd51 100644 --- a/lib/class-wp-json-customposttype.php +++ b/lib/class-wp-json-customposttype.php @@ -38,10 +38,18 @@ public function __construct(WP_JSON_ResponseHandler $server) { return; } + parent::__construct($server); + } + + /** + * Add actions and filters for the post type + * + * This method should be called after instantiation to automatically add the + * required filters for the post type. + */ + public function register_filters() { add_filter( 'json_endpoints', array( $this, 'register_routes' ) ); add_filter( 'json_post_type_data', array( $this, 'type_archive_link' ), 10, 2 ); - - parent::__construct($server); } /** diff --git a/plugin.php b/plugin.php index 1f5cc573c5..6820821ab6 100644 --- a/plugin.php +++ b/plugin.php @@ -51,8 +51,7 @@ function json_api_default_filters($server) { // Pages $wp_json_pages = new WP_JSON_Pages($server); - add_filter( 'json_endpoints', array( $wp_json_pages, 'register_routes' ), 1 ); - add_filter( 'json_post_type_data', array( $wp_json_pages, 'type_archive_link' ), 10, 2 ); + $wp_json_pages->register_filters(); // Media $wp_json_media = new WP_JSON_Media($server); From a9d5c4762970ef7ad275184fd9ae483924ac3a57 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 6 Apr 2014 14:47:13 +1000 Subject: [PATCH 41/42] Add call to register_filters in documentation Missed in #126. --- docs/guides/extending.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guides/extending.md b/docs/guides/extending.md index 8f6124a5af..fad216325e 100644 --- a/docs/guides/extending.md +++ b/docs/guides/extending.md @@ -268,6 +268,7 @@ hooking and more for you: require_once dirname( __FILE__ ) . '/class-myplugin-api-mytype.php'; $myplugin_api_mytype = new MyPlugin_API_MyType( $server ); + $myplugin->register_filters(); } add_action( 'wp_json_server_before_serve', 'myplugin_api_init' ); From 1c0a4f4209c53e1c82a276bc5736fc3d95675165 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 6 Apr 2014 16:03:28 +1000 Subject: [PATCH 42/42] Update to 0.9 --- CHANGELOG.md | 143 +++++++++++++++++++++++++++++++++++ lib/class-wp-json-server.php | 2 +- plugin.php | 2 +- 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46a99d1df3..6c59547b64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,148 @@ # Changelog +## 0.9 + +- Move from `wp-json.php/` to `wp-json/` + + **This breaks backwards compatibility** and requires any clients to now use + `wp-json/`, or preferably the new RSD/Link headers. + + (props @rmccue, @matrixik, [#46][gh-46], [#96][gh-96], [#106][gh-106]) + +- Move filter registration out of CPT constructor. CPT subclasses now require + you to call `$myobject->register_filters()`, in order to move global state out + of the constructor. + + **This breaks backwards compatibility** and requires any subclassing to now + call `$myobject->register_filters()` + + (props @rmccue, @thenbrent, [#42][gh-42], [#126][gh-126]) + +- Introduce Response/ResponseInterface + + Endpoints that need to set headers or response codes should now return a + `WP_JSON_Response` rather than using the server methods. + `WP_JSON_ResponseInterface` may also be used for more flexible use of the + response methods. + + **Deprecation warning:** Calling `WP_JSON_Server::header`, + `WP_JSON_Server::link_header` and `WP_JSON_Server::query_navigation_headers` + is now deprecated. This will be removed in 1.0. + + (props @rmccue, [#33][gh-33]) + +- Change all semiCamelCase names to underscore_case. + + **Deprecation warning**: Any calls to semiCamelCase methods require any + subclassing to update method references. This will be removed in 1.0. + + (props @osiux, [#36][gh-36], [#82][gh-82]) + +- Add multisite compatibility. If the plugin is network activated, the plugin is + now activated once-per-site, so `wp-json/` is always site-local. + + (props @rachelbaker, [#48][gh-48], [#49][gh-49]) + +- Add RSD and Link headers for discovery + + (props @rmccue, [#40][gh-40]) + +- WP_JSON_Posts->prepare_author() now verifies the `$user` object is set. + + (props @rachelbaker, [#51][gh-51], [#54][gh-54]) + +- Added unit testing framework. Currently only a smaller number of tests, but we + plan to increase this significantly as soon as possible. + + (props @tierra, @osiux, [#65][gh-65], [#76][gh-76], [#84][gh-84]) + +- Link collection filtering docs to URL formatting guide. + + (props @kadamwhite, [#74][gh-74]) + +- Remove hardcoded `/pages` references from `WP_JSON_Pages` + + (props @rmccue, @thenbrent, [#28][gh-28], [#78][gh-78]) + +- Fix compatibility with `DateTime::createFromFormat` on PHP 5.2 + + (props @osiux, [#52][gh-52], [#79][gh-79]) + +- Document that `WP_JSON_CustomPostType::__construct()` requires a param of type + `WP_JSON_ResponseHandler`. + + (props @tlovett1, [#88][gh-88]) + +- Add timezone parameter to WP_JSON_DateTime::createFromFormat() + + (props @royboy789, @rachelbaker, [#85][gh-85], [#87][gh-87]) + +- Remove IXR references. `IXR_Error` is no longer accepted as a return value. + + **This breaks backwards compatibility** and requires anyone returning + `IXR_Error` objects to now return `WP_Error` or `WP_JSON_ResponseInterface` + objects. + + (props @rmccue, [#50][gh-50], [#77][gh-77]) + +- Fix bugs with attaching featured images to posts: + - `WP_JSON_Media::attachThumbnail()` should do nothing if `$update` is false + without a post ID + - The post ID must be fetched from the `$post` array. + + (props @Webbgaraget, [#55][gh-55]) + +- Don't declare `jsonSerialize` on ResponseInterface + + (props @rmccue, [#97][gh-97]) + +- Allow JSON post creation/update for `WP_JSON_CustomPostType` + + (props @tlovett1, [#90][gh-90], [#108][gh-108]) + +- Return null if post doesn't have an excerpt + + (props @rachelbacker, [#72][gh-72]) + +- Fix link to issue tracker in README + + (props @rmccue, @tobych, [#125][gh-125]) + +[View all changes](https://github.com/rmccue/WP-API/compare/0.8...0.9) + +[gh-28]: https://github.com/WP-API/WP-API/issues/28 +[gh-33]: https://github.com/WP-API/WP-API/issues/33 +[gh-36]: https://github.com/WP-API/WP-API/issues/36 +[gh-40]: https://github.com/WP-API/WP-API/issues/40 +[gh-42]: https://github.com/WP-API/WP-API/issues/42 +[gh-46]: https://github.com/WP-API/WP-API/issues/46 +[gh-48]: https://github.com/WP-API/WP-API/issues/48 +[gh-49]: https://github.com/WP-API/WP-API/issues/49 +[gh-50]: https://github.com/WP-API/WP-API/issues/50 +[gh-51]: https://github.com/WP-API/WP-API/issues/51 +[gh-52]: https://github.com/WP-API/WP-API/issues/52 +[gh-54]: https://github.com/WP-API/WP-API/issues/54 +[gh-55]: https://github.com/WP-API/WP-API/issues/55 +[gh-65]: https://github.com/WP-API/WP-API/issues/65 +[gh-72]: https://github.com/WP-API/WP-API/issues/72 +[gh-74]: https://github.com/WP-API/WP-API/issues/74 +[gh-76]: https://github.com/WP-API/WP-API/issues/76 +[gh-77]: https://github.com/WP-API/WP-API/issues/77 +[gh-78]: https://github.com/WP-API/WP-API/issues/78 +[gh-79]: https://github.com/WP-API/WP-API/issues/79 +[gh-82]: https://github.com/WP-API/WP-API/issues/82 +[gh-84]: https://github.com/WP-API/WP-API/issues/84 +[gh-85]: https://github.com/WP-API/WP-API/issues/85 +[gh-87]: https://github.com/WP-API/WP-API/issues/87 +[gh-88]: https://github.com/WP-API/WP-API/issues/88 +[gh-90]: https://github.com/WP-API/WP-API/issues/90 +[gh-96]: https://github.com/WP-API/WP-API/issues/96 +[gh-97]: https://github.com/WP-API/WP-API/issues/97 +[gh-106]: https://github.com/WP-API/WP-API/issues/106 +[gh-108]: https://github.com/WP-API/WP-API/issues/108 +[gh-125]: https://github.com/WP-API/WP-API/issues/125 +[gh-126]: https://github.com/WP-API/WP-API/issues/126 + ## 0.8 - Add compatibility layer for JsonSerializable. You can now return arbitrary objects from endpoints and use the `jsonSerialize()` method to return the data diff --git a/lib/class-wp-json-server.php b/lib/class-wp-json-server.php index cb4a53daf9..e38428446f 100644 --- a/lib/class-wp-json-server.php +++ b/lib/class-wp-json-server.php @@ -5,7 +5,7 @@ * Contains the WP_JSON_Server class. * * @package WordPress - * @version 0.8 + * @version 0.9 */ require_once ABSPATH . 'wp-admin/includes/admin.php'; diff --git a/plugin.php b/plugin.php index 6820821ab6..783505e31a 100644 --- a/plugin.php +++ b/plugin.php @@ -4,7 +4,7 @@ * Description: JSON-based REST API for WordPress, developed as part of GSoC 2013. * Author: Ryan McCue * Author URI: http://ryanmccue.info/ - * Version: 0.8 + * Version: 0.9 * Plugin URI: https://github.com/rmccue/WP-API */ include_once( dirname( __FILE__ ) . '/lib/class-jsonserializable.php' );