summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJorge Manuel B. S. Vicetto (jmbsvicetto) <jmbsvicetto@gentoo.org>2015-05-01 00:40:49 +0000
committerJorge Manuel B. S. Vicetto (jmbsvicetto) <jmbsvicetto@gentoo.org>2015-05-01 00:40:49 +0000
commitc64ce3ae8de09092f9570ab88a68fe920b0fd970 (patch)
treea9959002055a8bdff0ee46bf82ca6a2a39bf00cd /plugins/jetpack/json-endpoints
parentAdd easy-table plugin, requested by hwoarang (diff)
downloadblogs-gentoo-c64ce3ae8de09092f9570ab88a68fe920b0fd970.tar.gz
blogs-gentoo-c64ce3ae8de09092f9570ab88a68fe920b0fd970.tar.bz2
blogs-gentoo-c64ce3ae8de09092f9570ab88a68fe920b0fd970.zip
Update plugins and themes to the latest versions.
Signed-off-by: Jorge Manuel B. S. Vicetto (jmbsvicetto) <jmbsvicetto@gentoo.org>
Diffstat (limited to 'plugins/jetpack/json-endpoints')
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-comment-endpoint.php198
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-endpoint.php24
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-v1-1-endpoint.php24
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-comment-endpoint.php22
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-endpoint.php17
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-v1-1-endpoint.php17
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php29
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-v1-1-endpoint.php28
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-site-endpoint.php462
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomies-endpoint.php84
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomy-endpoint.php27
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-comments-endpoint.php261
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-embeds-endpoint.php35
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-endpoint.php48
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-v1-1-endpoint.php239
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-endpoint.php276
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-v1-1-endpoint.php436
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-shortcodes-endpoint.php27
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-list-users-endpoint.php78
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-menus-v1-1-endpoint.php697
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-post-endpoint.php664
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-post-v1-1-endpoint.php664
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-publicize-endpoint.php172
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-endpoint.php61
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-reversal-endpoint.php74
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-render-endpoint.php124
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-render-shortcode-endpoint.php50
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-sharing-buttons-endpoint.php385
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php349
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-taxonomy-endpoint.php29
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-comment-endpoint.php256
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-endpoint.php39
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-v1-1-endpoint.php77
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-endpoint.php659
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-1-endpoint.php668
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-2-endpoint.php572
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-update-taxonomy-endpoint.php148
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-endpoint.php59
-rw-r--r--plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-v1-1-endpoint.php47
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-check-capabilities-endpoint.php25
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-endpoint.php20
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-modify-endpoint.php66
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-endpoint.php115
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-endpoint.php124
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-get-endpoint.php6
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-list-endpoint.php13
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-modify-endpoint.php62
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-delete-endpoint.php33
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php197
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-get-endpoint.php6
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-install-endpoint.php83
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php15
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-endpoint.php178
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-sync-endpoint.php13
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-active-endpoint.php49
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-delete-endpoint.php41
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-endpoint.php158
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-get-endpoint.php6
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-install-endpoint.php82
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-list-endpoint.php13
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-modify-endpoint.php58
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-updates-status-endpoint.php34
-rw-r--r--plugins/jetpack/json-endpoints/jetpack/json-api-jetpack-endpoints.php580
63 files changed, 10103 insertions, 0 deletions
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-comment-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-comment-endpoint.php
new file mode 100644
index 00000000..0d1b01cf
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-comment-endpoint.php
@@ -0,0 +1,198 @@
+<?php
+
+
+abstract class WPCOM_JSON_API_Comment_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $comment_object_format = array(
+ // explicitly document and cast all output
+ 'ID' => '(int) The comment ID.',
+ 'post' => "(object>post_reference) A reference to the comment's post.",
+ 'author' => '(object>author) The author of the comment.',
+ 'date' => "(ISO 8601 datetime) The comment's creation time.",
+ 'URL' => '(URL) The full permalink URL to the comment.',
+ 'short_URL' => '(URL) The wp.me short URL.',
+ 'content' => '(HTML) <code>context</code> dependent.',
+ 'status' => array(
+ 'approved' => 'The comment has been approved.',
+ 'unapproved' => 'The comment has been held for review in the moderation queue.',
+ 'spam' => 'The comment has been marked as spam.',
+ 'trash' => 'The comment is in the trash.',
+ ),
+ 'parent' => "(object>comment_reference|false) A reference to the comment's parent, if it has one.",
+ 'type' => array(
+ 'comment' => 'The comment is a regular comment.',
+ 'trackback' => 'The comment is a trackback.',
+ 'pingback' => 'The comment is a pingback.',
+ ),
+ 'like_count' => '(int) The number of likes for this comment.',
+ 'i_like' => '(bool) Does the current user like this comment?',
+ 'meta' => '(object) Meta data',
+ );
+
+ // var $response_format =& $this->comment_object_format;
+
+ function __construct( $args ) {
+ if ( !$this->response_format ) {
+ $this->response_format =& $this->comment_object_format;
+ }
+ parent::__construct( $args );
+ }
+
+ function get_comment( $comment_id, $context ) {
+ global $blog_id;
+
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ $types = array( '', 'comment', 'pingback', 'trackback' );
+ if ( !in_array( $comment->comment_type, $types ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ $post = get_post( $comment->comment_post_ID );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ $status = wp_get_comment_status( $comment->comment_ID );
+
+ // Permissions
+ switch ( $context ) {
+ case 'edit' :
+ if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit comment', 403 );
+ }
+
+ $GLOBALS['post'] = $post;
+ $comment = get_comment_to_edit( $comment->comment_ID );
+ foreach ( array( 'comment_author', 'comment_author_email', 'comment_author_url' ) as $field ) {
+ $comment->$field = htmlspecialchars_decode( $comment->$field, ENT_QUOTES );
+ }
+ break;
+ case 'display' :
+ if ( 'approved' !== $status ) {
+ $current_user_id = get_current_user_id();
+ $user_can_read_coment = false;
+ if ( $current_user_id && $comment->user_id && $current_user_id == $comment->user_id ) {
+ $user_can_read_coment = true;
+ } elseif (
+ $comment->comment_author_email && $comment->comment_author
+ &&
+ isset( $this->api->token_details['user'] )
+ &&
+ $this->api->token_details['user']['user_email'] === $comment->comment_author_email
+ &&
+ $this->api->token_details['user']['display_name'] === $comment->comment_author
+ ) {
+ $user_can_read_coment = true;
+ } else {
+ $user_can_read_coment = current_user_can( 'edit_comment', $comment->comment_ID );
+ }
+
+ if ( !$user_can_read_coment ) {
+ return new WP_Error( 'unauthorized', 'User cannot read unapproved comment', 403 );
+ }
+ }
+
+ $GLOBALS['post'] = $post;
+ setup_postdata( $post );
+ break;
+ default :
+ return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ $GLOBALS['comment'] = $comment;
+ $response = array();
+
+ foreach ( array_keys( $this->comment_object_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ // explicitly cast all output
+ $response[$key] = (int) $comment->comment_ID;
+ break;
+ case 'post' :
+ $response[$key] = (object) array(
+ 'ID' => (int) $post->ID,
+ 'title' => (string) get_the_title( $post->ID ),
+ 'type' => (string) $post->post_type,
+ 'link' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
+ );
+ break;
+ case 'author' :
+ $response[$key] = (object) $this->get_author( $comment, 'edit' === $context && current_user_can( 'edit_comment', $comment->comment_ID ) );
+ break;
+ case 'date' :
+ $response[$key] = (string) $this->format_date( $comment->comment_date_gmt, $comment->comment_date );
+ break;
+ case 'URL' :
+ $response[$key] = (string) esc_url_raw( get_comment_link( $comment->comment_ID ) );
+ break;
+ case 'short_URL' :
+ // @todo - pagination
+ $response[$key] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) . "%23comment-{$comment->comment_ID}" );
+ break;
+ case 'content' :
+ if ( 'display' === $context ) {
+ ob_start();
+ comment_text();
+ $response[$key] = (string) ob_get_clean();
+ } else {
+ $response[$key] = (string) $comment->comment_content;
+ }
+ break;
+ case 'status' :
+ $response[$key] = (string) $status;
+ break;
+ case 'parent' : // (object|false)
+ if ( $comment->comment_parent ) {
+ $parent = get_comment( $comment->comment_parent );
+ $response[$key] = (object) array(
+ 'ID' => (int) $parent->comment_ID,
+ 'type' => (string) ( $parent->comment_type ? $parent->comment_type : 'comment' ),
+ 'link' => (string) $this->get_comment_link( $blog_id, $parent->comment_ID ),
+ );
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'type' :
+ $response[$key] = (string) ( $comment->comment_type ? $comment->comment_type : 'comment' );
+ break;
+ case 'like_count' :
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $response[ $key ] = (int) $this->api->comment_like_count( $blog_id, $post->ID, $comment->comment_ID );
+ }
+ break;
+ case 'i_like' :
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $response[ $key ] = (bool) Likes::comment_like_current_user_likes( $blog_id, $comment->comment_ID );
+ }
+ break;
+ case 'meta' :
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID ),
+ 'help' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'help' ),
+ 'site' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+ 'post' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $comment->comment_post_ID ),
+ 'replies' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'replies/' ),
+// 'author' => (string) $this->get_user_link( $comment->user_id ),
+// 'via' => (string) $this->get_post_link( $ping_origin_blog_id, $ping_origin_post_id ), // Ping/trackbacks
+ 'likes' => (string) $this->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'likes/' ),
+ ),
+ );
+ break;
+ }
+ }
+
+ unset( $GLOBALS['comment'], $GLOBALS['post'] );
+ return $response;
+ }
+}
+
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-endpoint.php
new file mode 100644
index 00000000..5f26a7dc
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-endpoint.php
@@ -0,0 +1,24 @@
+<?php
+
+class WPCOM_JSON_API_Delete_Media_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( !current_user_can( 'upload_files', $media_id ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ $item = $this->get_media_item( $media_id );
+
+ if ( is_wp_error( $item ) ) {
+ return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
+ }
+
+ wp_delete_post( $media_id );
+ $item->status = 'deleted';
+ return $item;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-v1-1-endpoint.php
new file mode 100644
index 00000000..10c639ed
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-delete-media-v1-1-endpoint.php
@@ -0,0 +1,24 @@
+<?php
+
+class WPCOM_JSON_API_Delete_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'upload_files', $media_id ) ) {
+ return new WP_Error( 'unauthorized', 'User is not authorized delete media', 403 );
+ }
+
+ $item = $this->get_media_item_v1_1( $media_id );
+
+ if ( is_wp_error( $item ) ) {
+ return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
+ }
+
+ wp_delete_post( $media_id, true );
+ $item->status = 'deleted';
+ return $item;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-comment-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-comment-endpoint.php
new file mode 100644
index 00000000..af167e0a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-comment-endpoint.php
@@ -0,0 +1,22 @@
+<?php
+
+class WPCOM_JSON_API_Get_Comment_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
+ // /sites/%s/comments/%d -> $blog_id, $comment_id
+ function callback( $path = '', $blog_id = 0, $comment_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ $return = $this->get_comment( $comment_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-endpoint.php
new file mode 100644
index 00000000..1935445e
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-endpoint.php
@@ -0,0 +1,17 @@
+<?php
+
+class WPCOM_JSON_API_Get_Media_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ //upload_files can probably be used for other endpoints but we want contributors to be able to use media too
+ if ( !current_user_can( 'edit_posts', $media_id ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ return $this->get_media_item( $media_id );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-v1-1-endpoint.php
new file mode 100644
index 00000000..11976509
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-media-v1-1-endpoint.php
@@ -0,0 +1,17 @@
+<?php
+
+class WPCOM_JSON_API_Get_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ //upload_files can probably be used for other endpoints but we want contributors to be able to use media too
+ if ( ! current_user_can( 'edit_posts', $media_id ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ return $this->get_media_item_v1_1( $media_id );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php
new file mode 100644
index 00000000..43b05032
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php
@@ -0,0 +1,29 @@
+<?php
+class WPCOM_JSON_API_Get_Post_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ // /sites/%s/posts/name:%s -> $blog_id, $post_id // not documented
+ // /sites/%s/posts/slug:%s -> $blog_id, $post_id
+ function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( false === strpos( $path, '/posts/slug:' ) && false === strpos( $path, '/posts/name:' ) ) {
+ $get_by = 'ID';
+ } else {
+ $get_by = 'name';
+ }
+
+ $return = $this->get_post_by( $get_by, $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-v1-1-endpoint.php
new file mode 100644
index 00000000..5194daa5
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-v1-1-endpoint.php
@@ -0,0 +1,28 @@
+<?php
+class WPCOM_JSON_API_Get_Post_v1_1_Endpoint extends WPCOM_JSON_API_Post_v1_1_Endpoint {
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ // /sites/%s/posts/slug:%s -> $blog_id, $post_id
+ function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( false === strpos( $path, '/posts/slug:' ) ) {
+ $get_by = 'ID';
+ } else {
+ $get_by = 'name';
+ }
+
+ $return = $this->get_post_by( $get_by, $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-site-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-site-endpoint.php
new file mode 100644
index 00000000..934f7e3d
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-site-endpoint.php
@@ -0,0 +1,462 @@
+<?php
+
+class WPCOM_JSON_API_GET_Site_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ public static $site_format = array(
+ 'ID' => '(int) Site ID',
+ 'name' => '(string) Title of site',
+ 'description' => '(string) Tagline or description of site',
+ 'URL' => '(string) Full URL to the site',
+ 'jetpack' => '(bool) Whether the site is a Jetpack site or not',
+ 'post_count' => '(int) The number of posts the site has',
+ 'subscribers_count' => '(int) The number of subscribers the site has',
+ 'lang' => '(string) Primary language code of the site',
+ 'icon' => '(array) An array of icon formats for the site',
+ 'logo' => '(array) The site logo, set in the Customizer',
+ 'visible' => '(bool) If this site is visible in the user\'s site list',
+ 'is_private' => '(bool) If the site is a private site or not',
+ 'is_following' => '(bool) If the current user is subscribed to this site in the reader',
+ 'options' => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site. Note: Post formats is deprecated, please see /sites/$id/post-formats/',
+ 'meta' => '(object) Meta data',
+ );
+
+ // /sites/mine
+ // /sites/%s -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ global $wpdb;
+ if ( 'mine' === $blog_id ) {
+ $api = WPCOM_JSON_API::init();
+ if ( !$api->token_details || empty( $api->token_details['blog_id'] ) ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to query information about the current blog.', 403 );
+ }
+ $blog_id = $api->token_details['blog_id'];
+ }
+
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $response = $this->build_current_site_response();
+
+ do_action( 'wpcom_json_api_objects', 'sites' );
+
+ return $response;
+ }
+
+ /**
+ * Collects the necessary information to return for a site's response.
+ *
+ * @return (array)
+ */
+ public function build_current_site_response( ) {
+
+ global $wpdb, $wp_version;
+
+ $response_format = self::$site_format;
+
+ $is_user_logged_in = is_user_logged_in();
+
+ $visible = array();
+
+ if ( $is_user_logged_in ) {
+ $current_user = wp_get_current_user();
+ $visible = get_user_meta( $current_user->ID, 'blog_visibility', true );
+
+ if ( !is_array( $visible ) )
+ $visible = array();
+
+ }
+
+ $blog_id = (int) $this->api->get_blog_id_for_output();
+
+ $is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
+ $site_url = get_option( 'siteurl' );
+
+ if ( $is_jetpack ) {
+ remove_filter( 'option_stylesheet', 'fix_theme_location' );
+ if ( 'https' !== parse_url( $site_url, PHP_URL_SCHEME ) ) {
+ add_filter( 'set_url_scheme', array( $this, 'force_http' ), 10, 3 );
+ }
+ }
+ foreach ( array_keys( $response_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ $response[$key] = $blog_id;
+ break;
+ case 'name' :
+ $response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
+ break;
+ case 'description' :
+ $response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
+ break;
+ case 'URL' :
+ $response[$key] = (string) home_url();
+ break;
+ case 'jetpack' :
+ $response[$key] = $is_jetpack; // jetpack magic affects this value
+ break;
+ case 'is_private' :
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $public_setting = get_option( 'blog_public' );
+ if ( -1 == $public_setting )
+ $response[$key] = true;
+ else
+ $response[$key] = false;
+ } else {
+ $response[$key] = false; // magic
+ }
+ break;
+ case 'visible' :
+ if ( $is_user_logged_in ){
+ $is_visible = true;
+ if ( isset( $visible[$blog_id] ) ) {
+ $is_visible = $visible[$blog_id];
+ }
+ // null and true are visible
+ $response[$key] = $is_visible;
+ }
+ break;
+ case 'post_count' :
+ if ( $is_user_logged_in )
+ $response[$key] = (int) $wpdb->get_var("SELECT COUNT(*) FROM $wpdb->posts WHERE post_status = 'publish'");
+ break;
+ case 'lang' :
+ if ( $is_user_logged_in )
+ $response[$key] = (string) get_bloginfo( 'language' );
+ break;
+ case 'icon' :
+ if ( function_exists( 'blavatar_domain' ) && function_exists( 'blavatar_exists' ) && function_exists( 'blavatar_url' ) ) {
+ $domain = blavatar_domain( home_url() );
+ if ( blavatar_exists( $domain ) ) {
+ $response[ $key ] = array(
+ 'img' => (string) remove_query_arg( 's', blavatar_url( $domain, 'img' ) ),
+ 'ico' => (string) remove_query_arg( 's', blavatar_url( $domain, 'ico' ) ),
+ );
+ } else {
+ // This is done so that we can access the updated blavatar on .com via the /me/sites endpoint
+ if( is_jetpack_site() ) {
+
+ $site_icon_url = get_option( 'jetpack_site_icon_url' );
+ if( $site_icon_url ) {
+ $response[ $key ] = array(
+ 'img' => (string) jetpack_photon_url( $site_icon_url, array() , 'https' ),
+ 'ico' => (string) jetpack_photon_url( $site_icon_url, array( 'w' => 16 ), 'https' )
+ );
+ }
+ }
+ }
+ } elseif ( function_exists( 'jetpack_site_icon_url' ) && function_exists( 'jetpack_photon_url' ) ) {
+ $response[ $key ] = array(
+ 'img' => (string) jetpack_photon_url( jetpack_site_icon_url( get_current_blog_id() , 80 ), array( 'w' => 80 ), 'https' ),
+ 'ico' => (string) jetpack_photon_url( jetpack_site_icon_url( get_current_blog_id() , 16 ), array( 'w' => 16 ), 'https' ),
+ );
+ }
+ break;
+ case 'logo' :
+ // Set an empty response array.
+ $response[$key] = array(
+ 'id' => (int) 0,
+ 'sizes' => array(),
+ 'url' => '',
+ );
+
+ // Get current site logo values.
+ $logo = get_option( 'site_logo' );
+
+ // Update the response array if there's a site logo currenty active.
+ if ( $logo && 0 != $logo['id'] ) {
+ $response[$key]['id'] = $logo['id'];
+ $response[$key]['url'] = $logo['url'];
+
+ foreach ( $logo['sizes'] as $size => $properties ) {
+ $response[$key]['sizes'][$size] = $properties;
+ }
+ }
+ break;
+ case 'subscribers_count' :
+
+ if ( function_exists( 'wpcom_subs_total_wpcom_subscribers' ) ) {
+ $total_wpcom_subs = wpcom_subs_total_wpcom_subscribers(
+ array(
+ 'blog_id' => $blog_id,
+ )
+ );
+ $response[$key] = $total_wpcom_subs;
+ } else {
+ $response[$key] = 0; // magic
+ }
+ break;
+ case 'is_following':
+ $response[$key] = (bool) $this->api->is_following( $blog_id );
+ break;
+ case 'options':
+ // Figure out if the blog supports VideoPress, have to do some extra checking for JP blogs
+ $has_videopress = false;
+ if ( get_option( 'video_upgrade' ) == '1' ) {
+ $has_videopress = true;
+ } else {
+ if ( class_exists( 'Jetpack_Options' ) ) {
+ $videopress = Jetpack_Options::get_option( 'videopress', array() );
+ if ( $videopress['blog_id'] > 0 )
+ $has_videopress = true;
+ }
+ }
+
+ // deprecated - see separate endpoint. get a list of supported post formats
+ $all_formats = get_post_format_strings();
+ $supported = get_theme_support( 'post-formats' );
+
+ $supported_formats = array();
+
+ if ( isset( $supported[0] ) ) {
+ foreach ( $supported[0] as $format ) {
+ $supported_formats[ $format ] = $all_formats[ $format ];
+ }
+ }
+
+ // determine if sharing buttons should be visible by default
+ $default_sharing_status = false;
+ if ( class_exists( 'Sharing_Service' ) ) {
+ $ss = new Sharing_Service();
+ $blog_services = $ss->get_blog_services();
+ $default_sharing_status = ! empty( $blog_services['visible'] );
+ }
+
+ $is_mapped_domain = false;
+
+ if ( function_exists( 'get_primary_redirect' ) ) {
+ $primary_redirect = strtolower( get_primary_redirect() );
+ if ( false === strpos( $primary_redirect, '.wordpress.com' ) ) {
+ $is_mapped_domain = true;
+ }
+ }
+
+ $is_redirect = false;
+
+ if ( function_exists( 'get_primary_domain_mapping_record' ) ) {
+ if ( get_primary_domain_mapping_record()->type == 1 ) {
+ $is_redirect = true;
+ }
+ }
+
+ if ( function_exists( 'get_mime_types' ) ) {
+ $allowed_file_types = get_mime_types();
+ } else {
+ // http://codex.wordpress.org/Uploading_Files
+ $mime_types = get_allowed_mime_types();
+ foreach ( $mime_types as $type => $mime_type ) {
+ $extras = explode( '|', $type );
+ foreach ( $extras as $extra ) {
+ $allowed_file_types[] = $extra;
+ }
+ }
+ }
+
+ if ( function_exists( 'get_blog_details' ) ) {
+ $blog_details = get_blog_details();
+ if ( ! empty( $blog_details->registered ) ) {
+ $registered_date = $blog_details->registered;
+ }
+ }
+
+ $upgraded_filetypes_enabled = false;
+ if ( $is_jetpack || get_option( 'use_upgraded_upload_filetypes' ) ) {
+ $upgraded_filetypes_enabled = true;
+ }
+
+ $response[$key] = array(
+ 'timezone' => (string) get_option( 'timezone_string' ),
+ 'gmt_offset' => (float) get_option( 'gmt_offset' ),
+ 'videopress_enabled' => $has_videopress,
+ 'upgraded_filetypes_enabled' => $upgraded_filetypes_enabled,
+ 'login_url' => wp_login_url(),
+ 'admin_url' => get_admin_url(),
+ 'is_mapped_domain' => $is_mapped_domain,
+ 'is_redirect' => $is_redirect,
+ 'unmapped_url' => get_site_url( $blog_id ),
+ 'featured_images_enabled' => current_theme_supports( 'post-thumbnails' ),
+ 'theme_slug' => get_option( 'stylesheet' ),
+ 'header_image' => get_theme_mod( 'header_image_data' ),
+ 'background_color' => get_theme_mod( 'background_color' ),
+ 'image_default_link_type' => get_option( 'image_default_link_type' ),
+ 'image_thumbnail_width' => (int) get_option( 'thumbnail_size_w' ),
+ 'image_thumbnail_height' => (int) get_option( 'thumbnail_size_h' ),
+ 'image_thumbnail_crop' => get_option( 'thumbnail_crop' ),
+ 'image_medium_width' => (int) get_option( 'medium_size_w' ),
+ 'image_medium_height' => (int) get_option( 'medium_size_h' ),
+ 'image_large_width' => (int) get_option( 'large_size_w' ),
+ 'image_large_height' => (int) get_option( 'large_size_h' ),
+ 'post_formats' => $supported_formats,
+ 'allowed_file_types' => $allowed_file_types,
+ 'show_on_front' => get_option( 'show_on_front' ),
+ 'default_likes_enabled' => (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) ),
+ 'default_sharing_status' => (bool) $default_sharing_status,
+ 'default_comment_status' => ( 'closed' == get_option( 'default_comment_status' ) ? false : true ),
+ 'default_ping_status' => ( 'closed' == get_option( 'default_ping_status' ) ? false : true ),
+ 'software_version' => $wp_version,
+ 'created_at' => ! empty( $registered_date ) ? $this->format_date( $registered_date ) : '0000-00-00T00:00:00+00:00',
+ );
+
+ if ( 'page' === get_option( 'show_on_front' ) ) {
+ $response['options']['page_on_front'] = (int) get_option( 'page_on_front' );
+ $response['options']['page_for_posts'] = (int) get_option( 'page_for_posts' );
+ }
+
+ if ( $is_jetpack ) {
+ $response['options']['jetpack_version'] = get_option( 'jetpack_version' );
+
+ if( get_option( 'jetpack_main_network_site' ) ) {
+ $response['options']['main_network_site'] = (string) rtrim( get_option( 'jetpack_main_network_site' ), '/' );
+ }
+
+ // Sites have to prove that they are not main_network site.
+ // If the sync happends right then we should be able to see that we are not dealing with a network site
+ $response['options']['is_multi_network'] = (bool) get_option( 'jetpack_is_main_network', true );
+ $response['options']['is_multi_site'] = (bool) get_option( 'jetpack_is_multi_site', true );
+
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) )
+ unset( $response[$key] );
+ break;
+ case 'meta' :
+ $xmlrpc_scheme = apply_filters( 'wpcom_json_api_xmlrpc_scheme', parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );
+ $xmlrpc_url = site_url( 'xmlrpc.php', $xmlrpc_scheme );
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_site_link( $blog_id ),
+ 'help' => (string) $this->get_site_link( $blog_id, 'help' ),
+ 'posts' => (string) $this->get_site_link( $blog_id, 'posts/' ),
+ 'comments' => (string) $this->get_site_link( $blog_id, 'comments/' ),
+ 'xmlrpc' => (string) $xmlrpc_url,
+ ),
+ );
+ break;
+ }
+ }
+ if ( $is_jetpack ) {
+ add_filter( 'option_stylesheet', 'fix_theme_location' );
+ if ( 'https' !== parse_url( $site_url, PHP_URL_SCHEME ) ) {
+ remove_filter( 'set_url_scheme', array( $this, 'force_http' ), 10, 3 );
+ }
+ }
+
+ return $response;
+
+ }
+
+ function force_http( $url, $scheme, $orig_scheme ) {
+ return preg_replace('/^https:\/\//', 'http://', $url, 1 );
+ }
+
+}
+
+class WPCOM_JSON_API_List_Post_Formats_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/post-formats -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $this->load_theme_functions();
+ }
+
+ // Get a list of supported post formats.
+ $all_formats = get_post_format_strings();
+ $supported = get_theme_support( 'post-formats' );
+
+ $supported_formats = $response['formats'] = array();
+
+ if ( isset( $supported[0] ) ) {
+ foreach ( $supported[0] as $format ) {
+ $supported_formats[ $format ] = $all_formats[ $format ];
+ }
+ }
+
+ $response['formats'] = $supported_formats;
+
+ return $response;
+ }
+}
+
+class WPCOM_JSON_API_List_Page_Templates_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/page-templates -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $this->load_theme_functions();
+ }
+
+ $response = array();
+ $page_templates = array();
+
+ $templates = get_page_templates();
+ ksort( $templates );
+
+ foreach ( array_keys( $templates ) as $label ) {
+ $page_templates[] = array(
+ 'label' => $label,
+ 'file' => $templates[ $label ]
+ );
+ }
+
+ $response['templates'] = $page_templates;
+
+ return $response;
+ }
+}
+
+class WPCOM_JSON_API_List_Post_Types_Endpoint extends WPCOM_JSON_API_Endpoint {
+ static $post_type_keys_to_include = array( 'name', 'label', 'description', 'map_meta_cap' );
+
+ // /sites/%s/post-types -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $this->load_theme_functions();
+ }
+
+ $args = $this->query_args();
+ $queryable_only = isset( $args['api_queryable'] ) && $args['api_queryable'];
+
+ // Get a list of available post types
+ $post_types = get_post_types( array( 'public' => true ) );
+ $formatted_post_type_objects = array();
+
+ // Retrieve post type object for each post type
+ foreach ( $post_types as $post_type ) {
+ // Skip non-queryable if filtering on queryable only
+ $is_queryable = $this->is_post_type_allowed( $post_type );
+ if ( $queryable_only && ! $is_queryable ) {
+ continue;
+ }
+
+ $post_type_object = get_post_type_object( $post_type );
+ $formatted_post_type_object = array();
+
+ // Include only the desired keys in the response
+ foreach ( self::$post_type_keys_to_include as $key ) {
+ $formatted_post_type_object[ $key ] = $post_type_object->{ $key };
+ }
+ $formatted_post_type_object['api_queryable'] = $is_queryable;
+
+ $formatted_post_type_objects[] = $formatted_post_type_object;
+ }
+
+ return array(
+ 'found' => count( $formatted_post_type_objects ),
+ 'post_types' => $formatted_post_type_objects
+ );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomies-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomies-endpoint.php
new file mode 100644
index 00000000..e9db059a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomies-endpoint.php
@@ -0,0 +1,84 @@
+<?php
+
+class WPCOM_JSON_API_Get_Taxonomies_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/tags -> $blog_id
+ // /sites/%s/categories -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+ $args = $this->process_args( $args );
+
+ if ( preg_match( '#/tags#i', $path ) ) {
+ return $this->tags( $args );
+ } else {
+ return $this->categories( $args );
+ }
+ }
+
+ function process_args( $args ) {
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 100;
+ } elseif ( 1000 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 1000.', 400 );
+ }
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+
+ $args['offset'] = ( $args['page'] - 1 ) * $args['number'];
+ unset( $args['page'] );
+ }
+
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+
+ $args['orderby'] = $args['order_by'];
+ unset( $args['order_by'] );
+
+ unset( $args['context'], $args['pretty'], $args['http_envelope'], $args['fields'] );
+ return $args;
+ }
+
+ function categories( $args ) {
+ $args['get'] = 'all';
+
+ $cats = get_categories( $args );
+ unset( $args['offset'] );
+ $found = wp_count_terms( 'category', $args );
+
+ $cats_obj = array();
+ foreach ( $cats as $cat ) {
+ $cats_obj[] = $this->format_taxonomy( $cat, 'category', 'display' );
+ }
+
+ return array(
+ 'found' => (int) $found,
+ 'categories' => $cats_obj
+ );
+ }
+
+ function tags( $args ) {
+ $args['get'] = 'all';
+
+ $tags = (array) get_tags( $args );
+ unset( $args['offset'] );
+ $found = wp_count_terms( 'post_tag', $args );
+
+ $tags_obj = array();
+ foreach ( $tags as $tag ) {
+ $tags_obj[] = $this->format_taxonomy( $tag, 'post_tag', 'display' );
+ }
+
+ return array(
+ 'found' => (int) $found,
+ 'tags' => $tags_obj
+ );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomy-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomy-endpoint.php
new file mode 100644
index 00000000..6843c254
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-taxonomy-endpoint.php
@@ -0,0 +1,27 @@
+<?php
+class WPCOM_JSON_API_Get_Taxonomy_Endpoint extends WPCOM_JSON_API_Taxonomy_Endpoint {
+ // /sites/%s/tags/slug:%s -> $blog_id, $tag_id
+ // /sites/%s/categories/slug:%s -> $blog_id, $tag_id
+ function callback( $path = '', $blog_id = 0, $taxonomy_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+ if ( preg_match( '#/tags/#i', $path ) ) {
+ $taxonomy_type = "post_tag";
+ } else {
+ $taxonomy_type = "category";
+ }
+
+ $return = $this->get_taxonomy( $taxonomy_id, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-comments-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-comments-endpoint.php
new file mode 100644
index 00000000..942d6962
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-comments-endpoint.php
@@ -0,0 +1,261 @@
+<?php
+
+class WPCOM_JSON_API_List_Comments_Walker extends Walker {
+ public $tree_type = 'comment';
+
+ public $db_fields = array(
+ 'parent' => 'comment_parent',
+ 'id' => 'comment_ID'
+ );
+
+ public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
+ $output[] = $object->comment_ID;
+ }
+
+ /**
+ * Taken from WordPress's Walker_Comment::display_element()
+ *
+ * This function is designed to enhance Walker::display_element() to
+ * display children of higher nesting levels than selected inline on
+ * the highest depth level displayed. This prevents them being orphaned
+ * at the end of the comment list.
+ *
+ * Example: max_depth = 2, with 5 levels of nested content.
+ * 1
+ * 1.1
+ * 1.1.1
+ * 1.1.1.1
+ * 1.1.1.1.1
+ * 1.1.2
+ * 1.1.2.1
+ * 2
+ * 2.2
+ *
+ * @see Walker_Comment::display_element()
+ * @see Walker::display_element()
+ * @see wp_list_comments()
+ */
+ public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
+
+ if ( !$element )
+ return;
+
+ $id_field = $this->db_fields['id'];
+ $id = $element->$id_field;
+
+ parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
+
+ // If we're at the max depth, and the current element still has children, loop over those and display them at this level
+ // This is to prevent them being orphaned to the end of the list.
+ if ( $max_depth <= $depth + 1 && isset( $children_elements[$id]) ) {
+ foreach ( $children_elements[ $id ] as $child )
+ $this->display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
+
+ unset( $children_elements[ $id ] );
+ }
+
+ }
+}
+
+// @todo permissions
+class WPCOM_JSON_API_List_Comments_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
+ var $response_format = array(
+ 'found' => '(int) The total number of comments found that match the request (ignoring limits, offsets, and pagination).',
+ 'comments' => '(array:comment) An array of comment objects.',
+ );
+
+ function __construct( $args ) {
+ parent::__construct( $args );
+ $this->query = array_merge( $this->query, array(
+ 'number' => '(int=20) The number of comments to return. Limit: 100. When using hierarchical=1, number refers to the number of top-level comments returned.',
+ 'offset' => '(int=0) 0-indexed offset. Not available if using hierarchical=1.',
+ 'page' => '(int) Return the Nth 1-indexed page of comments. Takes precedence over the <code>offset</code> parameter. When using hierarchical=1, pagination is a bit different. See the note on the number parameter.',
+ 'order' => array(
+ 'DESC' => 'Return comments in descending order from newest to oldest.',
+ 'ASC' => 'Return comments in ascending order from oldest to newest.',
+ ),
+ 'hierarchical' => array(
+ 'false' => '',
+ 'true' => '(BETA) Order the comment list hierarchically.',
+ ),
+ 'after' => '(ISO 8601 datetime) Return comments dated on or after the specified datetime. Not available if using hierarchical=1.',
+ 'before' => '(ISO 8601 datetime) Return comments dated on or before the specified datetime. Not available if using hierarchical=1.',
+ 'type' => array(
+ 'any' => 'Return all comments regardless of type.',
+ 'comment' => 'Return only regular comments.',
+ 'trackback' => 'Return only trackbacks.',
+ 'pingback' => 'Return only pingbacks.',
+ 'pings' => 'Return both trackbacks and pingbacks.',
+ ),
+ 'status' => array(
+ 'approved' => 'Return only approved comments.',
+ 'unapproved' => 'Return only comments in the moderation queue.',
+ 'spam' => 'Return only comments marked as spam.',
+ 'trash' => 'Return only comments in the trash.',
+ 'all' => 'Return comments of all statuses.',
+ ),
+ ) );
+ }
+
+ // /sites/%s/comments/ -> $blog_id
+ // /sites/%s/posts/%d/replies/ -> $blog_id, $post_id
+ // /sites/%s/comments/%d/replies/ -> $blog_id, $comment_id
+ function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ if ( false !== strpos( $path, '/posts/' ) ) {
+ // We're looking for comments of a particular post
+ $post_id = $object_id;
+ $comment_id = 0;
+ } else {
+ // We're looking for comments for the whole blog, or replies to a single comment
+ $comment_id = $object_id;
+ $post_id = 0;
+ }
+
+ // We can't efficiently get the number of replies to a single comment
+ $count = false;
+ $found = -1;
+
+ if ( !$comment_id ) {
+ // We can get comment counts for the whole site or for a single post, but only for certain queries
+ if ( 'any' === $args['type'] && !isset( $args['after'] ) && !isset( $args['before'] ) ) {
+ $count = wp_count_comments( $post_id );
+ }
+ }
+
+ switch ( $args['status'] ) {
+ case 'approved' :
+ $status = 'approve';
+ if ( $count ) {
+ $found = $count->approved;
+ }
+ break;
+ default :
+ if ( !current_user_can( 'moderate_comments' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot read non-approved comments', 403 );
+ }
+ if ( 'unapproved' === $args['status'] ) {
+ $status = 'hold';
+ $count_status = 'moderated';
+ } elseif ( 'all' === $args['status'] ) {
+ $status = 'all';
+ $count_status = 'total_comments';
+ } else {
+ $status = $count_status = $args['status'];
+ }
+ if ( $count ) {
+ $found = $count->$count_status;
+ }
+ }
+
+ $query = array(
+ 'order' => $args['order'],
+ 'type' => 'any' === $args['type'] ? false : $args['type'],
+ 'status' => $status,
+ );
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+ } else {
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+ }
+
+ if ( ! $args['hierarchical'] ) {
+ $query['number'] = $args['number'];
+
+ if ( isset( $args['page'] ) ) {
+ $query['offset'] = ( $args['page'] - 1 ) * $args['number'];
+ } else {
+ $query['offset'] = $args['offset'];
+ }
+
+ $is_before = isset( $args['before_gmt'] );
+ $is_after = isset( $args['after_gmt'] );
+
+ if ( $is_before || $is_after ) {
+ $query['date_query'] = array(
+ 'column' => 'comment_date_gmt',
+ 'inclusive' => true,
+ );
+
+ if ( $is_before ) {
+ $query['date_query']['before'] = $args['before_gmt'];
+ }
+
+ if ( $is_after ) {
+ $query['date_query']['after'] = $args['after_gmt'];
+ }
+ }
+ }
+
+ if ( $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+ $query['post_id'] = $post->ID;
+ if ( $this->api->ends_with( $this->path, '/replies' ) ) {
+ $query['parent'] = 0;
+ }
+ } elseif ( $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+ $query['parent'] = $comment_id;
+ }
+
+ $comments = get_comments( $query );
+
+ update_comment_cache( $comments );
+
+ if ( $args['hierarchical'] ) {
+ $walker = new WPCOM_JSON_API_List_Comments_Walker;
+ $comment_ids = $walker->paged_walk( $comments, get_option( 'thread_comments_depth', -1 ), isset( $args['page'] ) ? $args['page'] : 1 , $args['number'] );
+ $comments = array_map( 'get_comment', $comment_ids );
+ }
+
+ $return = array();
+
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'found' :
+ $return[$key] = (int) $found;
+ break;
+ case 'comments' :
+ $return_comments = array();
+ foreach ( $comments as $comment ) {
+ $the_comment = $this->get_comment( $comment->comment_ID, $args['context'] );
+ if ( $the_comment && !is_wp_error( $the_comment ) ) {
+ $return_comments[] = $the_comment;
+ }
+ }
+
+ if ( $return_comments ) {
+ do_action( 'wpcom_json_api_objects', 'comments', count( $return_comments ) );
+ }
+
+ $return[$key] = $return_comments;
+ break;
+ }
+ }
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-embeds-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-embeds-endpoint.php
new file mode 100644
index 00000000..761ca165
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-embeds-endpoint.php
@@ -0,0 +1,35 @@
+<?php
+class WPCOM_JSON_API_List_Embeds_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/embeds -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ // permissions check
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'Your token must have permission to post on this blog.', 403 );
+ }
+
+ // list em
+ $output = array( 'embeds' => array() );
+
+ global $wp_embed;
+ $oembed = _wp_oembed_get_object();
+
+ foreach( $wp_embed->handlers as $priority => $handlers ) {
+ foreach( $handlers as $handler ) {
+ if ( ! empty( $handler['regex'] ) )
+ $output['embeds'][] = $handler['regex'];
+ }
+ }
+
+ foreach ( $oembed->providers as $regex => $oembed_info ) {
+ if ( ! empty( $regex ) )
+ $output['embeds'][] = $regex;
+ }
+
+ return $output;
+ }
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-endpoint.php
new file mode 100644
index 00000000..74f8443f
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-endpoint.php
@@ -0,0 +1,48 @@
+<?php
+
+class WPCOM_JSON_API_List_Media_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ //upload_files can probably be used for other endpoints but we want contributors to be able to use media too
+ if ( !current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ $args = $this->query_args();
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ $media = get_posts( array(
+ 'post_type' => 'attachment',
+ 'post_parent' => $args['parent_id'],
+ 'offset' => $args['offset'],
+ 'numberposts' => $args['number'],
+ 'post_mime_type' => $args['mime_type']
+ ) );
+
+ $response = array();
+ foreach ( $media as $item ) {
+ $response[] = $this->get_media_item( $item->ID );
+ }
+
+ $_num = (array) wp_count_attachments();
+ $_total_media = array_sum( $_num ) - $_num['trash'];
+
+ $return = array(
+ 'found' => $_total_media,
+ 'media' => $response
+ );
+
+ return $return;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-v1-1-endpoint.php
new file mode 100644
index 00000000..e53f2c4b
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-media-v1-1-endpoint.php
@@ -0,0 +1,239 @@
+<?php
+
+class WPCOM_JSON_API_List_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ var $date_range = array();
+ var $page_handle = array();
+
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ //upload_files can probably be used for other endpoints but we want contributors to be able to use media too
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ $args = $this->query_args();
+ $is_eligible_for_page_handle = true;
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ if ( isset( $args['before'] ) ) {
+ $this->date_range['before'] = $args['before'];
+ }
+ if ( isset( $args['after'] ) ) {
+ $this->date_range['after'] = $args['after'];
+ }
+
+
+
+ $query = array(
+ 'post_type' => 'attachment',
+ 'post_status' => 'inherit',
+ 'post_parent' => isset( $args['parent_id'] ) ? $args['parent_id'] : null,
+ 'offset' => isset( $args['offset'] ) ? $args['offset'] : null,
+ 'posts_per_page' => $args['number'],
+ 'post_mime_type' => isset( $args['mime_type'] ) ? $args['mime_type'] : null,
+ 'order' => isset( $args['order'] ) ? $args['order'] : 'DESC',
+ 'orderby' => isset( $args['order_by'] ) ? $args['order_by'] : 'date',
+ 's' => isset( $args['search'] ) ? $args['search'] : null,
+ );
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+
+ $query['paged'] = $args['page'];
+ if ( $query['paged'] !== 1 ) {
+ $is_eligible_for_page_handle = false;
+ }
+ } else {
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+
+ $query['offset'] = $args['offset'];
+ if ( $query['offset'] !== 0 ) {
+ $is_eligible_for_page_handle = false;
+ }
+ }
+
+ if ( isset( $args['page_handle'] ) ) {
+ $page_handle = wp_parse_args( $args['page_handle'] );
+ if ( isset( $page_handle['value'] ) && isset( $page_handle['id'] ) ) {
+ // we have a valid looking page handle
+ $this->page_handle = $page_handle;
+ add_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) );
+ }
+ }
+
+ if ( $this->date_range ) {
+ add_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ }
+
+ $this->performed_query = $query;
+ add_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) );
+
+ $media = new WP_Query( $query );
+
+ remove_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) );
+
+ if ( $this->date_range ) {
+ remove_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ $this->date_range = array();
+ }
+
+ if ( $this->page_handle ) {
+ remove_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) );
+ }
+
+ $response = array();
+ foreach ( $media->posts as $item ) {
+ $response[] = $this->get_media_item_v1_1( $item->ID );
+ }
+
+ $return = array(
+ 'found' => (int) $media->found_posts,
+ 'media' => $response
+ );
+
+ if ( $is_eligible_for_page_handle && $return['media'] ) {
+ $last_post = end( $return['media'] );
+ reset( $return['media'] );
+
+ if ( ( $return['found'] > count( $return['media'] ) ) && $last_post ) {
+ $return['meta'] = array();
+ $return['meta']['next_page'] = $this->build_page_handle( $last_post, $query );
+ }
+ }
+
+ return $return;
+ }
+
+ function build_page_handle( $post, $query ) {
+ $column = $query['orderby'];
+ if ( ! $column ) {
+ $column = 'date';
+ }
+ return build_query( array( 'value' => urlencode( $post->$column ), 'id' => $post->ID ) );
+ }
+
+ function handle_where_for_page_handle( $where ) {
+ global $wpdb;
+
+ $column = $this->performed_query['orderby'];
+ if ( ! $column ) {
+ $column = 'date';
+ }
+ $order = $this->performed_query['order'];
+ if ( ! $order ) {
+ $order = 'DESC';
+ }
+
+ if ( ! in_array( $column, array( 'ID', 'title', 'date', 'modified', 'comment_count' ) ) ) {
+ return $where;
+ }
+
+ if ( ! in_array( $order, array( 'DESC', 'ASC' ) ) ) {
+ return $where;
+ }
+
+ $db_column = '';
+ $db_value = '';
+ switch( $column ) {
+ case 'ID':
+ $db_column = 'ID';
+ $db_value = '%d';
+ break;
+ case 'title':
+ $db_column = 'post_title';
+ $db_value = '%s';
+ break;
+ case 'date':
+ $db_column = 'post_date';
+ $db_value = 'CAST( %s as DATETIME )';
+ break;
+ case 'modified':
+ $db_column = 'post_modified';
+ $db_value = 'CAST( %s as DATETIME )';
+ break;
+ case 'comment_count':
+ $db_column = 'comment_count';
+ $db_value = '%d';
+ break;
+ }
+
+ if ( 'DESC'=== $order ) {
+ $db_order = '<';
+ } else {
+ $db_order = '>';
+ }
+
+ // Add a clause that limits the results to items beyond the passed item, or equivalent to the passed item
+ // but with an ID beyond the passed item. When we're ordering by the ID already, we only ask for items
+ // beyond the passed item.
+ $where .= $wpdb->prepare( " AND ( ( `$wpdb->posts`.`$db_column` $db_order $db_value ) ", $this->page_handle['value'] );
+ if ( $db_column !== 'ID' ) {
+ $where .= $wpdb->prepare( "OR ( `$wpdb->posts`.`$db_column` = $db_value AND `$wpdb->posts`.ID $db_order %d )", $this->page_handle['value'], $this->page_handle['id'] );
+ }
+ $where .= ' )';
+
+ return $where;
+ }
+
+ function handle_date_range( $where ) {
+ global $wpdb;
+
+ switch ( count( $this->date_range ) ) {
+ case 2 :
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date BETWEEN CAST( %s AS DATETIME ) AND CAST( %s AS DATETIME ) ",
+ $this->date_range['after'],
+ $this->date_range['before']
+ );
+ break;
+ case 1 :
+ if ( isset( $this->date_range['before'] ) ) {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date <= CAST( %s AS DATETIME ) ",
+ $this->date_range['before']
+ );
+ } else {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date >= CAST( %s AS DATETIME ) ",
+ $this->date_range['after']
+ );
+ }
+ break;
+ }
+
+ return $where;
+ }
+
+ function handle_orderby_for_page_handle( $orderby ) {
+ global $wpdb;
+ if ( $this->performed_query['orderby'] === 'ID' ) {
+ // bail if we're already ordering by ID
+ return $orderby;
+ }
+
+ if ( $orderby ) {
+ $orderby .= ' ,';
+ }
+ $order = $this->performed_query['order'];
+ if ( ! $order ) {
+ $order = 'DESC';
+ }
+ $orderby .= " `$wpdb->posts`.ID $order";
+ return $orderby;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-endpoint.php
new file mode 100644
index 00000000..d1400282
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-endpoint.php
@@ -0,0 +1,276 @@
+<?php
+
+class WPCOM_JSON_API_List_Posts_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
+ var $date_range = array();
+
+ var $response_format = array(
+ 'found' => '(int) The total number of posts found that match the request (ignoring limits, offsets, and pagination).',
+ 'posts' => '(array:post) An array of post objects.',
+ );
+
+ // /sites/%s/posts/ -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ if ( isset( $args['type'] ) && ! $this->is_post_type_allowed( $args['type'] ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ // Normalize post_type
+ if ( isset( $args['type'] ) && 'any' == $args['type'] ) {
+ if ( version_compare( $this->api->version, '1.1', '<' ) ) {
+ $args['type'] = array( 'post', 'page' );
+ } else { // 1.1+
+ $args['type'] = $this->_get_whitelisted_post_types();
+ }
+ }
+
+ // determine statuses
+ $status = $args['status'];
+ $status = ( $status ) ? explode( ',', $status ) : array( 'publish' );
+ if ( is_user_logged_in() ) {
+ $statuses_whitelist = array(
+ 'publish',
+ 'pending',
+ 'draft',
+ 'future',
+ 'private',
+ 'trash',
+ 'any',
+ );
+ $status = array_intersect( $status, $statuses_whitelist );
+ } else {
+ // logged-out users can see only published posts
+ $statuses_whitelist = array( 'publish', 'any' );
+ $status = array_intersect( $status, $statuses_whitelist );
+
+ if ( empty( $status ) ) {
+ // requested only protected statuses? nothing for you here
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ // clear it (AKA published only) because "any" includes protected
+ $status = array();
+ }
+
+ // let's be explicit about defaulting to 'post'
+ $args['type'] = isset( $args['type'] ) ? $args['type'] : 'post';
+
+ // make sure the user can read or edit the requested post type(s)
+ if ( is_array( $args['type'] ) ) {
+ $allowed_types = array();
+ foreach ( $args['type'] as $post_type ) {
+ if ( $this->current_user_can_access_post_type( $post_type, $args['context'] ) ) {
+ $allowed_types[] = $post_type;
+ }
+ }
+
+ if ( empty( $allowed_types ) ) {
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ $args['type'] = $allowed_types;
+ }
+ else {
+ if ( ! $this->current_user_can_access_post_type( $args['type'], $args['context'] ) ) {
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ }
+
+ $query = array(
+ 'posts_per_page' => $args['number'],
+ 'order' => $args['order'],
+ 'orderby' => $args['order_by'],
+ 'post_type' => $args['type'],
+ 'post_status' => $status,
+ 'post_parent' => isset( $args['parent_id'] ) ? $args['parent_id'] : null,
+ 'author' => isset( $args['author'] ) && 0 < $args['author'] ? $args['author'] : null,
+ 's' => isset( $args['search'] ) ? $args['search'] : null,
+ 'fields' => 'ids',
+ );
+
+ if ( ! is_user_logged_in () ) {
+ $query['has_password'] = false;
+ }
+
+ if ( isset( $args['meta_key'] ) ) {
+ $show = false;
+ if ( $this->is_metadata_public( $args['meta_key'] ) )
+ $show = true;
+ if ( current_user_can( 'edit_post_meta', $query['post_type'], $args['meta_key'] ) )
+ $show = true;
+
+ if ( is_protected_meta( $args['meta_key'], 'post' ) && ! $show )
+ return new WP_Error( 'invalid_meta_key', 'Invalid meta key', 404 );
+
+ $meta = array( 'key' => $args['meta_key'] );
+ if ( isset( $args['meta_value'] ) )
+ $meta['value'] = $args['meta_value'];
+
+ $query['meta_query'] = array( $meta );
+ }
+
+ if (
+ isset( $args['sticky'] )
+ &&
+ ( $sticky = get_option( 'sticky_posts' ) )
+ &&
+ is_array( $sticky )
+ ) {
+ if ( $args['sticky'] ) {
+ $query['post__in'] = $sticky;
+ } else {
+ $query['post__not_in'] = $sticky;
+ $query['ignore_sticky_posts'] = 1;
+ }
+ } else {
+ $query['post__not_in'] = $sticky;
+ $query['ignore_sticky_posts'] = 1;
+ }
+
+ if ( isset( $args['exclude'] ) ) {
+ $query['post__not_in'] = array_merge( $query['post__not_in'], (array) $args['exclude'] );
+ }
+
+ if ( isset( $args['exclude_tree'] ) && is_post_type_hierarchical( $args['type'] ) ) {
+ // get_page_children is a misnomer; it supports all hierarchical post types
+ $page_args = array(
+ 'child_of' => $args['exclude_tree'],
+ 'post_type' => $args['type'],
+ // since we're looking for things to exclude, be aggressive
+ 'post_status' => 'publish,draft,pending,private,future,trash',
+ );
+ $post_descendants = get_pages( $page_args );
+
+ $exclude_tree = array( $args['exclude_tree'] );
+ foreach ( $post_descendants as $child ) {
+ $exclude_tree[] = $child->ID;
+ }
+
+ $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $exclude_tree ) : $exclude_tree;
+ }
+
+ if ( isset( $args['category'] ) ) {
+ $category = get_term_by( 'slug', $args['category'], 'category' );
+ if ( $category === false) {
+ $query['category_name'] = $args['category'];
+ } else {
+ $query['cat'] = $category->term_id;
+ }
+ }
+
+ if ( isset( $args['tag'] ) ) {
+ $query['tag'] = $args['tag'];
+ }
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+
+ $query['paged'] = $args['page'];
+ } else {
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+
+ $query['offset'] = $args['offset'];
+ }
+
+ if ( isset( $args['before'] ) ) {
+ $this->date_range['before'] = $args['before'];
+ }
+ if ( isset( $args['after'] ) ) {
+ $this->date_range['after'] = $args['after'];
+ }
+
+ if ( $this->date_range ) {
+ add_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ }
+
+ /**
+ * 'column' necessary for the me/posts endpoint (which extends sites/$site/posts).
+ * Would need to be added to the sites/$site/posts definition if we ever want to
+ * use it there.
+ */
+ $column_whitelist = array( 'post_modified_gmt' );
+ if ( isset( $args['column'] ) && in_array( $args['column'], $column_whitelist ) ) {
+ $query['column'] = $args['column'];
+ }
+
+ $wp_query = new WP_Query( $query );
+ if ( $this->date_range ) {
+ remove_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ $this->date_range = array();
+ }
+
+ $return = array();
+ $excluded_count = 0;
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'found' :
+ $return[$key] = (int) $wp_query->found_posts;
+ break;
+ case 'posts' :
+ $posts = array();
+ foreach ( $wp_query->posts as $post_ID ) {
+ $the_post = $this->get_post_by( 'ID', $post_ID, $args['context'] );
+ if ( $the_post && ! is_wp_error( $the_post ) ) {
+ $posts[] = $the_post;
+ } else {
+ $excluded_count++;
+ }
+ }
+
+ if ( $posts ) {
+ do_action( 'wpcom_json_api_objects', 'posts', count( $posts ) );
+ }
+
+ $return[$key] = $posts;
+ break;
+ }
+ }
+
+ $return['found'] -= $excluded_count;
+
+ return $return;
+ }
+
+ function handle_date_range( $where ) {
+ global $wpdb;
+
+ switch ( count( $this->date_range ) ) {
+ case 2 :
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date BETWEEN CAST( %s AS DATETIME ) AND CAST( %s AS DATETIME ) ",
+ $this->date_range['after'],
+ $this->date_range['before']
+ );
+ break;
+ case 1 :
+ if ( isset( $this->date_range['before'] ) ) {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date <= CAST( %s AS DATETIME ) ",
+ $this->date_range['before']
+ );
+ } else {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.post_date >= CAST( %s AS DATETIME ) ",
+ $this->date_range['after']
+ );
+ }
+ break;
+ }
+
+ return $where;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-v1-1-endpoint.php
new file mode 100644
index 00000000..257e6dfa
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-posts-v1-1-endpoint.php
@@ -0,0 +1,436 @@
+<?php
+
+class WPCOM_JSON_API_List_Posts_v1_1_Endpoint extends WPCOM_JSON_API_Post_v1_1_Endpoint {
+ var $date_range = array();
+ var $modified_range = array();
+ var $page_handle = array();
+ var $performed_query = null;
+
+ var $response_format = array(
+ 'found' => '(int) The total number of posts found that match the request (ignoring limits, offsets, and pagination).',
+ 'posts' => '(array:post) An array of post objects.',
+ );
+
+ // /sites/%s/posts/ -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+ $is_eligible_for_page_handle = true;
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ if ( isset( $args['type'] ) && ! $this->is_post_type_allowed( $args['type'] ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ // Normalize post_type
+ if ( isset( $args['type'] ) && 'any' == $args['type'] ) {
+ if ( version_compare( $this->api->version, '1.1', '<' ) ) {
+ $args['type'] = array( 'post', 'page' );
+ } else { // 1.1+
+ $args['type'] = $this->_get_whitelisted_post_types();
+ }
+ }
+
+ // determine statuses
+ $status = $args['status'];
+ $status = ( $status ) ? explode( ',', $status ) : array( 'publish' );
+ if ( is_user_logged_in() ) {
+ $statuses_whitelist = array(
+ 'publish',
+ 'pending',
+ 'draft',
+ 'future',
+ 'private',
+ 'trash',
+ 'any',
+ );
+ $status = array_intersect( $status, $statuses_whitelist );
+ } else {
+ // logged-out users can see only published posts
+ $statuses_whitelist = array( 'publish', 'any' );
+ $status = array_intersect( $status, $statuses_whitelist );
+
+ if ( empty( $status ) ) {
+ // requested only protected statuses? nothing for you here
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ // clear it (AKA published only) because "any" includes protected
+ $status = array();
+ }
+
+ if ( isset( $args['type'] ) &&
+ ! in_array( $args['type'], array( 'post', 'page', 'revision', 'any' ) ) &&
+ defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $this->load_theme_functions();
+ }
+
+ // let's be explicit about defaulting to 'post'
+ $args['type'] = isset( $args['type'] ) ? $args['type'] : 'post';
+
+ // make sure the user can read or edit the requested post type(s)
+ if ( is_array( $args['type'] ) ) {
+ $allowed_types = array();
+ foreach ( $args['type'] as $post_type ) {
+ if ( $this->current_user_can_access_post_type( $post_type, $args['context'] ) ) {
+ $allowed_types[] = $post_type;
+ }
+ }
+
+ if ( empty( $allowed_types ) ) {
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ $args['type'] = $allowed_types;
+ }
+ else {
+ if ( ! $this->current_user_can_access_post_type( $args['type'], $args['context'] ) ) {
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ }
+
+
+ $query = array(
+ 'posts_per_page' => $args['number'],
+ 'order' => $args['order'],
+ 'orderby' => $args['order_by'],
+ 'post_type' => $args['type'],
+ 'post_status' => $status,
+ 'post_parent' => isset( $args['parent_id'] ) ? $args['parent_id'] : null,
+ 'author' => isset( $args['author'] ) && 0 < $args['author'] ? $args['author'] : null,
+ 's' => isset( $args['search'] ) ? $args['search'] : null,
+ 'fields' => 'ids',
+ );
+
+ if ( ! is_user_logged_in () ) {
+ $query['has_password'] = false;
+ }
+
+ if ( isset( $args['meta_key'] ) ) {
+ $show = false;
+ if ( $this->is_metadata_public( $args['meta_key'] ) )
+ $show = true;
+ if ( current_user_can( 'edit_post_meta', $query['post_type'], $args['meta_key'] ) )
+ $show = true;
+
+ if ( is_protected_meta( $args['meta_key'], 'post' ) && ! $show )
+ return new WP_Error( 'invalid_meta_key', 'Invalid meta key', 404 );
+
+ $meta = array( 'key' => $args['meta_key'] );
+ if ( isset( $args['meta_value'] ) )
+ $meta['value'] = $args['meta_value'];
+
+ $query['meta_query'] = array( $meta );
+ }
+
+ if ( $args['sticky'] === 'include' ) {
+ $query['ignore_sticky_posts'] = 1;
+ } else if ( $args['sticky'] === 'exclude' ) {
+ $sticky = get_option( 'sticky_posts' );
+ if ( is_array( $sticky ) ) {
+ $query['post__not_in'] = $sticky;
+ }
+ } else if ( $args['sticky'] === 'require' ) {
+ $sticky = get_option( 'sticky_posts' );
+ if ( is_array( $sticky ) && ! empty( $sticky ) ) {
+ $query['post__in'] = $sticky;
+ } else {
+ // no sticky posts exist
+ return array( 'found' => 0, 'posts' => array() );
+ }
+ }
+
+ if ( isset( $args['exclude'] ) ) {
+ $excluded_ids = (array) $args['exclude'];
+ $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $excluded_ids ) : $excluded_ids;
+ }
+
+ if ( isset( $args['exclude_tree'] ) && is_post_type_hierarchical( $args['type'] ) ) {
+ // get_page_children is a misnomer; it supports all hierarchical post types
+ $page_args = array(
+ 'child_of' => $args['exclude_tree'],
+ 'post_type' => $args['type'],
+ // since we're looking for things to exclude, be aggressive
+ 'post_status' => 'publish,draft,pending,private,future,trash',
+ );
+ $post_descendants = get_pages( $page_args );
+
+ $exclude_tree = array( $args['exclude_tree'] );
+ foreach ( $post_descendants as $child ) {
+ $exclude_tree[] = $child->ID;
+ }
+
+ $query['post__not_in'] = isset( $query['post__not_in'] ) ? array_merge( $query['post__not_in'], $exclude_tree ) : $exclude_tree;
+ }
+
+ if ( isset( $args['category'] ) ) {
+ $category = get_term_by( 'slug', $args['category'], 'category' );
+ if ( $category === false) {
+ $query['category_name'] = $args['category'];
+ } else {
+ $query['cat'] = $category->term_id;
+ }
+ }
+
+ if ( isset( $args['tag'] ) ) {
+ $query['tag'] = $args['tag'];
+ }
+
+ if ( isset( $args['page'] ) ) {
+ if ( $args['page'] < 1 ) {
+ $args['page'] = 1;
+ }
+
+ $query['paged'] = $args['page'];
+ if ( $query['paged'] !== 1 ) {
+ $is_eligible_for_page_handle = false;
+ }
+ } else {
+ if ( $args['offset'] < 0 ) {
+ $args['offset'] = 0;
+ }
+
+ $query['offset'] = $args['offset'];
+ if ( $query['offset'] !== 0 ) {
+ $is_eligible_for_page_handle = false;
+ }
+ }
+
+ if ( isset( $args['before'] ) ) {
+ $this->date_range['before'] = $args['before'];
+ }
+ if ( isset( $args['after'] ) ) {
+ $this->date_range['after'] = $args['after'];
+ }
+
+ if ( isset( $args['modified_before_gmt'] ) ) {
+ $this->modified_range['before'] = $args['modified_before_gmt'];
+ }
+ if ( isset( $args['modified_after_gmt'] ) ) {
+ $this->modified_range['after'] = $args['modified_after_gmt'];
+ }
+
+ if ( $this->date_range ) {
+ add_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ }
+
+ if ( $this->modified_range ) {
+ add_filter( 'posts_where', array( $this, 'handle_modified_range' ) );
+ }
+
+ if ( isset( $args['page_handle'] ) ) {
+ $page_handle = wp_parse_args( $args['page_handle'] );
+ if ( isset( $page_handle['value'] ) && isset( $page_handle['id'] ) ) {
+ // we have a valid looking page handle
+ $this->page_handle = $page_handle;
+ add_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) );
+ }
+ }
+
+ /**
+ * 'column' necessary for the me/posts endpoint (which extends sites/$site/posts).
+ * Would need to be added to the sites/$site/posts definition if we ever want to
+ * use it there.
+ */
+ $column_whitelist = array( 'post_modified_gmt' );
+ if ( isset( $args['column'] ) && in_array( $args['column'], $column_whitelist ) ) {
+ $query['column'] = $args['column'];
+ }
+
+ $this->performed_query = $query;
+ add_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) );
+
+ $wp_query = new WP_Query( $query );
+
+ remove_filter( 'posts_orderby', array( $this, 'handle_orderby_for_page_handle' ) );
+
+ if ( $this->date_range ) {
+ remove_filter( 'posts_where', array( $this, 'handle_date_range' ) );
+ $this->date_range = array();
+ }
+
+ if ( $this->modified_range ) {
+ remove_filter( 'posts_where', array( $this, 'handle_modified_range' ) );
+ $this->modified_range = array();
+ }
+
+ if ( $this->page_handle ) {
+ remove_filter( 'posts_where', array( $this, 'handle_where_for_page_handle' ) );
+
+ }
+
+ $return = array();
+ $excluded_count = 0;
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'found' :
+ $return[$key] = (int) $wp_query->found_posts;
+ break;
+ case 'posts' :
+ $posts = array();
+ foreach ( $wp_query->posts as $post_ID ) {
+ $the_post = $this->get_post_by( 'ID', $post_ID, $args['context'] );
+ if ( $the_post && ! is_wp_error( $the_post ) ) {
+ $posts[] = $the_post;
+ } else {
+ $excluded_count++;
+ }
+ }
+
+ if ( $posts ) {
+ do_action( 'wpcom_json_api_objects', 'posts', count( $posts ) );
+ }
+
+ $return[$key] = $posts;
+ break;
+ }
+ }
+
+ if ( $is_eligible_for_page_handle && $return['posts'] ) {
+ $last_post = end( $return['posts'] );
+ reset( $return['posts'] );
+
+ if ( ( $return['found'] > count( $return['posts'] ) ) && $last_post ) {
+ $return['meta'] = array();
+ $return['meta']['next_page'] = $this->build_page_handle( $last_post, $query );
+ }
+ }
+
+ $return['found'] -= $excluded_count;
+
+ return $return;
+ }
+
+ function build_page_handle( $post, $query ) {
+ $column = $query['orderby'];
+ if ( ! $column ) {
+ $column = 'date';
+ }
+ return build_query( array( 'value' => urlencode($post[$column]), 'id' => $post['ID'] ) );
+ }
+
+ function _build_date_range_query( $column, $range, $where ) {
+ global $wpdb;
+
+ switch ( count( $range ) ) {
+ case 2 :
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.$column >= CAST( %s AS DATETIME ) AND `$wpdb->posts`.$column < CAST( %s AS DATETIME ) ",
+ $range['after'],
+ $range['before']
+ );
+ break;
+ case 1 :
+ if ( isset( $range['before'] ) ) {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.$column < CAST( %s AS DATETIME ) ",
+ $range['before']
+ );
+ } else {
+ $where .= $wpdb->prepare(
+ " AND `$wpdb->posts`.$column > CAST( %s AS DATETIME ) ",
+ $range['after']
+ );
+ }
+ break;
+ }
+
+ return $where;
+ }
+
+ function handle_date_range( $where ) {
+ return $this->_build_date_range_query( 'post_date', $this->date_range, $where );
+ }
+
+ function handle_modified_range( $where ) {
+ return $this->_build_date_range_query( 'post_modified_gmt', $this->modified_range, $where );
+ }
+
+ function handle_where_for_page_handle( $where ) {
+ global $wpdb;
+
+ $column = $this->performed_query['orderby'];
+ if ( ! $column ) {
+ $column = 'date';
+ }
+ $order = $this->performed_query['order'];
+ if ( ! $order ) {
+ $order = 'DESC';
+ }
+
+ if ( ! in_array( $column, array( 'ID', 'title', 'date', 'modified', 'comment_count' ) ) ) {
+ return $where;
+ }
+
+ if ( ! in_array( $order, array( 'DESC', 'ASC' ) ) ) {
+ return $where;
+ }
+
+ $db_column = '';
+ $db_value = '';
+ switch( $column ) {
+ case 'ID':
+ $db_column = 'ID';
+ $db_value = '%d';
+ break;
+ case 'title':
+ $db_column = 'post_title';
+ $db_value = '%s';
+ break;
+ case 'date':
+ $db_column = 'post_date';
+ $db_value = 'CAST( %s as DATETIME )';
+ break;
+ case 'modified':
+ $db_column = 'post_modified';
+ $db_value = 'CAST( %s as DATETIME )';
+ break;
+ case 'comment_count':
+ $db_column = 'comment_count';
+ $db_value = '%d';
+ break;
+ }
+
+ if ( 'DESC'=== $order ) {
+ $db_order = '<';
+ } else {
+ $db_order = '>';
+ }
+
+ // Add a clause that limits the results to items beyond the passed item, or equivalent to the passed item
+ // but with an ID beyond the passed item. When we're ordering by the ID already, we only ask for items
+ // beyond the passed item.
+ $where .= $wpdb->prepare( " AND ( ( `$wpdb->posts`.`$db_column` $db_order $db_value ) ", $this->page_handle['value'] );
+ if ( $db_column !== 'ID' ) {
+ $where .= $wpdb->prepare( "OR ( `$wpdb->posts`.`$db_column` = $db_value AND `$wpdb->posts`.ID $db_order %d )", $this->page_handle['value'], $this->page_handle['id'] );
+ }
+ $where .= ' )';
+
+ return $where;
+ }
+
+ function handle_orderby_for_page_handle( $orderby ) {
+ global $wpdb;
+ if ( $this->performed_query['orderby'] === 'ID' ) {
+ // bail if we're already ordering by ID
+ return $orderby;
+ }
+
+ if ( $orderby ) {
+ $orderby .= ' ,';
+ }
+ $order = $this->performed_query['order'];
+ if ( ! $order ) {
+ $order = 'DESC';
+ }
+ $orderby .= " `$wpdb->posts`.ID $order";
+ return $orderby;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-shortcodes-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-shortcodes-endpoint.php
new file mode 100644
index 00000000..9b2fc1aa
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-shortcodes-endpoint.php
@@ -0,0 +1,27 @@
+<?php
+class WPCOM_JSON_API_List_Shortcodes_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/shortcodes -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ // permissions check
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'Your token must have permission to post on this blog.', 403 );
+ }
+
+ // list em
+ global $shortcode_tags;
+ $output = array( 'shortcodes' => array() );
+
+ foreach ( $shortcode_tags as $tag => $class ) {
+ if ( '__return_false' == $class )
+ continue;
+ $output['shortcodes'][] = $tag;
+ }
+
+ return $output;
+ }
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-users-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-users-endpoint.php
new file mode 100644
index 00000000..21d2c777
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-list-users-endpoint.php
@@ -0,0 +1,78 @@
+<?php
+class WPCOM_JSON_API_List_Users_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ var $response_format = array(
+ 'found' => '(int) The total number of authors found that match the request (i
+gnoring limits and offsets).',
+ 'users' => '(array:author) Array of user objects',
+ );
+
+ // /sites/%s/users/ -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $args = $this->query_args();
+
+ $authors_only = ( ! empty( $args['authors_only'] ) );
+
+ if ( $args['number'] < 1 ) {
+ $args['number'] = 20;
+ } elseif ( 100 < $args['number'] ) {
+ return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
+ }
+
+ if ( $authors_only ) {
+ if ( empty( $args['type'] ) )
+ $args['type'] = 'post';
+
+ if ( ! $this->is_post_type_allowed( $args['type'] ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ $post_type_object = get_post_type_object( $args['type'] );
+ if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->edit_others_posts ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view authors for specified post type', 403 );
+ }
+ } elseif ( ! current_user_can( 'list_users' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view users for specified site', 403 );
+ }
+
+ $query = array(
+ 'number' => $args['number'],
+ 'offset' => $args['offset'],
+ 'order' => $args['order'],
+ 'orderby' => $args['order_by'],
+ 'fields' => 'ID',
+ );
+
+ if ( $authors_only )
+ $query['who'] = 'authors';
+
+ $user_query = new WP_User_Query( $query );
+
+ $return = array();
+ foreach ( array_keys( $this->response_format ) as $key ) {
+ switch ( $key ) {
+ case 'found' :
+ $return[$key] = (int) $user_query->get_total();
+ break;
+ case 'users' :
+ $users = array();
+ foreach ( $user_query->get_results() as $u ) {
+ $the_user = $this->get_author( $u, true );
+ if ( $the_user && ! is_wp_error( $the_user ) ) {
+ $users[] = $the_user;
+ }
+ }
+
+ $return[$key] = $users;
+ break;
+ }
+ }
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-menus-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-menus-v1-1-endpoint.php
new file mode 100644
index 00000000..9d08935a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-menus-v1-1-endpoint.php
@@ -0,0 +1,697 @@
+<?php
+abstract class WPCOM_JSON_API_Menus_Abstract_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ protected function switch_to_blog_and_validate_user( $site ) {
+ $site_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $site ) );
+ if ( is_wp_error( $site_id ) ) {
+ return $site_id;
+ }
+
+ if ( ! current_user_can( 'edit_theme_options' ) ) {
+ return new WP_Error( 'unauthorised', 'User cannot edit theme options on this site.', 403 );
+ }
+
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $this->load_theme_functions();
+ }
+
+ return $site_id;
+ }
+
+
+ protected function get_locations() {
+ $locations = array();
+ $menus = get_registered_nav_menus();
+ if ( !empty( $menus ) ) {
+ foreach( $menus as $name => $description ) {
+ $locations[] = array( 'name' => $name, 'description' => $description );
+ }
+ }
+
+ $locations = array_merge( $locations, WPCOM_JSON_API_Menus_Widgets::get() );
+
+ // Primary (first) location should have defaultState -> default,
+ // all other locations (including widgets) should have defaultState -> empty.
+ for ( $i = 0; $i < count( $locations ); $i++ ) {
+ $locations[ $i ]['defaultState'] = $i ? 'empty' : 'default';
+ }
+ return $locations;
+ }
+
+ protected function simplify( $data ) {
+ $simplifier = new WPCOM_JSON_API_Menus_Simplifier( $data );
+ return $simplifier->translate();
+ }
+
+ protected function complexify( $data ) {
+ $complexifier = new WPCOM_JSON_API_Menus_Complexify( $data );
+ return $complexifier->translate();
+ }
+}
+
+abstract class WPCOM_JSON_API_Menus_Translator {
+ protected $filter = '';
+
+ protected $filters = array();
+
+ public function __construct( $menus ) {
+ $this->is_single_menu = ! is_array( $menus );
+ $this->menus = is_array( $menus ) ? $menus : array( $menus );
+ }
+
+ public function translate() {
+ $result = $this->menus;
+ foreach ( $this->filters as $f ) {
+ $result = call_user_func( array( $this, $f ), $result );
+ if ( is_wp_error($result ) ) {
+ return $result;
+ }
+ }
+ return $this->maybe_extract( $result );
+ }
+
+ protected function maybe_extract( $menus ) {
+ return $this->is_single_menu ? $menus[0] : $menus;
+ }
+
+ public function whitelist_and_rename_with( $object, $dict ) {
+ $keys = array_keys( $dict );
+ $return = array();
+ foreach ( (array) $object as $k => $v ) {
+ if ( in_array( $k, $keys ) ) {
+ if ( is_array( $dict[ $k ] ) ) {
+ settype( $v, $dict[ $k ]['type'] );
+ $return[ $dict[ $k ]['name'] ] = $v;
+ } else {
+ $new_k = $dict[ $k ];
+ $return[ $new_k ] = $v;
+ }
+ }
+ }
+ return $return;
+ }
+}
+
+class WPCOM_JSON_API_Menus_Simplifier extends WPCOM_JSON_API_Menus_Translator {
+ protected $filter = 'wpcom_menu_api_translator_simplify';
+
+ protected $filters = array(
+ 'whitelist_and_rename_keys',
+ 'add_locations',
+ 'treeify',
+ 'add_widget_locations',
+ );
+
+ protected $menu_whitelist = array(
+ 'term_id' => array( 'name' => 'id', 'type' => 'int' ),
+ 'name' => array( 'name' => 'name', 'type' => 'string' ),
+ 'description' => array( 'name' => 'description', 'type' => 'string' ),
+ 'items' => array( 'name' => 'items', 'type' => 'array' ),
+ );
+
+ protected $menu_item_whitelist = array(
+ 'db_id' => array( 'name' => 'id', 'type' => 'int' ),
+ 'object_id' => array( 'name' => 'content_id', 'type' => 'int' ),
+ 'object' => array( 'name' => 'type', 'type' => 'string' ),
+ 'type' => array( 'name' => 'type_family', 'type' => 'string' ),
+ 'type_label' => array( 'name' => 'type_label', 'type' => 'string' ),
+ 'title' => array( 'name' => 'name', 'type' => 'string' ),
+ 'menu_order' => array( 'name' => 'order', 'type' => 'int' ),
+ 'menu_item_parent' => array( 'name' => 'parent', 'type' => 'int' ),
+ 'url' => array( 'name' => 'url', 'type' => 'string' ),
+ 'target' => array( 'name' => 'link_target', 'type' => 'string' ),
+ 'attr_title' => array( 'name' => 'link_title', 'type' => 'string' ),
+ 'description' => array( 'name' => 'description', 'type' => 'string' ),
+ 'classes' => array( 'name' => 'classes', 'type' => 'array' ),
+ 'xfn' => array( 'name' => 'xfn', 'type' => 'string' ),
+ );
+
+ /**************************
+ * Filters methods
+ **************************/
+
+ public function treeify( $menus ) {
+ return array_map( array( $this, 'treeify_menu' ), $menus );
+ }
+
+ // turn the flat item list into a tree of items
+ protected function treeify_menu( $menu ) {
+ $indexed_nodes = array();
+ $tree = array();
+
+ foreach( $menu['items'] as &$item ) {
+ $indexed_nodes[ $item['id'] ] = &$item;
+ }
+
+ foreach( $menu['items'] as &$item ) {
+ if ( $item['parent'] && isset( $indexed_nodes[ $item['parent'] ] ) ) {
+ $parent_node = &$indexed_nodes[ $item['parent'] ];
+ if ( !isset( $parent_node['items'] ) ) {
+ $parent_node['items'] = array();
+ }
+ $parent_node['items'][ $item['order'] ] = &$item;
+ } else {
+ $tree[ $item['order'] ] = &$item;
+ }
+ unset( $item['order'] );
+ unset( $item['parent'] );
+ }
+
+ $menu['items'] = $tree;
+ $this->remove_item_keys( $menu );
+ return $menu;
+ }
+
+ // recursively ensure item lists are contiguous
+ protected function remove_item_keys( &$item ) {
+ if ( ! isset( $item['items'] ) || ! is_array( $item['items'] ) ) {
+ return;
+ }
+
+
+ foreach( $item['items'] as &$it ) {
+ $this->remove_item_keys( $it );
+ }
+
+ $item['items'] = array_values( $item['items'] );
+ }
+
+ protected function whitelist_and_rename_keys( $menus ) {
+ $transformed_menus = array();
+
+ foreach ( $menus as $menu ) {
+ $menu = $this->whitelist_and_rename_with( $menu, $this->menu_whitelist );
+
+ if ( isset( $menu['items'] ) ) {
+ foreach ( $menu['items'] as &$item ) {
+ $item = $this->whitelist_and_rename_with( $item, $this->menu_item_whitelist );
+ }
+ }
+
+ $transformed_menus[] = $menu;
+ }
+
+ return $transformed_menus;
+ }
+
+ protected function add_locations( $menus ) {
+ $menus_with_locations = array();
+
+ foreach( $menus as $menu ) {
+ $menu['locations'] = array_keys( get_nav_menu_locations(), $menu['id'] );
+ $menus_with_locations[] = $menu;
+ }
+
+ return $menus_with_locations;
+ }
+
+ protected function add_widget_locations( $menus ) {
+ $nav_menu_widgets = WPCOM_JSON_API_Menus_Widgets::get();
+
+ if ( ! is_array( $nav_menu_widgets ) ) {
+ return $menus;
+ }
+
+ foreach ( $menus as &$menu ) {
+ $widget_locations = array();
+
+ foreach ( $nav_menu_widgets as $key => $widget ) {
+ if ( is_array( $widget ) && isset( $widget['nav_menu'] ) &&
+ $widget['nav_menu'] === $menu['id'] ) {
+ $widget_locations[] = 'nav_menu_widget-' . $key;
+ }
+ }
+ $menu['locations'] = array_merge( $menu['locations'], $widget_locations );
+ }
+
+ return $menus;
+ }
+}
+
+class WPCOM_JSON_API_Menus_Complexify extends WPCOM_JSON_API_Menus_Translator {
+ protected $filter = 'wpcom_menu_api_translator_complexify';
+
+ protected $filters = array(
+ 'untreeify',
+ 'set_locations',
+ 'whitelist_and_rename_keys',
+ );
+
+ protected $menu_whitelist = array(
+ 'id' => 'term_id',
+ 'name' => 'menu-name',
+ 'description' => 'description',
+ 'items' => 'items',
+ );
+
+ protected $menu_item_whitelist = array(
+ 'id' => 'menu-item-db-id',
+ 'content_id' => 'menu-item-object-id',
+ 'type' => 'menu-item-object',
+ 'type_family' => 'menu-item-type',
+ 'type_label' => 'menu-item-type-label',
+ 'name' => 'menu-item-title',
+ 'order' => 'menu-item-position',
+ 'parent' => 'menu-item-parent-id',
+ 'url' => 'menu-item-url',
+ 'link_target' => 'menu-item-target',
+ 'link_title' => 'menu-item-attr-title',
+ 'status' => 'menu-item-status',
+ 'tmp_id' => 'tmp_id',
+ 'tmp_parent' => 'tmp_parent',
+ 'description' => 'menu-item-description',
+ 'classes' => 'menu-item-classes',
+ 'xfn' => 'menu-item-xfn',
+ );
+
+ /**************************
+ * Filters methods
+ **************************/
+
+ public function untreeify( $menus ) {
+ return array_map( array( $this, 'untreeify_menu' ), $menus );
+ }
+
+ // convert the tree of menu items to a flat list suitable for
+ // the nav_menu APIs
+ protected function untreeify_menu( $menu ) {
+ if ( empty( $menu['items'] ) ) {
+ return $menu;
+ }
+
+ $items_list = array();
+ $counter = 1;
+ foreach ( $menu['items'] as &$item ) {
+ $item[ 'parent' ] = 0;
+ }
+ $this->untreeify_items( $menu['items'], $items_list, $counter );
+ $menu['items'] = $items_list;
+
+ return $menu;
+ }
+
+ /**
+ * Recurse the items tree adding each item to a flat list and restoring
+ * `order` and `parent` fields.
+ *
+ * @param array $items item tree
+ * @param array &$items_list output flat list of items
+ * @param int &$counter for creating temporary IDs
+ */
+ protected function untreeify_items( $items, &$items_list, &$counter ) {
+ foreach( $items as $index => $item ) {
+ $item['order'] = $index + 1;
+
+ if( ! isset( $item['id'] ) ) {
+ $this->set_tmp_id( $item, $counter++ );
+ }
+
+ if ( isset( $item['items'] ) && is_array( $item['items'] ) ) {
+ foreach ( $item['items'] as &$i ) {
+ $i['parent'] = $item['id'];
+ }
+ $this->untreeify_items( $item[ 'items' ], $items_list, $counter );
+ unset( $item['items'] );
+ }
+
+ $items_list[] = $item;
+ }
+ }
+
+ /**
+ * Populate `tmp_id` field for a new item, and `tmp_parent` field
+ * for all its children, to maintain the hierarchy.
+ * These fields will be used when creating
+ * new items with wp_update_nav_menu_item().
+ */
+ private function set_tmp_id( &$item, $tmp_id ) {
+ $item['tmp_id'] = $tmp_id;
+ if ( ! isset( $item['items'] ) || ! is_array( $item['items'] ) ) {
+ return;
+ }
+ foreach ( $item['items'] as &$child ) {
+ $child['tmp_parent'] = $tmp_id;
+ }
+ }
+
+ protected function whitelist_and_rename_keys( $menus ) {
+ $transformed_menus = array();
+ foreach ( $menus as $menu ) {
+ $menu = $this->whitelist_and_rename_with( $menu, $this->menu_whitelist );
+ if ( isset( $menu['items'] ) ) {
+ $menu['items'] = array_map( array( $this, 'whitelist_and_rename_item_keys' ), $menu['items'] );
+ }
+ $transformed_menus[] = $menu;
+ }
+
+ return $transformed_menus;
+ }
+
+ protected function whitelist_and_rename_item_keys( $item ) {
+ $item = $this->implode_array_fields( $item );
+ $item = $this->whitelist_and_rename_with( $item, $this->menu_item_whitelist );
+ return $item;
+ }
+
+ // all item fields are set as strings
+ protected function implode_array_fields( $menu_item ) {
+ return array_map( array( $this, 'implode_array_field' ), $menu_item );
+ }
+
+ protected function implode_array_field( $field ) {
+ if ( is_array( $field ) ) {
+ return implode( ' ', $field );
+ }
+ return $field;
+ }
+
+ protected function set_locations( $menus ) {
+ foreach ( $menus as $menu ) {
+ if ( isset( $menu['locations'] ) ) {
+ if ( true !== $this->locations_are_valid( $menu['locations'] ) ) {
+ return $this->locations_are_valid( $menu['locations'] );
+ }
+ }
+ }
+
+ return array_map( array( $this, 'set_location' ), $menus );
+ }
+
+ protected function set_location( $menu ) {
+ $this->set_menu_at_locations( $menu['locations'], $menu['id'] );
+ return $menu;
+ }
+
+ protected function set_menu_at_locations( $locations, $menu_id ) {
+ $location_map = get_nav_menu_locations();
+ $this->remove_menu_from_all_locations( $menu_id, $location_map );
+
+ if ( is_array( $locations ) ) {
+ foreach ( $locations as $location ) {
+ $location_map[ $location ] = $menu_id;
+ }
+ }
+
+ set_theme_mod( 'nav_menu_locations', $location_map );
+
+ $this->set_widget_menu_at_locations( $locations, $menu_id );
+ }
+
+ protected function remove_menu_from_all_locations( $menu_id, &$location_map ) {
+ foreach ( get_nav_menu_locations() as $existing_location => $existing_menu_id) {
+ if ( $existing_menu_id == $menu_id ) {
+ unset( $location_map[$existing_location] );
+ }
+ }
+ }
+
+ protected function set_widget_menu_at_locations( $locations, $menu_id ) {
+ $nav_menu_widgets = get_option( 'widget_nav_menu' );
+
+ if ( ! is_array( $nav_menu_widgets ) ) {
+ return;
+ }
+
+ // Remove menus from all custom menu widget locations
+ foreach ( $nav_menu_widgets as &$widget ) {
+ if ( is_array( $widget ) && $widget['nav_menu'] == $menu_id ) {
+ $widget['nav_menu'] = 0;
+ }
+ }
+
+ if ( is_array( $locations ) ) {
+ foreach ( $locations as $location ) {
+ if ( preg_match( '/^nav_menu_widget-(\d+)/', $location, $matches ) ) {
+ if ( isset( $matches[1] ) ) {
+ $nav_menu_widgets[$matches[1]]['nav_menu'] = $menu_id;
+ }
+ }
+ }
+ }
+
+ update_option( 'widget_nav_menu', $nav_menu_widgets );
+ }
+
+ protected function locations_are_valid( $locations ) {
+ if ( is_int( $locations ) ) {
+ if ( $locations != 0) {
+ return new WP_Error( 'locations-int', 'Locations int must be 0.', 400 );
+ } else {
+ return true;
+ }
+ } elseif ( is_array( $locations ) ) {
+ foreach ( $locations as $location_name ) {
+ if ( ! $this->location_name_exists( $location_name ) ) {
+ return new WP_Error( 'locations-array',
+ sprintf( "Location '%s' does not exist.", $location_name ), 404 );
+ }
+ }
+ return true;
+ }
+ return new WP_Error( 'locations', 'Locations must be array or integer.', 400 );
+ }
+
+ protected function location_name_exists( $location_name ) {
+ $widget_location_names = wp_list_pluck( WPCOM_JSON_API_Menus_Widgets::get(), 'name' );
+
+ if ( ! is_array( get_registered_nav_menus() ) ) {
+ return false;
+ }
+
+ return array_key_exists( $location_name, get_registered_nav_menus() ) ||
+ in_array( $location_name, $widget_location_names );
+ }
+
+}
+
+class WPCOM_JSON_API_Menus_New_Menu_Endpoint extends WPCOM_JSON_API_Menus_Abstract_Endpoint {
+ function callback( $path = '', $site = 0 ) {
+ $site_id = $this->switch_to_blog_and_validate_user( $this->api->get_blog_id( $site ) );
+
+ if ( is_wp_error( $site_id ) ) {
+ return $site_id;
+ }
+
+ $data = $this->input();
+
+ $id = wp_create_nav_menu( $data['name'] );
+
+ if ( is_wp_error( $id ) ) {
+ return $id;
+ }
+
+ return array( 'id' => $id );
+ }
+}
+
+class WPCOM_JSON_API_Menus_Update_Menu_Endpoint extends WPCOM_JSON_API_Menus_Abstract_Endpoint {
+ function callback( $path = '', $site = 0, $menu_id = 0 ) {
+ $site_id = $this->switch_to_blog_and_validate_user( $this->api->get_blog_id( $site ) );
+
+ if ( is_wp_error( $site_id ) ) {
+ return $site_id;
+ }
+
+ if ( $menu_id <= 0 ) {
+ return new WP_Error( 'menu-id', 'Menu ID must be greater than 0.', 400 );
+ }
+
+ $data = $this->input( true, false );
+ $data = $this->complexify( array( $data ) );
+ if ( is_wp_error( $data ) ) {
+ return $data;
+ }
+ $data = $data[0];
+
+ // Avoid special-case handling of an unset 'items' field in empty menus
+ $data['items'] = isset( $data['items'] ) ? $data['items'] : array();
+
+ $data = $this->create_new_items( $data, $menu_id );
+
+ $result = wp_update_nav_menu_object( $menu_id, array( 'menu-name' => $data['menu-name'] ) );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ $delete_status = $this->delete_items_not_present( $menu_id, $data['items'] );
+ if( is_wp_error( $delete_status ) ) {
+ return $delete_status;
+ }
+
+ foreach ( $data['items'] as $item ) {
+ $item_id = isset( $item['menu-item-db-id'] ) ? $item['menu-item-db-id'] : 0;
+ $result = wp_update_nav_menu_item( $menu_id, $item_id, $item );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ }
+
+ $items = wp_get_nav_menu_items( $menu_id, array( 'update_post_term_cache' => false ) );
+
+ if ( is_wp_error( $items ) ) {
+ return $items;
+ }
+
+ $menu = wp_get_nav_menu_object( $menu_id );
+ $menu->items = $items;
+
+ return array( 'menu' => $this->simplify( $menu ) );
+ }
+
+ /**
+ * New items can have a 'tmp_id', allowing them to
+ * be used as parent items before they have been created.
+ *
+ * This function will create items that have a 'tmp_id' set, and
+ * update any items with a 'tmp_parent' to use the
+ * newly created item as a parent.
+ */
+ function create_new_items( $data, $menu_id ) {
+ $tmp_to_actual_ids = array();
+ foreach ( $data['items'] as &$item ) {
+ if ( isset( $item['tmp_id'] ) ) {
+ $actual_id = wp_update_nav_menu_item( $menu_id, 0, $item );
+ $tmp_to_actual_ids[ $item['tmp_id'] ] = $actual_id;
+ unset( $item['tmp_id'] );
+ $item['menu-item-db-id'] = $actual_id;
+ }
+ }
+
+ foreach ( $data['items'] as &$item ) {
+ if ( isset( $item['tmp_parent'] ) ) {
+ $item['menu-item-parent-id'] = $tmp_to_actual_ids[ $item['tmp_parent'] ];
+ unset( $item['tmp_parent'] );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * remove any existing menu items not present in the supplied array.
+ * returns wp_error if an item cannot be deleted.
+ */
+ function delete_items_not_present( $menu_id, $menu_items ) {
+
+ $existing_items = wp_get_nav_menu_items( $menu_id, array( 'update_post_term_cache' => false ) );
+ if ( ! is_array( $existing_items ) ) {
+ return true;
+ }
+
+ $existing_ids = wp_list_pluck( $existing_items, 'db_id' );
+ $ids_to_keep = wp_list_pluck( $menu_items, 'menu-item-db-id' );
+ $ids_to_remove = array_diff( $existing_ids, $ids_to_keep );
+
+ foreach ( $ids_to_remove as $id ) {
+ if ( false === wp_delete_post( $id, true ) ) {
+ return new WP_Error( 'menu-item',
+ sprintf( 'Failed to delete menu item with id: %d.', $id ), 400 );
+ }
+ }
+
+ return true;
+ }
+}
+
+class WPCOM_JSON_API_Menus_List_Menus_Endpoint extends WPCOM_JSON_API_Menus_Abstract_Endpoint {
+ function callback( $path = '', $site = 0 ) {
+ $site_id = $this->switch_to_blog_and_validate_user( $this->api->get_blog_id( $site ) );
+
+ if ( is_wp_error( $site_id ) ) {
+ return $site_id;
+ }
+
+ $menus = wp_get_nav_menus( array( 'orderby' => 'term_id' ) );
+
+ if ( is_wp_error( $menus ) ) {
+ return $menus;
+ }
+
+ foreach ( $menus as $m ) {
+ $items = wp_get_nav_menu_items( $m->term_id, array( 'update_post_term_cache' => false ) );
+ if ( is_wp_error( $items ) ) {
+ return $items;
+ }
+ $m->items = $items;
+ }
+
+ $menus = $this->simplify( $menus );
+
+ if ( is_wp_error( $this->get_locations() ) ) {
+ return $this->get_locations();
+ }
+
+ return array( 'menus' => $menus, 'locations' => $this->get_locations() );
+ }
+}
+
+class WPCOM_JSON_API_Menus_Get_Menu_Endpoint extends WPCOM_JSON_API_Menus_Abstract_Endpoint {
+ function callback( $path = '', $site = 0, $menu_id = 0 ) {
+ $site_id = $this->switch_to_blog_and_validate_user( $this->api->get_blog_id( $site ) );
+
+ if ( is_wp_error( $site_id ) ) {
+ return $site_id;
+ }
+
+ if ( $menu_id <= 0 ) {
+ return new WP_Error( 'menu-id', 'Menu ID must be greater than 0.', 400 );
+ }
+
+ $menu = get_term( $menu_id, 'nav_menu' );
+
+ if ( is_wp_error( $menu ) ) {
+ return $menu;
+ }
+
+ $items = wp_get_nav_menu_items( $menu_id, array( 'update_post_term_cache' => false ) );
+
+ if ( is_wp_error( $items ) ) {
+ return $items;
+ }
+
+ $menu->items = $items;
+
+ return $this->simplify( $menu );
+ }
+}
+
+class WPCOM_JSON_API_Menus_Delete_Menu_Endpoint extends WPCOM_JSON_API_Menus_Abstract_Endpoint {
+ function callback( $path = '', $site = 0, $menu_id = 0 ) {
+ $site_id = $this->switch_to_blog_and_validate_user( $this->api->get_blog_id( $site ) );
+
+ if ( is_wp_error( $site_id ) ) {
+ return $site_id;
+ }
+
+ if ( $menu_id <= 0 ) {
+ return new WP_Error( 'menu-id', 'Menu ID must be greater than 0.', 400 );
+ }
+
+ $result = wp_delete_nav_menu( $menu_id );
+ if ( ! is_wp_error( $result ) ) {
+ $result = array( 'deleted' => $result );
+ }
+
+ return $result;
+ }
+}
+
+class WPCOM_JSON_API_Menus_Widgets {
+ static function get() {
+ $locations = array();
+ $nav_menu_widgets = get_option( 'widget_nav_menu' );
+
+ if ( ! is_array( $nav_menu_widgets ) ) {
+ return $locations;
+ }
+
+ foreach ( $nav_menu_widgets as $k => $v ) {
+ if ( is_array( $v ) && isset( $v['title'] ) ) {
+ $locations[$k] = array( 'name' => 'nav_menu_widget-' . $k, 'description' => $v['title'] );
+ }
+ }
+
+ return $locations;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-post-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-post-endpoint.php
new file mode 100644
index 00000000..6366354a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-post-endpoint.php
@@ -0,0 +1,664 @@
+<?php
+
+abstract class WPCOM_JSON_API_Post_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $post_object_format = array(
+ // explicitly document and cast all output
+ 'ID' => '(int) The post ID.',
+ 'site_ID' => '(int) The site ID.',
+ 'author' => '(object>author) The author of the post.',
+ 'date' => "(ISO 8601 datetime) The post's creation time.",
+ 'modified' => "(ISO 8601 datetime) The post's most recent update time.",
+ 'title' => '(HTML) <code>context</code> dependent.',
+ 'URL' => '(URL) The full permalink URL to the post.',
+ 'short_URL' => '(URL) The wp.me short URL.',
+ 'content' => '(HTML) <code>context</code> dependent.',
+ 'excerpt' => '(HTML) <code>context</code> dependent.',
+ 'slug' => '(string) The name (slug) for the post, used in URLs.',
+ 'guid' => '(string) The GUID for the post.',
+ 'status' => array(
+ 'publish' => 'The post is published.',
+ 'draft' => 'The post is saved as a draft.',
+ 'pending' => 'The post is pending editorial approval.',
+ 'private' => 'The post is published privately',
+ 'future' => 'The post is scheduled for future publishing.',
+ 'trash' => 'The post is in the trash.',
+ 'auto-draft' => 'The post is a placeholder for a new post.',
+ ),
+ 'sticky' => '(bool) Is the post sticky?',
+ 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
+ 'parent' => "(object>post_reference|false) A reference to the post's parent, if it has one.",
+ 'type' => "(string) The post's post_type. Post types besides post, page and revision need to be whitelisted using the <code>rest_api_allowed_post_types</code> filter.",
+ 'comments_open' => '(bool) Is the post open for comments?',
+ 'pings_open' => '(bool) Is the post open for pingbacks, trackbacks?',
+ 'likes_enabled' => "(bool) Is the post open to likes?",
+ 'sharing_enabled' => "(bool) Should sharing buttons show on this post?",
+ 'comment_count' => '(int) The number of comments for this post.',
+ 'like_count' => '(int) The number of likes for this post.',
+ 'i_like' => '(bool) Does the current user like this post?',
+ 'is_reblogged' => '(bool) Did the current user reblog this post?',
+ 'is_following' => '(bool) Is the current user following this blog?',
+ 'global_ID' => '(string) A unique WordPress.com-wide representation of a post.',
+ 'featured_image' => '(URL) The URL to the featured image for this post if it has one.',
+ 'post_thumbnail' => '(object>attachment) The attachment object for the featured image if it has one.',
+ 'format' => array(), // see constructor
+ 'geo' => '(object>geo|false)',
+ 'menu_order' => '(int) (Pages Only) The order pages should appear in.',
+ 'publicize_URLs' => '(array:URL) Array of Twitter and Facebook URLs published by this post.',
+ 'tags' => '(object:tag) Hash of tags (keyed by tag name) applied to the post.',
+ 'categories' => '(object:category) Hash of categories (keyed by category name) applied to the post.',
+ 'attachments' => '(object:attachment) Hash of post attachments (keyed by attachment ID).',
+ 'metadata' => '(array) Array of post metadata keys and values. All unprotected meta keys are available by default for read requests. Both unprotected and protected meta keys are available for authenticated requests with access. Protected meta keys can be made available with the <code>rest_api_allowed_public_metadata</code> filter.',
+ 'meta' => '(object) API result meta data',
+ 'current_user_can' => '(object) List of permissions. Note, deprecated in favor of `capabilities`',
+ 'capabilities' => '(object) List of post-specific permissions for the user; publish_post, edit_post, delete_post',
+ );
+
+ // var $response_format =& $this->post_object_format;
+
+ function __construct( $args ) {
+ if ( is_array( $this->post_object_format ) && isset( $this->post_object_format['format'] ) ) {
+ $this->post_object_format['format'] = get_post_format_strings();
+ }
+ if ( !$this->response_format ) {
+ $this->response_format =& $this->post_object_format;
+ }
+ parent::__construct( $args );
+ }
+
+ function is_metadata_public( $key ) {
+ if ( empty( $key ) )
+ return false;
+
+ // Default whitelisted meta keys.
+ $whitelisted_meta = array( '_thumbnail_id' );
+
+ // whitelist of metadata that can be accessed
+ if ( in_array( $key, apply_filters( 'rest_api_allowed_public_metadata', $whitelisted_meta ) ) )
+ return true;
+
+ if ( 0 === strpos( $key, 'geo_' ) )
+ return true;
+
+ if ( 0 === strpos( $key, '_wpas_' ) )
+ return true;
+
+ return false;
+ }
+
+ function the_password_form() {
+ return __( 'This post is password protected.', 'jetpack' );
+ }
+
+ /**
+ * Get a post by a specified field and value
+ *
+ * @param string $field
+ * @param string $field_value
+ * @param string $context Post use context (e.g. 'display')
+ * @return array Post
+ **/
+ function get_post_by( $field, $field_value, $context = 'display' ) {
+ global $blog_id;
+
+ $is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
+
+ if ( defined( 'GEO_LOCATION__CLASS' ) && class_exists( GEO_LOCATION__CLASS ) ) {
+ $geo = call_user_func( array( GEO_LOCATION__CLASS, 'init' ) );
+ } else {
+ $geo = false;
+ }
+
+ if ( 'display' === $context ) {
+ $args = $this->query_args();
+ if ( isset( $args['content_width'] ) && $args['content_width'] ) {
+ $GLOBALS['content_width'] = (int) $args['content_width'];
+ }
+ }
+
+ if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'wp-windows8' ) ) {
+ remove_shortcode( 'gallery', 'gallery_shortcode' );
+ add_shortcode( 'gallery', array( &$this, 'win8_gallery_shortcode' ) );
+ }
+
+ switch ( $field ) {
+ case 'name' :
+ $post_id = $this->get_post_id_by_name( $field_value );
+ if ( is_wp_error( $post_id ) ) {
+ return $post_id;
+ }
+ break;
+ default :
+ $post_id = (int) $field_value;
+ break;
+ }
+
+ $post = get_post( $post_id, OBJECT, $context );
+
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( ! $this->is_post_type_allowed( $post->post_type ) && ( ! function_exists( 'is_post_freshly_pressed' ) || ! is_post_freshly_pressed( $post->ID ) ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ // Permissions
+ $capabilities = $this->get_current_user_capabilities( $post );
+
+ switch ( $context ) {
+ case 'edit' :
+ if ( ! $capabilities['edit_post'] ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+ break;
+ case 'display' :
+ break;
+ default :
+ return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ $GLOBALS['post'] = $post;
+
+ if ( 'display' === $context ) {
+ setup_postdata( $post );
+ }
+
+ $response = array();
+
+ foreach ( array_keys( $this->post_object_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ // explicitly cast all output
+ $response[$key] = (int) $post->ID;
+ break;
+ case 'site_ID' :
+ $response[$key] = (int) $this->api->get_blog_id_for_output();
+ break;
+ case 'author' :
+ $response[$key] = (object) $this->get_author( $post, 'edit' === $context && $capabilities['edit_post'] );
+ break;
+ case 'date' :
+ $response[$key] = (string) $this->format_date( $post->post_date_gmt, $post->post_date );
+ break;
+ case 'modified' :
+ $response[$key] = (string) $this->format_date( $post->post_modified_gmt, $post->post_modified );
+ break;
+ case 'title' :
+ if ( 'display' === $context ) {
+ $response[$key] = (string) get_the_title( $post->ID );
+ } else {
+ $response[$key] = (string) htmlspecialchars_decode( $post->post_title, ENT_QUOTES );
+ }
+ break;
+ case 'URL' :
+ if ( 'revision' === $post->post_type ) {
+ $response[$key] = (string) esc_url_raw( get_permalink( $post->post_parent ) );
+ } else {
+ $response[$key] = (string) esc_url_raw( get_permalink( $post->ID ) );
+ }
+ break;
+ case 'short_URL' :
+ $response[$key] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) );
+ break;
+ case 'content' :
+ if ( 'display' === $context ) {
+ add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ $response[$key] = (string) $this->get_the_post_content_for_display();
+ remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ } else {
+ $response[$key] = (string) $post->post_content;
+ }
+ break;
+ case 'excerpt' :
+ if ( 'display' === $context ) {
+ add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ ob_start();
+ the_excerpt();
+ $response[$key] = (string) ob_get_clean();
+ remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ } else {
+ $response[$key] = htmlspecialchars_decode( (string) $post->post_excerpt, ENT_QUOTES );
+ }
+ break;
+ case 'status' :
+ $response[$key] = (string) get_post_status( $post->ID );
+ break;
+ case 'sticky' :
+ $response[$key] = (bool) is_sticky( $post->ID );
+ break;
+ case 'slug' :
+ $response[$key] = (string) $post->post_name;
+ break;
+ case 'guid' :
+ $response[$key] = (string) $post->guid;
+ break;
+ case 'password' :
+ $response[$key] = (string) $post->post_password;
+ if ( 'edit' === $context ) {
+ $response[$key] = htmlspecialchars_decode( (string) $response[$key], ENT_QUOTES );
+ }
+ break;
+ case 'parent' : // (object|false)
+ if ( $post->post_parent ) {
+ $parent = get_post( $post->post_parent );
+ if ( 'display' === $context ) {
+ $parent_title = (string) get_the_title( $parent->ID );
+ } else {
+ $parent_title = (string) htmlspecialchars_decode( $post->post_title, ENT_QUOTES );
+ }
+ $response[$key] = (object) array(
+ 'ID' => (int) $parent->ID,
+ 'type' => (string) $parent->post_type,
+ 'link' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $parent->ID ),
+ 'title' => $parent_title,
+ );
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'type' :
+ $response[$key] = (string) $post->post_type;
+ break;
+ case 'comments_open' :
+ $response[$key] = (bool) comments_open( $post->ID );
+ break;
+ case 'pings_open' :
+ $response[$key] = (bool) pings_open( $post->ID );
+ break;
+ case 'likes_enabled' :
+ $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
+ $post_likes_switched = (bool) get_post_meta( $post->ID, 'switch_like_status', true );
+ $post_likes_enabled = $sitewide_likes_enabled;
+ if ( $post_likes_switched ) {
+ $post_likes_enabled = ! $post_likes_enabled;
+ }
+ $response[$key] = (bool) $post_likes_enabled;
+ break;
+ case 'sharing_enabled' :
+ $show = true;
+ /** This filter is documented in modules/sharedaddy/sharing-service.php */
+ $show = apply_filters( 'sharing_show', $show, $post );
+
+ $switched_status = get_post_meta( $post->ID, 'sharing_disabled', false );
+
+ if ( !empty( $switched_status ) )
+ $show = false;
+ $response[$key] = (bool) $show;
+ break;
+ case 'comment_count' :
+ $response[$key] = (int) $post->comment_count;
+ break;
+ case 'like_count' :
+ $response[$key] = (int) $this->api->post_like_count( $blog_id, $post->ID );
+ break;
+ case 'i_like' :
+ $response[$key] = (int) $this->api->is_liked( $blog_id, $post->ID );
+ break;
+ case 'is_reblogged':
+ $response[$key] = (int) $this->api->is_reblogged( $blog_id, $post->ID );
+ break;
+ case 'is_following':
+ $response[$key] = (int) $this->api->is_following( $blog_id );
+ break;
+ case 'global_ID':
+ $response[$key] = (string) $this->api->add_global_ID( $blog_id, $post->ID );
+ break;
+ case 'featured_image' :
+ if ( $is_jetpack && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
+ $response[ $key ] = get_post_meta( $post->ID, '_jetpack_featured_image', true );
+ } else {
+ $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
+ if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
+ $response[ $key ] = (string) $image_attributes[0];
+ } else {
+ $response[ $key ] = '';
+ }
+ }
+ break;
+ case 'post_thumbnail' :
+ $response[$key] = null;
+
+ $thumb_id = get_post_thumbnail_id( $post->ID );
+ if ( ! empty( $thumb_id ) ) {
+ $attachment = get_post( $thumb_id );
+ if ( ! empty( $attachment ) )
+ $featured_image_object = $this->get_attachment( $attachment );
+
+ if ( ! empty( $featured_image_object ) ) {
+ $response[$key] = (object) $featured_image_object;
+ }
+ }
+ break;
+ case 'format' :
+ $response[$key] = (string) get_post_format( $post->ID );
+ if ( !$response[$key] ) {
+ $response[$key] = 'standard';
+ }
+ break;
+ case 'geo' : // (object|false)
+ if ( !$geo ) {
+ $response[$key] = false;
+ } else {
+ $geo_data = $geo->get_geo( 'post', $post->ID );
+ $response[$key] = false;
+ if ( $geo_data ) {
+ $geo_data = array_intersect_key( $geo_data, array( 'latitude' => true, 'longitude' => true, 'address' => true, 'public' => true ) );
+ if ( $geo_data ) {
+ $response[$key] = (object) array(
+ 'latitude' => isset( $geo_data['latitude'] ) ? (float) $geo_data['latitude'] : 0,
+ 'longitude' => isset( $geo_data['longitude'] ) ? (float) $geo_data['longitude'] : 0,
+ 'address' => isset( $geo_data['address'] ) ? (string) $geo_data['address'] : '',
+ );
+ } else {
+ $response[$key] = false;
+ }
+ // Private
+ if ( !isset( $geo_data['public'] ) || !$geo_data['public'] ) {
+ if ( 'edit' !== $context || ! $capabilities['edit_post'] ) {
+ // user can't access
+ $response[$key] = false;
+ }
+ }
+ }
+ }
+ break;
+ case 'menu_order':
+ $response[$key] = (int) $post->menu_order;
+ break;
+ case 'publicize_URLs' :
+ $publicize_URLs = array();
+ $publicize = get_post_meta( $post->ID, 'publicize_results', true );
+ if ( $publicize ) {
+ foreach ( $publicize as $service => $data ) {
+ switch ( $service ) {
+ case 'twitter' :
+ foreach ( $data as $datum ) {
+ $publicize_URLs[] = esc_url_raw( "https://twitter.com/{$datum['user_id']}/status/{$datum['post_id']}" );
+ }
+ break;
+ case 'fb' :
+ foreach ( $data as $datum ) {
+ $publicize_URLs[] = esc_url_raw( "https://www.facebook.com/permalink.php?story_fbid={$datum['post_id']}&id={$datum['user_id']}" );
+ }
+ break;
+ }
+ }
+ }
+ $response[$key] = (array) $publicize_URLs;
+ break;
+ case 'tags' :
+ $response[$key] = array();
+ $terms = wp_get_post_tags( $post->ID );
+ foreach ( $terms as $term ) {
+ if ( !empty( $term->name ) ) {
+ $response[$key][$term->name] = $this->format_taxonomy( $term, 'post_tag', 'display' );
+ }
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'categories':
+ $response[$key] = array();
+ $terms = wp_get_object_terms( $post->ID, 'category', array( 'fields' => 'all' ) );
+ foreach ( $terms as $term ) {
+ if ( !empty( $term->name ) ) {
+ $response[$key][$term->name] = $this->format_taxonomy( $term, 'category', 'display' );
+ }
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'attachments':
+ $response[$key] = array();
+ $_attachments = get_posts( array( 'post_parent' => $post->ID, 'post_status' => 'inherit', 'post_type' => 'attachment', 'posts_per_page' => 100 ) );
+ foreach ( $_attachments as $attachment ) {
+ $response[$key][$attachment->ID] = $this->get_attachment( $attachment );
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'metadata' : // (array|false)
+ $metadata = array();
+ foreach ( (array) has_meta( $post_id ) as $meta ) {
+ // Don't expose protected fields.
+ $show = false;
+ if ( $this->is_metadata_public( $meta['meta_key'] ) )
+ $show = true;
+ if ( current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
+ $show = true;
+
+ if ( !$show )
+ continue;
+
+ $metadata[] = array(
+ 'id' => $meta['meta_id'],
+ 'key' => $meta['meta_key'],
+ 'value' => maybe_unserialize( $meta['meta_value'] ),
+ );
+ }
+
+ if ( ! empty( $metadata ) ) {
+ $response[$key] = $metadata;
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'meta' :
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
+ 'help' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'help' ),
+ 'site' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+// 'author' => (string) $this->get_user_link( $post->post_author ),
+// 'via' => (string) $this->get_post_link( $reblog_origin_blog_id, $reblog_origin_post_id ),
+ 'replies' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'replies/' ),
+ 'likes' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'likes/' ),
+ ),
+ );
+ break;
+ case 'current_user_can' :
+ $response[$key] = $capabilities;
+ break;
+ case 'capabilities' :
+ $response[$key] = $capabilities;
+ break;
+
+ }
+ }
+
+ // WPCOM_JSON_API_Post_Endpoint::find_featured_worthy_media( $post );
+ // $response['featured_media'] = self::find_featured_media( $response );
+
+ unset( $GLOBALS['post'] );
+ return $response;
+ }
+
+ // No Blog ID parameter. No Post ID parameter. Depends on globals.
+ // Expects setup_postdata() to already have been run
+ function get_the_post_content_for_display() {
+ global $pages, $page;
+
+ $old_pages = $pages;
+ $old_page = $page;
+
+ $content = join( "\n\n", $pages );
+ $content = preg_replace( '/<!--more(.*?)?-->/', '', $content );
+ $pages = array( $content );
+ $page = 1;
+
+ ob_start();
+ the_content();
+ $return = ob_get_clean();
+
+ $pages = $old_pages;
+ $page = $old_page;
+
+ return $return;
+ }
+
+ function get_blog_post( $blog_id, $post_id, $context = 'display' ) {
+ $blog_id = $this->api->get_blog_id( $blog_id );
+ if ( !$blog_id || is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+ switch_to_blog( $blog_id );
+ $post = $this->get_post_by( 'ID', $post_id, $context );
+ restore_current_blog();
+ return $post;
+ }
+
+ /**
+ * Supporting featured media in post endpoints. Currently on for wpcom blogs
+ * since it's calling WPCOM_JSON_API_Read_Endpoint methods which presently
+ * rely on wpcom specific functionality.
+ *
+ * @param WP_Post $post
+ * @return object list of featured media
+ */
+ public static function find_featured_media( &$post ) {
+
+ if ( class_exists( 'WPCOM_JSON_API_Read_Endpoint' ) ) {
+ return WPCOM_JSON_API_Read_Endpoint::find_featured_worthy_media( (array) $post );
+ } else {
+ return (object) array();
+ }
+
+ }
+
+
+
+ function win8_gallery_shortcode( $attr ) {
+ global $post;
+
+ static $instance = 0;
+ $instance++;
+
+ $output = '';
+
+ // We're trusting author input, so let's at least make sure it looks like a valid orderby statement
+ if ( isset( $attr['orderby'] ) ) {
+ $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] );
+ if ( !$attr['orderby'] )
+ unset( $attr['orderby'] );
+ }
+
+ extract( shortcode_atts( array(
+ 'order' => 'ASC',
+ 'orderby' => 'menu_order ID',
+ 'id' => $post->ID,
+ 'include' => '',
+ 'exclude' => '',
+ 'slideshow' => false
+ ), $attr, 'gallery' ) );
+
+ // Custom image size and always use it
+ add_image_size( 'win8app-column', 480 );
+ $size = 'win8app-column';
+
+ $id = intval( $id );
+ if ( 'RAND' === $order )
+ $orderby = 'none';
+
+ if ( !empty( $include ) ) {
+ $include = preg_replace( '/[^0-9,]+/', '', $include );
+ $_attachments = get_posts( array( 'include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ $attachments = array();
+ foreach ( $_attachments as $key => $val ) {
+ $attachments[$val->ID] = $_attachments[$key];
+ }
+ } elseif ( !empty( $exclude ) ) {
+ $exclude = preg_replace( '/[^0-9,]+/', '', $exclude );
+ $attachments = get_children( array( 'post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ } else {
+ $attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ }
+
+ if ( ! empty( $attachments ) ) {
+ foreach ( $attachments as $id => $attachment ) {
+ $link = isset( $attr['link'] ) && 'file' === $attr['link'] ? wp_get_attachment_link( $id, $size, false, false ) : wp_get_attachment_link( $id, $size, true, false );
+
+ if ( $captiontag && trim($attachment->post_excerpt) ) {
+ $output .= "<div class='wp-caption aligncenter'>$link
+ <p class='wp-caption-text'>" . wptexturize($attachment->post_excerpt) . "</p>
+ </div>";
+ } else {
+ $output .= $link . ' ';
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns attachment object.
+ *
+ * @param $attachment attachment row
+ *
+ * @return (object)
+ */
+ function get_attachment( $attachment ) {
+ $metadata = wp_get_attachment_metadata( $attachment->ID );
+
+ $result = array(
+ 'ID' => (int) $attachment->ID,
+ 'URL' => (string) wp_get_attachment_url( $attachment->ID ),
+ 'guid' => (string) $attachment->guid,
+ 'mime_type' => (string) $attachment->post_mime_type,
+ 'width' => (int) isset( $metadata['width'] ) ? $metadata['width'] : 0,
+ 'height' => (int) isset( $metadata['height'] ) ? $metadata['height'] : 0,
+ );
+
+ if ( isset( $metadata['duration'] ) ) {
+ $result['duration'] = (int) $metadata['duration'];
+ }
+
+ return (object) apply_filters( 'get_attachment', $result );
+ }
+
+ /**
+ * Get post-specific user capabilities
+ * @param WP_Post $post post object
+ * @return array array of post-level permissions; 'publish_post', 'delete_post', 'edit_post'
+ */
+ function get_current_user_capabilities( $post ) {
+ return array(
+ 'publish_post' => current_user_can( 'publish_post', $post ),
+ 'delete_post' => current_user_can( 'delete_post', $post ),
+ 'edit_post' => current_user_can( 'edit_post', $post )
+ );
+ }
+
+ /**
+ * Get post ID by name
+ *
+ * Attempts to match name on post title and page path
+ *
+ * @param string $name
+ *
+ * @return int|object Post ID on success, WP_Error object on failure
+ **/
+ protected function get_post_id_by_name( $name ) {
+ $name = sanitize_title( $name );
+
+ if ( ! $name ) {
+ return new WP_Error( 'invalid_post', 'Invalid post', 400 );
+ }
+
+ $posts = get_posts( array( 'name' => $name ) );
+
+ if ( ! $posts || ! isset( $posts[0]->ID ) || ! $posts[0]->ID ) {
+ $page = get_page_by_path( $name );
+
+ if ( ! $page ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ $post_id = $page->ID;
+ } else {
+ $post_id = (int) $posts[0]->ID;
+ }
+
+ return $post_id;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-post-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-post-v1-1-endpoint.php
new file mode 100644
index 00000000..94574f92
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-post-v1-1-endpoint.php
@@ -0,0 +1,664 @@
+<?php
+
+abstract class WPCOM_JSON_API_Post_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $post_object_format = array(
+ // explicitly document and cast all output
+ 'ID' => '(int) The post ID.',
+ 'site_ID' => '(int) The site ID.',
+ 'author' => '(object>author) The author of the post.',
+ 'date' => "(ISO 8601 datetime) The post's creation time.",
+ 'modified' => "(ISO 8601 datetime) The post's most recent update time.",
+ 'title' => '(HTML) <code>context</code> dependent.',
+ 'URL' => '(URL) The full permalink URL to the post.',
+ 'short_URL' => '(URL) The wp.me short URL.',
+ 'content' => '(HTML) <code>context</code> dependent.',
+ 'excerpt' => '(HTML) <code>context</code> dependent.',
+ 'slug' => '(string) The name (slug) for the post, used in URLs.',
+ 'guid' => '(string) The GUID for the post.',
+ 'status' => array(
+ 'publish' => 'The post is published.',
+ 'draft' => 'The post is saved as a draft.',
+ 'pending' => 'The post is pending editorial approval.',
+ 'private' => 'The post is published privately',
+ 'future' => 'The post is scheduled for future publishing.',
+ 'trash' => 'The post is in the trash.',
+ 'auto-draft' => 'The post is a placeholder for a new post.',
+ ),
+ 'sticky' => '(bool) Is the post sticky?',
+ 'password' => '(string) The plaintext password protecting the post, or, more likely, the empty string if the post is not password protected.',
+ 'parent' => "(object>post_reference|false) A reference to the post's parent, if it has one.",
+ 'type' => "(string) The post's post_type. Post types besides post, page and revision need to be whitelisted using the <code>rest_api_allowed_post_types</code> filter.",
+ 'discussion' => '(object) Hash of discussion options for the post',
+ 'likes_enabled' => "(bool) Is the post open to likes?",
+ 'sharing_enabled' => "(bool) Should sharing buttons show on this post?",
+ 'like_count' => '(int) The number of likes for this post.',
+ 'i_like' => '(bool) Does the current user like this post?',
+ 'is_reblogged' => '(bool) Did the current user reblog this post?',
+ 'is_following' => '(bool) Is the current user following this blog?',
+ 'global_ID' => '(string) A unique WordPress.com-wide representation of a post.',
+ 'featured_image' => '(URL) The URL to the featured image for this post if it has one.',
+ 'post_thumbnail' => '(object>attachment) The attachment object for the featured image if it has one.',
+ 'format' => array(), // see constructor
+ 'geo' => '(object>geo|false)',
+ 'menu_order' => '(int) (Pages Only) The order pages should appear in.',
+ 'page_template' => '(string) (Pages Only) The page template this page is using.',
+ 'publicize_URLs' => '(array:URL) Array of Twitter and Facebook URLs published by this post.',
+ 'tags' => '(object:tag) Hash of tags (keyed by tag name) applied to the post.',
+ 'categories' => '(object:category) Hash of categories (keyed by category name) applied to the post.',
+ 'attachments' => '(object:attachment) Hash of post attachments (keyed by attachment ID). Returns the most recent 20 attachments. Use the `/sites/$site/media` endpoint to query the attachments beyond the default of 20 that are returned here.',
+ 'attachment_count' => '(int) The total number of attachments for this post. Use the `/sites/$site/media` endpoint to query the attachments beyond the default of 20 that are returned here.',
+ 'metadata' => '(array) Array of post metadata keys and values. All unprotected meta keys are available by default for read requests. Both unprotected and protected meta keys are available for authenticated requests with access. Protected meta keys can be made available with the <code>rest_api_allowed_public_metadata</code> filter.',
+ 'meta' => '(object) API result meta data',
+ 'capabilities' => '(object) List of post-specific permissions for the user; publish_post, edit_post, delete_post',
+ );
+
+ // var $response_format =& $this->post_object_format;
+
+ function __construct( $args ) {
+ if ( is_array( $this->post_object_format ) && isset( $this->post_object_format['format'] ) ) {
+ $this->post_object_format['format'] = get_post_format_strings();
+ }
+ if ( !$this->response_format ) {
+ $this->response_format =& $this->post_object_format;
+ }
+ parent::__construct( $args );
+ }
+
+ function is_metadata_public( $key ) {
+ if ( empty( $key ) )
+ return false;
+
+ // Default whitelisted meta keys.
+ $whitelisted_meta = array( '_thumbnail_id' );
+
+ // whitelist of metadata that can be accessed
+ if ( in_array( $key, apply_filters( 'rest_api_allowed_public_metadata', $whitelisted_meta ) ) )
+ return true;
+
+ if ( 0 === strpos( $key, 'geo_' ) )
+ return true;
+
+ if ( 0 === strpos( $key, '_wpas_' ) )
+ return true;
+
+ return false;
+ }
+
+ function the_password_form() {
+ return __( 'This post is password protected.', 'jetpack' );
+ }
+
+ /**
+ * Get a post by a specified field and value
+ *
+ * @param string $field
+ * @param string $field_value
+ * @param string $context Post use context (e.g. 'display')
+ * @return array Post
+ **/
+ function get_post_by( $field, $field_value, $context = 'display' ) {
+ global $blog_id;
+
+ $is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
+
+ if ( defined( 'GEO_LOCATION__CLASS' ) && class_exists( GEO_LOCATION__CLASS ) ) {
+ $geo = call_user_func( array( GEO_LOCATION__CLASS, 'init' ) );
+ } else {
+ $geo = false;
+ }
+
+ if ( 'display' === $context ) {
+ $args = $this->query_args();
+ if ( isset( $args['content_width'] ) && $args['content_width'] ) {
+ $GLOBALS['content_width'] = (int) $args['content_width'];
+ }
+ }
+
+ if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'wp-windows8' ) ) {
+ remove_shortcode( 'gallery', 'gallery_shortcode' );
+ add_shortcode( 'gallery', array( &$this, 'win8_gallery_shortcode' ) );
+ }
+
+ switch ( $field ) {
+ case 'name' :
+ $post_id = $this->get_post_id_by_name( $field_value );
+ if ( is_wp_error( $post_id ) ) {
+ return $post_id;
+ }
+ break;
+ default :
+ $post_id = (int) $field_value;
+ break;
+ }
+
+ $post = get_post( $post_id, OBJECT, $context );
+
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( ! $this->is_post_type_allowed( $post->post_type ) && ( ! function_exists( 'is_post_freshly_pressed' ) || ! is_post_freshly_pressed( $post->ID ) ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ // Permissions
+ $capabilities = $this->get_current_user_capabilities( $post );
+
+ switch ( $context ) {
+ case 'edit' :
+ if ( ! $capabilities['edit_post'] ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+ break;
+ case 'display' :
+ break;
+ default :
+ return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ $GLOBALS['post'] = $post;
+
+ if ( 'display' === $context ) {
+ setup_postdata( $post );
+ }
+
+ $response = array();
+ foreach ( array_keys( $this->post_object_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ // explicitly cast all output
+ $response[$key] = (int) $post->ID;
+ break;
+ case 'site_ID' :
+ $response[$key] = (int) $this->api->get_blog_id_for_output();
+ break;
+ case 'author' :
+ $response[$key] = (object) $this->get_author( $post, 'edit' === $context && $capabilities['edit_post'] );
+ break;
+ case 'date' :
+ $response[$key] = (string) $this->format_date( $post->post_date_gmt, $post->post_date );
+ break;
+ case 'modified' :
+ $response[$key] = (string) $this->format_date( $post->post_modified_gmt, $post->post_modified );
+ break;
+ case 'title' :
+ if ( 'display' === $context ) {
+ $response[$key] = (string) get_the_title( $post->ID );
+ } else {
+ $response[$key] = (string) htmlspecialchars_decode( $post->post_title, ENT_QUOTES );
+ }
+ break;
+ case 'URL' :
+ if ( 'revision' === $post->post_type ) {
+ $response[$key] = (string) esc_url_raw( get_permalink( $post->post_parent ) );
+ } else {
+ $response[$key] = (string) esc_url_raw( get_permalink( $post->ID ) );
+ }
+ break;
+ case 'short_URL' :
+ $response[$key] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) );
+ break;
+ case 'content' :
+ if ( 'display' === $context ) {
+ add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ $response[$key] = (string) $this->get_the_post_content_for_display();
+ remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ } else {
+ $response[$key] = (string) $post->post_content;
+ }
+ break;
+ case 'excerpt' :
+ if ( 'display' === $context ) {
+ add_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ ob_start();
+ the_excerpt();
+ $response[$key] = (string) ob_get_clean();
+ remove_filter( 'the_password_form', array( $this, 'the_password_form' ) );
+ } else {
+ $response[$key] = htmlspecialchars_decode( (string) $post->post_excerpt, ENT_QUOTES );
+ }
+ break;
+ case 'status' :
+ $response[$key] = (string) get_post_status( $post->ID );
+ break;
+ case 'sticky' :
+ $response[$key] = (bool) is_sticky( $post->ID );
+ break;
+ case 'slug' :
+ $response[$key] = (string) $post->post_name;
+ break;
+ case 'guid' :
+ $response[$key] = (string) $post->guid;
+ break;
+ case 'password' :
+ $response[$key] = (string) $post->post_password;
+ if ( 'edit' === $context ) {
+ $response[$key] = htmlspecialchars_decode( (string) $response[$key], ENT_QUOTES );
+ }
+ break;
+ case 'parent' : // (object|false)
+ if ( $post->post_parent ) {
+ $parent = get_post( $post->post_parent );
+ if ( 'display' === $context ) {
+ $parent_title = (string) get_the_title( $parent->ID );
+ } else {
+ $parent_title = (string) htmlspecialchars_decode( $post->post_title, ENT_QUOTES );
+ }
+ $response[$key] = (object) array(
+ 'ID' => (int) $parent->ID,
+ 'type' => (string) $parent->post_type,
+ 'link' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $parent->ID ),
+ 'title' => $parent_title,
+ );
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'type' :
+ $response[$key] = (string) $post->post_type;
+ break;
+ case 'discussion' :
+ $response[$key] = array(
+ 'comments_open' => (bool) comments_open( $post->ID ),
+ 'comment_status' => (string) $post->comment_status,
+ 'pings_open' => (bool) pings_open( $post->ID ),
+ 'ping_status' => (string) $post->ping_status,
+ 'comment_count' => (int) $post->comment_count,
+ );
+ break;
+ case 'likes_enabled' :
+ $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
+ $post_likes_switched = (bool) get_post_meta( $post->ID, 'switch_like_status', true );
+ $post_likes_enabled = $sitewide_likes_enabled;
+ if ( $post_likes_switched ) {
+ $post_likes_enabled = ! $post_likes_enabled;
+ }
+ $response[$key] = (bool) $post_likes_enabled;
+ break;
+ case 'sharing_enabled' :
+ $show = true;
+ /** This filter is documented in modules/sharedaddy/sharing-service.php */
+ $show = apply_filters( 'sharing_show', $show, $post );
+
+ $switched_status = get_post_meta( $post->ID, 'sharing_disabled', false );
+
+ if ( !empty( $switched_status ) )
+ $show = false;
+ $response[$key] = (bool) $show;
+ break;
+ case 'like_count' :
+ $response[$key] = (int) $this->api->post_like_count( $blog_id, $post->ID );
+ break;
+ case 'i_like' :
+ $response[$key] = (int) $this->api->is_liked( $blog_id, $post->ID );
+ break;
+ case 'is_reblogged':
+ $response[$key] = (int) $this->api->is_reblogged( $blog_id, $post->ID );
+ break;
+ case 'is_following':
+ $response[$key] = (int) $this->api->is_following( $blog_id );
+ break;
+ case 'global_ID':
+ $response[$key] = (string) $this->api->add_global_ID( $blog_id, $post->ID );
+ break;
+ case 'featured_image' :
+ if ( $is_jetpack && ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
+ $response[ $key ] = get_post_meta( $post->ID, '_jetpack_featured_image', true );
+ } else {
+ $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
+ if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
+ $response[ $key ] = (string) $image_attributes[0];
+ } else {
+ $response[ $key ] = '';
+ }
+ }
+ break;
+ case 'post_thumbnail' :
+ $response[$key] = null;
+
+ $thumb_id = get_post_thumbnail_id( $post->ID );
+ if ( ! empty( $thumb_id ) ) {
+ $attachment = get_post( $thumb_id );
+ if ( ! empty( $attachment ) )
+ $featured_image_object = $this->get_attachment( $attachment );
+
+ if ( ! empty( $featured_image_object ) ) {
+ $response[$key] = (object) $featured_image_object;
+ }
+ }
+ break;
+ case 'format' :
+ $response[$key] = (string) get_post_format( $post->ID );
+ if ( !$response[$key] ) {
+ $response[$key] = 'standard';
+ }
+ break;
+ case 'geo' : // (object|false)
+ if ( !$geo ) {
+ $response[$key] = false;
+ } else {
+ $geo_data = $geo->get_geo( 'post', $post->ID );
+ $response[$key] = false;
+ if ( $geo_data ) {
+ $geo_data = array_intersect_key( $geo_data, array( 'latitude' => true, 'longitude' => true, 'address' => true, 'public' => true ) );
+ if ( $geo_data ) {
+ $response[$key] = (object) array(
+ 'latitude' => isset( $geo_data['latitude'] ) ? (float) $geo_data['latitude'] : 0,
+ 'longitude' => isset( $geo_data['longitude'] ) ? (float) $geo_data['longitude'] : 0,
+ 'address' => isset( $geo_data['address'] ) ? (string) $geo_data['address'] : '',
+ );
+ } else {
+ $response[$key] = false;
+ }
+ // Private
+ if ( !isset( $geo_data['public'] ) || !$geo_data['public'] ) {
+ if ( 'edit' !== $context || ! $capabilities['edit_post'] ) {
+ // user can't access
+ $response[$key] = false;
+ }
+ }
+ }
+ }
+ break;
+ case 'menu_order':
+ $response[$key] = (int) $post->menu_order;
+ break;
+ case 'page_template':
+ $response[$key] = (string) get_post_meta( $post->ID, '_wp_page_template', true );
+ break;
+ case 'publicize_URLs' :
+ $publicize_URLs = array();
+ $publicize = get_post_meta( $post->ID, 'publicize_results', true );
+ if ( $publicize ) {
+ foreach ( $publicize as $service => $data ) {
+ switch ( $service ) {
+ case 'twitter' :
+ foreach ( $data as $datum ) {
+ $publicize_URLs[] = esc_url_raw( "https://twitter.com/{$datum['user_id']}/status/{$datum['post_id']}" );
+ }
+ break;
+ case 'fb' :
+ foreach ( $data as $datum ) {
+ $publicize_URLs[] = esc_url_raw( "https://www.facebook.com/permalink.php?story_fbid={$datum['post_id']}&id={$datum['user_id']}" );
+ }
+ break;
+ }
+ }
+ }
+ $response[$key] = (array) $publicize_URLs;
+ break;
+ case 'tags' :
+ $response[$key] = array();
+ $terms = wp_get_post_tags( $post->ID );
+ foreach ( $terms as $term ) {
+ if ( !empty( $term->name ) ) {
+ $response[$key][$term->name] = $this->format_taxonomy( $term, 'post_tag', 'display' );
+ }
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'categories':
+ $response[$key] = array();
+ $terms = wp_get_object_terms( $post->ID, 'category', array( 'fields' => 'all' ) );
+ foreach ( $terms as $term ) {
+ if ( !empty( $term->name ) ) {
+ $response[$key][$term->name] = $this->format_taxonomy( $term, 'category', 'display' );
+ }
+ }
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'attachments':
+ $response[$key] = array();
+ $_attachments = new WP_Query( array( 'post_parent' => $post->ID, 'post_status' => 'inherit', 'post_type' => 'attachment', 'posts_per_page' => '20' ) );
+ foreach ( $_attachments->posts as $attachment ) {
+ $response[$key][$attachment->ID] = $this->get_media_item_v1_1( $attachment->ID );
+ }
+ $response['attachment_count'] = $_attachments->found_posts;
+ $response[$key] = (object) $response[$key];
+ break;
+ case 'metadata' : // (array|false)
+ $metadata = array();
+ foreach ( (array) has_meta( $post_id ) as $meta ) {
+ // Don't expose protected fields.
+ $show = false;
+ if ( $this->is_metadata_public( $meta['meta_key'] ) )
+ $show = true;
+ if ( current_user_can( 'edit_post_meta', $post_id , $meta['meta_key'] ) )
+ $show = true;
+
+ if ( !$show )
+ continue;
+
+ $metadata[] = array(
+ 'id' => $meta['meta_id'],
+ 'key' => $meta['meta_key'],
+ 'value' => maybe_unserialize( $meta['meta_value'] ),
+ );
+ }
+
+ if ( ! empty( $metadata ) ) {
+ $response[$key] = $metadata;
+ } else {
+ $response[$key] = false;
+ }
+ break;
+ case 'meta' :
+ $response[$key] = (object) array(
+ 'links' => (object) array(
+ 'self' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
+ 'help' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'help' ),
+ 'site' => (string) $this->get_site_link( $this->api->get_blog_id_for_output() ),
+// 'author' => (string) $this->get_user_link( $post->post_author ),
+// 'via' => (string) $this->get_post_link( $reblog_origin_blog_id, $reblog_origin_post_id ),
+ 'replies' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'replies/' ),
+ 'likes' => (string) $this->get_post_link( $this->api->get_blog_id_for_output(), $post->ID, 'likes/' ),
+ ),
+ );
+ break;
+ case 'capabilities' :
+ $response[$key] = $capabilities;
+ break;
+
+ }
+ }
+
+ // WPCOM_JSON_API_Post_Endpoint::find_featured_worthy_media( $post );
+ // $response['featured_media'] = self::find_featured_media( $response );
+
+ unset( $GLOBALS['post'] );
+ return $response;
+ }
+
+ // No Blog ID parameter. No Post ID parameter. Depends on globals.
+ // Expects setup_postdata() to already have been run
+ function get_the_post_content_for_display() {
+ global $pages, $page;
+
+ $old_pages = $pages;
+ $old_page = $page;
+
+ $content = join( "\n\n", $pages );
+ $content = preg_replace( '/<!--more(.*?)?-->/', '', $content );
+ $pages = array( $content );
+ $page = 1;
+
+ ob_start();
+ the_content();
+ $return = ob_get_clean();
+
+ $pages = $old_pages;
+ $page = $old_page;
+
+ return $return;
+ }
+
+ function get_blog_post( $blog_id, $post_id, $context = 'display' ) {
+ $blog_id = $this->api->get_blog_id( $blog_id );
+ if ( !$blog_id || is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+ switch_to_blog( $blog_id );
+ $post = $this->get_post_by( 'ID', $post_id, $context );
+ restore_current_blog();
+ return $post;
+ }
+
+ /**
+ * Supporting featured media in post endpoints. Currently on for wpcom blogs
+ * since it's calling WPCOM_JSON_API_Read_Endpoint methods which presently
+ * rely on wpcom specific functionality.
+ *
+ * @param WP_Post $post
+ * @return object list of featured media
+ */
+ public static function find_featured_media( &$post ) {
+
+ if ( class_exists( 'WPCOM_JSON_API_Read_Endpoint' ) ) {
+ return WPCOM_JSON_API_Read_Endpoint::find_featured_worthy_media( (array) $post );
+ } else {
+ return (object) array();
+ }
+
+ }
+
+
+
+ function win8_gallery_shortcode( $attr ) {
+ global $post;
+
+ static $instance = 0;
+ $instance++;
+
+ $output = '';
+
+ // We're trusting author input, so let's at least make sure it looks like a valid orderby statement
+ if ( isset( $attr['orderby'] ) ) {
+ $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] );
+ if ( !$attr['orderby'] )
+ unset( $attr['orderby'] );
+ }
+
+ extract( shortcode_atts( array(
+ 'order' => 'ASC',
+ 'orderby' => 'menu_order ID',
+ 'id' => $post->ID,
+ 'include' => '',
+ 'exclude' => '',
+ 'slideshow' => false
+ ), $attr, 'gallery' ) );
+
+ // Custom image size and always use it
+ add_image_size( 'win8app-column', 480 );
+ $size = 'win8app-column';
+
+ $id = intval( $id );
+ if ( 'RAND' === $order )
+ $orderby = 'none';
+
+ if ( !empty( $include ) ) {
+ $include = preg_replace( '/[^0-9,]+/', '', $include );
+ $_attachments = get_posts( array( 'include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ $attachments = array();
+ foreach ( $_attachments as $key => $val ) {
+ $attachments[$val->ID] = $_attachments[$key];
+ }
+ } elseif ( !empty( $exclude ) ) {
+ $exclude = preg_replace( '/[^0-9,]+/', '', $exclude );
+ $attachments = get_children( array( 'post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ } else {
+ $attachments = get_children( array( 'post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby ) );
+ }
+
+ if ( ! empty( $attachments ) ) {
+ foreach ( $attachments as $id => $attachment ) {
+ $link = isset( $attr['link'] ) && 'file' === $attr['link'] ? wp_get_attachment_link( $id, $size, false, false ) : wp_get_attachment_link( $id, $size, true, false );
+
+ if ( $captiontag && trim($attachment->post_excerpt) ) {
+ $output .= "<div class='wp-caption aligncenter'>$link
+ <p class='wp-caption-text'>" . wptexturize($attachment->post_excerpt) . "</p>
+ </div>";
+ } else {
+ $output .= $link . ' ';
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns attachment object.
+ *
+ * @param $attachment attachment row
+ *
+ * @return (object)
+ */
+ function get_attachment( $attachment ) {
+ $metadata = wp_get_attachment_metadata( $attachment->ID );
+
+ $result = array(
+ 'ID' => (int) $attachment->ID,
+ 'URL' => (string) wp_get_attachment_url( $attachment->ID ),
+ 'guid' => (string) $attachment->guid,
+ 'mime_type' => (string) $attachment->post_mime_type,
+ 'width' => (int) isset( $metadata['width'] ) ? $metadata['width'] : 0,
+ 'height' => (int) isset( $metadata['height'] ) ? $metadata['height'] : 0,
+ );
+
+ if ( isset( $metadata['duration'] ) ) {
+ $result['duration'] = (int) $metadata['duration'];
+ }
+
+ return (object) apply_filters( 'get_attachment', $result );
+ }
+
+ /**
+ * Get post-specific user capabilities
+ * @param WP_Post $post post object
+ * @return array array of post-level permissions; 'publish_post', 'delete_post', 'edit_post'
+ */
+ function get_current_user_capabilities( $post ) {
+ return array(
+ 'publish_post' => current_user_can( 'publish_post', $post ),
+ 'delete_post' => current_user_can( 'delete_post', $post ),
+ 'edit_post' => current_user_can( 'edit_post', $post )
+ );
+ }
+
+ /**
+ * Get post ID by name
+ *
+ * Attempts to match name on post title and page path
+ *
+ * @param string $name
+ *
+ * @return int|object Post ID on success, WP_Error object on failure
+ **/
+ protected function get_post_id_by_name( $name ) {
+ $name = sanitize_title( $name );
+
+ if ( ! $name ) {
+ return new WP_Error( 'invalid_post', 'Invalid post', 400 );
+ }
+
+ $posts = get_posts( array( 'name' => $name ) );
+
+ if ( ! $posts || ! isset( $posts[0]->ID ) || ! $posts[0]->ID ) {
+ $page = get_page_by_path( $name );
+
+ if ( ! $page ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ $post_id = $page->ID;
+ } else {
+ $post_id = (int) $posts[0]->ID;
+ }
+
+ return $post_id;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-publicize-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-publicize-endpoint.php
new file mode 100644
index 00000000..5220e112
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-publicize-endpoint.php
@@ -0,0 +1,172 @@
+<?php
+
+class WPCOM_JSON_API_Get_Connections_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/connections
+ function callback( $path = '', $blog_id = 0 ) {
+ // Verify required Publicize Jetpack module is active
+ if ( ! class_exists( 'Publicize' ) || ( method_exists( 'Jetpack', 'is_module_active' ) && ! Jetpack::is_module_active( 'publicize' ) ) ) {
+ return new WP_Error( 'missing_jetpack_module', 'The Publicize module must be activated in order to use this endpoint.', 400 );
+ }
+
+ // Authenticate user
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ), false );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $current_user = wp_get_current_user();
+ if ( ! $current_user->ID ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to query information about the current user.', 403 );
+ }
+
+ // Parse query arguments to determine if filtering is requested
+ $args = $this->query_args();
+ $service_filter = false;
+ if ( ! empty( $args['service'] ) ) {
+ $service_filter = $args['service'];
+ }
+
+ // Iterate over connected services
+ $publicize = new Publicize();
+ $connected_services = $publicize->get_services( 'connected' );
+ $output = array();
+ foreach( $connected_services as $service => $connections ) {
+ if ( false != $service_filter && $service_filter != $service ) {
+ continue;
+ }
+
+ foreach ( $connections as $connection_id => $connection ) {
+ $output[] = WPCOM_JSON_API_Get_Connection_Endpoint::get_connection( $service, $connection );
+ }
+ }
+
+ return array( 'connections' => $output );
+ }
+}
+
+class WPCOM_JSON_API_Get_Connection_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function get_connection_by_id( $connection_id ) {
+ $publicize = new Publicize();
+
+ $connected_services = $publicize->get_services( 'connected' );
+ foreach ( $connected_services as $service => $connections ) {
+ foreach ( $connections as $c => $connection ) {
+ if ( $connection_id == $publicize->get_connection_id( $connection ) ) {
+ return WPCOM_JSON_API_Get_Connection_Endpoint::get_connection( $service, $connections[ $c ] );
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function get_connection( $service, $connection ) {
+ $publicize = new Publicize();
+
+ $connection_id = $publicize->get_connection_id( $connection );
+ if ( method_exists( $connection, 'get_meta' ) ) {
+ $connection_meta = $connection->get_meta();
+ $connection_data = (array) $connection->get_meta( 'connection_data' );
+ } else {
+ $connection_meta = $connection;
+ $connection_data = $connection['connection_data'];
+ }
+
+ return array(
+ 'ID' => (int) $connection_id,
+ 'token_ID' => (int) $connection_data['token_id'],
+ 'conn_ID' => (int) $connection_id,
+ 'site_ID' => (int) $connection_data['blog_id'],
+ 'user_ID' => (int) $connection_data['user_id'],
+ 'shared' => ( 0 == (int) $connection_data['user_id'] ) ? true : false,
+ 'service' => $service,
+ 'label' => $publicize->get_service_label( $service ),
+ 'issued' => $connection_meta['issued'],
+ 'expires' => $connection_meta['expires'],
+ 'external_ID' => $connection_meta['external_id'],
+ 'external_name' => $connection_meta['external_name'],
+ 'external_display' => $publicize->get_display_name( $service, $connection ),
+ 'URL' => $publicize->get_profile_link( $service, $connection ),
+ 'status' => ( method_exists( $connection, 'is_expired' ) && $connection->is_expired( HOUR_IN_SECONDS ) ) ? 'broken' : 'ok',
+ 'refresh_url' => $publicize->refresh_url( $service ),
+ 'meta' => maybe_unserialize( $connection_data['meta'] ),
+ );
+ }
+
+ // /sites/%s/connections/$connection_id
+ function callback( $path = '', $blog_id = 0, $connection_id = 0 ) {
+ // Verify required Publicize Jetpack module is active
+ if ( ! class_exists( 'Publicize' ) || ( method_exists( 'Jetpack', 'is_module_active' ) && ! Jetpack::is_module_active( 'publicize' ) ) ) {
+ return new WP_Error( 'missing_jetpack_module', 'The Publicize module must be activated in order to use this endpoint.', 400 );
+ }
+
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ), false );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $current_user = wp_get_current_user();
+ if ( ! $current_user->ID ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to query information about the current user.', 403 );
+ }
+
+ // Attempt to find connection
+ $connection = WPCOM_JSON_API_Get_Connection_Endpoint::get_connection_by_id( $connection_id );
+
+ // Verify that user has permission to view this connection
+ if ( $current_user->ID != $connection['user_ID'] && 0 != $connection['user_ID'] ) {
+ return new WP_Error( 'authorization_required', 'You do not have permission to access this resource.', 403 );
+ }
+
+ if ( empty( $connection ) ) {
+ return new WP_Error( 'unknown_connection', 'Connection not found.', 404 );
+ }
+
+ return $connection;
+ }
+}
+
+class WPCOM_JSON_API_Delete_Connection_Endpoint extends WPCOM_JSON_API_Endpoint {
+ // /sites/%s/connections/$connection_id/delete
+ function callback( $path = '', $blog_id = 0 , $connection_id = 0 ) {
+ // Verify required Publicize Jetpack module is active
+ if ( ! class_exists( 'Publicize' ) || ( method_exists( 'Jetpack', 'is_module_active' ) && ! Jetpack::is_module_active( 'publicize' ) ) ) {
+ return new WP_Error( 'missing_jetpack_module', 'The Publicize module must be activated in order to use this endpoint.', 400 );
+ }
+
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ), false );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $current_user = wp_get_current_user();
+ if ( ! $current_user->ID ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to query information about the current user.', 403 );
+ }
+
+ // Attempt to find connection
+ $connection = WPCOM_JSON_API_Get_Connection_Endpoint::get_connection_by_id( $connection_id );
+
+ if ( empty( $connection ) ) {
+ return new WP_Error( 'unknown_connection', 'Connection not found.', 404 );
+ }
+
+ // Verify that user has permission to view this connection
+ if ( $current_user->ID != $connection['user_ID'] && 0 != $connection['user_ID'] ) {
+ return new WP_Error( 'authorization_required', 'You do not have permission to access this resource.', 403 );
+ }
+
+ // Remove publicize connections related to the connection
+ $publicize = new Publicize();
+ $is_deleted = ( false !== $publicize->disconnect( $connection['service'], $connection_id ) );
+
+ if ( $is_deleted ) {
+ do_action( 'rest_api_delete_publicize_connection', $connection_id );
+ }
+
+ return array(
+ 'ID' => (int) $connection_id,
+ 'deleted' => $is_deleted
+ );
+ }
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-endpoint.php
new file mode 100644
index 00000000..fe4cf85b
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-endpoint.php
@@ -0,0 +1,61 @@
+<?php
+class WPCOM_JSON_API_Render_Embed_Endpoint extends WPCOM_JSON_API_Render_Endpoint {
+ // /sites/%s/embeds/render -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'Your token must have permission to post on this blog.', 403 );
+ }
+
+ $args = $this->query_args();
+ $embed_url = trim( $args['embed_url'] );
+
+ // quick validation
+ if ( ! preg_match_all( '|^\s*(https?://[^\s"]+)\s*$|im', $embed_url, $matches ) ) {
+ return new WP_Error( 'invalid_embed_url', 'The embed_url parameter must be a valid URL.', 400 );
+ }
+
+ if ( count( $matches[1] ) > 1 ) {
+ return new WP_Error( 'invalid_embed', 'Only one embed can be rendered at a time.', 400 );
+ }
+
+ $embed_url = array_shift( $matches[1] );
+ $parts = parse_url( $embed_url );
+ if ( ! $parts ) {
+ return new WP_Error( 'invalid_embed_url', 'The embed_url parameter must be a valid URL.', 400 );
+ }
+
+ // in order for oEmbed to fire in the `$wp_embed->shortcode` method, we need to set a post as the current post
+ $_posts = get_posts( array( 'posts_per_page' => 1, 'suppress_filters' => false ) );
+ if ( ! empty( $_posts ) ) {
+ global $post;
+ $post = array_shift( $_posts );
+ }
+
+ global $wp_embed;
+ $render = $this->process_render( array( $this, 'do_embed' ), $embed_url );
+
+ // if nothing happened, then the shortcode does not exist.
+ $is_an_embed = ( $embed_url != $render['result'] && $wp_embed->maybe_make_link( $embed_url ) != $render['result'] );
+ if ( ! $is_an_embed ) {
+ return new WP_Error( 'invalid_embed', 'The requested URL is not an embed.', 400 );
+ }
+
+ // our output for this endpoint..
+ $return['embed_url'] = $embed_url;
+ $return['result'] = $render['result'];
+
+ $return = $this->add_assets( $return, $render['loaded_scripts'], $render['loaded_styles'] );
+
+ return $return;
+ }
+
+ function do_embed( $embed_url ) {
+ global $wp_embed;
+ return $wp_embed->shortcode( array(), $embed_url );
+ }
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-reversal-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-reversal-endpoint.php
new file mode 100644
index 00000000..04dd694a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-embed-reversal-endpoint.php
@@ -0,0 +1,74 @@
+<?php
+class WPCOM_JSON_API_Render_Embed_Reversal_Endpoint extends WPCOM_JSON_API_Render_Endpoint {
+ // /sites/%s/embeds/reversal -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'Your token must have permission to post on this blog.', 403 );
+ }
+
+ $is_shortcode = $is_embed = false;
+
+ $input = $this->input( true );
+ $maybe_embed = trim( $input['maybe_embed'] );
+ if ( empty( $maybe_embed ) ) {
+ return new WP_Error( 'empty_embed', 'Please provide an embed code to process.', 400 );
+ }
+
+ $ksesed_content = trim( wp_strip_all_tags( wp_kses_post( $maybe_embed ), true ) );
+ if ( empty( $ksesed_content ) ) {
+ return new WP_Error( 'invalid_embed', 'Invalid or empty embed provided.', 400 );
+ }
+
+ $shortcode_pattern = get_shortcode_regex();
+ $url_pattern = '/^http(s)?:\/\/[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(\/.*)?$/i';
+ preg_match_all( "/$shortcode_pattern/s", $ksesed_content, $shortcode_matches );
+ preg_match_all( "$url_pattern", $ksesed_content, $url_matches );
+
+ if ( empty( $shortcode_matches[0] ) && empty( $url_matches[0] ) )
+ return new WP_Error( 'invalid_embed', 'The provided embed is not supported.', 400 );
+
+ if ( ( count( $shortcode_matches[0] ) + count( $url_matches[0] ) ) > 1 ) {
+ return new WP_Error( 'invalid_embed', 'Only one embed/shortcode reversal can be rendered at a time.', 400 );
+ }
+
+ if ( ! empty( $shortcode_matches[0] ) ) {
+ $is_shortcode = true;
+ } elseif ( ! empty( $url_matches[0] ) ) {
+ $is_embed = true;
+ }
+
+ $render = $this->process_render( array( $this, 'render_shortcode_reversal' ), array( 'shortcode_reversal' => $ksesed_content, 'is_shortcode' => $is_shortcode, 'is_embed' => $is_embed ) );
+
+
+ // if nothing happened, then the shortcode does not exist.
+ global $wp_embed;
+ if ( empty( $render ) || empty( $render['result'] ) || $ksesed_content == $render['result'] || $wp_embed->maybe_make_link( $maybe_embed ) == $render['result'] ) {
+ return new WP_Error( 'invalid_embed', 'The provided embed is not supported.', 400 );
+ }
+
+ // our output for this endpoint..
+ $return['maybe_embed'] = $maybe_embed;
+ $return['result'] = $ksesed_content;
+ $return['reversal_type'] = ( $is_embed ) ? 'embed' : 'shortcode';
+ $return['render_result'] = $render['result'];
+
+ $return = $this->add_assets( $return, $render['loaded_scripts'], $render['loaded_styles'] );
+
+ return $return;
+ }
+
+ function render_shortcode_reversal( $args ) {
+ if ( $args['is_shortcode'] ) {
+ return call_user_func( array( $this, 'do_shortcode' ), $args['shortcode_reversal'] );
+ } else if ( $args['is_embed'] ) {
+ return call_user_func( array( $this, 'do_embed' ), $args['shortcode_reversal'] );
+ }
+ return false;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-endpoint.php
new file mode 100644
index 00000000..273429e0
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-endpoint.php
@@ -0,0 +1,124 @@
+<?php
+
+// these are helpers for the shortcode and embed render endpoints
+abstract class WPCOM_JSON_API_Render_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ /*
+ * Figure out what scripts and styles to load.
+ * props to o2's o2_Read_API::poll() function for inspiration.
+ *
+ * In short we figure out what scripts load for a "normal" page load by executing wp_head and wp_footer
+ * then we render our shortcode (to both get our result, and to have the shortcode files enqueue their resources)
+ * then we load wp_head and wp_footer again to see what new resources were added
+ * finally we find out the url to the source file and any extra info (like media or init js)
+ */
+ function process_render( $callback, $callback_arg ) {
+ global $wp_scripts, $wp_styles;
+
+ // initial scripts & styles (to subtract)
+ ob_start();
+ wp_head();
+ wp_footer();
+ ob_end_clean();
+ $initial_scripts = $wp_scripts->done;
+ $initial_styles = $wp_styles->done;
+
+ // actually render the shortcode, get the result, and do the resource loading again so we can subtract..
+ ob_start();
+ wp_head();
+ ob_end_clean();
+ $result = call_user_func( $callback, $callback_arg );
+ ob_start();
+ wp_footer();
+ ob_end_clean();
+
+ // find the difference (the new resource files)
+ $loaded_scripts = array_diff( $wp_scripts->done, $initial_scripts );
+ $loaded_styles = array_diff( $wp_styles->done, $initial_styles );
+ return array(
+ 'result' => $result,
+ 'loaded_scripts' => $loaded_scripts,
+ 'loaded_styles' => $loaded_styles,
+ );
+ }
+
+ /**
+ * Takes the list of styles and scripts and adds them to the JSON response
+ */
+ function add_assets( $return, $loaded_scripts, $loaded_styles ) {
+ global $wp_scripts, $wp_styles;
+ // scripts first, just cuz
+ if ( count( $loaded_scripts ) > 0 ) {
+ $scripts = array();
+ foreach ( $loaded_scripts as $handle ) {
+ if ( !isset( $wp_scripts->registered[ $handle ] ) )
+ continue;
+
+ $src = $wp_scripts->registered[ $handle ]->src;
+
+ // attach version and an extra query parameters
+ $ver = $this->get_version( $wp_scripts->registered[ $handle ]->ver, $wp_scripts->default_version );
+ if ( isset( $wp_scripts->args[ $handle ] ) ) {
+ $ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
+ }
+ $src = add_query_arg( 'ver', $ver, $src );
+
+ // add to an aray so we can return all this info
+ $scripts[ $handle ] = array(
+ 'src' => $src,
+ );
+ $extra = $wp_scripts->print_extra_script( $handle, false );
+ if ( !empty( $extra ) ) {
+ $scripts[$handle]['extra'] = $extra;
+ }
+ }
+ $return['scripts'] = $scripts;
+ }
+ // now styles
+ if ( count( $loaded_styles ) > 0 ) {
+ $styles = array();
+ foreach ( $loaded_styles as $handle ) {
+ if ( !isset( $wp_styles->registered[ $handle ] ) )
+ continue;
+
+ $src = $wp_styles->registered[ $handle ]->src;
+
+ // attach version and an extra query parameters
+ $ver = $this->get_version( $wp_styles->registered[ $handle ]->ver, $wp_styles->default_version );
+ if ( isset( $wp_styles->args[ $handle ] ) ) {
+ $ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
+ }
+ $src = add_query_arg( 'ver', $ver, $src );
+
+ // is there a special media (print, screen, etc) for this? if not, default to 'all'
+ $media = 'all';
+ if ( isset( $wp_styles->registered[ $handle ]->args ) ) {
+ $media = esc_attr( $wp_styles->registered[ $handle ]->args );
+ }
+
+ // add to an aray so we can return all this info
+ $styles[ $handle ] = array (
+ 'src' => $src,
+ 'media' => $media,
+ );
+ }
+
+ $return['styles'] = $styles;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns the 'version' string set by the shortcode so different versions of scripts/styles can be loaded
+ */
+ function get_version( $this_scripts_version, $default_version ) {
+ if ( null === $this_scripts_version ) {
+ $ver = '';
+ } else {
+ $ver = $this_scripts_version ? $this_scripts_version : $default_version;
+ }
+ return $ver;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-shortcode-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-shortcode-endpoint.php
new file mode 100644
index 00000000..055f84d1
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-render-shortcode-endpoint.php
@@ -0,0 +1,50 @@
+<?php
+class WPCOM_JSON_API_Render_Shortcode_Endpoint extends WPCOM_JSON_API_Render_Endpoint {
+ // /sites/%s/shortcodes/render -> $blog_id
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return new WP_Error( 'unauthorized', 'Your token must have permission to post on this blog.', 403 );
+ }
+
+ $args = $this->query_args();
+ $shortcode = trim( $args['shortcode'] );
+
+ // Quick validation - shortcodes should always be enclosed in brackets []
+ if ( ! wp_startswith( $shortcode, '[' ) || ! wp_endswith( $shortcode, ']' ) ) {
+ return new WP_Error( 'invalid_shortcode', 'The shortcode parameter must begin and end with square brackets.', 400 );
+ }
+
+ // Make sure only one shortcode is being rendered at a time
+ $pattern = get_shortcode_regex();
+ preg_match_all( "/$pattern/s", $shortcode, $matches );
+ if ( count( $matches[0] ) > 1 ) {
+ return new WP_Error( 'invalid_shortcode', 'Only one shortcode can be rendered at a time.', 400 );
+ }
+
+ $render = $this->process_render( array( $this, 'do_shortcode' ), $shortcode );
+
+ // if nothing happened, then the shortcode does not exist.
+ if ( $shortcode == $render['result'] ) {
+ return new WP_Error( 'invalid_shortcode', 'The requested shortcode does not exist.', 400 );
+ }
+
+ // our output for this endpoint..
+ $return['shortcode'] = $shortcode;
+ $return['result'] = $render['result'];
+
+ $return = $this->add_assets( $return, $render['loaded_scripts'], $render['loaded_styles'] );
+
+ return $return;
+ }
+
+ function do_shortcode( $shortcode ) {
+ $result = do_shortcode( $shortcode );
+ return $result;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-sharing-buttons-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-sharing-buttons-endpoint.php
new file mode 100644
index 00000000..8b2dc15f
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-sharing-buttons-endpoint.php
@@ -0,0 +1,385 @@
+<?php
+
+abstract class WPCOM_JSON_API_Sharing_Button_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ public static $all_visibilities = array( 'visible', 'hidden' );
+
+ protected $sharing_service;
+
+ protected function setup() {
+ $this->sharing_service = new Sharing_Service();
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return new WP_Error( 'forbidden', 'You do not have the capability to manage sharing buttons for this site', 403 );
+ } else if ( ! class_exists( 'Sharing_Service' ) || ! class_exists( 'Sharing_Source' ) ||
+ ( method_exists( 'Jetpack', 'is_module_active' ) && ! Jetpack::is_module_active( 'sharedaddy' ) ) ) {
+ return new WP_Error( 'missing_jetpack_module', 'The Sharing module must be activated in order to use this endpoint', 400 );
+ }
+ }
+
+ public function format_sharing_button( $button ) {
+ $response = array(
+ 'ID' => $button->get_id(),
+ 'name' => $button->get_name(),
+ 'shortname' => $button->shortname,
+ 'custom' => is_a( $button, 'Share_Custom' ),
+ 'enabled' => $this->is_button_enabled( $button ),
+ );
+
+ if ( $response['enabled'] ) {
+ // Status is either "disabled" or the visibility value
+ $response['visibility'] = $this->get_button_visibility( $button );
+ }
+
+ if ( ! empty( $button->genericon ) ) {
+ // Only pre-defined sharing buttons include genericon
+ $response['genericon'] = $button->genericon;
+ }
+
+ if ( method_exists( $button, 'get_options' ) ) {
+ // merge get_options() values into response, primarily to account
+ // for custom sharing button values
+ foreach ( $button->get_options() as $key => $value ) {
+ // Capitalize URL property
+ if ( 'url' === strtolower( $key ) ) {
+ $key = strtoupper( $key );
+ }
+
+ $response[ $key ] = $value;
+ }
+ }
+
+ return $response;
+ }
+
+ public function get_button_visibility( $button ) {
+ $services = $this->sharing_service->get_blog_services();
+ $visibilities = self::$all_visibilities;
+ $button_id = $button->get_id();
+
+ foreach ( $visibilities as $visibility ) {
+ if ( isset( $services[ $visibility ][ $button_id ] ) ) {
+ return $visibility;
+ }
+ }
+
+ return false;
+ }
+
+ public function is_button_enabled( $button ) {
+ return false !== $this->get_button_visibility( $button );
+ }
+
+ protected function is_button_input_for_custom( $button ) {
+ return ( isset( $button['custom'] ) && $button['custom'] ) ||
+ ( isset( $button['ID'] ) && 1 === preg_match( '/^custom-/', $button['ID'] ) ) ||
+ ! empty( $button['name'] ) || ! empty( $button['URL'] ) || ! empty( $button['icon'] );
+ }
+
+ protected function validate_button_input( $button, $is_new = false ) {
+ if ( ! empty( $button['visibility'] ) && ! in_array( $button['visibility'], self::$all_visibilities ) ) {
+ return new WP_Error( 'invalid_visibility', sprintf( 'The visibility field must be one of the following values: %s', implode( ', ', self::$all_visibilities ) ), 400 );
+ } else if ( $is_new && empty( $button['URL'] ) ) {
+ return new WP_Error( 'invalid_request', 'The URL field is required', 400 );
+ } else if ( $is_new && empty( $button['icon'] ) ) {
+ return new WP_Error( 'invalid_request', 'The icon field is required', 400 );
+ }
+ }
+
+ public function create_custom_button( $button ) {
+ // Default visibility to 'visible' if enabled
+ if ( empty( $button['visibility'] ) && true === $button['enabled'] ) {
+ $button['visibility'] = 'visible';
+ }
+
+ $updated_service = $this->sharing_service->new_service( $button['name'], $button['URL'], $button['icon'] );
+ if ( false !== $updated_service && ( true === $button['enabled'] || ! empty( $button['visibility'] ) ) ) {
+ $blog_services = $this->sharing_service->get_blog_services();
+ $blog_services[ $button['visibility'] ][ (string) $updated_service->get_id() ] = $updated_service;
+ $this->sharing_service->set_blog_services( array_keys( $blog_services['visible'] ), array_keys( $blog_services['hidden'] ) );
+ }
+
+ return $updated_service;
+ }
+
+ public function update_button( $button_id, $button ) {
+ $blog_services = $this->sharing_service->get_blog_services();
+
+ // Find existing button
+ $all_buttons = $this->sharing_service->get_all_services_blog();
+ if ( ! array_key_exists( $button_id, $all_buttons ) ) {
+ // Button doesn't exist
+ return new WP_Error( 'not_found', 'The specified sharing button was not found', 404 );
+ }
+
+ $updated_service = $all_buttons[ $button_id ];
+ $service_id = $updated_service->get_id();
+ if ( is_a( $all_buttons[ $button_id ], 'Share_Custom' ) ) {
+ // Replace options for existing custom button
+ $options = $updated_service->get_options();
+ $name = isset( $button['name'] ) ? $button['name'] : $options['name'];
+ $url = isset( $button['URL'] ) ? $button['URL'] : $options['url'];
+ $icon = isset( $button['icon'] ) ? $button['icon'] : $options['icon'];
+ $updated_service = new Share_Custom( $service_id, array( 'name' => $name, 'url' => $url, 'icon' => $icon ) );
+ $this->sharing_service->set_service( $button_id, $updated_service );
+ }
+
+ // Default visibility to 'visible' if enabled
+ if ( empty( $button['visibility'] ) && true === $button['enabled'] ) {
+ $button['visibility'] = 'visible';
+ } else if ( false === $button['enabled'] ) {
+ unset( $button['visibility'] );
+ }
+
+ // Update button visibility and enabled status
+ $visibility_changed = ( isset( $button['visibility'] ) || true === $button['enabled'] ) && ! array_key_exists( $service_id, $blog_services[ $button['visibility'] ] );
+ $is_disabling = false === $button['enabled'];
+ if ( $visibility_changed || $is_disabling ) {
+ // Remove from all other visibilities
+ foreach ( $blog_services as $service_visibility => $services ) {
+ if ( $service_visibility !== $button['visibility'] || $is_disabling ) {
+ unset( $blog_services[ $service_visibility ][ $service_id ] );
+ }
+ }
+
+ if ( $visibility_changed ) {
+ $blog_services[ $button['visibility'] ][ $service_id ] = $updated_service;
+ }
+
+ $this->sharing_service->set_blog_services( array_keys( $blog_services['visible'] ), array_keys( $blog_services['hidden'] ) );
+ }
+
+ return $updated_service;
+ }
+
+}
+
+class WPCOM_JSON_API_Get_Sharing_Buttons_Endpoint extends WPCOM_JSON_API_Sharing_Button_Endpoint {
+
+ // GET /sites/%s/sharing-buttons -> $blog_id
+ public function callback( $path = '', $blog_id = 0 ) {
+ $args = $this->query_args();
+
+ // Validate request
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $continue = $this->setup();
+ if ( is_wp_error( $continue ) ) {
+ return $continue;
+ }
+
+ if ( ! empty( $args['visibility'] ) && ! in_array( $args['visibility'], self::$all_visibilities ) ) {
+ return new WP_Error( 'invalid_visibility', sprintf( 'The visibility field must be one of the following values: %s', implode( ', ', self::$all_visibilities ) ), 400 );
+ }
+
+ // Determine which visibilities to include based on request
+ $visibilities = empty( $args['visibility'] ) ? self::$all_visibilities : array( $args['visibility'] );
+
+ // Discover enabled services
+ $buttons = array();
+ $enabled_services = $this->sharing_service->get_blog_services();
+ $all_services = $this->sharing_service->get_all_services_blog();
+
+ // Include buttons of desired visibility
+ foreach ( $visibilities as $visibility ) {
+ $buttons = array_merge( $buttons, $enabled_services[ $visibility ] );
+ }
+
+ // Unless `enabled_only` or `visibility` is specified, append the
+ // remaining buttons to the end of the array
+ if ( ( ! isset( $args['enabled_only'] ) || ! $args['enabled_only'] ) && empty( $args['visibility'] ) ) {
+ foreach ( $all_services as $id => $button ) {
+ if ( ! array_key_exists( $id, $buttons ) ) {
+ $buttons[ $id ] = $button;
+ }
+ }
+ }
+
+ // Format each button in the response
+ $response = array();
+ foreach ( $buttons as $button ) {
+ $response[] = $this->format_sharing_button( $button );
+ }
+
+ return array(
+ 'found' => count( $response ),
+ 'sharing_buttons' => $response
+ );
+ }
+}
+
+class WPCOM_JSON_API_Get_Sharing_Button_Endpoint extends WPCOM_JSON_API_Sharing_Button_Endpoint {
+
+ // GET /sites/%s/sharing-buttons/%s -> $blog_id, $button_id
+ public function callback( $path = '', $blog_id = 0, $button_id = 0 ) {
+ // Validate request
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $continue = $this->setup();
+ if ( is_wp_error( $continue ) ) {
+ return $continue;
+ }
+
+ // Search existing services for button
+ $all_buttons = $this->sharing_service->get_all_services_blog();
+ if ( ! array_key_exists( $button_id, $all_buttons ) ) {
+ return new WP_Error( 'not_found', 'The specified sharing button was not found', 404 );
+ } else {
+ return $this->format_sharing_button( $all_buttons[ $button_id ] );
+ }
+ }
+
+}
+
+class WPCOM_JSON_API_Update_Sharing_Buttons_Endpoint extends WPCOM_JSON_API_Sharing_Button_Endpoint {
+
+ // POST /sites/%s/sharing-buttons -> $blog_id
+ public function callback( $path = '', $blog_id = 0 ) {
+ $input = $this->input();
+
+ // Validate request
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $continue = $this->setup();
+ if ( is_wp_error( $continue ) ) {
+ return $continue;
+ }
+
+ $all_buttons = $this->sharing_service->get_all_services_blog();
+
+ // We do a first pass of all buttons to verify that no validation
+ // issues exist before continuing to update
+ foreach ( $input['sharing_buttons'] as $button ) {
+ $button_exists = isset( $button['ID'] ) && array_key_exists( $button['ID'], $all_buttons );
+ $is_custom = $this->is_button_input_for_custom( $button );
+
+ // If neither custom nor existing, bail
+ if ( ! $button_exists && ! $is_custom ) {
+ return new WP_Error( 'not_found', 'The specified sharing button was not found', 404 );
+ }
+
+ // Validate input, only testing custom values if the button doesn't
+ // already exist
+ $validation_error = $this->validate_button_input( $button, ! $button_exists );
+ if ( is_wp_error( $validation_error ) ) {
+ return $validation_error;
+ }
+ }
+
+ // Reset all existing buttons
+ $this->sharing_service->set_blog_services( array(), array() );
+
+ // Finally, we iterate over each button and update or create
+ $success = true;
+ $updated = array();
+ foreach ( $input['sharing_buttons'] as $button ) {
+ $button_exists = isset( $button['ID'] ) && array_key_exists( $button['ID'], $all_buttons );
+ if ( $button_exists ) {
+ $updated_service = $this->update_button( $button['ID'], $button );
+ } else {
+ $updated_service = $this->create_custom_button( $button );
+ }
+
+ // We'll allow the request to continue if a failure occurred, but
+ // log it for the response
+ if ( false === $updated_service ) {
+ $success = false;
+ } else {
+ $updated[] = $this->format_sharing_button( $updated_service );
+ }
+ }
+
+ return array(
+ 'success' => $success,
+ 'updated' => $updated
+ );
+ }
+
+}
+
+class WPCOM_JSON_API_Update_Sharing_Button_Endpoint extends WPCOM_JSON_API_Sharing_Button_Endpoint {
+
+ // POST /sites/%s/sharing-buttons/new -> $blog_id
+ // POST /sites/%s/sharing-buttons/%s -> $blog_id, $button_id
+ public function callback( $path = '', $blog_id = 0, $button_id = 0 ) {
+ $new = $this->api->ends_with( $path, '/new' );
+ $input = $this->input();
+
+ // Validate request
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $continue = $this->setup();
+ if ( is_wp_error( $continue ) ) {
+ return $continue;
+ }
+
+ $validation_error = $this->validate_button_input( $input, $new );
+ if ( is_wp_error( $validation_error ) ) {
+ return $validation_error;
+ }
+
+ // Update or create button
+ if ( $new ) {
+ $updated_service = $this->create_custom_button( $input );
+ } else {
+ $updated_service = $this->update_button( $button_id, $input );
+ }
+
+ if ( false === $updated_service ) {
+ return new WP_Error( 'invalid_request', sprintf( 'The sharing button was not %s', $new ? 'created' : 'updated' ), 400 );
+ } else if ( is_wp_error( $updated_service ) ) {
+ return $updated_service;
+ } else {
+ return $this->format_sharing_button( $updated_service );
+ }
+ }
+
+}
+
+class WPCOM_JSON_API_Delete_Sharing_Button_Endpoint extends WPCOM_JSON_API_Sharing_Button_Endpoint {
+
+ // POST /sites/%s/sharing-buttons/%s/delete -> $blog_id, $button_id
+ public function callback( $path = '', $blog_id = 0, $button_id = 0 ) {
+ // Validate request
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ $continue = $this->setup();
+ if ( is_wp_error( $continue ) ) {
+ return $continue;
+ }
+
+ // Find existing button
+ $all_buttons = $this->sharing_service->get_all_services_blog();
+ if ( ! array_key_exists( $button_id, $all_buttons ) ) {
+ // Button doesn't exist
+ return new WP_Error( 'not_found', 'The specified sharing button was not found', 404 );
+ }
+
+ // Verify button is custom
+ if ( ! is_a( $all_buttons[ $button_id ], 'Share_Custom' ) ) {
+ return new WP_error( 'invalid_request', 'Only custom sharing buttons can be deleted', 400 );
+ }
+
+ $success = $this->sharing_service->delete_service( $button_id );
+ return array(
+ 'ID' => $button_id,
+ 'success' => $success
+ );
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
new file mode 100644
index 00000000..1918182b
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
@@ -0,0 +1,349 @@
+<?php
+
+class WPCOM_JSON_API_Site_Settings_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ public static $site_format = array(
+ 'ID' => '(int) Site ID',
+ 'name' => '(string) Title of site',
+ 'description' => '(string) Tagline or description of site',
+ 'URL' => '(string) Full URL to the site',
+ 'lang' => '(string) Primary language code of the site',
+ 'settings' => '(array) An array of options/settings for the blog. Only viewable by users with post editing rights to the site.',
+ );
+
+ // GET /sites/%s/settings
+ // POST /sites/%s/settings
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $this->load_theme_functions();
+ }
+
+ if ( ! is_user_logged_in() ) {
+ return new WP_Error( 'Unauthorized', 'You must be logged-in to manage settings.', 401 );
+ } else if ( ! current_user_can( 'manage_options' ) ) {
+ return new WP_Error( 'Forbidden', 'You do not have the capability to manage settings for this site.', 403 );
+ }
+
+ if ( 'GET' === $this->api->method ) {
+ do_action( 'wpcom_json_api_objects', 'sites' );
+ return $this->get_settings_response();
+ } else if ( 'POST' === $this->api->method ) {
+ return $this->update_settings();
+ } else {
+ return new WP_Error( 'bad_request', 'An unsupported request method was used.' );
+ }
+
+ }
+
+ /**
+ * Determines whether jetpack_relatedposts is supported
+ *
+ * @return (bool)
+ */
+ public function jetpack_relatedposts_supported() {
+ $wpcom_related_posts_theme_blacklist = array(
+ 'Expound',
+ 'Traveler',
+ 'Opti',
+ 'Currents',
+ );
+ return ( ! in_array( wp_get_theme()->get( 'Name' ), $wpcom_related_posts_theme_blacklist ) );
+ }
+
+ /**
+ * Returns category details
+ *
+ * @return (array)
+ */
+ public function get_category_details( $category ) {
+ return array(
+ 'value' => $category->term_id,
+ 'name' => $category->name
+ );
+ }
+
+ /**
+ * Collects the necessary information to return for a get settings response.
+ *
+ * @return (array)
+ */
+ public function get_settings_response() {
+
+ $response_format = self::$site_format;
+ $blog_id = (int) $this->api->get_blog_id_for_output();
+ $is_jetpack = true === apply_filters( 'is_jetpack_site', false, $blog_id );
+
+ foreach ( array_keys( $response_format ) as $key ) {
+ switch ( $key ) {
+ case 'ID' :
+ $response[$key] = $blog_id;
+ break;
+ case 'name' :
+ $response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
+ break;
+ case 'description' :
+ $response[$key] = (string) htmlspecialchars_decode( get_bloginfo( 'description' ), ENT_QUOTES );
+ break;
+ case 'URL' :
+ $response[$key] = (string) home_url();
+ break;
+ case 'lang' :
+ $response[$key] = (string) get_bloginfo( 'language' );
+ break;
+ case 'settings':
+
+ $jetpack_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
+
+ if ( method_exists( 'Jetpack', 'is_module_active' ) ) {
+ $jetpack_relatedposts_options[ 'enabled' ] = Jetpack::is_module_active( 'related-posts' );
+ }
+
+ // array_values() is necessary to ensure the array starts at index 0.
+ $post_categories = array_values(
+ array_map(
+ array( $this, 'get_category_details' ),
+ get_categories( array( 'hide_empty' => false ) )
+ )
+ );
+
+ $response[$key] = array(
+
+ // also exists as "options"
+ 'admin_url' => get_admin_url(),
+ 'default_ping_status' => (bool) ( 'closed' != get_option( 'default_ping_status' ) ),
+ 'default_comment_status' => (bool) ( 'closed' != get_option( 'default_comment_status' ) ),
+
+ // new stuff starts here
+ 'blog_public' => (int) get_option( 'blog_public' ),
+ 'jetpack_sync_non_public_post_stati' => (bool) Jetpack_Options::get_option( 'sync_non_public_post_stati' ),
+ 'jetpack_relatedposts_allowed' => (bool) $this->jetpack_relatedposts_supported(),
+ 'jetpack_relatedposts_enabled' => (bool) $jetpack_relatedposts_options[ 'enabled' ],
+ 'jetpack_relatedposts_show_headline' => (bool) $jetpack_relatedposts_options[ 'show_headline' ],
+ 'jetpack_relatedposts_show_thumbnails' => (bool) $jetpack_relatedposts_options[ 'show_thumbnails' ],
+ 'default_category' => get_option('default_category'),
+ 'post_categories' => (array) $post_categories,
+ 'default_post_format' => get_option( 'default_post_format' ),
+ 'default_pingback_flag' => (bool) get_option( 'default_pingback_flag' ),
+ 'require_name_email' => (bool) get_option( 'require_name_email' ),
+ 'comment_registration' => (bool) get_option( 'comment_registration' ),
+ 'close_comments_for_old_posts' => (bool) get_option( 'close_comments_for_old_posts' ),
+ 'close_comments_days_old' => (int) get_option( 'close_comments_days_old' ),
+ 'thread_comments' => (bool) get_option( 'thread_comments' ),
+ 'thread_comments_depth' => (int) get_option( 'thread_comments_depth' ),
+ 'page_comments' => (bool) get_option( 'page_comments' ),
+ 'comments_per_page' => (int) get_option( 'comments_per_page' ),
+ 'default_comments_page' => get_option( 'default_comments_page' ),
+ 'comment_order' => get_option( 'comment_order' ),
+ 'comments_notify' => (bool) get_option( 'comments_notify' ),
+ 'moderation_notify' => (bool) get_option( 'moderation_notify' ),
+ 'social_notifications_like' => ( "on" == get_option( 'social_notifications_like' ) ),
+ 'social_notifications_reblog' => ( "on" == get_option( 'social_notifications_reblog' ) ),
+ 'social_notifications_subscribe' => ( "on" == get_option( 'social_notifications_subscribe' ) ),
+ 'comment_moderation' => (bool) get_option( 'comment_moderation' ),
+ 'comment_whitelist' => (bool) get_option( 'comment_whitelist' ),
+ 'comment_max_links' => (int) get_option( 'comment_max_links' ),
+ 'moderation_keys' => get_option( 'moderation_keys' ),
+ 'blacklist_keys' => get_option( 'blacklist_keys' ),
+ 'lang_id' => get_option( 'lang_id' ),
+ 'wga' => get_option( 'wga' ),
+ 'disabled_likes' => (bool) get_option( 'disabled_likes' ),
+ 'disabled_reblogs' => (bool) get_option( 'disabled_reblogs' ),
+ 'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
+ 'twitter_via' => (string) get_option( 'twitter_via' ),
+ 'jetpack-twitter-cards-site-tag' => (string) get_option( 'jetpack-twitter-cards-site-tag' ),
+ );
+
+ if ( class_exists( 'Sharing_Service' ) ) {
+ $ss = new Sharing_Service();
+ $sharing = $ss->get_global_options();
+ $response[ $key ]['sharing_button_style'] = (string) $sharing['button_style'];
+ $response[ $key ]['sharing_label'] = (string) $sharing['sharing_label'];
+ $response[ $key ]['sharing_show'] = (array) $sharing['show'];
+ $response[ $key ]['sharing_open_links'] = (string) $sharing['open_links'];
+ }
+
+ if ( function_exists( 'jetpack_protect_format_whitelist' ) ) {
+ $response[ $key ]['jetpack_protect_whitelist'] = jetpack_protect_format_whitelist();
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) )
+ unset( $response[$key] );
+ break;
+ }
+ }
+
+ return $response;
+
+ }
+
+ /**
+ * Updates site settings for authorized users
+ *
+ * @return (array)
+ */
+ public function update_settings() {
+
+ // $this->input() retrieves posted arguments whitelisted and casted to the $request_format
+ // specs that get passed in when this class is instantiated
+ $input = $this->input();
+
+ $jetpack_relatedposts_options = array();
+ $sharing_options = array();
+ $updated = array();
+
+ foreach ( $input as $key => $value ) {
+
+ if ( ! is_array( $value ) ) {
+ $value = trim( $value );
+ }
+ $value = wp_unslash( $value );
+
+ switch ( $key ) {
+
+ case 'default_ping_status':
+ case 'default_comment_status':
+ // settings are stored as closed|open
+ $coerce_value = ( $value ) ? 'open' : 'closed';
+ if ( update_option( $key, $coerce_value ) ) {
+ $updated[ $key ] = $value;
+ };
+ break;
+ case 'jetpack_protect_whitelist':
+ if ( function_exists( 'jetpack_protect_save_whitelist' ) ) {
+ $result = jetpack_protect_save_whitelist( $value );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ $updated[ $key ] = jetpack_protect_format_whitelist();
+ }
+ break;
+ case 'jetpack_sync_non_public_post_stati':
+ Jetpack_Options::update_option( 'sync_non_public_post_stati', $value );
+ break;
+ case 'jetpack_relatedposts_enabled':
+ case 'jetpack_relatedposts_show_thumbnails':
+ case 'jetpack_relatedposts_show_headline':
+ if ( ! $this->jetpack_relatedposts_supported() ) {
+ break;
+ }
+ if ( 'jetpack_relatedposts_enabled' === $key && method_exists( 'Jetpack', 'is_module_active' ) && $this->jetpack_relatedposts_supported() ) {
+ $before_action = Jetpack::is_module_active('related-posts');
+ if ( $value ) {
+ Jetpack::activate_module( 'related-posts', false, false );
+ } else {
+ Jetpack::deactivate_module( 'related-posts' );
+ }
+ $after_action = Jetpack::is_module_active('related-posts');
+ if ( $after_action == $before_action ) {
+ break;
+ }
+ }
+ $just_the_key = substr( $key, 21 );
+ $jetpack_relatedposts_options[ $just_the_key ] = $value;
+ break;
+
+ case 'social_notifications_like':
+ case 'social_notifications_reblog':
+ case 'social_notifications_subscribe':
+ // settings are stored as on|off
+ $coerce_value = ( $value ) ? 'on' : 'off';
+ if ( update_option( $key, $coerce_value ) ) {
+ $updated[ $key ] = $value;
+ }
+ break;
+ case 'wga':
+ if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^UA-[\d-]+$/i', $value['code'] ) ) {
+ return new WP_Error( 'invalid_code', 'Invalid UA ID' );
+ }
+ $wga = get_option( 'wga', array() );
+ $wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics
+ if ( update_option( 'wga', $wga ) ) {
+ $updated[ $key ] = $value;
+ }
+
+ $enabled_or_disabled = $wga['code'] ? 'enabled' : 'disabled';
+ bump_stats_extras( 'google-analytics', $enabled_or_disabled );
+
+ $business_plugins = WPCOM_Business_Plugins::instance();
+ $business_plugins->activate_plugin( 'wp-google-analytics' );
+ break;
+
+ case 'jetpack_comment_likes_enabled':
+ // settings are stored as 1|0
+ $coerce_value = (int) $value;
+ if ( update_option( $key, $coerce_value ) ) {
+ $updated[ $key ] = $value;
+ }
+ break;
+
+ // Sharing options
+ case 'sharing_button_style':
+ case 'sharing_show':
+ case 'sharing_open_links':
+ $sharing_options[ preg_replace( '/^sharing_/', '', $key ) ] = $value;
+ break;
+ case 'sharing_label':
+ $sharing_options[ $key ] = $value;
+ break;
+
+ // no worries, we've already whitelisted and casted arguments above
+ default:
+ if ( update_option( $key, $value ) ) {
+ $updated[ $key ] = $value;
+ }
+
+ }
+ }
+
+ if ( count( $jetpack_relatedposts_options ) ) {
+ // track new jetpack_relatedposts options against old
+ $old_relatedposts_options = Jetpack_Options::get_option( 'relatedposts' );
+ if ( Jetpack_Options::update_option( 'relatedposts', $jetpack_relatedposts_options ) ) {
+ foreach( $jetpack_relatedposts_options as $key => $value ) {
+ if ( $value !== $old_relatedposts_options[ $key ] ) {
+ $updated[ 'jetpack_relatedposts_' . $key ] = $value;
+ }
+ }
+ }
+ }
+
+ if ( ! empty( $sharing_options ) && class_exists( 'Sharing_Service' ) ) {
+ $ss = new Sharing_Service();
+
+ // Merge current values with updated, since Sharing_Service expects
+ // all values to be included when updating
+ $current_sharing_options = $ss->get_global_options();
+ foreach ( $current_sharing_options as $key => $val ) {
+ if ( ! isset( $sharing_options[ $key ] ) ) {
+ $sharing_options[ $key ] = $val;
+ }
+ }
+
+ $updated_social_options = $ss->set_global_options( $sharing_options );
+
+ if ( isset( $input['sharing_button_style'] ) ) {
+ $updated['sharing_button_style'] = (string) $updated_social_options['button_style'];
+ }
+ if ( isset( $input['sharing_label'] ) ) {
+ // Sharing_Service won't report label as updated if set to default
+ $updated['sharing_label'] = (string) $sharing_options['sharing_label'];
+ }
+ if ( isset( $input['sharing_show'] ) ) {
+ $updated['sharing_show'] = (array) $updated_social_options['show'];
+ }
+ if ( isset( $input['sharing_open_links'] ) ) {
+ $updated['sharing_open_links'] = (string) $updated_social_options['open_links'];
+ }
+ }
+
+ return array(
+ 'updated' => $updated
+ );
+
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-taxonomy-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-taxonomy-endpoint.php
new file mode 100644
index 00000000..483c0b70
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-taxonomy-endpoint.php
@@ -0,0 +1,29 @@
+<?php
+abstract class WPCOM_JSON_API_Taxonomy_Endpoint extends WPCOM_JSON_API_Endpoint {
+ var $category_object_format = array(
+ 'ID' => '(int) The category ID.',
+ 'name' => "(string) The name of the category.",
+ 'slug' => "(string) The slug of the category.",
+ 'description' => '(string) The description of the category.',
+ 'post_count' => "(int) The number of posts using this category.",
+ 'parent' => "(int) The parent ID for the category.",
+ 'meta' => '(object) Meta data',
+ );
+
+ var $tag_object_format = array(
+ 'ID' => '(int) The tag ID.',
+ 'name' => "(string) The name of the tag.",
+ 'slug' => "(string) The slug of the tag.",
+ 'description' => '(string) The description of the tag.',
+ 'post_count' => "(int) The number of posts using this t.",
+ 'meta' => '(object) Meta data',
+ );
+
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( preg_match( '#/tags/#i', $this->path ) )
+ $this->response_format =& $this->tag_object_format;
+ else
+ $this->response_format =& $this->category_object_format;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-comment-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-comment-endpoint.php
new file mode 100644
index 00000000..9c96d422
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-comment-endpoint.php
@@ -0,0 +1,256 @@
+<?php
+class WPCOM_JSON_API_Update_Comment_Endpoint extends WPCOM_JSON_API_Comment_Endpoint {
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( $this->api->ends_with( $this->path, '/delete' ) ) {
+ $this->comment_object_format['status']['deleted'] = 'The comment has been deleted permanently.';
+ }
+ }
+
+ // /sites/%s/posts/%d/replies/new -> $blog_id, $post_id
+ // /sites/%s/comments/%d/replies/new -> $blog_id, $comment_id
+ // /sites/%s/comments/%d -> $blog_id, $comment_id
+ // /sites/%s/comments/%d/delete -> $blog_id, $comment_id
+ function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
+ if ( $this->api->ends_with( $path, '/new' ) )
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ), false );
+ else
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_comment( $path, $blog_id, $object_id );
+ } elseif ( $this->api->ends_with( $path, '/new' ) ) {
+ if ( false !== strpos( $path, '/posts/' ) ) {
+ return $this->new_comment( $path, $blog_id, $object_id, 0 );
+ } else {
+ return $this->new_comment( $path, $blog_id, 0, $object_id );
+ }
+ }
+
+ return $this->update_comment( $path, $blog_id, $object_id );
+ }
+
+ // /sites/%s/posts/%d/replies/new -> $blog_id, $post_id
+ // /sites/%s/comments/%d/replies/new -> $blog_id, $comment_id
+ function new_comment( $path, $blog_id, $post_id, $comment_parent_id ) {
+ if ( !$post_id ) {
+ $comment_parent = get_comment( $comment_parent_id );
+ if ( !$comment_parent_id || !$comment_parent || is_wp_error( $comment_parent ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ $post_id = $comment_parent->comment_post_ID;
+ }
+
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( -1 == get_option( 'blog_public' ) && ! apply_filters( 'wpcom_json_api_user_is_member_of_blog', is_user_member_of_blog() ) && ! is_super_admin() ) {
+ return new WP_Error( 'unauthorized', 'User cannot create comments', 403 );
+ }
+
+ if ( !comments_open( $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'Comments on this post are closed', 403 );
+ }
+
+ $can_view = $this->user_can_view_post( $post->ID );
+ if ( !$can_view || is_wp_error( $can_view ) ) {
+ return $can_view;
+ }
+
+ $post_status = get_post_status_object( get_post_status( $post ) );
+ if ( !$post_status->public && !$post_status->private ) {
+ return new WP_Error( 'unauthorized', 'Comments on drafts are not allowed', 403 );
+ }
+
+ $args = $this->query_args();
+ $input = $this->input();
+ if ( !is_array( $input ) || !$input || !strlen( $input['content'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $user = wp_get_current_user();
+ if ( !$user || is_wp_error( $user ) || !$user->ID ) {
+ $auth_required = false;
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ $auth_required = true;
+ } elseif ( isset( $this->api->token_details['user'] ) ) {
+ $user = (object) $this->api->token_details['user'];
+ foreach ( array( 'display_name', 'user_email', 'user_url' ) as $user_datum ) {
+ if ( !isset( $user->$user_datum ) ) {
+ $auth_required = true;
+ }
+ }
+ if ( !isset( $user->ID ) ) {
+ $user->ID = 0;
+ }
+ } else {
+ $auth_required = true;
+ }
+
+ if ( $auth_required ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to comment.', 403 );
+ }
+ }
+
+ $insert = array(
+ 'comment_post_ID' => $post->ID,
+ 'user_ID' => $user->ID,
+ 'comment_author' => $user->display_name,
+ 'comment_author_email' => $user->user_email,
+ 'comment_author_url' => $user->user_url,
+ 'comment_content' => $input['content'],
+ 'comment_parent' => $comment_parent_id,
+ 'comment_type' => '',
+ );
+
+ if ( $comment_parent_id ) {
+ if ( $comment_parent->comment_approved === '0' && current_user_can( 'edit_comment', $comment_parent->comment_ID ) ) {
+ wp_set_comment_status( $comment_parent->comment_ID, 'approve' );
+ }
+ }
+
+ $this->api->trap_wp_die( 'comment_failure' );
+ $comment_id = wp_new_comment( add_magic_quotes( $insert ) );
+ $this->api->trap_wp_die( null );
+
+ $return = $this->get_comment( $comment_id, $args['context'] );
+ if ( !$return ) {
+ return new WP_Error( 400, __( 'Comment cache problem?', 'jetpack' ) );
+ }
+ if ( is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+ return $return;
+ }
+
+ // /sites/%s/comments/%d -> $blog_id, $comment_id
+ function update_comment( $path, $blog_id, $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit comment', 403 );
+ }
+
+ $args = $this->query_args();
+ $input = $this->input( false );
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $update = array();
+ foreach ( $input as $key => $value ) {
+ $update["comment_$key"] = $value;
+ }
+
+ $comment_status = wp_get_comment_status( $comment->comment_ID );
+ if ( $comment_status !== $update['status'] && !current_user_can( 'moderate_comments' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot moderate comments', 403 );
+ }
+
+ if ( isset( $update['comment_status'] ) ) {
+ if ( count( $update ) === 1 ) {
+ // We are only here to update the comment status so let's respond ASAP
+ add_action( 'wp_set_comment_status', array( $this, 'output_comment' ), 0, 1 );
+ }
+ switch ( $update['comment_status'] ) {
+ case 'approved' :
+ if ( 'approve' !== $comment_status ) {
+ wp_set_comment_status( $comment->comment_ID, 'approve' );
+ }
+ break;
+ case 'unapproved' :
+ if ( 'hold' !== $comment_status ) {
+ wp_set_comment_status( $comment->comment_ID, 'hold' );
+ }
+ break;
+ case 'spam' :
+ if ( 'spam' !== $comment_status ) {
+ wp_spam_comment( $comment->comment_ID );
+ }
+ break;
+ case 'unspam' :
+ if ( 'spam' === $comment_status ) {
+ wp_unspam_comment( $comment->comment_ID );
+ }
+ break;
+ case 'trash' :
+ if ( ! EMPTY_TRASH_DAYS ) {
+ return new WP_Error( 'trash_disabled', 'Cannot trash comment', 403 );
+ }
+
+ if ( 'trash' !== $comment_status ) {
+ wp_trash_comment( $comment_id );
+ }
+ break;
+ case 'untrash' :
+ if ( 'trash' === $comment_status ) {
+ wp_untrash_comment( $comment->comment_ID );
+ }
+ break;
+ default:
+ $update['comment_approved'] = 1;
+ break;
+ }
+ unset( $update['comment_status'] );
+ }
+
+ if ( ! empty( $update ) ) {
+ $update['comment_ID'] = $comment->comment_ID;
+ wp_update_comment( add_magic_quotes( $update ) );
+ }
+
+ $return = $this->get_comment( $comment->comment_ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+ return $return;
+ }
+
+ // /sites/%s/comments/%d/delete -> $blog_id, $comment_id
+ function delete_comment( $path, $blog_id, $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( !$comment || is_wp_error( $comment ) ) {
+ return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
+ }
+
+ if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) { // [sic] There is no delete_comment cap
+ return new WP_Error( 'unauthorized', 'User cannot delete comment', 403 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_comment( $comment->comment_ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'comments' );
+
+ wp_delete_comment( $comment->comment_ID );
+ $status = wp_get_comment_status( $comment->comment_ID );
+ if ( false === $status ) {
+ $return['status'] = 'deleted';
+ return $return;
+ }
+
+ return $this->get_comment( $comment->comment_ID, $args['context'] );
+ }
+
+ function output_comment( $comment_id ) {
+ $args = $this->query_args();
+ $output = $this->get_comment( $comment_id, $args['context'] );
+ $this->api->output_early( 200, $output );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-endpoint.php
new file mode 100644
index 00000000..00e170ea
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-endpoint.php
@@ -0,0 +1,39 @@
+<?php
+
+class WPCOM_JSON_API_Update_Media_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( !current_user_can( 'upload_files', $media_id ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ $item = $this->get_media_item( $media_id );
+
+ if ( is_wp_error( $item ) ) {
+ return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
+ }
+
+ $input = $this->input( true );
+ $insert = array();
+
+ if ( !empty( $input['title'] ) ) {
+ $insert['post_title'] = $input['title'];
+ }
+
+ if ( !empty( $input['caption'] ) )
+ $insert['post_excerpt'] = $input['caption'];
+
+ if ( !empty( $input['description'] ) )
+ $insert['post_content'] = $input['description'];
+
+ $insert['ID'] = $media_id;
+ wp_update_post( (object) $insert );
+
+ $item = $this->get_media_item( $media_id );
+ return $item;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-v1-1-endpoint.php
new file mode 100644
index 00000000..8a7c8c74
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-media-v1-1-endpoint.php
@@ -0,0 +1,77 @@
+<?php
+
+class WPCOM_JSON_API_Update_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'upload_files', $media_id ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
+ }
+
+ $item = $this->get_media_item_v1_1( $media_id );
+
+ if ( is_wp_error( $item ) ) {
+ return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
+ }
+
+ $input = $this->input( true );
+ $insert = array();
+
+ if ( ! empty( $input['title'] ) ) {
+ $insert['post_title'] = $input['title'];
+ }
+
+ if ( ! empty( $input['caption'] ) ) {
+ $insert['post_excerpt'] = $input['caption'];
+ }
+
+ if ( ! empty( $input['description'] ) ) {
+ $insert['post_content'] = $input['description'];
+ }
+
+ if ( ! empty( $input['parent_id'] ) ) {
+ $insert['post_parent'] = $input['parent_id'];
+ }
+
+ if ( ! empty( $input['alt'] ) ) {
+ $alt = wp_strip_all_tags( $input['alt'], true );
+ update_post_meta( $media_id, '_wp_attachment_image_alt', $alt );
+ }
+
+ // audio only artist/album info
+ if ( 0 === strpos( $item->mime_type, 'audio/' ) ) {
+ $changed = false;
+ $id3data = wp_get_attachment_metadata( $media_id );
+
+ if ( ! is_array( $id3data ) ) {
+ $changed = true;
+ $id3data = array();
+ }
+
+ $id3_keys = array(
+ 'artist' => __( 'Artist', 'jetpack' ),
+ 'album' => __( 'Album', 'jetpack' )
+ );
+
+ foreach ( $id3_keys as $key => $label ) {
+ if ( ! empty( $input[ $key ] ) ) {
+ $changed = true;
+ $id3data[ $key ] = wp_strip_all_tags( $input[ $key ], true );
+ }
+ }
+
+ if ( $changed ) {
+ wp_update_attachment_metadata( $media_id, $id3data );
+ }
+ }
+
+ $insert['ID'] = $media_id;
+ wp_update_post( (object) $insert );
+
+ $item = $this->get_media_item_v1_1( $media_id );
+ return $item;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-endpoint.php
new file mode 100644
index 00000000..7e5f9dd0
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-endpoint.php
@@ -0,0 +1,659 @@
+<?php
+class WPCOM_JSON_API_Update_Post_Endpoint extends WPCOM_JSON_API_Post_Endpoint {
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( $this->api->ends_with( $this->path, '/delete' ) ) {
+ $this->post_object_format['status']['deleted'] = 'The post has been deleted permanently.';
+ }
+ }
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ // /sites/%s/posts/%d/delete -> $blog_id, $post_id
+ // /sites/%s/posts/%d/restore -> $blog_id, $post_id
+ function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_post( $path, $blog_id, $post_id );
+ } elseif ( $this->api->ends_with( $path, '/restore' ) ) {
+ return $this->restore_post( $path, $blog_id, $post_id );
+ } else {
+ return $this->write_post( $path, $blog_id, $post_id );
+ }
+ }
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ function write_post( $path, $blog_id, $post_id ) {
+ $new = $this->api->ends_with( $path, '/new' );
+ $args = $this->query_args();
+
+ // unhook publicize, it's hooked again later -- without this, skipping services is impossible
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ remove_action( 'save_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ), 100, 2 );
+ add_action( 'rest_api_inserted_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ) );
+ }
+
+ if ( $new ) {
+ $input = $this->input( true );
+
+ if ( 'revision' === $input['type'] ) {
+ if ( ! isset( $input['parent'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+ $input['status'] = 'inherit'; // force inherit for revision type
+ $input['slug'] = $input['parent'] . '-autosave-v1';
+ }
+ elseif ( !isset( $input['title'] ) && !isset( $input['content'] ) && !isset( $input['excerpt'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ // default to post
+ if ( empty( $input['type'] ) )
+ $input['type'] = 'post';
+
+ $post_type = get_post_type_object( $input['type'] );
+
+ if ( ! $this->is_post_type_allowed( $input['type'] ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ if ( ! empty( $input['author'] ) ) {
+ $author_id = $this->parse_and_set_author( $input['author'], $input['type'] );
+ unset( $input['author'] );
+ if ( is_wp_error( $author_id ) )
+ return $author_id;
+ }
+
+ if ( 'publish' === $input['status'] ) {
+ if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
+ if ( current_user_can( $post_type->cap->edit_posts ) ) {
+ $input['status'] = 'pending';
+ } else {
+ return new WP_Error( 'unauthorized', 'User cannot publish posts', 403 );
+ }
+ }
+ } else {
+ if ( !current_user_can( $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit posts', 403 );
+ }
+ }
+ } else {
+ $input = $this->input( false );
+
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $post = get_post( $post_id );
+ $_post_type = ( ! empty( $input['type'] ) ) ? $input['type'] : $post->post_type;
+ $post_type = get_post_type_object( $_post_type );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+
+ if ( ! empty( $input['author'] ) ) {
+ $author_id = $this->parse_and_set_author( $input['author'], $_post_type );
+ unset( $input['author'] );
+ if ( is_wp_error( $author_id ) )
+ return $author_id;
+ }
+
+ if ( 'publish' === $input['status'] && 'publish' !== $post->post_status && !current_user_can( 'publish_post', $post->ID ) ) {
+ $input['status'] = 'pending';
+ }
+ $last_status = $post->post_status;
+ $new_status = $input['status'];
+ }
+
+ // Fix for https://iorequests.wordpress.com/2014/08/13/scheduled-posts-made-in-the/
+ // See: https://a8c.slack.com/archives/io/p1408047082000273
+ // If date was set, $this->input will set date_gmt, date still needs to be adjusted for the blog's offset
+ if ( isset( $input['date_gmt'] ) ) {
+ $gmt_offset = get_option( 'gmt_offset' );
+ $time_with_offset = strtotime( $input['date_gmt'] ) + $gmt_offset * HOUR_IN_SECONDS;
+ $input['date'] = date( 'Y-m-d H:i:s', $time_with_offset );
+ }
+
+ if ( ! empty( $author_id ) && get_current_user_id() != $author_id ) {
+ if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
+ return new WP_Error( 'unauthorized', "User is not allowed to publish others' posts.", 403 );
+ } elseif ( ! user_can( $author_id, $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'Assigned author cannot publish post.', 403 );
+ }
+ }
+
+ if ( !is_post_type_hierarchical( $post_type->name ) && 'revision' !== $post_type->name ) {
+ unset( $input['parent'] );
+ }
+
+ $tax_input = array();
+
+ foreach ( array( 'categories' => 'category', 'tags' => 'post_tag' ) as $key => $taxonomy ) {
+ if ( ! isset( $input[ $key ] ) ) {
+ continue;
+ }
+
+ $tax_input[ $taxonomy ] = array();
+
+ $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
+
+ if ( is_array( $input[$key] ) ) {
+ $terms = $input[$key];
+ } else {
+ $terms = explode( ',', $input[$key] );
+ }
+
+ foreach ( $terms as $term ) {
+ /**
+ * `curl --data 'category[]=123'` should be interpreted as a category ID,
+ * not a category whose name is '123'.
+ *
+ * Consequence: To add a category/tag whose name is '123', the client must
+ * first look up its ID.
+ */
+ if ( ctype_digit( $term ) ) {
+ $term = (int) $term;
+ }
+
+ $term_info = term_exists( $term, $taxonomy );
+
+ if ( ! $term_info ) {
+ // A term ID that doesn't already exist. Ignore it: we don't know what name to give it.
+ if ( is_int( $term ) ){
+ continue;
+ }
+ // only add a new tag/cat if the user has access to
+ $tax = get_taxonomy( $taxonomy );
+ if ( !current_user_can( $tax->cap->edit_terms ) ) {
+ continue;
+ }
+
+ $term_info = wp_insert_term( $term, $taxonomy );
+ }
+
+ if ( ! is_wp_error( $term_info ) ) {
+ if ( $is_hierarchical ) {
+ // Categories must be added by ID
+ $tax_input[$taxonomy][] = (int) $term_info['term_id'];
+ } else {
+ // Tags must be added by name
+ if ( is_int( $term ) ) {
+ $term = get_term( $term, $taxonomy );
+ $tax_input[$taxonomy][] = $term->name;
+ } else {
+ $tax_input[$taxonomy][] = $term;
+ }
+ }
+ }
+ }
+ }
+
+ if ( isset( $input['categories'] ) && empty( $tax_input['category'] ) && 'revision' !== $post_type->name ) {
+ $tax_input['category'][] = get_option( 'default_category' );
+ }
+
+ unset( $input['tags'], $input['categories'] );
+
+ $insert = array();
+
+ if ( !empty( $input['slug'] ) ) {
+ $insert['post_name'] = $input['slug'];
+ unset( $input['slug'] );
+ }
+
+ if ( true === $input['comments_open'] )
+ $insert['comment_status'] = 'open';
+ else if ( false === $input['comments_open'] )
+ $insert['comment_status'] = 'closed';
+
+ if ( true === $input['pings_open'] )
+ $insert['ping_status'] = 'open';
+ else if ( false === $input['pings_open'] )
+ $insert['ping_status'] = 'closed';
+
+ unset( $input['comments_open'], $input['pings_open'] );
+
+ $insert['menu_order'] = $input['menu_order'];
+ unset( $input['menu_order'] );
+
+ $publicize = $input['publicize'];
+ $publicize_custom_message = $input['publicize_message'];
+ unset( $input['publicize'], $input['publicize_message'] );
+
+ if ( isset( $input['featured_image'] ) ) {
+ $featured_image = trim( $input['featured_image'] );
+ $delete_featured_image = empty( $featured_image );
+ $featured_image = $input['featured_image'];
+ unset( $input['featured_image'] );
+ }
+
+ $metadata = $input['metadata'];
+ unset( $input['metadata'] );
+
+ $likes = $input['likes_enabled'];
+ $sharing = $input['sharing_enabled'];
+
+ unset( $input['likes_enabled'] );
+ unset( $input['sharing_enabled'] );
+
+ $sticky = $input['sticky'];
+ unset( $input['sticky'] );
+
+ foreach ( $input as $key => $value ) {
+ $insert["post_$key"] = $value;
+ }
+
+ if ( ! empty( $author_id ) ) {
+ $insert['post_author'] = absint( $author_id );
+ }
+
+ if ( ! empty( $tax_input ) ) {
+ $insert['tax_input'] = $tax_input;
+ }
+
+ $has_media = isset( $input['media'] ) && $input['media'] ? count( $input['media'] ) : false;
+ $has_media_by_url = isset( $input['media_urls'] ) && $input['media_urls'] ? count( $input['media_urls'] ) : false;
+
+ if ( $new ) {
+
+ if ( false === strpos( $input['content'], '[gallery' ) && ( $has_media || $has_media_by_url ) ) {
+ switch ( ( $has_media + $has_media_by_url ) ) {
+ case 0 :
+ // No images - do nothing.
+ break;
+ case 1 :
+ // 1 image - make it big
+ $insert['post_content'] = $input['content'] = "[gallery size=full columns=1]\n\n" . $input['content'];
+ break;
+ default :
+ // Several images - 3 column gallery
+ $insert['post_content'] = $input['content'] = "[gallery]\n\n" . $input['content'];
+ break;
+ }
+ }
+
+ $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
+ } else {
+ $insert['ID'] = $post->ID;
+
+ // wp_update_post ignores date unless edit_date is set
+ // See: http://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts
+ // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302
+ if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
+ $insert['edit_date'] = true;
+ }
+
+ $post_id = wp_update_post( (object) $insert );
+
+ }
+
+ if ( !$post_id || is_wp_error( $post_id ) ) {
+ return $post_id;
+ }
+
+ // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint)
+ $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( is_wp_error( $post_check ) ) {
+ return $post_check;
+ }
+
+ if ( $has_media ) {
+ $this->api->trap_wp_die( 'upload_error' );
+ foreach ( $input['media'] as $media_item ) {
+ $_FILES['.api.media.item.'] = $media_item;
+ // check for WP_Error if we ever actually need $media_id
+ $media_id = media_handle_upload( '.api.media.item.', $post_id );
+ }
+ $this->api->trap_wp_die( null );
+
+ unset( $_FILES['.api.media.item.'] );
+ }
+
+ if ( $has_media_by_url ) {
+ foreach ( $input['media_urls'] as $url ) {
+ $this->handle_media_sideload( $url, $post_id );
+ }
+ }
+
+ // Set like status for the post
+ $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
+ if ( $new ) {
+ if ( $sitewide_likes_enabled ) {
+ if ( false === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ } else {
+ if ( $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ }
+ } else {
+ if ( isset( $likes ) ) {
+ if ( $sitewide_likes_enabled ) {
+ if ( false === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ } else {
+ if ( true === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ }
+ }
+ }
+
+ // Set sharing status of the post
+ if ( $new ) {
+ $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
+ if ( false === $sharing_enabled ) {
+ update_post_meta( $post_id, 'sharing_disabled', 1 );
+ }
+ }
+ else {
+ if ( isset( $sharing ) && true === $sharing ) {
+ delete_post_meta( $post_id, 'sharing_disabled' );
+ } else if ( isset( $sharing ) && false == $sharing ) {
+ update_post_meta( $post_id, 'sharing_disabled', 1 );
+ }
+ }
+
+ if ( true === $sticky ) {
+ stick_post( $post_id );
+ } else {
+ unstick_post( $post_id );
+ }
+
+ // WPCOM Specific (Jetpack's will get bumped elsewhere
+ // Tracks how many posts are published and sets meta so we can track some other cool stats (like likes & comments on posts published)
+ if ( ( $new && 'publish' == $input['status'] ) || ( !$new && isset( $last_status ) && 'publish' != $last_status && isset( $new_status ) && 'publish' == $new_status ) ) {
+ if ( function_exists( 'bump_stats_extras' ) ) {
+ bump_stats_extras( 'api-insights-posts', $this->api->token_details['client_id'] );
+ update_post_meta( $post_id, '_rest_api_published', 1 );
+ update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
+ }
+ }
+
+
+ // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
+ // to instead flag the ones we don't want to be skipped. proceed with said logic.
+ // any posts coming from Path (client ID 25952) should also not publicize
+ if ( $publicize === false || 25952 == $this->api->token_details['client_id'] ) {
+ // No publicize at all, skip all by ID
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
+ $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
+ if ( ! $service_connections ) {
+ continue;
+ }
+ foreach ( $service_connections as $service_connection ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ }
+ }
+ } else if ( is_array( $publicize ) && ( count ( $publicize ) > 0 ) ) {
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ /*
+ * We support both indexed and associative arrays:
+ * * indexed are to pass entire services
+ * * associative are to pass specific connections per service
+ *
+ * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
+ *
+ * EG: array( 'twitter', 'facebook') will only publicize to those, ignoring the other available services
+ * Form data: publicize[]=twitter&publicize[]=facebook
+ * EG: array( 'twitter' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3', 'facebook' => (int) $pub_conn_id_7 ) will publicize to two Twitter accounts, and one Facebook connection, of potentially many.
+ * Form data: publicize[twitter]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
+ * EG: array( 'twitter', 'facebook' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3' ) will publicize to all available Twitter accounts, but only 2 of potentially many Facebook connections
+ * Form data: publicize[]=twitter&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
+ */
+
+ // Delete any stale SKIP value for the service by name. We'll add it back by ID.
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
+
+ // Get the user's connections
+ $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
+
+ // if the user doesn't have any connections for this service, move on
+ if ( ! $service_connections ) {
+ continue;
+ }
+
+ if ( !in_array( $name, $publicize ) && !array_key_exists( $name, $publicize ) ) {
+ // Skip the whole service by adding each connection ID
+ foreach ( $service_connections as $service_connection ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ }
+ } else if ( !empty( $publicize[ $name ] ) ) {
+ // Seems we're being asked to only push to [a] specific connection[s].
+ // Explode the list on commas, which will also support a single passed ID
+ $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
+ // Flag the connections we can't match with the requested list to be skipped.
+ foreach ( $service_connections as $service_connection ) {
+ if ( !in_array( $service_connection->meta['connection_data']->id, $requested_connections ) ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ } else {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
+ }
+ }
+ } else {
+ // delete all SKIP values; it's okay to publish to all connected IDs for this service
+ foreach ( $service_connections as $service_connection ) {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
+ }
+ }
+ }
+ }
+
+ if ( !empty( $publicize_custom_message ) )
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
+
+ set_post_format( $post_id, $insert['post_format'] );
+
+ if ( isset( $featured_image ) ) {
+ $this->parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
+ }
+
+ if ( ! empty( $metadata ) ) {
+ foreach ( (array) $metadata as $meta ) {
+
+ $meta = (object) $meta;
+
+ $existing_meta_item = new stdClass;
+
+ if ( empty( $meta->operation ) )
+ $meta->operation = 'update';
+
+ if ( ! empty( $meta->value ) ) {
+ if ( 'true' == $meta->value )
+ $meta->value = true;
+ if ( 'false' == $meta->value )
+ $meta->value = false;
+ }
+
+ if ( ! empty( $meta->id ) ) {
+ $meta->id = absint( $meta->id );
+ $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
+ }
+
+ $unslashed_meta_key = wp_unslash( $meta->key ); // should match what the final key will be
+ $meta->key = wp_slash( $meta->key );
+ $unslashed_existing_meta_key = wp_unslash( $existing_meta_item->meta_key );
+ $existing_meta_item->meta_key = wp_slash( $existing_meta_item->meta_key );
+
+ // make sure that the meta id passed matches the existing meta key
+ if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
+ $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
+ if ( $meta_by_id->meta_key !== $meta->key ) {
+ continue; // skip this meta
+ }
+ }
+
+ switch ( $meta->operation ) {
+ case 'delete':
+
+ if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
+ delete_metadata_by_mid( 'post', $meta->id );
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
+ delete_post_meta( $post_id, $meta->key, $meta->previous_value );
+ } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
+ delete_post_meta( $post_id, $meta->key );
+ }
+
+ break;
+ case 'add':
+
+ if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
+ continue;
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->value ) && ( current_user_can( 'add_post_meta', $post_id, $unslashed_meta_key ) ) || $this->is_metadata_public( $meta->key ) ) {
+ add_post_meta( $post_id, $meta->key, $meta->value );
+ }
+
+ break;
+ case 'update':
+
+ if ( ! isset( $meta->value ) ) {
+ continue;
+ } elseif ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_existing_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_metadata_by_mid( 'post', $meta->id, $meta->value );
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_post_meta( $post_id, $meta->key,$meta->value, $meta->previous_value );
+ } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_post_meta( $post_id, $meta->key, $meta->value );
+ }
+
+ break;
+ }
+
+ }
+ }
+
+ do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
+
+ $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ if ( 'revision' === $input['type'] ) {
+ $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
+ }
+
+ // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above
+ $return['sticky'] = ( true === $sticky );
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+
+ // /sites/%s/posts/%d/delete -> $blog_id, $post_id
+ function delete_post( $path, $blog_id, $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( ! $this->is_post_type_allowed( $post->post_type ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ if ( !current_user_can( 'delete_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot delete posts', 403 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ wp_delete_post( $post->ID );
+
+ $status = get_post_status( $post->ID );
+ if ( false === $status ) {
+ $return['status'] = 'deleted';
+ return $return;
+ }
+
+ return $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ }
+
+ // /sites/%s/posts/%d/restore -> $blog_id, $post_id
+ function restore_post( $path, $blog_id, $post_id ) {
+ $args = $this->query_args();
+ $post = get_post( $post_id );
+
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'delete_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot restore trashed posts', 403 );
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ wp_untrash_post( $post->ID );
+
+ return $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ }
+
+ private function parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image ) {
+ if ( $delete_featured_image ) {
+ delete_post_thumbnail( $post_id );
+ return;
+ }
+
+ $featured_image = (string) $featured_image;
+
+ // if we got a post ID, we can just set it as the thumbnail
+ if ( ctype_digit( $featured_image ) && 'attachment' == get_post_type( $featured_image ) ) {
+ set_post_thumbnail( $post_id, $featured_image );
+ return $featured_image;
+ }
+
+ $featured_image_id = $this->handle_media_sideload( $featured_image, $post_id );
+
+ if ( empty( $featured_image_id ) || ! is_int( $featured_image_id ) )
+ return false;
+
+ set_post_thumbnail( $post_id, $featured_image_id );
+ return $featured_image_id;
+ }
+
+ private function parse_and_set_author( $author = null, $post_type = 'post' ) {
+ if ( empty( $author ) || ! post_type_supports( $post_type, 'author' ) )
+ return get_current_user_id();
+
+ if ( ctype_digit( $author ) ) {
+ $_user = get_user_by( 'id', $author );
+ if ( ! $_user || is_wp_error( $_user ) )
+ return new WP_Error( 'invalid_author', 'Invalid author provided' );
+
+ return $_user->ID;
+ }
+
+ $_user = get_user_by( 'login', $author );
+ if ( ! $_user || is_wp_error( $_user ) )
+ return new WP_Error( 'invalid_author', 'Invalid author provided' );
+
+ return $_user->ID;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-1-endpoint.php
new file mode 100644
index 00000000..7b4cf2e7
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-1-endpoint.php
@@ -0,0 +1,668 @@
+<?php
+class WPCOM_JSON_API_Update_Post_v1_1_Endpoint extends WPCOM_JSON_API_Post_v1_1_Endpoint {
+ function __construct( $args ) {
+ parent::__construct( $args );
+ if ( $this->api->ends_with( $this->path, '/delete' ) ) {
+ $this->post_object_format['status']['deleted'] = 'The post has been deleted permanently.';
+ }
+ }
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ // /sites/%s/posts/%d/delete -> $blog_id, $post_id
+ // /sites/%s/posts/%d/restore -> $blog_id, $post_id
+ function callback( $path = '', $blog_id = 0, $post_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_post( $path, $blog_id, $post_id );
+ } elseif ( $this->api->ends_with( $path, '/restore' ) ) {
+ return $this->restore_post( $path, $blog_id, $post_id );
+ } else {
+ return $this->write_post( $path, $blog_id, $post_id );
+ }
+ }
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ function write_post( $path, $blog_id, $post_id ) {
+ $new = $this->api->ends_with( $path, '/new' );
+ $args = $this->query_args();
+
+ // unhook publicize, it's hooked again later -- without this, skipping services is impossible
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ remove_action( 'save_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ), 100, 2 );
+ add_action( 'rest_api_inserted_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ) );
+ }
+
+ if ( $new ) {
+ $input = $this->input( true );
+
+ if ( 'revision' === $input['type'] ) {
+ if ( ! isset( $input['parent'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+ $input['status'] = 'inherit'; // force inherit for revision type
+ $input['slug'] = $input['parent'] . '-autosave-v1';
+ }
+ elseif ( !isset( $input['title'] ) && !isset( $input['content'] ) && !isset( $input['excerpt'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ // default to post
+ if ( empty( $input['type'] ) )
+ $input['type'] = 'post';
+
+ $post_type = get_post_type_object( $input['type'] );
+
+ if ( ! $this->is_post_type_allowed( $input['type'] ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ if ( ! empty( $input['author'] ) ) {
+ $author_id = $this->parse_and_set_author( $input['author'], $input['type'] );
+ unset( $input['author'] );
+ if ( is_wp_error( $author_id ) )
+ return $author_id;
+ }
+
+ if ( 'publish' === $input['status'] ) {
+ if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
+ if ( current_user_can( $post_type->cap->edit_posts ) ) {
+ $input['status'] = 'pending';
+ } else {
+ return new WP_Error( 'unauthorized', 'User cannot publish posts', 403 );
+ }
+ }
+ } else {
+ if ( !current_user_can( $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit posts', 403 );
+ }
+ }
+ } else {
+ $input = $this->input( false );
+
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $post = get_post( $post_id );
+ $_post_type = ( ! empty( $input['type'] ) ) ? $input['type'] : $post->post_type;
+ $post_type = get_post_type_object( $_post_type );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+
+ if ( ! empty( $input['author'] ) ) {
+ $author_id = $this->parse_and_set_author( $input['author'], $_post_type );
+ unset( $input['author'] );
+ if ( is_wp_error( $author_id ) )
+ return $author_id;
+ }
+
+ if ( 'publish' === $input['status'] && 'publish' !== $post->post_status && !current_user_can( 'publish_post', $post->ID ) ) {
+ $input['status'] = 'pending';
+ }
+ $last_status = $post->post_status;
+ $new_status = $input['status'];
+ }
+
+ // Fix for https://iorequests.wordpress.com/2014/08/13/scheduled-posts-made-in-the/
+ // See: https://a8c.slack.com/archives/io/p1408047082000273
+ // If date was set, $this->input will set date_gmt, date still needs to be adjusted for the blog's offset
+ if ( isset( $input['date_gmt'] ) ) {
+ $gmt_offset = get_option( 'gmt_offset' );
+ $time_with_offset = strtotime( $input['date_gmt'] ) + $gmt_offset * HOUR_IN_SECONDS;
+ $input['date'] = date( 'Y-m-d H:i:s', $time_with_offset );
+ }
+
+ if ( ! empty( $author_id ) && get_current_user_id() != $author_id ) {
+ if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
+ return new WP_Error( 'unauthorized', "User is not allowed to publish others' posts.", 403 );
+ } elseif ( ! user_can( $author_id, $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'Assigned author cannot publish post.', 403 );
+ }
+ }
+
+ if ( !is_post_type_hierarchical( $post_type->name ) && 'revision' !== $post_type->name ) {
+ unset( $input['parent'] );
+ }
+
+ $tax_input = array();
+
+ foreach ( array( 'categories' => 'category', 'tags' => 'post_tag' ) as $key => $taxonomy ) {
+ if ( ! isset( $input[ $key ] ) ) {
+ continue;
+ }
+
+ $tax_input[ $taxonomy ] = array();
+
+ $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
+
+ if ( is_array( $input[$key] ) ) {
+ $terms = $input[$key];
+ } else {
+ $terms = explode( ',', $input[$key] );
+ }
+
+ foreach ( $terms as $term ) {
+ /**
+ * `curl --data 'category[]=123'` should be interpreted as a category ID,
+ * not a category whose name is '123'.
+ *
+ * Consequence: To add a category/tag whose name is '123', the client must
+ * first look up its ID.
+ */
+ if ( ctype_digit( $term ) ) {
+ $term = (int) $term;
+ }
+
+ $term_info = term_exists( $term, $taxonomy );
+
+ if ( ! $term_info ) {
+ // A term ID that doesn't already exist. Ignore it: we don't know what name to give it.
+ if ( is_int( $term ) ){
+ continue;
+ }
+ // only add a new tag/cat if the user has access to
+ $tax = get_taxonomy( $taxonomy );
+ if ( !current_user_can( $tax->cap->edit_terms ) ) {
+ continue;
+ }
+
+ $term_info = wp_insert_term( $term, $taxonomy );
+ }
+
+ if ( ! is_wp_error( $term_info ) ) {
+ if ( $is_hierarchical ) {
+ // Categories must be added by ID
+ $tax_input[$taxonomy][] = (int) $term_info['term_id'];
+ } else {
+ // Tags must be added by name
+ if ( is_int( $term ) ) {
+ $term = get_term( $term, $taxonomy );
+ $tax_input[$taxonomy][] = $term->name;
+ } else {
+ $tax_input[$taxonomy][] = $term;
+ }
+ }
+ }
+ }
+ }
+
+ if ( isset( $input['categories'] ) && empty( $tax_input['category'] ) && 'revision' !== $post_type->name ) {
+ $tax_input['category'][] = get_option( 'default_category' );
+ }
+
+ unset( $input['tags'], $input['categories'] );
+
+ $insert = array();
+
+ if ( !empty( $input['slug'] ) ) {
+ $insert['post_name'] = $input['slug'];
+ unset( $input['slug'] );
+ }
+
+ if ( isset( $input['discussion'] ) ) {
+ $discussion = (array) $input['discussion'];
+ foreach ( array( 'comment', 'ping' ) as $discussion_type ) {
+ $discussion_open = sprintf( '%ss_open', $discussion_type );
+ $discussion_status = sprintf( '%s_status', $discussion_type );
+
+ if ( isset( $discussion[ $discussion_open ] ) ) {
+ $is_open = WPCOM_JSON_API::is_truthy( $discussion[ $discussion_open ] );
+ $discussion[ $discussion_status ] = $is_open ? 'open' : 'closed';
+ }
+
+ if ( in_array( $discussion[ $discussion_status ], array( 'open', 'closed' ) ) ) {
+ $insert[ $discussion_status ] = $discussion[ $discussion_status ];
+ }
+ }
+ }
+
+ unset( $input['discussion'] );
+
+ $insert['menu_order'] = $input['menu_order'];
+ unset( $input['menu_order'] );
+
+ $publicize = $input['publicize'];
+ $publicize_custom_message = $input['publicize_message'];
+ unset( $input['publicize'], $input['publicize_message'] );
+
+ if ( isset( $input['featured_image'] ) ) {
+ $featured_image = trim( $input['featured_image'] );
+ $delete_featured_image = empty( $featured_image );
+ unset( $input['featured_image'] );
+ }
+
+ $metadata = $input['metadata'];
+ unset( $input['metadata'] );
+
+ $likes = $input['likes_enabled'];
+ $sharing = $input['sharing_enabled'];
+
+ unset( $input['likes_enabled'] );
+ unset( $input['sharing_enabled'] );
+
+ $sticky = $input['sticky'];
+ unset( $input['sticky'] );
+
+ foreach ( $input as $key => $value ) {
+ $insert["post_$key"] = $value;
+ }
+
+ if ( ! empty( $author_id ) ) {
+ $insert['post_author'] = absint( $author_id );
+ }
+
+ if ( ! empty( $tax_input ) ) {
+ $insert['tax_input'] = $tax_input;
+ }
+
+ $has_media = ! empty( $input['media'] ) ? count( $input['media'] ) : false;
+ $has_media_by_url = ! empty( $input['media_urls'] ) ? count( $input['media_urls'] ) : false;
+
+ if ( $new ) {
+
+ if ( false === strpos( $input['content'], '[gallery' ) && ( $has_media || $has_media_by_url ) ) {
+ switch ( ( $has_media + $has_media_by_url ) ) {
+ case 0 :
+ // No images - do nothing.
+ break;
+ case 1 :
+ // 1 image - make it big
+ $insert['post_content'] = $input['content'] = "[gallery size=full columns=1]\n\n" . $input['content'];
+ break;
+ default :
+ // Several images - 3 column gallery
+ $insert['post_content'] = $input['content'] = "[gallery]\n\n" . $input['content'];
+ break;
+ }
+ }
+
+ $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
+ } else {
+ $insert['ID'] = $post->ID;
+
+ // wp_update_post ignores date unless edit_date is set
+ // See: http://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts
+ // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302
+ if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
+ $insert['edit_date'] = true;
+ }
+
+ $post_id = wp_update_post( (object) $insert );
+ }
+
+
+ if ( !$post_id || is_wp_error( $post_id ) ) {
+ return $post_id;
+ }
+
+ // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint)
+ $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( is_wp_error( $post_check ) ) {
+ return $post_check;
+ }
+
+ if ( $has_media || $has_media_by_url ) {
+ $media_files = ! empty( $input['media'] ) ? $input['media'] : array();
+ $media_urls = ! empty( $input['media_urls'] ) ? $input['media_urls'] : array();
+ $media_attrs = ! empty( $input['media_attrs'] ) ? $input['media_attrs'] : array();
+ $force_parent_id = $post_id;
+ $media_results = $this->handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs, $force_parent_id );
+ }
+
+ // set page template for this post..
+ if ( isset( $input['page_template'] ) && 'page' == $post_type->name ) {
+ $page_template = $input['page_template'];
+ $page_templates = wp_get_theme()->get_page_templates( get_post( $post_id ) );
+ if ( empty( $page_template ) || 'default' == $page_template || isset( $page_templates[ $page_template ] ) ) {
+ update_post_meta( $post_id, '_wp_page_template', $page_template );
+ }
+ }
+
+ // Set like status for the post
+ $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
+ if ( $new ) {
+ if ( $sitewide_likes_enabled ) {
+ if ( false === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ } else {
+ if ( $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ }
+ } else {
+ if ( isset( $likes ) ) {
+ if ( $sitewide_likes_enabled ) {
+ if ( false === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ } else {
+ if ( true === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ }
+ }
+ }
+
+ // Set sharing status of the post
+ if ( $new ) {
+ $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
+ if ( false === $sharing_enabled ) {
+ update_post_meta( $post_id, 'sharing_disabled', 1 );
+ }
+ }
+ else {
+ if ( isset( $sharing ) && true === $sharing ) {
+ delete_post_meta( $post_id, 'sharing_disabled' );
+ } else if ( isset( $sharing ) && false == $sharing ) {
+ update_post_meta( $post_id, 'sharing_disabled', 1 );
+ }
+ }
+
+ if ( true === $sticky ) {
+ stick_post( $post_id );
+ } else {
+ unstick_post( $post_id );
+ }
+
+ // WPCOM Specific (Jetpack's will get bumped elsewhere
+ // Tracks how many posts are published and sets meta so we can track some other cool stats (like likes & comments on posts published)
+ if ( ( $new && 'publish' == $input['status'] ) || ( !$new && isset( $last_status ) && 'publish' != $last_status && isset( $new_status ) && 'publish' == $new_status ) ) {
+ if ( function_exists( 'bump_stats_extras' ) ) {
+ bump_stats_extras( 'api-insights-posts', $this->api->token_details['client_id'] );
+ update_post_meta( $post_id, '_rest_api_published', 1 );
+ update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
+ }
+ }
+
+
+ // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
+ // to instead flag the ones we don't want to be skipped. proceed with said logic.
+ // any posts coming from Path (client ID 25952) should also not publicize
+ if ( $publicize === false || 25952 == $this->api->token_details['client_id'] ) {
+ // No publicize at all, skip all by ID
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
+ $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
+ if ( ! $service_connections ) {
+ continue;
+ }
+ foreach ( $service_connections as $service_connection ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ }
+ }
+ } else if ( is_array( $publicize ) && ( count ( $publicize ) > 0 ) ) {
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ /*
+ * We support both indexed and associative arrays:
+ * * indexed are to pass entire services
+ * * associative are to pass specific connections per service
+ *
+ * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
+ *
+ * EG: array( 'twitter', 'facebook') will only publicize to those, ignoring the other available services
+ * Form data: publicize[]=twitter&publicize[]=facebook
+ * EG: array( 'twitter' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3', 'facebook' => (int) $pub_conn_id_7 ) will publicize to two Twitter accounts, and one Facebook connection, of potentially many.
+ * Form data: publicize[twitter]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
+ * EG: array( 'twitter', 'facebook' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3' ) will publicize to all available Twitter accounts, but only 2 of potentially many Facebook connections
+ * Form data: publicize[]=twitter&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
+ */
+
+ // Delete any stale SKIP value for the service by name. We'll add it back by ID.
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
+
+ // Get the user's connections
+ $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
+
+ // if the user doesn't have any connections for this service, move on
+ if ( ! $service_connections ) {
+ continue;
+ }
+
+ if ( !in_array( $name, $publicize ) && !array_key_exists( $name, $publicize ) ) {
+ // Skip the whole service by adding each connection ID
+ foreach ( $service_connections as $service_connection ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ }
+ } else if ( !empty( $publicize[ $name ] ) ) {
+ // Seems we're being asked to only push to [a] specific connection[s].
+ // Explode the list on commas, which will also support a single passed ID
+ $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
+
+ // Flag the connections we can't match with the requested list to be skipped.
+ foreach ( $service_connections as $service_connection ) {
+ if ( !in_array( $service_connection->meta['connection_data']->id, $requested_connections ) ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ } else {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
+ }
+ }
+ } else {
+ // delete all SKIP values; it's okay to publish to all connected IDs for this service
+ foreach ( $service_connections as $service_connection ) {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
+ }
+ }
+ }
+ }
+
+ if ( !empty( $publicize_custom_message ) )
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
+
+ set_post_format( $post_id, $insert['post_format'] );
+
+ if ( isset( $featured_image ) ) {
+ $this->parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
+ }
+
+ if ( ! empty( $metadata ) ) {
+ foreach ( (array) $metadata as $meta ) {
+
+ $meta = (object) $meta;
+
+ $existing_meta_item = new stdClass;
+
+ if ( empty( $meta->operation ) )
+ $meta->operation = 'update';
+
+ if ( ! empty( $meta->value ) ) {
+ if ( 'true' == $meta->value )
+ $meta->value = true;
+ if ( 'false' == $meta->value )
+ $meta->value = false;
+ }
+
+ if ( ! empty( $meta->id ) ) {
+ $meta->id = absint( $meta->id );
+ $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
+ }
+
+ $unslashed_meta_key = wp_unslash( $meta->key ); // should match what the final key will be
+ $meta->key = wp_slash( $meta->key );
+ $unslashed_existing_meta_key = wp_unslash( $existing_meta_item->meta_key );
+ $existing_meta_item->meta_key = wp_slash( $existing_meta_item->meta_key );
+
+ // make sure that the meta id passed matches the existing meta key
+ if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
+ $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
+ if ( $meta_by_id->meta_key !== $meta->key ) {
+ continue; // skip this meta
+ }
+ }
+
+ switch ( $meta->operation ) {
+ case 'delete':
+
+ if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
+ delete_metadata_by_mid( 'post', $meta->id );
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
+ delete_post_meta( $post_id, $meta->key, $meta->previous_value );
+ } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
+ delete_post_meta( $post_id, $meta->key );
+ }
+
+ break;
+ case 'add':
+
+ if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
+ continue;
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->value ) && ( current_user_can( 'add_post_meta', $post_id, $unslashed_meta_key ) ) || $this->is_metadata_public( $meta->key ) ) {
+ add_post_meta( $post_id, $meta->key, $meta->value );
+ }
+
+ break;
+ case 'update':
+
+ if ( ! isset( $meta->value ) ) {
+ continue;
+ } elseif ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_existing_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_metadata_by_mid( 'post', $meta->id, $meta->value );
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_post_meta( $post_id, $meta->key,$meta->value, $meta->previous_value );
+ } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_post_meta( $post_id, $meta->key, $meta->value );
+ }
+
+ break;
+ }
+
+ }
+ }
+
+ do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
+
+ $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ if ( 'revision' === $input['type'] ) {
+ $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
+ }
+
+ // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above
+ $return['sticky'] = ( true === $sticky );
+
+ if ( ! empty( $media_results['errors'] ) )
+ $return['media_errors'] = $media_results['errors'];
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+
+ // /sites/%s/posts/%d/delete -> $blog_id, $post_id
+ function delete_post( $path, $blog_id, $post_id ) {
+ $post = get_post( $post_id );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( ! $this->is_post_type_allowed( $post->post_type ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ if ( !current_user_can( 'delete_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot delete posts', 403 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ wp_delete_post( $post->ID );
+
+ $status = get_post_status( $post->ID );
+ if ( false === $status ) {
+ $return['status'] = 'deleted';
+ return $return;
+ }
+
+ return $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ }
+
+ // /sites/%s/posts/%d/restore -> $blog_id, $post_id
+ function restore_post( $path, $blog_id, $post_id ) {
+ $args = $this->query_args();
+ $post = get_post( $post_id );
+
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'delete_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot restore trashed posts', 403 );
+ }
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ wp_untrash_post( $post->ID );
+
+ return $this->get_post_by( 'ID', $post->ID, $args['context'] );
+ }
+
+ protected function parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image ) {
+ if ( $delete_featured_image ) {
+ delete_post_thumbnail( $post_id );
+ return;
+ }
+
+ $featured_image = (string) $featured_image;
+
+ // if we got a post ID, we can just set it as the thumbnail
+ if ( ctype_digit( $featured_image ) && 'attachment' == get_post_type( $featured_image ) ) {
+ set_post_thumbnail( $post_id, $featured_image );
+ return $featured_image;
+ }
+
+ $featured_image_id = $this->handle_media_sideload( $featured_image, $post_id );
+
+ if ( empty( $featured_image_id ) || ! is_int( $featured_image_id ) )
+ return false;
+
+ set_post_thumbnail( $post_id, $featured_image_id );
+ return $featured_image_id;
+ }
+
+ protected function parse_and_set_author( $author = null, $post_type = 'post' ) {
+ if ( empty( $author ) || ! post_type_supports( $post_type, 'author' ) )
+ return get_current_user_id();
+
+ if ( ctype_digit( $author ) ) {
+ $_user = get_user_by( 'id', $author );
+ if ( ! $_user || is_wp_error( $_user ) )
+ return new WP_Error( 'invalid_author', 'Invalid author provided' );
+
+ return $_user->ID;
+ }
+
+ $_user = get_user_by( 'login', $author );
+ if ( ! $_user || is_wp_error( $_user ) )
+ return new WP_Error( 'invalid_author', 'Invalid author provided' );
+
+ return $_user->ID;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-2-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-2-endpoint.php
new file mode 100644
index 00000000..2d50168b
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-post-v1-2-endpoint.php
@@ -0,0 +1,572 @@
+<?php
+class WPCOM_JSON_API_Update_Post_v1_2_Endpoint extends WPCOM_JSON_API_Update_Post_v1_1_Endpoint {
+
+ // /sites/%s/posts/new -> $blog_id
+ // /sites/%s/posts/%d -> $blog_id, $post_id
+ function write_post( $path, $blog_id, $post_id ) {
+ $new = $this->api->ends_with( $path, '/new' );
+ $args = $this->query_args();
+
+ // unhook publicize, it's hooked again later -- without this, skipping services is impossible
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ remove_action( 'save_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ), 100, 2 );
+ add_action( 'rest_api_inserted_post', array( $GLOBALS['publicize_ui']->publicize, 'async_publicize_post' ) );
+ }
+
+ if ( $new ) {
+ $input = $this->input( true );
+
+ if ( 'revision' === $input['type'] ) {
+ if ( ! isset( $input['parent'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+ $input['status'] = 'inherit'; // force inherit for revision type
+ $input['slug'] = $input['parent'] . '-autosave-v1';
+ }
+ elseif ( !isset( $input['title'] ) && !isset( $input['content'] ) && !isset( $input['excerpt'] ) ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ // default to post
+ if ( empty( $input['type'] ) )
+ $input['type'] = 'post';
+
+ $post_type = get_post_type_object( $input['type'] );
+
+ if ( ! $this->is_post_type_allowed( $input['type'] ) ) {
+ return new WP_Error( 'unknown_post_type', 'Unknown post type', 404 );
+ }
+
+ if ( ! empty( $input['author'] ) ) {
+ $author_id = parent::parse_and_set_author( $input['author'], $input['type'] );
+ unset( $input['author'] );
+ if ( is_wp_error( $author_id ) )
+ return $author_id;
+ }
+
+ if ( 'publish' === $input['status'] ) {
+ if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
+ if ( current_user_can( $post_type->cap->edit_posts ) ) {
+ $input['status'] = 'pending';
+ } else {
+ return new WP_Error( 'unauthorized', 'User cannot publish posts', 403 );
+ }
+ }
+ } else {
+ if ( !current_user_can( $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit posts', 403 );
+ }
+ }
+ } else {
+ $input = $this->input( false );
+
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $post = get_post( $post_id );
+ $_post_type = ( ! empty( $input['type'] ) ) ? $input['type'] : $post->post_type;
+ $post_type = get_post_type_object( $_post_type );
+ if ( !$post || is_wp_error( $post ) ) {
+ return new WP_Error( 'unknown_post', 'Unknown post', 404 );
+ }
+
+ if ( !current_user_can( 'edit_post', $post->ID ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit post', 403 );
+ }
+
+ if ( ! empty( $input['author'] ) ) {
+ $author_id = parent::parse_and_set_author( $input['author'], $_post_type );
+ unset( $input['author'] );
+ if ( is_wp_error( $author_id ) )
+ return $author_id;
+ }
+
+ if ( 'publish' === $input['status'] && 'publish' !== $post->post_status && !current_user_can( 'publish_post', $post->ID ) ) {
+ $input['status'] = 'pending';
+ }
+ $last_status = $post->post_status;
+ $new_status = $input['status'];
+ }
+
+ // Fix for https://iorequests.wordpress.com/2014/08/13/scheduled-posts-made-in-the/
+ // See: https://a8c.slack.com/archives/io/p1408047082000273
+ // If date was set, $this->input will set date_gmt, date still needs to be adjusted for the blog's offset
+ if ( isset( $input['date_gmt'] ) ) {
+ $gmt_offset = get_option( 'gmt_offset' );
+ $time_with_offset = strtotime( $input['date_gmt'] ) + $gmt_offset * HOUR_IN_SECONDS;
+ $input['date'] = date( 'Y-m-d H:i:s', $time_with_offset );
+ }
+
+ if ( ! empty( $author_id ) && get_current_user_id() != $author_id ) {
+ if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
+ return new WP_Error( 'unauthorized', "User is not allowed to publish others' posts.", 403 );
+ } elseif ( ! user_can( $author_id, $post_type->cap->edit_posts ) ) {
+ return new WP_Error( 'unauthorized', 'Assigned author cannot publish post.', 403 );
+ }
+ }
+
+ if ( !is_post_type_hierarchical( $post_type->name ) && 'revision' !== $post_type->name ) {
+ unset( $input['parent'] );
+ }
+
+ /* add taxonomies by name */
+ $tax_input = array();
+ foreach ( array( 'categories' => 'category', 'tags' => 'post_tag' ) as $key => $taxonomy ) {
+ if ( ! isset( $input[ $key ] ) ) {
+ continue;
+ }
+
+ $tax_input[ $taxonomy ] = array();
+
+ $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
+
+ if ( is_array( $input[$key] ) ) {
+ $terms = $input[$key];
+ } else {
+ $terms = explode( ',', $input[$key] );
+ }
+
+ foreach ( $terms as $term ) {
+ /**
+ * We assume these are names, not IDs, even if they are numeric.
+ * Note: A category named "0" will not work right.
+ * https://core.trac.wordpress.org/ticket/9059
+ */
+ $term_info = get_term_by( 'name', $term, $taxonomy, ARRAY_A );
+
+ if ( ! $term_info ) {
+ // only add a new tag/cat if the user has access to
+ $tax = get_taxonomy( $taxonomy );
+ if ( ! current_user_can( $tax->cap->edit_terms ) ) {
+ continue;
+ }
+
+ $term_info = wp_insert_term( $term, $taxonomy );
+ }
+
+ if ( ! is_wp_error( $term_info ) ) {
+ if ( $is_hierarchical ) {
+ // Categories must be added by ID
+ $tax_input[$taxonomy][] = (int) $term_info['term_id'];
+ } else {
+ // Tags must be added by name
+ $tax_input[$taxonomy][] = $term;
+ }
+ }
+ }
+ }
+
+ /* add taxonomies by ID */
+ foreach ( array( 'categories_by_id' => 'category', 'tags_by_id' => 'post_tag' ) as $key => $taxonomy ) {
+ if ( ! isset( $input[ $key ] ) ) {
+ continue;
+ }
+
+ // combine with any previous selections
+ if ( ! is_array( $tax_input[ $taxonomy ] ) ) {
+ $tax_input[ $taxonomy ] = array();
+ }
+
+ $is_hierarchical = is_taxonomy_hierarchical( $taxonomy );
+
+ if ( is_array( $input[$key] ) ) {
+ $terms = $input[$key];
+ } else {
+ $terms = explode( ',', $input[$key] );
+ }
+
+ foreach ( $terms as $term ) {
+ if ( ! ctype_digit( $term ) ) {
+ // skip anything that doesn't look like an ID
+ continue;
+ }
+ $term = (int) $term;
+ $term_info = get_term_by( 'id', $term, $taxonomy, ARRAY_A );
+
+ if ( $term_info && ! is_wp_error( $term_info ) ) {
+ if ( $is_hierarchical ) {
+ // Categories must be added by ID
+ $tax_input[$taxonomy][] = $term;
+ } else {
+ // Tags must be added by name
+ $tax_input[$taxonomy][] = $term_info['name'];
+ }
+ }
+ }
+ }
+
+ if ( ( isset( $input['categories'] ) || isset( $input['categories_by_id'] ) )
+ && empty( $tax_input['category'] ) && 'revision' !== $post_type->name ) {
+ $tax_input['category'][] = get_option( 'default_category' );
+ }
+
+ unset( $input['tags'], $input['categories'], $input['tags_by_id'], $input['categories_by_id'] );
+
+ $insert = array();
+
+ if ( !empty( $input['slug'] ) ) {
+ $insert['post_name'] = $input['slug'];
+ unset( $input['slug'] );
+ }
+
+ if ( isset( $input['discussion'] ) ) {
+ $discussion = (array) $input['discussion'];
+ foreach ( array( 'comment', 'ping' ) as $discussion_type ) {
+ $discussion_open = sprintf( '%ss_open', $discussion_type );
+ $discussion_status = sprintf( '%s_status', $discussion_type );
+
+ if ( isset( $discussion[ $discussion_open ] ) ) {
+ $is_open = WPCOM_JSON_API::is_truthy( $discussion[ $discussion_open ] );
+ $discussion[ $discussion_status ] = $is_open ? 'open' : 'closed';
+ }
+
+ if ( in_array( $discussion[ $discussion_status ], array( 'open', 'closed' ) ) ) {
+ $insert[ $discussion_status ] = $discussion[ $discussion_status ];
+ }
+ }
+ }
+
+ unset( $input['discussion'] );
+
+ $insert['menu_order'] = $input['menu_order'];
+ unset( $input['menu_order'] );
+
+ $publicize = $input['publicize'];
+ $publicize_custom_message = $input['publicize_message'];
+ unset( $input['publicize'], $input['publicize_message'] );
+
+ if ( isset( $input['featured_image'] ) ) {
+ $featured_image = trim( $input['featured_image'] );
+ $delete_featured_image = empty( $featured_image );
+ unset( $input['featured_image'] );
+ }
+
+ $metadata = $input['metadata'];
+ unset( $input['metadata'] );
+
+ $likes = $input['likes_enabled'];
+ $sharing = $input['sharing_enabled'];
+
+ unset( $input['likes_enabled'] );
+ unset( $input['sharing_enabled'] );
+
+ $sticky = $input['sticky'];
+ unset( $input['sticky'] );
+
+ foreach ( $input as $key => $value ) {
+ $insert["post_$key"] = $value;
+ }
+
+ if ( ! empty( $author_id ) ) {
+ $insert['post_author'] = absint( $author_id );
+ }
+
+ if ( ! empty( $tax_input ) ) {
+ $insert['tax_input'] = $tax_input;
+ }
+
+ $has_media = ! empty( $input['media'] ) ? count( $input['media'] ) : false;
+ $has_media_by_url = ! empty( $input['media_urls'] ) ? count( $input['media_urls'] ) : false;
+
+ if ( $new ) {
+
+ if ( false === strpos( $input['content'], '[gallery' ) && ( $has_media || $has_media_by_url ) ) {
+ switch ( ( $has_media + $has_media_by_url ) ) {
+ case 0 :
+ // No images - do nothing.
+ break;
+ case 1 :
+ // 1 image - make it big
+ $insert['post_content'] = $input['content'] = "[gallery size=full columns=1]\n\n" . $input['content'];
+ break;
+ default :
+ // Several images - 3 column gallery
+ $insert['post_content'] = $input['content'] = "[gallery]\n\n" . $input['content'];
+ break;
+ }
+ }
+
+ $post_id = wp_insert_post( add_magic_quotes( $insert ), true );
+ } else {
+ $insert['ID'] = $post->ID;
+
+ // wp_update_post ignores date unless edit_date is set
+ // See: http://codex.wordpress.org/Function_Reference/wp_update_post#Scheduling_posts
+ // See: https://core.trac.wordpress.org/browser/tags/3.9.2/src/wp-includes/post.php#L3302
+ if ( isset( $input['date_gmt'] ) || isset( $input['date'] ) ) {
+ $insert['edit_date'] = true;
+ }
+
+ $post_id = wp_update_post( (object) $insert );
+ }
+
+
+ if ( !$post_id || is_wp_error( $post_id ) ) {
+ return $post_id;
+ }
+
+ // make sure this post actually exists and is not an error of some kind (ie, trying to load media in the posts endpoint)
+ $post_check = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( is_wp_error( $post_check ) ) {
+ return $post_check;
+ }
+
+ if ( $has_media || $has_media_by_url ) {
+ $media_files = ! empty( $input['media'] ) ? $input['media'] : array();
+ $media_urls = ! empty( $input['media_urls'] ) ? $input['media_urls'] : array();
+ $media_attrs = ! empty( $input['media_attrs'] ) ? $input['media_attrs'] : array();
+ $force_parent_id = $post_id;
+ $media_results = $this->handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs, $force_parent_id );
+ }
+
+ // set page template for this post..
+ if ( isset( $input['page_template'] ) && 'page' == $post_type->name ) {
+ $page_template = $input['page_template'];
+ $page_templates = wp_get_theme()->get_page_templates( get_post( $post_id ) );
+ if ( empty( $page_template ) || 'default' == $page_template || isset( $page_templates[ $page_template ] ) ) {
+ update_post_meta( $post_id, '_wp_page_template', $page_template );
+ }
+ }
+
+ // Set like status for the post
+ $sitewide_likes_enabled = (bool) apply_filters( 'wpl_is_enabled_sitewide', ! get_option( 'disabled_likes' ) );
+ if ( $new ) {
+ if ( $sitewide_likes_enabled ) {
+ if ( false === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ } else {
+ if ( $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ }
+ } else {
+ if ( isset( $likes ) ) {
+ if ( $sitewide_likes_enabled ) {
+ if ( false === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ } else {
+ if ( true === $likes ) {
+ update_post_meta( $post_id, 'switch_like_status', 1 );
+ } else {
+ delete_post_meta( $post_id, 'switch_like_status' );
+ }
+ }
+ }
+ }
+
+ // Set sharing status of the post
+ if ( $new ) {
+ $sharing_enabled = isset( $sharing ) ? (bool) $sharing : true;
+ if ( false === $sharing_enabled ) {
+ update_post_meta( $post_id, 'sharing_disabled', 1 );
+ }
+ }
+ else {
+ if ( isset( $sharing ) && true === $sharing ) {
+ delete_post_meta( $post_id, 'sharing_disabled' );
+ } else if ( isset( $sharing ) && false == $sharing ) {
+ update_post_meta( $post_id, 'sharing_disabled', 1 );
+ }
+ }
+
+ if ( true === $sticky ) {
+ stick_post( $post_id );
+ } else {
+ unstick_post( $post_id );
+ }
+
+ // WPCOM Specific (Jetpack's will get bumped elsewhere
+ // Tracks how many posts are published and sets meta so we can track some other cool stats (like likes & comments on posts published)
+ if ( ( $new && 'publish' == $input['status'] ) || ( !$new && isset( $last_status ) && 'publish' != $last_status && isset( $new_status ) && 'publish' == $new_status ) ) {
+ if ( function_exists( 'bump_stats_extras' ) ) {
+ bump_stats_extras( 'api-insights-posts', $this->api->token_details['client_id'] );
+ update_post_meta( $post_id, '_rest_api_published', 1 );
+ update_post_meta( $post_id, '_rest_api_client_id', $this->api->token_details['client_id'] );
+ }
+ }
+
+
+ // We ask the user/dev to pass Publicize services he/she wants activated for the post, but Publicize expects us
+ // to instead flag the ones we don't want to be skipped. proceed with said logic.
+ // any posts coming from Path (client ID 25952) should also not publicize
+ if ( $publicize === false || 25952 == $this->api->token_details['client_id'] ) {
+ // No publicize at all, skip all by ID
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
+ $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
+ if ( ! $service_connections ) {
+ continue;
+ }
+ foreach ( $service_connections as $service_connection ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ }
+ }
+ } else if ( is_array( $publicize ) && ( count ( $publicize ) > 0 ) ) {
+ foreach ( $GLOBALS['publicize_ui']->publicize->get_services( 'all' ) as $name => $service ) {
+ /*
+ * We support both indexed and associative arrays:
+ * * indexed are to pass entire services
+ * * associative are to pass specific connections per service
+ *
+ * We do support mixed arrays: mixed integer and string keys (see 3rd example below).
+ *
+ * EG: array( 'twitter', 'facebook') will only publicize to those, ignoring the other available services
+ * Form data: publicize[]=twitter&publicize[]=facebook
+ * EG: array( 'twitter' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3', 'facebook' => (int) $pub_conn_id_7 ) will publicize to two Twitter accounts, and one Facebook connection, of potentially many.
+ * Form data: publicize[twitter]=$pub_conn_id_0,$pub_conn_id_3&publicize[facebook]=$pub_conn_id_7
+ * EG: array( 'twitter', 'facebook' => '(int) $pub_conn_id_0, (int) $pub_conn_id_3' ) will publicize to all available Twitter accounts, but only 2 of potentially many Facebook connections
+ * Form data: publicize[]=twitter&publicize[facebook]=$pub_conn_id_0,$pub_conn_id_3
+ */
+
+ // Delete any stale SKIP value for the service by name. We'll add it back by ID.
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $name );
+
+ // Get the user's connections
+ $service_connections = $GLOBALS['publicize_ui']->publicize->get_connections( $name );
+
+ // if the user doesn't have any connections for this service, move on
+ if ( ! $service_connections ) {
+ continue;
+ }
+
+ if ( !in_array( $name, $publicize ) && !array_key_exists( $name, $publicize ) ) {
+ // Skip the whole service by adding each connection ID
+ foreach ( $service_connections as $service_connection ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ }
+ } else if ( !empty( $publicize[ $name ] ) ) {
+ // Seems we're being asked to only push to [a] specific connection[s].
+ // Explode the list on commas, which will also support a single passed ID
+ $requested_connections = explode( ',', ( preg_replace( '/[\s]*/', '', $publicize[ $name ] ) ) );
+
+ // Flag the connections we can't match with the requested list to be skipped.
+ foreach ( $service_connections as $service_connection ) {
+ if ( !in_array( $service_connection->meta['connection_data']->id, $requested_connections ) ) {
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id, 1 );
+ } else {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
+ }
+ }
+ } else {
+ // delete all SKIP values; it's okay to publish to all connected IDs for this service
+ foreach ( $service_connections as $service_connection ) {
+ delete_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_SKIP . $service_connection->unique_id );
+ }
+ }
+ }
+ }
+
+ if ( !empty( $publicize_custom_message ) )
+ update_post_meta( $post_id, $GLOBALS['publicize_ui']->publicize->POST_MESS, trim( $publicize_custom_message ) );
+
+ set_post_format( $post_id, $insert['post_format'] );
+
+ if ( isset( $featured_image ) ) {
+ parent::parse_and_set_featured_image( $post_id, $delete_featured_image, $featured_image );
+ }
+
+ if ( ! empty( $metadata ) ) {
+ foreach ( (array) $metadata as $meta ) {
+
+ $meta = (object) $meta;
+
+ $existing_meta_item = new stdClass;
+
+ if ( empty( $meta->operation ) )
+ $meta->operation = 'update';
+
+ if ( ! empty( $meta->value ) ) {
+ if ( 'true' == $meta->value )
+ $meta->value = true;
+ if ( 'false' == $meta->value )
+ $meta->value = false;
+ }
+
+ if ( ! empty( $meta->id ) ) {
+ $meta->id = absint( $meta->id );
+ $existing_meta_item = get_metadata_by_mid( 'post', $meta->id );
+ }
+
+ $unslashed_meta_key = wp_unslash( $meta->key ); // should match what the final key will be
+ $meta->key = wp_slash( $meta->key );
+ $unslashed_existing_meta_key = wp_unslash( $existing_meta_item->meta_key );
+ $existing_meta_item->meta_key = wp_slash( $existing_meta_item->meta_key );
+
+ // make sure that the meta id passed matches the existing meta key
+ if ( ! empty( $meta->id ) && ! empty( $meta->key ) ) {
+ $meta_by_id = get_metadata_by_mid( 'post', $meta->id );
+ if ( $meta_by_id->meta_key !== $meta->key ) {
+ continue; // skip this meta
+ }
+ }
+
+ switch ( $meta->operation ) {
+ case 'delete':
+
+ if ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_existing_meta_key ) ) {
+ delete_metadata_by_mid( 'post', $meta->id );
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
+ delete_post_meta( $post_id, $meta->key, $meta->previous_value );
+ } elseif ( ! empty( $meta->key ) && current_user_can( 'delete_post_meta', $post_id, $unslashed_meta_key ) ) {
+ delete_post_meta( $post_id, $meta->key );
+ }
+
+ break;
+ case 'add':
+
+ if ( ! empty( $meta->id ) || ! empty( $meta->previous_value ) ) {
+ continue;
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->value ) && ( current_user_can( 'add_post_meta', $post_id, $unslashed_meta_key ) ) || $this->is_metadata_public( $meta->key ) ) {
+ add_post_meta( $post_id, $meta->key, $meta->value );
+ }
+
+ break;
+ case 'update':
+
+ if ( ! isset( $meta->value ) ) {
+ continue;
+ } elseif ( ! empty( $meta->id ) && ! empty( $existing_meta_item->meta_key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_existing_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_metadata_by_mid( 'post', $meta->id, $meta->value );
+ } elseif ( ! empty( $meta->key ) && ! empty( $meta->previous_value ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_post_meta( $post_id, $meta->key,$meta->value, $meta->previous_value );
+ } elseif ( ! empty( $meta->key ) && ( current_user_can( 'edit_post_meta', $post_id, $unslashed_meta_key ) || $this->is_metadata_public( $meta->key ) ) ) {
+ update_post_meta( $post_id, $meta->key, $meta->value );
+ }
+
+ break;
+ }
+
+ }
+ }
+
+ do_action( 'rest_api_inserted_post', $post_id, $insert, $new );
+
+ $return = $this->get_post_by( 'ID', $post_id, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ if ( 'revision' === $input['type'] ) {
+ $return['preview_nonce'] = wp_create_nonce( 'post_preview_' . $input['parent'] );
+ }
+
+ // workaround for sticky test occasionally failing, maybe a race condition with stick_post() above
+ $return['sticky'] = ( true === $sticky );
+
+ if ( ! empty( $media_results['errors'] ) )
+ $return['media_errors'] = $media_results['errors'];
+
+ do_action( 'wpcom_json_api_objects', 'posts' );
+
+ return $return;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-taxonomy-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-taxonomy-endpoint.php
new file mode 100644
index 00000000..850eb14b
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-update-taxonomy-endpoint.php
@@ -0,0 +1,148 @@
+<?php
+
+class WPCOM_JSON_API_Update_Taxonomy_Endpoint extends WPCOM_JSON_API_Taxonomy_Endpoint {
+ // /sites/%s/tags|categories/new -> $blog_id
+ // /sites/%s/tags|categories/slug:%s -> $blog_id, $taxonomy_id
+ // /sites/%s/tags|categories/slug:%s/delete -> $blog_id, $taxonomy_id
+ function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( preg_match( '#/tags/#i', $path ) ) {
+ $taxonomy_type = "post_tag";
+ } else {
+ $taxonomy_type = "category";
+ }
+
+ if ( $this->api->ends_with( $path, '/delete' ) ) {
+ return $this->delete_taxonomy( $path, $blog_id, $object_id, $taxonomy_type );
+ } elseif ( $this->api->ends_with( $path, '/new' ) ) {
+ return $this->new_taxonomy( $path, $blog_id, $taxonomy_type );
+ }
+
+ return $this->update_taxonomy( $path, $blog_id, $object_id, $taxonomy_type );
+ }
+
+ // /sites/%s/tags|categories/new -> $blog_id
+ function new_taxonomy( $path, $blog_id, $taxonomy_type ) {
+ $args = $this->query_args();
+ $input = $this->input();
+ if ( !is_array( $input ) || !$input || !strlen( $input['name'] ) ) {
+ return new WP_Error( 'invalid_input', 'Unknown data passed', 400 );
+ }
+
+ $user = wp_get_current_user();
+ if ( !$user || is_wp_error( $user ) || !$user->ID ) {
+ return new WP_Error( 'authorization_required', 'An active access token must be used to manage taxonomies.', 403 );
+ }
+
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->edit_terms ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+ }
+
+ if ( term_exists( $input['name'], $taxonomy_type ) ) {
+ return new WP_Error( 'duplicate', 'A taxonomy with that name already exists', 400 );
+ }
+
+ if ( 'category' !== $taxonomy_type )
+ $input['parent'] = 0;
+
+ $data = wp_insert_term( addslashes( $input['name'] ), $taxonomy_type,
+ array(
+ 'description' => addslashes( $input['description'] ),
+ 'parent' => $input['parent']
+ )
+ );
+
+ if ( is_wp_error( $data ) )
+ return $data;
+
+ $taxonomy = get_term_by( 'id', $data['term_id'], $taxonomy_type );
+
+ $return = $this->get_taxonomy( $taxonomy->slug, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+ return $return;
+ }
+
+ // /sites/%s/tags|categories/slug:%s -> $blog_id, $taxonomy_id
+ function update_taxonomy( $path, $blog_id, $object_id, $taxonomy_type ) {
+ $taxonomy = get_term_by( 'slug', $object_id, $taxonomy_type );
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->edit_terms ) )
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+
+ if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
+ }
+
+ if ( false === term_exists( $object_id, $taxonomy_type ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'That taxonomy does not exist', 404 );
+ }
+
+ $args = $this->query_args();
+ $input = $this->input( false );
+ if ( !is_array( $input ) || !$input ) {
+ return new WP_Error( 'invalid_input', 'Invalid request input', 400 );
+ }
+
+ $update = array();
+ if ( 'category' === $taxonomy_type && !empty( $input['parent'] ) )
+ $update['parent'] = $input['parent'];
+
+ if ( !empty( $input['description'] ) )
+ $update['description'] = addslashes( $input['description'] );
+
+ if ( !empty( $input['name'] ) )
+ $update['name'] = addslashes( $input['name'] );
+
+
+ $data = wp_update_term( $taxonomy->term_id, $taxonomy_type, $update );
+ $taxonomy = get_term_by( 'id', $data['term_id'], $taxonomy_type );
+
+ $return = $this->get_taxonomy( $taxonomy->slug, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+ return $return;
+ }
+
+ // /sites/%s/tags|categories/%s/delete -> $blog_id, $taxonomy_id
+ function delete_taxonomy( $path, $blog_id, $object_id, $taxonomy_type ) {
+ $taxonomy = get_term_by( 'slug', $object_id, $taxonomy_type );
+ $tax = get_taxonomy( $taxonomy_type );
+ if ( !current_user_can( $tax->cap->delete_terms ) )
+ return new WP_Error( 'unauthorized', 'User cannot edit taxonomy', 403 );
+
+ if ( !$taxonomy || is_wp_error( $taxonomy ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'Unknown taxonomy', 404 );
+ }
+
+ if ( false === term_exists( $object_id, $taxonomy_type ) ) {
+ return new WP_Error( 'unknown_taxonomy', 'That taxonomy does not exist', 404 );
+ }
+
+ $args = $this->query_args();
+ $return = $this->get_taxonomy( $taxonomy->slug, $taxonomy_type, $args['context'] );
+ if ( !$return || is_wp_error( $return ) ) {
+ return $return;
+ }
+
+ do_action( 'wpcom_json_api_objects', 'taxonomies' );
+
+ wp_delete_term( $taxonomy->term_id, $taxonomy_type );
+
+ return array(
+ 'slug' => (string) $taxonomy->slug,
+ 'success' => 'true',
+ );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-endpoint.php
new file mode 100644
index 00000000..fda2d279
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-endpoint.php
@@ -0,0 +1,59 @@
+<?php
+
+class WPCOM_JSON_API_Upload_Media_Endpoint extends WPCOM_JSON_API_Endpoint {
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'upload_files' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
+ }
+
+ $input = $this->input( true );
+
+ $has_media = isset( $input['media'] ) && $input['media'] ? count( $input['media'] ) : false;
+ $has_media_urls = isset( $input['media_urls'] ) && $input['media_urls'] ? count( $input['media_urls'] ) : false;
+
+ $media_ids = $files = $errors = array();
+
+ if ( $has_media ) {
+ $this->api->trap_wp_die( 'upload_error' );
+ foreach ( $input['media'] as $index => $media_item ) {
+ $_FILES['.api.media.item.'] = $media_item;
+ // check for WP_Error if we ever actually need $media_id
+ $media_id = media_handle_upload( '.api.media.item.', 0 );
+ if ( is_wp_error( $media_id ) ) {
+ if ( 1 === count( $input['media'] ) && ! $has_media_urls ) {
+ unset( $_FILES['.api.media.item.'] );
+ return $media_id;
+ }
+ $errors[ $index ]['error'] = $media_id->get_error_code();
+ $errors[ $index ]['message'] = $media_id->get_error_message();
+ } else {
+ $media_ids[ $index ] = $media_id;
+ }
+ $files[] = $media_item;
+ }
+ $this->api->trap_wp_die( null );
+
+ unset( $_FILES['.api.media.item.'] );
+ }
+
+ if ( $has_media_urls ) {
+ foreach ( $input['media_urls'] as $url ) {
+ $id = $this->handle_media_sideload( $url );
+ if ( ! empty( $id ) && is_int( $id ) )
+ $media_ids[] = $id;
+ }
+ }
+
+ $results = array();
+ foreach ( $media_ids as $media_id ) {
+ $results[] = $this->get_media_item( $media_id );
+ }
+
+ return array( 'media' => $results, 'errors' => $errors );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-v1-1-endpoint.php b/plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-v1-1-endpoint.php
new file mode 100644
index 00000000..4c33b270
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/class.wpcom-json-api-upload-media-v1-1-endpoint.php
@@ -0,0 +1,47 @@
+<?php
+class WPCOM_JSON_API_Upload_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ function callback( $path = '', $blog_id = 0 ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( ! current_user_can( 'upload_files' ) ) {
+ return new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
+ }
+
+ $input = $this->input( true );
+
+ $media_files = ! empty( $input['media'] ) ? $input['media'] : array();
+ $media_urls = ! empty( $input['media_urls'] ) ? $input['media_urls'] : array();
+ $media_attrs = ! empty( $input['attrs'] ) ? $input['attrs'] : array();
+ if ( empty( $media_files ) && empty( $media_urls ) ) {
+ return new WP_Error( 'invalid_input', 'No media provided in input.' );
+ }
+
+ $create_media = $this->handle_media_creation_v1_1( $media_files, $media_urls, $media_attrs );
+ $media_ids = $create_media['media_ids'];
+ $errors = $create_media['errors'];
+
+ $results = array();
+ if ( count( $media_ids ) <= 0 ) {
+ $this->api->output_early( 400, array( 'errors' => $errors ) );
+ } else {
+ foreach ( $media_ids as $media_id ) {
+ $result = $this->get_media_item_v1_1( $media_id );
+ if ( is_wp_error( $result ) ) {
+ $errors[] = array( 'file' => $media_id, 'error' => $result->get_error_code(), 'message' => $result->get_error_message() );
+ } else {
+ $results[] = $result;
+ }
+ }
+ if ( count( $errors ) > 0 ) {
+ return array( 'media' => $results, 'errors' => $errors );
+ } else {
+ return array( 'media' => $results );
+ }
+ }
+
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-check-capabilities-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-check-capabilities-endpoint.php
new file mode 100644
index 00000000..0e4405b0
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-check-capabilities-endpoint.php
@@ -0,0 +1,25 @@
+<?php
+
+class Jetpack_JSON_API_Check_Capabilities_Endpoint extends Jetpack_JSON_API_Modules_Endpoint {
+ // GET /sites/%s/me/capability
+ public function callback( $path = '', $_blog_id = 0 ) {
+ // Check minimum capability and blog membership first
+ if ( is_wp_error( $error = $this->validate_call( $_blog_id, 'read', false ) ) ) {
+ return $error;
+ }
+
+ $args = $this->input();
+
+ if ( ! isset( $args['capability'] ) || empty( $args['capability'] ) ) {
+ return new WP_Error( 'missing_capability', __( 'You are required to specify a capability to check.', 'jetpack' ), 400 );
+ }
+
+ $capability = $args['capability'];
+ if ( is_array( $capability ) ) {
+ $results = array_map( 'current_user_can', $capability );
+ return array_combine( $capability, $results );
+ } else {
+ return current_user_can( $capability );
+ }
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-endpoint.php
new file mode 100644
index 00000000..f63a6cd7
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-endpoint.php
@@ -0,0 +1,20 @@
+<?php
+
+class Jetpack_JSON_API_Core_Endpoint extends Jetpack_JSON_API_Endpoint {
+ // POST /sites/%s/core
+ // POST /sites/%s/core/update
+ protected $needed_capabilities = 'manage_options';
+ protected $new_version;
+ protected $log;
+
+ public function result() {
+ global $wp_version;
+
+ return array(
+ 'version' => ( empty( $this->new_version ) ) ? $wp_version : $this->new_version,
+ 'autoupdate' => Jetpack_Options::get_option( 'autoupdate_core', false ),
+ 'log' => $this->log,
+ );
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-modify-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-modify-endpoint.php
new file mode 100644
index 00000000..dba600db
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-modify-endpoint.php
@@ -0,0 +1,66 @@
+<?php
+
+class Jetpack_JSON_API_Core_Modify_Endpoint extends Jetpack_JSON_API_Core_Endpoint {
+ // POST /sites/%s/core
+ // POST /sites/%s/core/update
+ protected $needed_capabilities = 'update_core';
+ protected $action = 'default_action';
+ protected $new_version;
+ protected $log;
+
+ public function default_action() {
+ $args = $this->input();
+
+ if ( isset( $args['autoupdate'] ) && is_bool( $args['autoupdate'] ) ) {
+ Jetpack_Options::update_option( 'autoupdate_core', $args['autoupdate'] );
+ }
+
+ return true;
+ }
+
+ protected function update( $version, $locale ) {
+ $args = $this->input();
+ $version = isset( $args['version'] ) ? $args['version'] : false;
+ $locale = isset( $args['locale'] ) ? $args['locale'] : get_locale();
+
+ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+
+ delete_site_transient( 'update_core' );
+ wp_version_check( array(), true );
+
+ if ( $version ) {
+ $update = find_core_update( $version, $locale );
+ } else {
+ $update = $this->find_latest_update_offer();
+ }
+
+ $skin = new Automatic_Upgrader_Skin();
+ $upgrader = new Core_Upgrader( $skin );
+
+ $this->new_version = $upgrader->upgrade( $update );
+
+ $this->log = $upgrader->skin->get_upgrade_messages();
+
+ if ( is_wp_error( $this->new_version ) ) {
+ return $this->new_version;
+ }
+
+ return $this->new_version;
+ }
+
+ protected function find_latest_update_offer() {
+ // Select the latest update.
+ // Remove filters to bypass automattic updates.
+ add_filter( 'request_filesystem_credentials', '__return_true' );
+ add_filter( 'automatic_updates_is_vcs_checkout', '__return_false' );
+ add_filter( 'allow_major_auto_core_updates', '__return_true' );
+ add_filter( 'send_core_update_notification_email', '__return_false' );
+ $update = find_core_auto_update();
+ remove_filter( 'request_filesystem_credentials', '__return_true' );
+ remove_filter( 'automatic_updates_is_vcs_checkout', '__return_false' );
+ remove_filter( 'allow_major_auto_core_updates', '__return_true' );
+ remove_filter( 'send_core_update_notification_email', '__return_false' );
+ return $update;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-endpoint.php
new file mode 100644
index 00000000..782d106e
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-endpoint.php
@@ -0,0 +1,115 @@
+<?php
+
+include JETPACK__PLUGIN_DIR . '/modules/module-info.php';
+
+/**
+ * Base class for Jetpack Endpoints, has the validate_call helper function.
+ */
+abstract class Jetpack_JSON_API_Endpoint extends WPCOM_JSON_API_Endpoint {
+
+ protected $needed_capabilities;
+ protected $expected_actions = array();
+ protected $action;
+
+
+ public function callback( $path = '', $blog_id = 0, $object = null ) {
+ if ( is_wp_error( $error = $this->validate_call( $blog_id, $this->needed_capabilities ) ) ) {
+ return $error;
+ }
+
+ if ( is_wp_error( $error = $this->validate_input( $object ) ) ) {
+ return $error;
+ }
+
+ if ( ! empty( $this->action ) ) {
+ if( is_wp_error( $error = call_user_func( array( $this, $this->action ) ) ) ) {
+ return $error;
+ }
+ }
+
+ return $this->result();
+ }
+
+ abstract protected function result();
+
+ protected function validate_input( $object ) {
+ $args = $this->input();
+
+ if( isset( $args['action'] ) && $args['action'] == 'update' ) {
+ $this->action = 'update';
+ }
+
+ if ( preg_match( "/\/update\/?$/", $this->path ) ) {
+ $this->action = 'update';
+
+ } elseif( preg_match( "/\/install\/?$/", $this->path ) ) {
+ $this->action = 'install';
+
+ } elseif( ! empty( $args['action'] ) ) {
+ if( ! in_array( $args['action'], $this->expected_actions ) ) {
+ return new WP_Error( 'invalid_action', __( 'You must specify a valid action', 'jetpack' ) );
+ }
+ $this->action = $args['action'];
+ }
+ return true;
+ }
+
+ /**
+ * Switches to the blog and checks current user capabilities.
+ * @return bool|WP_Error a WP_Error object or true if things are good.
+ */
+ protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) {
+ $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $_blog_id ) );
+ if ( is_wp_error( $blog_id ) ) {
+ return $blog_id;
+ }
+
+ if ( is_wp_error( $error = $this->check_capability( $capability ) ) ) {
+ return $error;
+ }
+
+ if ( $check_manage_active && 'GET' !== $this->method && ! Jetpack::is_module_active( 'manage' ) ) {
+ return new WP_Error( 'unauthorized_full_access', __( 'Full management mode is off for this site.', 'jetpack' ), 403 );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param $capability
+ *
+ * @return bool|WP_Error
+ */
+ protected function check_capability( $capability ) {
+ if ( is_array( $capability ) ) {
+ // the idea is that the we can pass in an array of capabilitie that the user needs to have before we allowing them to do something
+ $capabilities = ( isset( $capability['capabilities'] ) ? $capability['capabilities'] : $capability );
+
+ // We can pass in the number of conditions we must pass by default it is all.
+ $must_pass = ( isset( $capability['must_pass'] ) && is_int( $capability['must_pass'] ) ? $capability['must_pass'] : count( $capabilities ) );
+
+ $failed = array(); // store the failed capabilities
+ $passed = 0; //
+
+ foreach ( $capabilities as $cap ) {
+ if ( current_user_can( $cap ) ) {
+ $passed ++;
+ } else {
+ $failed[] = $cap;
+ }
+ }
+ // Check that must have conditions is less then
+ if ( $passed < $must_pass ) {
+ return new WP_Error( 'unauthorized', sprintf( __( 'This user is not authorized to %s on this blog.', 'jetpack' ), implode( ', ', $failed ), 403 ) );
+ }
+
+ } else {
+ if ( !current_user_can( $capability ) ) {
+ return new WP_Error( 'unauthorized', sprintf( __( 'This user is not authorized to %s on this blog.', 'jetpack' ), $capability ), 403 );
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-endpoint.php
new file mode 100644
index 00000000..0d9d54a8
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-endpoint.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * Base class for working with Jetpack Modules.
+ */
+abstract class Jetpack_JSON_API_Modules_Endpoint extends Jetpack_JSON_API_Endpoint {
+
+ protected $modules = array();
+
+ protected $bulk = true;
+
+ static $_response_format = array(
+ 'id' => '(string) The module\'s ID',
+ 'active' => '(boolean) The module\'s status.',
+ 'name' => '(string) The module\'s name.',
+ 'description' => '(safehtml) The module\'s description.',
+ 'sort' => '(int) The module\'s display order.',
+ 'introduced' => '(string) The Jetpack version when the module was introduced.',
+ 'changed' => '(string) The Jetpack version when the module was changed.',
+ 'free' => '(boolean) The module\'s Free or Paid status.',
+ 'module_tags' => '(array) The module\'s tags.'
+ );
+
+ protected function result() {
+
+ $modules = $this->get_modules();
+
+ if ( ! $this->bulk && ! empty( $modules ) ) {
+ return array_pop( $modules );
+ }
+
+ return array( 'modules' => $modules );
+
+ }
+
+ /**
+ * Walks through either the submitted modules or list of themes and creates the global array
+ * @param $theme
+ *
+ * @return bool
+ */
+ protected function validate_input( $module) {
+ $args = $this->input();
+ // lets set what modules were requested, and validate them
+ if ( ! isset( $module ) || empty( $module ) ) {
+
+ if ( ! $args['modules'] || empty( $args['modules'] ) ) {
+ return new WP_Error( 'missing_module', __( 'You are required to specify a module.', 'jetpack' ), 400 );
+ }
+ if ( is_array( $args['modules'] ) ) {
+ $this->modules = $args['modules'];
+ } else {
+ $this->modules[] = $args['modules'];
+ }
+ } else {
+ $this->modules[] = urldecode( $module );
+ $this->bulk = false;
+ }
+
+ if ( is_wp_error( $error = $this->validate_modules() ) ) {
+ return $error;
+ }
+
+ return parent::validate_input( $module );
+ }
+
+ /**
+ * Walks through submitted themes to make sure they are valid
+ * @return bool|WP_Error
+ */
+ protected function validate_modules() {
+ foreach ( $this->modules as $module ) {
+ if ( ! Jetpack::is_module( $module ) ) {
+ return new WP_Error( 'unknown_jetpack_module', sprintf( __( 'Module not found: `%s`.', 'jetpack' ), $module ), 404 );
+ }
+ }
+ return true;
+ }
+
+ protected static function format_module( $module_slug ) {
+ $module_data = Jetpack::get_module( $module_slug );
+
+ $module = array();
+ $module['id'] = $module_slug;
+ $module['active'] = Jetpack::is_module_active( $module_slug );
+ $module['name'] = $module_data['name'];
+ $module['short_description'] = $module_data['description'];
+ $module['sort'] = $module_data['sort'];
+ $module['introduced'] = $module_data['introduced'];
+ $module['changed'] = $module_data['changed'];
+ $module['free'] = $module_data['free'];
+ $module['module_tags'] = $module_data['module_tags'];
+
+ // Fetch the HTML formatted long description
+ ob_start();
+ if ( Jetpack::is_active() && has_action( 'jetpack_module_more_info_connected_' . $module_slug ) ) {
+ do_action( 'jetpack_module_more_info_connected_' . $module_slug );
+ } else {
+ do_action( 'jetpack_module_more_info_' . $module_slug );
+ }
+ $module['description'] = ob_get_clean();
+
+ return $module;
+ }
+
+ /**
+ * Format a list of modules for public display, using the supplied offset and limit args
+ * @uses WPCOM_JSON_API_Endpoint::query_args()
+ * @return array Public API modules objects
+ */
+ protected function get_modules() {
+ $modules = array_values( $this->modules );
+ // do offset & limit - we've already returned a 400 error if they're bad numbers
+ $args = $this->query_args();
+
+ if ( isset( $args['offset'] ) )
+ $modules = array_slice( $modules, (int) $args['offset'] );
+ if ( isset( $args['limit'] ) )
+ $modules = array_slice( $modules, 0, (int) $args['limit'] );
+
+ return array_map( array( $this, 'format_module' ), $modules );
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-get-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-get-endpoint.php
new file mode 100644
index 00000000..28a70dba
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-get-endpoint.php
@@ -0,0 +1,6 @@
+<?php
+
+class Jetpack_JSON_API_Modules_Get_Endpoint extends Jetpack_JSON_API_Modules_Endpoint {
+ // GET /sites/%s/jetpack/modules/%s
+ protected $needed_capabilities = 'jetpack_manage_modules';
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-list-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-list-endpoint.php
new file mode 100644
index 00000000..2ed4dbdd
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-list-endpoint.php
@@ -0,0 +1,13 @@
+<?php
+
+class Jetpack_JSON_API_Modules_List_Endpoint extends Jetpack_JSON_API_Modules_Endpoint {
+ // GET /sites/%s/jetpack/modules
+
+ protected $needed_capabilities = 'jetpack_manage_modules';
+
+ public function validate_input( $module ) {
+ $this->modules = Jetpack::get_available_modules();
+ return true;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-modify-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-modify-endpoint.php
new file mode 100644
index 00000000..e1562f50
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-modify-endpoint.php
@@ -0,0 +1,62 @@
+<?php
+
+class Jetpack_JSON_API_Modules_Modify_Endpoint extends Jetpack_JSON_API_Modules_Endpoint {
+ // POST /sites/%s/jetpack/modules/%s/activate
+ // POST /sites/%s/jetpack/modules/%s
+ // POST /sites/%s/jetpack/modules
+
+ protected $needed_capabilities = 'activate_plugins';
+ protected $action = 'default_action';
+
+ public function default_action() {
+ $args = $this->input();
+ if ( isset( $args['active'] ) && is_bool( $args['active'] ) ) {
+ if ( $args['active'] ) {
+ return $this->activate_module();
+ } else {
+ return $this->deactivate_module();
+ }
+ }
+
+ return true;
+ }
+
+ protected function activate_module() {
+ foreach ( $this->modules as $module ) {
+ if ( Jetpack::is_module_active( $module ) ) {
+ $error = $this->log[ $module ][] = __( 'The Jetpack Module is already activated.', 'jetpack' );
+ continue;
+ }
+ $result = Jetpack::activate_module( $module, false, false );
+ if ( false === $result || ! Jetpack::is_module_active( $module ) ) {
+ $error = $this->log[ $module ][] = __( 'There was an error while activating the module.', 'jetpack' );
+ }
+ }
+
+ if ( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'activation_error', $error, 400 );
+ }
+
+ return true;
+ }
+
+ protected function deactivate_module() {
+ foreach ( $this->modules as $module ) {
+ if ( ! Jetpack::is_module_active( $module ) ) {
+ $error = $this->log[ $module ][] = __( 'The Jetpack Module is already deactivated.', 'jetpack' );
+ continue;
+ }
+ $result = Jetpack::deactivate_module( $module );
+ if ( false === $result || Jetpack::is_module_active( $module ) ) {
+ $error = $this->log[ $module ][] = __( 'There was an error while deactivating the module.', 'jetpack' );
+ }
+ }
+
+ if ( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'deactivation_error', $error, 400 );
+ }
+
+ return true;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-delete-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-delete-endpoint.php
new file mode 100644
index 00000000..97de744c
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-delete-endpoint.php
@@ -0,0 +1,33 @@
+<?php
+
+class Jetpack_JSON_API_Plugins_Delete_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
+
+ // POST /sites/%s/plugins/%s/delete
+ protected $needed_capabilities = 'delete_plugins';
+ protected $action = 'delete';
+
+ protected function delete() {
+
+ foreach( $this->plugins as $plugin ) {
+
+ if ( Jetpack::is_plugin_active( $plugin ) ) {
+ $error = $this->log[ $plugin ][] ='You cannot delete a plugin while it is active on the main site.';
+ continue;
+ }
+
+ $result = delete_plugins ( array( $plugin ) );
+ if ( is_wp_error( $result ) ) {
+ $error = $this->log[ $plugin ][] = $result->get_error_message();
+ } else {
+ $this->log[ $plugin ][] = 'Plugin deleted';
+ }
+ }
+
+ if( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'delete_plugin_error', $error, 400 );
+ }
+
+ return true;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php
new file mode 100644
index 00000000..9fb4fc28
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * Base class for working with plugins.
+ */
+abstract class Jetpack_JSON_API_Plugins_Endpoint extends Jetpack_JSON_API_Endpoint {
+
+ protected $plugins = array();
+
+ protected $network_wide = false;
+
+ protected $bulk = true;
+ protected $log;
+
+ static $_response_format = array(
+ 'id' => '(safehtml) The plugin\'s ID',
+ 'slug' => '(safehtml) The plugin\'s .org slug',
+ 'active' => '(boolean) The plugin status.',
+ 'update' => '(object) The plugin update info.',
+ 'name' => '(safehtml) The name of the plugin.',
+ 'plugin_url' => '(url) Link to the plugin\'s web site.',
+ 'version' => '(safehtml) The plugin version number.',
+ 'description' => '(safehtml) Description of what the plugin does and/or notes from the author',
+ 'author' => '(safehtml) The author\'s name',
+ 'author_url' => '(url) The authors web site address',
+ 'network' => '(boolean) Whether the plugin can only be activated network wide.',
+ 'autoupdate' => '(boolean) Whether the plugin is automatically updated',
+ 'next_autoupdate' => '(string) Y-m-d H:i:s for next scheduled update event',
+ 'log' => '(array:safehtml) An array of update log strings.',
+ );
+
+ protected function result() {
+
+ $plugins = $this->get_plugins();
+
+ if ( ! $this->bulk && ! empty( $plugins ) ) {
+ return array_pop( $plugins );
+ }
+
+ return array( 'plugins' => $plugins );
+
+ }
+
+ protected function validate_input( $plugin ) {
+
+ if ( is_wp_error( $error = parent::validate_input( $plugin ) ) ) {
+ return $error;
+ }
+
+ if ( is_wp_error( $error = $this->validate_network_wide() ) ) {
+ return $error;
+ }
+
+ $args = $this->input();
+ // find out what plugin, or plugins we are dealing with
+ // validate the requested plugins
+ if ( ! isset( $plugin ) || empty( $plugin ) ) {
+ if ( ! $args['plugins'] || empty( $args['plugins'] ) ) {
+ return new WP_Error( 'missing_plugin', __( 'You are required to specify a plugin.', 'jetpack' ), 400 );
+ }
+ if ( is_array( $args['plugins'] ) ) {
+ $this->plugins = $args['plugins'];
+ } else {
+ $this->plugins[] = $args['plugins'];
+ }
+ } else {
+ $this->bulk = false;
+ $this->plugins[] = urldecode( $plugin );
+ }
+
+ if ( is_wp_error( $error = $this->validate_plugins() ) ) {
+ return $error;
+ };
+
+ return true;
+ }
+
+ /**
+ * Walks through submitted plugins to make sure they are valid
+ * @return bool|WP_Error
+ */
+ protected function validate_plugins() {
+ if ( empty( $this->plugins ) || ! is_array( $this->plugins ) ) {
+ return new WP_Error( 'missing_plugins', __( 'No plugins found.', 'jetpack' ));
+ }
+ foreach( $this->plugins as $index => $plugin ) {
+ if ( ! preg_match( "/\.php$/", $plugin ) ) {
+ $plugin = $plugin . '.php';
+ $this->plugins[ $index ] = $plugin;
+ }
+ if ( is_wp_error( $error = $this->validate_plugin( $plugin ) ) ) {
+ return $error;
+ }
+ }
+ return true;
+ }
+
+ protected function format_plugin( $plugin_file, $plugin_data ) {
+ $plugin = array();
+ $plugin['id'] = preg_replace("/(.+)\.php$/", "$1", $plugin_file );
+ $plugin['slug'] = $this->get_plugin_slug( $plugin_file );
+ $plugin['active'] = Jetpack::is_plugin_active( $plugin_file );
+ $plugin['name'] = $plugin_data['Name'];
+ $plugin['plugin_url'] = $plugin_data['PluginURI'];
+ $plugin['version'] = $plugin_data['Version'];
+ $plugin['description'] = $plugin_data['Description'];
+ $plugin['author'] = $plugin_data['Author'];
+ $plugin['author_url'] = $plugin_data['AuthorURI'];
+ $plugin['network'] = $plugin_data['Network'];
+ $plugin['update'] = $this->get_plugin_updates( $plugin_file );
+ $plugin['next_autoupdate'] = date( 'Y-m-d H:i:s', wp_next_scheduled( 'wp_maybe_auto_update' ) );
+ $plugin['autoupdate'] = in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins', array() ) );
+ if ( ! empty ( $this->log[ $plugin_file ] ) ) {
+ $plugin['log'] = $this->log[ $plugin_file ];
+ }
+ return $plugin;
+ }
+
+ protected function get_plugins() {
+ $plugins = array();
+ $installed_plugins = get_plugins();
+ foreach( $this->plugins as $plugin ) {
+ if ( ! isset( $installed_plugins[ $plugin ] ) )
+ continue;
+ $plugins[] = $this->format_plugin( $plugin, $installed_plugins[ $plugin ] );
+ }
+ $args = $this->query_args();
+
+ if ( isset( $args['offset'] ) ) {
+ $plugins = array_slice( $plugins, (int) $args['offset'] );
+ }
+ if ( isset( $args['limit'] ) ) {
+ $plugins = array_slice( $plugins, 0, (int) $args['limit'] );
+ }
+
+ return $plugins;
+ }
+
+ protected function validate_network_wide() {
+ $args = $this->input();
+
+ if ( isset( $args['network_wide'] ) && $args['network_wide'] ) {
+ $this->network_wide = true;
+ }
+
+ if ( $this->network_wide && ! current_user_can( 'manage_network_plugins' ) ) {
+ return new WP_Error( 'unauthorized', __( 'This user is not authorized to manage plugins network wide.', 'jetpack' ), 403 );
+ }
+
+ return true;
+ }
+
+
+ protected function validate_plugin( $plugin ) {
+ if ( ! isset( $plugin) || empty( $plugin ) ) {
+ return new WP_Error( 'missing_plugin', __( 'You are required to specify a plugin to activate.', 'jetpack' ), 400 );
+ }
+
+ if ( is_wp_error( $error = validate_plugin( urldecode( $plugin ) ) ) ) {
+ return new WP_Error( 'unknown_plugin', $error->get_error_messages() , 404 );
+ }
+
+ return true;
+ }
+
+ protected function get_plugin_updates( $plugin_file ) {
+ $plugin_updates = get_plugin_updates();
+ if ( isset( $plugin_updates[ $plugin_file ] ) ){
+ return $plugin_updates[ $plugin_file ]->update;
+ }
+ return null;
+ }
+
+ protected function get_plugin_slug( $plugin_file ) {
+ $update_plugins = get_site_transient( 'update_plugins' );
+ if ( isset( $update_plugins->no_update ) ) {
+ if ( isset( $update_plugins->no_update[ $plugin_file ] ) ) {
+ $slug = $update_plugins->no_update[ $plugin_file ]->slug;
+ }
+ }
+
+ if ( empty( $slug ) && isset( $update_plugins->response ) ) {
+ if ( isset( $update_plugins->response[ $plugin_file ] ) ) {
+ $slug = $update_plugins->response[ $plugin_file ]->slug;
+ }
+ }
+
+ // Try to infer from the plugin file if not cached
+ if ( empty( $slug) ) {
+ $slug = dirname( $plugin_file );
+ if ( '.' === $slug ) {
+ $slug = preg_replace("/(.+)\.php$/", "$1", $plugin_file );
+ }
+ }
+ return $slug;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-get-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-get-endpoint.php
new file mode 100644
index 00000000..69f3a64c
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-get-endpoint.php
@@ -0,0 +1,6 @@
+<?php
+
+class Jetpack_JSON_API_Plugins_Get_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
+ // GET /sites/%s/plugins/%s
+ protected $needed_capabilities = 'activate_plugins';
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-install-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-install-endpoint.php
new file mode 100644
index 00000000..081a5add
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-install-endpoint.php
@@ -0,0 +1,83 @@
+<?php
+
+include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+include_once ABSPATH . 'wp-admin/includes/file.php';
+
+class Jetpack_JSON_API_Plugins_Install_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
+
+ // POST /sites/%s/plugins/%s/new
+ protected $needed_capabilities = 'install_plugins';
+ protected $action = 'install';
+ protected $download_links = array();
+
+ protected function install() {
+ foreach ( $this->plugins as $index => $slug ) {
+
+ $skin = new Automatic_Upgrader_Skin();
+ $upgrader = new Plugin_Upgrader( $skin );
+
+ $result = $upgrader->install( $this->download_links[ $slug ] );
+
+ if ( ! $this->bulk && is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ $plugin = self::get_plugin_id_by_slug( $slug );
+
+ if ( ! $plugin ) {
+ $error = $this->log[ $slug ]['error'] = __( 'There was an error installing your plugin', 'jetpack' );
+ }
+
+ if ( ! $this->bulk && ! $result ) {
+ $error = $this->log[ $slug ]['error'] = __( 'An unknown error occurred during installation', 'jetpack' );
+ }
+
+ $this->log[ $plugin ][] = $upgrader->skin->get_upgrade_messages();
+ }
+
+ if ( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'install_error', $this->log[ $slug ]['error'], 400 );
+ }
+
+ // replace the slug with the actual plugin id
+ $this->plugins[ $index ] = $plugin;
+
+ return true;
+ }
+
+ protected function validate_plugins() {
+ if ( empty( $this->plugins ) || ! is_array( $this->plugins ) ) {
+ return new WP_Error( 'missing_plugins', __( 'No plugins found.', 'jetpack' ) );
+ }
+ foreach( $this->plugins as $index => $slug ) {
+
+ // make sure it is not already installed
+ if ( self::get_plugin_id_by_slug( $slug ) ) {
+ return new WP_Error( 'plugin_already_installed', __( 'The plugin is already installed', 'jetpack' ) );
+ }
+
+ $response = wp_remote_get( "http://api.wordpress.org/plugins/info/1.0/$slug" );
+ $plugin_data = unserialize( $response['body'] );
+ if ( is_wp_error( $plugin_data ) ) {
+ return $plugin_data;
+ }
+
+ $this->download_links[ $slug ] = $plugin_data->download_link;
+
+ }
+ return true;
+ }
+
+ protected static function get_plugin_id_by_slug( $slug ) {
+ $plugins = get_plugins();
+ if( ! is_array( $plugins ) ) {
+ return false;
+ }
+ foreach( $plugins as $id => $plugin_data ) {
+ if( strpos( $id, $slug ) !== false ) {
+ return $id;
+ }
+ }
+ return false;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php
new file mode 100644
index 00000000..259aa846
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php
@@ -0,0 +1,15 @@
+<?php
+
+class Jetpack_JSON_API_Plugins_List_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
+ // GET /sites/%s/plugins
+
+ protected $needed_capabilities = 'activate_plugins';
+
+ public function validate_input( $plugin ) {
+ wp_update_plugins();
+ $this->plugins = array_keys( get_plugins() );
+ return true;
+ }
+
+}
+
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-endpoint.php
new file mode 100644
index 00000000..4fbddaba
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-endpoint.php
@@ -0,0 +1,178 @@
+<?php
+
+class Jetpack_JSON_API_Plugins_Modify_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
+ // POST /sites/%s/plugins/%s
+ // POST /sites/%s/plugins
+
+ protected $needed_capabilities = 'activate_plugins';
+ protected $action = 'default_action';
+ protected $expected_actions = array( 'update', 'install', 'delete' );
+
+ public function callback( $path = '', $blog_id = 0, $object = null ) {
+ Jetpack_JSON_API_Endpoint::validate_input( $object );
+ switch ( $this->action ) {
+ case 'update' :
+ $this->needed_capabilities = 'update_plugins';
+ break;
+ case 'install' :
+ $this->needed_capabilities = 'install_plugins';
+ break;
+ }
+ if ( isset( $args['autoupdate'] ) ) {
+ $this->needed_capabilities = 'update_plugins';
+ }
+
+ return parent::callback( $path, $blog_id, $object );
+ }
+
+ public function default_action() {
+ $args = $this->input();
+
+ if ( isset( $args['autoupdate'] ) && is_bool( $args['autoupdate'] ) ) {
+ if ( $args['autoupdate'] ) {
+ $this->autoupdate_on();
+ } else {
+ $this->autoupdate_off();
+ }
+ }
+
+ if ( isset( $args['active'] ) && is_bool( $args['active'] ) ) {
+ if ( $args['active'] ) {
+ return $this->activate();
+ } else {
+ return $this->deactivate();
+ }
+ }
+
+ return true;
+ }
+
+ protected function autoupdate_on() {
+ $autoupdate_plugins = Jetpack_Options::get_option( 'autoupdate_plugins', array() );
+ $autoupdate_plugins = array_unique( array_merge( $autoupdate_plugins, $this->plugins) );
+ Jetpack_Options::update_option( 'autoupdate_plugins', $autoupdate_plugins );
+ }
+
+ protected function autoupdate_off() {
+ $autoupdate_plugins = Jetpack_Options::get_option( 'autoupdate_plugins', array() );
+ $autoupdate_plugins = array_diff( $autoupdate_plugins, $this->plugins );
+ Jetpack_Options::update_option( 'autoupdate_plugins', $autoupdate_plugins );
+ }
+
+ protected function activate() {
+ foreach ( $this->plugins as $plugin ) {
+ if ( ( ! $this->network_wide && Jetpack::is_plugin_active( $plugin ) ) || is_plugin_active_for_network( $plugin ) ) {
+ $this->log[ $plugin ]['error'] = __( 'The Plugin is already active.', 'jetpack' );
+ $has_errors = true;
+ continue;
+ }
+
+ if( ! $this->network_wide && is_network_only_plugin( $plugin ) ) {
+ $this->log[ $plugin ]['error'] = __( 'Plugin can only be Network Activated', 'jetpack' );
+ $has_errors = true;
+ continue;
+ }
+
+ $result = activate_plugin( $plugin, '', $this->network_wide );
+
+ if ( is_wp_error( $result ) ) {
+ $this->log[ $plugin ]['error'] = $result->get_error_messages();
+ $has_errors = true;
+ continue;
+ }
+
+ $success = Jetpack::is_plugin_active( $plugin );
+ if ( $success && $this->network_wide ) {
+ $success &= is_plugin_active_for_network( $plugin );
+ }
+
+ if ( ! $success ) {
+ $this->log[ $plugin ]['error'] = $result->get_error_messages;
+ $has_errors = true;
+ continue;
+ }
+ $this->log[ $plugin ][] = __( 'Plugin activated.', 'jetpack' );
+ }
+ if ( ! $this->bulk && isset( $has_errors ) ) {
+ $plugin = $this->plugins[0];
+ return new WP_Error( 'activation_error', $this->log[ $plugin ]['error'] );
+ }
+ }
+
+ protected function deactivate() {
+ foreach( $this->plugins as $plugin ) {
+ if ( ! Jetpack::is_plugin_active( $plugin ) ) {
+ $error = $this->log[ $plugin ]['error'] = __( 'The Plugin is already deactivated.', 'jetpack' );
+ continue;
+ }
+
+ deactivate_plugins( $plugin, false, $this->network_wide );
+
+ $success = ! Jetpack::is_plugin_active( $plugin );
+ if ( $success && $this->network_wide ) {
+ $success &= ! is_plugin_active_for_network( $plugin );
+ }
+
+ if ( ! $success ) {
+ $error = $this->log[ $plugin ]['error'] = __( 'There was an error deactivating your plugin', 'jetpack' );
+ continue;
+ }
+ $this->log[ $plugin ][] = __( 'Plugin deactivated.', 'jetpack' );
+ }
+ if ( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'deactivation_error', $error );
+ }
+ }
+
+ protected function update() {
+
+ wp_clean_plugins_cache();
+ ob_start();
+ wp_update_plugins(); // Check for Plugin updates
+ ob_end_clean();
+
+ $update_plugins = get_site_transient( 'update_plugins' );
+
+ if ( isset( $update_plugins->response ) ) {
+ $plugin_updates_needed = array_keys( $update_plugins->response );
+ } else {
+ $plugin_updates_needed = array();
+ }
+
+ $update_attempted = false;
+
+ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+
+ // unhook this functions that output things before we send our response header.
+ remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
+ remove_action( 'upgrader_process_complete', 'wp_version_check' );
+ remove_action( 'upgrader_process_complete', 'wp_update_themes' );
+
+ foreach ( $this->plugins as $plugin ) {
+
+ if ( ! in_array( $plugin, $plugin_updates_needed ) ) {
+ $this->log[ $plugin ][] = __( 'No update needed', 'jetpack' );
+ continue;
+ }
+
+ $update_attempted = true;
+
+ // Object created inside the for loop to clean the messages for each plugin
+ $skin = new Automatic_Upgrader_Skin();
+ // The Automatic_Upgrader_Skin skin shouldn't output anything.
+ $upgrader = new Plugin_Upgrader( $skin );
+ $upgrader->init();
+ // This avoids the plugin to be deactivated.
+ defined( 'DOING_CRON' ) or define( 'DOING_CRON', true );
+ $result = $upgrader->upgrade( $plugin );
+
+ $this->log[ $plugin ][] = $upgrader->skin->get_upgrade_messages();
+ }
+
+ if ( ! $this->bulk && ! $result && $update_attempted ) {
+ return new WP_Error( 'update_fail', __( 'There was an error updating your plugin', 'jetpack' ), 400 );
+ }
+
+ return $this->default_action();
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-sync-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-sync-endpoint.php
new file mode 100644
index 00000000..8f6b1dd6
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-sync-endpoint.php
@@ -0,0 +1,13 @@
+<?php
+
+class Jetpack_JSON_API_Sync_Endpoint extends Jetpack_JSON_API_Endpoint {
+ // POST /sites/%s/sync
+ protected $needed_capabilities = 'manage_options';
+
+ protected function result() {
+ Jetpack::init();
+ do_action( 'jetpack_sync_all_registered_options' );
+ $result['scheduled'] = true;
+ return $result;
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-active-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-active-endpoint.php
new file mode 100644
index 00000000..2461f16a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-active-endpoint.php
@@ -0,0 +1,49 @@
+<?php
+
+class Jetpack_JSON_API_Themes_Active_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
+ // GET /sites/%s/themes/mine => current theme
+ // POST /sites/%s/themes/mine => switch theme
+ public function callback( $path = '', $blog_id = 0 ) {
+
+ if ( is_wp_error( $error = $this->validate_call( $blog_id, 'switch_themes', true ) ) ) {
+ return $error;
+ }
+
+ if ( 'POST' === $this->api->method )
+ return $this->switch_theme();
+ else
+ return $this->get_current_theme();
+ }
+
+ protected function switch_theme() {
+ $args = $this->input();
+
+ if ( ! isset( $args['theme'] ) || empty( $args['theme'] ) ) {
+ return new WP_Error( 'missing_theme', __( 'You are required to specify a theme to switch to.', 'jetpack' ), 400 );
+ }
+
+ $theme_slug = $args['theme'];
+
+ if ( ! $theme_slug ) {
+ return new WP_Error( 'theme_not_found', __( 'Theme is empty.', 'jetpack' ), 404 );
+ }
+
+ $theme = wp_get_theme( $theme_slug );
+
+ if ( ! $theme->exists() ) {
+ return new WP_Error( 'theme_not_found', __( 'The specified theme was not found.', 'jetpack' ), 404 );
+ }
+
+ if ( ! $theme->is_allowed() ) {
+ return new WP_Error( 'theme_not_found', __( 'You are not allowed to switch to this theme', 'jetpack' ), 403 );
+ }
+
+ switch_theme( $theme_slug );
+
+ return $this->get_current_theme();
+ }
+
+ protected function get_current_theme() {
+ return $this->format_theme( wp_get_theme() );
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-delete-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-delete-endpoint.php
new file mode 100644
index 00000000..ff567113
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-delete-endpoint.php
@@ -0,0 +1,41 @@
+<?php
+
+class Jetpack_JSON_API_Themes_Delete_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
+
+ // POST /sites/%s/plugins/%s/delete
+ protected $needed_capabilities = 'delete_themes';
+ protected $action = 'delete';
+
+ protected function delete() {
+
+ foreach( $this->themes as $theme ) {
+
+ // Don't delete an active child theme
+ if ( is_child_theme() && $theme == get_stylesheet() ) {
+ $error = $this->log[ $theme ]['error'] = 'You cannot delete a theme while it is active on the main site.';
+ continue;
+ }
+
+ if( $theme == get_template() ) {
+ $error = $this->log[ $theme ]['error'] = 'You cannot delete a theme while it is active on the main site.';
+ continue;
+ }
+
+ $result = delete_theme( $theme );
+
+ if ( is_wp_error( $result ) ) {
+ $error = $this->log[ $theme ]['error'] = $result->get_error_messages;
+ } else {
+ $this->log[ $theme ][] = 'Theme deleted';
+ }
+
+ }
+
+ if( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'delete_theme_error', $error, 400 );
+ }
+
+ return true;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-endpoint.php
new file mode 100644
index 00000000..c479a4c4
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-endpoint.php
@@ -0,0 +1,158 @@
+<?php
+
+
+// THEMES
+
+/**
+ * Base class for working with themes, has useful helper functions.
+ */
+abstract class Jetpack_JSON_API_Themes_Endpoint extends Jetpack_JSON_API_Endpoint {
+
+ protected $themes = array();
+
+ protected $bulk = true;
+ protected $log;
+
+ static $_response_format = array(
+ 'id' => '(string) The theme\'s ID.',
+ 'screenshot' => '(string) A theme screenshot URL',
+ 'name' => '(string) The name of the theme.',
+ 'description' => '(string) A description of the theme.',
+ 'tags' => '(array) Tags indicating styles and features of the theme.',
+ 'log' => '(array) An array of log strings',
+ 'autoupdate' => '(bool) Whether the theme is automatically updated',
+ );
+
+ protected function result() {
+
+ $themes = $this->get_themes();
+
+ if ( ! $this->bulk && ! empty( $themes ) ) {
+ return array_pop( $themes );
+ }
+
+ return array( 'themes' => $themes );
+
+ }
+
+ /**
+ * Walks through either the submitted theme or list of themes and creates the global array
+ * @param $theme
+ *
+ * @return bool
+ */
+ protected function validate_input( $theme ) {
+ $args = $this->input();
+ // lets set what themes were requested, and validate them
+ if ( ! isset( $theme ) || empty( $theme ) ) {
+
+ if ( ! $args['themes'] || empty( $args['themes'] ) ) {
+ return new WP_Error( 'missing_theme', __( 'You are required to specify a theme to update.', 'jetpack' ), 400 );
+ }
+ if ( is_array( $args['themes'] ) ) {
+ $this->themes = $args['themes'];
+ } else {
+ $this->themes[] = $args['themes'];
+ }
+ } else {
+ $this->themes[] = urldecode( $theme );
+ $this->bulk = false;
+ }
+
+ if ( is_wp_error( $error = $this->validate_themes() ) ) {
+ return error;
+ }
+
+ return parent::validate_input( $theme );
+ }
+
+ /**
+ * Walks through submitted themes to make sure they are valid
+ * @return bool|WP_Error
+ */
+ protected function validate_themes() {
+ foreach ( $this->themes as $theme ) {
+ if ( is_wp_error( $error = wp_get_theme( $theme )->errors() ) ) {
+ return new WP_Error( 'unknown_theme', $error->get_error_messages() , 404 );
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Format a theme for the public API
+ * @param object $theme WP_Theme object
+ * @return array Named array of theme info used by the API
+ */
+ protected function format_theme( $theme ) {
+
+ if ( ! ( $theme instanceof WP_Theme ) ) {
+ $theme = wp_get_theme( $theme );
+ }
+
+ $fields = array(
+ 'name' => 'Name',
+ 'description' => 'Description',
+ 'tags' => 'Tags',
+ 'version' => 'Version'
+ );
+
+ $id = $theme->get_stylesheet();
+ $formatted_theme = array(
+ 'id' => $id,
+ 'screenshot' => jetpack_photon_url( $theme->get_screenshot(), array(), 'network_path' )
+ );
+
+ foreach( $fields as $key => $field ) {
+ $formatted_theme[ $key ] = $theme->get( $field );
+ }
+
+ $update_themes = get_site_transient( 'update_themes' );
+ $formatted_theme['update'] = ( isset( $update_themes->response[ $id ] ) ) ? $update_themes->response[ $id ] : null;
+
+ $autoupdate_themes = Jetpack_Options::get_option( 'autoupdate_themes', array() );
+
+ $autoupdate = in_array( $id, $autoupdate_themes );
+
+ $formatted_theme['autoupdate'] = $autoupdate;
+
+ if( isset( $this->log[ $id ] ) ) {
+ $formatted_theme['log'] = $this->log[ $id ];
+ }
+
+ return $formatted_theme;
+ }
+
+ /**
+ * Checks the query_args our collection endpoint was passed to ensure that it's in the proper bounds.
+ * @return bool|WP_Error a WP_Error object if the args are out of bounds, true if things are good.
+ */
+ protected function check_query_args() {
+ $args = $this->query_args();
+ if ( $args['offset'] < 0 )
+ return new WP_Error( 'invalid_offset', __( 'Offset must be greater than or equal to 0.', 'jetpack' ), 400 );
+ if ( $args['limit'] < 0 )
+ return new WP_Error( 'invalid_limit', __( 'Limit must be greater than or equal to 0.', 'jetpack' ), 400 );
+ return true;
+ }
+
+ /**
+ * Format a list of themes for public display, using the supplied offset and limit args
+ * @uses WPCOM_JSON_API_Endpoint::query_args()
+ * @return array Public API theme objects
+ */
+ protected function get_themes() {
+ // ditch keys
+ $themes = array_values( $this->themes );
+ // do offset & limit - we've already returned a 400 error if they're bad numbers
+ $args = $this->query_args();
+
+ if ( isset( $args['offset'] ) )
+ $themes = array_slice( $themes, (int) $args['offset'] );
+ if ( isset( $args['limit'] ) )
+ $themes = array_slice( $themes, 0, (int) $args['limit'] );
+
+ return array_map( array( $this, 'format_theme' ), $themes );
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-get-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-get-endpoint.php
new file mode 100644
index 00000000..20a4ac4e
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-get-endpoint.php
@@ -0,0 +1,6 @@
+<?php
+
+class Jetpack_JSON_API_Themes_Get_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
+ // GET /sites/%s/themes/%s
+ protected $needed_capabilities = 'activate_themes';
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-install-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-install-endpoint.php
new file mode 100644
index 00000000..e994816a
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-install-endpoint.php
@@ -0,0 +1,82 @@
+<?php
+
+include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+include_once ABSPATH . 'wp-admin/includes/file.php';
+
+class Jetpack_JSON_API_Themes_Install_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
+
+ // POST /sites/%s/themes/%s/install
+ protected $needed_capabilities = 'install_themes';
+ protected $action = 'install';
+ protected $download_links = array();
+
+ protected function install() {
+
+ foreach ( $this->themes as $theme ) {
+
+ $skin = new Automatic_Upgrader_Skin();
+ $upgrader = new Theme_Upgrader( $skin );
+
+ $result = $upgrader->install( $this->download_links[ $theme ] );
+
+ if ( ! $this->bulk && is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ if ( ! $result ) {
+ $error = $this->log[ $theme ]['error'] = __( 'An unknown error occurred during installation', 'jetpack' );
+ }
+
+ elseif ( ! self::is_installed_theme( $theme ) ) {
+ $error = $this->log[ $theme ]['error'] = __( 'There was an error installing your theme', 'jetpack' );
+ }
+
+ else {
+ $this->log[ $theme ][] = $upgrader->skin->get_upgrade_messages();
+ }
+ }
+
+ if ( ! $this->bulk && isset( $error ) ) {
+ return new WP_Error( 'install_error', $error, 400 );
+ }
+
+ return true;
+ }
+
+ protected function validate_themes() {
+ if ( empty( $this->themes ) || ! is_array( $this->themes ) ) {
+ return new WP_Error( 'missing_themes', __( 'No themes found.', 'jetpack' ) );
+ }
+ foreach( $this->themes as $index => $theme ) {
+
+ if ( self::is_installed_theme( $theme ) ) {
+ return new WP_Error( 'theme_already_installed', __( 'The theme is already installed', 'jetpack' ) );
+ }
+
+ $params = (object) array( 'slug' => $theme );
+ $url = 'https://api.wordpress.org/themes/info/1.0/';
+ $args = array(
+ 'body' => array(
+ 'action' => 'theme_information',
+ 'request' => serialize( $params ),
+ )
+ );
+ $response = wp_remote_post( $url, $args );
+ $theme_data = unserialize( $response['body'] );
+ if ( is_wp_error( $theme_data ) ) {
+ return $theme_data;
+ }
+ $this->download_links[ $theme ] = $theme_data->download_link;
+
+ }
+ return true;
+ }
+
+ protected static function is_installed_theme( $theme ) {
+ $wp_theme = wp_get_theme( $theme );
+ return $wp_theme->exists();
+ }
+
+
+}
+
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-list-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-list-endpoint.php
new file mode 100644
index 00000000..526cf4d7
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-list-endpoint.php
@@ -0,0 +1,13 @@
+<?php
+
+class Jetpack_JSON_API_Themes_List_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
+ // GET /sites/%s/themes
+
+ protected $needed_capabilities = 'switch_themes';
+
+ public function validate_input( $theme ) {
+ $this->themes = wp_get_themes( array( 'allowed' => true ) );
+ return true;
+ }
+
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-modify-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-modify-endpoint.php
new file mode 100644
index 00000000..4719df6c
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-modify-endpoint.php
@@ -0,0 +1,58 @@
+<?php
+
+class Jetpack_JSON_API_Themes_Modify_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
+ // POST /sites/%s/themes/%s
+ // POST /sites/%s/themes
+
+ protected $needed_capabilities = 'update_themes';
+ protected $action = 'default_action';
+ protected $expected_actions = array( 'update' );
+
+ public function default_action() {
+ $args = $this->input();
+ if ( isset( $args['autoupdate'] ) && is_bool( $args['autoupdate'] ) ) {
+ if ( $args['autoupdate'] ) {
+ $this->autoupdate_on();
+ } else {
+ $this->autoupdate_off();
+ }
+ }
+
+ return true;
+ }
+
+ function autoupdate_on() {
+ $autoupdate_themes = Jetpack_Options::get_option( 'autoupdate_themes', array() );
+ $autoupdate_themes = array_unique( array_merge( $autoupdate_themes, $this->themes ) );
+ Jetpack_Options::update_option( 'autoupdate_themes', $autoupdate_themes );
+ }
+
+ function autoupdate_off() {
+ $autoupdate_themes = Jetpack_Options::get_option( 'autoupdate_themes', array() );
+ $autoupdate_themes = array_diff( $autoupdate_themes, $this->themes );
+ Jetpack_Options::update_option( 'autoupdate_themes', $autoupdate_themes );
+ }
+
+ function update() {
+ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+
+ // Clear the cache.
+ wp_update_themes();
+
+ foreach ( $this->themes as $theme ) {
+ // Objects created inside the for loop to clean the messages for each theme
+ $skin = new Automatic_Upgrader_Skin();
+ $upgrader = new Theme_Upgrader( $skin );
+ $upgrader->init();
+ $result = $upgrader->upgrade( $theme );
+ $this->log[ $theme ][] = $upgrader->skin->get_upgrade_messages();
+ }
+
+ if ( ! $this->bulk && ! $result ) {
+ return new WP_Error( 'update_fail', __( 'There was an error updating your theme', 'jetpack' ), 400 );
+ }
+
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-updates-status-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-updates-status-endpoint.php
new file mode 100644
index 00000000..48f9ae9d
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-updates-status-endpoint.php
@@ -0,0 +1,34 @@
+<?php
+
+class Jetpack_JSON_API_Updates_Status extends Jetpack_JSON_API_Endpoint {
+ // GET /sites/%s/updates
+ protected $needed_capabilities = 'manage_options';
+
+ protected function result() {
+
+ wp_update_themes();
+ wp_update_plugins();
+
+ $update_data = wp_get_update_data();
+ if ( ! isset( $update_data['counts'] ) ) {
+ return new WP_Error( 'get_update_data_error', __( 'There was an error while getting the update data for this site.', 'jetpack' ), 500 );
+ }
+
+ $result = $update_data['counts'];
+
+ include( ABSPATH . WPINC . '/version.php' ); // $wp_version;
+ $result['wp_version'] = isset( $wp_version ) ? $wp_version : null;
+
+ if ( ! empty( $result['wordpress'] ) ) {
+ $cur = get_preferred_from_update_core();
+ if ( isset( $cur->response ) && $cur->response === 'upgrade' ) {
+ $result['wp_update_version'] = $cur->current;
+ }
+ }
+
+ $result['jp_version'] = JETPACK__VERSION;
+
+ return $result;
+
+ }
+}
diff --git a/plugins/jetpack/json-endpoints/jetpack/json-api-jetpack-endpoints.php b/plugins/jetpack/json-endpoints/jetpack/json-api-jetpack-endpoints.php
new file mode 100644
index 00000000..b5f9c3e2
--- /dev/null
+++ b/plugins/jetpack/json-endpoints/jetpack/json-api-jetpack-endpoints.php
@@ -0,0 +1,580 @@
+<?php
+
+$json_jetpack_endpoints_dir = dirname( __FILE__ ) . '/';
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-endpoint.php' );
+
+// THEMES
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-endpoint.php' );
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-active-endpoint.php' );
+
+new Jetpack_JSON_API_Themes_Active_Endpoint( array(
+ 'description' => 'Get the active theme of your blog',
+ 'stat' => 'themes:mine',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/themes/mine',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/mine'
+) );
+
+new Jetpack_JSON_API_Themes_Active_Endpoint( array(
+ 'description' => 'Change the active theme of your blog',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/themes/mine',
+ 'stat' => 'themes:mine',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'query_parameters' => array(
+ 'context' => false
+ ),
+ 'request_format' => array(
+ 'theme' => '(string) The ID of the theme that should be activated'
+ ),
+ 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'theme' => 'twentytwelve'
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/mine'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-list-endpoint.php' );
+
+new Jetpack_JSON_API_Themes_List_Endpoint( array(
+ 'description' => 'Get WordPress.com Themes allowed on your blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'themes',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/themes',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => array(
+ 'found' => '(int) The total number of themes found.',
+ 'themes' => '(array) An array of theme objects.',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-get-endpoint.php' );
+new Jetpack_JSON_API_Themes_Get_Endpoint( array(
+ 'description' => 'Get a single theme on a jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'themes:get:1',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/themes/%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$theme' => '(string) The theme slug',
+ ),
+ 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/twentyfourteen'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-modify-endpoint.php' );
+new Jetpack_JSON_API_Themes_Modify_Endpoint( array(
+ 'description' => 'Modify a single theme on a jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'themes:modify:1',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/themes/%s',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$theme' => '(string) The theme slug',
+ ),
+ 'request_format' => array(
+ 'action' => '(string) Only possible value is \'update\'. More to follow.',
+ 'autoupdate' => '(bool) Whether or not to automatically update the theme.',
+ ),
+ 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'action' => 'update',
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/twentyfourteen'
+) );
+
+new Jetpack_JSON_API_Themes_Modify_Endpoint( array(
+ 'description' => 'Modify a list of themes on a jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'themes:modify',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/themes',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+ 'request_format' => array(
+ 'action' => '(string) Only possible value is \'update\'. More to follow.',
+ 'autoupdate' => '(bool) Whether or not to automatically update the theme.',
+ 'themes' => '(array) A list of theme slugs',
+ ),
+ 'response_format' => array(
+ 'themes' => '(array:theme) A list of theme objects',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'action' => 'autoupdate_on',
+ 'themes' => array(
+ 'twentytwelve',
+ 'twentyfourteen',
+ ),
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-install-endpoint.php' );
+// POST /sites/%s/themes/%s/install
+new Jetpack_JSON_API_Themes_Install_Endpoint( array(
+ 'description' => 'Install a theme to your jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'themes:1:install',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/themes/%s/install',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$theme' => '(int|string) The theme slug to install',
+ ),
+ 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/twentyfourteen/install'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-delete-endpoint.php' );
+// POST /sites/%s/themes/%s/delete
+new Jetpack_JSON_API_Themes_Delete_Endpoint( array(
+ 'description' => 'Delete/Uninstall a theme from your jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'themes:1:delete',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/themes/%s/delete',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$theme' => '(string) The slug of the theme to delete',
+ ),
+ 'response_format' => Jetpack_JSON_API_Themes_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/themes/twentyfourteen/delete'
+) );
+
+
+// PLUGINS
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-endpoint.php' );
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-get-endpoint.php' );
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-list-endpoint.php' );
+
+new Jetpack_JSON_API_Plugins_List_Endpoint( array(
+ 'description' => 'Get installed Plugins on your blog',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/plugins',
+ 'stat' => 'plugins',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => array(
+ 'plugins' => '(array) An array of plugin objects.',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins'
+) );
+
+new Jetpack_JSON_API_Plugins_Get_Endpoint( array(
+ 'description' => 'Get the Plugin data.',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/plugins/%s/',
+ 'stat' => 'plugins:1',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$plugin' => '(string) The plugin ID',
+ ),
+ 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/hello-dolly%20hello'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-modify-endpoint.php' );
+
+new Jetpack_JSON_API_Plugins_Modify_Endpoint( array(
+ 'description' => 'Activate/Deactivate a Plugin on your Jetpack Site, or set automatic updates',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/plugins/%s',
+ 'stat' => 'plugins:1:modify',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$plugin' => '(string) The plugin ID',
+ ),
+ 'request_format' => array(
+ 'action' => '(string) Possible values are \'update\'',
+ 'autoupdate' => '(bool) Whether or not to automatically update the plugin',
+ 'active' => '(bool) Activate or deactivate the plugin',
+ 'network_wide' => '(bool) Do action network wide (default value: false)',
+ ),
+ 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'action' => 'update',
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/hello-dolly%20hello'
+) );
+
+new Jetpack_JSON_API_Plugins_Modify_Endpoint( array(
+ 'description' => 'Activate/Deactivate a list of plugins on your Jetpack Site, or set automatic updates',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/plugins',
+ 'stat' => 'plugins:modify',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ ),
+ 'request_format' => array(
+ 'action' => '(string) Possible values are \'update\'',
+ 'autoupdate' => '(bool) Whether or not to automatically update the plugin',
+ 'active' => '(bool) Activate or deactivate the plugin',
+ 'network_wide' => '(bool) Do action network wide (default value: false)',
+ 'plugins' => '(array) A list of plugin ids to modify',
+ ),
+ 'response_format' => array(
+ 'plugins' => '(array:plugin) An array of plugin objects.',
+ 'updated' => '(array) A list of plugin ids that were updated. Only present if action is update.',
+ 'not_updated' => '(array) A list of plugin ids that were not updated. Only present if action is update.',
+ 'log' => '(array) Update log. Only present if action is update.',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'active' => true,
+ 'plugins' => array(
+ 'jetpack/jetpack',
+ 'akismet/akismet',
+ ),
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-install-endpoint.php' );
+// POST /sites/%s/plugins/%s/install
+new Jetpack_JSON_API_Plugins_Install_Endpoint( array(
+ 'description' => 'Install a plugin to your jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'plugins:1:install',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/plugins/%s/install',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$plugin' => '(int|string) The plugin slug to install',
+ ),
+ 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/akismet/install'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-delete-endpoint.php' );
+// POST /sites/%s/plugins/%s/delete
+new Jetpack_JSON_API_Plugins_Delete_Endpoint( array(
+ 'description' => 'Delete/Uninstall a plugin from your jetpack blog',
+ 'group' => '__do_not_document',
+ 'stat' => 'plugins:1:delete',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/plugins/%s/delete',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$plugin' => '(int|string) The plugin slug to delete',
+ ),
+ 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/akismet%2Fakismet/delete'
+) );
+
+new Jetpack_JSON_API_Plugins_Modify_Endpoint( array(
+ 'description' => 'Update a Plugin on your Jetpack Site',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/plugins/%s/update/',
+ 'stat' => 'plugins:1:update',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$plugin' => '(string) The plugin ID',
+ ),
+ 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/hello-dolly%20hello/update'
+) );
+
+// Jetpack Modules
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-modules-endpoint.php' );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-modules-get-endpoint.php' );
+
+new Jetpack_JSON_API_Modules_Get_Endpoint( array(
+ 'description' => 'Get the info about a Jetpack Module on your Jetpack Site',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/jetpack/modules/%s/',
+ 'stat' => 'jetpack:modules:1',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$module' => '(string) The module name',
+ ),
+ 'response_format' => Jetpack_JSON_API_Modules_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/jetpack/modules/stats'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-modules-modify-endpoint.php' );
+
+new Jetpack_JSON_API_Modules_Modify_Endpoint( array(
+ 'description' => 'Modify the status of a Jetpack Module on your Jetpack Site',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/jetpack/modules/%s/',
+ 'stat' => 'jetpack:modules:1',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain',
+ '$module' => '(string) The module name',
+ ),
+ 'request_format' => array(
+ 'active' => '(bool) The module activation status',
+ ),
+ 'response_format' => Jetpack_JSON_API_Modules_Endpoint::$_response_format,
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'active' => true,
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/jetpack/modules/stats'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-modules-list-endpoint.php' );
+
+new Jetpack_JSON_API_Modules_List_Endpoint( array(
+ 'description' => 'Get the list of available Jetpack modules on your site',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/jetpack/modules',
+ 'stat' => 'jetpack:modules',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => array(
+ 'found' => '(int) The total number of modules found.',
+ 'modules' => '(array) An array of module objects.',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/jetpack/modules'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-updates-status-endpoint.php' );
+
+new Jetpack_JSON_API_Updates_Status( array(
+ 'description' => 'Get counts for available updates',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/updates',
+ 'stat' => 'updates',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => array(
+ 'plugins' => '(int) The total number of plugins updates.',
+ 'themes' => '(int) The total number of themes updates.',
+ 'wordpress' => '(int) The total number of core updates.',
+ 'translations' => '(int) The total number of translation updates.',
+ 'total' => '(int) The total number of updates.',
+ 'wp_version' => '(safehtml) The wp_version string.',
+ 'wp_update_version' => '(safehtml) The wp_version to update string.',
+ 'jp_version' => '(safehtml) The site Jetpack version.',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/updates'
+) );
+
+
+// Jetpack Extras
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-check-capabilities-endpoint.php' );
+
+new Jetpack_JSON_API_Check_Capabilities_Endpoint( array(
+ 'description' => 'Check if the current user has a certain capability over a Jetpack site',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/me/capability',
+ 'stat' => 'me:capabulity',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => '(bool) True if the user has the queried capability.',
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'capability' => 'A single capability or an array of capabilities'
+ )
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/me/capability'
+) );
+
+
+// CORE
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-core-endpoint.php' );
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-core-modify-endpoint.php' );
+
+new Jetpack_JSON_API_Core_Endpoint( array(
+ 'description' => 'Gets info about a Jetpack blog\'s core installation',
+ 'method' => 'GET',
+ 'path' => '/sites/%s/core',
+ 'stat' => 'core',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => array(
+ 'version' => '(string) The current version',
+ 'autoupdate' => '(bool) Whether or not we automatically update core'
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/core'
+) );
+
+new Jetpack_JSON_API_Core_Modify_Endpoint( array(
+ 'description' => 'Update WordPress installation on a Jetpack blog',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/core/update',
+ 'stat' => 'core:update',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'request_format' => array(
+ 'version' => '(string) The core version to update',
+ ),
+ 'response_format' => array(
+ 'version' => '(string) The core version after the upgrade has run.',
+ 'log' => '(array:safehtml) An array of log strings.',
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/core/update'
+) );
+
+new Jetpack_JSON_API_Core_Endpoint( array(
+ 'description' => 'Toggle automatic core updates for a Jetpack blog',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/core',
+ 'stat' => 'core',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'request_format' => array(
+ 'autoupdate' => '(bool) Whether or not we automatically update core',
+ ),
+ 'response_format' => array(
+ 'version' => '(string) The current version',
+ 'autoupdate' => '(bool) Whether or not we automatically update core'
+ ),
+ 'example_request_data' => array(
+ 'headers' => array(
+ 'authorization' => 'Bearer YOUR_API_TOKEN'
+ ),
+ 'body' => array(
+ 'autoupdate' => true,
+ ),
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/core'
+) );
+
+require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-sync-endpoint.php' );
+
+new Jetpack_JSON_API_Sync_Endpoint( array(
+ 'description' => 'Force sync of all options and constants',
+ 'method' => 'POST',
+ 'path' => '/sites/%s/sync',
+ 'stat' => 'sync',
+ 'path_labels' => array(
+ '$site' => '(int|string) The site ID, The site domain'
+ ),
+ 'response_format' => array(
+ 'scheduled' => '(bool) Whether or not the synchronisation was scheduled'
+ ),
+ 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync'
+) );