Added login request

This commit is contained in:
Nedim Uka
2018-06-20 18:03:43 +02:00
parent 4e52521fae
commit 593b445a21
4716 changed files with 1218265 additions and 57 deletions

View File

@@ -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 );
}
}
}

View File

@@ -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,
);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,247 @@
<?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 );
return array( 'success' => is_null( $next ) ? true : false );
}
}
// 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 );
}
}

View File

@@ -0,0 +1,115 @@
<?php
include JETPACK__PLUGIN_DIR . '/modules/module-info.php';
/**
* Base class for Jetpack Endpoints, has the validate_call helper function.
*/
abstract class Jetpack_JSON_API_Endpoint extends WPCOM_JSON_API_Endpoint {
protected $needed_capabilities;
protected $expected_actions = array();
protected $action;
public function callback( $path = '', $blog_id = 0, $object = null ) {
if ( is_wp_error( $error = $this->validate_call( $blog_id, $this->needed_capabilities ) ) ) {
return $error;
}
if ( is_wp_error( $error = $this->validate_input( $object ) ) ) {
return $error;
}
if ( ! empty( $this->action ) ) {
if( is_wp_error( $error = call_user_func( array( $this, $this->action ) ) ) ) {
return $error;
}
}
return $this->result();
}
abstract protected function result();
protected function validate_input( $object ) {
$args = $this->input();
if( isset( $args['action'] ) && $args['action'] == 'update' ) {
$this->action = 'update';
}
if ( preg_match( "/\/update\/?$/", $this->path ) ) {
$this->action = 'update';
} elseif( preg_match( "/\/install\/?$/", $this->path ) ) {
$this->action = 'install';
} elseif( ! empty( $args['action'] ) ) {
if( ! in_array( $args['action'], $this->expected_actions ) ) {
return new WP_Error( 'invalid_action', __( 'You must specify a valid action', 'jetpack' ) );
}
$this->action = $args['action'];
}
return true;
}
/**
* Switches to the blog and checks current user capabilities.
* @return bool|WP_Error a WP_Error object or true if things are good.
*/
protected function validate_call( $_blog_id, $capability, $check_manage_active = true ) {
$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $_blog_id ) );
if ( is_wp_error( $blog_id ) ) {
return $blog_id;
}
if ( is_wp_error( $error = $this->check_capability( $capability ) ) ) {
return $error;
}
if ( $check_manage_active && 'GET' !== $this->method && ! Jetpack::is_module_active( 'manage' ) ) {
return new WP_Error( 'unauthorized_full_access', __( 'Full management mode is off for this site.', 'jetpack' ), 403 );
}
return true;
}
/**
* @param $capability
*
* @return bool|WP_Error
*/
protected function check_capability( $capability ) {
if ( is_array( $capability ) ) {
// the idea is that the we can pass in an array of capabilitie that the user needs to have before we allowing them to do something
$capabilities = ( isset( $capability['capabilities'] ) ? $capability['capabilities'] : $capability );
// We can pass in the number of conditions we must pass by default it is all.
$must_pass = ( isset( $capability['must_pass'] ) && is_int( $capability['must_pass'] ) ? $capability['must_pass'] : count( $capabilities ) );
$failed = array(); // store the failed capabilities
$passed = 0; //
foreach ( $capabilities as $cap ) {
if ( current_user_can( $cap ) ) {
$passed ++;
} else {
$failed[] = $cap;
}
}
// Check that must have conditions is less then
if ( $passed < $must_pass ) {
return new WP_Error( 'unauthorized', sprintf( __( 'This user is not authorized to %s on this blog.', 'jetpack' ), implode( ', ', $failed ), 403 ) );
}
} else {
if ( !current_user_can( $capability ) ) {
return new WP_Error( 'unauthorized', sprintf( __( 'This user is not authorized to %s on this blog.', 'jetpack' ), $capability ), 403 );
}
}
return true;
}
}

View File

@@ -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(),
);
}
}

View File

@@ -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 ) );
}
}

View File

@@ -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 ),
);
}
}

View File

@@ -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 ),
);
}
}

View File

@@ -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 ),
);
}
}

View File

@@ -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 )
);
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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';
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,315 @@
<?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' ) );
$plugin['action_links'] = $this->get_plugin_action_links( $plugin_file );
$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 );
$plugin['action_links'] = $this->get_plugin_action_links( $plugin_file );
$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 );
}
}

View File

@@ -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';
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,427 @@
<?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 ) {
global $wp_version;
if ( version_compare( $wp_version, '4.9-beta2' ) >= 0 ) {
if ( $plugin ) {
return current_user_can( $capability, $plugin );
}
return current_user_can( $capability );
}
// Assume that the user has the activate plugins capability.
return current_user_can( 'activate_plugins' );
}
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
*
* @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 );
}
}

View File

@@ -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 );
}
}
}

View File

@@ -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' );
}
}

View File

@@ -0,0 +1,319 @@
<?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();
return $store->checksum_histogram( $args['object_type'], $args['buckets'], $args['start_id'], $args['end_id'], $columns, $args['strip_non_ascii'] );
}
}
// 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
);
}
}

View File

@@ -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() );
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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';
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
*
* @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;
}
}

View File

@@ -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' );
}
}

View File

@@ -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,
);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 );
}
}