diff options
author | 2019-05-22 01:01:36 -0400 | |
---|---|---|
committer | 2019-05-22 01:01:36 -0400 | |
commit | 0914c92da22824025992c368c745546e41fbeb84 (patch) | |
tree | 965f6adf3b725e56d559fe4a93eff02281499dcc /plugins/jetpack/json-endpoints/jetpack | |
parent | Deleting plugins for update (diff) | |
download | blogs-gentoo-0914c92da22824025992c368c745546e41fbeb84.tar.gz blogs-gentoo-0914c92da22824025992c368c745546e41fbeb84.tar.bz2 blogs-gentoo-0914c92da22824025992c368c745546e41fbeb84.zip |
Adding Plugins
Updating the following
akismet.4.1.2, google-authenticator.0.52, jetpack.7.3.1
Signed-off-by: Yury German <blueknight@gentoo.org>
Diffstat (limited to 'plugins/jetpack/json-endpoints/jetpack')
43 files changed, 4904 insertions, 0 deletions
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..c86cddec --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-check-capabilities-endpoint.php @@ -0,0 +1,26 @@ +<?php + +class Jetpack_JSON_API_Check_Capabilities_Endpoint extends Jetpack_JSON_API_Modules_Endpoint { + // GET /sites/%s/me/capability + // The unused $object parameter is for making the method signature compatible with its parent class method. + public function callback( $path = '', $_blog_id = 0, $object = null ) { + // 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..8f707ad4 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-core-modify-endpoint.php @@ -0,0 +1,75 @@ +<?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() { + $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(); + } + + /** + * Pre-upgrade action + * + * @since 3.9.3 + * + * @param object|array $update as returned by find_core_update() or find_core_auto_update() + */ + do_action('jetpack_pre_core_upgrade', $update); + + $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-cron-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-cron-endpoint.php new file mode 100644 index 00000000..9638c3eb --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-cron-endpoint.php @@ -0,0 +1,252 @@ +<?php + +// GET /sites/%s/cron +class Jetpack_JSON_API_Cron_Endpoint extends Jetpack_JSON_API_Endpoint { + protected $needed_capabilities = 'manage_options'; + + protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) { + return parent::validate_call( $_blog_id, $capability, false ); + } + + protected function result() { + return array( + 'cron_array' => _get_cron_array(), + 'current_timestamp' => time() + ); + } + + protected function sanitize_hook( $hook ) { + return preg_replace( '/[^A-Za-z0-9-_]/', '', $hook ); + } + + protected function resolve_arguments() { + $args = $this->input(); + return isset( $args['arguments'] ) ? json_decode( $args['arguments'] ) : array(); + } + + protected function is_cron_locked( $gmt_time ) { + // The cron lock: a unix timestamp from when the cron was spawned. + $doing_cron_transient = $this->get_cron_lock(); + if ( $doing_cron_transient && ( $doing_cron_transient + WP_CRON_LOCK_TIMEOUT > $gmt_time ) ) { + return new WP_Error( 'cron-is-locked', 'Current there is a cron already happening.', 403 ); + } + return $doing_cron_transient; + } + + protected function maybe_unlock_cron( $doing_wp_cron ) { + if ( $this->get_cron_lock() == $doing_wp_cron ) { + delete_transient( 'doing_cron' ); + } + } + + protected function lock_cron() { + $lock = sprintf( '%.22F', microtime( true ) ); + set_transient( 'doing_cron', $lock ); + return $lock; + } + + protected function get_schedules( $hook, $args ) { + $crons = _get_cron_array(); + $key = md5(serialize($args)); + if ( empty( $crons ) ) + return array(); + $found = array(); + foreach ( $crons as $timestamp => $cron ) { + if ( isset( $cron[$hook][$key] ) ) + $found[] = $timestamp; + } + + return $found; + } + + /** + * This function is based on the one found in wp-cron.php with a similar name + * @return int + */ + protected function get_cron_lock() { + global $wpdb; + + $value = 0; + if ( wp_using_ext_object_cache() ) { + /* + * Skip local cache and force re-fetch of doing_cron transient + * in case another process updated the cache. + */ + $value = wp_cache_get( 'doing_cron', 'transient', true ); + } else { + $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", '_transient_doing_cron' ) ); + if ( is_object( $row ) ) { + $value = $row->option_value; + } + } + return $value; + } +} + +// POST /sites/%s/cron +class Jetpack_JSON_API_Cron_Post_Endpoint extends Jetpack_JSON_API_Cron_Endpoint { + + protected function result() { + define( 'DOING_CRON', true ); + set_time_limit( 0 ); + $args = $this->input(); + + if ( false === $crons = _get_cron_array() ) { + return new WP_Error( 'no-cron-event', 'Currently there are no cron events', 400 ); + } + + $timestamps_to_run = array_keys( $crons ); + $gmt_time = microtime( true ); + + if ( isset( $timestamps_to_run[0] ) && $timestamps_to_run[0] > $gmt_time ) { + return new WP_Error( 'no-cron-event', 'Currently there are no cron events ready to be run', 400 ); + } + + $locked = $this->is_cron_locked( $gmt_time ); + if ( is_wp_error( $locked ) ) { + return $locked; + } + + $lock = $this->lock_cron(); + $processed_events = array(); + + foreach ( $crons as $timestamp => $cronhooks ) { + if ( $timestamp > $gmt_time && ! isset( $args[ 'hook' ] ) ) { + break; + } + + foreach ( $cronhooks as $hook => $hook_data ) { + if ( isset( $args[ 'hook' ] ) && ! in_array( $hook, $args['hook'] ) ) { + continue; + } + + foreach ( $hook_data as $hash => $hook_item ) { + + $schedule = $hook_item['schedule']; + $arguments = $hook_item['args']; + + if ( $schedule != false ) { + wp_reschedule_event( $timestamp, $schedule, $hook, $arguments ); + } + + wp_unschedule_event( $timestamp, $hook, $arguments ); + + do_action_ref_array( $hook, $arguments ); + $processed_events[] = array( $hook => $arguments ); + + // If the hook ran too long and another cron process stole the lock, + // or if we things are taking longer then 20 seconds then quit. + if ( ( $this->get_cron_lock() != $lock ) || ( $gmt_time + 20 > microtime( true ) ) ) { + $this->maybe_unlock_cron( $lock ); + return array( 'success' => $processed_events ); + } + + } + } + } + + $this->maybe_unlock_cron( $lock ); + return array( 'success' => $processed_events ); + } +} + +// POST /sites/%s/cron/schedule +class Jetpack_JSON_API_Cron_Schedule_Endpoint extends Jetpack_JSON_API_Cron_Endpoint { + + protected function result() { + $args = $this->input(); + if ( ! isset( $args['timestamp'] ) ) { + return new WP_Error( 'missing_argument', 'Please provide the timestamp argument', 400 ); + } + + if ( ! is_int( $args['timestamp'] ) || $args['timestamp'] < time() ) { + return new WP_Error( 'timestamp-invalid', 'Please provide timestamp that is an integer and set in the future', 400 ); + } + + if ( ! isset( $args['hook'] ) ) { + return new WP_Error( 'missing_argument', 'Please provide the hook argument', 400 ); + } + + $hook = $this->sanitize_hook( $args['hook'] ); + + $locked = $this->is_cron_locked( microtime( true ) ); + if ( is_wp_error( $locked ) ) { + return $locked; + } + + $arguments = $this->resolve_arguments(); + $next_scheduled = $this->get_schedules( $hook, $arguments ); + + if ( isset( $args['recurrence'] ) ) { + $schedules = wp_get_schedules(); + if ( ! isset( $schedules[ $args['recurrence'] ] ) ) { + return new WP_Error( 'invalid-recurrence', 'Please provide a valid recurrence argument', 400 ); + } + + if ( count( $next_scheduled ) > 0 ) { + return new WP_Error( 'event-already-scheduled', 'This event is ready scheduled', 400 ); + } + $lock = $this->lock_cron(); + wp_schedule_event( $args['timestamp'], $args['recurrence'], $hook, $arguments ); + $this->maybe_unlock_cron( $lock ); + return array( 'success' => true ); + } + + foreach( $next_scheduled as $scheduled_time ) { + if ( abs( $scheduled_time - $args['timestamp'] ) <= 10 * MINUTE_IN_SECONDS ) { + return new WP_Error( 'event-already-scheduled', 'This event is ready scheduled', 400 ); + } + } + $lock = $this->lock_cron(); + $next = wp_schedule_single_event( $args['timestamp'], $hook, $arguments ); + $this->maybe_unlock_cron( $lock ); + /** + * Note: Before WP 5.1, the return value was either `false` or `null`. + * With 5.1 and later, the return value is now `false` or `true`. + * We need to account for both. + */ + return array( 'success' => false !== $next ); + } +} + +// POST /sites/%s/cron/unschedule +class Jetpack_JSON_API_Cron_Unschedule_Endpoint extends Jetpack_JSON_API_Cron_Endpoint { + + protected function result() { + $args = $this->input(); + + if ( !isset( $args['hook'] ) ) { + return new WP_Error( 'missing_argument', 'Please provide the hook argument', 400 ); + } + + $hook = $this->sanitize_hook( $args['hook'] ); + + $locked = $this->is_cron_locked( microtime( true ) ); + if ( is_wp_error( $locked ) ) { + return $locked; + } + + $crons = _get_cron_array(); + if ( empty( $crons ) ) { + return new WP_Error( 'cron-not-present', 'Unable to unschedule an event, no events in the cron', 400 ); + } + + $arguments = $this->resolve_arguments(); + + if ( isset( $args['timestamp'] ) ) { + $next_schedulded = $this->get_schedules( $hook, $arguments ); + if ( in_array( $args['timestamp'], $next_schedulded ) ) { + return new WP_Error( 'event-not-present', 'Unable to unschedule the event, the event doesn\'t exist', 400 ); + } + + $lock = $this->lock_cron(); + wp_unschedule_event( $args['timestamp'], $hook, $arguments ); + $this->maybe_unlock_cron( $lock ); + return array( 'success' => true ); + } + $lock = $this->lock_cron(); + wp_clear_scheduled_hook( $hook, $arguments ); + $this->maybe_unlock_cron( $lock ); + return array( 'success' => true ); + } +} 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..8a349015 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-endpoint.php @@ -0,0 +1,128 @@ +<?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_validation = 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_validation && + 'GET' !== $this->method && + /** + * Filter to disallow JSON API requests to the site. + * Setting to false disallows you to manage your site remotely from WordPress.com + * and disallows plugin auto-updates. + * + * @since 7.3.0 + * + * @param bool $check_validation Whether to allow API requests to manage the site + */ + ! apply_filters( 'jetpack_json_manage_api_enabled', $check_validation ) + ) { + 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-get-comment-backup-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-comment-backup-endpoint.php new file mode 100644 index 00000000..0e4bc256 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-comment-backup-endpoint.php @@ -0,0 +1,52 @@ +<?php + +class Jetpack_JSON_API_Get_Comment_Backup_Endpoint extends Jetpack_JSON_API_Endpoint { + // /sites/%s/comments/%d/backup -> $blog_id, $comment_id + + protected $needed_capabilities = array(); // This endpoint is only accessible using a site token + protected $comment_id; + + function validate_input( $comment_id ) { + if ( empty( $comment_id ) || ! is_numeric( $comment_id ) ) { + return new WP_Error( 'comment_id_not_specified', __( 'You must specify a Comment ID', 'jetpack' ), 400 ); + } + + $this->comment_id = intval( $comment_id ); + + return true; + } + + protected function result() { + $comment = get_comment( $this->comment_id ); + if ( empty( $comment ) ) { + return new WP_Error( 'comment_not_found', __( 'Comment not found', 'jetpack' ), 404 ); + } + + $allowed_keys = array( + 'comment_ID', + 'comment_post_ID', + 'comment_author', + 'comment_author_email', + 'comment_author_url', + 'comment_author_IP', + 'comment_date', + 'comment_date_gmt', + 'comment_content', + 'comment_karma', + 'comment_approved', + 'comment_agent', + 'comment_type', + 'comment_parent', + 'user_id', + ); + + $comment = array_intersect_key( $comment->to_array(), array_flip( $allowed_keys ) ); + $comment_meta = get_comment_meta( $comment['comment_ID'] ); + + return array( + 'comment' => $comment, + 'meta' => is_array( $comment_meta ) ? $comment_meta : array(), + ); + } + +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-database-object-backup-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-database-object-backup-endpoint.php new file mode 100644 index 00000000..b7134730 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-database-object-backup-endpoint.php @@ -0,0 +1,97 @@ +<?php + +class Jetpack_JSON_API_Get_Database_Object_Backup_Endpoint extends Jetpack_JSON_API_Endpoint { + // /sites/%s/database-object/backup -> $blog_id + + protected $needed_capabilities = array(); // This endpoint is only accessible using a site token + protected $object_type; + protected $object_id; + + // Full list of database objects that can be retrieved via this endpoint. + protected $object_types = array( + 'woocommerce_attribute' => array( + 'table' => 'woocommerce_attribute_taxonomies', + 'id_field' => 'attribute_id', + ), + + 'woocommerce_downloadable_product_permission' => array( + 'table' => 'woocommerce_downloadable_product_permissions', + 'id_field' => 'permission_id', + ), + + 'woocommerce_order_item' => array( + 'table' => 'woocommerce_order_items', + 'id_field' => 'order_item_id', + 'meta_type' => 'order_item', + ), + + 'woocommerce_payment_token' => array( + 'table' => 'woocommerce_payment_tokens', + 'id_field' => 'token_id', + 'meta_type' => 'payment_token', + ), + + 'woocommerce_tax_rate' => array( + 'table' => 'woocommerce_tax_rates', + 'id_field' => 'tax_rate_id', + 'child_table' => 'woocommerce_tax_rate_locations', + 'child_id_field' => 'tax_rate_id', + ), + + 'woocommerce_webhook' => array( + 'table' => 'wc_webhooks', + 'id_field' => 'webhook_id', + ), + ); + + function validate_input( $object ) { + $query_args = $this->query_args(); + + if ( empty( $query_args['object_type'] ) || empty( $query_args['object_id'] ) ) { + return new WP_Error( 'invalid_args', __( 'You must specify both an object type and id to fetch', 'jetpack' ), 400 ); + } + + if ( empty( $this->object_types[ $query_args['object_type'] ] ) ) { + return new WP_Error( 'invalid_args', __( 'Specified object_type not recognized', 'jetpack' ), 400 ); + } + + $this->object_type = $this->object_types[ $query_args['object_type'] ]; + $this->object_id = $query_args['object_id']; + + return true; + } + + protected function result() { + global $wpdb; + + $table = $wpdb->prefix . $this->object_type['table']; + $id_field = $this->object_type['id_field']; + + // Fetch the requested object + $query = $wpdb->prepare( 'select * from `' . $table . '` where `' . $id_field . '` = %d', $this->object_id ); + $object = $wpdb->get_row( $query ); + + if ( empty( $object ) ) { + return new WP_Error( 'object_not_found', __( 'Object not found', 'jetpack' ), 404 ); + } + + $result = array( 'object' => $object ); + + // Fetch associated metadata (if this object type has any) + if ( ! empty( $this->object_type['meta_type'] ) ) { + $result['meta'] = get_metadata( $this->object_type['meta_type'], $this->object_id ); + } + + // If there is a child linked table (eg: woocommerce_tax_rate_locations), fetch linked records + if ( ! empty( $this->object_type['child_table'] ) ) { + $child_table = $wpdb->prefix . $this->object_type['child_table']; + $child_id_field = $this->object_type['child_id_field']; + + $query = $wpdb->prepare( 'select * from `' . $child_table . '` where `' . $child_id_field . '` = %d', $this->object_id ); + $result[ 'children' ] = $wpdb->get_results( $query ); + } + + return $result; + } + +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-option-backup-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-option-backup-endpoint.php new file mode 100644 index 00000000..a5d8d3a7 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-option-backup-endpoint.php @@ -0,0 +1,35 @@ +<?php + +class Jetpack_JSON_API_Get_Option_Backup_Endpoint extends Jetpack_JSON_API_Endpoint { + // /sites/%s/options/backup -> $blog_id + + protected $needed_capabilities = array(); // This endpoint is only accessible using a site token + protected $option_names; + + function validate_input( $object ) { + $query_args = $this->query_args(); + + if ( empty( $query_args['name'] ) ) { + return new WP_Error( 'option_name_not_specified', __( 'You must specify an option name', 'jetpack' ), 400 ); + } + + if ( is_array( $query_args['name'] ) ) { + $this->option_names = $query_args['name']; + } else { + $this->option_names = array( $query_args['name'] ); + } + + return true; + } + + protected function result() { + $options = array_map( array( $this, 'get_option_row' ), $this->option_names ); + return array( 'options' => $options ); + } + + private function get_option_row( $name ) { + global $wpdb; + return $wpdb->get_row( $wpdb->prepare( "select * from `{$wpdb->options}` where option_name = %s", $name ) ); + } + +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-post-backup-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-post-backup-endpoint.php new file mode 100644 index 00000000..903a16ac --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-post-backup-endpoint.php @@ -0,0 +1,31 @@ +<?php + +class Jetpack_JSON_API_Get_Post_Backup_Endpoint extends Jetpack_JSON_API_Endpoint { + // /sites/%s/posts/%d/backup -> $blog_id, $post_id + + protected $needed_capabilities = array(); // This endpoint is only accessible using a site token + protected $post_id; + + function validate_input( $post_id ) { + if ( empty( $post_id ) || ! is_numeric( $post_id ) ) { + return new WP_Error( 'post_id_not_specified', __( 'You must specify a Post ID', 'jetpack' ), 400 ); + } + + $this->post_id = intval( $post_id ); + + return true; + } + + protected function result() { + $post = get_post( $this->post_id ); + if ( empty( $post ) ) { + return new WP_Error( 'post_not_found', __( 'Post not found', 'jetpack' ), 404 ); + } + + return array( + 'post' => (array)$post, + 'meta' => get_post_meta( $post->ID ), + ); + } + +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-term-backup-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-term-backup-endpoint.php new file mode 100644 index 00000000..40d0ab97 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-term-backup-endpoint.php @@ -0,0 +1,32 @@ +<?php + +class Jetpack_JSON_API_Get_Term_Backup_Endpoint extends Jetpack_JSON_API_Endpoint { + // /sites/%s/terms/%d/backup -> $blog_id, $term_id + + protected $needed_capabilities = array(); // This endpoint is only accessible using a site token + protected $term_id; + + function validate_input( $term_id ) { + if ( empty( $term_id ) || ! is_numeric( $term_id ) ) { + return new WP_Error( 'term_id_not_specified', __( 'You must specify a Term ID', 'jetpack' ), 400 ); + } + + $this->term_id = intval( $term_id ); + + return true; + } + + protected function result() { + $term = get_term( $this->term_id ); + if ( empty( $term ) ) { + return new WP_Error( 'term_not_found', __( 'Term not found', 'jetpack' ), 404 ); + } + + return array( + 'term' => (array) $term, + 'meta' => get_term_meta( $this->term_id ), + ); + } + +} + diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-user-backup-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-user-backup-endpoint.php new file mode 100644 index 00000000..22ca195d --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-get-user-backup-endpoint.php @@ -0,0 +1,32 @@ +<?php + +class Jetpack_JSON_API_Get_User_Backup_Endpoint extends Jetpack_JSON_API_Endpoint { + // /sites/%s/users/%d/backup -> $blog_id, $user_id + + protected $needed_capabilities = array(); // This endpoint is only accessible using a site token + protected $user_id; + + function validate_input( $user_id ) { + if ( empty( $user_id ) || ! is_numeric( $user_id ) ) { + return new WP_Error( 'user_id_not_specified', __( 'You must specify a User ID', 'jetpack' ), 400 ); + } + + $this->user_id = intval( $user_id ); + + return true; + } + + protected function result() { + $user = get_user_by( 'id', $this->user_id ); + if ( empty( $user ) ) { + return new WP_Error( 'user_not_found', __( 'User not found', 'jetpack' ), 404 ); + } + + return array( + 'user' => $user->to_array(), + 'meta' => get_user_meta( $user->ID ), + ); + } + +} + diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-jps-woocommerce-connect-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-jps-woocommerce-connect-endpoint.php new file mode 100644 index 00000000..75a3b04d --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-jps-woocommerce-connect-endpoint.php @@ -0,0 +1,58 @@ +<?php + +class Jetpack_JSON_API_JPS_WooCommerce_Connect_Endpoint extends Jetpack_JSON_API_Endpoint { + + protected $needed_capabilities = 'manage_options'; + + function result() { + $input = $this->input(); + $helper_data = get_option( 'woocommerce_helper_data', array() ); + + if ( ! empty( $helper_data['auth'] ) ) { + return new WP_Error( + 'already_configured', + __( 'WooCommerce auth data is already set.', 'jetpack' ) + ); + } + + // Only update the auth field for `woocommerce_helper_data` instead of blowing out the entire option. + $helper_data['auth'] = array( + 'user_id' => $input['user_id'], + 'site_id' => $input['site_id'], + 'updated' => time(), + 'access_token' => $input['access_token'], + 'access_token_secret' => $input['access_token_secret'], + ); + + $updated = update_option( + 'woocommerce_helper_data', + $helper_data + ); + + return array( + 'success' => $updated, + ); + } + + function validate_input( $object ) { + $input = $this->input(); + + if ( empty( $input['access_token'] ) ) { + return new WP_Error( 'input_error', __( 'access_token is required', 'jetpack' ) ); + } + + if ( empty( $input['access_token_secret'] ) ) { + return new WP_Error( 'input_error', __( 'access_token_secret is required', 'jetpack' ) ); + } + + if ( empty( $input['user_id'] ) ) { + return new WP_Error( 'input_error', __( 'user_id is required', 'jetpack' ) ); + } + + if ( empty( $input['site_id'] ) ) { + return new WP_Error( 'input_error', __( 'site_id is required', 'jetpack' ) ); + } + + return parent::validate_input( $object ); + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-log-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-log-endpoint.php new file mode 100644 index 00000000..f1d0f4da --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-log-endpoint.php @@ -0,0 +1,16 @@ +<?php + +class Jetpack_JSON_API_Jetpack_Log_Endpoint extends Jetpack_JSON_API_Endpoint { + // GET /sites/%s/jetpack-log + protected $needed_capabilities = 'manage_options'; + + protected function result() { + $args = $this->input(); + $event = ( isset( $args['event'] ) && is_string( $args['event'] ) ) ? $code : false; + $num = ( isset( $args['num'] ) ) ? intval( $num ) : false; + + return array( + 'log' => Jetpack::get_log( $event, $num ) + ); + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-maybe-auto-update-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-maybe-auto-update-endpoint.php new file mode 100644 index 00000000..5b368760 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-maybe-auto-update-endpoint.php @@ -0,0 +1,32 @@ +<?php + +class Jetpack_JSON_API_Maybe_Auto_Update_Endpoint extends Jetpack_JSON_API_Endpoint { + // POST /sites/%s/maybe_auto_update + protected $needed_capabilities = array( 'update_core', 'update_plugins', 'update_themes' ); + + protected $update_results = array(); + + protected function result() { + add_action( 'automatic_updates_complete', array( $this, 'get_update_results' ), 100, 1 ); + + wp_maybe_auto_update(); + + $result['log'] = $this->update_results; + + if ( empty( $result['log'] ) ) { + $possible_reasons_for_failure = Jetpack_Autoupdate::get_possible_failures(); + + if ( $possible_reasons_for_failure ) { + $result['log']['error'] = $possible_reasons_for_failure; + } + + } + + return $result; + } + + public function get_update_results( $results ) { + $this->update_results = $results; + } + +} 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..2f56f1ee --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-modules-endpoint.php @@ -0,0 +1,125 @@ +<?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.', + 'override' => '(string) The module\'s override. Empty if no override, otherwise \'active\' or \'inactive\'', + ); + + 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']; + + $overrides_instance = Jetpack_Modules_Overrides::instance(); + $module['override'] = $overrides_instance->get_module_override( $module_slug ); + + // Fetch the HTML formatted long description + ob_start(); + /** This action is documented in class.jetpack-modules-list-table.php */ + 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..3748d621 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-delete-endpoint.php @@ -0,0 +1,78 @@ +<?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', + 'min_version' => '1', + 'max_version' => '1.1', + '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' + ) +); +// v1.2 +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', + 'min_version' => '1.2', + '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_v1_2, + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.2/sites/example.wordpress.org/plugins/akismet%2Fakismet/delete' + ) +); + +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.', 'jetpack' ); + 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..1df4fe66 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php @@ -0,0 +1,321 @@ +<?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', + 'autoupdate_translation' => '(boolean) Whether the plugin is automatically updating translations', + 'next_autoupdate' => '(string) Y-m-d H:i:s for next scheduled update event', + 'log' => '(array:safehtml) An array of update log strings.', + 'uninstallable' => '(boolean) Whether the plugin is unistallable.', + 'action_links' => '(array) An array of action links that the plugin uses.', + ); + + static $_response_format_v1_2 = array( + 'slug' => '(safehtml) The plugin\'s .org slug', + 'active' => '(boolean) The plugin status.', + 'update' => '(object) The plugin update info.', + 'name' => '(safehtml) The plugin\'s ID', + 'display_name' => '(safehtml) The name of the plugin.', + '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', + 'plugin_url' => '(url) Link to the plugin\'s web site.', + 'network' => '(boolean) Whether the plugin can only be activated network wide.', + 'autoupdate' => '(boolean) Whether the plugin is automatically updated', + 'autoupdate_translation' => '(boolean) Whether the plugin is automatically updating translations', + 'uninstallable' => '(boolean) Whether the plugin is unistallable.', + 'action_links' => '(array) An array of action links that the plugin uses.', + '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; + } + $valid = $this->validate_plugin( urldecode( $plugin ) ) ; + if ( is_wp_error( $valid ) ) { + return $valid; + } + } + + return true; + } + + protected function format_plugin( $plugin_file, $plugin_data ) { + if ( version_compare( $this->min_version, '1.2', '>=' ) ) { + return $this->format_plugin_v1_2( $plugin_file, $plugin_data ); + } + $plugin = array(); + $plugin['id'] = preg_replace("/(.+)\.php$/", "$1", $plugin_file ); + $plugin['slug'] = Jetpack_Autoupdate::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' ) ); + $action_link = $this->get_plugin_action_links( $plugin_file ); + if ( ! empty( $action_link ) ) { + $plugin['action_links'] = $action_link; + } + + $autoupdate = in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins', array() ) ); + $plugin['autoupdate'] = $autoupdate; + + $autoupdate_translation = in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ) ); + $plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false ); + + $plugin['uninstallable'] = is_uninstallable_plugin( $plugin_file ); + + if ( ! empty ( $this->log[ $plugin_file ] ) ) { + $plugin['log'] = $this->log[ $plugin_file ]; + } + return $plugin; + } + + protected function format_plugin_v1_2( $plugin_file, $plugin_data ) { + $plugin = array(); + $plugin['slug'] = Jetpack_Autoupdate::get_plugin_slug( $plugin_file ); + $plugin['active'] = Jetpack::is_plugin_active( $plugin_file ); + $plugin['name'] = preg_replace("/(.+)\.php$/", "$1", $plugin_file ); + $plugin['display_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 ); + $action_link = $this->get_plugin_action_links( $plugin_file ); + if ( ! empty( $action_link ) ) { + $plugin['action_links'] = $action_link; + } + + $autoupdate = $this->plugin_has_autoupdates_enabled( $plugin_file ); + $plugin['autoupdate'] = $autoupdate; + + $autoupdate_translation = $this->plugin_has_translations_autoupdates_enabled( $plugin_file ); + $plugin['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false ); + $plugin['uninstallable'] = is_uninstallable_plugin( $plugin_file ); + + if ( ! empty ( $this->log[ $plugin_file ] ) ) { + $plugin['log'] = $this->log[ $plugin_file ]; + } + + return $plugin; + } + + protected function plugin_has_autoupdates_enabled( $plugin_file ) { + return (bool) in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins', array() ) ); + } + + protected function plugin_has_translations_autoupdates_enabled( $plugin_file ) { + return (bool) in_array( $plugin_file, Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ) ); + } + + + protected function get_file_mod_capabilities() { + $reasons_can_not_autoupdate = array(); + $reasons_can_not_modify_files = array(); + + $has_file_system_write_access = Jetpack_Sync_Functions::file_system_write_access(); + if ( ! $has_file_system_write_access ) { + $reasons_can_not_modify_files['has_no_file_system_write_access'] = __( 'The file permissions on this host prevent editing files.', 'jetpack' ); + } + + $disallow_file_mods = Jetpack_Constants::get_constant('DISALLOW_FILE_MODS' ); + if ( $disallow_file_mods ) { + $reasons_can_not_modify_files['disallow_file_mods'] = __( 'File modifications are explicitly disabled by a site administrator.', 'jetpack' ); + } + + $automatic_updater_disabled = Jetpack_Constants::get_constant( 'AUTOMATIC_UPDATER_DISABLED' ); + if ( $automatic_updater_disabled ) { + $reasons_can_not_autoupdate['automatic_updater_disabled'] = __( 'Any autoupdates are explicitly disabled by a site administrator.', 'jetpack' ); + } + + if ( is_multisite() ) { + // is it the main network ? is really is multi network + if ( Jetpack::is_multi_network() ) { + $reasons_can_not_modify_files['is_multi_network'] = __( 'Multi network install are not supported.', 'jetpack' ); + } + // Is the site the main site here. + if ( ! is_main_site() ) { + $reasons_can_not_modify_files['is_sub_site'] = __( 'The site is not the main network site', 'jetpack' ); + } + } + + $file_mod_capabilities = array( + 'modify_files' => (bool) empty( $reasons_can_not_modify_files ), // install, remove, update + 'autoupdate_files' => (bool) empty( $reasons_can_not_modify_files ) && empty( $reasons_can_not_autoupdate ), // enable autoupdates + ); + + if ( ! empty( $reasons_can_not_modify_files ) ) { + $file_mod_capabilities['reasons_modify_files_unavailable'] = $reasons_can_not_modify_files; + } + + if ( ! $file_mod_capabilities['autoupdate_files'] ) { + $file_mod_capabilities['reasons_autoupdate_unavailable'] = array_merge( $reasons_can_not_autoupdate, $reasons_can_not_modify_files ); + } + return $file_mod_capabilities; + } + + protected function get_plugins() { + $plugins = array(); + /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ + $installed_plugins = apply_filters( 'all_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( $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 ] ) ) { + $update = $plugin_updates[ $plugin_file ]->update; + $cleaned_update = array(); + foreach( (array) $update as $update_key => $update_value ) { + switch ( $update_key ) { + case 'id': + case 'slug': + case 'plugin': + case 'new_version': + case 'tested': + $cleaned_update[ $update_key ] = wp_kses( $update_value, array() ); + break; + case 'url': + case 'package': + $cleaned_update[ $update_key ] = esc_url( $update_value ); + break; + } + } + return (object) $cleaned_update; + } + return null; + } + + protected function get_plugin_action_links( $plugin_file ) { + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php'; + return Jetpack_Sync_Functions::get_plugins_action_links( $plugin_file ); + } +} 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..276fca49 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-get-endpoint.php @@ -0,0 +1,28 @@ +<?php + +new Jetpack_JSON_API_Plugins_Get_Endpoint( + array( + 'description' => 'Get the Plugin data.', + 'method' => 'GET', + 'path' => '/sites/%s/plugins/%s/', + 'min_version' => '1', + 'max_version' => '1.1', + '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' + ) +); +// no v1.2 version since it is .com only +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..bbd6de19 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-install-endpoint.php @@ -0,0 +1,96 @@ +<?php + +include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; +include_once ABSPATH . 'wp-admin/includes/file.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', + 'min_version' => '1', + 'max_version' => '1.1', + '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' + ) +); + +new Jetpack_JSON_API_Plugins_Install_Endpoint( + array( + 'description' => 'Install a plugin to your jetpack blog', + 'group' => '__do_not_document', + 'stat' => 'plugins:1:install', + 'min_version' => '1.2', + '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_v1_2, + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.2/sites/example.wordpress.org/plugins/akismet/install' + ) +); + +class Jetpack_JSON_API_Plugins_Install_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint { + + // POST /sites/%s/plugins/%s/install + protected $needed_capabilities = 'install_plugins'; + protected $action = 'install'; + + protected function install() { + jetpack_require_lib( 'plugins' ); + $result = ''; + foreach ( $this->plugins as $index => $slug ) { + $result = Jetpack_Plugins::install_plugin( $slug ); + if ( is_wp_error( $result ) ) { + $this->log[ $slug ][] = $result->get_error_message(); + if ( ! $this->bulk ) { + return $result; + } + } + } + + if ( is_wp_error( $result ) ) { + return $result; + } + + // No errors, install worked. Now replace the slug with the actual plugin id + $this->plugins[$index] = Jetpack_Plugins::get_plugin_id_by_slug( $slug ); + + return true; + } + + protected function validate_plugins() { + if ( empty( $this->plugins ) || ! is_array( $this->plugins ) ) { + return new WP_Error( 'missing_plugins', __( 'No plugins found.', 'jetpack' ) ); + } + + jetpack_require_lib( 'plugins' ); + foreach ( $this->plugins as $index => $slug ) { + // make sure it is not already installed + if ( Jetpack_Plugins::get_plugin_id_by_slug( $slug ) ) { + return new WP_Error( 'plugin_already_installed', __( 'The plugin is already installed', 'jetpack' ) ); + } + + } + + return true; + } +} 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..ff8004d0 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php @@ -0,0 +1,36 @@ +<?php + +new Jetpack_JSON_API_Plugins_List_Endpoint( + array( + 'description' => 'Get installed Plugins on your blog', + 'method' => 'GET', + 'path' => '/sites/%s/plugins', + 'stat' => 'plugins', + 'min_version' => '1', + 'max_version' => '1.1', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'plugins' => '(plugin) 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' + ) +); +// No v1.2 versions since they are .com only +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..49cf43dc --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-endpoint.php @@ -0,0 +1,420 @@ +<?php +new Jetpack_JSON_API_Plugins_Modify_Endpoint( + array( + 'description' => 'Activate/Deactivate a Plugin on your Jetpack Site, or set automatic updates', + 'min_version' => '1', + 'max_version' => '1.1', + '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)', + ), + 'query_parameters' => array( + 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event', + ), + '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', + 'min_version' => '1', + 'max_version' => '1.1', + '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', + ), + 'query_parameters' => array( + 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event', + ), + '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' + ) +); + +new Jetpack_JSON_API_Plugins_Modify_Endpoint( + array( + 'description' => 'Update a Plugin on your Jetpack Site', + 'min_version' => '1', + 'max_version' => '1.1', + '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', + ), + 'query_parameters' => array( + 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event', + ), + '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' + ) +); + +class Jetpack_JSON_API_Plugins_Modify_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint { + // POST /sites/%s/plugins/%s + // POST /sites/%s/plugins + protected $slug = null; + protected $needed_capabilities = 'activate_plugins'; + protected $action = 'default_action'; + protected $expected_actions = array( 'update', 'install', 'delete', 'update_translations' ); + + public function callback( $path = '', $blog_id = 0, $object = null ) { + Jetpack_JSON_API_Endpoint::validate_input( $object ); + switch ( $this->action ) { + case 'delete': + $this->needed_capabilities = 'delete_plugins'; + case 'update_translations': + case 'update' : + $this->needed_capabilities = 'update_plugins'; + break; + case 'install' : + $this->needed_capabilities = 'install_plugins'; + break; + } + + if ( isset( $args['autoupdate'] ) || isset( $args['autoupdate_translations'] ) ) { + $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'] ) { + // We don't have to check for activate_plugins permissions since we assume that the user has those + // Since we set them via $needed_capabilities. + return $this->activate(); + } else { + if ( $this->current_user_can( 'deactivate_plugins' ) ) { + return $this->deactivate(); + } else { + return new WP_Error( 'unauthorized_error', __( 'Plugin deactivation is not allowed', 'jetpack' ), '403' ); + } + } + } + + if ( isset( $args['autoupdate_translations'] ) && is_bool( $args['autoupdate_translations'] ) ) { + if ( $args['autoupdate_translations'] ) { + $this->autoupdate_translations_on(); + } else { + $this->autoupdate_translations_off(); + } + } + + 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 autoupdate_translations_on() { + $autoupdate_plugins = Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ); + $autoupdate_plugins = array_unique( array_merge( $autoupdate_plugins, $this->plugins ) ); + Jetpack_Options::update_option( 'autoupdate_plugins_translations', $autoupdate_plugins ); + } + + protected function autoupdate_translations_off() { + $autoupdate_plugins = Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() ); + $autoupdate_plugins = array_diff( $autoupdate_plugins, $this->plugins ); + Jetpack_Options::update_option( 'autoupdate_plugins_translations', $autoupdate_plugins ); + } + + protected function activate() { + $permission_error = false; + foreach ( $this->plugins as $plugin ) { + + if ( ! $this->current_user_can( 'activate_plugin', $plugin ) ) { + $this->log[$plugin]['error'] = __( 'Sorry, you are not allowed to activate this plugin.' ); + $has_errors = true; + $permission_error = true; + continue; + } + + 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 ) && is_multisite() ) { + $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]; + if ( $permission_error ) { + return new WP_Error( 'unauthorized_error', $this->log[$plugin]['error'], 403 ); + } + + return new WP_Error( 'activation_error', $this->log[$plugin]['error'] ); + } + } + + protected function current_user_can( $capability, $plugin = null ) { + if ( $plugin ) { + return current_user_can( $capability, $plugin ); + } + + return current_user_can( $capability ); + } + + protected function deactivate() { + $permission_error = false; + foreach ( $this->plugins as $plugin ) { + if ( ! $this->current_user_can( 'deactivate_plugin', $plugin ) ) { + $error = $this->log[$plugin]['error'] = __( 'Sorry, you are not allowed to deactivate this plugin.', 'jetpack' ); + $permission_error = true; + continue; + } + + 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 ) ) { + if ( $permission_error ) { + return new WP_Error( 'unauthorized_error', $error, 403 ); + } + + return new WP_Error( 'deactivation_error', $error ); + } + } + + protected function update() { + $query_args = $this->query_args(); + if ( isset( $query_args['autoupdate'] ) && $query_args['autoupdate'] ) { + Jetpack_Constants::set_constant( 'JETPACK_PLUGIN_AUTOUPDATE', true ); + } + 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' ); + + $result = false; + + foreach ( $this->plugins as $plugin ) { + + if ( ! in_array( $plugin, $plugin_updates_needed ) ) { + $this->log[$plugin][] = __( 'No update needed', 'jetpack' ); + continue; + } + + /** + * Pre-upgrade action + * + * @since 3.9.3 + * + * @param array $plugin Plugin data + * @param array $plugin Array of plugin objects + * @param bool $updated_attempted false for the first update, true subsequently + */ + do_action( 'jetpack_pre_plugin_upgrade', $plugin, $this->plugins, $update_attempted ); + + $update_attempted = true; + + // Object created inside the for loop to clean the messages for each plugin + $skin = new WP_Ajax_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. + // Using bulk upgrade puts the site into maintenance mode during the upgrades + $result = $upgrader->bulk_upgrade( array( $plugin ) ); + $errors = $upgrader->skin->get_errors(); + $this->log[$plugin] = $upgrader->skin->get_upgrade_messages(); + + if ( is_wp_error( $errors ) && $errors->get_error_code() ) { + return $errors; + } + } + + 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(); + } + + function update_translations() { + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + // Clear the cache. + wp_clean_plugins_cache(); + ob_start(); + wp_update_plugins(); // Check for Plugin updates + ob_end_clean(); + + $available_updates = get_site_transient( 'update_plugins' ); + if ( ! isset( $available_updates->translations ) || empty( $available_updates->translations ) ) { + return new WP_Error( 'nothing_to_translate' ); + } + + $update_attempted = false; + $result = false; + foreach ( $this->plugins as $plugin ) { + $this->slug = Jetpack_Autoupdate::get_plugin_slug( $plugin ); + $translation = array_filter( $available_updates->translations, array( $this, 'get_translation' ) ); + + if ( empty( $translation ) ) { + $this->log[$plugin][] = __( 'No update needed', 'jetpack' ); + continue; + } + + /** + * Pre-upgrade action + * + * @since 4.4.0 + * + * @param array $plugin Plugin data + * @param array $plugin Array of plugin objects + * @param bool $update_attempted false for the first update, true subsequently + */ + do_action( 'jetpack_pre_plugin_upgrade_translations', $plugin, $this->plugins, $update_attempted ); + + $update_attempted = true; + + $skin = new Automatic_Upgrader_Skin(); + $upgrader = new Language_Pack_Upgrader( $skin ); + $upgrader->init(); + + $result = $upgrader->upgrade( (object) $translation[0] ); + + $this->log[$plugin] = $upgrader->skin->get_upgrade_messages(); + } + + if ( ! $this->bulk && ! $result ) { + return new WP_Error( 'update_fail', __( 'There was an error updating your plugin', 'jetpack' ), 400 ); + } + + return true; + } + + protected function get_translation( $translation ) { + return ( $translation['slug'] === $this->slug ); + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-v1-2-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-v1-2-endpoint.php new file mode 100644 index 00000000..23940da0 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-modify-v1-2-endpoint.php @@ -0,0 +1,191 @@ +<?php +new Jetpack_JSON_API_Plugins_Modify_v1_2_Endpoint( + array( + 'description' => 'Activate/Deactivate a Plugin on your Jetpack Site, or set automatic updates', + 'min_version' => '1.2', + '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)', + ), + 'query_parameters' => array( + 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event', + ), + 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format_v1_2, + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + 'body' => array( + 'action' => 'update', + ) + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.2/sites/example.wordpress.org/plugins/hello-dolly%20hello' + ) +); + +new Jetpack_JSON_API_Plugins_Modify_v1_2_Endpoint( + array( + 'description' => 'Activate/Deactivate a list of plugins on your Jetpack Site, or set automatic updates', + 'min_version' => '1.2', + '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', + ), + 'query_parameters' => array( + 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event', + ), + 'response_format' => array( + 'plugins' => '(array:plugin_v1_2) 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.2/sites/example.wordpress.org/plugins' + ) +); + +new Jetpack_JSON_API_Plugins_Modify_v1_2_Endpoint( + array( + 'description' => 'Update a Plugin on your Jetpack Site', + 'min_version' => '1.2', + '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', + ), + 'query_parameters' => array( + 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event', + ), + 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format_v1_2, + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.2/sites/example.wordpress.org/plugins/hello-dolly%20hello/update' + ) +); + +class Jetpack_JSON_API_Plugins_Modify_v1_2_Endpoint extends Jetpack_JSON_API_Plugins_Modify_Endpoint { + + protected function activate() { + $permission_error = false; + $has_errors = false; + foreach ( $this->plugins as $plugin ) { + + if ( ! $this->current_user_can( 'activate_plugin', $plugin ) ) { + $this->log[$plugin]['error'] = __( 'Sorry, you are not allowed to activate this plugin.' ); + $has_errors = true; + $permission_error = true; + continue; + } + + if ( ( ! $this->network_wide && Jetpack::is_plugin_active( $plugin ) ) || is_plugin_active_for_network( $plugin ) ) { + continue; + } + + if ( ! $this->network_wide && is_network_only_plugin( $plugin ) && is_multisite() ) { + $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 && $has_errors ) { + $plugin = $this->plugins[0]; + if ( $permission_error ) { + return new WP_Error( 'unauthorized_error', $this->log[$plugin]['error'], 403 ); + } + + return new WP_Error( 'activation_error', $this->log[$plugin]['error'] ); + } + } + + + protected function deactivate() { + $permission_error = false; + foreach ( $this->plugins as $plugin ) { + if ( ! $this->current_user_can( 'deactivate_plugin', $plugin ) ) { + $error = $this->log[$plugin]['error'] = __( 'Sorry, you are not allowed to deactivate this plugin.', 'jetpack' ); + $permission_error = true; + continue; + } + + if ( ! Jetpack::is_plugin_active( $plugin ) ) { + 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 ) ) { + if ( $permission_error ) { + return new WP_Error( 'unauthorized_error', $error, 403 ); + } + + return new WP_Error( 'deactivation_error', $error ); + } + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-new-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-new-endpoint.php new file mode 100644 index 00000000..286a625b --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-new-endpoint.php @@ -0,0 +1,136 @@ +<?php + +include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; +include_once ABSPATH . 'wp-admin/includes/file.php'; + + +// POST /sites/%s/plugins/new +new Jetpack_JSON_API_Plugins_New_Endpoint( + array( + 'description' => 'Install a plugin to a Jetpack site by uploading a zip file', + 'group' => '__do_not_document', + 'stat' => 'plugins:new', + 'min_version' => '1', + 'max_version' => '1.1', + 'method' => 'POST', + 'path' => '/sites/%s/plugins/new', + 'path_labels' => array( + '$site' => '(int|string) Site ID or domain', + ), + 'request_format' => array( + 'zip' => '(zip) Plugin package zip file. multipart/form-data encoded. ', + ), + '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/new' + ) +); + + +new Jetpack_JSON_API_Plugins_New_Endpoint( + array( + 'description' => 'Install a plugin to a Jetpack site by uploading a zip file', + 'group' => '__do_not_document', + 'stat' => 'plugins:new', + 'min_version' => '1.2', + 'method' => 'POST', + 'path' => '/sites/%s/plugins/new', + 'path_labels' => array( + '$site' => '(int|string) Site ID or domain', + ), + 'request_format' => array( + 'zip' => '(zip) Plugin package zip file. multipart/form-data encoded. ', + ), + 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format_v1_2, + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.2/sites/example.wordpress.org/plugins/new' + ) +); + +class Jetpack_JSON_API_Plugins_New_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint { + + // POST /sites/%s/plugins/new + protected $needed_capabilities = 'install_plugins'; + protected $action = 'install'; + + protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) { + $validate = parent::validate_call( $_blog_id, $capability, $check_manage_active ); + if ( is_wp_error( $validate ) ) { + + // Lets delete the attachment... if the user doesn't have the right permissions to do things. + $args = $this->input(); + if ( isset( $args['zip'][0]['id'] ) ) { + wp_delete_attachment( $args['zip'][0]['id'], true ); + } + } + + return $validate; + } + + // no need to try to validate the plugin since we didn't pass one in. + protected function validate_input( $plugin ) { + $this->bulk = false; + $this->plugins = array(); + } + + function install() { + $args = $this->input(); + + if ( isset( $args['zip'][0]['id'] ) ) { + $plugin_attachment_id = $args['zip'][0]['id']; + $local_file = get_attached_file( $plugin_attachment_id ); + if ( ! $local_file ) { + return new WP_Error( 'local-file-does-not-exist' ); + } + jetpack_require_lib( 'class.jetpack-automatic-install-skin' ); + $skin = new Jetpack_Automatic_Install_Skin(); + $upgrader = new Plugin_Upgrader( $skin ); + + $pre_install_plugin_list = get_plugins(); + $result = $upgrader->install( $local_file ); + + // clean up. + wp_delete_attachment( $plugin_attachment_id, true ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + $after_install_plugin_list = get_plugins(); + $plugin = array_values( array_diff( array_keys( $after_install_plugin_list ), array_keys( $pre_install_plugin_list ) ) ); + + if ( ! $result ) { + $error_code = $upgrader->skin->get_main_error_code(); + $message = $upgrader->skin->get_main_error_message(); + if ( empty( $message ) ) { + $message = __( 'An unknown error occurred during installation', 'jetpack' ); + } + + if ( 'download_failed' === $error_code ) { + $error_code = 'no_package'; + } + + return new WP_Error( $error_code, $message, 400 ); + } + + if ( empty( $plugin ) ) { + return new WP_Error( 'plugin_already_installed' ); + } + + $this->plugins = $plugin; + $this->log[ $plugin[0] ] = $upgrader->skin->get_upgrade_messages(); + + return true; + } + + return new WP_Error( 'no_plugin_installed' ); + } +} 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..72dcd52c --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-sync-endpoint.php @@ -0,0 +1,322 @@ +<?php + +// POST /sites/%s/sync +class Jetpack_JSON_API_Sync_Endpoint extends Jetpack_JSON_API_Endpoint { + protected $needed_capabilities = 'manage_options'; + + protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) { + return parent::validate_call( $_blog_id, $capability, false ); + } + + protected function result() { + $args = $this->input(); + $modules = null; + + // convert list of modules in comma-delimited format into an array + // of "$modulename => true" + if ( isset( $args['modules'] ) && ! empty( $args['modules'] ) ) { + $modules = array_map( '__return_true', array_flip( array_map( 'trim', explode( ',', $args['modules'] ) ) ) ); + } + + foreach ( array( 'posts', 'comments', 'users' ) as $module_name ) { + if ( 'users' === $module_name && isset( $args[ $module_name ] ) && 'initial' === $args[ $module_name ] ) { + $modules[ 'users' ] = 'initial'; + } elseif ( isset( $args[ $module_name ] ) ) { + $ids = explode( ',', $args[ $module_name ] ); + if ( count( $ids ) > 0 ) { + $modules[ $module_name ] = $ids; + } + } + } + + if ( empty( $modules ) ) { + $modules = null; + } + return array( 'scheduled' => Jetpack_Sync_Actions::do_full_sync( $modules ) ); + } + + protected function validate_queue( $query ) { + if ( ! isset( $query ) ) { + return new WP_Error( 'invalid_queue', 'Queue name is required', 400 ); + } + + if ( ! in_array( $query, array( 'sync', 'full_sync' ) ) ) { + return new WP_Error( 'invalid_queue', 'Queue name should be sync or full_sync', 400 ); + } + return $query; + } +} + +// GET /sites/%s/sync/status +class Jetpack_JSON_API_Sync_Status_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + return Jetpack_Sync_Actions::get_sync_status(); + } +} + +// GET /sites/%s/data-check +class Jetpack_JSON_API_Sync_Check_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-wp-replicastore.php'; + $store = new Jetpack_Sync_WP_Replicastore(); + return $store->checksum_all(); + } +} + +// GET /sites/%s/data-histogram +class Jetpack_JSON_API_Sync_Histogram_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $args = $this->query_args(); + + if ( isset( $args['columns'] ) ) { + $columns = array_map( 'trim', explode( ',', $args['columns'] ) ); + } else { + $columns = null; // go with defaults + } + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-wp-replicastore.php'; + $store = new Jetpack_Sync_WP_Replicastore(); + + if ( ! isset( $args['strip_non_ascii'] ) ) { + $args['strip_non_ascii'] = true; + } + $histogram = $store->checksum_histogram( $args['object_type'], $args['buckets'], $args['start_id'], $args['end_id'], $columns, $args['strip_non_ascii'], $args['shared_salt'] ); + + return array( 'histogram' => $histogram, 'type' => $store->get_checksum_type() ); + } +} + +// POST /sites/%s/sync/settings +class Jetpack_JSON_API_Sync_Modify_Settings_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $args = $this->input(); + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-settings.php'; + + $sync_settings = Jetpack_Sync_Settings::get_settings(); + + foreach ( $args as $key => $value ) { + if ( $value !== false ) { + if ( is_numeric( $value ) ) { + $value = (int) $value; + } + + // special case for sending empty arrays - a string with value 'empty' + if ( $value === 'empty' ) { + $value = array(); + } + + $sync_settings[ $key ] = $value; + } + } + + Jetpack_Sync_Settings::update_settings( $sync_settings ); + + // re-fetch so we see what's really being stored + return Jetpack_Sync_Settings::get_settings(); + } +} + +// GET /sites/%s/sync/settings +class Jetpack_JSON_API_Sync_Get_Settings_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-settings.php'; + + return Jetpack_Sync_Settings::get_settings(); + } +} + +// GET /sites/%s/sync/object +class Jetpack_JSON_API_Sync_Object extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $args = $this->query_args(); + + $module_name = $args['module_name']; + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-modules.php'; + + if ( ! $sync_module = Jetpack_Sync_Modules::get_module( $module_name ) ) { + return new WP_Error( 'invalid_module', 'You specified an invalid sync module' ); + } + + $object_type = $args['object_type']; + $object_ids = $args['object_ids']; + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php'; + $codec = Jetpack_Sync_Sender::get_instance()->get_codec(); + + Jetpack_Sync_Settings::set_is_syncing( true ); + $objects = $codec->encode( $sync_module->get_objects_by_id( $object_type, $object_ids ) ); + Jetpack_Sync_Settings::set_is_syncing( false ); + + return array( + 'objects' => $objects, + 'codec' => $codec->name(), + ); + } +} + +class Jetpack_JSON_API_Sync_Now_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $args = $this->input(); + $queue_name = $this->validate_queue( $args['queue'] ); + + if ( is_wp_error( $queue_name ) ){ + return $queue_name; + } + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php'; + + $sender = Jetpack_Sync_Sender::get_instance(); + $response = $sender->do_sync_for_queue( new Jetpack_Sync_Queue( $args['queue'] ) ); + + return array( + 'response' => $response + ); + } +} + +class Jetpack_JSON_API_Sync_Checkout_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $args = $this->input(); + $queue_name = $this->validate_queue( $args['queue'] ); + + if ( is_wp_error( $queue_name ) ){ + return $queue_name; + } + + if ( $args[ 'number_of_items' ] < 1 || $args[ 'number_of_items' ] > 100 ) { + return new WP_Error( 'invalid_number_of_items', 'Number of items needs to be an integer that is larger than 0 and less then 100', 400 ); + } + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-queue.php'; + $queue = new Jetpack_Sync_Queue( $queue_name ); + + if ( 0 === $queue->size() ) { + return new WP_Error( 'queue_size', 'The queue is empty and there is nothing to send', 400 ); + } + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-sender.php'; + $sender = Jetpack_Sync_Sender::get_instance(); + + // try to give ourselves as much time as possible + set_time_limit( 0 ); + + // let's delete the checkin state + if ( $args['force'] ) { + $queue->unlock(); + } + + $buffer = $this->get_buffer( $queue, $args[ 'number_of_items' ] ); + + // Check that the $buffer is not checkout out already + if ( is_wp_error( $buffer ) ) { + return new WP_Error( 'buffer_open', "We couldn't get the buffer it is currently checked out", 400 ); + } + + if ( ! is_object( $buffer ) ) { + return new WP_Error( 'buffer_non-object', 'Buffer is not an object', 400 ); + } + + Jetpack_Sync_Settings::set_is_syncing( true ); + list( $items_to_send, $skipped_items_ids, $items ) = $sender->get_items_to_send( $buffer, $args['encode'] ); + Jetpack_Sync_Settings::set_is_syncing( false ); + + return array( + 'buffer_id' => $buffer->id, + 'items' => $items_to_send, + 'skipped_items' => $skipped_items_ids, + 'codec' => $args['encode'] ? $sender->get_codec()->name() : null, + 'sent_timestamp' => time(), + ); + } + + protected function get_buffer( $queue, $number_of_items ) { + $start = time(); + $max_duration = 5; // this will try to get the buffer + + $buffer = $queue->checkout( $number_of_items ); + $duration = time() - $start; + + while( is_wp_error( $buffer ) && $duration < $max_duration ) { + sleep( 2 ); + $duration = time() - $start; + $buffer = $queue->checkout( $number_of_items ); + } + + if ( $buffer === false ) { + return new WP_Error( 'queue_size', 'The queue is empty and there is nothing to send', 400 ); + } + + return $buffer; + } +} + +class Jetpack_JSON_API_Sync_Close_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $request_body = $this->input(); + $queue_name = $this->validate_queue( $request_body['queue'] ); + + if ( is_wp_error( $queue_name ) ) { + return $queue_name; + } + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-queue.php'; + + if ( ! isset( $request_body['buffer_id'] ) ) { + return new WP_Error( 'missing_buffer_id', 'Please provide a buffer id', 400 ); + } + + if ( ! isset( $request_body['item_ids'] ) || ! is_array( $request_body['item_ids'] ) ) { + return new WP_Error( 'missing_item_ids', 'Please provide a list of item ids in the item_ids argument', 400 ); + } + + //Limit to A-Z,a-z,0-9,_,- + $request_body ['buffer_id'] = preg_replace( '/[^A-Za-z0-9]/', '', $request_body['buffer_id'] ); + $request_body['item_ids'] = array_filter( array_map( array( 'Jetpack_JSON_API_Sync_Close_Endpoint', 'sanitize_item_ids' ), $request_body['item_ids'] ) ); + + $buffer = new Jetpack_Sync_Queue_Buffer( $request_body['buffer_id'], $request_body['item_ids'] ); + $queue = new Jetpack_Sync_Queue( $queue_name ); + + $response = $queue->close( $buffer, $request_body['item_ids'] ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + return array( + 'success' => $response + ); + } + + protected static function sanitize_item_ids( $item ) { + // lets not delete any options that don't start with jpsq_sync- + if ( substr( $item, 0, 5 ) !== 'jpsq_' ) { + return null; + } + //Limit to A-Z,a-z,0-9,_,-,. + return preg_replace( '/[^A-Za-z0-9-_.]/', '', $item ); + } +} + +class Jetpack_JSON_API_Sync_Unlock_Endpoint extends Jetpack_JSON_API_Sync_Endpoint { + protected function result() { + $args = $this->input(); + + if ( ! isset( $args['queue'] ) ) { + return new WP_Error( 'invalid_queue', 'Queue name is required', 400 ); + } + + if ( ! in_array( $args['queue'], array( 'sync', 'full_sync' ) ) ) { + return new WP_Error( 'invalid_queue', 'Queue name should be sync or full_sync', 400 ); + } + + require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-queue.php'; + $queue = new Jetpack_Sync_Queue( $args['queue'] ); + + // False means that there was no lock to delete. + $response = $queue->unlock(); + return array( + 'success' => $response + ); + } +} 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..db23c52f --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-active-endpoint.php @@ -0,0 +1,50 @@ +<?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 + // The unused $object parameter is for making the method signature compatible with its parent class method. + public function callback( $path = '', $blog_id = 0, $object = null ) { + + 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..97bcc58d --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-delete-endpoint.php @@ -0,0 +1,60 @@ +<?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; + } + + /** + * Filters whether to use an alternative process for deleting a WordPress.com theme. + * The alternative process can be executed during the filter. + * + * The filter can also return an instance of WP_Error; in which case the endpoint response will + * contain this error. + * + * @module json-api + * + * @since 4.4.2 + * + * @param bool $use_alternative_delete_method Whether to use the alternative method of deleting + * a WPCom theme. + * @param string $theme_slug Theme name (slug). If it is a WPCom theme, + * it should be suffixed with `-wpcom`. + */ + $result = apply_filters( 'jetpack_wpcom_theme_delete', false, $theme ); + + if ( ! $result ) { + $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..2e32dccd --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-endpoint.php @@ -0,0 +1,178 @@ +<?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; + protected $current_theme_id; + + static $_response_format = array( + 'id' => '(string) The theme\'s ID.', + 'screenshot' => '(string) A theme screenshot URL', + 'name' => '(string) The name of the theme.', + 'theme_uri' => '(string) The URI of the theme\'s webpage.', + 'description' => '(string) A description of the theme.', + 'author' => '(string) The author of the theme.', + 'author_uri' => '(string) The website of the theme author.', + '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', + 'autoupdate_translation' => '(bool) Whether the theme is automatically updating translations', + ); + + 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', + 'theme_uri' => 'ThemeURI', + 'description' => 'Description', + 'author' => 'Author', + 'author_uri' => 'AuthorURI', + 'tags' => 'Tags', + 'version' => 'Version' + ); + + $id = $theme->get_stylesheet(); + $formatted_theme = array( + 'id' => $id, + 'screenshot' => jetpack_photon_url( $theme->get_screenshot(), array(), 'network_path' ), + 'active' => $id === $this->current_theme_id, + ); + + 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 = in_array( $id, Jetpack_Options::get_option( 'autoupdate_themes', array() ) ); + $formatted_theme['autoupdate'] = $autoupdate; + + $autoupdate_translation = in_array( $id, Jetpack_Options::get_option( 'autoupdate_themes_translations', array() ) ); + $formatted_theme['autoupdate_translation'] = $autoupdate || $autoupdate_translation || Jetpack_Options::get_option( 'autoupdate_translations', false ); + + if ( isset( $this->log[ $id ] ) ) { + $formatted_theme['log'] = $this->log[ $id ]; + } + + /** + * Filter the array of theme information that will be returned per theme by the Jetpack theme APIs. + * + * @module json-api + * + * @since 4.7.0 + * + * @param array $formatted_theme The theme info array. + */ + return apply_filters( 'jetpack_format_theme_details', $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'] ); + + $this->current_theme_id = wp_get_theme()->get_stylesheet(); + + 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..cfc352af --- /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 = 'switch_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..c3cec3d3 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-install-endpoint.php @@ -0,0 +1,173 @@ +<?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 ) { + + /** + * Filters whether to use an alternative process for installing a WordPress.com theme. + * The alternative process can be executed during the filter. + * + * The filter can also return an instance of WP_Error; in which case the endpoint response will + * contain this error. + * + * @module json-api + * + * @since 4.4.2 + * + * @param bool $use_alternative_install_method Whether to use the alternative method of installing + * a WPCom theme. + * @param string $theme_slug Theme name (slug). If it is a WPCom theme, + * it should be suffixed with `-wpcom`. + */ + $result = apply_filters( 'jetpack_wpcom_theme_install', false, $theme ); + + $skin = null; + $upgrader = null; + $link = null; + + // If the alternative install method was not used, use the standard method. + if ( ! $result ) { + jetpack_require_lib( 'class.jetpack-automatic-install-skin' ); + $skin = new Jetpack_Automatic_Install_Skin(); + $upgrader = new Theme_Upgrader( $skin ); + + $link = $this->download_links[ $theme ]; + $result = $upgrader->install( $link ); + } + + if ( file_exists( $link ) ) { + // Delete if link was tmp local file + unlink( $link ); + } + + 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' ); + } + + elseif ( $upgrader ) { + $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' ) ); + } + + /** + * Filters whether to skip the standard method of downloading and validating a WordPress.com + * theme. An alternative method of WPCom theme download and validation can be + * executed during the filter. + * + * The filter can also return an instance of WP_Error; in which case the endpoint response will + * contain this error. + * + * @module json-api + * + * @since 4.4.2 + * + * @param bool $skip_download_filter_result Whether to skip the standard method of downloading + * and validating a WPCom theme. + * @param string $theme_slug Theme name (slug). If it is a WPCom theme, + * it should be suffixed with `-wpcom`. + */ + $skip_download_filter_result = apply_filters( 'jetpack_wpcom_theme_skip_download', false, $theme ); + + if ( is_wp_error( $skip_download_filter_result ) ) { + return $skip_download_filter_result; + } elseif ( $skip_download_filter_result ) { + continue; + } + + if ( wp_endswith( $theme, '-wpcom' ) ) { + $file = self::download_wpcom_theme_to_file( $theme ); + + if ( is_wp_error( $file ) ) { + return $file; + } + + $this->download_links[ $theme ] = $file; + continue; + } + + $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; + } + + if ( ! is_object( $theme_data ) && !isset( $theme_data->download_link ) ) { + return new WP_Error( 'theme_not_found', __( 'This theme does not exist', 'jetpack' ) , 404 ); + } + + $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(); + } + + protected static function download_wpcom_theme_to_file( $theme ) { + $wpcom_theme_slug = preg_replace( '/-wpcom$/', '', $theme ); + + $file = wp_tempnam( 'theme' ); + if ( ! $file ) { + return new WP_Error( 'problem_creating_theme_file', __( 'Problem creating file for theme download', 'jetpack' ) ); + } + + $url = "themes/download/$theme.zip"; + $args = array( 'stream' => true, 'filename' => $file ); + $result = Jetpack_Client::wpcom_json_api_request_as_blog( $url, '1.1', $args ); + + $response = $result[ 'response' ]; + if ( $response[ 'code' ] !== 200 ) { + unlink( $file ); + return new WP_Error( 'problem_fetching_theme', __( 'Problem downloading theme', 'jetpack' ) ); + } + + return $file; + } +} 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..072bfc5c --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-modify-endpoint.php @@ -0,0 +1,130 @@ +<?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', 'update_translations' ); + + 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['autoupdate_translations'] ) && is_bool( $args['autoupdate_translations'] ) ) { + if ( $args['autoupdate_translations'] ) { + $this->autoupdate_translations_on(); + } else { + $this->autoupdate_translations_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 autoupdate_translations_on() { + $autoupdate_themes_translations = Jetpack_Options::get_option( 'autoupdate_themes_translations', array() ); + $autoupdate_themes_translations = array_unique( array_merge( $autoupdate_themes_translations, $this->themes ) ); + Jetpack_Options::update_option( 'autoupdate_themes_translations', $autoupdate_themes_translations ); + } + + function autoupdate_translations_off() { + $autoupdate_themes_translations = Jetpack_Options::get_option( 'autoupdate_themes_translations', array() ); + $autoupdate_themes_translations = array_diff( $autoupdate_themes_translations, $this->themes ); + Jetpack_Options::update_option( 'autoupdate_themes_translations', $autoupdate_themes_translations ); + } + + function update() { + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + // Clear the cache. + wp_update_themes(); + + foreach ( $this->themes as $theme ) { + /** + * Pre-upgrade action + * + * @since 3.9.3 + * + * @param object $theme WP_Theme object + * @param array $themes Array of theme objects + */ + do_action('jetpack_pre_theme_upgrade', $theme, $this->themes); + // 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; + } + + function update_translations() { + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + // Clear the cache. + wp_update_themes(); + + $available_themes_updates = get_site_transient( 'update_themes' ); + + if ( ! isset( $available_themes_updates->translations ) || empty( $available_themes_updates->translations ) ) { + return new WP_Error( 'nothing_to_translate' ); + } + + foreach( $available_themes_updates->translations as $translation ) { + $theme = $translation['slug'] ; + if ( ! in_array( $translation['slug'], $this->themes ) ) { + $this->log[ $theme ][] = __( 'No update needed', 'jetpack' ); + continue; + } + + /** + * Pre-upgrade action + * + * @since 4.4.0 + * + * @param object $theme WP_Theme object + * @param array $themes Array of theme objects + */ + do_action( 'jetpack_pre_theme_upgrade_translations', $theme, $this->themes ); + // Objects created inside the for loop to clean the messages for each theme + $skin = new Automatic_Upgrader_Skin(); + $upgrader = new Language_Pack_Upgrader( $skin ); + $upgrader->init(); + + $result = $upgrader->upgrade( (object) $translation ); + $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; + } + +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-new-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-new-endpoint.php new file mode 100644 index 00000000..75768183 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-themes-new-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_Themes_New_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 validate_call( $_blog_id, $capability, $check_manage_active = true ) { + $validate = parent::validate_call( $_blog_id, $capability, $check_manage_active ); + if ( is_wp_error( $validate ) ) { + // Lets delete the attachment... if the user doesn't have the right permissions to do things. + $args = $this->input(); + if ( isset( $args['zip'][0]['id'] ) ) { + wp_delete_attachment( $args['zip'][0]['id'], true ); + } + } + + return $validate; + } + + protected function validate_input( $theme ) { + $this->bulk = false; + $this->themes = array(); + } + + function install() { + $args = $this->input(); + + if ( isset( $args['zip'][0]['id'] ) ) { + $attachment_id = $args['zip'][0]['id']; + $local_file = get_attached_file( $attachment_id ); + if ( ! $local_file ) { + return new WP_Error( 'local-file-does-not-exist' ); + } + jetpack_require_lib( 'class.jetpack-automatic-install-skin' ); + $skin = new Jetpack_Automatic_Install_Skin(); + $upgrader = new Theme_Upgrader( $skin ); + + $pre_install_list = wp_get_themes(); + $result = $upgrader->install( $local_file ); + + // clean up. + wp_delete_attachment( $attachment_id, true ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + $after_install_list = wp_get_themes(); + $plugin = array_values( array_diff( array_keys( $after_install_list ), array_keys( $pre_install_list ) ) ); + + if ( ! $result ) { + $error_code = $upgrader->skin->get_main_error_code(); + $message = $upgrader->skin->get_main_error_message(); + if ( empty( $message ) ) { + $message = __( 'An unknown error occurred during installation', 'jetpack' ); + } + + if ( 'download_failed' === $error_code ) { + $error_code = 'no_package'; + } + + return new WP_Error( $error_code, $message, 400 ); + } + + if ( empty( $plugin ) ) { + return new WP_Error( 'theme_already_installed' ); + } + + $this->themes = $plugin; + $this->log[ $plugin[0] ] = $upgrader->skin->get_upgrade_messages(); + + return true; + } + + return new WP_Error( 'no_theme_installed' ); + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-translations-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-translations-endpoint.php new file mode 100644 index 00000000..65b6e725 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-translations-endpoint.php @@ -0,0 +1,20 @@ +<?php + +// Translations +class Jetpack_JSON_API_Translations_Endpoint extends Jetpack_JSON_API_Endpoint { + // GET /sites/%s/translations + // POST /sites/%s/translations + // POST /sites/%s/translations/update + protected $needed_capabilities = array( 'update_core', 'update_plugins', 'update_themes' ); + protected $log; + protected $success; + + public function result() { + return array( + 'translations' => wp_get_translation_updates(), + 'autoupdate' => Jetpack_Options::get_option( 'autoupdate_translations', false ), + 'log' => $this->log, + 'success' => $this->success, + ); + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-translations-modify-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-translations-modify-endpoint.php new file mode 100644 index 00000000..fd5f6a56 --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-translations-modify-endpoint.php @@ -0,0 +1,29 @@ +<?php + +class Jetpack_JSON_API_Translations_Modify_Endpoint extends Jetpack_JSON_API_Translations_Endpoint { + // POST /sites/%s/translations + // POST /sites/%s/translations/update + 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_translations', $args['autoupdate'] ); + } + + return true; + } + + protected function update() { + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + $upgrader = new Language_Pack_Upgrader( new Automatic_Upgrader_Skin() ); + $result = $upgrader->bulk_upgrade(); + + $this->log = $upgrader->skin->get_upgrade_messages(); + $this->success = ( ! is_wp_error( $result ) ) ? (bool) $result : false; + } +} 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/class.jetpack-json-api-user-connect-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-connect-endpoint.php new file mode 100644 index 00000000..b30597de --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-connect-endpoint.php @@ -0,0 +1,30 @@ +<?php + +class Jetpack_JSON_API_User_Connect_Endpoint extends Jetpack_JSON_API_Endpoint { + + protected $needed_capabilities = 'create_users'; + + private $user_id; + private $user_token; + + function result() { + Jetpack::update_user_token( $this->user_id, sprintf( '%s.%d', $this->user_token, $this->user_id ), false ); + return array( 'success' => Jetpack::is_user_connected( $this->user_id ) ); + } + + function validate_input( $user_id ) { + $input = $this->input(); + if ( ! isset( $user_id ) ) { + return new WP_Error( 'input_error', __( 'user_id is required', 'jetpack' ) ); + } + $this->user_id = $user_id; + if ( Jetpack::is_user_connected( $this->user_id ) ) { + return new WP_Error( 'user_already_connected', __( 'The user is already connected', 'jetpack' ) ); + } + if ( ! isset( $input['user_token'] ) ) { + return new WP_Error( 'input_error', __( 'user_token is required', 'jetpack' ) ); + } + $this->user_token = sanitize_text_field( $input[ 'user_token'] ); + return parent::validate_input( $user_id ); + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-create-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-create-endpoint.php new file mode 100644 index 00000000..bd71249b --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-create-endpoint.php @@ -0,0 +1,72 @@ +<?php + +class Jetpack_JSON_API_User_Create_Endpoint extends Jetpack_JSON_API_Endpoint { + + protected $needed_capabilities = 'create_users'; + + private $user_data; + + function result() { + return $this->create_or_get_user(); + } + + function validate_input( $object ) { + $this->user_data = $this->input(); + + if ( empty( $this->user_data ) ) { + return new WP_Error( 'input_error', __( 'user_data is required', 'jetpack' ) ); + } + if ( ! isset( $this->user_data[ 'email' ] ) ) { + return new WP_Error( 'input_error', __( 'user email is required', 'jetpack' ) ); + } + if ( ! isset( $this->user_data[ 'login' ] ) ) { + return new WP_Error( 'input_error', __( 'user login is required', 'jetpack' ) ); + } + return parent::validate_input( $object ); + } + + function create_or_get_user() { + require_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php'; + // Check for an existing user + $user = get_user_by( 'email', $this->user_data['email'] ); + $roles = (array) $this->user_data['roles']; + $role = array_pop( $roles ); + + $query_args = $this->query_args(); + if ( isset( $query_args['invite_accepted'] ) && $query_args['invite_accepted'] ) { + Jetpack_Constants::set_constant( 'JETPACK_INVITE_ACCEPTED', true ); + } + + if ( ! $user ) { + // We modify the input here to mimick the same call structure of the update user endpoint. + $this->user_data = (object) $this->user_data; + $this->user_data->role = $role; + $this->user_data->url = isset( $this->user_data->URL ) ? $this->user_data->URL : ''; + $this->user_data->display_name = $this->user_data->name; + $this->user_data->description = ''; + $user = Jetpack_SSO_Helpers::generate_user( $this->user_data ); + } + + if ( is_multisite() ) { + add_user_to_blog( get_current_blog_id(), $user->ID, $role ); + } + + if ( ! $user ) { + + return false; + } + + return $this->get_user( $user->ID ); + } + + public function get_user( $user_id ) { + $the_user = $this->get_author( $user_id, true ); + if ( $the_user && ! is_wp_error( $the_user ) ) { + $userdata = get_userdata( $user_id ); + $the_user->roles = ! is_wp_error( $userdata ) ? $userdata->roles : array(); + } + + return $the_user; + } + +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php new file mode 100644 index 00000000..3a76256f --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php @@ -0,0 +1,41 @@ +<?php + +class WPCOM_JSON_API_Get_Option_Endpoint extends Jetpack_JSON_API_Endpoint { + + protected $needed_capabilities = 'manage_options'; + + public $option_name; + public $site_option; + + function result() { + if ( $this->site_option ) { + return array( 'option_value' => get_site_option( $this->option_name ) ); + } + return array( 'option_value' => get_option( $this->option_name ) ); + } + + function validate_input( $object ) { + $query_args = $this->query_args(); + $this->option_name = isset( $query_args['option_name'] ) ? $query_args['option_name'] : false; + if ( ! $this->option_name ) { + return new WP_Error( 'option_name_not_set', __( 'You must specify an option_name', 'jetpack' ) ); + } + $this->site_option = isset( $query_args['site_option'] ) ? $query_args['site_option'] : false; + + require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-defaults.php'; + /** + * Filter the list of options that are manageable via the JSON API. + * + * @module json-api + * + * @since 3.8.2 + * + * @param array The default list of site options. + * @param bool Is the option a site option. + */ + if ( ! in_array( $this->option_name, apply_filters( 'jetpack_options_whitelist', Jetpack_Sync_Defaults::$default_options_whitelist, $this->site_option ) ) ) { + return new WP_Error( 'option_name_not_in_whitelist', __( 'You must specify a whitelisted option_name', 'jetpack' ) ); + } + return true; + } +} diff --git a/plugins/jetpack/json-endpoints/jetpack/class.wpcom-json-api-update-option-endpoint.php b/plugins/jetpack/json-endpoints/jetpack/class.wpcom-json-api-update-option-endpoint.php new file mode 100644 index 00000000..20f8895c --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/class.wpcom-json-api-update-option-endpoint.php @@ -0,0 +1,31 @@ +<?php + +class WPCOM_JSON_API_Update_Option_Endpoint extends WPCOM_JSON_API_Get_Option_Endpoint { + public $option_value; + + function result() { + if ( $this->site_option ) { + update_site_option( $this->option_name, $this->option_value ); + } else { + update_option( $this->option_name, $this->option_value ); + } + return parent::result(); + } + + function validate_input( $object ) { + $input = $this->input(); + $query_args = $this->query_args(); + if ( ! isset( $input['option_value'] ) || is_array( $input['option_value'] ) ) { + return new WP_Error( 'option_value_not_set', __( 'You must specify an option_value', 'jetpack' ) ); + } + if ( $query_args['is_array'] ) { + // When converted back from JSON, the value is an object. + // Cast it to an array for options that expect arrays. + $this->option_value = (array) $input['option_value']; + } else { + $this->option_value = $input['option_value']; + } + + return parent::validate_input( $object ); + } +} 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..a7fd2acd --- /dev/null +++ b/plugins/jetpack/json-endpoints/jetpack/json-api-jetpack-endpoints.php @@ -0,0 +1,1234 @@ +<?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' ); +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-themes-new-endpoint.php' ); + +// POST /sites/%s/themes/%new +new Jetpack_JSON_API_Themes_New_Endpoint( array( + 'description' => 'Install a theme to your jetpack blog', + 'group' => '__do_not_document', + 'stat' => 'themes:new', + 'method' => 'POST', + 'path' => '/sites/%s/themes/new', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + ), + 'request_format' => array( + 'zip' => '(zip) Theme package zip file. multipart/form-data encoded. ', + ), + '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/new' +) ); + + + +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' => 'GET', + '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' ); +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-new-endpoint.php' ); +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-install-endpoint.php' ); +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-delete-endpoint.php' ); +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-modify-endpoint.php' ); + +// PLUGINS V1.2 +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-plugins-modify-v1-2-endpoint.php' ); + +// 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' ); + +// POST /sites/%s/sync +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' + ), + 'request_format' => array( + 'modules' => '(string) Comma-delimited set of sync modules to use (default: all of them)', + 'posts' => '(string) Comma-delimited list of post IDs to sync', + 'comments' => '(string) Comma-delimited list of comment IDs to sync', + 'users' => '(string) Comma-delimited list of user IDs to sync', + ), + 'response_format' => array( + 'scheduled' => '(bool) Whether or not the synchronisation was started' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync' +) ); + +// GET /sites/%s/sync/status +new Jetpack_JSON_API_Sync_Status_Endpoint( array( + 'description' => 'Status of the current full sync or the previous full sync', + 'method' => 'GET', + 'path' => '/sites/%s/sync/status', + 'stat' => 'sync-status', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'started' => '(int|null) The unix timestamp when the last sync started', + 'queue_finished' => '(int|null) The unix timestamp when the enqueuing was done for the last sync', + 'send_started' => '(int|null) The unix timestamp when the last sent process started', + 'finished' => '(int|null) The unix timestamp when the last sync finished', + 'total' => '(array) Count of actions that could be sent', + 'queue' => '(array) Count of actions that have been added to the queue', + 'sent' => '(array) Count of actions that have been sent', + 'config' => '(array) Configuration of the last full sync', + 'queue_size' => '(int) Number of items in the sync queue', + 'queue_lag' => '(float) Time delay of the oldest item in the sync queue', + 'queue_next_sync' => '(float) Time in seconds before trying to sync again', + 'full_queue_size' => '(int) Number of items in the full sync queue', + 'full_queue_lag' => '(float) Time delay of the oldest item in the full sync queue', + 'full_queue_next_sync' => '(float) Time in seconds before trying to sync the full sync queue again', + 'cron_size' => '(int) Size of the current cron array', + 'next_cron' => '(int) The number of seconds till the next item in cron.', + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/status' +) ); + + +// GET /sites/%s/data-checksums +new Jetpack_JSON_API_Sync_Check_Endpoint( array( + 'description' => 'Check that cacheable data on the site is in sync with wordpress.com', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/data-checksums', + 'stat' => 'data-checksums', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'posts' => '(string) Posts checksum', + 'comments' => '(string) Comments checksum', + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/data-checksums' +) ); + +// GET /sites/%s/data-histogram +new Jetpack_JSON_API_Sync_Histogram_Endpoint( array( + 'description' => 'Get a histogram of checksums for certain synced data', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/data-histogram', + 'stat' => 'data-histogram', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'query_parameters' => array( + 'object_type' => '(string=posts) The type of object to checksum - posts, comments or options', + 'buckets' => '(int=10) The number of buckets for the checksums', + 'start_id' => '(int=0) Starting ID for the range', + 'end_id' => '(int=null) Ending ID for the range', + 'columns' => '(string) Columns to checksum', + 'strip_non_ascii' => '(bool=true) Strip non-ascii characters from all columns', + 'shared_salt' => '(string) Salt to reduce the collision and improve validation', + ), + 'response_format' => array( + 'histogram' => '(array) Associative array of histograms by ID range, e.g. "500-999" => "abcd1234"', + 'type' => '(string) Type of checksum algorithm', + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/data-histogram' +) ); + +$sync_settings_response = array( + 'dequeue_max_bytes' => '(int|bool=false) Maximum bytes to read from queue in a single request', + 'sync_wait_time' => '(int|bool=false) Wait time between requests in seconds if sync threshold exceeded', + 'sync_wait_threshold' => '(int|bool=false) If a request to WPCOM exceeds this duration, wait sync_wait_time seconds before sending again', + 'upload_max_bytes' => '(int|bool=false) Maximum bytes to send in a single request', + 'upload_max_rows' => '(int|bool=false) Maximum rows to send in a single request', + 'max_queue_size' => '(int|bool=false) Maximum queue size that that the queue is allowed to expand to in DB rows to prevent the DB from filling up. Needs to also meet the max_queue_lag limit.', + 'max_queue_lag' => '(int|bool=false) Maximum queue lag in seconds used to prevent the DB from filling up. Needs to also meet the max_queue_size limit.', + 'queue_max_writes_sec' => '(int|bool=false) Maximum writes per second to allow to the queue during full sync.', + 'post_types_blacklist' => '(array|string|bool=false) List of post types to exclude from sync. Send "empty" to unset.', + 'post_meta_whitelist' => '(array|string|bool=false) List of post meta to be included in sync. Send "empty" to unset.', + 'comment_meta_whitelist' => '(array|string|bool=false) List of comment meta to be included in sync. Send "empty" to unset.', + 'disable' => '(int|bool=false) Set to 1 or true to disable sync entirely.', + 'render_filtered_content' => '(int|bool=true) Set to 1 or true to render filtered content.', + 'max_enqueue_full_sync' => '(int|bool=false) Maximum number of rows to enqueue during each full sync process', + 'max_queue_size_full_sync' => '(int|bool=false) Maximum queue size that full sync is allowed to use', + 'sync_via_cron' => '(int|bool=false) Set to 1 or true to avoid using cron for sync.', + 'cron_sync_time_limit' => '(int|bool=false) Limit cron jobs to number of seconds', + 'enqueue_wait_time' => '(int|bool=false) Wait time in seconds between attempting to continue a full sync, via requests', +); + +// GET /sites/%s/sync/settings +new Jetpack_JSON_API_Sync_Get_Settings_Endpoint( array( + 'description' => 'Update sync settings', + 'method' => 'GET', + 'group' => '__do_not_document', + 'path' => '/sites/%s/sync/settings', + 'stat' => 'write-sync-settings', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => $sync_settings_response, + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/settings' +) ); + +// POST /sites/%s/sync/settings +new Jetpack_JSON_API_Sync_Modify_Settings_Endpoint( array( + 'description' => 'Update sync settings', + 'method' => 'POST', + 'group' => '__do_not_document', + 'path' => '/sites/%s/sync/settings', + 'stat' => 'write-sync-settings', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => $sync_settings_response, + 'response_format' => $sync_settings_response, + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/settings' +) ); + +// GET /sites/%s/sync/object +new Jetpack_JSON_API_Sync_Object( array( + 'description' => 'Get an object by ID from one of the sync modules, in the format it would be synced in', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/sync/object', + 'stat' => 'sync-object', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'query_parameters' => array( + 'module_name' => '(string) The sync module ID, e.g. "posts"', + 'object_type' => '(string) An identified for the object type, e.g. "post"', + 'object_ids' => '(array) The IDs of the objects', + ), + 'response_format' => array( + 'objects' => '(string) The encoded objects', + 'codec' => '(string) The codec used to encode the objects, deflate-json-array or simple' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/object?module_name=posts&object_type=post&object_ids[]=1&object_ids[]=2&object_ids[]=3' +) ); + +// POST /sites/%s/sync/now +new Jetpack_JSON_API_Sync_Now_Endpoint( array( + 'description' => 'Force immediate sync of top items on a queue', + 'method' => 'POST', + 'path' => '/sites/%s/sync/now', + 'stat' => 'sync-now', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'queue' => '(string) sync or full_sync', + ), + 'response_format' => array( + 'response' => '(array) The response from the server' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/now?queue=full_sync' +) ); + + +// POST /sites/%s/sync/unlock +new Jetpack_JSON_API_Sync_Unlock_Endpoint( array( + 'description' => 'Unlock the queue in case it gets locked by a process.', + 'method' => 'POST', + 'path' => '/sites/%s/sync/unlock', + 'group' => '__do_not_document', + 'stat' => 'sync-unlock', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'queue' => '(string) sync or full_sync', + ), + 'response_format' => array( + 'success' => '(bool) Unlocking the queue successful?' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/unlock' +) ); + +// POST /sites/%s/sync/checkout +new Jetpack_JSON_API_Sync_Checkout_Endpoint( array( + 'description' => 'Locks the queue and returns items and the buffer ID.', + 'method' => 'POST', + 'path' => '/sites/%s/sync/checkout', + 'group' => '__do_not_document', + 'stat' => 'sync-checkout', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'queue' => '(string) sync or full_sync', + 'number_of_items' => '(int=10) Maximum number of items from the queue to be returned', + 'encode' => '(bool=true) Use the default encode method', + 'force' => '(bool=false) Force unlock the queue', + ), + 'response_format' => array( + 'buffer_id' => '(string) Buffer ID that we are using', + 'items' => '(array) Items from the queue that are ready to be processed by the sync server', + 'skipped_items' => '(array) Skipped item ids', + 'codec' => '(string) The name of the codec used to encode the data', + 'sent_timestamp' => '(int) Current timestamp of the server', + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/checkout' +) ); + +// POST /sites/%s/sync/close +new Jetpack_JSON_API_Sync_Close_Endpoint( array( + 'description' => 'Closes the buffer and delete the processed items from the queue.', + 'method' => 'POST', + 'path' => '/sites/%s/sync/close', + 'group' => '__do_not_document', + 'stat' => 'sync-close', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'item_ids' => '(array) Item IDs to delete from the queue.', + 'queue' => '(string) sync or full_sync', + 'buffer_id' => '(string) buffer ID that was opened during the checkout step.', + ), + 'response_format' => array( + 'success' => '(bool) Closed the buffer successfully?' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/sync/close' +) ); + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-log-endpoint.php' ); + +new Jetpack_JSON_API_Jetpack_Log_Endpoint( array( + 'description' => 'Get the Jetpack log', + 'method' => 'GET', + 'path' => '/sites/%s/jetpack-log', + 'stat' => 'log', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'event' => '(string) The event to filter by, by default all entries are returned', + 'num' => '(int) The number of entries to get, by default all entries are returned' + ), + 'response_format' => array( + 'log' => '(array) An array of jetpack log entries' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/jetpack-log' +) ); + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-maybe-auto-update-endpoint.php' ); + +new Jetpack_JSON_API_Maybe_Auto_Update_Endpoint( array( + 'description' => 'Maybe Auto Update Core, Plugins, Themes and Languages', + 'method' => 'POST', + 'path' => '/sites/%s/maybe-auto-update', + 'stat' => 'maybe-auto-update', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'log' => '(array) Results of running the update job' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/maybe-auto-update' + +) ); + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-translations-endpoint.php' ); +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-translations-modify-endpoint.php' ); + +new Jetpack_JSON_API_Translations_Endpoint( array( + 'description' => 'Gets info about a Jetpack blog\'s core installation', + 'method' => 'GET', + 'path' => '/sites/%s/translations', + 'stat' => 'translations', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'translations' => '(array) A list of translations that are available', + 'autoupdate' => '(bool) Whether or not we automatically update translations', + '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/translations' +) ); + +new Jetpack_JSON_API_Translations_Modify_Endpoint( array( + 'description' => 'Toggle automatic core updates for a Jetpack blog', + 'method' => 'POST', + 'path' => '/sites/%s/translations', + 'stat' => 'translations', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'autoupdate' => '(bool) Whether or not we automatically update translations', + ), + 'response_format' => array( + 'translations' => '(array) A list of translations that are available', + 'autoupdate' => '(bool) Whether or not we automatically update translations', + ), + '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/translations' +) ); + +new Jetpack_JSON_API_Translations_Modify_Endpoint( array( + 'description' => 'Update All Translations installation on a Jetpack blog', + 'method' => 'POST', + 'path' => '/sites/%s/translations/update', + 'stat' => 'translations:update', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'log' => '(array:safehtml) An array of log strings.', + 'success' => '(bool) Was the operation successful' + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/translations/update' +) ); + +// Options +require_once( $json_jetpack_endpoints_dir . 'class.wpcom-json-api-get-option-endpoint.php' ); + +new WPCOM_JSON_API_Get_Option_Endpoint( array ( + 'method' => 'GET', + 'description' => 'Fetches an option.', + 'group' => '__do_not_document', + 'stat' => 'option', + 'path' => '/sites/%s/option', + 'path_labels' => array( + '$site' => '(int|string) Site ID or domain', + ), + 'query_parameters' => array( + 'option_name' => '(string) The name of the option to fetch.', + 'site_option' => '(bool=false) True if the option is a site option.', + ), + 'response_format' => array( + 'option_value' => '(string|object) The value of the option.', + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/option?option_name=blogname', + 'example_request_data' => array( + 'headers' => array( 'authorization' => 'Bearer YOUR_API_TOKEN' ), + ), +) ); + +require_once( $json_jetpack_endpoints_dir . 'class.wpcom-json-api-update-option-endpoint.php' ); + +new WPCOM_JSON_API_Update_Option_Endpoint( array ( + 'method' => 'POST', + 'description' => 'Updates an option.', + 'group' => '__do_not_document', + 'stat' => 'option:update', + 'path' => '/sites/%s/option', + 'path_labels' => array( + '$site' => '(int|string) Site ID or domain', + ), + 'query_parameters' => array( + 'option_name' => '(string) The name of the option to fetch.', + 'site_option' => '(bool=false) True if the option is a site option.', + 'is_array' => '(bool=false) True if the value should be converted to an array before saving.', + ), + 'request_format' => array( + 'option_value' => '(string|object) The new value of the option.', + ), + 'response_format' => array( + 'option_value' => '(string|object) The value of the updated option.', + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/option', + 'example_request_data' => array( + 'headers' => array( 'authorization' => 'Bearer YOUR_API_TOKEN' ), + 'body' => array( + 'option_value' => 'My new blog name' + ), + ), +) ); + + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-cron-endpoint.php' ); + +// GET /sites/%s/cron +new Jetpack_JSON_API_Cron_Endpoint( array( + 'description' => 'Fetches the cron array', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/cron', + 'stat' => 'cron-get', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'response_format' => array( + 'cron_array' => '(array) The cron array', + 'current_timestamp' => '(int) Current server timestamp' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/cron', + 'example_request_data' => array( + 'headers' => array( 'authorization' => 'Bearer YOUR_API_TOKEN' ), + ), +) ); + +// POST /sites/%s/cron +new Jetpack_JSON_API_Cron_Post_Endpoint( array( + 'description' => 'Process items in the cron', + 'group' => '__do_not_document', + 'method' => 'POST', + 'path' => '/sites/%s/cron', + 'stat' => 'cron-run', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'hooks' => '(array) List of hooks to run if they have been scheduled (optional)', + ), + 'response_format' => array( + 'success' => '(array) Of processed hooks with their arguments' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/cron', + 'example_request_data' => array( + 'headers' => array( 'authorization' => 'Bearer YOUR_API_TOKEN' ), + 'body' => array( + 'hooks' => array( 'jetpack_sync_cron' ) + ), + ), +) ); + +// POST /sites/%s/cron/schedule +new Jetpack_JSON_API_Cron_Schedule_Endpoint( array( + 'description' => 'Schedule one or a recurring hook to fire at a particular time', + 'group' => '__do_not_document', + 'method' => 'POST', + 'path' => '/sites/%s/cron/schedule', + 'stat' => 'cron-schedule', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'hook' => '(string) Hook name that should run when the event is scheduled', + 'timestamp' => '(int) Timestamp when the event should take place, has to be in the future', + 'arguments' => '(string) JSON Object of arguments that the hook will use (optional)', + 'recurrence' => '(string) How often the event should take place. If empty only one event will be scheduled. Possible values 1min, hourly, twicedaily, daily (optional) ' + ), + 'response_format' => array( + 'success' => '(bool) Was the event scheduled?' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/cron/schedule', + 'example_request_data' => array( + 'headers' => array( 'authorization' => 'Bearer YOUR_API_TOKEN' ), + 'body' => array( + 'hook' => 'jetpack_sync_cron', + 'arguments' => '[]', + 'recurrence'=> '1min', + 'timestamp' => 1476385523 + ), + ), +) ); + +// POST /sites/%s/cron/unschedule +new Jetpack_JSON_API_Cron_Unschedule_Endpoint( array( + 'description' => 'Unschedule one or all events with a particular hook and arguments', + 'group' => '__do_not_document', + 'method' => 'POST', + 'path' => '/sites/%s/cron/unschedule', + 'stat' => 'cron-unschedule', + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain' + ), + 'request_format' => array( + 'hook' => '(string) Name of the hook that should be unscheduled', + 'timestamp' => '(int) Timestamp of the hook that you want to unschedule. This will unschedule only 1 event. (optional)', + 'arguments' => '(string) JSON Object of arguments that the hook has been scheduled with (optional)', + ), + 'response_format' => array( + 'success' => '(bool) Was the event unscheduled?' + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/example.wordpress.org/cron/unschedule', + 'example_request_data' => array( + 'headers' => array( 'authorization' => 'Bearer YOUR_API_TOKEN' ), + 'body' => array( + 'hook' => 'jetpack_sync_cron', + 'arguments' => '[]', + 'timestamp' => 1476385523 + ), + ), +) ); + +// BACKUPS + +// GET /sites/%s/database-object/backup +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-get-database-object-backup-endpoint.php' ); +new Jetpack_JSON_API_Get_Database_Object_Backup_Endpoint( array( + 'description' => 'Fetch a backup of a database object, along with all of its metadata', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/database-object/backup', + 'stat' => 'database-objects:1:backup', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + ), + 'query_parameters' => array( + 'object_type' => '(string) Type of object to fetch from the database', + 'object_id' => '(int) ID of the database object to fetch', + ), + 'response_format' => array( + 'object' => '(array) Database object row', + 'meta' => '(array) Associative array of key/value metadata associated with the row', + 'children' => '(array) Where appropriate, child records associated with the object. eg: Woocommerce tax rate locations', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/database-object/backup' +) ); + +// GET /sites/%s/comments/%d/backup +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-get-comment-backup-endpoint.php' ); +new Jetpack_JSON_API_Get_Comment_Backup_Endpoint( array( + 'description' => 'Fetch a backup of a comment, along with all of its metadata', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/comments/%d/backup', + 'stat' => 'comments:1:backup', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + '$post' => '(int) The comment ID', + ), + 'response_format' => array( + 'comment' => '(array) Comment table row', + 'meta' => '(array) Associative array of key/value commentmeta data', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/comments/1/backup' +) ); + +// GET /sites/%s/options/backup +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-get-option-backup-endpoint.php' ); +new Jetpack_JSON_API_Get_Option_Backup_Endpoint( array( + 'description' => 'Fetch a backup of an option', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/options/backup', + 'stat' => 'options:backup', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + ), + 'query_parameters' => array( + 'name' => '(string|array) One or more option names to include in the backup', + ), + 'response_format' => array( + 'options' => '(array) Associative array of option_name => option_value entries', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ) + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/options/backup' +) ); + +// GET /sites/%s/posts/%d/backup +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-get-post-backup-endpoint.php' ); +new Jetpack_JSON_API_Get_Post_Backup_Endpoint( array( + 'description' => 'Fetch a backup of a post, along with all of its metadata', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/posts/%d/backup', + 'stat' => 'posts:1:backup', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + '$post' => '(int) The post ID', + ), + 'response_format' => array( + 'post' => '(array) Post table row', + 'meta' => '(array) Associative array of key/value postmeta data', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/posts/1/backup' +) ); + +// GET /sites/%s/terms/%d/backup +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-get-term-backup-endpoint.php' ); +new Jetpack_JSON_API_Get_Term_Backup_Endpoint( array( + 'description' => 'Fetch a backup of a term, along with all of its metadata', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/terms/%d/backup', + 'stat' => 'terms:1:backup', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + '$term' => '(int) The term ID', + ), + 'response_format' => array( + 'term' => '(array) Term table row', + 'meta' => '(array) Metadata associated with the term', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/terms/1/backup' +) ); + +// GET /sites/%s/users/%d/backup +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-get-user-backup-endpoint.php' ); +new Jetpack_JSON_API_Get_User_Backup_Endpoint( array( + 'description' => 'Fetch a backup of a user, along with all of its metadata', + 'group' => '__do_not_document', + 'method' => 'GET', + 'path' => '/sites/%s/users/%d/backup', + 'stat' => 'users:1:backup', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + '$user' => '(int) The user ID', + ), + 'response_format' => array( + 'user' => '(array) User table row', + 'meta' => '(array) Associative array of key/value usermeta data', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + ), + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/users/1/backup' +) ); + +// USERS + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-user-connect-endpoint.php' ); + +// POST /sites/%s/users/%d/connect +new Jetpack_JSON_API_User_Connect_Endpoint( array( + 'description' => 'Creates or returns a new user given profile data', + 'group' => '__do_not_document', + 'method' => 'POST', + 'path' => '/sites/%s/users/%d/connect', + 'stat' => 'users:connect', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + '$user_id' => '(int) The site user ID to connect', + ), + 'request_format' => array( + 'user_token' => '(string) The user token', + ), + 'response_format' => array( + 'success' => '(bool) Was the user connected', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN', + ), + 'body' => array( + 'user_token' => 'XDH55jndskjf3klh3', + ) + ), + 'example_response' => '{ + "success" => true + }', + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/users/6/connect' +) ); + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-user-create-endpoint.php' ); + +// POST /sites/%s/users/create +new Jetpack_JSON_API_User_Create_Endpoint( array( + 'description' => 'Creates or returns a new user given profile data', + 'group' => '__do_not_document', + 'method' => 'POST', + 'path' => '/sites/%s/users/create', + 'stat' => 'users:create', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + ), + 'query_parameters' => array( + 'invite_accepted' => '(bool=false) If the user is being created in the invite context', + ), + 'request_format' => WPCOM_JSON_API_Site_User_Endpoint::$user_format, + 'response_format' => WPCOM_JSON_API_Site_User_Endpoint::$user_format, + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN' + ), + 'body' => array( + 'roles' => array( + array( + 'administrator', + ) + ), + 'first_name' => 'John', + 'last_name' => 'Doe', + 'email' => 'john.doe@example.wordpress.org', + ) + ), + 'example_response' => '{ + "ID": 18342963, + "login": "binarysmash" + "email": false, + "name": "binarysmash", + "URL": "http:\/\/binarysmash.wordpress.com", + "avatar_URL": "http:\/\/0.gravatar.com\/avatar\/a178ebb1731d432338e6bb0158720fcc?s=96&d=identicon&r=G", + "profile_URL": "http:\/\/en.gravatar.com\/binarysmash", + "roles": [ "administrator" ] + }', + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/users/create' + +) ); + +require_once( $json_jetpack_endpoints_dir . 'class.jetpack-json-api-jps-woocommerce-connect-endpoint.php' ); + +// POST /sites/%s/jps/woo-connect +new Jetpack_JSON_API_JPS_WooCommerce_Connect_Endpoint( array( + 'description' => 'Attempts to connect the WooCommerce plugin for this site to WooCommerce.com.', + 'group' => '__do_not_document', + 'method' => 'POST', + 'path' => '/sites/%s/jps/woo-connect', + 'stat' => 'jps:woo-connect', + 'allow_jetpack_site_auth' => true, + 'path_labels' => array( + '$site' => '(int|string) The site ID, The site domain', + ), + 'request_format' => array( + 'access_token' => '(string) The access token for WooCommerce to connect to WooCommerce.com', + 'access_token_secret' => '(string) The access token secret for WooCommerce to connect to WooCommerce.com', + 'user_id' => '(int) The user\'s ID after registering for a host plan', + 'site_id' => '(int) The site\'s ID after registering for a host plan', + ), + 'response_format' => array( + 'success' => '(bool) Setting access token and access token secret successful?', + ), + 'example_request_data' => array( + 'headers' => array( + 'authorization' => 'Bearer YOUR_API_TOKEN', + ), + 'body' => array( + 'access_token' => '123456789', + 'access_token_secret' => 'abcdefghiklmnop', + 'user_id' => 1, + 'site_id' => 2, + ), + ), + 'example_response' => '{ "success": true }', + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/jps/woo-connect' +) ); |