Add upstream

This commit is contained in:
root
2019-10-24 00:12:05 +02:00
parent 85d41e4216
commit ac980f592c
3504 changed files with 1049983 additions and 29971 deletions

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit73780ab222d7fdeca4785a6d0fe43054::getLoader();

View File

@@ -0,0 +1,139 @@
<?php
/**
* This file `autoload_packages.php`was generated by automattic/jetpack-autoloader.
*
* From your plugin include this file with:
* require_once . plugin_dir_path( __FILE__ ) . '/vendor/autoload_packages.php';
*
* @package Automattic\Jetpack\Autoloader
*/
// phpcs:disable PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_namespaceFound
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_ns_cFound
namespace Automattic\Jetpack\Autoloader;
if ( ! function_exists( __NAMESPACE__ . '\enqueue_package_class' ) ) {
global $jetpack_packages_classes;
if ( ! is_array( $jetpack_packages_classes ) ) {
$jetpack_packages_classes = array();
}
/**
* Adds the version of a package to the $jetpack_packages global array so that
* the autoloader is able to find it.
*
* @param string $class_name Name of the class that you want to autoload.
* @param string $version Version of the class.
* @param string $path Absolute path to the class so that we can load it.
*/
function enqueue_package_class( $class_name, $version, $path ) {
global $jetpack_packages_classes;
if ( ! isset( $jetpack_packages_classes[ $class_name ] ) ) {
$jetpack_packages_classes[ $class_name ] = array(
'version' => $version,
'path' => $path,
);
}
// If we have a @dev version set always use that one!
if ( 'dev-' === substr( $jetpack_packages_classes[ $class_name ]['version'], 0, 4 ) ) {
return;
}
// Always favour the @dev version. Since that version is the same as bleeding edge.
// We need to make sure that we don't do this in production!
if ( 'dev-' === substr( $version, 0, 4 ) ) {
$jetpack_packages_classes[ $class_name ] = array(
'version' => $version,
'path' => $path,
);
return;
}
// Set the latest version!
if ( version_compare( $jetpack_packages_classes[ $class_name ]['version'], $version, '<' ) ) {
$jetpack_packages_classes[ $class_name ] = array(
'version' => $version,
'path' => $path,
);
}
}
}
if ( ! function_exists( __NAMESPACE__ . '\autoloader' ) ) {
/**
* Used for autoloading jetpack packages.
*
* @param string $class_name Class Name to load.
*/
function autoloader( $class_name ) {
global $jetpack_packages_classes;
if ( isset( $jetpack_packages_classes[ $class_name ] ) ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
// TODO ideally we shouldn't skip any of these, see: https://github.com/Automattic/jetpack/pull/12646.
$ignore = in_array(
$class_name,
array(
'Automattic\Jetpack\JITM',
'Automattic\Jetpack\Connection\Manager',
'Automattic\Jetpack\Connection\Manager_Interface',
'Automattic\Jetpack\Connection\XMLRPC_Connector',
'Jetpack_Options',
'Jetpack_Signature',
'Automattic\Jetpack\Sync\Main',
'Automattic\Jetpack\Constants',
'Automattic\Jetpack\Tracking',
'Automattic\Jetpack\Plugin\Tracking',
),
true
);
if ( ! $ignore && function_exists( 'did_action' ) && ! did_action( 'plugins_loaded' ) ) {
_doing_it_wrong(
esc_html( $class_name ),
sprintf(
/* translators: %s Name of a PHP Class */
esc_html__( 'Not all plugins have loaded yet but we requested the class %s', 'jetpack' ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$class_name
),
esc_html( $jetpack_packages_classes[ $class_name ]['version'] )
);
}
}
if ( file_exists( $jetpack_packages_classes[ $class_name ]['path'] ) ) {
require_once $jetpack_packages_classes[ $class_name ]['path'];
return true;
}
}
return false;
}
// Add the jetpack autoloader.
spl_autoload_register( __NAMESPACE__ . '\autoloader' );
}
/**
* Prepare all the classes for autoloading.
*/
function enqueue_packages_d582360579fbf905fb430b67505bb288() {
$class_map = require_once dirname( __FILE__ ) . '/composer/autoload_classmap_package.php';
foreach ( $class_map as $class_name => $class_info ) {
enqueue_package_class( $class_name, $class_info['version'], $class_info['path'] );
}
$includeFiles = require __DIR__ . '/composer/autoload_files.php';
foreach ( $includeFiles as $fileIdentifier => $file ) {
if ( empty( $GLOBALS['__composer_autoload_files'][ $fileIdentifier ] ) ) {
require $file;
$GLOBALS['__composer_autoload_files'][ $fileIdentifier ] = true;
}
}
}
enqueue_packages_d582360579fbf905fb430b67505bb288();

View File

@@ -0,0 +1,29 @@
<?php
namespace Automattic\Jetpack;
use Automattic\Jetpack\Constants as Jetpack_Constants;
class Assets {
// static-only class
private function __construct() {}
/**
* Given a minified path, and a non-minified path, will return
* a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
*
* Both `$min_base` and `$non_min_base` are expected to be relative to the
* root Jetpack directory.
*
* @since 5.6.0
*
* @param string $min_path
* @param string $non_min_path
* @return string The URL to the file
*/
public static function get_file_url_for_environment( $min_path, $non_min_path ) {
$path = ( Jetpack_Constants::is_defined( 'SCRIPT_DEBUG' ) && Jetpack_Constants::get_constant( 'SCRIPT_DEBUG' ) )
? $non_min_path
: $min_path;
return plugins_url( $path, Jetpack_Constants::get_constant( 'JETPACK__PLUGIN_FILE' ) );
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Legacy global scope functions.
*
* @package global-functions
*/
if ( ! defined( 'ABSPATH' ) ) {
return;
}
// Add here, after the condition above, any code that should only run when WordPress is running.
// Autoload will load everything even when PHPCS is running and we don't want to run these
// in such case because they will fatal, for example, due to 'add_action' being undefined.
/**
* Load necessary functions.
*/
function jetpack_compat_require_defined_functions() {
jetpack_require_lib( 'tracks/client' );
}
add_action( 'plugins_loaded', 'jetpack_compat_require_defined_functions' );

View File

@@ -0,0 +1,90 @@
<?php
/**
* Jetpack Client
*
* Deprecated methods for Jetpack to act as client with wpcom, provided for back-compatibility.
*
* @category Connection
* @package Client
*/
use Automattic\Jetpack\Connection\Client;
/**
* Class Jetpack_Client
*
* @deprecated Use Automattic\Jetpack\Connection\Client
*/
class Jetpack_Client {
/**
* Jetpack API version.
*
* @deprecated use Automattic\Jetpack\Connection\Client::WPCOM_JSON_API_VERSION
*/
const WPCOM_JSON_API_VERSION = '1.1';
/**
* Perform remote request.
*
* @deprecated use Automattic\Jetpack\Connection\Client::remote_request
*
* @param array $args Arguments.
* @param null $body Request body.
*
* @return array|WP_Error
*/
public static function remote_request( $args, $body = null ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Connection\Client' );
return Client::remote_request( $args, $body );
}
/**
* Request to wpcom using the blog id.
*
* @deprecated use Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog
*
* @param string $path Endpoint path.
* @param string $version Endpoint version.
* @param array $args Arguments.
* @param null $body Request body.
* @param string $base_api_path Endpoint base prefix.
*
* @return Array|WP_Error
*/
public static function wpcom_json_api_request_as_blog(
$path,
$version = self::WPCOM_JSON_API_VERSION,
$args = array(),
$body = null,
$base_api_path = 'rest'
) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Connection\Client' );
return Client::wpcom_json_api_request_as_blog( $path, $version, $args, $body, $base_api_path );
}
/**
* Wrapper for wp_remote_request(). Turns off SSL verification for certain SSL errors.
* This is lame, but many, many, many hosts have misconfigured SSL.
*
* @deprecated use Automattic\Jetpack\Connection\Client::_wp_remote_request
*
* When Jetpack is registered, the jetpack_fallback_no_verify_ssl_certs option is set to the current time if:
* 1. a certificate error is found AND
* 2. not verifying the certificate works around the problem.
*
* The option is checked on each request.
*
* @internal
* @see Jetpack::fix_url_for_bad_hosts()
*
* @param String $url the request URL.
* @param Array $args request arguments.
* @param Boolean $set_fallback whether to allow flagging this request to use a fallback certficate override.
* @return array|WP_Error WP HTTP response on success
*/
public static function _wp_remote_request( $url, $args, $set_fallback = false ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Connection\Client' );
return Client::_wp_remote_request( $url, $args, $set_fallback );
}
}

View File

@@ -0,0 +1,359 @@
<?php
/**
* A compatibility shim for the sync actions class.
*
* @package jetpack-compat
*/
use Automattic\Jetpack\Sync\Actions;
/**
* Class Jetpack_Sync_Actions
*
* @deprecated Use Automattic\Jetpack\Sync\Actions
*/
class Jetpack_Sync_Actions extends Automattic\Jetpack\Sync\Actions {
/**
* Initializes the class.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::init
*/
public static function init() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::init();
}
/**
* Adds a shutdown sender callback.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::add_sender_shutdown
*/
public static function add_sender_shutdown() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::add_sender_shutdown();
}
/**
* Returns false or true based on whether this class should initialize the sender
* in current circumstances.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::should_initialize_sender
*
* @return Boolean should the object initialize sender?
*/
public static function should_initialize_sender() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::should_initialize_sender();
}
/**
* Returns false or true based on whether sync is allowed.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::sync_allowed
*
* @return Boolean is sync allowed?
*/
public static function sync_allowed() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::sync_allowed();
}
/**
* Returns false or true based on whether sync via cron is allowed.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::sync_via_cron_allowed
*
* @return Boolean is sync via cron allowed?
*/
public static function sync_via_cron_allowed() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::sync_via_cron_allowed();
}
/**
* Filters a boolean value that determines whether blacklisted posts should be prevented
* from being publicized.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::prevent_publicize_blacklisted_posts
*
* @param Boolean $should_publicize initial setting value.
* @param WP_Post $post the post object.
* @return Boolean whether to prevent publicizing.
*/
public static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::prevent_publicize_blacklisted_posts( $should_publicize, $post );
}
/**
* Set the importing flag to true.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::set_is_importing_true
*/
public static function set_is_importing_true() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::set_is_importing_true();
}
/**
* Send the sync data.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::send_data
*
* @param Mixed $data the sync data.
* @param String $codec_name the codec slug.
* @param Integer $sent_timestamp the current server timestamp.
* @param Integer $queue_id the queue identifier.
* @param Integer $checkout_duration time spent retrieving items.
* @param Integer $preprocess_duration Time spent converting items into data.
* @return WP_Response the response object.
*/
public static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration );
}
/**
* Commence initial sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::do_initial_sync
*/
public static function do_initial_sync() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::do_initial_sync();
}
/**
* Commence full sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::do_full_sync
*
* @param Array $modules the modules list.
* @return Boolean whether the sync was initialized.
*/
public static function do_full_sync( $modules = null ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::do_full_sync( $modules );
}
/**
* Schedule cron sessions.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::jetpack_cron_schedule
*
* @param Array $schedules the schedules to add.
*/
public static function jetpack_cron_schedule( $schedules ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::jetpack_cron_schedule( $schedules );
}
/**
* Commence cron sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::do_cron_sync
*/
public static function do_cron_sync() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::do_cron_sync();
}
/**
* Commence cron full sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::do_cron_full_sync
*/
public static function do_cron_full_sync() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::do_cron_full_sync();
}
/**
* Commence cron sync of a specific type of object.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::do_cron_sync_by_type
*
* @param Array $type the type of object to sync.
*/
public static function do_cron_sync_by_type( $type ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::do_cron_sync_by_type();
}
/**
* Initalize the listener of the object.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::initialize_listener
*/
public static function initialize_listener() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::initialize_listener();
}
/**
* Initalize the sender of the object.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::initialize_sender
*/
public static function initialize_sender() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::initialize_sender();
}
/**
* Initalize the woocommerce listeners.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::initialize_woocommerce
*/
public static function initialize_woocommerce() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::initialize_woocommerce();
}
/**
* Add the woocommerce sync module.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::add_woocommerce_sync_module
*
* @param Array $sync_modules an array of modules.
*/
public static function add_woocommerce_sync_module( $sync_modules ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::add_woocommerce_sync_module( $sync_modules );
}
/**
* Initalize the WP Super Cache listener.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::initialize_wp_super_cache
*/
public static function initialize_wp_super_cache() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::initialize_wp_super_cache();
}
/**
* Add the WP Super Cache sync module.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::add_wp_super_cache_sync_module
*
* @param Array $sync_modules the list to be amended.
*/
public static function add_wp_super_cache_sync_module( $sync_modules ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::add_wp_super_cache_sync_module( $sync_modules );
}
/**
* Sanitizes the filtered sync cron schedule.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::sanitize_filtered_sync_cron_schedule
*
* @param String $schedule the cron schedule to sanitize.
* @return String sanitized cron schedule.
*/
public static function sanitize_filtered_sync_cron_schedule( $schedule ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::sanitize_filtered_sync_cron_schedule( $schedule );
}
/**
* Returns the time offset for a the start schedule.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::get_start_time_offset
*
* @param String $schedule the schedule string.
* @param String $hook hook slug.
* @return Integer start time offset.
*/
public static function get_start_time_offset( $schedule = '', $hook = '' ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::get_start_time_offset( $schedule, $hook );
}
/**
* If needed, schedule a cron sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::maybe_schedule_sync_cron
*
* @param String $schedule the schedule string.
* @param String $hook hook slug.
*/
public static function maybe_schedule_sync_cron( $schedule, $hook ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::maybe_schedule_sync_cron( $schedule, $hook );
}
/**
* Clears cron jobs scheduled for sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::clear_sync_cron_jobs
*/
public static function clear_sync_cron_jobs() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::clear_sync_cron_jobs();
}
/**
* Initialize cron jobs for sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::init_sync_cron_jobs
*/
public static function init_sync_cron_jobs() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::init_sync_cron_jobs();
}
/**
* Cleans up schedules on plugin upgrade.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::cleanup_on_upgrade
*
* @param String $new_version the new version.
* @param String $old_version the old version.
*/
public static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::cleanup_on_upgrade( $new_version, $old_version );
}
/**
* Clears cron jobs scheduled for sync.
*
* @deprecated \Automattic\Jetpack\Sync\Actions::get_sync_status
*
* @param Array $fields sync fields to get status of.
*/
public static function get_sync_status( $fields = null ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Actions' );
return Actions::get_sync_status( $fields );
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* A compatibility shim for the sync modules class.
*
* @package jetpack-compat
*/
use Automattic\Jetpack\Sync\Modules;
/**
* Class Jetpack_Sync_Modules
*
* @deprecated Use Automattic\Jetpack\Sync\Modules
*/
class Jetpack_Sync_Modules {
/**
* Returns the sync module object.
*
* @param String $module_name the module name.
* @return Automattic\Jetpack\Sync\Modules\Module the module object.
*/
public static function get_module( $module_name ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Modules' );
return Modules::get_module( $module_name );
}
}

View File

@@ -0,0 +1,102 @@
<?php
use Automattic\Jetpack\Sync\Settings;
/**
* Class Jetpack_Sync_Settings
*
* @deprecated Use Automattic\Jetpack\Sync\Settings
*/
class Jetpack_Sync_Settings {
static function get_settings() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::get_settings();
}
static function get_setting( $setting ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::get_setting( $setting );
}
static function update_settings( $new_settings ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::update_settings( $new_settings );
}
static function is_network_setting( $setting ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::is_network_setting( $setting );
}
static function get_blacklisted_post_types_sql() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::get_blacklisted_post_types_sql();
}
static function get_whitelisted_post_meta_sql() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::get_whitelisted_post_meta_sql();
}
static function get_whitelisted_comment_meta_sql() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::get_whitelisted_comment_meta_sql();
}
static function get_comments_filter_sql() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::get_comments_filter_sql();
}
static function reset_data() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::reset_data();
}
static function set_importing( $is_importing ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::set_importing( $is_importing );
}
static function is_importing() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::is_importing();
}
static function is_sync_enabled() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::is_sync_enabled();
}
static function set_doing_cron( $is_doing_cron ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::set_doing_cron( $is_doing_cron );
}
static function is_doing_cron() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::is_doing_cron();
}
static function is_syncing() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
return Settings::is_syncing();
}
static function set_is_syncing( $is_syncing ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::set_is_syncing( $is_syncing );
}
static function is_sending() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::is_sending();
}
static function set_is_sending( $is_sending ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Sync\Settings' );
Settings::set_is_sending( $is_sending );
}
}

View File

@@ -0,0 +1,21 @@
<?php
use Automattic\Jetpack\Tracking;
class JetpackTracking {
static function enqueue_tracks_scripts() {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Tracking' );
$tracking = new Tracking();
return $tracking->enqueue_tracks_scripts();
}
static function record_user_event( $event_type, $data = array(), $user = null ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Tracking' );
$tracking = new Tracking();
return $tracking->record_user_event( $event_type, $data, $user );
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* @deprecated 7.5.0 use Automattic\Jetpack\Tracking->tracks_get_identity instead
*/
function jetpack_tracks_get_identity( $user_id ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Tracking->tracks_get_identity' );
$tracking = new Automattic\Jetpack\Tracking( 'jetpack', Jetpack::connection() );
return $tracking->tracks_get_identity( $user_id );
}
/**
* @deprecated 7.5.0 use Automattic\Jetpack\Tracking->tracks_record_event instead
*/
function jetpack_tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
_deprecated_function( __METHOD__, 'jetpack-7.5', 'Automattic\Jetpack\Tracking->tracks_record_event' );
$tracking = new Automattic\Jetpack\Tracking( 'jetpack', Jetpack::connection() );
return $tracking->tracks_record_event( $user, $event_name, $properties, $event_timestamp_millis );
}

View File

@@ -0,0 +1,267 @@
<?php
use \Automattic\Jetpack\Connection\Manager as Connection_Manager;
class Jetpack_Signature {
public $token;
public $secret;
public $current_request_url;
function __construct( $access_token, $time_diff = 0 ) {
$secret = explode( '.', $access_token );
if ( 2 != count( $secret ) ) {
return;
}
$this->token = $secret[0];
$this->secret = $secret[1];
$this->time_diff = $time_diff;
}
function sign_current_request( $override = array() ) {
if ( isset( $override['scheme'] ) ) {
$scheme = $override['scheme'];
if ( ! in_array( $scheme, array( 'http', 'https' ) ) ) {
return new WP_Error( 'invalid_scheme', 'Invalid URL scheme' );
}
} else {
if ( is_ssl() ) {
$scheme = 'https';
} else {
$scheme = 'http';
}
}
$host_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $_SERVER['SERVER_PORT'];
$connection = new Connection_Manager();
/**
* Note: This port logic is tested in the Jetpack_Cxn_Tests->test__server_port_value() test.
* Please update the test if any changes are made in this logic.
*/
if ( is_ssl() ) {
// 443: Standard Port
// 80: Assume we're behind a proxy without X-Forwarded-Port. Hardcoding "80" here means most sites
// with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with
// the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a
// site at https://example.com:80/ (which would be a strange configuration).
// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
// if the site is behind a proxy running on port 443 without
// X-Forwarded-Port and the back end's port is *not* 80. It's better,
// though, to configure the proxy to send X-Forwarded-Port.
$https_port = defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ? JETPACK_SIGNATURE__HTTPS_PORT : 443;
$port = in_array( $host_port, array( 443, 80, $https_port ) ) ? '' : $host_port;
} else {
// 80: Standard Port
// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
// if the site is behind a proxy running on port 80 without
// X-Forwarded-Port. It's better, though, to configure the proxy to
// send X-Forwarded-Port.
$http_port = defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ? JETPACK_SIGNATURE__HTTP_PORT : 80;
$port = in_array( $host_port, array( 80, $http_port ) ) ? '' : $host_port;
}
$this->current_request_url = "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . stripslashes( $_SERVER['REQUEST_URI'] );
if ( array_key_exists( 'body', $override ) && ! empty( $override['body'] ) ) {
$body = $override['body'];
} elseif ( 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
$body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null;
// Convert the $_POST to the body, if the body was empty. This is how arrays are hashed
// and encoded on the Jetpack side.
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
if ( empty( $body ) && is_array( $_POST ) && count( $_POST ) > 0 ) {
$body = $_POST;
}
}
} elseif ( 'PUT' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
// This is a little strange-looking, but there doesn't seem to be another way to get the PUT body
$raw_put_data = file_get_contents( 'php://input' );
parse_str( $raw_put_data, $body );
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
$put_data = json_decode( $raw_put_data, true );
if ( is_array( $put_data ) && count( $put_data ) > 0 ) {
$body = $put_data;
}
}
} else {
$body = null;
}
if ( empty( $body ) ) {
$body = null;
}
$a = array();
foreach ( array( 'token', 'timestamp', 'nonce', 'body-hash' ) as $parameter ) {
if ( isset( $override[ $parameter ] ) ) {
$a[ $parameter ] = $override[ $parameter ];
} else {
$a[ $parameter ] = isset( $_GET[ $parameter ] ) ? stripslashes( $_GET[ $parameter ] ) : '';
}
}
$method = isset( $override['method'] ) ? $override['method'] : $_SERVER['REQUEST_METHOD'];
return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $method, $this->current_request_url, $body, true );
}
// body_hash v. body-hash is annoying. Refactor to accept an array?
function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '', $method = '', $url = '', $body = null, $verify_body_hash = true ) {
if ( ! $this->secret ) {
return new WP_Error( 'invalid_secret', 'Invalid secret' );
}
if ( ! $this->token ) {
return new WP_Error( 'invalid_token', 'Invalid token' );
}
list( $token ) = explode( '.', $token );
$signature_details = compact( 'token', 'timestamp', 'nonce', 'body_hash', 'method', 'url' );
if ( 0 !== strpos( $token, "$this->token:" ) ) {
return new WP_Error( 'token_mismatch', 'Incorrect token', compact( 'signature_details' ) );
}
// If we got an array at this point, let's encode it, so we can see what it looks like as a string.
if ( is_array( $body ) ) {
if ( count( $body ) > 0 ) {
$body = json_encode( $body );
} else {
$body = '';
}
}
$required_parameters = array( 'token', 'timestamp', 'nonce', 'method', 'url' );
if ( ! is_null( $body ) ) {
$required_parameters[] = 'body_hash';
if ( ! is_string( $body ) ) {
return new WP_Error( 'invalid_body', 'Body is malformed.', compact( 'signature_details' ) );
}
}
foreach ( $required_parameters as $required ) {
if ( ! is_scalar( $$required ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', str_replace( '_', '-', $required ) ), compact( 'signature_details' ) );
}
if ( ! strlen( $$required ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is missing.', str_replace( '_', '-', $required ) ), compact( 'signature_details' ) );
}
}
if ( empty( $body ) ) {
if ( $body_hash ) {
return new WP_Error( 'invalid_body_hash', 'Invalid body hash for empty body.', compact( 'signature_details' ) );
}
} else {
$connection = new Connection_Manager();
if ( $verify_body_hash && $connection->sha1_base64( $body ) !== $body_hash ) {
return new WP_Error( 'invalid_body_hash', 'The body hash does not match.', compact( 'signature_details' ) );
}
}
$parsed = parse_url( $url );
if ( ! isset( $parsed['host'] ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'url' ), compact( 'signature_details' ) );
}
if ( ! empty( $parsed['port'] ) ) {
$port = $parsed['port'];
} else {
if ( 'http' == $parsed['scheme'] ) {
$port = 80;
} elseif ( 'https' == $parsed['scheme'] ) {
$port = 443;
} else {
return new WP_Error( 'unknown_scheme_port', "The scheme's port is unknown", compact( 'signature_details' ) );
}
}
if ( ! ctype_digit( "$timestamp" ) || 10 < strlen( $timestamp ) ) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug.
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'timestamp' ), compact( 'signature_details' ) );
}
$local_time = $timestamp - $this->time_diff;
if ( $local_time < time() - 600 || $local_time > time() + 300 ) {
return new WP_Error( 'invalid_signature', 'The timestamp is too old.', compact( 'signature_details' ) );
}
if ( 12 < strlen( $nonce ) || preg_match( '/[^a-zA-Z0-9]/', $nonce ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'nonce' ), compact( 'signature_details' ) );
}
$normalized_request_pieces = array(
$token,
$timestamp,
$nonce,
$body_hash,
strtoupper( $method ),
strtolower( $parsed['host'] ),
$port,
$parsed['path'],
// Normalized Query String
);
$normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) );
$flat_normalized_request_pieces = array();
foreach ( $normalized_request_pieces as $piece ) {
if ( is_array( $piece ) ) {
foreach ( $piece as $subpiece ) {
$flat_normalized_request_pieces[] = $subpiece;
}
} else {
$flat_normalized_request_pieces[] = $piece;
}
}
$normalized_request_pieces = $flat_normalized_request_pieces;
$normalized_request_string = join( "\n", $normalized_request_pieces ) . "\n";
return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $this->secret, true ) );
}
function normalized_query_parameters( $query_string ) {
parse_str( $query_string, $array );
if ( get_magic_quotes_gpc() ) {
$array = stripslashes_deep( $array );
}
unset( $array['signature'] );
$names = array_keys( $array );
$values = array_values( $array );
$names = array_map( array( $this, 'encode_3986' ), $names );
$values = array_map( array( $this, 'encode_3986' ), $values );
$pairs = array_map( array( $this, 'join_with_equal_sign' ), $names, $values );
sort( $pairs );
return $pairs;
}
function encode_3986( $string_or_array ) {
if ( is_array( $string_or_array ) ) {
return array_map( array( $this, 'encode_3986' ), $string_or_array );
}
$string_or_array = rawurlencode( $string_or_array );
return str_replace( '%7E', '~', $string_or_array ); // prior to PHP 5.3, rawurlencode was RFC 1738
}
function join_with_equal_sign( $name, $value ) {
if ( is_array( $value ) ) {
$result = array();
foreach ( $value as $array_key => $array_value ) {
$result[] = $name . '[' . $array_key . ']' . '=' . $array_value;
}
return $result;
}
return "{$name}={$value}";
}
}

View File

@@ -0,0 +1,454 @@
<?php
/**
* The Connection Client class file.
*
* @package jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
/**
* The Client class that is used to connect to WordPress.com Jetpack API.
*/
class Client {
const WPCOM_JSON_API_VERSION = '1.1';
/**
* Makes an authorized remote request using Jetpack_Signature
*
* @param Array $args the arguments for the remote request.
* @param Array|String $body the request body.
* @return array|WP_Error WP HTTP response on success
*/
public static function remote_request( $args, $body = null ) {
$defaults = array(
'url' => '',
'user_id' => 0,
'blog_id' => 0,
'auth_location' => Constants::get_constant( 'JETPACK_CLIENT__AUTH_LOCATION' ),
'method' => 'POST',
'timeout' => 10,
'redirection' => 0,
'headers' => array(),
'stream' => false,
'filename' => null,
'sslverify' => true,
);
$args = wp_parse_args( $args, $defaults );
$args['blog_id'] = (int) $args['blog_id'];
if ( 'header' !== $args['auth_location'] ) {
$args['auth_location'] = 'query_string';
}
$token = \Jetpack_Data::get_access_token( $args['user_id'] );
if ( ! $token ) {
return new \WP_Error( 'missing_token' );
}
$method = strtoupper( $args['method'] );
$timeout = intval( $args['timeout'] );
$redirection = $args['redirection'];
$stream = $args['stream'];
$filename = $args['filename'];
$sslverify = $args['sslverify'];
$request = compact( 'method', 'body', 'timeout', 'redirection', 'stream', 'filename', 'sslverify' );
@list( $token_key, $secret ) = explode( '.', $token->secret ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( empty( $token ) || empty( $secret ) ) {
return new \WP_Error( 'malformed_token' );
}
$token_key = sprintf(
'%s:%d:%d',
$token_key,
Constants::get_constant( 'JETPACK__API_VERSION' ),
$token->external_user_id
);
$time_diff = (int) \Jetpack_Options::get_option( 'time_diff' );
$jetpack_signature = new \Jetpack_Signature( $token->secret, $time_diff );
$timestamp = time() + $time_diff;
if ( function_exists( 'wp_generate_password' ) ) {
$nonce = wp_generate_password( 10, false );
} else {
$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
}
// Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing.
if ( is_null( $body ) ) {
$body_hash = '';
} else {
// Allow arrays to be used in passing data.
$body_to_hash = $body;
if ( is_array( $body ) ) {
// We cast this to a new variable, because the array form of $body needs to be
// maintained so it can be passed into the request later on in the code.
if ( count( $body ) > 0 ) {
$body_to_hash = wp_json_encode( self::_stringify_data( $body ) );
} else {
$body_to_hash = '';
}
}
if ( ! is_string( $body_to_hash ) ) {
return new \WP_Error( 'invalid_body', 'Body is malformed.' );
}
$body_hash = base64_encode( sha1( $body_to_hash, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
$auth = array(
'token' => $token_key,
'timestamp' => $timestamp,
'nonce' => $nonce,
'body-hash' => $body_hash,
);
if ( false !== strpos( $args['url'], 'xmlrpc.php' ) ) {
$url_args = array(
'for' => 'jetpack',
'wpcom_blog_id' => \Jetpack_Options::get_option( 'id' ),
);
} else {
$url_args = array();
}
if ( 'header' !== $args['auth_location'] ) {
$url_args += $auth;
}
$url = add_query_arg( urlencode_deep( $url_args ), $args['url'] );
$url = \Jetpack::fix_url_for_bad_hosts( $url );
$signature = $jetpack_signature->sign_request( $token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false );
if ( ! $signature || is_wp_error( $signature ) ) {
return $signature;
}
// Send an Authorization header so various caches/proxies do the right thing.
$auth['signature'] = $signature;
$auth['version'] = Constants::get_constant( 'JETPACK__VERSION' );
$header_pieces = array();
foreach ( $auth as $key => $value ) {
$header_pieces[] = sprintf( '%s="%s"', $key, $value );
}
$request['headers'] = array_merge(
$args['headers'],
array(
'Authorization' => 'X_JETPACK ' . join( ' ', $header_pieces ),
)
);
if ( 'header' !== $args['auth_location'] ) {
$url = add_query_arg( 'signature', rawurlencode( $signature ), $url );
}
return self::_wp_remote_request( $url, $request );
}
/**
* Wrapper for wp_remote_request(). Turns off SSL verification for certain SSL errors.
* This is lame, but many, many, many hosts have misconfigured SSL.
*
* When Jetpack is registered, the jetpack_fallback_no_verify_ssl_certs option is set to the current time if:
* 1. a certificate error is found AND
* 2. not verifying the certificate works around the problem.
*
* The option is checked on each request.
*
* @internal
* @see Jetpack::fix_url_for_bad_hosts()
*
* @param String $url the request URL.
* @param Array $args request arguments.
* @param Boolean $set_fallback whether to allow flagging this request to use a fallback certficate override.
* @return array|WP_Error WP HTTP response on success
*/
public static function _wp_remote_request( $url, $args, $set_fallback = false ) {
/**
* SSL verification (`sslverify`) for the JetpackClient remote request
* defaults to off, use this filter to force it on.
*
* Return `true` to ENABLE SSL verification, return `false`
* to DISABLE SSL verification.
*
* @since 3.6.0
*
* @param bool Whether to force `sslverify` or not.
*/
if ( apply_filters( 'jetpack_client_verify_ssl_certs', false ) ) {
return wp_remote_request( $url, $args );
}
$fallback = \Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
if ( false === $fallback ) {
\Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', 0 );
}
if ( (int) $fallback ) {
// We're flagged to fallback.
$args['sslverify'] = false;
}
$response = wp_remote_request( $url, $args );
if (
! $set_fallback // We're not allowed to set the flag on this request, so whatever happens happens.
||
isset( $args['sslverify'] ) && ! $args['sslverify'] // No verification - no point in doing it again.
||
! is_wp_error( $response ) // Let it ride.
) {
self::set_time_diff( $response, $set_fallback );
return $response;
}
// At this point, we're not flagged to fallback and we are allowed to set the flag on this request.
$message = $response->get_error_message();
// Is it an SSL Certificate verification error?
if (
false === strpos( $message, '14090086' ) // OpenSSL SSL3 certificate error.
&&
false === strpos( $message, '1407E086' ) // OpenSSL SSL2 certificate error.
&&
false === strpos( $message, 'error setting certificate verify locations' ) // cURL CA bundle not found.
&&
false === strpos( $message, 'Peer certificate cannot be authenticated with' ) // cURL CURLE_SSL_CACERT: CA bundle found, but not helpful
// Different versions of curl have different error messages
// this string should catch them all.
&&
false === strpos( $message, 'Problem with the SSL CA cert' ) // cURL CURLE_SSL_CACERT_BADFILE: probably access rights.
) {
// No, it is not.
return $response;
}
// Redo the request without SSL certificate verification.
$args['sslverify'] = false;
$response = wp_remote_request( $url, $args );
if ( ! is_wp_error( $response ) ) {
// The request went through this time, flag for future fallbacks.
\Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', time() );
self::set_time_diff( $response, $set_fallback );
}
return $response;
}
/**
* Sets the time difference for correct signature computation.
*
* @param HTTP_Response $response the response object.
* @param Boolean $force_set whether to force setting the time difference.
*/
public static function set_time_diff( &$response, $force_set = false ) {
$code = wp_remote_retrieve_response_code( $response );
// Only trust the Date header on some responses.
if ( 200 != $code && 304 != $code && 400 != $code && 401 != $code ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
return;
}
$date = wp_remote_retrieve_header( $response, 'date' );
if ( ! $date ) {
return;
}
$time = (int) strtotime( $date );
if ( 0 >= $time ) {
return;
}
$time_diff = $time - time();
if ( $force_set ) { // During register.
\Jetpack_Options::update_option( 'time_diff', $time_diff );
} else { // Otherwise.
$old_diff = \Jetpack_Options::get_option( 'time_diff' );
if ( false === $old_diff || abs( $time_diff - (int) $old_diff ) > 10 ) {
\Jetpack_Options::update_option( 'time_diff', $time_diff );
}
}
}
/**
* Queries the WordPress.com REST API with a user token.
*
* @param string $path REST API path.
* @param string $version REST API version. Default is `2`.
* @param array $args Arguments to {@see WP_Http}. Default is `array()`.
* @param string $body Body passed to {@see WP_Http}. Default is `null`.
* @param string $base_api_path REST API root. Default is `wpcom`.
*
* @return array|WP_Error $response Response data, else {@see WP_Error} on failure.
*/
public static function wpcom_json_api_request_as_user(
$path,
$version = '2',
$args = array(),
$body = null,
$base_api_path = 'wpcom'
) {
$base_api_path = trim( $base_api_path, '/' );
$version = ltrim( $version, 'v' );
$path = ltrim( $path, '/' );
$args = array_intersect_key(
$args,
array(
'headers' => 'array',
'method' => 'string',
'timeout' => 'int',
'redirection' => 'int',
'stream' => 'boolean',
'filename' => 'string',
'sslverify' => 'boolean',
)
);
$args['user_id'] = get_current_user_id();
$args['method'] = isset( $args['method'] ) ? strtoupper( $args['method'] ) : 'GET';
$args['url'] = sprintf(
'%s://%s/%s/v%s/%s',
self::protocol(),
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_HOST' ),
$base_api_path,
$version,
$path
);
if ( isset( $body ) && ! isset( $args['headers'] ) && in_array( $args['method'], array( 'POST', 'PUT', 'PATCH' ), true ) ) {
$args['headers'] = array( 'Content-Type' => 'application/json' );
}
if ( isset( $body ) && ! is_string( $body ) ) {
$body = wp_json_encode( $body );
}
return self::remote_request( $args, $body );
}
/**
* Query the WordPress.com REST API using the blog token
*
* @param String $path The API endpoint relative path.
* @param String $version The API version.
* @param Array $args Request arguments.
* @param String $body Request body.
* @param String $base_api_path (optional) the API base path override, defaults to 'rest'.
* @return Array|WP_Error $response Data.
*/
public static function wpcom_json_api_request_as_blog(
$path,
$version = self::WPCOM_JSON_API_VERSION,
$args = array(),
$body = null,
$base_api_path = 'rest'
) {
$filtered_args = array_intersect_key(
$args,
array(
'headers' => 'array',
'method' => 'string',
'timeout' => 'int',
'redirection' => 'int',
'stream' => 'boolean',
'filename' => 'string',
'sslverify' => 'boolean',
)
);
// unprecedingslashit.
$_path = preg_replace( '/^\//', '', $path );
// Use GET by default whereas `remote_request` uses POST.
$request_method = ( isset( $filtered_args['method'] ) ) ? $filtered_args['method'] : 'GET';
$url = sprintf(
'%s://%s/%s/v%s/%s',
self::protocol(),
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_HOST' ),
$base_api_path,
$version,
$_path
);
$validated_args = array_merge(
$filtered_args,
array(
'url' => $url,
'blog_id' => (int) \Jetpack_Options::get_option( 'id' ),
'method' => $request_method,
)
);
return self::remote_request( $validated_args, $body );
}
/**
* Takes an array or similar structure and recursively turns all values into strings. This is used to
* make sure that body hashes are made ith the string version, which is what will be seen after a
* server pulls up the data in the $_POST array.
*
* @param Array|Mixed $data the data that needs to be stringified.
*
* @return array|string
*/
public static function _stringify_data( $data ) {
// Booleans are special, lets just makes them and explicit 1/0 instead of the 0 being an empty string.
if ( is_bool( $data ) ) {
return $data ? '1' : '0';
}
// Cast objects into arrays.
if ( is_object( $data ) ) {
$data = (array) $data;
}
// Non arrays at this point should be just converted to strings.
if ( ! is_array( $data ) ) {
return (string) $data;
}
foreach ( $data as $key => &$value ) {
$value = self::_stringify_data( $value );
}
return $data;
}
/**
* Gets protocol string.
*
* @return string `https` (if possible), else `http`.
*/
public static function protocol() {
/**
* Determines whether Jetpack can send outbound https requests to the WPCOM api.
*
* @since 3.6.0
*
* @param bool $proto Defaults to true.
*/
$https = apply_filters( 'jetpack_can_make_outbound_https', true );
return $https ? 'https' : 'http';
}
}

View File

@@ -0,0 +1,663 @@
<?php
/**
* The Jetpack Connection manager class file.
*
* @package jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
/**
* The Jetpack Connection Manager class that is used as a single gateway between WordPress.com
* and Jetpack.
*/
class Manager implements Manager_Interface {
const SECRETS_MISSING = 'secrets_missing';
const SECRETS_EXPIRED = 'secrets_expired';
const SECRETS_OPTION_NAME = 'jetpack_secrets';
const MAGIC_NORMAL_TOKEN_KEY = ';normal;';
const JETPACK_MASTER_USER = true;
/**
* The procedure that should be run to generate secrets.
*
* @var Callable
*/
protected $secret_callable;
/**
* Initializes all needed hooks and request handlers. Handles API calls, upload
* requests, authentication requests. Also XMLRPC options requests.
* Fallback XMLRPC is also a bridge, but probably can be a class that inherits
* this one. Among other things it should strip existing methods.
*
* @param Array $methods an array of API method names for the Connection to accept and
* pass on to existing callables. It's possible to specify whether
* each method should be available for unauthenticated calls or not.
* @see Jetpack::__construct
*/
public function initialize( $methods ) {
$methods;
}
/**
* Returns true if the current site is connected to WordPress.com.
*
* @return Boolean is the site connected?
*/
public function is_active() {
return (bool) $this->get_access_token( self::JETPACK_MASTER_USER );
}
/**
* Returns true if the user with the specified identifier is connected to
* WordPress.com.
*
* @param Integer|Boolean $user_id the user identifier.
* @return Boolean is the user connected?
*/
public function is_user_connected( $user_id = false ) {
$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
if ( ! $user_id ) {
return false;
}
return (bool) $this->get_access_token( $user_id );
}
/**
* Get the wpcom user data of the current|specified connected user.
*
* @param Integer $user_id the user identifier.
* @return Object the user object.
*/
public function get_connected_user_data( $user_id = null ) {
if ( ! $user_id ) {
$user_id = get_current_user_id();
}
$transient_key = "jetpack_connected_user_data_$user_id";
$cached_user_data = get_transient( $transient_key );
if ( $cached_user_data ) {
return $cached_user_data;
}
\Jetpack::load_xml_rpc_client();
$xml = new \Jetpack_IXR_Client(
array(
'user_id' => $user_id,
)
);
$xml->query( 'wpcom.getUser' );
if ( ! $xml->isError() ) {
$user_data = $xml->getResponse();
set_transient( $transient_key, $xml->getResponse(), DAY_IN_SECONDS );
return $user_data;
}
return false;
}
/**
* Is the user the connection owner.
*
* @param Integer $user_id the user identifier.
* @return Boolean is the user the connection owner?
*/
public function is_connection_owner( $user_id ) {
return $user_id;
}
/**
* Unlinks the current user from the linked WordPress.com user
*
* @param Integer $user_id the user identifier.
*/
public static function disconnect_user( $user_id ) {
return $user_id;
}
/**
* Initializes a transport server, whatever it may be, saves into the object property.
* Should be changed to be protected.
*/
public function initialize_server() {
}
/**
* Checks if the current request is properly authenticated, bails if not.
* Should be changed to be protected.
*/
public function require_authentication() {
}
/**
* Verifies the correctness of the request signature.
* Should be changed to be protected.
*/
public function verify_signature() {
}
/**
* Attempts Jetpack registration which sets up the site for connection. Should
* remain public because the call to action comes from the current site, not from
* WordPress.com.
*
* @return Integer zero on success, or a bitmask on failure.
*/
public function register() {
return 0;
}
/**
* Returns the callable that would be used to generate secrets.
*
* @return Callable a function that returns a secure string to be used as a secret.
*/
protected function get_secret_callable() {
if ( ! isset( $this->secret_callable ) ) {
/**
* Allows modification of the callable that is used to generate connection secrets.
*
* @param Callable a function or method that returns a secret string.
*/
$this->secret_callable = apply_filters( 'jetpack_connection_secret_generator', 'wp_generate_password' );
}
return $this->secret_callable;
}
/**
* Generates two secret tokens and the end of life timestamp for them.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
* @param Integer $exp Expiration time in seconds.
*/
public function generate_secrets( $action, $user_id, $exp ) {
$callable = $this->get_secret_callable();
$secrets = \Jetpack_Options::get_raw_option(
self::SECRETS_OPTION_NAME,
array()
);
$secret_name = 'jetpack_' . $action . '_' . $user_id;
if (
isset( $secrets[ $secret_name ] ) &&
$secrets[ $secret_name ]['exp'] > time()
) {
return $secrets[ $secret_name ];
}
$secret_value = array(
'secret_1' => call_user_func( $callable ),
'secret_2' => call_user_func( $callable ),
'exp' => time() + $exp,
);
$secrets[ $secret_name ] = $secret_value;
\Jetpack_Options::update_raw_option( self::SECRETS_OPTION_NAME, $secrets );
return $secrets[ $secret_name ];
}
/**
* Returns two secret tokens and the end of life timestamp for them.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
* @return string|array an array of secrets or an error string.
*/
public function get_secrets( $action, $user_id ) {
$secret_name = 'jetpack_' . $action . '_' . $user_id;
$secrets = \Jetpack_Options::get_raw_option(
self::SECRETS_OPTION_NAME,
array()
);
if ( ! isset( $secrets[ $secret_name ] ) ) {
return self::SECRETS_MISSING;
}
if ( $secrets[ $secret_name ]['exp'] < time() ) {
$this->delete_secrets( $action, $user_id );
return self::SECRETS_EXPIRED;
}
return $secrets[ $secret_name ];
}
/**
* Deletes secret tokens in case they, for example, have expired.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
*/
public function delete_secrets( $action, $user_id ) {
$secret_name = 'jetpack_' . $action . '_' . $user_id;
$secrets = \Jetpack_Options::get_raw_option(
self::SECRETS_OPTION_NAME,
array()
);
if ( isset( $secrets[ $secret_name ] ) ) {
unset( $secrets[ $secret_name ] );
\Jetpack_Options::update_raw_option( self::SECRETS_OPTION_NAME, $secrets );
}
}
/**
* Responds to a WordPress.com call to register the current site.
* Should be changed to protected.
*
* @param array $registration_data Array of [ secret_1, user_id ].
*/
public function handle_registration( array $registration_data ) {
list( $registration_secret_1, $registration_user_id ) = $registration_data;
if ( empty( $registration_user_id ) ) {
return new \WP_Error( 'registration_state_invalid', __( 'Invalid Registration State', 'jetpack' ), 400 );
}
return $this->verify_secrets( 'register', $registration_secret_1, (int) $registration_user_id );
}
/**
* Verify a Previously Generated Secret.
*
* @param string $action The type of secret to verify.
* @param string $secret_1 The secret string to compare to what is stored.
* @param int $user_id The user ID of the owner of the secret.
*/
protected function verify_secrets( $action, $secret_1, $user_id ) {
$allowed_actions = array( 'register', 'authorize', 'publicize' );
if ( ! in_array( $action, $allowed_actions, true ) ) {
return new \WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
}
$user = get_user_by( 'id', $user_id );
/**
* We've begun verifying the previously generated secret.
*
* @since 7.5.0
*
* @param string $action The type of secret to verify.
* @param \WP_User $user The user object.
*/
do_action( 'jetpack_verify_secrets_begin', $action, $user );
$return_error = function( \WP_Error $error ) use ( $action, $user ) {
/**
* Verifying of the previously generated secret has failed.
*
* @since 7.5.0
*
* @param string $action The type of secret to verify.
* @param \WP_User $user The user object.
* @param \WP_Error $error The error object.
*/
do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
return $error;
};
$stored_secrets = $this->get_secrets( $action, $user_id );
$this->delete_secrets( $action, $user_id );
if ( empty( $secret_1 ) ) {
return $return_error(
new \WP_Error(
'verify_secret_1_missing',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ),
400
)
);
} elseif ( ! is_string( $secret_1 ) ) {
return $return_error(
new \WP_Error(
'verify_secret_1_malformed',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ),
400
)
);
} elseif ( empty( $user_id ) ) {
// $user_id is passed around during registration as "state".
return $return_error(
new \WP_Error(
'state_missing',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ),
400
)
);
} elseif ( ! ctype_digit( (string) $user_id ) ) {
return $return_error(
new \WP_Error(
'verify_secret_1_malformed',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ),
400
)
);
}
if ( ! $stored_secrets ) {
return $return_error(
new \WP_Error(
'verify_secrets_missing',
__( 'Verification secrets not found', 'jetpack' ),
400
)
);
} elseif ( is_wp_error( $stored_secrets ) ) {
$stored_secrets->add_data( 400 );
return $return_error( $stored_secrets );
} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
return $return_error(
new \WP_Error(
'verify_secrets_incomplete',
__( 'Verification secrets are incomplete', 'jetpack' ),
400
)
);
} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
return $return_error(
new \WP_Error(
'verify_secrets_mismatch',
__( 'Secret mismatch', 'jetpack' ),
400
)
);
}
/**
* We've succeeded at verifying the previously generated secret.
*
* @since 7.5.0
*
* @param string $action The type of secret to verify.
* @param \WP_User $user The user object.
*/
do_action( 'jetpack_verify_secrets_success', $action, $user );
return $stored_secrets['secret_2'];
}
/**
* Responds to a WordPress.com call to authorize the current user.
* Should be changed to protected.
*/
public function handle_authorization() {
}
/**
* Builds a URL to the Jetpack connection auth page.
* This needs rethinking.
*
* @param bool $raw If true, URL will not be escaped.
* @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
* If string, will be a custom redirect.
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
* @param bool $register If true, will generate a register URL regardless of the existing token, since 4.9.0.
*
* @return string Connect URL
*/
public function build_connect_url( $raw, $redirect, $from, $register ) {
return array( $raw, $redirect, $from, $register );
}
/**
* Disconnects from the Jetpack servers.
* Forgets all connection details and tells the Jetpack servers to do the same.
*/
public function disconnect_site() {
}
/**
* The Base64 Encoding of the SHA1 Hash of the Input.
*
* @param string $text The string to hash.
* @return string
*/
public function sha1_base64( $text ) {
return base64_encode( sha1( $text, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
/**
* This function mirrors Jetpack_Data::is_usable_domain() in the WPCOM codebase.
*
* @param string $domain The domain to check.
*
* @return bool|WP_Error
*/
public function is_usable_domain( $domain ) {
// If it's empty, just fail out.
if ( ! $domain ) {
return new \WP_Error(
'fail_domain_empty',
/* translators: %1$s is a domain name. */
sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is empty.', 'jetpack' ), $domain )
);
}
/**
* Skips the usuable domain check when connecting a site.
*
* Allows site administrators with domains that fail gethostname-based checks to pass the request to WP.com
*
* @since 4.1.0
*
* @param bool If the check should be skipped. Default false.
*/
if ( apply_filters( 'jetpack_skip_usuable_domain_check', false ) ) {
return true;
}
// None of the explicit localhosts.
$forbidden_domains = array(
'wordpress.com',
'localhost',
'localhost.localdomain',
'127.0.0.1',
'local.wordpress.test', // VVV pattern.
'local.wordpress-trunk.test', // VVV pattern.
'src.wordpress-develop.test', // VVV pattern.
'build.wordpress-develop.test', // VVV pattern.
);
if ( in_array( $domain, $forbidden_domains, true ) ) {
return new \WP_Error(
'fail_domain_forbidden',
sprintf(
/* translators: %1$s is a domain name. */
__(
'Domain `%1$s` just failed is_usable_domain check as it is in the forbidden array.',
'jetpack'
),
$domain
)
);
}
// No .test or .local domains.
if ( preg_match( '#\.(test|local)$#i', $domain ) ) {
return new \WP_Error(
'fail_domain_tld',
sprintf(
/* translators: %1$s is a domain name. */
__(
'Domain `%1$s` just failed is_usable_domain check as it uses an invalid top level domain.',
'jetpack'
),
$domain
)
);
}
// No WPCOM subdomains.
if ( preg_match( '#\.WordPress\.com$#i', $domain ) ) {
return new \WP_Error(
'fail_subdomain_wpcom',
sprintf(
/* translators: %1$s is a domain name. */
__(
'Domain `%1$s` just failed is_usable_domain check as it is a subdomain of WordPress.com.',
'jetpack'
),
$domain
)
);
}
// If PHP was compiled without support for the Filter module (very edge case).
if ( ! function_exists( 'filter_var' ) ) {
// Just pass back true for now, and let wpcom sort it out.
return true;
}
return true;
}
/**
* Gets the requested token.
*
* Tokens are one of two types:
* 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token,
* though some sites can have multiple "Special" Blog Tokens (see below). These tokens
* are not associated with a user account. They represent the site's connection with
* the Jetpack servers.
* 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token.
*
* All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the
* token, and $private is a secret that should never be displayed anywhere or sent
* over the network; it's used only for signing things.
*
* Blog Tokens can be "Normal" or "Special".
* * Normal: The result of a normal connection flow. They look like
* "{$random_string_1}.{$random_string_2}"
* That is, $token_key and $private are both random strings.
* Sites only have one Normal Blog Token. Normal Tokens are found in either
* Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN
* constant (rare).
* * Special: A connection token for sites that have gone through an alternative
* connection flow. They look like:
* ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}"
* That is, $private is a random string and $token_key has a special structure with
* lots of semicolons.
* Most sites have zero Special Blog Tokens. Special tokens are only found in the
* JETPACK_BLOG_TOKEN constant.
*
* In particular, note that Normal Blog Tokens never start with ";" and that
* Special Blog Tokens always do.
*
* When searching for a matching Blog Tokens, Blog Tokens are examined in the following
* order:
* 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant)
* 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' ))
* 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant)
*
* @param int|false $user_id false: Return the Blog Token. int: Return that user's User Token.
* @param string|false $token_key If provided, check that the token matches the provided input.
* @param bool|true $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found.
*
* @return object|false
*/
public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
$possible_special_tokens = array();
$possible_normal_tokens = array();
$user_tokens = \Jetpack_Options::get_option( 'user_tokens' );
if ( $user_id ) {
if ( ! $user_tokens ) {
return $suppress_errors ? false : new \WP_Error( 'no_user_tokens' );
}
if ( self::JETPACK_MASTER_USER === $user_id ) {
$user_id = \Jetpack_Options::get_option( 'master_user' );
if ( ! $user_id ) {
return $suppress_errors ? false : new \WP_Error( 'empty_master_user_option' );
}
}
if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
return $suppress_errors ? false : new \WP_Error( 'no_token_for_user', sprintf( 'No token for user %d', $user_id ) );
}
$user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
return $suppress_errors ? false : new \WP_Error( 'token_malformed', sprintf( 'Token for user %d is malformed', $user_id ) );
}
if ( $user_token_chunks[2] !== (string) $user_id ) {
return $suppress_errors ? false : new \WP_Error( 'user_id_mismatch', sprintf( 'Requesting user_id %d does not match token user_id %d', $user_id, $user_token_chunks[2] ) );
}
$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
} else {
$stored_blog_token = \Jetpack_Options::get_option( 'blog_token' );
if ( $stored_blog_token ) {
$possible_normal_tokens[] = $stored_blog_token;
}
$defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' );
if ( $defined_tokens_string ) {
$defined_tokens = explode( ',', $defined_tokens_string );
foreach ( $defined_tokens as $defined_token ) {
if ( ';' === $defined_token[0] ) {
$possible_special_tokens[] = $defined_token;
} else {
$possible_normal_tokens[] = $defined_token;
}
}
}
}
if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
$possible_tokens = $possible_normal_tokens;
} else {
$possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens );
}
if ( ! $possible_tokens ) {
return $suppress_errors ? false : new \WP_Error( 'no_possible_tokens' );
}
$valid_token = false;
if ( false === $token_key ) {
// Use first token.
$valid_token = $possible_tokens[0];
} elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
// Use first normal token.
$valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check.
} else {
// Use the token matching $token_key or false if none.
// Ensure we check the full key.
$token_check = rtrim( $token_key, '.' ) . '.';
foreach ( $possible_tokens as $possible_token ) {
if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) {
$valid_token = $possible_token;
break;
}
}
}
if ( ! $valid_token ) {
return $suppress_errors ? false : new \WP_Error( 'no_valid_token' );
}
return (object) array(
'secret' => $valid_token,
'external_user_id' => (int) $user_id,
);
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* The Jetpack Connection Interface file.
*
* @package jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* The Connection interface class file.
*
* @package jetpack-connection
*/
/**
* The interface that the Connection class must inherit in order to be used for connecting
* to WordPress.com
*/
interface Manager_Interface {
/**
* Initializes all needed hooks and request handlers. Handles API calls, upload
* requests, authentication requests. Also XMLRPC options requests.
* Fallback XMLRPC is also a bridge, but probably can be a class that inherits
* this one. Among other things it should strip existing methods.
*
* @param Array $methods an array of API method names for the Connection to accept and
* pass on to existing callables. It's possible to specify whether
* each method should be available for unauthenticated calls or not.
* @see Jetpack::__construct
*/
public function initialize( $methods );
/**
* Returns true if the current site is connected to WordPress.com.
*
* @return Boolean is the site connected?
*/
public function is_active();
/**
* Returns true if the user with the specified identifier is connected to
* WordPress.com.
*
* @param Integer $user_id the user identifier.
* @return Boolean is the user connected?
*/
public function is_user_connected( $user_id );
/**
* Get the wpcom user data of the current|specified connected user.
*
* @param Integer $user_id the user identifier.
* @return Object the user object.
*/
public function get_connected_user_data( $user_id );
/**
* Is the user the connection owner.
*
* @param Integer $user_id the user identifier.
* @return Boolean is the user the connection owner?
*/
public function is_connection_owner( $user_id );
/**
* Unlinks the current user from the linked WordPress.com user
*
* @param Integer $user_id the user identifier.
*/
public static function disconnect_user( $user_id );
/**
* Initializes a transport server, whatever it may be, saves into the object property.
* Should be changed to be protected.
*/
public function initialize_server();
/**
* Checks if the current request is properly authenticated, bails if not.
* Should be changed to be protected.
*/
public function require_authentication();
/**
* Verifies the correctness of the request signature.
* Should be changed to be protected.
*/
public function verify_signature();
/**
* Attempts Jetpack registration which sets up the site for connection. Should
* remain public because the call to action comes from the current site, not from
* WordPress.com.
*
* @return Integer zero on success, or a bitmask on failure.
*/
public function register();
/**
* Creates two secret tokens and the end of life timestamp for them.
*
* Note these tokens are unique per call, NOT static per site for connecting.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
* @return array
*/
public function get_secrets( $action, $user_id );
/**
* Responds to a WordPress.com call to register the current site.
* Should be changed to protected.
*
* @param array $registration_data Array of [ secret_1, user_id ].
*/
public function handle_registration( array $registration_data );
/**
* Responds to a WordPress.com call to authorize the current user.
* Should be changed to protected.
*/
public function handle_authorization();
/**
* Builds a URL to the Jetpack connection auth page.
* This needs rethinking.
*
* @param bool $raw If true, URL will not be escaped.
* @param bool|string $redirect If true, will redirect back to Jetpack wp-admin landing page after connection.
* If string, will be a custom redirect.
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
* @param bool $register If true, will generate a register URL regardless of the existing token, since 4.9.0.
*
* @return string Connect URL
*/
public function build_connect_url( $raw, $redirect, $from, $register );
/**
* Disconnects from the Jetpack servers.
* Forgets all connection details and tells the Jetpack servers to do the same.
*/
public function disconnect_site();
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Sets up the Connection REST API endpoints.
*
* @package jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* Registers the REST routes for Connections.
*/
class REST_Connector {
/**
* The Connection Manager.
*
* @var Manager
*/
private $connection;
/**
* Constructor.
*
* @param Manager $connection The Connection Manager.
*/
public function __construct( Manager $connection ) {
$this->connection = $connection;
// Register a site.
register_rest_route(
'jetpack/v4',
'/verify_registration',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'verify_registration' ),
)
);
}
/**
* Handles verification that a site is registered.
*
* @since 5.4.0
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return string|WP_Error
*/
public function verify_registration( \WP_REST_Request $request ) {
$registration_data = array( $request['secret_1'], $request['state'] );
return $this->connection->handle_registration( $registration_data );
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* Sets up the Connection XML-RPC methods.
*
* @package jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* Registers the XML-RPC methods for Connections.
*/
class XMLRPC_Connector {
/**
* The Connection Manager.
*
* @var Manager
*/
private $connection;
/**
* Constructor.
*
* @param Manager $connection The Connection Manager.
*/
public function __construct( Manager $connection ) {
$this->connection = $connection;
add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
}
/**
* Attached to the `xmlrpc_methods` filter.
*
* @param array $methods The already registered XML-RPC methods.
* @return array
*/
public function xmlrpc_methods( $methods ) {
return array_merge(
$methods,
array(
'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
)
);
}
/**
* Handles verification that a site is registered.
*
* @param array $registration_data The data sent by the XML-RPC client:
* [ $secret_1, $user_id ].
*
* @return string|IXR_Error
*/
public function verify_registration( $registration_data ) {
return $this->output( $this->connection->handle_registration( $registration_data ) );
}
/**
* Normalizes output for XML-RPC.
*
* @param mixed $data The data to output.
*/
private function output( $data ) {
if ( is_wp_error( $data ) ) {
$code = $data->get_error_data();
if ( ! $code ) {
$code = -10520;
}
return new \IXR_Error(
$code,
sprintf( 'Jetpack: [%s] %s', $data->get_error_code(), $data->get_error_message() )
);
}
return $data;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* A constants manager for Jetpack.
*
* @package jetpack-constants
*/
namespace Automattic\Jetpack;
/**
* Class Automattic\Jetpack\Constants
*
* Testing constants is hard. Once you define a constant, it's defined. Constants Manager is an
* abstraction layer so that unit tests can set "constants" for tests.
*
* To test your code, you'll need to swap out `defined( 'CONSTANT' )` with `Automattic\Jetpack\Constants::is_defined( 'CONSTANT' )`
* and replace `CONSTANT` with `Automattic\Jetpack\Constants::get_constant( 'CONSTANT' )`. Then in the unit test, you can set the
* constant with `Automattic\Jetpack\Constants::set_constant( 'CONSTANT', $value )` and then clean up after each test with something like
* this:
*
* function tearDown() {
* Automattic\Jetpack\Constants::clear_constants();
* }
*/
class Constants {
/**
* A container for all defined constants.
*
* @access public
* @static
*
* @var array.
*/
public static $set_constants = array();
/**
* Checks if a "constant" has been set in constants Manager
* and has the value of true
*
* @param string $name The name of the constant.
*
* @return bool
*/
public static function is_true( $name ) {
return self::is_defined( $name ) && self::get_constant( $name );
}
/**
* Checks if a "constant" has been set in constants Manager, and if not,
* checks if the constant was defined with define( 'name', 'value ).
*
* @param string $name The name of the constant.
*
* @return bool
*/
public static function is_defined( $name ) {
return array_key_exists( $name, self::$set_constants )
? true
: defined( $name );
}
/**
* Attempts to retrieve the "constant" from constants Manager, and if it hasn't been set,
* then attempts to get the constant with the constant() function.
*
* @param string $name The name of the constant.
*
* @return mixed null if the constant does not exist or the value of the constant.
*/
public static function get_constant( $name ) {
if ( array_key_exists( $name, self::$set_constants ) ) {
return self::$set_constants[ $name ];
}
return defined( $name ) ? constant( $name ) : null;
}
/**
* Sets the value of the "constant" within constants Manager.
*
* @param string $name The name of the constant.
* @param string $value The value of the constant.
*/
public static function set_constant( $name, $value ) {
self::$set_constants[ $name ] = $value;
}
/**
* Will unset a "constant" from constants Manager if the constant exists.
*
* @param string $name The name of the constant.
*
* @return bool Whether the constant was removed.
*/
public static function clear_single_constant( $name ) {
if ( ! array_key_exists( $name, self::$set_constants ) ) {
return false;
}
unset( self::$set_constants[ $name ] );
return true;
}
/**
* Resets all of the constants within constants Manager.
*/
public static function clear_constants() {
self::$set_constants = array();
}
}

View File

@@ -0,0 +1,479 @@
/*!
* Do not modify this file directly. It is automatically generated.
*/
/*!
* Do not modify this file directly. It is compiled SASS code.
*/
@charset "UTF-8";
/*
The MIT License (MIT)
Copyright © 20112015 thoughtbot, inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://github.com/thoughtbot/bourbon
*/
.jitm-button {
background: white;
border-color: #d5d5d5;
border-style: solid;
border-width: 1px 1px 2px;
color: #414141;
cursor: pointer;
display: inline-block;
margin: 0;
outline: 0;
overflow: hidden;
font-weight: 500;
text-overflow: ellipsis;
text-decoration: none;
vertical-align: top;
box-sizing: border-box;
font-size: 0.875rem;
line-height: 1.3125rem;
border-radius: 0.25rem;
padding: 0.4375rem 0.875rem 0.5625rem;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.jitm-button:hover {
border-color: #bbbbbb;
color: #414141;
}
.jitm-button:active {
border-width: 2px 1px 1px;
}
.jitm-button:visited {
color: #414141;
}
.jitm-button[disabled], .jitm-button:disabled {
color: #eeeeee;
background: white;
border-color: #eeeeee;
cursor: default;
}
.jitm-button[disabled]:active, .jitm-button:disabled:active {
border-width: 1px 1px 2px;
}
.jitm-button:focus {
outline: 0;
border-color: #00aadc;
box-shadow: 0 0 0 2px #78dcfa;
}
.jitm-button.is-compact {
padding: 0.4375rem;
color: #888888;
font-size: 0.75rem;
line-height: 1;
}
.jitm-button.is-compact:disabled {
color: #eeeeee;
}
.jitm-button.hidden {
display: none;
}
.jitm-button.is-primary {
background: #00aadc;
border-color: #0087be;
color: white;
}
.jitm-button.is-primary:hover, .jitm-button.is-primary:focus {
border-color: #005082;
color: white;
}
.jitm-button.is-primary[disabled], .jitm-button.is-primary:disabled {
background: #bceefd;
border-color: #8cc9e2;
color: white;
}
.jitm-button.is-primary.is-compact {
color: white;
}
.jitm-card {
display: block;
clear: both;
position: relative;
margin: 3rem auto 0 1.25rem;
padding: 1rem;
box-sizing: border-box;
background: white;
box-shadow: 0 0 0 1px rgba(213, 213, 213, 0.5), 0 1px 2px #eeeeee;
}
.jitm-card:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
@media (min-width: 481px) {
.jitm-card {
margin-bottom: 1rem;
padding: 1.5rem;
}
}
.jitm-card.is-compact {
margin-bottom: 0.0625rem;
}
@media (min-width: 481px) {
.jitm-card.is-compact {
margin-bottom: 1px;
padding: 1rem 1.5rem;
}
}
.jitm-card.is-card-link {
padding-left: 3rem;
}
#screen-meta-links + .jitm-card {
margin: 2.5rem auto 0 1.5385em;
}
#dolly + .jitm-card {
margin: 3rem auto 0 1rem;
}
.post-php .jitm-card {
margin-left: 0;
}
.jp-lower .jitm-card {
margin: 0 0 1.5rem;
}
.jitm-banner.jitm-card {
border-right: 4px solid;
display: flex;
padding: 0.75rem 0.75rem 0.75rem 0.375rem;
position: relative;
z-index: 2;
border-right-color: #4ab866;
}
@media (max-width: 480px) {
.jitm-banner.jitm-card {
display: block;
}
}
.jitm-banner.jitm-card.is-card-link {
padding: 0.75rem 1rem 0.75rem 3rem;
}
.jitm-banner.jitm-card.is-dismissible {
padding-left: 3rem;
}
.jitm-banner.jitm-card .jitm-banner__icon {
color: #4ab866;
}
.jitm-banner.jitm-card .jitm-banner__icon-circle {
background-color: #4ab866;
}
.jitm-banner.jitm-card.is-upgrade-personal {
border-right-color: #f0b849;
}
.jitm-banner.jitm-card.is-upgrade-personal .jitm-banner__icon {
color: #f0b849;
}
.jitm-banner.jitm-card.is-upgrade-personal .jitm-banner__icon-circle {
background-color: #f0b849;
}
.jitm-banner.jitm-card.is-upgrade-premium {
border-right-color: #4ab866;
}
.jitm-banner.jitm-card.is-upgrade-premium .jitm-banner__icon {
color: #4ab866;
}
.jitm-banner.jitm-card.is-upgrade-premium .jitm-banner__icon-circle {
background-color: #4ab866;
}
.jitm-banner.jitm-card.is-upgrade-business, .jitm-banner.jitm-card.woo-jitm {
border-right-color: #855DA6;
}
.jitm-banner.jitm-card.is-upgrade-business .jitm-banner__icon, .jitm-banner.jitm-card.woo-jitm .jitm-banner__icon {
color: #855DA6;
}
.jitm-banner.jitm-card.is-upgrade-business .jitm-banner__icon-circle, .jitm-banner.jitm-card.woo-jitm .jitm-banner__icon-circle {
background-color: #855DA6;
}
.jitm-banner.jitm-card .jitm-card__link-indicator {
align-items: center;
color: #0087be;
display: flex;
}
.jitm-banner.jitm-card:hover {
transition: all 100ms ease-in-out;
}
.jitm-banner.jitm-card:hover.is-card-link {
box-shadow: 0 0 0 1px #a2a2a2, 0 2px 4px #d5d5d5;
}
.jitm-banner.jitm-card:hover .jitm-card__link-indicator {
color: #005082;
}
@media (min-width: 481px) {
.jitm-banner.jitm-card {
padding: 0.75rem 1rem;
}
.jitm-banner.jitm-card.is-dismissible {
padding-left: 1rem;
}
}
.jitm-banner__icons {
display: flex;
}
.jitm-banner__icons .jitm-banner__icon,
.jitm-banner__icons .jitm-banner__icon-circle {
border-radius: 50%;
flex-shrink: 0;
height: 1.5rem;
width: 1.5rem;
margin-left: 1rem;
margin-top: -0.125rem;
text-align: center;
top: 0.25rem;
}
.jitm-banner__icons .jitm-banner__icon {
align-self: center;
color: white;
display: block;
}
.jitm-banner__icons .jitm-banner__icon-circle {
color: white;
display: none;
padding: 0.1875rem 0.1875rem 0.25rem 0.25rem;
}
@media (min-width: 481px) {
.jitm-banner__icons {
align-items: center;
}
.jitm-banner__icons .jitm-banner__icon {
display: none;
}
.jitm-banner__icons .jitm-banner__icon-circle {
display: block;
}
}
.jitm-banner__icon-plan {
display: flex;
margin-left: 1rem;
}
.jitm-banner__icon-plan .dops-plan-icon {
height: 2rem;
width: 2rem;
}
.jitm-banner__icon-plan .jp-emblem {
position: relative;
top: 0.125rem;
}
@media (max-width: 480px) {
.jitm-banner__icon-plan .jp-emblem {
margin-bottom: 0.75rem;
}
}
.jitm-banner__icon-plan .jp-emblem svg {
height: 2rem;
width: 2rem;
fill: #00BE28;
}
@media (min-width: 481px) {
.jitm-banner__icon-plan {
align-items: center;
}
}
.jitm-banner__content {
align-items: center;
display: flex;
flex-grow: 1;
flex-wrap: wrap;
}
@media (min-width: 481px) {
.jitm-banner__content {
flex-wrap: nowrap;
}
}
.jitm-banner__info {
flex-grow: 1;
line-height: 1.4;
}
@media (min-width: 481px) {
.jitm-banner__info {
flex-basis: 50%;
}
}
@media (min-width: 961px) {
.jitm-banner__info {
flex-basis: 70%;
}
}
.jitm-banner__info .jitm-banner__title,
.jitm-banner__info .jitm-banner__description {
color: #414141;
}
.jitm-banner__info .jitm-banner__title {
font-size: 14px;
font-weight: 500;
}
.jitm-banner__info .jitm-banner__description {
font-size: 0.75rem;
line-height: 1.5;
margin-top: 0.375rem;
}
.jitm-banner__info .banner__list {
font-size: 12px;
list-style: none;
margin: 10px 0;
}
.jitm-banner__info .banner__list li {
margin: 6px 0;
}
.jitm-banner__info .banner__list li .gridicon {
fill: #a2a2a2;
display: inline;
margin-left: 12px;
vertical-align: bottom;
}
.jitm-banner__action {
align-self: center;
font-size: 0.75rem;
margin: 0.5rem 0 0;
text-align: right;
width: 100%;
}
.jitm-banner__action .jitm-banner__prices {
display: flex;
justify-content: flex-start;
}
.jitm-banner__action .jitm-banner__prices .dops-plan-price {
margin-bottom: 0;
}
.jitm-banner__action .jitm-banner__prices .dops-plan-price.is-discounted,
.jitm-banner__action .jitm-banner__prices .dops-plan-price.is-discounted .dops-plan-price__currency-symbol {
color: #414141;
}
.has-call-to-action .jitm-banner__action .jitm-banner__prices .dops-plan-price {
margin-bottom: 0.5rem;
}
@media (min-width: 481px) {
.jitm-banner__action {
margin: 0 0.5rem 0 0.25rem;
text-align: center;
width: auto;
}
.jitm-banner__action .is-dismissible {
margin-top: 2.5rem;
}
.jitm-banner__action .jitm-banner__prices {
justify-content: flex-end;
text-align: left;
}
}
.jitm-banner__dismiss {
display: block;
text-decoration: none;
line-height: .5;
}
.jitm-banner__dismiss:before {
color: #6f6f6f;
font: 400 16px/1 dashicons;
content: '\f158';
}
@media (min-width: 661px) {
.jitm-banner__dismiss {
margin-left: -0.5rem;
}
}
@media (max-width: 480px) {
.jitm-banner__dismiss {
position: absolute;
top: 0.875rem;
left: 0.875rem;
}
}
.jitm-banner__action + .jitm-banner__dismiss {
margin-right: 0.625rem;
}
#dolly + .jitm-card {
margin: 3rem auto 0 1rem;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,448 @@
<?php
namespace Automattic\Jetpack;
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
use Automattic\Jetpack\Tracking;
/**
* Jetpack just in time messaging through out the admin
*
* @since 5.6.0
*/
class JITM {
const PACKAGE_VERSION = '1.0';
/**
* Tracking object.
*
* @var Automattic\Jetpack\Tracking
*
* @access private
*/
private $tracking;
/**
* JITM constructor.
*/
public function __construct() {
$this->tracking = new Tracking();
}
public function register() {
/**
* Filter to turn off all just in time messages
*
* @since 3.7.0
* @since 5.4.0 Correct docblock to reflect default arg value
*
* @param bool false Whether to show just in time messages.
*/
if ( ! apply_filters( 'jetpack_just_in_time_msgs', false ) ) {
return false;
}
add_action( 'current_screen', array( $this, 'prepare_jitms' ) );
return true;
}
/**
* Get's the Jetpack emblem
*
* @return string The Jetpack emblem
*/
function get_emblem() {
$jetpack_logo = new Jetpack_Logo();
return '<div class="jp-emblem">' . $jetpack_logo->get_jp_emblem() . '</div>';
}
/**
* Prepare actions according to screen and post type.
*
* @since 3.8.2
*
* @uses Jetpack_Autoupdate::get_possible_failures()
*
* @param object $screen
*/
function prepare_jitms( $screen ) {
if ( ! in_array(
$screen->id,
array(
'jetpack_page_stats',
'jetpack_page_akismet-key-config',
'admin_page_jetpack_modules',
)
) ) {
add_action( 'admin_enqueue_scripts', array( $this, 'jitm_enqueue_files' ) );
add_action( 'admin_notices', array( $this, 'ajax_message' ) );
add_action( 'edit_form_top', array( $this, 'ajax_message' ) );
}
}
/**
* A special filter for WooCommerce, to set a message based on local state.
*
* @param $message string The current message
*
* @return array The new message
*/
static function jitm_woocommerce_services_msg( $content ) {
if ( ! function_exists( 'wc_get_base_location' ) ) {
return $content;
}
$base_location = wc_get_base_location();
switch ( $base_location['country'] ) {
case 'US':
$content->message = esc_html__( 'New free service: Show USPS shipping rates on your store! Added bonus: print shipping labels without leaving WooCommerce.', 'jetpack' );
break;
case 'CA':
$content->message = esc_html__( 'New free service: Show Canada Post shipping rates on your store!', 'jetpack' );
break;
default:
$content->message = '';
}
return $content;
}
/**
* A special filter for WooCommerce Call To Action button
*
* @param $CTA string The existing CTA
*
* @return string The new CTA
*/
static function jitm_jetpack_woo_services_install( $CTA ) {
return wp_nonce_url(
add_query_arg(
array(
'wc-services-action' => 'install',
),
admin_url( 'admin.php?page=wc-settings' )
),
'wc-services-install'
);
}
/**
* A special filter for WooCommerce Call To Action button
*
* @param $CTA string The existing CTA
*
* @return string The new CTA
*/
static function jitm_jetpack_woo_services_activate( $CTA ) {
return wp_nonce_url(
add_query_arg(
array(
'wc-services-action' => 'activate',
),
admin_url( 'admin.php?page=wc-settings' )
),
'wc-services-install'
);
}
/**
* Injects the dom to show a JITM inside of
*/
function ajax_message() {
$message_path = $this->get_message_path();
$query_string = _http_build_query( $_GET, '', ',' );
$current_screen = wp_unslash( $_SERVER['REQUEST_URI'] );
?>
<div class="jetpack-jitm-message"
data-nonce="<?php echo wp_create_nonce( 'wp_rest' ); ?>"
data-message-path="<?php echo esc_attr( $message_path ); ?>"
data-query="<?php echo urlencode_deep( $query_string ); ?>"
data-redirect="<?php echo urlencode_deep( $current_screen ); ?>"
></div>
<?php
}
/**
* Get's the current message path for display of a JITM
*
* @return string The message path
*/
function get_message_path() {
$screen = get_current_screen();
return 'wp:' . $screen->id . ':' . current_filter();
}
/**
* Function to enqueue jitm css and js
*/
function jitm_enqueue_files() {
$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
wp_register_style(
'jetpack-jitm-css',
plugins_url( "assets/jetpack-admin-jitm{$min}.css", __DIR__ ),
false,
self::PACKAGE_VERSION .
'-201243242'
);
wp_style_add_data( 'jetpack-jitm-css', 'rtl', 'replace' );
wp_style_add_data( 'jetpack-jitm-css', 'suffix', $min );
wp_enqueue_style( 'jetpack-jitm-css' );
wp_enqueue_script(
'jetpack-jitm-new',
Assets::get_file_url_for_environment( '_inc/build/jetpack-jitm.min.js', '_inc/jetpack-jitm.js' ),
array( 'jquery' ),
self::PACKAGE_VERSION, // TODO: Keep in sync with version specified in composer.json
true
);
wp_localize_script(
'jetpack-jitm-new',
'jitm_config',
array(
'api_root' => esc_url_raw( rest_url() ),
'activate_module_text' => esc_html__( 'Activate', 'jetpack' ),
'activated_module_text' => esc_html__( 'Activated', 'jetpack' ),
'activating_module_text' => esc_html__( 'Activating', 'jetpack' ),
)
);
}
/**
* Dismisses a JITM feature class so that it will no longer be shown
*
* @param $id string The id of the JITM that was dismissed
* @param $feature_class string The feature class of the JITM that was dismissed
*
* @return bool Always true
*/
function dismiss( $id, $feature_class ) {
$this->tracking->record_user_event(
'jitm_dismiss_client',
array(
'jitm_id' => $id,
'feature_class' => $feature_class,
)
);
$hide_jitm = \Jetpack_Options::get_option( 'hide_jitm' );
if ( ! is_array( $hide_jitm ) ) {
$hide_jitm = array();
}
if ( isset( $hide_jitm[ $feature_class ] ) ) {
if ( ! is_array( $hide_jitm[ $feature_class ] ) ) {
$hide_jitm[ $feature_class ] = array(
'last_dismissal' => 0,
'number' => 0,
);
}
} else {
$hide_jitm[ $feature_class ] = array(
'last_dismissal' => 0,
'number' => 0,
);
}
$number = $hide_jitm[ $feature_class ]['number'];
$hide_jitm[ $feature_class ] = array(
'last_dismissal' => time(),
'number' => $number + 1,
);
\Jetpack_Options::update_option( 'hide_jitm', $hide_jitm );
return true;
}
/**
* Asks the wpcom API for the current message to display keyed on query string and message path
*
* @param $message_path string The message path to ask for
* @param $query string The query string originally from the front end
*
* @return array The JITM's to show, or an empty array if there is nothing to show
*/
function get_messages( $message_path, $query ) {
// custom filters go here
add_filter( 'jitm_woocommerce_services_msg', array( 'Jetpack_JITM', 'jitm_woocommerce_services_msg' ) );
add_filter( 'jitm_jetpack_woo_services_install', array( 'Jetpack_JITM', 'jitm_jetpack_woo_services_install' ) );
add_filter(
'jitm_jetpack_woo_services_activate',
array(
'Jetpack_JITM',
'jitm_jetpack_woo_services_activate',
)
);
$user = wp_get_current_user();
// unauthenticated or invalid requests just bail
if ( ! $user ) {
return array();
}
$site_id = \Jetpack_Options::get_option( 'id' );
// build our jitm request
$path = add_query_arg(
array(
'external_user_id' => urlencode_deep( $user->ID ),
'query_string' => urlencode_deep( $query ),
'mobile_browser' => jetpack_is_mobile( 'smart' ) ? 1 : 0,
'_locale' => get_user_locale(),
),
sprintf( '/sites/%d/jitm/%s', $site_id, $message_path )
);
// attempt to get from cache
$envelopes = get_transient( 'jetpack_jitm_' . substr( md5( $path ), 0, 31 ) );
// if something is in the cache and it was put in the cache after the last sync we care about, use it
$use_cache = false;
/** This filter is documented in class.jetpack.php */
if ( apply_filters( 'jetpack_just_in_time_msg_cache', false ) ) {
$use_cache = true;
}
if ( $use_cache ) {
$last_sync = (int) get_transient( 'jetpack_last_plugin_sync' );
$from_cache = $envelopes && $last_sync > 0 && $last_sync < $envelopes['last_response_time'];
} else {
$from_cache = false;
}
// otherwise, ask again
if ( ! $from_cache ) {
$wpcom_response = Client::wpcom_json_api_request_as_blog(
$path,
'2',
array(
'user_id' => $user->ID,
'user_roles' => implode( ',', $user->roles ),
),
null,
'wpcom'
);
// silently fail...might be helpful to track it?
if ( is_wp_error( $wpcom_response ) ) {
return array();
}
$envelopes = json_decode( $wpcom_response['body'] );
if ( ! is_array( $envelopes ) ) {
return array();
}
$expiration = isset( $envelopes[0] ) ? $envelopes[0]->ttl : 300;
// do not cache if expiration is 0 or we're not using the cache
if ( 0 != $expiration && $use_cache ) {
$envelopes['last_response_time'] = time();
set_transient( 'jetpack_jitm_' . substr( md5( $path ), 0, 31 ), $envelopes, $expiration );
}
}
$hidden_jitms = \Jetpack_Options::get_option( 'hide_jitm' );
unset( $envelopes['last_response_time'] );
/**
* Allow adding your own custom JITMs after a set of JITMs has been received.
*
* @since 6.9.0
*
* @param array $envelopes array of existing JITMs.
*/
$envelopes = apply_filters( 'jetpack_jitm_received_envelopes', $envelopes );
foreach ( $envelopes as $idx => &$envelope ) {
$dismissed_feature = isset( $hidden_jitms[ $envelope->feature_class ] ) && is_array( $hidden_jitms[ $envelope->feature_class ] ) ? $hidden_jitms[ $envelope->feature_class ] : null;
// if the this feature class has been dismissed and the request has not passed the ttl, skip it as it's been dismissed
if ( is_array( $dismissed_feature ) && ( time() - $dismissed_feature['last_dismissal'] < $envelope->expires || $dismissed_feature['number'] >= $envelope->max_dismissal ) ) {
unset( $envelopes[ $idx ] );
continue;
}
$this->tracking->record_user_event(
'jitm_view_client',
array(
'jitm_id' => $envelope->id,
)
);
$normalized_site_url = \Jetpack::build_raw_urls( get_home_url() );
$url_params = array(
'source' => "jitm-$envelope->id",
'site' => $normalized_site_url,
'u' => $user->ID,
);
if ( ! class_exists( 'Jetpack_Affiliate' ) ) {
require_once JETPACK__PLUGIN_DIR . 'class.jetpack-affiliate.php';
}
// Get affiliate code and add it to the array of URL parameters
if ( '' !== ( $aff = \Jetpack_Affiliate::init()->get_affiliate_code() ) ) {
$url_params['aff'] = $aff;
}
$envelope->url = add_query_arg( $url_params, 'https://jetpack.com/redirect/' );
$envelope->jitm_stats_url = \Jetpack::build_stats_url( array( 'x_jetpack-jitm' => $envelope->id ) );
if ( $envelope->CTA->hook ) {
$envelope->url = apply_filters( 'jitm_' . $envelope->CTA->hook, $envelope->url );
unset( $envelope->CTA->hook );
}
if ( isset( $envelope->content->hook ) ) {
$envelope->content = apply_filters( 'jitm_' . $envelope->content->hook, $envelope->content );
unset( $envelope->content->hook );
}
// no point in showing an empty message
if ( empty( $envelope->content->message ) ) {
unset( $envelopes[ $idx ] );
continue;
}
switch ( $envelope->content->icon ) {
case 'jetpack':
$jetpack_logo = new Jetpack_Logo();
$envelope->content->icon = '<div class="jp-emblem">' . $jetpack_logo->get_jp_emblem() . '</div>';
break;
case 'woocommerce':
$envelope->content->icon = '<div class="jp-emblem"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 168 100" xml:space="preserve" enable-background="new 0 0 168 100" width="50" height="30"><style type="text/css">
.st0{clip-path:url(#SVGID_2_);enable-background:new ;}
.st1{clip-path:url(#SVGID_4_);}
.st2{clip-path:url(#SVGID_6_);}
.st3{clip-path:url(#SVGID_8_);fill:#8F567F;}
.st4{clip-path:url(#SVGID_10_);fill:#FFFFFE;}
.st5{clip-path:url(#SVGID_12_);fill:#FFFFFE;}
.st6{clip-path:url(#SVGID_14_);fill:#FFFFFE;}
</style><g><defs><polygon id="SVGID_1_" points="83.8 100 0 100 0 0.3 83.8 0.3 167.6 0.3 167.6 100 "/></defs><clipPath id="SVGID_2_"><use xlink:href="#SVGID_1_" overflow="visible"/></clipPath><g class="st0"><g><defs><rect id="SVGID_3_" width="168" height="100"/></defs><clipPath id="SVGID_4_"><use xlink:href="#SVGID_3_" overflow="visible"/></clipPath><g class="st1"><defs><path id="SVGID_5_" d="M15.6 0.3H152c8.6 0 15.6 7 15.6 15.6v52c0 8.6-7 15.6-15.6 15.6h-48.9l6.7 16.4L80.2 83.6H15.6C7 83.6 0 76.6 0 67.9v-52C0 7.3 7 0.3 15.6 0.3"/></defs><clipPath id="SVGID_6_"><use xlink:href="#SVGID_5_" overflow="visible"/></clipPath><g class="st2"><defs><rect id="SVGID_7_" width="168" height="100"/></defs><clipPath id="SVGID_8_"><use xlink:href="#SVGID_7_" overflow="visible"/></clipPath><rect x="-10" y="-9.7" class="st3" width="187.6" height="119.7"/></g></g></g></g></g><g><defs><path id="SVGID_9_" d="M8.4 14.5c1-1.3 2.4-2 4.3-2.1 3.5-0.2 5.5 1.4 6 4.9 2.1 14.3 4.4 26.4 6.9 36.4l15-28.6c1.4-2.6 3.1-3.9 5.2-4.1 3-0.2 4.9 1.7 5.6 5.7 1.7 9.1 3.9 16.9 6.5 23.4 1.8-17.4 4.8-30 9-37.7 1-1.9 2.5-2.9 4.5-3 1.6-0.1 3 0.3 4.3 1.4 1.3 1 2 2.3 2.1 3.9 0.1 1.2-0.1 2.3-0.7 3.3 -2.7 5-4.9 13.2-6.6 24.7 -1.7 11.1-2.3 19.8-1.9 26.1 0.1 1.7-0.1 3.2-0.8 4.5 -0.8 1.5-2 2.4-3.7 2.5 -1.8 0.1-3.6-0.7-5.4-2.5C52.4 66.7 47.4 57 43.7 44.1c-4.4 8.8-7.7 15.3-9.9 19.7 -4 7.7-7.5 11.7-10.3 11.9 -1.9 0.1-3.5-1.4-4.8-4.7 -3.5-9-7.3-26.3-11.3-52C7.1 17.3 7.5 15.8 8.4 14.5"/></defs><clipPath id="SVGID_10_"><use xlink:href="#SVGID_9_" overflow="visible"/></clipPath><rect x="-2.7" y="-0.6" class="st4" width="90.6" height="86.4"/></g><g><defs><path id="SVGID_11_" d="M155.6 25.2c-2.5-4.3-6.1-6.9-11-7.9 -1.3-0.3-2.5-0.4-3.7-0.4 -6.6 0-11.9 3.4-16.1 10.2 -3.6 5.8-5.3 12.3-5.3 19.3 0 5.3 1.1 9.8 3.3 13.6 2.5 4.3 6.1 6.9 11 7.9 1.3 0.3 2.5 0.4 3.7 0.4 6.6 0 12-3.4 16.1-10.2 3.6-5.9 5.3-12.4 5.3-19.4C159 33.4 157.9 28.9 155.6 25.2zM147 44.2c-0.9 4.5-2.7 7.9-5.2 10.1 -2 1.8-3.9 2.5-5.5 2.2 -1.7-0.3-3-1.8-4-4.4 -0.8-2.1-1.2-4.2-1.2-6.2 0-1.7 0.2-3.4 0.5-5 0.6-2.8 1.8-5.5 3.6-8.1 2.3-3.3 4.7-4.8 7.1-4.2 1.7 0.3 3 1.8 4 4.4 0.8 2.1 1.2 4.2 1.2 6.2C147.5 40.9 147.3 42.6 147 44.2z"/></defs><clipPath id="SVGID_12_"><use xlink:href="#SVGID_11_" overflow="visible"/></clipPath><rect x="109.6" y="6.9" class="st5" width="59.4" height="71.4"/></g><g><defs><path id="SVGID_13_" d="M112.7 25.2c-2.5-4.3-6.1-6.9-11-7.9 -1.3-0.3-2.5-0.4-3.7-0.4 -6.6 0-11.9 3.4-16.1 10.2 -3.5 5.8-5.3 12.3-5.3 19.3 0 5.3 1.1 9.8 3.3 13.6 2.5 4.3 6.1 6.9 11 7.9 1.3 0.3 2.5 0.4 3.7 0.4 6.6 0 12-3.4 16.1-10.2 3.5-5.9 5.3-12.4 5.3-19.4C116 33.4 114.9 28.9 112.7 25.2zM104.1 44.2c-0.9 4.5-2.7 7.9-5.2 10.1 -2 1.8-3.9 2.5-5.5 2.2 -1.7-0.3-3-1.8-4-4.4 -0.8-2.1-1.2-4.2-1.2-6.2 0-1.7 0.2-3.4 0.5-5 0.6-2.8 1.8-5.5 3.6-8.1 2.3-3.3 4.7-4.8 7.1-4.2 1.7 0.3 3 1.8 4 4.4 0.8 2.1 1.2 4.2 1.2 6.2C104.6 40.9 104.4 42.6 104.1 44.2z"/></defs><clipPath id="SVGID_14_"><use xlink:href="#SVGID_13_" overflow="visible"/></clipPath><rect x="66.7" y="6.9" class="st6" width="59.4" height="71.4"/></g></svg></div>';
break;
default:
$envelope->content->icon = '';
break;
}
$jetpack = \Jetpack::init();
$jetpack->stat( 'jitm', $envelope->id . '-viewed-' . JETPACK__VERSION );
$jetpack->do_stats( 'server_side' );
}
return $envelopes;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* A logo for Jetpack.
*
* @package jetpack-logo
*/
namespace Automattic\Jetpack\Assets;
/**
* Jetpack logo as SVG shapes.
*
* Initializes the logo property with a string describing the Jetpack logo.
* The Jetpack logo SVG string includes CSS classes to stylize it:
* - jetpack-logo: the wrapper <svg> tag.
* - jetpack-logo__icon-circle: the circle of the Jetpack mark.
* - jetpack-logo__icon-triangle: two shapes that correspond to each triangle in the Jetpack mark.
* - jetpack-logo__icon-text: the Jetpack lettering.
*
* @var string
*/
const JETPACK_LOGO_SVG = <<<'EOSVG'
<svg xmlns="http://www.w3.org/2000/svg" height="32" class="jetpack-logo" viewBox="0 0 118 32">
<path class="jetpack-logo__icon-circle" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16s16-7.2,16-16S24.8,0,16,0z" fill="#00be28" />
<polygon class="jetpack-logo__icon-triangle" points="15,19 7,19 15,3" fill="#fff" />
<polygon class="jetpack-logo__icon-triangle" points="17,29 17,13 25,13" fill="#fff" />
<path class="jetpack-logo__text" d="M41.3 26.6c-.5-.7-.9-1.4-1.3-2.1 2.3-1.4 3-2.5 3-4.6V8h-3V6h6v13.4C46 22.8 45 24.8 41.3 26.6zM58.5 21.3c-1.5.5-2.7.6-4.2.6-3.6 0-5.8-1.8-5.8-6 0-3.1 1.9-5.9 5.5-5.9s4.9 2.5 4.9 4.9c0 .8 0 1.5-.1 2h-7.3c.1 2.5 1.5 2.8 3.6 2.8 1.1 0 2.2-.3 3.4-.7C58.5 19 58.5 21.3 58.5 21.3zM56 15c0-1.4-.5-2.9-2-2.9-1.4 0-2.3 1.3-2.4 2.9C51.6 15 56 15 56 15zM65 18.4c0 1.1.8 1.3 1.4 1.3.5 0 2-.2 2.6-.4v2.1c-.9.3-2.5.5-3.7.5-1.5 0-3.2-.5-3.2-3.1V12H60v-2h2.1V7.1H65V10h4v2h-4V18.4zM71 10h3v1.3c1.1-.8 1.9-1.3 3.3-1.3 2.5 0 4.5 1.8 4.5 5.6s-2.2 6.3-5.8 6.3c-.9 0-1.3-.1-2-.3V28h-3V10zM76.5 12.3c-.8 0-1.6.4-2.5 1.2v5.9c.6.1.9.2 1.8.2 2 0 3.2-1.3 3.2-3.9C79 13.4 78.1 12.3 76.5 12.3zM93 22h-3v-1.5c-.9.7-1.9 1.5-3.5 1.5-1.5 0-3.1-1.1-3.1-3.2 0-2.9 2.5-3.4 4.2-3.7l2.4-.3v-.3c0-1.5-.5-2.3-2-2.3-.7 0-2.3.5-3.7 1.1L84 11c1.2-.4 3-1 4.4-1 2.7 0 4.6 1.4 4.6 4.7L93 22zM90 16.4l-2.2.4c-.7.1-1.4.5-1.4 1.6 0 .9.5 1.4 1.3 1.4s1.5-.5 2.3-1V16.4zM104.5 21.3c-1.1.4-2.2.6-3.5.6-4.2 0-5.9-2.4-5.9-5.9 0-3.7 2.3-6 6.1-6 1.4 0 2.3.2 3.2.5V13c-.8-.3-2-.6-3.2-.6-1.7 0-3.2.9-3.2 3.6 0 2.9 1.5 3.8 3.3 3.8.9 0 1.9-.2 3.2-.7V21.3zM110 15.2c.2-.3.2-.8 3.8-5.2h3.7l-4.6 5.7 5 6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z" />
</svg>
EOSVG;
/**
* Create and render a Jetpack logo.
*/
class Logo {
/**
* Return the Jetpack logo.
*
* @return string The Jetpack logo.
*/
public function render() {
return JETPACK_LOGO_SVG;
}
/**
* Return string containing the Jetpack logo.
*
* @since 7.5.0
*
* @param bool $logotype Should we use the full logotype (logo + text). Default to false.
*
* @return string
*/
public function get_jp_emblem( $logotype = false ) {
$logo = '<path fill="#00BE28" d="M16,0C7.2,0,0,7.2,0,16s7.2,16,16,16c8.8,0,16-7.2,16-16S24.8,0,16,0z M15.2,18.7h-8l8-15.5V18.7z M16.8,28.8 V13.3h8L16.8,28.8z"/>';
$text = '
<path d="M41.3,26.6c-0.5-0.7-0.9-1.4-1.3-2.1c2.3-1.4,3-2.5,3-4.6V8h-3V6h6v13.4C46,22.8,45,24.8,41.3,26.6z" />
<path d="M65,18.4c0,1.1,0.8,1.3,1.4,1.3c0.5,0,2-0.2,2.6-0.4v2.1c-0.9,0.3-2.5,0.5-3.7,0.5c-1.5,0-3.2-0.5-3.2-3.1V12H60v-2h2.1V7.1 H65V10h4v2h-4V18.4z" />
<path d="M71,10h3v1.3c1.1-0.8,1.9-1.3,3.3-1.3c2.5,0,4.5,1.8,4.5,5.6s-2.2,6.3-5.8,6.3c-0.9,0-1.3-0.1-2-0.3V28h-3V10z M76.5,12.3 c-0.8,0-1.6,0.4-2.5,1.2v5.9c0.6,0.1,0.9,0.2,1.8,0.2c2,0,3.2-1.3,3.2-3.9C79,13.4,78.1,12.3,76.5,12.3z" />
<path d="M93,22h-3v-1.5c-0.9,0.7-1.9,1.5-3.5,1.5c-1.5,0-3.1-1.1-3.1-3.2c0-2.9,2.5-3.4,4.2-3.7l2.4-0.3v-0.3c0-1.5-0.5-2.3-2-2.3 c-0.7,0-2.3,0.5-3.7,1.1L84,11c1.2-0.4,3-1,4.4-1c2.7,0,4.6,1.4,4.6,4.7L93,22z M90,16.4l-2.2,0.4c-0.7,0.1-1.4,0.5-1.4,1.6 c0,0.9,0.5,1.4,1.3,1.4s1.5-0.5,2.3-1V16.4z" />
<path d="M104.5,21.3c-1.1,0.4-2.2,0.6-3.5,0.6c-4.2,0-5.9-2.4-5.9-5.9c0-3.7,2.3-6,6.1-6c1.4,0,2.3,0.2,3.2,0.5V13 c-0.8-0.3-2-0.6-3.2-0.6c-1.7,0-3.2,0.9-3.2,3.6c0,2.9,1.5,3.8,3.3,3.8c0.9,0,1.9-0.2,3.2-0.7V21.3z" />
<path d="M110,15.2c0.2-0.3,0.2-0.8,3.8-5.2h3.7l-4.6,5.7l5,6.3h-3.7l-4.2-5.8V22h-3V6h3V15.2z" />
<path d="M58.5,21.3c-1.5,0.5-2.7,0.6-4.2,0.6c-3.6,0-5.8-1.8-5.8-6c0-3.1,1.9-5.9,5.5-5.9s4.9,2.5,4.9,4.9c0,0.8,0,1.5-0.1,2h-7.3 c0.1,2.5,1.5,2.8,3.6,2.8c1.1,0,2.2-0.3,3.4-0.7C58.5,19,58.5,21.3,58.5,21.3z M56,15c0-1.4-0.5-2.9-2-2.9c-1.4,0-2.3,1.3-2.4,2.9 C51.6,15,56,15,56,15z" />
';
return sprintf(
'<svg id="jetpack-logo__icon" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 %1$s 32">%2$s</svg>',
( true === $logotype ? '118' : '32' ),
( true === $logotype ? $logo . $text : $logo )
);
}
}

View File

@@ -0,0 +1,605 @@
<?php
use Automattic\Jetpack\Constants;
class Jetpack_Options {
/**
* An array that maps a grouped option type to an option name.
*
* @var array
*/
private static $grouped_options = array(
'compact' => 'jetpack_options',
'private' => 'jetpack_private_options',
);
/**
* Returns an array of option names for a given type.
*
* @param string $type The type of option to return. Defaults to 'compact'.
*
* @return array
*/
public static function get_option_names( $type = 'compact' ) {
switch ( $type ) {
case 'non-compact':
case 'non_compact':
return array(
'activated',
'active_modules',
'allowed_xsite_search_ids', // (array) Array of WP.com blog ids that are allowed to search the content of this site
'available_modules',
'do_activate',
'edit_links_calypso_redirect', // (bool) Whether post/page edit links on front end should point to Calypso.
'log',
'slideshow_background_color',
'widget_twitter',
'wpcc_options',
'relatedposts',
'file_data',
'autoupdate_plugins', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated
'autoupdate_plugins_translations', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated translation files.
'autoupdate_themes', // (array) An array of theme ids ( eg. twentyfourteen ) that should be autoupdated
'autoupdate_themes_translations', // (array) An array of theme ids ( eg. twentyfourteen ) that should autoupdated translation files.
'autoupdate_core', // (bool) Whether or not to autoupdate core
'autoupdate_translations', // (bool) Whether or not to autoupdate all translations
'json_api_full_management', // (bool) Allow full management (eg. Activate, Upgrade plugins) of the site via the JSON API.
'sync_non_public_post_stati', // (bool) Allow synchronisation of posts and pages with non-public status.
'site_icon_url', // (string) url to the full site icon
'site_icon_id', // (int) Attachment id of the site icon file
'dismissed_manage_banner', // (bool) Dismiss Jetpack manage banner allows the user to dismiss the banner permanently
'unique_connection', // (array) A flag to determine a unique connection to wordpress.com two values "connected" and "disconnected" with values for how many times each has occured
'protect_whitelist', // (array) IP Address for the Protect module to ignore
'sync_error_idc', // (bool|array) false or array containing the site's home and siteurl at time of IDC error
'safe_mode_confirmed', // (bool) True if someone confirms that this site was correctly put into safe mode automatically after an identity crisis is discovered.
'migrate_for_idc', // (bool) True if someone confirms that this site should migrate stats and subscribers from its previous URL
'dismissed_connection_banner', // (bool) True if the connection banner has been dismissed
'ab_connect_banner_green_bar', // (int) Version displayed of the A/B test for the green bar at the top of the connect banner.
'onboarding', // (string) Auth token to be used in the onboarding connection flow
'tos_agreed', // (bool) Whether or not the TOS for connection has been agreed upon.
'static_asset_cdn_files', // (array) An nested array of files that we can swap out for cdn versions.
'mapbox_api_key', // (string) Mapbox API Key, for use with Map block.
'mailchimp', // (string) Mailchimp keyring data, for mailchimp block.
);
case 'private':
return array(
'blog_token', // (string) The Client Secret/Blog Token of this site.
'user_token', // (string) The User Token of this site. (deprecated)
'user_tokens', // (array) User Tokens for each user of this site who has connected to jetpack.wordpress.com.
);
case 'network':
return array(
'onboarding', // (string) Auth token to be used in the onboarding connection flow
'file_data', // (array) List of absolute paths to all Jetpack modules
);
}
return array(
'id', // (int) The Client ID/WP.com Blog ID of this site.
'publicize_connections', // (array) An array of Publicize connections from WordPress.com
'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time
'old_version', // (string) Used to determine which modules are the most recently added. previous_version:time
'fallback_no_verify_ssl_certs', // (int) Flag for determining if this host must skip SSL Certificate verification due to misconfigured SSL.
'time_diff', // (int) Offset between Jetpack server's clocks and this server's clocks. Jetpack Server Time = time() + (int) Jetpack_Options::get_option( 'time_diff' )
'public', // (int|bool) If we think this site is public or not (1, 0), false if we haven't yet tried to figure it out.
'videopress', // (array) VideoPress options array.
'is_network_site', // (int|bool) If we think this site is a network or a single blog (1, 0), false if we haven't yet tried to figue it out.
'social_links', // (array) The specified links for each social networking site.
'identity_crisis_whitelist', // (array) An array of options, each having an array of the values whitelisted for it.
'gplus_authors', // (array) The Google+ authorship information for connected users.
'last_heartbeat', // (int) The timestamp of the last heartbeat that fired.
'hide_jitm', // (array) A list of just in time messages that we should not show because they have been dismissed by the user
'custom_css_4.7_migration', // (bool) Whether Custom CSS has scanned for and migrated any legacy CSS CPT entries to the new Core format.
'image_widget_migration', // (bool) Whether any legacy Image Widgets have been converted to the new Core widget
'gallery_widget_migration', // (bool) Whether any legacy Gallery Widgets have been converted to the new Core widget
'sso_first_login', // (bool) Is this the first time the user logins via SSO.
'dismissed_hints', // (array) Part of Plugin Search Hints. List of cards that have been dismissed.
'first_admin_view', // (bool) Set to true the first time the user views the admin. Usually after the initial connection.
);
}
/**
* Is the option name valid?
*
* @param string $name The name of the option
* @param string|null $group The name of the group that the option is in. Default to null, which will search non_compact.
*
* @return bool Is the option name valid?
*/
public static function is_valid( $name, $group = null ) {
if ( is_array( $name ) ) {
$compact_names = array();
foreach ( array_keys( self::$grouped_options ) as $_group ) {
$compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
}
$result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
return empty( $result );
}
if ( is_null( $group ) || 'non_compact' === $group ) {
if ( in_array( $name, self::get_option_names( $group ) ) ) {
return true;
}
}
foreach ( array_keys( self::$grouped_options ) as $_group ) {
if ( is_null( $group ) || $group === $_group ) {
if ( in_array( $name, self::get_option_names( $_group ) ) ) {
return true;
}
}
}
return false;
}
/**
* Checks if an option must be saved for the whole network in WP Multisite
*
* @param string $option_name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
*
* @return bool
*/
public static function is_network_option( $option_name ) {
if ( ! is_multisite() ) {
return false;
}
return in_array( $option_name, self::get_option_names( 'network' ) );
}
/**
* Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
*
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
* @param mixed $default (optional)
*
* @return mixed
*/
public static function get_option( $name, $default = false ) {
if ( self::is_valid( $name, 'non_compact' ) ) {
if ( self::is_network_option( $name ) ) {
return get_site_option( "jetpack_$name", $default );
}
return get_option( "jetpack_$name", $default );
}
foreach ( array_keys( self::$grouped_options ) as $group ) {
if ( self::is_valid( $name, $group ) ) {
return self::get_grouped_option( $group, $name, $default );
}
}
trigger_error( sprintf( 'Invalid Jetpack option name: %s', $name ), E_USER_WARNING );
return $default;
}
/**
* Returns the requested option, and ensures it's autoloaded in the future.
* This does _not_ adjust the prefix in any way (does not prefix jetpack_%)
*
* @param string $name Option name
* @param mixed $default (optional)
*
* @return mixed
*/
public static function get_option_and_ensure_autoload( $name, $default ) {
// In this function the name is not adjusted by prefixing jetpack_
// so if it has already prefixed, we'll replace it and then
// check if the option name is a network option or not
$jetpack_name = preg_replace( '/^jetpack_/', '', $name, 1 );
$is_network_option = self::is_network_option( $jetpack_name );
$value = $is_network_option ? get_site_option( $name ) : get_option( $name );
if ( false === $value && false !== $default ) {
if ( $is_network_option ) {
add_site_option( $name, $default );
} else {
add_option( $name, $default );
}
$value = $default;
}
return $value;
}
private static function update_grouped_option( $group, $name, $value ) {
$options = get_option( self::$grouped_options[ $group ] );
if ( ! is_array( $options ) ) {
$options = array();
}
$options[ $name ] = $value;
return update_option( self::$grouped_options[ $group ], $options );
}
/**
* Updates the single given option. Updates jetpack_options or jetpack_$name as appropriate.
*
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
* @param mixed $value Option value
* @param string $autoload If not compact option, allows specifying whether to autoload or not.
*
* @return bool Was the option successfully updated?
*/
public static function update_option( $name, $value, $autoload = null ) {
/**
* Fires before Jetpack updates a specific option.
*
* @since 3.0.0
*
* @param str $name The name of the option being updated.
* @param mixed $value The new value of the option.
*/
do_action( 'pre_update_jetpack_option_' . $name, $name, $value );
if ( self::is_valid( $name, 'non_compact' ) ) {
if ( self::is_network_option( $name ) ) {
return update_site_option( "jetpack_$name", $value );
}
return update_option( "jetpack_$name", $value, $autoload );
}
foreach ( array_keys( self::$grouped_options ) as $group ) {
if ( self::is_valid( $name, $group ) ) {
return self::update_grouped_option( $group, $name, $value );
}
}
trigger_error( sprintf( 'Invalid Jetpack option name: %s', $name ), E_USER_WARNING );
return false;
}
/**
* Updates the multiple given options. Updates jetpack_options and/or jetpack_$name as appropriate.
*
* @param array $array array( option name => option value, ... )
*/
public static function update_options( $array ) {
$names = array_keys( $array );
foreach ( array_diff( $names, self::get_option_names(), self::get_option_names( 'non_compact' ), self::get_option_names( 'private' ) ) as $unknown_name ) {
trigger_error( sprintf( 'Invalid Jetpack option name: %s', $unknown_name ), E_USER_WARNING );
unset( $array[ $unknown_name ] );
}
foreach ( $names as $name ) {
self::update_option( $name, $array[ $name ] );
}
}
/**
* Deletes the given option. May be passed multiple option names as an array.
* Updates jetpack_options and/or deletes jetpack_$name as appropriate.
*
* @param string|array $names Option names. They must come _without_ `jetpack_%` prefix. The method will prefix the option names.
*
* @return bool Was the option successfully deleted?
*/
public static function delete_option( $names ) {
$result = true;
$names = (array) $names;
if ( ! self::is_valid( $names ) ) {
trigger_error( sprintf( 'Invalid Jetpack option names: %s', print_r( $names, 1 ) ), E_USER_WARNING );
return false;
}
foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
if ( self::is_network_option( $name ) ) {
$result = delete_site_option( "jetpack_$name" );
} else {
$result = delete_option( "jetpack_$name" );
}
}
foreach ( array_keys( self::$grouped_options ) as $group ) {
if ( ! self::delete_grouped_option( $group, $names ) ) {
$result = false;
}
}
return $result;
}
private static function get_grouped_option( $group, $name, $default ) {
$options = get_option( self::$grouped_options[ $group ] );
if ( is_array( $options ) && isset( $options[ $name ] ) ) {
return $options[ $name ];
}
return $default;
}
private static function delete_grouped_option( $group, $names ) {
$options = get_option( self::$grouped_options[ $group ], array() );
$to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
if ( $to_delete ) {
foreach ( $to_delete as $name ) {
unset( $options[ $name ] );
}
return update_option( self::$grouped_options[ $group ], $options );
}
return true;
}
// Raw option methods allow Jetpack to get / update / delete options via direct DB queries, including options
// that are not created by the Jetpack plugin. This is helpful only in rare cases when we need to bypass
// cache and filters.
/**
* Deletes an option via $wpdb query.
*
* @param string $name Option name.
*
* @return bool Is the option deleted?
*/
static function delete_raw_option( $name ) {
if ( self::bypass_raw_option( $name ) ) {
return delete_option( $name );
}
global $wpdb;
$result = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name = %s", $name ) );
return $result;
}
/**
* Updates an option via $wpdb query.
*
* @param string $name Option name.
* @param mixed $value Option value.
* @param bool $autoload Specifying whether to autoload or not.
*
* @return bool Is the option updated?
*/
static function update_raw_option( $name, $value, $autoload = false ) {
if ( self::bypass_raw_option( $name ) ) {
return update_option( $name, $value, $autoload );
}
global $wpdb;
$autoload_value = $autoload ? 'yes' : 'no';
$old_value = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
$name
)
);
if ( $old_value === $value ) {
return false;
}
$serialized_value = maybe_serialize( $value );
// below we used "insert ignore" to at least suppress the resulting error
$updated_num = $wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
$serialized_value,
$name
)
);
// Try inserting the option if the value doesn't exits.
if ( ! $updated_num ) {
$updated_num = $wpdb->query(
$wpdb->prepare(
"INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, '$autoload_value' )",
$name,
$serialized_value
)
);
}
return (bool) $updated_num;
}
/**
* Gets an option via $wpdb query.
*
* @since 5.4.0
*
* @param string $name Option name.
* @param mixed $default Default option value if option is not found.
*
* @return mixed Option value, or null if option is not found and default is not specified.
*/
static function get_raw_option( $name, $default = null ) {
if ( self::bypass_raw_option( $name ) ) {
return get_option( $name, $default );
}
global $wpdb;
$value = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
$name
)
);
$value = maybe_unserialize( $value );
if ( $value === null && $default !== null ) {
return $default;
}
return $value;
}
/**
* This function checks for a constant that, if present, will disable direct DB queries Jetpack uses to manage certain options and force Jetpack to always use Options API instead.
* Options can be selectively managed via a blacklist by filtering option names via the jetpack_disabled_raw_option filter.
*
* @param $name Option name
*
* @return bool
*/
static function bypass_raw_option( $name ) {
if ( Constants::get_constant( 'JETPACK_DISABLE_RAW_OPTIONS' ) ) {
return true;
}
/**
* Allows to disable particular raw options.
*
* @since 5.5.0
*
* @param array $disabled_raw_options An array of option names that you can selectively blacklist from being managed via direct database queries.
*/
$disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
return isset( $disabled_raw_options[ $name ] );
}
/**
* Gets all known options that are used by Jetpack and managed by Jetpack_Options.
*
* @since 5.4.0
*
* @param boolean $strip_unsafe_options If true, and by default, will strip out options necessary for the connection to WordPress.com.
* @return array An array of all options managed via the Jetpack_Options class.
*/
static function get_all_jetpack_options( $strip_unsafe_options = true ) {
$jetpack_options = self::get_option_names();
$jetpack_options_non_compat = self::get_option_names( 'non_compact' );
$jetpack_options_private = self::get_option_names( 'private' );
$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
if ( $strip_unsafe_options ) {
// Flag some Jetpack options as unsafe
$unsafe_options = array(
'id', // (int) The Client ID/WP.com Blog ID of this site.
'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time
// non_compact
'activated',
// private
'register',
'blog_token', // (string) The Client Secret/Blog Token of this site.
'user_token', // (string) The User Token of this site. (deprecated)
'user_tokens',
);
// Remove the unsafe Jetpack options
foreach ( $unsafe_options as $unsafe_option ) {
if ( false !== ( $key = array_search( $unsafe_option, $all_jp_options ) ) ) {
unset( $all_jp_options[ $key ] );
}
}
}
return $all_jp_options;
}
/**
* Get all options that are not managed by the Jetpack_Options class that are used by Jetpack.
*
* @since 5.4.0
*
* @return array
*/
static function get_all_wp_options() {
// A manual build of the wp options
return array(
'sharing-options',
'disabled_likes',
'disabled_reblogs',
'jetpack_comments_likes_enabled',
'wp_mobile_excerpt',
'wp_mobile_featured_images',
'wp_mobile_app_promos',
'stats_options',
'stats_dashboard_widget',
'safecss_preview_rev',
'safecss_rev',
'safecss_revision_migrated',
'nova_menu_order',
'jetpack_portfolio',
'jetpack_portfolio_posts_per_page',
'jetpack_testimonial',
'jetpack_testimonial_posts_per_page',
'wp_mobile_custom_css',
'sharedaddy_disable_resources',
'sharing-options',
'sharing-services',
'site_icon_temp_data',
'featured-content',
'site_logo',
'jetpack_dismissed_notices',
'jetpack-twitter-cards-site-tag',
'jetpack-sitemap-state',
'jetpack_sitemap_post_types',
'jetpack_sitemap_location',
'jetpack_protect_key',
'jetpack_protect_blocked_attempts',
'jetpack_protect_activating',
'jetpack_connection_banner_ab',
'jetpack_active_plan',
'jetpack_activation_source',
'jetpack_sso_match_by_email',
'jetpack_sso_require_two_step',
'jetpack_sso_remove_login_form',
'jetpack_last_connect_url_check',
'jpo_business_address',
'jpo_site_type',
'jpo_homepage_format',
'jpo_contact_page',
'jetpack_excluded_extensions',
);
}
/**
* Gets all options that can be safely reset by CLI.
*
* @since 5.4.0
*
* @return array array Associative array containing jp_options which are managed by the Jetpack_Options class and wp_options which are not.
*/
static function get_options_for_reset() {
$all_jp_options = self::get_all_jetpack_options();
$wp_options = self::get_all_wp_options();
$options = array(
'jp_options' => $all_jp_options,
'wp_options' => $wp_options,
);
return $options;
}
/**
* Delete all known options
*
* @since 5.4.0
*
* @return void
*/
static function delete_all_known_options() {
// Delete all compact options
foreach ( (array) self::$grouped_options as $option_name ) {
delete_option( $option_name );
}
// Delete all non-compact Jetpack options
foreach ( (array) self::get_option_names( 'non-compact' ) as $option_name ) {
self::delete_option( $option_name );
}
// Delete all options that can be reset via CLI, that aren't Jetpack options
foreach ( (array) self::get_all_wp_options() as $option_name ) {
delete_option( $option_name );
}
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* A user roles class for Jetpack.
*
* @package jetpack-roles
*/
namespace Automattic\Jetpack;
/**
* Class Automattic\Jetpack\Roles
*
* Contains utilities for translating user roles to capabilities and vice versa.
*/
class Roles {
/**
* Map of roles we care about, and their corresponding minimum capabilities.
*
* @access protected
*
* @var array
*/
protected $capability_translations = array(
'administrator' => 'manage_options',
'editor' => 'edit_others_posts',
'author' => 'publish_posts',
'contributor' => 'edit_posts',
'subscriber' => 'read',
);
/**
* Get the role of the current user.
*
* @access public
*
* @return string|boolean Current user's role, false if not enough capabilities for any of the roles.
*/
public function translate_current_user_to_role() {
foreach ( $this->capability_translations as $role => $cap ) {
if ( current_user_can( $role ) || current_user_can( $cap ) ) {
return $role;
}
}
return false;
}
/**
* Get the role of a particular user.
*
* @access public
*
* @param \WP_User $user User object.
* @return string|boolean User's role, false if not enough capabilities for any of the roles.
*/
public function translate_user_to_role( $user ) {
foreach ( $this->capability_translations as $role => $cap ) {
if ( user_can( $user, $role ) || user_can( $user, $cap ) ) {
return $role;
}
}
return false;
}
/**
* Get the minimum capability for a role.
*
* @access public
*
* @param string $role Role name.
* @return string|boolean Capability, false if role isn't mapped to any capabilities.
*/
public function translate_role_to_cap( $role ) {
if ( ! isset( $this->capability_translations[ $role ] ) ) {
return false;
}
return $this->capability_translations[ $role ];
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* A status class for Jetpack.
*
* @package jetpack-status
*/
namespace Automattic\Jetpack;
/**
* Class Automattic\Jetpack\Status
*
* Used to retrieve information about the current status of Jetpack and the site overall.
*/
class Status {
/**
* Is Jetpack in development (offline) mode?
*
* @return bool Whether Jetpack's development mode is active.
*/
public function is_development_mode() {
$development_mode = false;
$site_url = site_url();
if ( defined( '\\JETPACK_DEV_DEBUG' ) ) {
$development_mode = constant( '\\JETPACK_DEV_DEBUG' );
} elseif ( $site_url ) {
$development_mode = false === strpos( $site_url, '.' );
}
/**
* Filters Jetpack's development mode.
*
* @see https://jetpack.com/support/development-mode/
*
* @since 2.2.1
*
* @param bool $development_mode Is Jetpack's development mode active.
*/
$development_mode = (bool) apply_filters( 'jetpack_development_mode', $development_mode );
return $development_mode;
}
/**
* Whether this is a system with a multiple networks.
* Implemented since there is no core is_multi_network function.
* Right now there is no way to tell which network is the dominant network on the system.
*
* @return boolean
*/
public function is_multi_network() {
global $wpdb;
// If we don't have a multi site setup no need to do any more.
if ( ! is_multisite() ) {
return false;
}
$num_sites = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->site}" );
if ( $num_sites > 1 ) {
return true;
}
return false;
}
/**
* Whether the current site is single user site.
*
* @return bool
*/
public function is_single_user_site() {
global $wpdb;
$some_users = get_transient( 'jetpack_is_single_user' );
if ( false === $some_users ) {
$some_users = $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}capabilities' LIMIT 2) AS someusers" );
set_transient( 'jetpack_is_single_user', (int) $some_users, 12 * HOUR_IN_SECONDS );
}
return 1 === (int) $some_users;
}
}

View File

@@ -0,0 +1,753 @@
<?php
/**
* A class that defines syncable actions for Jetpack.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Status;
/**
* The role of this class is to hook the Sync subsystem into WordPress - when to listen for actions,
* when to send, when to perform a full sync, etc.
*
* It also binds the action to send data to WPCOM to Jetpack's XMLRPC client object.
*/
class Actions {
/**
* A variable to hold a sync sender object.
*
* @access public
* @static
*
* @var Automattic\Jetpack\Sync\Sender
*/
public static $sender = null;
/**
* A variable to hold a sync listener object.
*
* @access public
* @static
*
* @var Automattic\Jetpack\Sync\Listener
*/
public static $listener = null;
/**
* Name of the sync cron schedule.
*
* @access public
*
* @var string
*/
const DEFAULT_SYNC_CRON_INTERVAL_NAME = 'jetpack_sync_interval';
/**
* Interval between the last and the next sync cron action.
*
* @access public
*
* @var int
*/
const DEFAULT_SYNC_CRON_INTERVAL_VALUE = 300; // 5 * MINUTE_IN_SECONDS;
/**
* Initialize Sync for cron jobs, set up listeners for WordPress Actions,
* and set up a shut-down action for sending actions to WordPress.com
*
* @access public
* @static
*/
public static function init() {
// Everything below this point should only happen if we're a valid sync site.
if ( ! self::sync_allowed() ) {
return;
}
if ( self::sync_via_cron_allowed() ) {
self::init_sync_cron_jobs();
} elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
self::clear_sync_cron_jobs();
}
// When importing via cron, do not sync.
add_action( 'wp_cron_importer_hook', array( __CLASS__, 'set_is_importing_true' ), 1 );
// Sync connected user role changes to WordPress.com.
Users::init();
// Publicize filter to prevent publicizing blacklisted post types.
add_filter( 'publicize_should_publicize_published_post', array( __CLASS__, 'prevent_publicize_blacklisted_posts' ), 10, 2 );
/**
* Fires on every request before default loading sync listener code.
* Return false to not load sync listener code that monitors common
* WP actions to be serialized.
*
* By default this returns true for cron jobs, non-GET-requests, or requests where the
* user is logged-in.
*
* @since 4.2.0
*
* @param bool should we load sync listener code for this request
*/
if ( apply_filters( 'jetpack_sync_listener_should_load', true ) ) {
self::initialize_listener();
}
add_action( 'init', array( __CLASS__, 'add_sender_shutdown' ), 90 );
}
/**
* Prepares sync to send actions on shutdown for the current request.
*
* @access public
* @static
*/
public static function add_sender_shutdown() {
/**
* Fires on every request before default loading sync sender code.
* Return false to not load sync sender code that serializes pending
* data and sends it to WPCOM for processing.
*
* By default this returns true for cron jobs, POST requests, admin requests, or requests
* by users who can manage_options.
*
* @since 4.2.0
*
* @param bool should we load sync sender code for this request
*/
if ( apply_filters(
'jetpack_sync_sender_should_load',
self::should_initialize_sender()
) ) {
self::initialize_sender();
add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
add_action( 'shutdown', array( self::$sender, 'do_full_sync' ) );
}
}
/**
* Decides if the sender should run on shutdown for this request.
*
* @access public
* @static
*
* @return bool
*/
public static function should_initialize_sender() {
if ( Constants::is_true( 'DOING_CRON' ) ) {
return self::sync_via_cron_allowed();
}
if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) {
return true;
}
if ( current_user_can( 'manage_options' ) ) {
return true;
}
if ( is_admin() ) {
return true;
}
if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
return true;
}
if ( Constants::get_constant( 'WP_CLI' ) ) {
return true;
}
return false;
}
/**
* Decides if sync should run at all during this request.
*
* @access public
* @static
*
* @return bool
*/
public static function sync_allowed() {
if ( defined( 'PHPUNIT_JETPACK_TESTSUITE' ) ) {
return true;
}
if ( ! Settings::is_sync_enabled() ) {
return false;
}
$status = new Status();
if ( $status->is_development_mode() ) {
return false;
}
if ( \Jetpack::is_staging_site() ) {
return false;
}
$connection = new Jetpack_Connection();
if ( ! $connection->is_active() ) {
if ( ! doing_action( 'jetpack_user_authorized' ) ) {
return false;
}
}
return true;
}
/**
* Determines if syncing during a cron job is allowed.
*
* @access public
* @static
*
* @return bool|int
*/
public static function sync_via_cron_allowed() {
return ( Settings::get_setting( 'sync_via_cron' ) );
}
/**
* Decides if the given post should be Publicized based on its type.
*
* @access public
* @static
*
* @param bool $should_publicize Publicize status prior to this filter running.
* @param \WP_Post $post The post to test for Publicizability.
* @return bool
*/
public static function prevent_publicize_blacklisted_posts( $should_publicize, $post ) {
if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
return false;
}
return $should_publicize;
}
/**
* Set an importing flag to `true` in sync settings.
*
* @access public
* @static
*/
public static function set_is_importing_true() {
Settings::set_importing( true );
}
/**
* Sends data to WordPress.com via an XMLRPC request.
*
* @access public
* @static
*
* @param object $data Data relating to a sync action.
* @param string $codec_name The name of the codec that encodes the data.
* @param float $sent_timestamp Current server time so we can compensate for clock differences.
* @param string $queue_id The queue the action belongs to, sync or full_sync.
* @param float $checkout_duration Time spent retrieving queue items from the DB.
* @param float $preprocess_duration Time spent converting queue items into data to send.
* @return Jetpack_Error|mixed|WP_Error The result of the sending request.
*/
public static function send_data( $data, $codec_name, $sent_timestamp, $queue_id, $checkout_duration, $preprocess_duration ) {
\Jetpack::load_xml_rpc_client();
$query_args = array(
'sync' => '1', // Add an extra parameter to the URL so we can tell it's a sync action.
'codec' => $codec_name,
'timestamp' => $sent_timestamp,
'queue' => $queue_id,
'home' => Functions::home_url(), // Send home url option to check for Identity Crisis server-side.
'siteurl' => Functions::site_url(), // Send siteurl option to check for Identity Crisis server-side.
'cd' => sprintf( '%.4f', $checkout_duration ),
'pd' => sprintf( '%.4f', $preprocess_duration ),
);
// Has the site opted in to IDC mitigation?
if ( \Jetpack::sync_idc_optin() ) {
$query_args['idc'] = true;
}
if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
$query_args['migrate_for_idc'] = true;
}
$query_args['timeout'] = Settings::is_doing_cron() ? 30 : 15;
/**
* Filters query parameters appended to the Sync request URL sent to WordPress.com.
*
* @since 4.7.0
*
* @param array $query_args associative array of query parameters.
*/
$query_args = apply_filters( 'jetpack_sync_send_data_query_args', $query_args );
$url = add_query_arg( $query_args, \Jetpack::xmlrpc_api_url() );
$rpc = new \Jetpack_IXR_Client(
array(
'url' => $url,
'user_id' => JETPACK_MASTER_USER,
'timeout' => $query_args['timeout'],
)
);
$result = $rpc->query( 'jetpack.syncActions', $data );
if ( ! $result ) {
return $rpc->get_jetpack_error();
}
$response = $rpc->getResponse();
// Check if WordPress.com IDC mitigation blocked the sync request.
if ( is_array( $response ) && isset( $response['error_code'] ) ) {
$error_code = $response['error_code'];
$allowed_idc_error_codes = array(
'jetpack_url_mismatch',
'jetpack_home_url_mismatch',
'jetpack_site_url_mismatch',
);
if ( in_array( $error_code, $allowed_idc_error_codes, true ) ) {
\Jetpack_Options::update_option(
'sync_error_idc',
\Jetpack::get_sync_error_idc_option( $response )
);
}
return new \WP_Error(
'sync_error_idc',
esc_html__( 'Sync has been blocked from WordPress.com because it would cause an identity crisis', 'jetpack' )
);
}
return $response;
}
/**
* Kicks off the initial sync.
*
* @access public
* @static
*
* @return bool|null False if sync is not allowed.
*/
public static function do_initial_sync() {
// Lets not sync if we are not suppose to.
if ( ! self::sync_allowed() ) {
return false;
}
$initial_sync_config = array(
'options' => true,
'functions' => true,
'constants' => true,
'users' => array( get_current_user_id() ),
);
if ( is_multisite() ) {
$initial_sync_config['network_options'] = true;
}
self::do_full_sync( $initial_sync_config );
}
/**
* Kicks off a full sync.
*
* @access public
* @static
*
* @param array $modules The sync modules should be included in this full sync. All will be included if null.
* @return bool True if full sync was successfully started.
*/
public static function do_full_sync( $modules = null ) {
if ( ! self::sync_allowed() ) {
return false;
}
$full_sync_module = Modules::get_module( 'full-sync' );
if ( ! $full_sync_module ) {
return false;
}
self::initialize_listener();
$full_sync_module->start( $modules );
return true;
}
/**
* Adds a cron schedule for regular syncing via cron, unless the schedule already exists.
*
* @access public
* @static
*
* @param array $schedules The list of WordPress cron schedules prior to this filter.
* @return array A list of WordPress cron schedules with the Jetpack sync interval added.
*/
public static function jetpack_cron_schedule( $schedules ) {
if ( ! isset( $schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] ) ) {
$minutes = intval( self::DEFAULT_SYNC_CRON_INTERVAL_VALUE / 60 );
$display = ( 1 === $minutes ) ?
__( 'Every minute', 'jetpack' ) :
/* translators: %d is an integer indicating the number of minutes. */
sprintf( __( 'Every %d minutes', 'jetpack' ), $minutes );
$schedules[ self::DEFAULT_SYNC_CRON_INTERVAL_NAME ] = array(
'interval' => self::DEFAULT_SYNC_CRON_INTERVAL_VALUE,
'display' => $display,
);
}
return $schedules;
}
/**
* Starts an incremental sync via cron.
*
* @access public
* @static
*/
public static function do_cron_sync() {
self::do_cron_sync_by_type( 'sync' );
}
/**
* Starts a full sync via cron.
*
* @access public
* @static
*/
public static function do_cron_full_sync() {
self::do_cron_sync_by_type( 'full_sync' );
}
/**
* Try to send actions until we run out of things to send,
* or have to wait more than 15s before sending again,
* or we hit a lock or some other sending issue
*
* @access public
* @static
*
* @param string $type Sync type. Can be `sync` or `full_sync`.
*/
public static function do_cron_sync_by_type( $type ) {
if ( ! self::sync_allowed() || ( 'sync' !== $type && 'full_sync' !== $type ) ) {
return;
}
self::initialize_sender();
$time_limit = Settings::get_setting( 'cron_sync_time_limit' );
$start_time = time();
do {
$next_sync_time = self::$sender->get_next_sync_time( $type );
if ( $next_sync_time ) {
$delay = $next_sync_time - time() + 1;
if ( $delay > 15 ) {
break;
} elseif ( $delay > 0 ) {
sleep( $delay );
}
}
$result = 'full_sync' === $type ? self::$sender->do_full_sync() : self::$sender->do_sync();
} while ( $result && ! is_wp_error( $result ) && ( $start_time + $time_limit ) > time() );
}
/**
* Initialize the sync listener.
*
* @access public
* @static
*/
public static function initialize_listener() {
self::$listener = Listener::get_instance();
}
/**
* Initializes the sync sender.
*
* @access public
* @static
*/
public static function initialize_sender() {
self::$sender = Sender::get_instance();
add_filter( 'jetpack_sync_send_data', array( __CLASS__, 'send_data' ), 10, 6 );
}
/**
* Initializes sync for WooCommerce.
*
* @access public
* @static
*/
public static function initialize_woocommerce() {
if ( false === class_exists( 'WooCommerce' ) ) {
return;
}
add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
}
/**
* Adds Woo's sync modules to existing modules for sending.
*
* @access public
* @static
*
* @param array $sync_modules The list of sync modules declared prior to this filter.
* @return array A list of sync modules that now includes Woo's modules.
*/
public static function add_woocommerce_sync_module( $sync_modules ) {
$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce';
return $sync_modules;
}
/**
* Initializes sync for WP Super Cache.
*
* @access public
* @static
*/
public static function initialize_wp_super_cache() {
if ( false === function_exists( 'wp_cache_is_enabled' ) ) {
return;
}
add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_wp_super_cache_sync_module' ) );
}
/**
* Adds WP Super Cache's sync modules to existing modules for sending.
*
* @access public
* @static
*
* @param array $sync_modules The list of sync modules declared prior to this filer.
* @return array A list of sync modules that now includes WP Super Cache's modules.
*/
public static function add_wp_super_cache_sync_module( $sync_modules ) {
$sync_modules[] = 'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache';
return $sync_modules;
}
/**
* Sanitizes the name of sync's cron schedule.
*
* @access public
* @static
*
* @param string $schedule The name of a WordPress cron schedule.
* @return string The sanitized name of sync's cron schedule.
*/
public static function sanitize_filtered_sync_cron_schedule( $schedule ) {
$schedule = sanitize_key( $schedule );
$schedules = wp_get_schedules();
// Make sure that the schedule has actually been registered using the `cron_intervals` filter.
if ( isset( $schedules[ $schedule ] ) ) {
return $schedule;
}
return self::DEFAULT_SYNC_CRON_INTERVAL_NAME;
}
/**
* Allows offsetting of start times for sync cron jobs.
*
* @access public
* @static
*
* @param string $schedule The name of a cron schedule.
* @param string $hook The hook that this method is responding to.
* @return int The offset for the sync cron schedule.
*/
public static function get_start_time_offset( $schedule = '', $hook = '' ) {
$start_time_offset = is_multisite()
? wp_rand( 0, ( 2 * self::DEFAULT_SYNC_CRON_INTERVAL_VALUE ) )
: 0;
/**
* Allows overriding the offset that the sync cron jobs will first run. This can be useful when scheduling
* cron jobs across multiple sites in a network.
*
* @since 4.5.0
*
* @param int $start_time_offset
* @param string $hook
* @param string $schedule
*/
return intval(
apply_filters(
'jetpack_sync_cron_start_time_offset',
$start_time_offset,
$hook,
$schedule
)
);
}
/**
* Decides if a sync cron should be scheduled.
*
* @access public
* @static
*
* @param string $schedule The name of a cron schedule.
* @param string $hook The hook that this method is responding to.
*/
public static function maybe_schedule_sync_cron( $schedule, $hook ) {
if ( ! $hook ) {
return;
}
$schedule = self::sanitize_filtered_sync_cron_schedule( $schedule );
$start_time = time() + self::get_start_time_offset( $schedule, $hook );
if ( ! wp_next_scheduled( $hook ) ) {
// Schedule a job to send pending queue items once a minute.
wp_schedule_event( $start_time, $schedule, $hook );
} elseif ( wp_get_schedule( $hook ) !== $schedule ) {
// If the schedule has changed, update the schedule.
wp_clear_scheduled_hook( $hook );
wp_schedule_event( $start_time, $schedule, $hook );
}
}
/**
* Clears Jetpack sync cron jobs.
*
* @access public
* @static
*/
public static function clear_sync_cron_jobs() {
wp_clear_scheduled_hook( 'jetpack_sync_cron' );
wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
}
/**
* Initializes Jetpack sync cron jobs.
*
* @access public
* @static
*/
public static function init_sync_cron_jobs() {
add_filter( 'cron_schedules', array( __CLASS__, 'jetpack_cron_schedule' ) );
add_action( 'jetpack_sync_cron', array( __CLASS__, 'do_cron_sync' ) );
add_action( 'jetpack_sync_full_cron', array( __CLASS__, 'do_cron_full_sync' ) );
/**
* Allows overriding of the default incremental sync cron schedule which defaults to once every 5 minutes.
*
* @since 4.3.2
*
* @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
*/
$incremental_sync_cron_schedule = apply_filters( 'jetpack_sync_incremental_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
self::maybe_schedule_sync_cron( $incremental_sync_cron_schedule, 'jetpack_sync_cron' );
/**
* Allows overriding of the full sync cron schedule which defaults to once every 5 minutes.
*
* @since 4.3.2
*
* @param string self::DEFAULT_SYNC_CRON_INTERVAL_NAME
*/
$full_sync_cron_schedule = apply_filters( 'jetpack_sync_full_sync_interval', self::DEFAULT_SYNC_CRON_INTERVAL_NAME );
self::maybe_schedule_sync_cron( $full_sync_cron_schedule, 'jetpack_sync_full_cron' );
}
/**
* Perform maintenance when a plugin upgrade occurs.
*
* @access public
* @static
*
* @param string $new_version New version of the plugin.
* @param string $old_version Old version of the plugin.
*/
public static function cleanup_on_upgrade( $new_version = null, $old_version = null ) {
if ( wp_next_scheduled( 'jetpack_sync_send_db_checksum' ) ) {
wp_clear_scheduled_hook( 'jetpack_sync_send_db_checksum' );
}
$is_new_sync_upgrade = version_compare( $old_version, '4.2', '>=' );
if ( ! empty( $old_version ) && $is_new_sync_upgrade && version_compare( $old_version, '4.5', '<' ) ) {
self::clear_sync_cron_jobs();
Settings::update_settings(
array(
'render_filtered_content' => Defaults::$default_render_filtered_content,
)
);
}
}
/**
* Get syncing status for the given fields.
*
* @access public
* @static
*
* @param string|null $fields A comma-separated string of the fields to include in the array from the JSON response.
* @return array An associative array with the status report.
*/
public static function get_sync_status( $fields = null ) {
self::initialize_sender();
$sync_module = Modules::get_module( 'full-sync' );
$queue = self::$sender->get_sync_queue();
$full_queue = self::$sender->get_full_sync_queue();
$cron_timestamps = array_keys( _get_cron_array() );
$next_cron = $cron_timestamps[0] - time();
$checksums = array();
if ( ! empty( $fields ) ) {
$store = new Replicastore();
$fields_params = array_map( 'trim', explode( ',', $fields ) );
if ( in_array( 'posts_checksum', $fields_params, true ) ) {
$checksums['posts_checksum'] = $store->posts_checksum();
}
if ( in_array( 'comments_checksum', $fields_params, true ) ) {
$checksums['comments_checksum'] = $store->comments_checksum();
}
if ( in_array( 'post_meta_checksum', $fields_params, true ) ) {
$checksums['post_meta_checksum'] = $store->post_meta_checksum();
}
if ( in_array( 'comment_meta_checksum', $fields_params, true ) ) {
$checksums['comment_meta_checksum'] = $store->comment_meta_checksum();
}
}
$full_sync_status = ( $sync_module ) ? $sync_module->get_status() : array();
return array_merge(
$full_sync_status,
$checksums,
array(
'cron_size' => count( $cron_timestamps ),
'next_cron' => $next_cron,
'queue_size' => $queue->size(),
'queue_lag' => $queue->lag(),
'queue_next_sync' => ( self::$sender->get_next_sync_time( 'sync' ) - microtime( true ) ),
'full_queue_size' => $full_queue->size(),
'full_queue_lag' => $full_queue->lag(),
'full_queue_next_sync' => ( self::$sender->get_next_sync_time( 'full_sync' ) - microtime( true ) ),
)
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Interface for encoding and decoding sync objects.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* Very simple interface for encoding and decoding input.
* This is used to provide compression and serialization to messages.
**/
interface Codec_Interface {
/**
* Retrieve the name of the codec.
* We send this with the payload so we can select the appropriate decoder at the other end.
*
* @access public
*
* @return string Name of the codec.
*/
public function name();
/**
* Encode a sync object.
*
* @access public
*
* @param mixed $object Sync object to encode.
* @return string Encoded sync object.
*/
public function encode( $object );
/**
* Encode a sync object.
*
* @access public
*
* @param string $input Encoded sync object to decode.
* @return mixed Decoded sync object.
*/
public function decode( $input );
}

View File

@@ -0,0 +1,843 @@
<?php
namespace Automattic\Jetpack\Sync;
require_once JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php';
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Sync\Functions;
/**
* Just some defaults that we share with the server
*/
class Defaults {
static $default_options_whitelist = array(
'stylesheet',
'blogname',
'blogdescription',
'blog_charset',
'permalink_structure',
'category_base',
'tag_base',
'sidebars_widgets',
'comment_moderation',
'default_comment_status',
'page_on_front',
'rss_use_excerpt',
'subscription_options',
'stb_enabled',
'stc_enabled',
'comment_registration',
'show_avatars',
'avatar_default',
'avatar_rating',
'highlander_comment_form_prompt',
'jetpack_comment_form_color_scheme',
'stats_options',
'gmt_offset',
'timezone_string',
'jetpack_sync_non_public_post_stati',
'jetpack_options',
'site_icon', // (int) - ID of core's Site Icon attachment ID
'default_post_format',
'default_category',
'large_size_w',
'large_size_h',
'thumbnail_size_w',
'thumbnail_size_h',
'medium_size_w',
'medium_size_h',
'thumbnail_crop',
'image_default_link_type',
'site_logo',
'sharing-options',
'sharing-services',
'post_count',
'default_ping_status',
'sticky_posts',
'blog_public',
'default_pingback_flag',
'require_name_email',
'close_comments_for_old_posts',
'close_comments_days_old',
'thread_comments',
'thread_comments_depth',
'page_comments',
'comments_per_page',
'default_comments_page',
'comment_order',
'comments_notify',
'moderation_notify',
'social_notifications_like',
'social_notifications_reblog',
'social_notifications_subscribe',
'comment_whitelist',
'comment_max_links',
'moderation_keys',
'jetpack_wga',
'disabled_likes',
'disabled_reblogs',
'jetpack_comment_likes_enabled',
'twitter_via',
'jetpack-memberships-connected-account-id',
'jetpack-twitter-cards-site-tag',
'wpcom_publish_posts_with_markdown',
'wpcom_publish_comments_with_markdown',
'jetpack_activated',
'jetpack_available_modules',
'jetpack_allowed_xsite_search_ids',
'jetpack_autoupdate_plugins',
'jetpack_autoupdate_plugins_translations',
'jetpack_autoupdate_themes',
'jetpack_autoupdate_themes_translations',
'jetpack_autoupdate_core',
'jetpack_autoupdate_translations',
'carousel_background_color',
'carousel_display_exif',
'jetpack_portfolio',
'jetpack_portfolio_posts_per_page',
'jetpack_testimonial',
'jetpack_testimonial_posts_per_page',
'tiled_galleries',
'gravatar_disable_hovercards',
'infinite_scroll',
'infinite_scroll_google_analytics',
'wp_mobile_excerpt',
'wp_mobile_featured_images',
'wp_mobile_app_promos',
'monitor_receive_notifications',
'post_by_email_address',
'jetpack_mailchimp',
'jetpack_protect_key',
'jetpack_protect_global_whitelist',
'jetpack_sso_require_two_step',
'jetpack_sso_match_by_email',
'jetpack_relatedposts',
'verification_services_codes',
'users_can_register',
'active_plugins',
'uninstall_plugins',
'advanced_seo_front_page_description', // Jetpack_SEO_Utils::FRONT_PAGE_META_OPTION
'advanced_seo_title_formats', // Jetpack_SEO_Titles::TITLE_FORMATS_OPTION
'jetpack_api_cache_enabled',
'start_of_week',
'blacklist_keys',
'posts_per_page',
'posts_per_rss',
'show_on_front',
'ping_sites',
'uploads_use_yearmonth_folders',
'date_format',
'time_format',
'admin_email',
'new_admin_email',
'default_email_category',
'default_role',
'page_for_posts',
'mailserver_url',
'mailserver_login', // Not syncing contents, only the option name
'mailserver_pass', // Not syncing contents, only the option name
'mailserver_port',
'wp_page_for_privacy_policy',
'enable_header_ad',
'wordads_second_belowpost',
'wordads_display_front_page',
'wordads_display_post',
'wordads_display_page',
'wordads_display_archive',
'wordads_custom_adstxt',
'site_segment',
'site_user_type',
'site_vertical',
'jetpack_excluded_extensions',
);
public static function get_options_whitelist() {
/** This filter is already documented in json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php */
$options_whitelist = apply_filters( 'jetpack_options_whitelist', self::$default_options_whitelist );
/**
* Filter the list of WordPress options that are manageable via the JSON API.
*
* @module sync
*
* @since 4.8.0
*
* @param array The default list of options.
*/
return apply_filters( 'jetpack_sync_options_whitelist', $options_whitelist );
}
// Do not sync contents for these events, only the option name
static $default_options_contentless = array(
'mailserver_login',
'mailserver_pass',
);
public static function get_options_contentless() {
/**
* Filter the list of WordPress options that should be synced without content
*
* @module sync
*
* @since 6.1.0
*
* @param array The list of options synced without content.
*/
return apply_filters( 'jetpack_sync_options_contentless', self::$default_options_contentless );
}
static $default_constants_whitelist = array(
'EMPTY_TRASH_DAYS',
'WP_POST_REVISIONS',
'AUTOMATIC_UPDATER_DISABLED',
'ABSPATH',
'WP_CONTENT_DIR',
'FS_METHOD',
'DISALLOW_FILE_EDIT',
'DISALLOW_FILE_MODS',
'WP_AUTO_UPDATE_CORE',
'WP_HTTP_BLOCK_EXTERNAL',
'WP_ACCESSIBLE_HOSTS',
'JETPACK__VERSION',
'IS_PRESSABLE',
'DISABLE_WP_CRON',
'ALTERNATE_WP_CRON',
'WP_CRON_LOCK_TIMEOUT',
'PHP_VERSION',
'WP_MEMORY_LIMIT',
'WP_MAX_MEMORY_LIMIT',
'WP_DEBUG',
);
public static function get_constants_whitelist() {
/**
* Filter the list of PHP constants that are manageable via the JSON API.
*
* @module sync
*
* @since 4.8.0
*
* @param array The default list of constants options.
*/
return apply_filters( 'jetpack_sync_constants_whitelist', self::$default_constants_whitelist );
}
static $default_callable_whitelist = array(
'wp_max_upload_size' => 'wp_max_upload_size',
'is_main_network' => array( __CLASS__, 'is_multi_network' ),
'is_multi_site' => 'is_multisite',
'main_network_site' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'main_network_site_url' ),
'site_url' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'site_url' ),
'home_url' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'home_url' ),
'single_user_site' => array( 'Jetpack', 'is_single_user_site' ),
'updates' => array( 'Jetpack', 'get_updates' ),
'has_file_system_write_access' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'file_system_write_access' ),
'is_version_controlled' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'is_version_controlled' ),
'taxonomies' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_taxonomies' ),
'post_types' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_post_types' ),
'post_type_features' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_post_type_features' ),
'shortcodes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_shortcodes' ),
'rest_api_allowed_post_types' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'rest_api_allowed_post_types' ),
'rest_api_allowed_public_metadata' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'rest_api_allowed_public_metadata' ),
'sso_is_two_step_required' => array( 'Jetpack_SSO_Helpers', 'is_two_step_required' ),
'sso_should_hide_login_form' => array( 'Jetpack_SSO_Helpers', 'should_hide_login_form' ),
'sso_match_by_email' => array( 'Jetpack_SSO_Helpers', 'match_by_email' ),
'sso_new_user_override' => array( 'Jetpack_SSO_Helpers', 'new_user_override' ),
'sso_bypass_default_login_form' => array( 'Jetpack_SSO_Helpers', 'bypass_login_forward_wpcom' ),
'wp_version' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ),
'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
'get_plugins_action_links' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins_action_links' ),
'active_modules' => array( 'Jetpack', 'get_active_modules' ),
'hosting_provider' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_hosting_provider' ),
'locale' => 'get_locale',
'site_icon_url' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'site_icon_url' ),
'roles' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'roles' ),
'timezone' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_timezone' ),
'available_jetpack_blocks' => array( 'Jetpack_Gutenberg', 'get_availability' ), // Includes both Gutenberg blocks *and* plugins
'paused_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_paused_themes' ),
'paused_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_paused_plugins' ),
);
static $default_post_type_attributes = array(
'name' => '',
'label' => '',
'labels' => array(),
'description' => '',
'public' => false,
'hierarchical' => false,
'exclude_from_search' => true,
'publicly_queryable' => null,
'show_ui' => false,
'show_in_menu' => null,
'show_in_nav_menus' => null,
'show_in_admin_bar' => false,
'menu_position' => null,
'menu_icon' => null,
'supports' => array(),
'capability_type' => 'post',
'capabilities' => array(),
'cap' => array(),
'map_meta_cap' => true,
'taxonomies' => array(),
'has_archive' => false,
'rewrite' => true,
'query_var' => true,
'can_export' => true,
'delete_with_user' => null,
'show_in_rest' => false,
'rest_base' => false,
'_builtin' => false,
'_edit_link' => 'post.php?post=%d',
);
public static function get_callable_whitelist() {
/**
* Filter the list of callables that are manageable via the JSON API.
*
* @module sync
*
* @since 4.8.0
*
* @param array The default list of callables.
*/
return apply_filters( 'jetpack_sync_callable_whitelist', self::$default_callable_whitelist );
}
static $blacklisted_post_types = array(
'ai1ec_event',
'bwg_album',
'bwg_gallery',
'customize_changeset', // WP built-in post type for Customizer changesets
'dn_wp_yt_log',
'http',
'idx_page',
'jetpack_migration',
'jp_img_sitemap',
'jp_img_sitemap_index',
'jp_sitemap',
'jp_sitemap_index',
'jp_sitemap_master',
'jp_vid_sitemap',
'jp_vid_sitemap_index',
'postman_sent_mail',
'rssap-feed',
'rssmi_feed_item',
'scheduled-action', // Action Scheduler - Job Queue for WordPress https://github.com/woocommerce/woocommerce/tree/e7762627c37ec1f7590e6cac4218ba0c6a20024d/includes/libraries/action-scheduler .
'secupress_log_action',
'sg_optimizer_jobs',
'snitch',
'vip-legacy-redirect',
'wp_automatic',
'wpephpcompat_jobs',
'wprss_feed_item',
);
/**
* Taxonomies that we're not syncing by default.
*
* The list is compiled by auditing the dynamic filters and actions that contain taxonomy slugs
* and could conflict with other existing filters/actions in WP core, Jetpack and WooCommerce.
*
* @var array
*/
public static $blacklisted_taxonomies = array(
'ancestors',
'archives_link',
'attached_file',
'attached_media',
'attached_media_args',
'attachment',
'available_languages',
'avatar',
'avatar_comment_types',
'avatar_data',
'avatar_url',
'bloginfo_rss',
'blogs_of_user',
'bookmark_link',
'bookmarks',
'calendar',
'canonical_url',
'categories_per_page',
'categories_taxonomy',
'category_form',
'category_form_fields',
'category_form_pre',
'comment',
'comment_author',
'comment_author_email',
'comment_author_IP',
'comment_author_link',
'comment_author_url',
'comment_author_url_link',
'comment_date',
'comment_excerpt',
'comment_ID',
'comment_link',
'comment_misc_actions',
'comment_text',
'comment_time',
'comment_type',
'comments_link',
'comments_number',
'comments_pagenum_link',
'custom_logo',
'date_sql',
'default_comment_status',
'delete_post_link',
'edit_bookmark_link',
'edit_comment_link',
'edit_post_link',
'edit_tag_link',
'edit_term_link',
'edit_user_link',
'enclosed',
'feed_build_date',
'form_advanced',
'form_after_editor',
'form_after_title',
'form_before_permalink',
'form_top',
'handle_product_cat',
'header_image_tag',
'header_video_url',
'image_tag',
'image_tag_class',
'lastpostdate',
'lastpostmodified',
'link',
'link_category_form',
'link_category_form_fields',
'link_category_form_pre',
'main_network_id',
'media',
'media_item_args',
'ms_user',
'network',
'object_terms',
'option',
'page',
'page_form',
'page_of_comment',
'page_uri',
'pagenum_link',
'pages',
'plugin',
'post',
'post_galleries',
'post_gallery',
'post_link',
'post_modified_time',
'post_status',
'post_time',
'postmeta',
'posts_per_page',
'product_cat',
'product_search_form',
'profile_url',
'pung',
'role_list',
'sample_permalink',
'sample_permalink_html',
'schedule',
'search_form',
'search_query',
'shortlink',
'site',
'site_email_content',
'site_icon_url',
'site_option',
'space_allowed',
'tag',
'tag_form',
'tag_form_fields',
'tag_form_pre',
'tag_link',
'tags',
'tags_per_page',
'term',
'term_link',
'term_relationships',
'term_taxonomies',
'term_taxonomy',
'terms',
'terms_args',
'terms_defaults',
'terms_fields',
'terms_orderby',
'the_archive_description',
'the_archive_title',
'the_categories',
'the_date',
'the_excerpt',
'the_guid',
'the_modified_date',
'the_modified_time',
'the_post_type_description',
'the_tags',
'the_terms',
'the_time',
'theme_starter_content',
'to_ping',
'user',
'user_created_user',
'user_form',
'user_profile',
'user_profile_update',
'usermeta',
'usernumposts',
'users_drafts',
'webhook',
'widget',
'woocommerce_archive',
'wp_title_rss',
);
static $default_post_checksum_columns = array(
'ID',
'post_modified',
);
static $default_post_meta_checksum_columns = array(
'meta_id',
'meta_value',
);
static $default_comment_checksum_columns = array(
'comment_ID',
'comment_content',
);
static $default_comment_meta_checksum_columns = array(
'meta_id',
'meta_value',
);
static $default_option_checksum_columns = array(
'option_name',
'option_value',
);
static $default_term_checksum_columns = array(
'term_id',
'name',
'slug',
);
static $default_term_taxonomy_checksum_columns = array(
'term_taxonomy_id',
'term_id',
'taxonomy',
'parent',
'count',
);
static $default_term_relationships_checksum_columns = array(
'object_id',
'term_taxonomy_id',
'term_order',
);
static $default_multisite_callable_whitelist = array(
'network_name' => array( 'Jetpack', 'network_name' ),
'network_allow_new_registrations' => array( 'Jetpack', 'network_allow_new_registrations' ),
'network_add_new_users' => array( 'Jetpack', 'network_add_new_users' ),
'network_site_upload_space' => array( 'Jetpack', 'network_site_upload_space' ),
'network_upload_file_types' => array( 'Jetpack', 'network_upload_file_types' ),
'network_enable_administration_menus' => array( 'Jetpack', 'network_enable_administration_menus' ),
);
public static function get_multisite_callable_whitelist() {
/**
* Filter the list of multisite callables that are manageable via the JSON API.
*
* @module sync
*
* @since 4.8.0
*
* @param array The default list of multisite callables.
*/
return apply_filters( 'jetpack_sync_multisite_callable_whitelist', self::$default_multisite_callable_whitelist );
}
static $post_meta_whitelist = array(
'_feedback_akismet_values',
'_feedback_email',
'_feedback_extra_fields',
'_g_feedback_shortcode',
'_jetpack_post_thumbnail',
'_menu_item_classes',
'_menu_item_menu_item_parent',
'_menu_item_object',
'_menu_item_object_id',
'_menu_item_orphaned',
'_menu_item_type',
'_menu_item_xfn',
'_publicize_facebook_user',
'_publicize_twitter_user',
'_thumbnail_id',
'_wp_attached_file',
'_wp_attachment_backup_sizes',
'_wp_attachment_context',
'_wp_attachment_image_alt',
'_wp_attachment_is_custom_background',
'_wp_attachment_is_custom_header',
'_wp_attachment_metadata',
'_wp_page_template',
'_wp_trash_meta_comments_status',
'_wpas_mess',
'content_width',
'custom_css_add',
'custom_css_preprocessor',
'enclosure',
'imagedata',
'nova_price',
'publicize_results',
'sharing_disabled',
'switch_like_status',
'videopress_guid',
'vimeo_poster_image',
'advanced_seo_description', // Jetpack_SEO_Posts::DESCRIPTION_META_KEY
);
public static function get_post_meta_whitelist() {
/**
* Filter the list of post meta data that are manageable via the JSON API.
*
* @module sync
*
* @since 4.8.0
*
* @param array The default list of meta data keys.
*/
return apply_filters( 'jetpack_sync_post_meta_whitelist', self::$post_meta_whitelist );
}
static $comment_meta_whitelist = array(
'hc_avatar',
'hc_post_as',
'hc_wpcom_id_sig',
'hc_foreign_user_id',
);
public static function get_comment_meta_whitelist() {
/**
* Filter the list of comment meta data that are manageable via the JSON API.
*
* @module sync
*
* @since 5.7.0
*
* @param array The default list of comment meta data keys.
*/
return apply_filters( 'jetpack_sync_comment_meta_whitelist', self::$comment_meta_whitelist );
}
// TODO: move this to server? - these are theme support values
// that should be synced as jetpack_current_theme_supports_foo option values
static $default_theme_support_whitelist = array(
'post-thumbnails',
'post-formats',
'custom-header',
'custom-background',
'custom-logo',
'menus',
'automatic-feed-links',
'editor-style',
'widgets',
'html5',
'title-tag',
'jetpack-social-menu',
'jetpack-responsive-videos',
'infinite-scroll',
'site-logo',
);
static function is_whitelisted_option( $option ) {
$whitelisted_options = self::get_options_whitelist();
foreach ( $whitelisted_options as $whitelisted_option ) {
if ( $whitelisted_option[0] === '/' && preg_match( $whitelisted_option, $option ) ) {
return true;
} elseif ( $whitelisted_option === $option ) {
return true;
}
}
return false;
}
static $default_capabilities_whitelist = array(
'switch_themes',
'edit_themes',
'edit_theme_options',
'install_themes',
'activate_plugins',
'edit_plugins',
'install_plugins',
'edit_users',
'edit_files',
'manage_options',
'moderate_comments',
'manage_categories',
'manage_links',
'upload_files',
'import',
'unfiltered_html',
'edit_posts',
'edit_others_posts',
'edit_published_posts',
'publish_posts',
'edit_pages',
'read',
'publish_pages',
'edit_others_pages',
'edit_published_pages',
'delete_pages',
'delete_others_pages',
'delete_published_pages',
'delete_posts',
'delete_others_posts',
'delete_published_posts',
'delete_private_posts',
'edit_private_posts',
'read_private_posts',
'delete_private_pages',
'edit_private_pages',
'read_private_pages',
'delete_users',
'create_users',
'unfiltered_upload',
'edit_dashboard',
'customize',
'delete_site',
'update_plugins',
'delete_plugins',
'update_themes',
'update_core',
'list_users',
'remove_users',
'add_users',
'promote_users',
'delete_themes',
'export',
'edit_comment',
'upload_plugins',
'upload_themes',
);
public static function get_capabilities_whitelist() {
/**
* Filter the list of capabilities that we care about
*
* @module sync
*
* @since 5.5.0
*
* @param array The default list of capabilities.
*/
return apply_filters( 'jetpack_sync_capabilities_whitelist', self::$default_capabilities_whitelist );
}
static function get_max_sync_execution_time() {
$max_exec_time = intval( ini_get( 'max_execution_time' ) );
if ( 0 === $max_exec_time ) {
// 0 actually means "unlimited", but let's not treat it that way
$max_exec_time = 60;
}
return floor( $max_exec_time / 3 );
}
static function get_default_setting( $setting ) {
$default_name = "default_$setting"; // e.g. default_dequeue_max_bytes
return self::$$default_name;
}
static $default_network_options_whitelist = array(
'site_name',
'jetpack_protect_key',
'jetpack_protect_global_whitelist',
'active_sitewide_plugins',
);
/**
* A mapping of known importers to friendly names.
*
* Keys are the class name of the known importer.
* Values are the friendly name.
*
* @since 7.3.0
*
* @var array
*/
public static $default_known_importers = array(
'Blogger_Importer' => 'blogger',
'LJ_API_Import' => 'livejournal',
'MT_Import' => 'mt',
'RSS_Import' => 'rss',
'WC_Tax_Rate_Importer' => 'woo-tax-rate',
'WP_Import' => 'wordpress',
);
/**
* Returns a list of known importers.
*
* @since 7.3.0
*
* @return array Known importers with importer class names as keys and friendly names as values.
*/
public static function get_known_importers() {
/**
* Filter the list of known importers.
*
* @module sync
*
* @since 7.3.0
*
* @param array The default list of known importers.
*/
return apply_filters( 'jetpack_sync_known_importers', self::$default_known_importers );
}
/**
* Whether this is a system with a multiple networks.
* We currently need this static wrapper because we statically define our default list of callables.
*
* @since 7.6.0
*
* @uses Automattic\Jetpack\Status::is_multi_network
*
* @return boolean
*/
public static function is_multi_network() {
$status = new Status();
return $status->is_multi_network();
}
static $default_taxonomy_whitelist = array();
static $default_dequeue_max_bytes = 500000; // very conservative value, 1/2 MB
static $default_upload_max_bytes = 600000; // a little bigger than the upload limit to account for serialization
static $default_upload_max_rows = 500;
static $default_sync_wait_time = 10; // seconds, between syncs
static $default_sync_wait_threshold = 5; // only wait before next send if the current send took more than X seconds
static $default_enqueue_wait_time = 10; // wait between attempting to continue a full sync, via requests
static $default_max_queue_size = 1000;
static $default_max_queue_lag = 900; // 15 minutes
static $default_queue_max_writes_sec = 100; // 100 rows a second
static $default_post_types_blacklist = array();
static $default_taxonomies_blacklist = array();
static $default_post_meta_whitelist = array();
static $default_comment_meta_whitelist = array();
static $default_disable = 0; // completely disable sending data to wpcom
static $default_network_disable = 0; // completely disable sending data to wpcom network wide
static $default_sync_via_cron = 1; // use cron to sync
static $default_render_filtered_content = 0; // render post_filtered_content
static $default_max_enqueue_full_sync = 100; // max number of items to enqueue at a time when running full sync
static $default_max_queue_size_full_sync = 1000; // max number of total items in the full sync queue
static $default_sync_callables_wait_time = MINUTE_IN_SECONDS; // seconds before sending callables again
static $default_sync_constants_wait_time = HOUR_IN_SECONDS; // seconds before sending constants again
static $default_sync_queue_lock_timeout = 120; // 2 minutes
static $default_cron_sync_time_limit = 30; // 30 seconds
}

View File

@@ -0,0 +1,420 @@
<?php
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Constants;
/*
* Utility functions to generate data synced to wpcom
*/
class Functions {
const HTTPS_CHECK_OPTION_PREFIX = 'jetpack_sync_https_history_';
const HTTPS_CHECK_HISTORY = 5;
public static function get_modules() {
require_once JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php';
return \Jetpack_Admin::init()->get_modules();
}
public static function get_taxonomies() {
global $wp_taxonomies;
$wp_taxonomies_without_callbacks = array();
foreach ( $wp_taxonomies as $taxonomy_name => $taxonomy ) {
$sanitized_taxonomy = self::sanitize_taxonomy( $taxonomy );
if ( ! empty( $sanitized_taxonomy ) ) {
$wp_taxonomies_without_callbacks[ $taxonomy_name ] = $sanitized_taxonomy;
} else {
error_log( 'Jetpack: Encountered a recusive taxonomy:' . $taxonomy_name );
}
}
return $wp_taxonomies_without_callbacks;
}
public static function get_shortcodes() {
global $shortcode_tags;
return array_keys( $shortcode_tags );
}
/**
* Removes any callback data since we will not be able to process it on our side anyways.
*/
public static function sanitize_taxonomy( $taxonomy ) {
// Lets clone the taxonomy object instead of modifing the global one.
$cloned_taxonomy = json_decode( wp_json_encode( $taxonomy ) );
// recursive taxonomies are no fun.
if ( is_null( $cloned_taxonomy ) ) {
return null;
}
// Remove any meta_box_cb if they are not the default wp ones.
if ( isset( $cloned_taxonomy->meta_box_cb ) &&
! in_array( $cloned_taxonomy->meta_box_cb, array( 'post_tags_meta_box', 'post_categories_meta_box' ) ) ) {
$cloned_taxonomy->meta_box_cb = null;
}
// Remove update call back
if ( isset( $cloned_taxonomy->update_count_callback ) &&
! is_null( $cloned_taxonomy->update_count_callback ) ) {
$cloned_taxonomy->update_count_callback = null;
}
// Remove rest_controller_class if it something other then the default.
if ( isset( $cloned_taxonomy->rest_controller_class ) &&
'WP_REST_Terms_Controller' !== $cloned_taxonomy->rest_controller_class ) {
$cloned_taxonomy->rest_controller_class = null;
}
return $cloned_taxonomy;
}
public static function get_post_types() {
global $wp_post_types;
$post_types_without_callbacks = array();
foreach ( $wp_post_types as $post_type_name => $post_type ) {
$sanitized_post_type = self::sanitize_post_type( $post_type );
if ( ! empty( $sanitized_post_type ) ) {
$post_types_without_callbacks[ $post_type_name ] = $sanitized_post_type;
} else {
error_log( 'Jetpack: Encountered a recusive post_type:' . $post_type_name );
}
}
return $post_types_without_callbacks;
}
public static function sanitize_post_type( $post_type ) {
// Lets clone the post type object instead of modifing the global one.
$sanitized_post_type = array();
foreach ( Defaults::$default_post_type_attributes as $attribute_key => $default_value ) {
if ( isset( $post_type->{ $attribute_key } ) ) {
$sanitized_post_type[ $attribute_key ] = $post_type->{ $attribute_key };
}
}
return (object) $sanitized_post_type;
}
public static function expand_synced_post_type( $sanitized_post_type, $post_type ) {
$post_type = sanitize_key( $post_type );
$post_type_object = new \WP_Post_Type( $post_type, $sanitized_post_type );
$post_type_object->add_supports();
$post_type_object->add_rewrite_rules();
$post_type_object->add_hooks();
$post_type_object->register_taxonomies();
return (object) $post_type_object;
}
public static function get_post_type_features() {
global $_wp_post_type_features;
return $_wp_post_type_features;
}
public static function get_hosting_provider() {
if ( defined( 'GD_SYSTEM_PLUGIN_DIR' ) || class_exists( '\\WPaaS\\Plugin' ) ) {
return 'gd-managed-wp';
}
if ( defined( 'MM_BASE_DIR' ) ) {
return 'bh';
}
if ( defined( 'IS_PRESSABLE' ) ) {
return 'pressable';
}
if ( function_exists( 'is_wpe' ) || function_exists( 'is_wpe_snapshot' ) ) {
return 'wpe';
}
if ( defined( 'VIP_GO_ENV' ) && false !== VIP_GO_ENV ) {
return 'vip-go';
}
return 'unknown';
}
public static function rest_api_allowed_post_types() {
/** This filter is already documented in class.json-api-endpoints.php */
return apply_filters( 'rest_api_allowed_post_types', array( 'post', 'page', 'revision' ) );
}
public static function rest_api_allowed_public_metadata() {
/** This filter is documented in json-endpoints/class.wpcom-json-api-post-endpoint.php */
return apply_filters( 'rest_api_allowed_public_metadata', array() );
}
/**
* Finds out if a site is using a version control system.
*
* @return bool
**/
public static function is_version_controlled() {
if ( ! class_exists( 'WP_Automatic_Updater' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
}
$updater = new \WP_Automatic_Updater();
return (bool) strval( $updater->is_vcs_checkout( $context = ABSPATH ) );
}
/**
* Returns true if the site has file write access false otherwise.
*
* @return bool
**/
public static function file_system_write_access() {
if ( ! function_exists( 'get_filesystem_method' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
require_once ABSPATH . 'wp-admin/includes/template.php';
$filesystem_method = get_filesystem_method();
if ( 'direct' === $filesystem_method ) {
return true;
}
ob_start();
if ( ! function_exists( 'request_filesystem_credentials' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
ob_end_clean();
if ( $filesystem_credentials_are_stored ) {
return true;
}
return false;
}
/**
* Helper function that is used when getting home or siteurl values. Decides
* whether to get the raw or filtered value.
*
* @return string
*/
public static function get_raw_or_filtered_url( $url_type ) {
$url_function = ( 'home' == $url_type )
? 'home_url'
: 'site_url';
if (
! Constants::is_defined( 'JETPACK_SYNC_USE_RAW_URL' ) ||
Constants::get_constant( 'JETPACK_SYNC_USE_RAW_URL' )
) {
$scheme = is_ssl() ? 'https' : 'http';
$url = self::get_raw_url( $url_type );
$url = set_url_scheme( $url, $scheme );
} else {
$url = self::normalize_www_in_url( $url_type, $url_function );
}
return self::get_protocol_normalized_url( $url_function, $url );
}
public static function home_url() {
$url = self::get_raw_or_filtered_url( 'home' );
/**
* Allows overriding of the home_url value that is synced back to WordPress.com.
*
* @since 5.2.0
*
* @param string $home_url
*/
return esc_url_raw( apply_filters( 'jetpack_sync_home_url', $url ) );
}
public static function site_url() {
$url = self::get_raw_or_filtered_url( 'siteurl' );
/**
* Allows overriding of the site_url value that is synced back to WordPress.com.
*
* @since 5.2.0
*
* @param string $site_url
*/
return esc_url_raw( apply_filters( 'jetpack_sync_site_url', $url ) );
}
public static function main_network_site_url() {
return self::get_protocol_normalized_url( 'main_network_site_url', network_site_url() );
}
public static function get_protocol_normalized_url( $callable, $new_value ) {
$option_key = self::HTTPS_CHECK_OPTION_PREFIX . $callable;
$parsed_url = wp_parse_url( $new_value );
if ( ! $parsed_url ) {
return $new_value;
}
if ( array_key_exists( 'scheme', $parsed_url ) ) {
$scheme = $parsed_url['scheme'];
} else {
$scheme = '';
}
$scheme_history = get_option( $option_key, array() );
$scheme_history[] = $scheme;
// Limit length to self::HTTPS_CHECK_HISTORY
$scheme_history = array_slice( $scheme_history, ( self::HTTPS_CHECK_HISTORY * -1 ) );
update_option( $option_key, $scheme_history );
$forced_scheme = in_array( 'https', $scheme_history ) ? 'https' : 'http';
return set_url_scheme( $new_value, $forced_scheme );
}
public static function get_raw_url( $option_name ) {
$value = null;
$constant = ( 'home' == $option_name )
? 'WP_HOME'
: 'WP_SITEURL';
// Since we disregard the constant for multisites in ms-default-filters.php,
// let's also use the db value if this is a multisite.
if ( ! is_multisite() && Constants::is_defined( $constant ) ) {
$value = Constants::get_constant( $constant );
} else {
// Let's get the option from the database so that we can bypass filters. This will help
// ensure that we get more uniform values.
$value = \Jetpack_Options::get_raw_option( $option_name );
}
return $value;
}
public static function normalize_www_in_url( $option, $url_function ) {
$url = wp_parse_url( call_user_func( $url_function ) );
$option_url = wp_parse_url( get_option( $option ) );
if ( ! $option_url || ! $url ) {
return $url;
}
if ( $url['host'] === "www.{$option_url[ 'host' ]}" ) {
// remove www if not present in option URL
$url['host'] = $option_url['host'];
}
if ( $option_url['host'] === "www.{$url[ 'host' ]}" ) {
// add www if present in option URL
$url['host'] = $option_url['host'];
}
$normalized_url = "{$url['scheme']}://{$url['host']}";
if ( isset( $url['path'] ) ) {
$normalized_url .= "{$url['path']}";
}
if ( isset( $url['query'] ) ) {
$normalized_url .= "?{$url['query']}";
}
return $normalized_url;
}
public static function get_plugins() {
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
return apply_filters( 'all_plugins', get_plugins() );
}
/**
* Get custom action link tags that the plugin is using
* Ref: https://codex.wordpress.org/Plugin_API/Filter_Reference/plugin_action_links_(plugin_file_name)
*
* @return array of plugin action links (key: link name value: url)
*/
public static function get_plugins_action_links( $plugin_file_singular = null ) {
// Some sites may have DOM disabled in PHP fail early
if ( ! class_exists( 'DOMDocument' ) ) {
return array();
}
$plugins_action_links = get_option( 'jetpack_plugin_api_action_links', array() );
if ( ! empty( $plugins_action_links ) ) {
if ( is_null( $plugin_file_singular ) ) {
return $plugins_action_links;
}
return ( isset( $plugins_action_links[ $plugin_file_singular ] ) ? $plugins_action_links[ $plugin_file_singular ] : null );
}
return array();
}
public static function wp_version() {
global $wp_version;
return $wp_version;
}
public static function site_icon_url( $size = 512 ) {
$site_icon = get_site_icon_url( $size );
return $site_icon ? $site_icon : get_option( 'jetpack_site_icon_url' );
}
public static function roles() {
$wp_roles = wp_roles();
return $wp_roles->roles;
}
/**
* Determine time zone from WordPress' options "timezone_string"
* and "gmt_offset".
*
* 1. Check if `timezone_string` is set and return it.
* 2. Check if `gmt_offset` is set, formats UTC-offset from it and return it.
* 3. Default to "UTC+0" if nothing is set.
*
* @return string
*/
public static function get_timezone() {
$timezone_string = get_option( 'timezone_string' );
if ( ! empty( $timezone_string ) ) {
return str_replace( '_', ' ', $timezone_string );
}
$gmt_offset = get_option( 'gmt_offset', 0 );
$formatted_gmt_offset = sprintf( '%+g', floatval( $gmt_offset ) );
$formatted_gmt_offset = str_replace(
array( '.25', '.5', '.75' ),
array( ':15', ':30', ':45' ),
(string) $formatted_gmt_offset
);
/* translators: %s is UTC offset, e.g. "+1" */
return sprintf( __( 'UTC%s', 'jetpack' ), $formatted_gmt_offset );
}
/**
* Return list of paused themes.
*
* @todo Remove function_exists check when WP 5.2 is the minimum.
*
* @return array|bool Array of paused themes or false if unsupported.
*/
public static function get_paused_themes() {
if ( function_exists( 'wp_paused_themes' ) ) {
$paused_themes = wp_paused_themes();
return $paused_themes->get_all();
}
return false;
}
/**
* Return list of paused plugins.
*
* @todo Remove function_exists check when WP 5.2 is the minimum.
*
* @return array|bool Array of paused plugins or false if unsupported.
*/
public static function get_paused_plugins() {
if ( function_exists( 'wp_paused_plugins' ) ) {
$paused_plugins = wp_paused_plugins();
return $paused_plugins->get_all();
}
return false;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Sync\Codec_Interface;
/**
* An implementation of Automattic\Jetpack\Sync\Codec_Interface that uses gzip's DEFLATE
* algorithm to compress objects serialized using json_encode
*/
class JSON_Deflate_Array_Codec implements Codec_Interface {
const CODEC_NAME = 'deflate-json-array';
public function name() {
return self::CODEC_NAME;
}
public function encode( $object ) {
return base64_encode( gzdeflate( $this->json_serialize( $object ) ) );
}
public function decode( $input ) {
return $this->json_unserialize( gzinflate( base64_decode( $input ) ) );
}
// @see https://gist.github.com/muhqu/820694
protected function json_serialize( $any ) {
if ( function_exists( 'jetpack_json_wrap' ) ) {
return wp_json_encode( jetpack_json_wrap( $any ) );
}
// This prevents fatal error when updating pre 6.0 via the cli command
return wp_json_encode( $this->json_wrap( $any ) );
}
protected function json_unserialize( $str ) {
return $this->json_unwrap( json_decode( $str, true ) );
}
private function json_wrap( &$any, $seen_nodes = array() ) {
if ( is_object( $any ) ) {
$input = get_object_vars( $any );
$input['__o'] = 1;
} else {
$input = &$any;
}
if ( is_array( $input ) ) {
$seen_nodes[] = &$any;
$return = array();
foreach ( $input as $k => &$v ) {
if ( ( is_array( $v ) || is_object( $v ) ) ) {
if ( in_array( $v, $seen_nodes, true ) ) {
continue;
}
$return[ $k ] = $this->json_wrap( $v, $seen_nodes );
} else {
$return[ $k ] = $v;
}
}
return $return;
}
return $any;
}
private function json_unwrap( $any ) {
if ( is_array( $any ) ) {
foreach ( $any as $k => $v ) {
if ( '__o' === $k ) {
continue;
}
$any[ $k ] = $this->json_unwrap( $v );
}
if ( isset( $any['__o'] ) ) {
unset( $any['__o'] );
$any = (object) $any;
}
}
return $any;
}
}

View File

@@ -0,0 +1,329 @@
<?php
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Roles;
/**
* This class monitors actions and logs them to the queue to be sent
*/
class Listener {
const QUEUE_STATE_CHECK_TRANSIENT = 'jetpack_sync_last_checked_queue_state';
const QUEUE_STATE_CHECK_TIMEOUT = 300; // 5 minutes
private $sync_queue;
private $full_sync_queue;
private $sync_queue_size_limit;
private $sync_queue_lag_limit;
// singleton functions
private static $instance;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
// this is necessary because you can't use "new" when you declare instance properties >:(
protected function __construct() {
Main::init();
$this->set_defaults();
$this->init();
}
private function init() {
$handler = array( $this, 'action_handler' );
$full_sync_handler = array( $this, 'full_sync_action_handler' );
foreach ( Modules::get_modules() as $module ) {
$module->init_listeners( $handler );
$module->init_full_sync_listeners( $full_sync_handler );
}
// Module Activation
add_action( 'jetpack_activate_module', $handler );
add_action( 'jetpack_deactivate_module', $handler );
// Jetpack Upgrade
add_action( 'updating_jetpack_version', $handler, 10, 2 );
// Send periodic checksum
add_action( 'jetpack_sync_checksum', $handler );
}
function get_sync_queue() {
return $this->sync_queue;
}
function get_full_sync_queue() {
return $this->full_sync_queue;
}
function set_queue_size_limit( $limit ) {
$this->sync_queue_size_limit = $limit;
}
function get_queue_size_limit() {
return $this->sync_queue_size_limit;
}
function set_queue_lag_limit( $age ) {
$this->sync_queue_lag_limit = $age;
}
function get_queue_lag_limit() {
return $this->sync_queue_lag_limit;
}
function force_recheck_queue_limit() {
delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->sync_queue->id );
delete_transient( self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $this->full_sync_queue->id );
}
// prevent adding items to the queue if it hasn't sent an item for 15 mins
// AND the queue is over 1000 items long (by default)
function can_add_to_queue( $queue ) {
if ( ! Settings::is_sync_enabled() ) {
return false;
}
$state_transient_name = self::QUEUE_STATE_CHECK_TRANSIENT . '_' . $queue->id;
$queue_state = get_transient( $state_transient_name );
if ( false === $queue_state ) {
$queue_state = array( $queue->size(), $queue->lag() );
set_transient( $state_transient_name, $queue_state, self::QUEUE_STATE_CHECK_TIMEOUT );
}
list( $queue_size, $queue_age ) = $queue_state;
return ( $queue_age < $this->sync_queue_lag_limit )
||
( ( $queue_size + 1 ) < $this->sync_queue_size_limit );
}
function full_sync_action_handler() {
$args = func_get_args();
$this->enqueue_action( current_filter(), $args, $this->full_sync_queue );
}
function action_handler() {
$args = func_get_args();
$this->enqueue_action( current_filter(), $args, $this->sync_queue );
}
// add many actions to the queue directly, without invoking them
/**
* Bulk add action to the queue.
*
* @param $action_name String the name the full sync action.
* @param $args_array Array of chunked arguments
*/
function bulk_enqueue_full_sync_actions( $action_name, $args_array ) {
$queue = $this->get_full_sync_queue();
// periodically check the size of the queue, and disable adding to it if
// it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped)
if ( ! $this->can_add_to_queue( $queue ) ) {
return;
}
// if we add any items to the queue, we should try to ensure that our script
// can't be killed before they are sent
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}
$data_to_enqueue = array();
$user_id = get_current_user_id();
$currtime = microtime( true );
$is_importing = Settings::is_importing();
foreach ( $args_array as $args ) {
$previous_end = isset( $args['previous_end'] ) ? $args['previous_end'] : null;
$args = isset( $args['ids'] ) ? $args['ids'] : $args;
/**
* Modify or reject the data within an action before it is enqueued locally.
*
* @since 4.2.0
*
* @module sync
*
* @param array The action parameters
*/
$args = apply_filters( "jetpack_sync_before_enqueue_$action_name", $args );
$action_data = array( $args );
if ( ! is_null( $previous_end ) ) {
$action_data[] = $previous_end;
}
// allow listeners to abort
if ( $args === false ) {
continue;
}
$data_to_enqueue[] = array(
$action_name,
$action_data,
$user_id,
$currtime,
$is_importing,
);
}
$queue->add_all( $data_to_enqueue );
}
function enqueue_action( $current_filter, $args, $queue ) {
// don't enqueue an action during the outbound http request - this prevents recursion
if ( Settings::is_sending() ) {
return;
}
/**
* Add an action hook to execute when anything on the whitelist gets sent to the queue to sync.
*
* @module sync
*
* @since 5.9.0
*/
do_action( 'jetpack_sync_action_before_enqueue' );
/**
* Modify or reject the data within an action before it is enqueued locally.
*
* @since 4.2.0
*
* @param array The action parameters
*/
$args = apply_filters( "jetpack_sync_before_enqueue_$current_filter", $args );
// allow listeners to abort
if ( $args === false ) {
return;
}
// periodically check the size of the queue, and disable adding to it if
// it exceeds some limit AND the oldest item exceeds the age limit (i.e. sending has stopped)
if ( ! $this->can_add_to_queue( $queue ) ) {
return;
}
// if we add any items to the queue, we should try to ensure that our script
// can't be killed before they are sent
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}
if (
'sync' === $queue->id ||
in_array(
$current_filter,
array(
'jetpack_full_sync_start',
'jetpack_full_sync_end',
'jetpack_full_sync_cancel',
)
)
) {
$queue->add(
array(
$current_filter,
$args,
get_current_user_id(),
microtime( true ),
Settings::is_importing(),
$this->get_actor( $current_filter, $args ),
)
);
} else {
$queue->add(
array(
$current_filter,
$args,
get_current_user_id(),
microtime( true ),
Settings::is_importing(),
)
);
}
// since we've added some items, let's try to load the sender so we can send them as quickly as possible
if ( ! Actions::$sender ) {
add_filter( 'jetpack_sync_sender_should_load', '__return_true' );
if ( did_action( 'init' ) ) {
Actions::add_sender_shutdown();
}
}
}
function get_actor( $current_filter, $args ) {
if ( 'wp_login' === $current_filter ) {
$user = get_user_by( 'ID', $args[1]->data->ID );
} else {
$user = wp_get_current_user();
}
$roles = new Roles();
$translated_role = $roles->translate_user_to_role( $user );
$actor = array(
'wpcom_user_id' => null,
'external_user_id' => isset( $user->ID ) ? $user->ID : null,
'display_name' => isset( $user->display_name ) ? $user->display_name : null,
'user_email' => isset( $user->user_email ) ? $user->user_email : null,
'user_roles' => isset( $user->roles ) ? $user->roles : null,
'translated_role' => $translated_role ? $translated_role : null,
'is_cron' => defined( 'DOING_CRON' ) ? DOING_CRON : false,
'is_rest' => defined( 'REST_API_REQUEST' ) ? REST_API_REQUEST : false,
'is_xmlrpc' => defined( 'XMLRPC_REQUEST' ) ? XMLRPC_REQUEST : false,
'is_wp_rest' => defined( 'REST_REQUEST' ) ? REST_REQUEST : false,
'is_ajax' => defined( 'DOING_AJAX' ) ? DOING_AJAX : false,
'is_wp_admin' => is_admin(),
'is_cli' => defined( 'WP_CLI' ) ? WP_CLI : false,
'from_url' => $this->get_request_url(),
);
if ( $this->should_send_user_data_with_actor( $current_filter ) ) {
require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
$actor['ip'] = jetpack_protect_get_ip();
$actor['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown';
}
return $actor;
}
function should_send_user_data_with_actor( $current_filter ) {
$should_send = in_array( $current_filter, array( 'jetpack_wp_login', 'wp_logout', 'jetpack_valid_failed_login_attempt' ) );
/**
* Allow or deny sending actor's user data ( IP and UA ) during a sync event
*
* @since 5.8.0
*
* @module sync
*
* @param bool True if we should send user data
* @param string The current filter that is performing the sync action
*/
return apply_filters( 'jetpack_sync_actor_user_data', $should_send, $current_filter );
}
function set_defaults() {
$this->sync_queue = new Queue( 'sync' );
$this->full_sync_queue = new Queue( 'full_sync' );
$this->set_queue_size_limit( Settings::get_setting( 'max_queue_size' ) );
$this->set_queue_lag_limit( Settings::get_setting( 'max_queue_lag' ) );
}
function get_request_url() {
if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
return 'http' . ( isset( $_SERVER['HTTPS'] ) ? 's' : '' ) . '://' . "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
}
return is_admin() ? get_admin_url( get_current_blog_id() ) : home_url();
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* This class hooks the main sync actions.
*
* @package jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* Jetpack Sync main class.
*/
class Main {
/**
* Initialize the main sync actions.
*/
public static function init() {
// Check for WooCommerce support.
add_action( 'plugins_loaded', array( 'Automattic\\Jetpack\\Sync\\Actions', 'initialize_woocommerce' ), 5 );
// Check for WP Super Cache.
add_action( 'plugins_loaded', array( 'Automattic\\Jetpack\\Sync\\Actions', 'initialize_wp_super_cache' ), 5 );
/*
* Init after plugins loaded and before the `init` action. This helps with issues where plugins init
* with a high priority or sites that use alternate cron.
*/
add_action( 'plugins_loaded', array( 'Automattic\\Jetpack\\Sync\\Actions', 'init' ), 90 );
// We need to define this here so that it's hooked before `updating_jetpack_version` is called.
add_action( 'updating_jetpack_version', array( 'Automattic\\Jetpack\\Sync\\Actions', 'cleanup_on_upgrade' ), 10, 2 );
add_action( 'jetpack_user_authorized', array( 'Automattic\\Jetpack\\Sync\\Actions', 'do_initial_sync' ), 10, 0 );
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* Simple wrapper that allows enumerating cached static instances
* of sync modules.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* A class to handle loading of sync modules.
*/
class Modules {
/**
* Lists classnames of sync modules we load by default.
*
* @access public
*
* @var array
*/
const DEFAULT_SYNC_MODULES = array(
'Jetpack_Sync_Modules_Constants',
'Jetpack_Sync_Modules_Callables',
'Jetpack_Sync_Modules_Network_Options',
'Jetpack_Sync_Modules_Options',
'Jetpack_Sync_Modules_Terms',
'Jetpack_Sync_Modules_Menus',
'Jetpack_Sync_Modules_Themes',
'Jetpack_Sync_Modules_Users',
'Jetpack_Sync_Modules_Import',
'Jetpack_Sync_Modules_Posts',
'Jetpack_Sync_Modules_Protect',
'Jetpack_Sync_Modules_Comments',
'Jetpack_Sync_Modules_Updates',
'Jetpack_Sync_Modules_Attachments',
'Jetpack_Sync_Modules_Meta',
'Jetpack_Sync_Modules_Plugins',
'Jetpack_Sync_Modules_Stats',
'Jetpack_Sync_Modules_Full_Sync',
'Automattic\\Jetpack\\Sync\\Modules\\Term_Relationships',
);
/**
* Maps classnames of sync modules before to v7.5 to classnames of sync modules after v7.5.
*
* @access public
*
* @var array
*/
const LEGACY_SYNC_MODULES_MAP = array(
'Jetpack_Sync_Modules_Constants' => 'Automattic\\Jetpack\\Sync\\Modules\\Constants',
'Jetpack_Sync_Modules_Callables' => 'Automattic\\Jetpack\\Sync\\Modules\\Callables',
'Jetpack_Sync_Modules_Network_Options' => 'Automattic\\Jetpack\\Sync\\Modules\\Network_Options',
'Jetpack_Sync_Modules_Options' => 'Automattic\\Jetpack\\Sync\\Modules\\Options',
'Jetpack_Sync_Modules_Terms' => 'Automattic\\Jetpack\\Sync\\Modules\\Terms',
'Jetpack_Sync_Modules_Menus' => 'Automattic\\Jetpack\\Sync\\Modules\\Menus',
'Jetpack_Sync_Modules_Themes' => 'Automattic\\Jetpack\\Sync\\Modules\\Themes',
'Jetpack_Sync_Modules_Users' => 'Automattic\\Jetpack\\Sync\\Modules\\Users',
'Jetpack_Sync_Modules_Import' => 'Automattic\\Jetpack\\Sync\\Modules\\Import',
'Jetpack_Sync_Modules_Posts' => 'Automattic\\Jetpack\\Sync\\Modules\\Posts',
'Jetpack_Sync_Modules_Protect' => 'Automattic\\Jetpack\\Sync\\Modules\\Protect',
'Jetpack_Sync_Modules_Comments' => 'Automattic\\Jetpack\\Sync\\Modules\\Comments',
'Jetpack_Sync_Modules_Updates' => 'Automattic\\Jetpack\\Sync\\Modules\\Updates',
'Jetpack_Sync_Modules_Attachments' => 'Automattic\\Jetpack\\Sync\\Modules\\Attachments',
'Jetpack_Sync_Modules_Meta' => 'Automattic\\Jetpack\\Sync\\Modules\\Meta',
'Jetpack_Sync_Modules_Plugins' => 'Automattic\\Jetpack\\Sync\\Modules\\Plugins',
'Jetpack_Sync_Modules_Stats' => 'Automattic\\Jetpack\\Sync\\Modules\\Stats',
'Jetpack_Sync_Modules_Full_Sync' => 'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync',
);
/**
* Keeps track of initialized sync modules.
*
* @access private
* @static
*
* @var null|array
*/
private static $initialized_modules = null;
/**
* Gets a list of initialized modules.
*
* @access public
* @static
*
* @return array|null
*/
public static function get_modules() {
if ( null === self::$initialized_modules ) {
self::$initialized_modules = self::initialize_modules();
}
return self::$initialized_modules;
}
/**
* Sets defaults for all initialized modules.
*
* @access public
* @static
*/
public static function set_defaults() {
foreach ( self::get_modules() as $module ) {
$module->set_defaults();
}
}
/**
* Gets the name of an initialized module. Returns false if given module has not been initialized.
*
* @access public
* @static
*
* @param string $module_name A module name.
*
* @return bool|Automattic\Jetpack\Sync\Modules\Module
*/
public static function get_module( $module_name ) {
foreach ( self::get_modules() as $module ) {
if ( $module->name() === $module_name ) {
return $module;
}
}
return false;
}
/**
* Loads and sets defaults for all declared modules.
*
* @access public
* @static
*
* @return array
*/
public static function initialize_modules() {
/**
* Filters the list of class names of sync modules.
* If you add to this list, make sure any classes implement the
* Jetpack_Sync_Module interface.
*
* @since 4.2.0
*/
$modules = apply_filters( 'jetpack_sync_modules', self::DEFAULT_SYNC_MODULES );
$modules = array_map( array( 'Automattic\\Jetpack\\Sync\\Modules', 'map_legacy_modules' ), $modules );
$modules = array_map( array( 'Automattic\\Jetpack\\Sync\\Modules', 'load_module' ), $modules );
return array_map( array( 'Automattic\\Jetpack\\Sync\\Modules', 'set_module_defaults' ), $modules );
}
/**
* Returns an instance of the given module class.
*
* @access public
* @static
*
* @param string $module_class The classname of a Jetpack sync module.
*
* @return Automattic\Jetpack\Sync\Modules\Module
*/
public static function load_module( $module_class ) {
return new $module_class();
}
/**
* For backwards compat, takes the classname of a given module pre Jetpack 7.5,
* and returns the new namespaced classname.
*
* @access public
* @static
*
* @param string $module_class The classname of a Jetpack sync module.
*
* @return string
*/
public static function map_legacy_modules( $module_class ) {
$legacy_map = self::LEGACY_SYNC_MODULES_MAP;
if ( isset( $legacy_map[ $module_class ] ) ) {
return $legacy_map[ $module_class ];
}
return $module_class;
}
/**
* Sets defaults for the given instance of a Jetpack sync module.
*
* @access public
* @static
*
* @param Automattic\Jetpack\Sync\Modules\Module $module Instance of a Jetpack sync module.
*
* @return Automattic\Jetpack\Sync\Modules\Module
*/
public static function set_module_defaults( $module ) {
$module->set_defaults();
if ( method_exists( $module, 'set_late_default' ) ) {
add_action( 'init', array( $module, 'set_late_default' ), 90 );
}
return $module;
}
}

View File

@@ -0,0 +1,470 @@
<?php
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Sync\Defaults;
/**
* A persistent queue that can be flushed in increments of N items,
* and which blocks reads until checked-out buffers are checked in or
* closed. This uses raw SQL for two reasons: speed, and not triggering
* tons of added_option callbacks.
*/
class Queue {
public $id;
private $row_iterator;
function __construct( $id ) {
$this->id = str_replace( '-', '_', $id ); // necessary to ensure we don't have ID collisions in the SQL
$this->row_iterator = 0;
$this->random_int = mt_rand( 1, 1000000 );
}
function add( $item ) {
global $wpdb;
$added = false;
// this basically tries to add the option until enough time has elapsed that
// it has a unique (microtime-based) option key
while ( ! $added ) {
$rows_added = $wpdb->query(
$wpdb->prepare(
"INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES (%s, %s,%s)",
$this->get_next_data_row_option_name(),
serialize( $item ),
'no'
)
);
$added = ( 0 !== $rows_added );
}
}
// Attempts to insert all the items in a single SQL query. May be subject to query size limits!
function add_all( $items ) {
global $wpdb;
$base_option_name = $this->get_next_data_row_option_name();
$query = "INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES ";
$rows = array();
for ( $i = 0; $i < count( $items ); $i += 1 ) {
$option_name = esc_sql( $base_option_name . '-' . $i );
$option_value = esc_sql( serialize( $items[ $i ] ) );
$rows[] = "('$option_name', '$option_value', 'no')";
}
$rows_added = $wpdb->query( $query . join( ',', $rows ) );
if ( count( $items ) === $rows_added ) {
return new \WP_Error( 'row_count_mismatch', "The number of rows inserted didn't match the size of the input array" );
}
}
// Peek at the front-most item on the queue without checking it out
function peek( $count = 1 ) {
$items = $this->fetch_items( $count );
if ( $items ) {
return Utils::get_item_values( $items );
}
return array();
}
function peek_by_id( $item_ids ) {
$items = $this->fetch_items_by_id( $item_ids );
if ( $items ) {
return Utils::get_item_values( $items );
}
return array();
}
// lag is the difference in time between the age of the oldest item
// (aka first or frontmost item) and the current time
function lag( $now = null ) {
global $wpdb;
$first_item_name = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 1",
"jpsq_{$this->id}-%"
)
);
if ( ! $first_item_name ) {
return 0;
}
if ( null === $now ) {
$now = microtime( true );
}
// break apart the item name to get the timestamp
$matches = null;
if ( preg_match( '/^jpsq_' . $this->id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) {
return $now - floatval( $matches[1] );
} else {
return 0;
}
}
function reset() {
global $wpdb;
$this->delete_checkout_id();
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
"jpsq_{$this->id}-%"
)
);
}
function size() {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT count(*) FROM $wpdb->options WHERE option_name LIKE %s",
"jpsq_{$this->id}-%"
)
);
}
// we use this peculiar implementation because it's much faster than count(*)
function has_any_items() {
global $wpdb;
$value = $wpdb->get_var(
$wpdb->prepare(
"SELECT exists( SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s )",
"jpsq_{$this->id}-%"
)
);
return ( $value === '1' );
}
function checkout( $buffer_size ) {
if ( $this->get_checkout_id() ) {
return new \WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
}
$buffer_id = uniqid();
$result = $this->set_checkout_id( $buffer_id );
if ( ! $result || is_wp_error( $result ) ) {
return $result;
}
$items = $this->fetch_items( $buffer_size );
if ( count( $items ) === 0 ) {
return false;
}
$buffer = new Queue_Buffer( $buffer_id, array_slice( $items, 0, $buffer_size ) );
return $buffer;
}
// this checks out rows until it either empties the queue or hits a certain memory limit
// it loads the sizes from the DB first so that it doesn't accidentally
// load more data into memory than it needs to.
// The only way it will load more items than $max_size is if a single queue item
// exceeds the memory limit, but in that case it will send that item by itself.
function checkout_with_memory_limit( $max_memory, $max_buffer_size = 500 ) {
if ( $this->get_checkout_id() ) {
return new \WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
}
$buffer_id = uniqid();
$result = $this->set_checkout_id( $buffer_id );
if ( ! $result || is_wp_error( $result ) ) {
return $result;
}
// get the map of buffer_id -> memory_size
global $wpdb;
$items_with_size = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, LENGTH(option_value) AS value_size FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
"jpsq_{$this->id}-%",
$max_buffer_size
),
OBJECT
);
if ( count( $items_with_size ) === 0 ) {
return false;
}
$total_memory = 0;
$min_item_id = $max_item_id = $items_with_size[0]->id;
foreach ( $items_with_size as $id => $item_with_size ) {
$total_memory += $item_with_size->value_size;
// if this is the first item and it exceeds memory, allow loop to continue
// we will exit on the next iteration instead
if ( $total_memory > $max_memory && $id > 0 ) {
break;
}
$max_item_id = $item_with_size->id;
}
$query = $wpdb->prepare(
"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name >= %s and option_name <= %s ORDER BY option_name ASC",
$min_item_id,
$max_item_id
);
$items = $wpdb->get_results( $query, OBJECT );
foreach ( $items as $item ) {
$item->value = maybe_unserialize( $item->value );
}
if ( count( $items ) === 0 ) {
$this->delete_checkout_id();
return false;
}
$buffer = new Queue_Buffer( $buffer_id, $items );
return $buffer;
}
function checkin( $buffer ) {
$is_valid = $this->validate_checkout( $buffer );
if ( is_wp_error( $is_valid ) ) {
return $is_valid;
}
$this->delete_checkout_id();
return true;
}
function close( $buffer, $ids_to_remove = null ) {
$is_valid = $this->validate_checkout( $buffer );
if ( is_wp_error( $is_valid ) ) {
return $is_valid;
}
$this->delete_checkout_id();
// by default clear all items in the buffer
if ( is_null( $ids_to_remove ) ) {
$ids_to_remove = $buffer->get_item_ids();
}
global $wpdb;
if ( count( $ids_to_remove ) > 0 ) {
$sql = "DELETE FROM $wpdb->options WHERE option_name IN (" . implode( ', ', array_fill( 0, count( $ids_to_remove ), '%s' ) ) . ')';
$query = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $ids_to_remove ) );
$wpdb->query( $query );
}
return true;
}
function flush_all() {
$items = Utils::get_item_values( $this->fetch_items() );
$this->reset();
return $items;
}
function get_all() {
return $this->fetch_items();
}
// use with caution, this could allow multiple processes to delete
// and send from the queue at the same time
function force_checkin() {
$this->delete_checkout_id();
}
// used to lock checkouts from the queue.
// tries to wait up to $timeout seconds for the queue to be empty
function lock( $timeout = 30 ) {
$tries = 0;
while ( $this->has_any_items() && $tries < $timeout ) {
sleep( 1 );
$tries += 1;
}
if ( $tries === 30 ) {
return new \WP_Error( 'lock_timeout', 'Timeout waiting for sync queue to empty' );
}
if ( $this->get_checkout_id() ) {
return new \WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
}
// hopefully this means we can acquire a checkout?
$result = $this->set_checkout_id( 'lock' );
if ( ! $result || is_wp_error( $result ) ) {
return $result;
}
return true;
}
function unlock() {
return $this->delete_checkout_id();
}
/**
* This option is specifically chosen to, as much as possible, preserve time order
* and minimise the possibility of collisions between multiple processes working
* at the same time.
*
* @return string
*/
protected function generate_option_name_timestamp() {
return sprintf( '%.6f', microtime( true ) );
}
private function get_checkout_id() {
global $wpdb;
$checkout_value = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s",
$this->get_lock_option_name()
)
);
if ( $checkout_value ) {
list( $checkout_id, $timestamp ) = explode( ':', $checkout_value );
if ( intval( $timestamp ) > time() ) {
return $checkout_id;
}
}
return false;
}
private function set_checkout_id( $checkout_id ) {
global $wpdb;
$expires = time() + Defaults::$default_sync_queue_lock_timeout;
$updated_num = $wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
"$checkout_id:$expires",
$this->get_lock_option_name()
)
);
if ( ! $updated_num ) {
$updated_num = $wpdb->query(
$wpdb->prepare(
"INSERT INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, 'no' )",
$this->get_lock_option_name(),
"$checkout_id:$expires"
)
);
}
return $updated_num;
}
private function delete_checkout_id() {
global $wpdb;
// rather than delete, which causes fragmentation, we update in place
return $wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
'0:0',
$this->get_lock_option_name()
)
);
}
private function get_lock_option_name() {
return "jpsq_{$this->id}_checkout";
}
private function get_next_data_row_option_name() {
$timestamp = $this->generate_option_name_timestamp();
// row iterator is used to avoid collisions where we're writing data waaay fast in a single process
if ( $this->row_iterator === PHP_INT_MAX ) {
$this->row_iterator = 0;
} else {
$this->row_iterator += 1;
}
return 'jpsq_' . $this->id . '-' . $timestamp . '-' . $this->random_int . '-' . $this->row_iterator;
}
private function fetch_items( $limit = null ) {
global $wpdb;
if ( $limit ) {
$query_sql = $wpdb->prepare( "SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d", "jpsq_{$this->id}-%", $limit );
} else {
$query_sql = $wpdb->prepare( "SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC", "jpsq_{$this->id}-%" );
}
return $this->query_for_items( $query_sql );
}
private function fetch_items_by_id( $items_ids ) {
global $wpdb;
$ids_placeholders = implode( ', ', array_fill( 0, count( $items_ids ), '%s' ) );
$query_sql = $wpdb->prepare(
"
SELECT option_name AS id, option_value AS value
FROM $wpdb->options
WHERE option_name IN ( $ids_placeholders )",
$items_ids
);
return $this->query_for_items( $query_sql );
}
private function query_for_items( $query_sql ) {
global $wpdb;
$items = $wpdb->get_results( $query_sql, OBJECT );
array_walk(
$items,
function( $item ) {
$item->value = maybe_unserialize( $item->value );
}
);
return $items;
}
private function validate_checkout( $buffer ) {
if ( ! $buffer instanceof Queue_Buffer ) {
return new \WP_Error( 'not_a_buffer', 'You must checkin an instance of Automattic\\Jetpack\\Sync\\Queue_Buffer' );
}
$checkout_id = $this->get_checkout_id();
if ( ! $checkout_id ) {
return new \WP_Error( 'buffer_not_checked_out', 'There are no checked out buffers' );
}
if ( $checkout_id != $buffer->id ) {
return new \WP_Error( 'buffer_mismatch', 'The buffer you checked in was not checked out' );
}
return true;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Sync queue buffer.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* A buffer of items from the queue that can be checked out.
*/
class Queue_Buffer {
/**
* Sync queue buffer ID.
*
* @access public
*
* @var int
*/
public $id;
/**
* Sync items.
*
* @access public
*
* @var array
*/
public $items_with_ids;
/**
* Constructor.
* Initializes the queue buffer.
*
* @access public
*
* @param int $id Sync queue buffer ID.
* @param array $items_with_ids Items for the buffer to work with.
*/
public function __construct( $id, $items_with_ids ) {
$this->id = $id;
$this->items_with_ids = $items_with_ids;
}
/**
* Retrieve the sync items in the buffer, in an ID => value form.
*
* @access public
*
* @return array Sync items in the buffer.
*/
public function get_items() {
return array_combine( $this->get_item_ids(), $this->get_item_values() );
}
/**
* Retrieve the values of the sync items in the buffer.
*
* @access public
*
* @return array Sync items values.
*/
public function get_item_values() {
return Utils::get_item_values( $this->items_with_ids );
}
/**
* Retrieve the IDs of the sync items in the buffer.
*
* @access public
*
* @return array Sync items IDs.
*/
public function get_item_ids() {
return Utils::get_item_ids( $this->items_with_ids );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,566 @@
<?php
/**
* Sync architecture prototype.
*
* To run tests: phpunit --testsuite sync --filter New_Sync
*
* @author Dan Walmsley
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* A high-level interface for objects that store synced WordPress data.
* Useful for ensuring that different storage mechanisms implement the
* required semantics for storing all the data that we sync.
*/
interface Replicastore_Interface {
/**
* Empty and reset the replicastore.
*
* @access public
*/
public function reset();
/**
* Ran when full sync has just started.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
*/
public function full_sync_start( $config );
/**
* Ran when full sync has just finished.
*
* @access public
*
* @param string $checksum Deprecated since 7.3.0.
*/
public function full_sync_end( $checksum );
/**
* Retrieve the number of posts with a particular post status within a certain range.
*
* @access public
*
* @todo Prepare the SQL query before executing it.
*
* @param string $status Post status.
* @param int $min_id Minimum post ID.
* @param int $max_id Maximum post ID.
*/
public function post_count( $status = null, $min_id = null, $max_id = null );
/**
* Retrieve the posts with a particular post status.
*
* @access public
*
* @param string $status Post status.
* @param int $min_id Minimum post ID.
* @param int $max_id Maximum post ID.
*/
public function get_posts( $status = null, $min_id = null, $max_id = null );
/**
* Retrieve a post object by the post ID.
*
* @access public
*
* @param int $id Post ID.
*/
public function get_post( $id );
/**
* Update or insert a post.
*
* @access public
*
* @param \WP_Post $post Post object.
* @param bool $silent Whether to perform a silent action.
*/
public function upsert_post( $post, $silent = false );
/**
* Delete a post by the post ID.
*
* @access public
*
* @param int $post_id Post ID.
*/
public function delete_post( $post_id );
/**
* Retrieve the checksum for posts within a range.
*
* @access public
*
* @param int $min_id Minimum post ID.
* @param int $max_id Maximum post ID.
*/
public function posts_checksum( $min_id = null, $max_id = null );
/**
* Retrieve the checksum for post meta within a range.
*
* @access public
*
* @param int $min_id Minimum post meta ID.
* @param int $max_id Maximum post meta ID.
*/
public function post_meta_checksum( $min_id = null, $max_id = null );
/**
* Retrieve the number of comments with a particular comment status within a certain range.
*
* @access public
*
* @param string $status Comment status.
* @param int $min_id Minimum comment ID.
* @param int $max_id Maximum comment ID.
*/
public function comment_count( $status = null, $min_id = null, $max_id = null );
/**
* Retrieve the comments with a particular comment status.
*
* @access public
*
* @param string $status Comment status.
* @param int $min_id Minimum comment ID.
* @param int $max_id Maximum comment ID.
*/
public function get_comments( $status = null, $min_id = null, $max_id = null );
/**
* Retrieve a comment object by the comment ID.
*
* @access public
*
* @param int $id Comment ID.
*/
public function get_comment( $id );
/**
* Update or insert a comment.
*
* @access public
*
* @param \WP_Comment $comment Comment object.
*/
public function upsert_comment( $comment );
/**
* Trash a comment by the comment ID.
*
* @access public
*
* @param int $comment_id Comment ID.
*/
public function trash_comment( $comment_id );
/**
* Mark a comment by the comment ID as spam.
*
* @access public
*
* @param int $comment_id Comment ID.
*/
public function spam_comment( $comment_id );
/**
* Delete a comment by the comment ID.
*
* @access public
*
* @param int $comment_id Comment ID.
*/
public function delete_comment( $comment_id );
/**
* Trash the comments of a post.
*
* @access public
*
* @param int $post_id Post ID.
* @param array $statuses Post statuses.
*/
public function trashed_post_comments( $post_id, $statuses );
/**
* Untrash the comments of a post.
*
* @access public
*
* @param int $post_id Post ID.
*/
public function untrashed_post_comments( $post_id );
/**
* Retrieve the checksum for comments within a range.
*
* @access public
*
* @param int $min_id Minimum comment ID.
* @param int $max_id Maximum comment ID.
*/
public function comments_checksum( $min_id = null, $max_id = null );
/**
* Retrieve the checksum for comment meta within a range.
*
* @access public
*
* @param int $min_id Minimum comment meta ID.
* @param int $max_id Maximum comment meta ID.
*/
public function comment_meta_checksum( $min_id = null, $max_id = null );
/**
* Update the value of an option.
*
* @access public
*
* @param string $option Option name.
* @param mixed $value Option value.
*/
public function update_option( $option, $value );
/**
* Retrieve an option value based on an option name.
*
* @access public
*
* @param string $option Name of option to retrieve.
* @param mixed $default Optional. Default value to return if the option does not exist.
*/
public function get_option( $option, $default = false );
/**
* Remove an option by name.
*
* @access public
*
* @param string $option Name of option to remove.
*/
public function delete_option( $option );
/**
* Change the features that the current theme supports.
*
* @access public
*
* @param array $theme_support Features that the theme supports.
*/
public function set_theme_support( $theme_support );
/**
* Whether the current theme supports a certain feature.
*
* @access public
*
* @param string $feature Name of the feature.
*/
public function current_theme_supports( $feature );
/**
* Retrieve metadata for the specified object.
*
* @access public
*
* @param string $type Meta type.
* @param int $object_id ID of the object.
* @param string $meta_key Meta key.
* @param bool $single If true, return only the first value of the specified meta_key.
*/
public function get_metadata( $type, $object_id, $meta_key = '', $single = false );
/**
* Stores remote meta key/values alongside an ID mapping key.
*
* @access public
*
* @param string $type Meta type.
* @param int $object_id ID of the object.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
* @param int $meta_id ID of the meta.
*/
public function upsert_metadata( $type, $object_id, $meta_key, $meta_value, $meta_id );
/**
* Delete metadata for the specified object.
*
* @access public
*
* @param string $type Meta type.
* @param int $object_id ID of the object.
* @param array $meta_ids IDs of the meta objects to delete.
*/
public function delete_metadata( $type, $object_id, $meta_ids );
/**
* Delete metadata with a certain key for the specified objects.
*
* @access public
*
* @param string $type Meta type.
* @param array $object_ids IDs of the objects.
* @param string $meta_key Meta key.
*/
public function delete_batch_metadata( $type, $object_ids, $meta_key );
/**
* Retrieve value of a constant based on the constant name.
*
* @access public
*
* @param string $constant Name of constant to retrieve.
*/
public function get_constant( $constant );
/**
* Set the value of a constant.
*
* @access public
*
* @param string $constant Name of constant to retrieve.
* @param mixed $value Value set for the constant.
*/
public function set_constant( $constant, $value );
/**
* Retrieve the number of the available updates of a certain type.
* Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
*
* @access public
*
* @param string $type Type of updates to retrieve.
*/
public function get_updates( $type );
/**
* Set the available updates of a certain type.
* Type is one of: `plugins`, `themes`, `wordpress`, `translations`, `total`, `wp_update_version`.
*
* @access public
*
* @param string $type Type of updates to set.
* @param int $updates Total number of updates.
*/
public function set_updates( $type, $updates );
/**
* Retrieve a callable value based on its name.
*
* @access public
*
* @param string $callable Name of the callable to retrieve.
*/
public function get_callable( $callable );
/**
* Update the value of a callable.
*
* @access public
*
* @param string $callable Callable name.
* @param mixed $value Callable value.
*/
public function set_callable( $callable, $value );
/**
* Retrieve a network option value based on a network option name.
*
* @access public
*
* @param string $option Name of network option to retrieve.
*/
public function get_site_option( $option );
/**
* Update the value of a network option.
*
* @access public
*
* @param string $option Network option name.
* @param mixed $value Network option value.
*/
public function update_site_option( $option, $value );
/**
* Remove a network option by name.
*
* @access public
*
* @param string $option Name of option to remove.
*/
public function delete_site_option( $option );
/**
* Retrieve the terms from a particular taxonomy.
*
* @access public
*
* @param string $taxonomy Taxonomy slug.
*/
public function get_terms( $taxonomy );
/**
* Retrieve a particular term.
*
* @access public
*
* @param string $taxonomy Taxonomy slug.
* @param int $term_id ID of the term.
* @param bool $is_term_id Whether this is a `term_id` or a `term_taxonomy_id`.
*/
public function get_term( $taxonomy, $term_id, $is_term_id = true );
/**
* Insert or update a term.
*
* @access public
*
* @param \WP_Term $term_object Term object.
*/
public function update_term( $term_object );
/**
* Delete a term by the term ID and its corresponding taxonomy.
*
* @access public
*
* @param int $term_id Term ID.
* @param string $taxonomy Taxonomy slug.
*/
public function delete_term( $term_id, $taxonomy );
/**
* Retrieve all terms from a taxonomy that are related to an object with a particular ID.
*
* @access public
*
* @param int $object_id Object ID.
* @param string $taxonomy Taxonomy slug.
*/
public function get_the_terms( $object_id, $taxonomy );
/**
* Add/update terms of a particular taxonomy of an object with the specified ID.
*
* @access public
*
* @param int $object_id The object to relate to.
* @param string $taxonomy The context in which to relate the term to the object.
* @param string|int|array $terms A single term slug, single term id, or array of either term slugs or ids.
* @param bool $append Optional. If false will delete difference of terms. Default false.
*/
public function update_object_terms( $object_id, $taxonomy, $terms, $append );
/**
* Remove certain term relationships from the specified object.
*
* @access public
*
* @todo Refactor to not use interpolated values when preparing the SQL query.
*
* @param int $object_id ID of the object.
* @param array $tt_ids Term taxonomy IDs.
*/
public function delete_object_terms( $object_id, $tt_ids );
/**
* Retrieve the number of users.
*
* @access public
*/
public function user_count();
/**
* Retrieve a user object by the user ID.
*
* @access public
*
* @param int $user_id User ID.
*/
public function get_user( $user_id );
/**
* Insert or update a user.
*
* @access public
*
* @param \WP_User $user User object.
*/
public function upsert_user( $user );
/**
* Delete a user.
*
* @access public
*
* @param int $user_id User ID.
*/
public function delete_user( $user_id );
/**
* Update/insert user locale.
*
* @access public
*
* @param int $user_id User ID.
* @param string $locale The user locale.
*/
public function upsert_user_locale( $user_id, $locale );
/**
* Delete user locale.
*
* @access public
*
* @param int $user_id User ID.
*/
public function delete_user_locale( $user_id );
/**
* Retrieve the user locale.
*
* @access public
*
* @param int $user_id User ID.
*/
public function get_user_locale( $user_id );
/**
* Retrieve the allowed mime types for the user.
*
* @access public
*
* @param int $user_id User ID.
*/
public function get_allowed_mime_types( $user_id );
/**
* Retrieve all the checksums we are interested in.
* Currently that is posts, comments, post meta and comment meta.
*
* @access public
*/
public function checksum_all();
/**
* Retrieve the checksum histogram for a specific object type.
*
* @access public
*
* @param string $object_type Object type.
* @param int $buckets Number of buckets to split the objects to.
* @param int $start_id Minimum object ID.
* @param int $end_id Maximum object ID.
*/
public function checksum_histogram( $object_type, $buckets, $start_id = null, $end_id = null );
}

View File

@@ -0,0 +1,759 @@
<?php
/**
* Sync sender.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Constants;
/**
* This class grabs pending actions from the queue and sends them
*/
class Sender {
/**
* Name of the option that stores the time of the next sync.
*
* @access public
*
* @var string
*/
const NEXT_SYNC_TIME_OPTION_NAME = 'jetpack_next_sync_time';
/**
* Sync timeout after a WPCOM error.
*
* @access public
*
* @var int
*/
const WPCOM_ERROR_SYNC_DELAY = 60;
/**
* Sync timeout after a queue has been locked.
*
* @access public
*
* @var int
*/
const QUEUE_LOCKED_SYNC_DELAY = 10;
/**
* Maximum bytes to checkout without exceeding the memory limit.
*
* @access private
*
* @var int
*/
private $dequeue_max_bytes;
/**
* Maximum bytes in a single encoded item.
*
* @access private
*
* @var int
*/
private $upload_max_bytes;
/**
* Maximum number of sync items in a single action.
*
* @access private
*
* @var int
*/
private $upload_max_rows;
/**
* Maximum time for perfirming a checkout of items from the queue (in seconds).
*
* @access private
*
* @var int
*/
private $max_dequeue_time;
/**
* How many seconds to wait after sending sync items after exceeding the sync wait threshold (in seconds).
*
* @access private
*
* @var int
*/
private $sync_wait_time;
/**
* How much maximum time to wait for the checkout to finish (in seconds).
*
* @access private
*
* @var int
*/
private $sync_wait_threshold;
/**
* How much maximum time to wait for the sync items to be queued for sending (in seconds).
*
* @access private
*
* @var int
*/
private $enqueue_wait_time;
/**
* Incremental sync queue object.
*
* @access private
*
* @var Automattic\Jetpack\Sync\Queue
*/
private $sync_queue;
/**
* Full sync queue object.
*
* @access private
*
* @var Automattic\Jetpack\Sync\Queue
*/
private $full_sync_queue;
/**
* Codec object for encoding and decoding sync items.
*
* @access private
*
* @var Automattic\Jetpack\Sync\Codec_Interface
*/
private $codec;
/**
* The current user before we change or clear it.
*
* @access private
*
* @var \WP_User
*/
private $old_user;
/**
* Container for the singleton instance of this class.
*
* @access private
* @static
*
* @var Automattic\Jetpack\Sync\Sender
*/
private static $instance;
/**
* Retrieve the singleton instance of this class.
*
* @access public
* @static
*
* @return Automattic\Jetpack\Sync\Sender
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
* This is necessary because you can't use "new" when you declare instance properties >:(
*
* @access protected
* @static
*/
protected function __construct() {
$this->set_defaults();
$this->init();
}
/**
* Initialize the sender.
* Prepares the current user and initializes all sync modules.
*
* @access private
*/
private function init() {
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_set_user_from_token' ), 1 );
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_clear_user_from_token' ), 20 );
foreach ( Modules::get_modules() as $module ) {
$module->init_before_send();
}
}
/**
* Detect if this is a XMLRPC request with a valid signature.
* If so, changes the user to the new one.
*
* @access public
*/
public function maybe_set_user_from_token() {
$jetpack = \Jetpack::init();
$verified_user = $jetpack->verify_xml_rpc_signature();
if ( Constants::is_true( 'XMLRPC_REQUEST' ) &&
! is_wp_error( $verified_user )
&& $verified_user
) {
$old_user = wp_get_current_user();
$this->old_user = isset( $old_user->ID ) ? $old_user->ID : 0;
wp_set_current_user( $verified_user['user_id'] );
}
}
/**
* If we used to have a previous current user, revert back to it.
*
* @access public
*/
public function maybe_clear_user_from_token() {
if ( isset( $this->old_user ) ) {
wp_set_current_user( $this->old_user );
}
}
/**
* Retrieve the next sync time.
*
* @access public
*
* @param string $queue_name Name of the queue.
* @return float Timestamp of the next sync.
*/
public function get_next_sync_time( $queue_name ) {
return (float) get_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, 0 );
}
/**
* Set the next sync time.
*
* @access public
*
* @param int $time Timestamp of the next sync.
* @param string $queue_name Name of the queue.
* @return boolean True if update was successful, false otherwise.
*/
public function set_next_sync_time( $time, $queue_name ) {
return update_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name, $time, true );
}
/**
* Trigger a full sync.
*
* @access public
*
* @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
*/
public function do_full_sync() {
if ( ! Modules::get_module( 'full-sync' ) ) {
return;
}
$this->continue_full_sync_enqueue();
return $this->do_sync_and_set_delays( $this->full_sync_queue );
}
/**
* Enqueue the next sync items for sending.
* Will not be done if the current request is a WP import one.
* Will be delayed until the next sync time comes.
*
* @access private
*/
private function continue_full_sync_enqueue() {
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
return false;
}
if ( $this->get_next_sync_time( 'full-sync-enqueue' ) > microtime( true ) ) {
return false;
}
Modules::get_module( 'full-sync' )->continue_enqueuing();
$this->set_next_sync_time( time() + $this->get_enqueue_wait_time(), 'full-sync-enqueue' );
}
/**
* Trigger incremental sync.
*
* @access public
*
* @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
*/
public function do_sync() {
return $this->do_sync_and_set_delays( $this->sync_queue );
}
/**
* Trigger sync for a certain sync queue.
* Responsible for setting next sync time.
* Will not be delayed if the current request is a WP import one.
* Will be delayed until the next sync time comes.
*
* @access public
*
* @param Automattic\Jetpack\Sync\Queue $queue Queue object.
* @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
*/
public function do_sync_and_set_delays( $queue ) {
// Don't sync if importing.
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
return new \WP_Error( 'is_importing' );
}
// Don't sync if we are throttled.
if ( $this->get_next_sync_time( $queue->id ) > microtime( true ) ) {
return new \WP_Error( 'sync_throttled' );
}
$start_time = microtime( true );
Settings::set_is_syncing( true );
$sync_result = $this->do_sync_for_queue( $queue );
Settings::set_is_syncing( false );
$exceeded_sync_wait_threshold = ( microtime( true ) - $start_time ) > (float) $this->get_sync_wait_threshold();
if ( is_wp_error( $sync_result ) ) {
if ( 'unclosed_buffer' === $sync_result->get_error_code() ) {
$this->set_next_sync_time( time() + self::QUEUE_LOCKED_SYNC_DELAY, $queue->id );
}
if ( 'wpcom_error' === $sync_result->get_error_code() ) {
$this->set_next_sync_time( time() + self::WPCOM_ERROR_SYNC_DELAY, $queue->id );
}
} elseif ( $exceeded_sync_wait_threshold ) {
// If we actually sent data and it took a while, wait before sending again.
$this->set_next_sync_time( time() + $this->get_sync_wait_time(), $queue->id );
}
return $sync_result;
}
/**
* Retrieve the next sync items to send.
*
* @access public
*
* @param Automattic\Jetpack\Sync\Queue_Buffer $buffer Queue buffer object.
* @param boolean $encode Whether to encode the items.
* @return array Sync items to send.
*/
public function get_items_to_send( $buffer, $encode = true ) {
// Track how long we've been processing so we can avoid request timeouts.
$start_time = microtime( true );
$upload_size = 0;
$items_to_send = array();
$items = $buffer->get_items();
// Set up current screen to avoid errors rendering content.
require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
require_once ABSPATH . 'wp-admin/includes/screen.php';
set_current_screen( 'sync' );
$skipped_items_ids = array();
/**
* We estimate the total encoded size as we go by encoding each item individually.
* This is expensive, but the only way to really know :/
*/
foreach ( $items as $key => $item ) {
// Suspending cache addition help prevent overloading in memory cache of large sites.
wp_suspend_cache_addition( true );
/**
* Modify the data within an action before it is serialized and sent to the server
* For example, during full sync this expands Post ID's into full Post objects,
* so that we don't have to serialize the whole object into the queue.
*
* @since 4.2.0
*
* @param array The action parameters
* @param int The ID of the user who triggered the action
*/
$item[1] = apply_filters( 'jetpack_sync_before_send_' . $item[0], $item[1], $item[2] );
wp_suspend_cache_addition( false );
if ( false === $item[1] ) {
$skipped_items_ids[] = $key;
continue;
}
$encoded_item = $encode ? $this->codec->encode( $item ) : $item;
$upload_size += strlen( $encoded_item );
if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
break;
}
$items_to_send[ $key ] = $encoded_item;
if ( microtime( true ) - $start_time > $this->max_dequeue_time ) {
break;
}
}
return array( $items_to_send, $skipped_items_ids, $items, microtime( true ) - $start_time );
}
/**
* If supported, flush all response data to the client and finish the request.
* This allows for time consuming tasks to be performed without leaving the connection open.
*
* @access private
*/
private function fastcgi_finish_request() {
if ( function_exists( 'fastcgi_finish_request' ) && version_compare( phpversion(), '7.0.16', '>=' ) ) {
fastcgi_finish_request();
}
}
/**
* Perform sync for a certain sync queue.
*
* @access public
*
* @param Automattic\Jetpack\Sync\Queue $queue Queue object.
* @return boolean|\WP_Error True if this sync sending was successful, error object otherwise.
*/
public function do_sync_for_queue( $queue ) {
do_action( 'jetpack_sync_before_send_queue_' . $queue->id );
if ( $queue->size() === 0 ) {
return new \WP_Error( 'empty_queue_' . $queue->id );
}
/**
* Now that we're sure we are about to sync, try to ignore user abort
* so we can avoid getting into a bad state.
*/
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}
/* Don't make the request block till we finish, if possible. */
if ( Constants::is_true( 'REST_REQUEST' ) || Constants::is_true( 'XMLRPC_REQUEST' ) ) {
$this->fastcgi_finish_request();
}
$checkout_start_time = microtime( true );
$buffer = $queue->checkout_with_memory_limit( $this->dequeue_max_bytes, $this->upload_max_rows );
if ( ! $buffer ) {
// Buffer has no items.
return new \WP_Error( 'empty_buffer' );
}
if ( is_wp_error( $buffer ) ) {
return $buffer;
}
$checkout_duration = microtime( true ) - $checkout_start_time;
list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $buffer, true );
if ( ! empty( $items_to_send ) ) {
/**
* Fires when data is ready to send to the server.
* Return false or WP_Error to abort the sync (e.g. if there's an error)
* The items will be automatically re-sent later
*
* @since 4.2.0
*
* @param array $data The action buffer
* @param string $codec The codec name used to encode the data
* @param double $time The current time
* @param string $queue The queue used to send ('sync' or 'full_sync')
*/
Settings::set_is_sending( true );
$processed_item_ids = apply_filters( 'jetpack_sync_send_data', $items_to_send, $this->codec->name(), microtime( true ), $queue->id, $checkout_duration, $preprocess_duration );
Settings::set_is_sending( false );
} else {
$processed_item_ids = $skipped_items_ids;
$skipped_items_ids = array();
}
if ( ! $processed_item_ids || is_wp_error( $processed_item_ids ) ) {
$checked_in_item_ids = $queue->checkin( $buffer );
if ( is_wp_error( $checked_in_item_ids ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( 'Error checking in buffer: ' . $checked_in_item_ids->get_error_message() );
$queue->force_checkin();
}
if ( is_wp_error( $processed_item_ids ) ) {
return new \WP_Error( 'wpcom_error', $processed_item_ids->get_error_code() );
}
// Returning a wpcom_error is a sign to the caller that we should wait a while before syncing again.
return new \WP_Error( 'wpcom_error', 'jetpack_sync_send_data_false' );
} else {
// Detect if the last item ID was an error.
$had_wp_error = is_wp_error( end( $processed_item_ids ) );
if ( $had_wp_error ) {
$wp_error = array_pop( $processed_item_ids );
}
// Also checkin any items that were skipped.
if ( count( $skipped_items_ids ) > 0 ) {
$processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
}
$processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );
/**
* Allows us to keep track of all the actions that have been sent.
* Allows us to calculate the progress of specific actions.
*
* @since 4.2.0
*
* @param array $processed_actions The actions that we send successfully.
*/
do_action( 'jetpack_sync_processed_actions', $processed_items );
$queue->close( $buffer, $processed_item_ids );
// Returning a WP_Error is a sign to the caller that we should wait a while before syncing again.
if ( $had_wp_error ) {
return new \WP_Error( 'wpcom_error', $wp_error->get_error_code() );
}
}
return true;
}
/**
* Get the incremental sync queue object.
*
* @access public
*
* @return Automattic\Jetpack\Sync\Queue Queue object.
*/
public function get_sync_queue() {
return $this->sync_queue;
}
/**
* Get the full sync queue object.
*
* @access public
*
* @return Automattic\Jetpack\Sync\Queue Queue object.
*/
public function get_full_sync_queue() {
return $this->full_sync_queue;
}
/**
* Get the codec object.
*
* @access public
*
* @return Automattic\Jetpack\Sync\Codec_Interface Codec object.
*/
public function get_codec() {
return $this->codec;
}
/**
* Determine the codec object.
* Use gzip deflate if supported.
*
* @access public
*/
public function set_codec() {
if ( function_exists( 'gzinflate' ) ) {
$this->codec = new JSON_Deflate_Array_Codec();
} else {
$this->codec = new Simple_Codec();
}
}
/**
* Compute and send all the checksums.
*
* @access public
*/
public function send_checksum() {
$store = new Replicastore();
do_action( 'jetpack_sync_checksum', $store->checksum_all() );
}
/**
* Reset the incremental sync queue.
*
* @access public
*/
public function reset_sync_queue() {
$this->sync_queue->reset();
}
/**
* Reset the full sync queue.
*
* @access public
*/
public function reset_full_sync_queue() {
$this->full_sync_queue->reset();
}
/**
* Set the maximum bytes to checkout without exceeding the memory limit.
*
* @access public
*
* @param int $size Maximum bytes to checkout.
*/
public function set_dequeue_max_bytes( $size ) {
$this->dequeue_max_bytes = $size;
}
/**
* Set the maximum bytes in a single encoded item.
*
* @access public
*
* @param int $max_bytes Maximum bytes in a single encoded item.
*/
public function set_upload_max_bytes( $max_bytes ) {
$this->upload_max_bytes = $max_bytes;
}
/**
* Set the maximum number of sync items in a single action.
*
* @access public
*
* @param int $max_rows Maximum number of sync items.
*/
public function set_upload_max_rows( $max_rows ) {
$this->upload_max_rows = $max_rows;
}
/**
* Set the sync wait time (in seconds).
*
* @access public
*
* @param int $seconds Sync wait time.
*/
public function set_sync_wait_time( $seconds ) {
$this->sync_wait_time = $seconds;
}
/**
* Get current sync wait time (in seconds).
*
* @access public
*
* @return int Sync wait time.
*/
public function get_sync_wait_time() {
return $this->sync_wait_time;
}
/**
* Set the enqueue wait time (in seconds).
*
* @access public
*
* @param int $seconds Enqueue wait time.
*/
public function set_enqueue_wait_time( $seconds ) {
$this->enqueue_wait_time = $seconds;
}
/**
* Get current enqueue wait time (in seconds).
*
* @access public
*
* @return int Enqueue wait time.
*/
public function get_enqueue_wait_time() {
return $this->enqueue_wait_time;
}
/**
* Set the sync wait threshold (in seconds).
*
* @access public
*
* @param int $seconds Sync wait threshold.
*/
public function set_sync_wait_threshold( $seconds ) {
$this->sync_wait_threshold = $seconds;
}
/**
* Get current sync wait threshold (in seconds).
*
* @access public
*
* @return int Sync wait threshold.
*/
public function get_sync_wait_threshold() {
return $this->sync_wait_threshold;
}
/**
* Set the maximum time for perfirming a checkout of items from the queue (in seconds).
*
* @access public
*
* @param int $seconds Maximum dequeue time.
*/
public function set_max_dequeue_time( $seconds ) {
$this->max_dequeue_time = $seconds;
}
/**
* Initialize the sync queues, codec and set the default settings.
*
* @access public
*/
public function set_defaults() {
$this->sync_queue = new Queue( 'sync' );
$this->full_sync_queue = new Queue( 'full_sync' );
$this->set_codec();
// Saved settings.
Settings::set_importing( null );
$settings = Settings::get_settings();
$this->set_dequeue_max_bytes( $settings['dequeue_max_bytes'] );
$this->set_upload_max_bytes( $settings['upload_max_bytes'] );
$this->set_upload_max_rows( $settings['upload_max_rows'] );
$this->set_sync_wait_time( $settings['sync_wait_time'] );
$this->set_enqueue_wait_time( $settings['enqueue_wait_time'] );
$this->set_sync_wait_threshold( $settings['sync_wait_threshold'] );
$this->set_max_dequeue_time( Defaults::get_max_sync_execution_time() );
}
/**
* Reset sync queues, modules and settings.
*
* @access public
*/
public function reset_data() {
$this->reset_sync_queue();
$this->reset_full_sync_queue();
foreach ( Modules::get_modules() as $module ) {
$module->reset_data();
}
foreach ( array( 'sync', 'full_sync', 'full-sync-enqueue' ) as $queue_name ) {
delete_option( self::NEXT_SYNC_TIME_OPTION_NAME . '_' . $queue_name );
}
Settings::reset_data();
}
/**
* Perform cleanup at the event of plugin uninstallation.
*
* @access public
*/
public function uninstall() {
// Lets delete all the other fun stuff like transient and option and the sync queue.
$this->reset_data();
// Delete the full sync status.
delete_option( 'jetpack_full_sync_status' );
// Clear the sync cron.
wp_clear_scheduled_hook( 'jetpack_sync_cron' );
wp_clear_scheduled_hook( 'jetpack_sync_full_cron' );
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* Sync server.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* Simple version of a Jetpack Sync Server - just receives arrays of events and
* issues them locally with the 'jetpack_sync_remote_action' action.
*/
class Server {
/**
* Codec used to decode sync events.
*
* @access private
*
* @var Automattic\Jetpack\Sync\Codec_Interface
*/
private $codec;
/**
* Maximum time for processing sync actions.
*
* @access public
*
* @var int
*/
const MAX_TIME_PER_REQUEST_IN_SECONDS = 15;
/**
* Prefix of the blog lock transient.
*
* @access public
*
* @var string
*/
const BLOG_LOCK_TRANSIENT_PREFIX = 'jp_sync_req_lock_';
/**
* Lifetime of the blog lock transient.
*
* @access public
*
* @var int
*/
const BLOG_LOCK_TRANSIENT_EXPIRY = 60; // Seconds.
/**
* Constructor.
*
* This is necessary because you can't use "new" when you declare instance properties >:(
*
* @access public
*/
public function __construct() {
$this->codec = new JSON_Deflate_Array_Codec();
}
/**
* Set the codec instance.
*
* @access public
*
* @param Automattic\Jetpack\Sync\Codec_Interface $codec Codec instance.
*/
public function set_codec( Codec_Interface $codec ) {
$this->codec = $codec;
}
/**
* Attempt to lock the request when the server receives concurrent requests from the same blog.
*
* @access public
*
* @param int $blog_id ID of the blog.
* @param int $expiry Blog lock transient lifetime.
* @return boolean True if succeeded, false otherwise.
*/
public function attempt_request_lock( $blog_id, $expiry = self::BLOG_LOCK_TRANSIENT_EXPIRY ) {
$transient_name = $this->get_concurrent_request_transient_name( $blog_id );
$locked_time = get_site_transient( $transient_name );
if ( $locked_time ) {
return false;
}
set_site_transient( $transient_name, microtime( true ), $expiry );
return true;
}
/**
* Retrieve the blog lock transient name for a particular blog.
*
* @access public
*
* @param int $blog_id ID of the blog.
* @return string Name of the blog lock transient.
*/
private function get_concurrent_request_transient_name( $blog_id ) {
return self::BLOG_LOCK_TRANSIENT_PREFIX . $blog_id;
}
/**
* Remove the request lock from a particular blog ID.
*
* @access public
*
* @param int $blog_id ID of the blog.
*/
public function remove_request_lock( $blog_id ) {
delete_site_transient( $this->get_concurrent_request_transient_name( $blog_id ) );
}
/**
* Receive and process sync events.
*
* @access public
*
* @param array $data Sync events.
* @param object $token The auth token used to invoke the API.
* @param int $sent_timestamp Timestamp (in seconds) when the actions were transmitted.
* @param string $queue_id ID of the queue from which the event was sent (`sync` or `full_sync`).
* @return array Processed sync events.
*/
public function receive( $data, $token = null, $sent_timestamp = null, $queue_id = null ) {
$start_time = microtime( true );
if ( ! is_array( $data ) ) {
return new \WP_Error( 'action_decoder_error', 'Events must be an array' );
}
if ( $token && ! $this->attempt_request_lock( $token->blog_id ) ) {
/**
* Fires when the server receives two concurrent requests from the same blog
*
* @since 4.2.0
*
* @param token The token object of the misbehaving site
*/
do_action( 'jetpack_sync_multi_request_fail', $token );
return new \WP_Error( 'concurrent_request_error', 'There is another request running for the same blog ID' );
}
$events = wp_unslash( array_map( array( $this->codec, 'decode' ), $data ) );
$events_processed = array();
/**
* Fires when an array of actions are received from a remote Jetpack site
*
* @since 4.2.0
*
* @param array Array of actions received from the remote site
*/
do_action( 'jetpack_sync_remote_actions', $events, $token );
foreach ( $events as $key => $event ) {
list( $action_name, $args, $user_id, $timestamp, $silent ) = $event;
/**
* Fires when an action is received from a remote Jetpack site
*
* @since 4.2.0
*
* @param string $action_name The name of the action executed on the remote site
* @param array $args The arguments passed to the action
* @param int $user_id The external_user_id who did the action
* @param bool $silent Whether the item was created via import
* @param double $timestamp Timestamp (in seconds) when the action occurred
* @param double $sent_timestamp Timestamp (in seconds) when the action was transmitted
* @param string $queue_id ID of the queue from which the event was sent (sync or full_sync)
* @param array $token The auth token used to invoke the API
*/
do_action( 'jetpack_sync_remote_action', $action_name, $args, $user_id, $silent, $timestamp, $sent_timestamp, $queue_id, $token );
$events_processed[] = $key;
if ( microtime( true ) - $start_time > self::MAX_TIME_PER_REQUEST_IN_SECONDS ) {
break;
}
}
if ( $token ) {
$this->remove_request_lock( $token->blog_id );
}
return $events_processed;
}
}

View File

@@ -0,0 +1,443 @@
<?php
/**
* Sync settings.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* Class to manage the sync settings.
*/
class Settings {
/**
* Prefix, used for the sync settings option names.
*
* @access public
*
* @var string
*/
const SETTINGS_OPTION_PREFIX = 'jetpack_sync_settings_';
/**
* A whitelist of valid settings.
*
* @access public
* @static
*
* @var array
*/
public static $valid_settings = array(
'dequeue_max_bytes' => true,
'upload_max_bytes' => true,
'upload_max_rows' => true,
'sync_wait_time' => true,
'sync_wait_threshold' => true,
'enqueue_wait_time' => true,
'max_queue_size' => true,
'max_queue_lag' => true,
'queue_max_writes_sec' => true,
'post_types_blacklist' => true,
'taxonomies_blacklist' => true,
'disable' => true,
'network_disable' => true,
'render_filtered_content' => true,
'post_meta_whitelist' => true,
'comment_meta_whitelist' => true,
'max_enqueue_full_sync' => true,
'max_queue_size_full_sync' => true,
'sync_via_cron' => true,
'cron_sync_time_limit' => true,
'known_importers' => true,
);
/**
* Whether WordPress is currently running an import.
*
* @access public
* @static
*
* @var null|boolean
*/
public static $is_importing;
/**
* Whether WordPress is currently running a WP cron request.
*
* @access public
* @static
*
* @var null|boolean
*/
public static $is_doing_cron;
/**
* Whether we're currently syncing.
*
* @access public
* @static
*
* @var null|boolean
*/
public static $is_syncing;
/**
* Whether we're currently sending sync items.
*
* @access public
* @static
*
* @var null|boolean
*/
public static $is_sending;
/**
* Some settings can be expensive to compute - let's cache them.
*
* @access public
* @static
*
* @var array
*/
public static $settings_cache = array();
/**
* Retrieve all settings with their current values.
*
* @access public
* @static
*
* @return array All current settings.
*/
public static function get_settings() {
$settings = array();
foreach ( array_keys( self::$valid_settings ) as $setting ) {
$settings[ $setting ] = self::get_setting( $setting );
}
return $settings;
}
/**
* Fetches the setting. It saves it if the setting doesn't exist, so that it gets
* autoloaded on page load rather than re-queried every time.
*
* @access public
* @static
*
* @param string $setting The setting name.
* @return mixed The setting value.
*/
public static function get_setting( $setting ) {
if ( ! isset( self::$valid_settings[ $setting ] ) ) {
return false;
}
if ( isset( self::$settings_cache[ $setting ] ) ) {
return self::$settings_cache[ $setting ];
}
if ( self::is_network_setting( $setting ) ) {
if ( is_multisite() ) {
$value = get_site_option( self::SETTINGS_OPTION_PREFIX . $setting );
} else {
// On single sites just return the default setting.
$value = Defaults::get_default_setting( $setting );
self::$settings_cache[ $setting ] = $value;
return $value;
}
} else {
$value = get_option( self::SETTINGS_OPTION_PREFIX . $setting );
}
if ( false === $value ) { // No default value is set.
$value = Defaults::get_default_setting( $setting );
if ( self::is_network_setting( $setting ) ) {
update_site_option( self::SETTINGS_OPTION_PREFIX . $setting, $value );
} else {
// We set one so that it gets autoloaded.
update_option( self::SETTINGS_OPTION_PREFIX . $setting, $value, true );
}
}
if ( is_numeric( $value ) ) {
$value = intval( $value );
}
$default_array_value = null;
switch ( $setting ) {
case 'post_types_blacklist':
$default_array_value = Defaults::$blacklisted_post_types;
break;
case 'taxonomies_blacklist':
$default_array_value = Defaults::$blacklisted_taxonomies;
break;
case 'post_meta_whitelist':
$default_array_value = Defaults::get_post_meta_whitelist();
break;
case 'comment_meta_whitelist':
$default_array_value = Defaults::get_comment_meta_whitelist();
break;
case 'known_importers':
$default_array_value = Defaults::get_known_importers();
break;
}
if ( $default_array_value ) {
if ( is_array( $value ) ) {
$value = array_unique( array_merge( $value, $default_array_value ) );
} else {
$value = $default_array_value;
}
}
self::$settings_cache[ $setting ] = $value;
return $value;
}
/**
* Change multiple settings in the same time.
*
* @access public
* @static
*
* @param array $new_settings The new settings.
*/
public static function update_settings( $new_settings ) {
$validated_settings = array_intersect_key( $new_settings, self::$valid_settings );
foreach ( $validated_settings as $setting => $value ) {
if ( self::is_network_setting( $setting ) ) {
if ( is_multisite() && is_main_site() ) {
update_site_option( self::SETTINGS_OPTION_PREFIX . $setting, $value );
}
} else {
update_option( self::SETTINGS_OPTION_PREFIX . $setting, $value, true );
}
unset( self::$settings_cache[ $setting ] );
// If we set the disabled option to true, clear the queues.
if ( ( 'disable' === $setting || 'network_disable' === $setting ) && ! ! $value ) {
$listener = Listener::get_instance();
$listener->get_sync_queue()->reset();
$listener->get_full_sync_queue()->reset();
}
}
}
/**
* Whether the specified setting is a network setting.
*
* @access public
* @static
*
* @param string $setting Setting name.
* @return boolean Whether the setting is a network setting.
*/
public static function is_network_setting( $setting ) {
return strpos( $setting, 'network_' ) === 0;
}
/**
* Returns escaped SQL for blacklisted post types.
* Can be injected directly into a WHERE clause.
*
* @access public
* @static
*
* @return string SQL WHERE clause.
*/
public static function get_blacklisted_post_types_sql() {
return 'post_type NOT IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ) ) . '\')';
}
/**
* Returns escaped SQL for blacklisted taxonomies.
* Can be injected directly into a WHERE clause.
*
* @access public
* @static
*
* @return string SQL WHERE clause.
*/
public static function get_blacklisted_taxonomies_sql() {
return 'taxonomy NOT IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ) ) . '\')';
}
/**
* Returns escaped SQL for blacklisted post meta.
* Can be injected directly into a WHERE clause.
*
* @access public
* @static
*
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_post_meta_sql() {
return 'meta_key IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_meta_whitelist' ) ) ) . '\')';
}
/**
* Returns escaped SQL for blacklisted comment meta.
* Can be injected directly into a WHERE clause.
*
* @access public
* @static
*
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_comment_meta_sql() {
return 'meta_key IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'comment_meta_whitelist' ) ) ) . '\')';
}
/**
* Returns escaped SQL for comments, excluding any spam comments.
* Can be injected directly into a WHERE clause.
*
* @access public
* @static
*
* @return string SQL WHERE clause.
*/
public static function get_comments_filter_sql() {
return "comment_approved <> 'spam'";
}
/**
* Delete any settings options and clean up the current settings state.
*
* @access public
* @static
*/
public static function reset_data() {
$valid_settings = self::$valid_settings;
self::$settings_cache = array();
foreach ( $valid_settings as $option => $value ) {
delete_option( self::SETTINGS_OPTION_PREFIX . $option );
}
self::set_importing( null );
self::set_doing_cron( null );
self::set_is_syncing( null );
self::set_is_sending( null );
}
/**
* Set the importing state.
*
* @access public
* @static
*
* @param boolean $is_importing Whether WordPress is currently importing.
*/
public static function set_importing( $is_importing ) {
// Set to NULL to revert to WP_IMPORTING, the standard behavior.
self::$is_importing = $is_importing;
}
/**
* Whether WordPress is currently importing.
*
* @access public
* @static
*
* @return boolean Whether WordPress is currently importing.
*/
public static function is_importing() {
if ( ! is_null( self::$is_importing ) ) {
return self::$is_importing;
}
return defined( 'WP_IMPORTING' ) && WP_IMPORTING;
}
/**
* Whether sync is enabled.
*
* @access public
* @static
*
* @return boolean Whether sync is enabled.
*/
public static function is_sync_enabled() {
return ! ( self::get_setting( 'disable' ) || self::get_setting( 'network_disable' ) );
}
/**
* Set the WP cron state.
*
* @access public
* @static
*
* @param boolean $is_doing_cron Whether WordPress is currently doing WP cron.
*/
public static function set_doing_cron( $is_doing_cron ) {
// Set to NULL to revert to WP_IMPORTING, the standard behavior.
self::$is_doing_cron = $is_doing_cron;
}
/**
* Whether WordPress is currently doing WP cron.
*
* @access public
* @static
*
* @return boolean Whether WordPress is currently doing WP cron.
*/
public static function is_doing_cron() {
if ( ! is_null( self::$is_doing_cron ) ) {
return self::$is_doing_cron;
}
return defined( 'DOING_CRON' ) && DOING_CRON;
}
/**
* Whether we are currently syncing.
*
* @access public
* @static
*
* @return boolean Whether we are currently syncing.
*/
public static function is_syncing() {
return (bool) self::$is_syncing || ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST );
}
/**
* Set the syncing state.
*
* @access public
* @static
*
* @param boolean $is_syncing Whether we are currently syncing.
*/
public static function set_is_syncing( $is_syncing ) {
self::$is_syncing = $is_syncing;
}
/**
* Whether we are currently sending sync items.
*
* @access public
* @static
*
* @return boolean Whether we are currently sending sync items.
*/
public static function is_sending() {
return (bool) self::$is_sending;
}
/**
* Set the sending state.
*
* @access public
* @static
*
* @param boolean $is_sending Whether we are currently sending sync items.
*/
public static function set_is_sending( $is_sending ) {
self::$is_sending = $is_sending;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* Simple codec for encoding and decoding sync objects.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* An implementation of Automattic\Jetpack\Sync\Codec_Interface that uses base64
* algorithm to compress objects serialized using json_encode.
*/
class Simple_Codec extends JSON_Deflate_Array_Codec {
/**
* Name of the codec.
*
* @access public
*
* @var string
*/
const CODEC_NAME = 'simple';
/**
* Retrieve the name of the codec.
*
* @access public
*
* @return string Name of the codec.
*/
public function name() {
return self::CODEC_NAME;
}
/**
* Encode a sync object.
*
* @access public
*
* @param mixed $object Sync object to encode.
* @return string Encoded sync object.
*/
public function encode( $object ) {
// This is intentionally using base64_encode().
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return base64_encode( $this->json_serialize( $object ) );
}
/**
* Encode a sync object.
*
* @access public
*
* @param string $input Encoded sync object to decode.
* @return mixed Decoded sync object.
*/
public function decode( $input ) {
// This is intentionally using base64_decode().
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
return $this->json_unserialize( base64_decode( $input ) );
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
* Sync for users.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
use Automattic\Jetpack\Roles;
/**
* Class Users.
*
* Responsible for syncing user data changes.
*/
class Users {
/**
* Roles of all users, indexed by user ID.
*
* @access public
* @static
*
* @var array
*/
public static $user_roles = array();
/**
* Jetpack connection manager instance.
*
* @access public
* @static
*
* @var null|Automattic\Jetpack\Connection\Manager
*/
public static $connection = null;
/**
* Initialize sync for user data changes.
*
* @access public
* @static
* @todo Eventually, connection needs to be instantiated at the top level in the sync package.
*/
public static function init() {
self::$connection = new Jetpack_Connection();
if ( self::$connection->is_active() ) {
// Kick off synchronization of user role when it changes.
add_action( 'set_user_role', array( __CLASS__, 'user_role_change' ) );
}
}
/**
* Synchronize connected user role changes.
*
* @access public
* @static
*
* @param int $user_id ID of the user.
*/
public static function user_role_change( $user_id ) {
if ( self::$connection->is_user_connected( $user_id ) ) {
self::update_role_on_com( $user_id );
// Try to choose a new master if we're demoting the current one.
self::maybe_demote_master_user( $user_id );
}
}
/**
* Retrieve the role of a user by their ID.
*
* @access public
* @static
*
* @param int $user_id ID of the user.
* @return string Role of the user.
*/
public static function get_role( $user_id ) {
if ( isset( self::$user_roles[ $user_id ] ) ) {
return self::$user_roles[ $user_id ];
}
$current_user_id = get_current_user_id();
wp_set_current_user( $user_id );
$roles = new Roles();
$role = $roles->translate_current_user_to_role();
wp_set_current_user( $current_user_id );
self::$user_roles[ $user_id ] = $role;
return $role;
}
/**
* Retrieve the signed role of a user by their ID.
*
* @access public
* @static
*
* @param int $user_id ID of the user.
* @return string Signed role of the user.
*/
public static function get_signed_role( $user_id ) {
return \Jetpack::sign_role( self::get_role( $user_id ), $user_id );
}
/**
* Retrieve the signed role and update it in WP.com for that user.
*
* @access public
* @static
*
* @param int $user_id ID of the user.
*/
public static function update_role_on_com( $user_id ) {
$signed_role = self::get_signed_role( $user_id );
\Jetpack::xmlrpc_async_call( 'jetpack.updateRole', $user_id, $signed_role );
}
/**
* Choose a new master user if we're demoting the current one.
*
* @access public
* @static
* @todo Disconnect if there is no user with enough capabilities to be the master user.
* @uses \WP_User_Query
*
* @param int $user_id ID of the user.
*/
public static function maybe_demote_master_user( $user_id ) {
$master_user_id = (int) \Jetpack_Options::get_option( 'master_user' );
$role = self::get_role( $user_id );
if ( $user_id === $master_user_id && 'administrator' !== $role ) {
$query = new \WP_User_Query(
array(
'fields' => array( 'id' ),
'role' => 'administrator',
'orderby' => 'id',
'exclude' => array( $master_user_id ),
)
);
$new_master = false;
foreach ( $query->results as $result ) {
$found_user_id = absint( $result->id );
if ( $found_user_id && self::$connection->is_user_connected( $found_user_id ) ) {
$new_master = $found_user_id;
break;
}
}
if ( $new_master ) {
\Jetpack_Options::update_option( 'master_user', $new_master );
}
// TODO: else disconnect..?
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Sync utils.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync;
/**
* Class for sync utilities.
*/
class Utils {
/**
* Retrieve the values of sync items.
*
* @access public
* @static
*
* @param array $items Array of sync items.
* @return array Array of sync item values.
*/
public static function get_item_values( $items ) {
return array_map( array( __CLASS__, 'get_item_value' ), $items );
}
/**
* Retrieve the IDs of sync items.
*
* @access public
* @static
*
* @param array $items Array of sync items.
* @return array Array of sync item IDs.
*/
public static function get_item_ids( $items ) {
return array_map( array( __CLASS__, 'get_item_id' ), $items );
}
/**
* Get the value of a sync item.
*
* @access private
* @static
*
* @param array $item Sync item.
* @return mixed Sync item value.
*/
private static function get_item_value( $item ) {
return $item->value;
}
/**
* Get the ID of a sync item.
*
* @access private
* @static
*
* @param array $item Sync item.
* @return int Sync item ID.
*/
private static function get_item_id( $item ) {
return $item->id;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* Attachments sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
/**
* Class to handle sync for attachments.
*/
class Attachments extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'attachments';
}
/**
* Initialize attachment action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'add_attachment', array( $this, 'process_add' ) );
add_action( 'attachment_updated', array( $this, 'process_update' ), 10, 3 );
add_action( 'jetpack_sync_save_update_attachment', $callable, 10, 2 );
add_action( 'jetpack_sync_save_add_attachment', $callable, 10, 2 );
add_action( 'jetpack_sync_save_attach_attachment', $callable, 10, 2 );
}
/**
* Handle the creation of a new attachment.
*
* @access public
*
* @param int $attachment_id ID of the attachment.
*/
public function process_add( $attachment_id ) {
$attachment = get_post( $attachment_id );
/**
* Fires when the client needs to sync an new attachment
*
* @since 4.2.0
*
* @param int Attachment ID.
* @param \WP_Post Attachment post object.
*/
do_action( 'jetpack_sync_save_add_attachment', $attachment_id, $attachment );
}
/**
* Handle updating an existing attachment.
*
* @access public
*
* @param int $attachment_id Attachment ID.
* @param \WP_Post $attachment_after Attachment post object before the update.
* @param \WP_Post $attachment_before Attachment post object after the update.
*/
public function process_update( $attachment_id, $attachment_after, $attachment_before ) {
// Check whether attachment was added to a post for the first time.
if ( 0 === $attachment_before->post_parent && 0 !== $attachment_after->post_parent ) {
/**
* Fires when an existing attachment is added to a post for the first time
*
* @since 6.6.0
*
* @param int $attachment_id Attachment ID.
* @param \WP_Post $attachment_after Attachment post object after the update.
*/
do_action( 'jetpack_sync_save_attach_attachment', $attachment_id, $attachment_after );
} else {
/**
* Fires when the client needs to sync an updated attachment
*
* @since 4.9.0
*
* @param int $attachment_id Attachment ID.
* @param \WP_Post $attachment_after Attachment post object after the update.
*
* Previously this action was synced using jetpack_sync_save_add_attachment action.
*/
do_action( 'jetpack_sync_save_update_attachment', $attachment_id, $attachment_after );
}
}
}

View File

@@ -0,0 +1,491 @@
<?php
/**
* Callables sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Functions;
use Automattic\Jetpack\Sync\Defaults;
use Automattic\Jetpack\Sync\Settings;
use Automattic\Jetpack\Constants as Jetpack_Constants;
/**
* Class to handle sync for callables.
*/
class Callables extends Module {
/**
* Name of the callables checksum option.
*
* @var string
*/
const CALLABLES_CHECKSUM_OPTION_NAME = 'jetpack_callables_sync_checksum';
/**
* Name of the transient for locking callables.
*
* @var string
*/
const CALLABLES_AWAIT_TRANSIENT_NAME = 'jetpack_sync_callables_await';
/**
* Whitelist for callables we want to sync.
*
* @access private
*
* @var array
*/
private $callable_whitelist;
/**
* For some options, we should always send the change right away!
*
* @access public
*
* @var array
*/
const ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS = array(
'jetpack_active_modules',
'home', // option is home, callable is home_url.
'siteurl',
'jetpack_sync_error_idc',
'paused_plugins',
'paused_themes',
);
/**
* For some options, the callable key differs from the option name/key
*
* @access public
*
* @var array
*/
const OPTION_NAMES_TO_CALLABLE_NAMES = array(
// @TODO: Audit the other option names for differences between the option names and callable names.
'home' => 'home_url',
'siteurl' => 'site_url',
);
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'functions';
}
/**
* Set module defaults.
* Define the callable whitelist based on whether this is a single site or a multisite installation.
*
* @access public
*/
public function set_defaults() {
if ( is_multisite() ) {
$this->callable_whitelist = array_merge( Defaults::get_callable_whitelist(), Defaults::get_multisite_callable_whitelist() );
} else {
$this->callable_whitelist = Defaults::get_callable_whitelist();
}
}
/**
* Initialize callables action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'jetpack_sync_callable', $callable, 10, 2 );
add_action( 'current_screen', array( $this, 'set_plugin_action_links' ), 9999 ); // Should happen very late.
foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option ) {
add_action( "update_option_{$option}", array( $this, 'unlock_sync_callable' ) );
add_action( "delete_option_{$option}", array( $this, 'unlock_sync_callable' ) );
}
// Provide a hook so that hosts can send changes to certain callables right away.
// Especially useful when a host uses constants to change home and siteurl.
add_action( 'jetpack_sync_unlock_sync_callable', array( $this, 'unlock_sync_callable' ) );
// get_plugins and wp_version
// gets fired when new code gets installed, updates etc.
add_action( 'upgrader_process_complete', array( $this, 'unlock_plugin_action_link_and_callables' ) );
add_action( 'update_option_active_plugins', array( $this, 'unlock_plugin_action_link_and_callables' ) );
}
/**
* Initialize callables action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_callables', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_callables' ) );
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) );
}
/**
* Perform module cleanup.
* Deletes any transients and options that this module uses.
* Usually triggered when uninstalling the plugin.
*
* @access public
*/
public function reset_data() {
delete_option( self::CALLABLES_CHECKSUM_OPTION_NAME );
delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
$url_callables = array( 'home_url', 'site_url', 'main_network_site_url' );
foreach ( $url_callables as $callable ) {
delete_option( Functions::HTTPS_CHECK_OPTION_PREFIX . $callable );
}
}
/**
* Set the callable whitelist.
*
* @access public
*
* @param array $callables The new callables whitelist.
*/
public function set_callable_whitelist( $callables ) {
$this->callable_whitelist = $callables;
}
/**
* Get the callable whitelist.
*
* @access public
*
* @return array The callables whitelist.
*/
public function get_callable_whitelist() {
return $this->callable_whitelist;
}
/**
* Retrieve all callables as per the current callables whitelist.
*
* @access public
*
* @return array All callables.
*/
public function get_all_callables() {
// get_all_callables should run as the master user always.
$current_user_id = get_current_user_id();
wp_set_current_user( \Jetpack_Options::get_option( 'master_user' ) );
$callables = array_combine(
array_keys( $this->get_callable_whitelist() ),
array_map( array( $this, 'get_callable' ), array_values( $this->get_callable_whitelist() ) )
);
wp_set_current_user( $current_user_id );
return $callables;
}
/**
* Invoke a particular callable.
* Used as a wrapper to standartize invocation.
*
* @access private
*
* @param callable $callable Callable to invoke.
* @return mixed Return value of the callable.
*/
private function get_callable( $callable ) {
return call_user_func( $callable );
}
/**
* Enqueue the callable actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Tells the client to sync all callables to the server
*
* @since 4.2.0
*
* @param boolean Whether to expand callables (should always be true)
*/
do_action( 'jetpack_full_sync_callables', true );
// The number of actions enqueued, and next module state (true == done).
return array( 1, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return 1;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_callables' );
}
/**
* Unlock callables so they would be available for syncing again.
*
* @access public
*/
public function unlock_sync_callable() {
delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
}
/**
* Unlock callables and plugin action links.
*
* @access public
*/
public function unlock_plugin_action_link_and_callables() {
delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
delete_transient( 'jetpack_plugin_api_action_links_refresh' );
add_filter( 'jetpack_check_and_send_callables', '__return_true' );
}
/**
* Parse and store the plugin action links if on the plugins page.
*
* @uses \DOMDocument
* @uses libxml_use_internal_errors
* @uses mb_convert_encoding
*
* @access public
*/
public function set_plugin_action_links() {
if (
! class_exists( '\DOMDocument' ) ||
! function_exists( 'libxml_use_internal_errors' ) ||
! function_exists( 'mb_convert_encoding' )
) {
return;
}
$current_screeen = get_current_screen();
$plugins_action_links = array();
// Is the transient lock in place?
$plugins_lock = get_transient( 'jetpack_plugin_api_action_links_refresh', false );
if ( ! empty( $plugins_lock ) && ( isset( $current_screeen->id ) && 'plugins' !== $current_screeen->id ) ) {
return;
}
$plugins = array_keys( Functions::get_plugins() );
foreach ( $plugins as $plugin_file ) {
/**
* Plugins often like to unset things but things break if they are not able to.
*/
$action_links = array(
'deactivate' => '',
'activate' => '',
'details' => '',
'delete' => '',
'edit' => '',
);
/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
$action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, null, 'all' );
/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
$action_links = apply_filters( "plugin_action_links_{$plugin_file}", $action_links, $plugin_file, null, 'all' );
$action_links = array_filter( $action_links );
$formatted_action_links = null;
if ( ! empty( $action_links ) && count( $action_links ) > 0 ) {
$dom_doc = new \DOMDocument();
foreach ( $action_links as $action_link ) {
// The @ is not enough to suppress errors when dealing with libxml,
// we have to tell it directly how we want to handle errors.
libxml_use_internal_errors( true );
$dom_doc->loadHTML( mb_convert_encoding( $action_link, 'HTML-ENTITIES', 'UTF-8' ) );
libxml_use_internal_errors( false );
$link_elements = $dom_doc->getElementsByTagName( 'a' );
if ( 0 === $link_elements->length ) {
continue;
}
$link_element = $link_elements->item( 0 );
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
if ( $link_element->hasAttribute( 'href' ) && $link_element->nodeValue ) {
$link_url = trim( $link_element->getAttribute( 'href' ) );
// Add the full admin path to the url if the plugin did not provide it.
$link_url_scheme = wp_parse_url( $link_url, PHP_URL_SCHEME );
if ( empty( $link_url_scheme ) ) {
$link_url = admin_url( $link_url );
}
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$formatted_action_links[ $link_element->nodeValue ] = $link_url;
}
}
}
if ( $formatted_action_links ) {
$plugins_action_links[ $plugin_file ] = $formatted_action_links;
}
}
// Cache things for a long time.
set_transient( 'jetpack_plugin_api_action_links_refresh', time(), DAY_IN_SECONDS );
update_option( 'jetpack_plugin_api_action_links', $plugins_action_links );
}
/**
* Whether a certain callable should be sent.
*
* @access public
*
* @param array $callable_checksums Callable checksums.
* @param string $name Name of the callable.
* @param string $checksum A checksum of the callable.
* @return boolean Whether to send the callable.
*/
public function should_send_callable( $callable_checksums, $name, $checksum ) {
$idc_override_callables = array(
'main_network_site',
'home_url',
'site_url',
);
if ( in_array( $name, $idc_override_callables, true ) && \Jetpack_Options::get_option( 'migrate_for_idc' ) ) {
return true;
}
return ! $this->still_valid_checksum( $callable_checksums, $name, $checksum );
}
/**
* Sync the callables if we're supposed to.
*
* @access public
*/
public function maybe_sync_callables() {
$callables = $this->get_all_callables();
if ( ! apply_filters( 'jetpack_check_and_send_callables', false ) ) {
if ( ! is_admin() ) {
// If we're not an admin and we're not doing cron and this isn't WP_CLI, don't sync anything.
if ( ! Settings::is_doing_cron() && ! Jetpack_Constants::get_constant( 'WP_CLI' ) ) {
return;
}
// If we're not an admin and we are doing cron, sync the Callables that are always supposed to sync ( See https://github.com/Automattic/jetpack/issues/12924 ).
$callables = $this->get_always_sent_callables();
}
if ( get_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ) ) {
return;
}
}
if ( empty( $callables ) ) {
return;
}
set_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME, microtime( true ), Defaults::$default_sync_callables_wait_time );
$callable_checksums = (array) \Jetpack_Options::get_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() );
$has_changed = false;
// Only send the callables that have changed.
foreach ( $callables as $name => $value ) {
$checksum = $this->get_check_sum( $value );
// Explicitly not using Identical comparison as get_option returns a string.
if ( ! is_null( $value ) && $this->should_send_callable( $callable_checksums, $name, $checksum ) ) {
/**
* Tells the client to sync a callable (aka function) to the server
*
* @since 4.2.0
*
* @param string The name of the callable
* @param mixed The value of the callable
*/
do_action( 'jetpack_sync_callable', $name, $value );
$callable_checksums[ $name ] = $checksum;
$has_changed = true;
} else {
$callable_checksums[ $name ] = $checksum;
}
}
if ( $has_changed ) {
\Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums );
}
}
/**
* Get the callables that should always be sent, e.g. on cron.
*
* @return array Callables that should always be sent
*/
protected function get_always_sent_callables() {
$callables = $this->get_all_callables();
$cron_callables = array();
foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option_name ) {
if ( array_key_exists( $option_name, $callables ) ) {
$cron_callables[ $option_name ] = $callables[ $option_name ];
continue;
}
// Check for the Callable name/key for the option, if different from option name.
if ( array_key_exists( $option_name, self::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
$callable_name = self::OPTION_NAMES_TO_CALLABLE_NAMES[ $option_name ];
if ( array_key_exists( $callable_name, $callables ) ) {
$cron_callables[ $callable_name ] = $callables[ $callable_name ];
}
}
}
return $cron_callables;
}
/**
* Expand the callables within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function expand_callables( $args ) {
if ( $args[0] ) {
$callables = $this->get_all_callables();
$callables_checksums = array();
foreach ( $callables as $name => $value ) {
$callables_checksums[ $name ] = $this->get_check_sum( $value );
}
\Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callables_checksums );
return $callables;
}
return $args;
}
}

View File

@@ -0,0 +1,371 @@
<?php
/**
* Comments sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Settings;
/**
* Class to handle sync for comments.
*/
class Comments extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'comments';
}
/**
* Retrieve a comment by its ID.
*
* @access public
*
* @param string $object_type Type of the sync object.
* @param int $id ID of the sync object.
* @return \WP_Comment|bool Filtered \WP_Comment object, or false if the object is not a comment.
*/
public function get_object_by_id( $object_type, $id ) {
$comment_id = intval( $id );
if ( 'comment' === $object_type ) {
$comment = get_comment( $comment_id );
if ( $comment ) {
return $this->filter_comment( $comment );
}
}
return false;
}
/**
* Initialize comments action listeners.
* Also responsible for initializing comment meta listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'wp_insert_comment', $callable, 10, 2 );
add_action( 'deleted_comment', $callable );
add_action( 'trashed_comment', $callable );
add_action( 'spammed_comment', $callable );
add_action( 'trashed_post_comments', $callable, 10, 2 );
add_action( 'untrash_post_comments', $callable );
add_action( 'comment_approved_to_unapproved', $callable );
add_action( 'comment_unapproved_to_approved', $callable );
add_action( 'jetpack_modified_comment_contents', $callable, 10, 2 );
add_action( 'untrashed_comment', $callable, 10, 2 );
add_action( 'unspammed_comment', $callable, 10, 2 );
add_filter( 'wp_update_comment_data', array( $this, 'handle_comment_contents_modification' ), 10, 3 );
/**
* Even though it's messy, we implement these hooks because
* the edit_comment hook doesn't include the data
* so this saves us a DB read for every comment event.
*/
foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
$comment_action_name = "comment_{$comment_status}_{$comment_type}";
add_action( $comment_action_name, $callable, 10, 2 );
}
}
// Listen for meta changes.
$this->init_listeners_for_meta_type( 'comment', $callable );
$this->init_meta_whitelist_handler( 'comment', array( $this, 'filter_meta' ) );
}
/**
* Handler for any comment content updates.
*
* @access public
*
* @param array $new_comment The new, processed comment data.
* @param array $old_comment The old, unslashed comment data.
* @param array $new_comment_with_slashes The new, raw comment data.
* @return array The new, processed comment data.
*/
public function handle_comment_contents_modification( $new_comment, $old_comment, $new_comment_with_slashes ) {
$changes = array();
$content_fields = array(
'comment_author',
'comment_author_email',
'comment_author_url',
'comment_content',
);
foreach ( $content_fields as $field ) {
if ( $new_comment_with_slashes[ $field ] !== $old_comment[ $field ] ) {
$changes[ $field ] = array( $new_comment[ $field ], $old_comment[ $field ] );
}
}
if ( ! empty( $changes ) ) {
/**
* Signals to the sync listener that this comment's contents were modified and a sync action
* reflecting the change(s) to the content should be sent
*
* @since 4.9.0
*
* @param int $new_comment['comment_ID'] ID of comment whose content was modified
* @param mixed $changes Array of changed comment fields with before and after values
*/
do_action( 'jetpack_modified_comment_contents', $new_comment['comment_ID'], $changes );
}
return $new_comment;
}
/**
* Initialize comments action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_comments', $callable ); // Also send comments meta.
}
/**
* Gets a filtered list of comment types that sync can hook into.
*
* @access public
*
* @return array Defaults to [ '', 'trackback', 'pingback' ].
*/
public function get_whitelisted_comment_types() {
/**
* Comment types present in this list will sync their status changes to WordPress.com.
*
* @since 7.6.0
*
* @param array A list of comment types.
*/
return apply_filters(
'jetpack_sync_whitelisted_comment_types',
array( '', 'trackback', 'pingback' )
);
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_wp_insert_comment', array( $this, 'expand_wp_insert_comment' ) );
foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
$comment_action_name = "comment_{$comment_status}_{$comment_type}";
add_filter(
'jetpack_sync_before_send_' . $comment_action_name,
array(
$this,
'expand_wp_insert_comment',
)
);
}
}
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
}
/**
* Enqueue the comments actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
global $wpdb;
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return int Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
global $wpdb;
$query = "SELECT count(*) FROM $wpdb->comments";
$where_sql = $this->get_where_sql( $config );
if ( $where_sql ) {
$query .= ' WHERE ' . $where_sql;
}
// TODO: Call $wpdb->prepare on the following query.
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Retrieve the WHERE SQL clause based on the module config.
*
* @access private
*
* @param array $config Full sync configuration for this sync module.
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
*/
private function get_where_sql( $config ) {
if ( is_array( $config ) ) {
return 'comment_ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
}
return null;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_comments' );
}
/**
* Count all the actions that are going to be sent.
*
* @access public
*
* @param array $action_names Names of all the actions that will be sent.
* @return int Number of actions.
*/
public function count_full_sync_actions( $action_names ) {
return $this->count_actions( $action_names, array( 'jetpack_full_sync_comments' ) );
}
/**
* Expand the comment status change before the data is serialized and sent to the server.
*
* @access public
* @todo This is not used currently - let's implement it.
*
* @param array $args The hook parameters.
* @return array The expanded hook parameters.
*/
public function expand_wp_comment_status_change( $args ) {
return array( $args[0], $this->filter_comment( $args[1] ) );
}
/**
* Expand the comment creation before the data is serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array The expanded hook parameters.
*/
public function expand_wp_insert_comment( $args ) {
return array( $args[0], $this->filter_comment( $args[1] ) );
}
/**
* Filter a comment object to the fields we need.
*
* @access public
*
* @param \WP_Comment $comment The unfiltered comment object.
* @return \WP_Comment Filtered comment object.
*/
public function filter_comment( $comment ) {
/**
* Filters whether to prevent sending comment data to .com
*
* Passing true to the filter will prevent the comment data from being sent
* to the WordPress.com.
* Instead we pass data that will still enable us to do a checksum against the
* Jetpacks data but will prevent us from displaying the data on in the API as well as
* other services.
*
* @since 4.2.0
*
* @param boolean false prevent post data from bing synced to WordPress.com
* @param mixed $comment WP_COMMENT object
*/
if ( apply_filters( 'jetpack_sync_prevent_sending_comment_data', false, $comment ) ) {
$blocked_comment = new \stdClass();
$blocked_comment->comment_ID = $comment->comment_ID;
$blocked_comment->comment_date = $comment->comment_date;
$blocked_comment->comment_date_gmt = $comment->comment_date_gmt;
$blocked_comment->comment_approved = 'jetpack_sync_blocked';
return $blocked_comment;
}
return $comment;
}
/**
* Whether a certain comment meta key is whitelisted for sync.
*
* @access public
*
* @param string $meta_key Comment meta key.
* @return boolean Whether the meta key is whitelisted.
*/
public function is_whitelisted_comment_meta( $meta_key ) {
return in_array( $meta_key, Settings::get_setting( 'comment_meta_whitelist' ), true );
}
/**
* Handler for filtering out non-whitelisted comment meta.
*
* @access public
*
* @param array $args Hook args.
* @return array|boolean False if not whitelisted, the original hook args otherwise.
*/
public function filter_meta( $args ) {
return ( $this->is_whitelisted_comment_meta( $args[2] ) ? $args : false );
}
/**
* Expand the comment IDs to comment objects and meta before being serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array The expanded hook parameters.
*/
public function expand_comment_ids( $args ) {
list( $comment_ids, $previous_interval_end ) = $args;
$comments = get_comments(
array(
'include_unapproved' => true,
'comment__in' => $comment_ids,
'orderby' => 'comment_ID',
'order' => 'DESC',
)
);
return array(
$comments,
$this->get_metadata( $comment_ids, 'comment', Settings::get_setting( 'comment_meta_whitelist' ) ),
$previous_interval_end,
);
}
}

View File

@@ -0,0 +1,248 @@
<?php
/**
* Constants sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
/**
* Class to handle sync for constants.
*/
class Constants extends Module {
/**
* Name of the constants checksum option.
*
* @var string
*/
const CONSTANTS_CHECKSUM_OPTION_NAME = 'jetpack_constants_sync_checksum';
/**
* Name of the transient for locking constants.
*
* @var string
*/
const CONSTANTS_AWAIT_TRANSIENT_NAME = 'jetpack_sync_constants_await';
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'constants';
}
/**
* Initialize constants action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'jetpack_sync_constant', $callable, 10, 2 );
}
/**
* Initialize constants action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_constants', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_constants' ) );
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_constants', array( $this, 'expand_constants' ) );
}
/**
* Perform module cleanup.
* Deletes any transients and options that this module uses.
* Usually triggered when uninstalling the plugin.
*
* @access public
*/
public function reset_data() {
delete_option( self::CONSTANTS_CHECKSUM_OPTION_NAME );
delete_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME );
}
/**
* Set the constants whitelist.
*
* @access public
* @todo We don't seem to use this one. Should we remove it?
*
* @param array $constants The new constants whitelist.
*/
public function set_constants_whitelist( $constants ) {
$this->constants_whitelist = $constants;
}
/**
* Get the constants whitelist.
*
* @access public
*
* @return array The constants whitelist.
*/
public function get_constants_whitelist() {
return Defaults::get_constants_whitelist();
}
/**
* Enqueue the constants actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Tells the client to sync all constants to the server
*
* @since 4.2.0
*
* @param boolean Whether to expand constants (should always be true)
*/
do_action( 'jetpack_full_sync_constants', true );
// The number of actions enqueued, and next module state (true == done).
return array( 1, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return 1;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_constants' );
}
/**
* Sync the constants if we're supposed to.
*
* @access public
*/
public function maybe_sync_constants() {
if ( get_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME ) ) {
return;
}
set_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME, microtime( true ), Defaults::$default_sync_constants_wait_time );
$constants = $this->get_all_constants();
if ( empty( $constants ) ) {
return;
}
$constants_checksums = (array) get_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, array() );
foreach ( $constants as $name => $value ) {
$checksum = $this->get_check_sum( $value );
// Explicitly not using Identical comparison as get_option returns a string.
if ( ! $this->still_valid_checksum( $constants_checksums, $name, $checksum ) && ! is_null( $value ) ) {
/**
* Tells the client to sync a constant to the server
*
* @since 4.2.0
*
* @param string The name of the constant
* @param mixed The value of the constant
*/
do_action( 'jetpack_sync_constant', $name, $value );
$constants_checksums[ $name ] = $checksum;
} else {
$constants_checksums[ $name ] = $checksum;
}
}
update_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, $constants_checksums );
}
/**
* Retrieve all constants as per the current constants whitelist.
* Public so that we don't have to store an option for each constant.
*
* @access public
*
* @return array All constants.
*/
public function get_all_constants() {
$constants_whitelist = $this->get_constants_whitelist();
return array_combine(
$constants_whitelist,
array_map( array( $this, 'get_constant' ), $constants_whitelist )
);
}
/**
* Retrieve the value of a constant.
* Used as a wrapper to standartize access to constants.
*
* @access private
*
* @param string $constant Constant name.
* @return mixed Return value of the constant.
*/
private function get_constant( $constant ) {
return ( defined( $constant ) ) ?
constant( $constant )
: null;
}
/**
* Expand the constants within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function expand_constants( $args ) {
if ( $args[0] ) {
$constants = $this->get_all_constants();
$constants_checksums = array();
foreach ( $constants as $name => $value ) {
$constants_checksums[ $name ] = $this->get_check_sum( $value );
}
update_option( self::CONSTANTS_CHECKSUM_OPTION_NAME, $constants_checksums );
return $constants;
}
return $args;
}
}

View File

@@ -0,0 +1,694 @@
<?php
/**
* Full sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Listener;
use Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Queue;
use Automattic\Jetpack\Sync\Settings;
/**
* This class does a full resync of the database by
* enqueuing an outbound action for every single object
* that we care about.
*
* This class, and its related class Jetpack_Sync_Module, contain a few non-obvious optimisations that should be explained:
* - we fire an action called jetpack_full_sync_start so that WPCOM can erase the contents of the cached database
* - for each object type, we page through the object IDs and enqueue them by firing some monitored actions
* - we load the full objects for those IDs in chunks of Jetpack_Sync_Module::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
* - we fire a trigger for the entire array which the Automattic\Jetpack\Sync\Listener then serializes and queues.
*/
class Full_Sync extends Module {
/**
* Prefix of the full sync status option name.
*
* @var string
*/
const STATUS_OPTION_PREFIX = 'jetpack_sync_full_';
/**
* Timeout between the previous and the next allowed full sync.
*
* @todo Remove this as it's no longer used since https://github.com/Automattic/jetpack/pull/4561
*
* @var int
*/
const FULL_SYNC_TIMEOUT = 3600;
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'full-sync';
}
/**
* Initialize action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
// Synthetic actions for full sync.
add_action( 'jetpack_full_sync_start', $callable, 10, 3 );
add_action( 'jetpack_full_sync_end', $callable, 10, 2 );
add_action( 'jetpack_full_sync_cancelled', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
// This is triggered after actions have been processed on the server.
add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
}
/**
* Start a full sync.
*
* @access public
*
* @param array $module_configs Full sync configuration for all sync modules.
* @return bool Always returns true at success.
*/
public function start( $module_configs = null ) {
$was_already_running = $this->is_started() && ! $this->is_finished();
// Remove all evidence of previous full sync items and status.
$this->reset_data();
if ( $was_already_running ) {
/**
* Fires when a full sync is cancelled.
*
* @since 4.2.0
*/
do_action( 'jetpack_full_sync_cancelled' );
}
$this->update_status_option( 'started', time() );
$this->update_status_option( 'params', $module_configs );
$enqueue_status = array();
$full_sync_config = array();
$include_empty = false;
$empty = array();
// Default value is full sync.
if ( ! is_array( $module_configs ) ) {
$module_configs = array();
$include_empty = true;
foreach ( Modules::get_modules() as $module ) {
$module_configs[ $module->name() ] = true;
}
}
// Set default configuration, calculate totals, and save configuration if totals > 0.
foreach ( Modules::get_modules() as $module ) {
$module_name = $module->name();
$module_config = isset( $module_configs[ $module_name ] ) ? $module_configs[ $module_name ] : false;
if ( ! $module_config ) {
continue;
}
if ( 'users' === $module_name && 'initial' === $module_config ) {
$module_config = $module->get_initial_sync_user_config();
}
$enqueue_status[ $module_name ] = false;
$total_items = $module->estimate_full_sync_actions( $module_config );
// If there's information to process, configure this module.
if ( ! is_null( $total_items ) && $total_items > 0 ) {
$full_sync_config[ $module_name ] = $module_config;
$enqueue_status[ $module_name ] = array(
$total_items, // Total.
0, // Queued.
false, // Current state.
);
} elseif ( $include_empty && 0 === $total_items ) {
$empty[ $module_name ] = true;
}
}
$this->set_config( $full_sync_config );
$this->set_enqueue_status( $enqueue_status );
$range = $this->get_content_range( $full_sync_config );
/**
* Fires when a full sync begins. This action is serialized
* and sent to the server so that it knows a full sync is coming.
*
* @since 4.2.0
* @since 7.3.0 Added $range arg.
* @since 7.4.0 Added $empty arg.
*
* @param array $full_sync_config Sync configuration for all sync modules.
* @param array $range Range of the sync items, containing min and max IDs for some item types.
* @param array $empty The modules with no items to sync during a full sync.
*/
do_action( 'jetpack_full_sync_start', $full_sync_config, $range, $empty );
$this->continue_enqueuing( $full_sync_config, $enqueue_status );
return true;
}
/**
* Enqueue the next items to sync.
*
* @access public
*
* @param array $configs Full sync configuration for all sync modules.
* @param array $enqueue_status Current status of the queue, indexed by sync modules.
*/
public function continue_enqueuing( $configs = null, $enqueue_status = null ) {
if ( ! $this->is_started() || $this->get_status_option( 'queue_finished' ) ) {
return;
}
// If full sync queue is full, don't enqueue more items.
$max_queue_size_full_sync = Settings::get_setting( 'max_queue_size_full_sync' );
$full_sync_queue = new Queue( 'full_sync' );
$available_queue_slots = $max_queue_size_full_sync - $full_sync_queue->size();
if ( $available_queue_slots <= 0 ) {
return;
} else {
$remaining_items_to_enqueue = min( Settings::get_setting( 'max_enqueue_full_sync' ), $available_queue_slots );
}
if ( ! $configs ) {
$configs = $this->get_config();
}
if ( ! $enqueue_status ) {
$enqueue_status = $this->get_enqueue_status();
}
foreach ( Modules::get_modules() as $module ) {
$module_name = $module->name();
// Skip module if not configured for this sync or module is done.
if ( ! isset( $configs[ $module_name ] )
|| // No module config.
! $configs[ $module_name ]
|| // No enqueue status.
! $enqueue_status[ $module_name ]
|| // Finished enqueuing this module.
true === $enqueue_status[ $module_name ][2] ) {
continue;
}
list( $items_enqueued, $next_enqueue_state ) = $module->enqueue_full_sync_actions( $configs[ $module_name ], $remaining_items_to_enqueue, $enqueue_status[ $module_name ][2] );
$enqueue_status[ $module_name ][2] = $next_enqueue_state;
// If items were processed, subtract them from the limit.
if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) {
$enqueue_status[ $module_name ][1] += $items_enqueued;
$remaining_items_to_enqueue -= $items_enqueued;
}
// Stop processing if we've reached our limit of items to enqueue.
if ( 0 >= $remaining_items_to_enqueue ) {
$this->set_enqueue_status( $enqueue_status );
return;
}
}
$this->set_enqueue_status( $enqueue_status );
// Setting autoload to true means that it's faster to check whether we should continue enqueuing.
$this->update_status_option( 'queue_finished', time(), true );
$range = $this->get_content_range( $configs );
/**
* Fires when a full sync ends. This action is serialized
* and sent to the server.
*
* @since 4.2.0
* @since 7.3.0 Added $range arg.
*
* @param string $checksum Deprecated since 7.3.0 - @see https://github.com/Automattic/jetpack/pull/11945/
* @param array $range Range of the sync items, containing min and max IDs for some item types.
*/
do_action( 'jetpack_full_sync_end', '', $range );
}
/**
* Get the range (min ID, max ID and total items) of items to sync.
*
* @access public
*
* @param string $type Type of sync item to get the range for.
* @return array Array of min ID, max ID and total items in the range.
*/
public function get_range( $type ) {
global $wpdb;
if ( ! in_array( $type, array( 'comments', 'posts' ), true ) ) {
return array();
}
switch ( $type ) {
case 'posts':
$table = $wpdb->posts;
$id = 'ID';
$where_sql = Settings::get_blacklisted_post_types_sql();
break;
case 'comments':
$table = $wpdb->comments;
$id = 'comment_ID';
$where_sql = Settings::get_comments_filter_sql();
break;
}
// TODO: Call $wpdb->prepare on the following query.
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$results = $wpdb->get_results( "SELECT MAX({$id}) as max, MIN({$id}) as min, COUNT({$id}) as count FROM {$table} WHERE {$where_sql}" );
if ( isset( $results[0] ) ) {
return $results[0];
}
return array();
}
/**
* Get the range for content (posts and comments) to sync.
*
* @access private
*
* @param array $config Full sync configuration for this all sync modules.
* @return array Array of range (min ID, max ID, total items) for all content types.
*/
private function get_content_range( $config ) {
$range = array();
// Only when we are sending the whole range do we want to send also the range.
if ( true === isset( $config['posts'] ) && $config['posts'] ) {
$range['posts'] = $this->get_range( 'posts' );
}
if ( true === isset( $config['comments'] ) && $config['comments'] ) {
$range['comments'] = $this->get_range( 'comments' );
}
return $range;
}
/**
* Update the progress after sync modules actions have been processed on the server.
*
* @access public
*
* @param array $actions Actions that have been processed on the server.
*/
public function update_sent_progress_action( $actions ) {
// Quick way to map to first items with an array of arrays.
$actions_with_counts = array_count_values( array_filter( array_map( array( $this, 'get_action_name' ), $actions ) ) );
// Total item counts for each action.
$actions_with_total_counts = $this->get_actions_totals( $actions );
if ( ! $this->is_started() || $this->is_finished() ) {
return;
}
if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
$this->update_status_option( 'send_started', time() );
}
foreach ( Modules::get_modules() as $module ) {
$module_actions = $module->get_full_sync_actions();
$status_option_name = "{$module->name()}_sent";
$total_option_name = "{$status_option_name}_total";
$items_sent = $this->get_status_option( $status_option_name, 0 );
$items_sent_total = $this->get_status_option( $total_option_name, 0 );
foreach ( $module_actions as $module_action ) {
if ( isset( $actions_with_counts[ $module_action ] ) ) {
$items_sent += $actions_with_counts[ $module_action ];
}
if ( ! empty( $actions_with_total_counts[ $module_action ] ) ) {
$items_sent_total += $actions_with_total_counts[ $module_action ];
}
}
if ( $items_sent > 0 ) {
$this->update_status_option( $status_option_name, $items_sent );
}
if ( 0 !== $items_sent_total ) {
$this->update_status_option( $total_option_name, $items_sent_total );
}
}
if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
$this->update_status_option( 'finished', time() );
}
}
/**
* Get the name of the action for an item in the sync queue.
*
* @access public
*
* @param array $queue_item Item of the sync queue.
* @return string|boolean Name of the action, false if queue item is invalid.
*/
public function get_action_name( $queue_item ) {
if ( is_array( $queue_item ) && isset( $queue_item[0] ) ) {
return $queue_item[0];
}
return false;
}
/**
* Retrieve the total number of items we're syncing in a particular queue item (action).
* `$queue_item[1]` is expected to contain chunks of items, and `$queue_item[1][0]`
* represents the first (and only) chunk of items to sync in that action.
*
* @access public
*
* @param array $queue_item Item of the sync queue that corresponds to a particular action.
* @return int Total number of items in the action.
*/
public function get_action_totals( $queue_item ) {
if ( is_array( $queue_item ) && isset( $queue_item[1][0] ) ) {
if ( is_array( $queue_item[1][0] ) ) {
// Let's count the items we sync in this action.
return count( $queue_item[1][0] );
}
// -1 indicates that this action syncs all items by design.
return -1;
}
return 0;
}
/**
* Retrieve the total number of items for a set of actions, grouped by action name.
*
* @access public
*
* @param array $actions An array of actions.
* @return array An array, representing the total number of items, grouped per action.
*/
public function get_actions_totals( $actions ) {
$totals = array();
foreach ( $actions as $action ) {
$name = $this->get_action_name( $action );
$action_totals = $this->get_action_totals( $action );
if ( ! isset( $totals[ $name ] ) ) {
$totals[ $name ] = 0;
}
$totals[ $name ] += $action_totals;
}
return $totals;
}
/**
* Whether full sync has started.
*
* @access public
*
* @return boolean
*/
public function is_started() {
return ! ! $this->get_status_option( 'started' );
}
/**
* Whether full sync has finished.
*
* @access public
*
* @return boolean
*/
public function is_finished() {
return ! ! $this->get_status_option( 'finished' );
}
/**
* Retrieve the status of the current full sync.
*
* @access public
*
* @return array Full sync status.
*/
public function get_status() {
$status = array(
'started' => $this->get_status_option( 'started' ),
'queue_finished' => $this->get_status_option( 'queue_finished' ),
'send_started' => $this->get_status_option( 'send_started' ),
'finished' => $this->get_status_option( 'finished' ),
'sent' => array(),
'sent_total' => array(),
'queue' => array(),
'config' => $this->get_status_option( 'params' ),
'total' => array(),
);
$enqueue_status = $this->get_enqueue_status();
foreach ( Modules::get_modules() as $module ) {
$name = $module->name();
if ( ! isset( $enqueue_status[ $name ] ) ) {
continue;
}
list( $total, $queued ) = $enqueue_status[ $name ];
if ( $total ) {
$status['total'][ $name ] = $total;
}
if ( $queued ) {
$status['queue'][ $name ] = $queued;
}
$sent = $this->get_status_option( "{$name}_sent" );
if ( $sent ) {
$status['sent'][ $name ] = $sent;
}
$sent_total = $this->get_status_option( "{$name}_sent_total" );
if ( $sent_total ) {
$status['sent_total'][ $name ] = $sent_total;
}
}
return $status;
}
/**
* Clear all the full sync status options.
*
* @access public
*/
public function clear_status() {
$prefix = self::STATUS_OPTION_PREFIX;
\Jetpack_Options::delete_raw_option( "{$prefix}_started" );
\Jetpack_Options::delete_raw_option( "{$prefix}_params" );
\Jetpack_Options::delete_raw_option( "{$prefix}_queue_finished" );
\Jetpack_Options::delete_raw_option( "{$prefix}_send_started" );
\Jetpack_Options::delete_raw_option( "{$prefix}_finished" );
$this->delete_enqueue_status();
foreach ( Modules::get_modules() as $module ) {
\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent" );
\Jetpack_Options::delete_raw_option( "{$prefix}_{$module->name()}_sent_total" );
}
}
/**
* Clear all the full sync data.
*
* @access public
*/
public function reset_data() {
$this->clear_status();
$this->delete_config();
$listener = Listener::get_instance();
$listener->get_full_sync_queue()->reset();
}
/**
* Get the value of a full sync status option.
*
* @access private
*
* @param string $name Name of the option.
* @param mixed $default Default value of the option.
* @return mixed Option value.
*/
private function get_status_option( $name, $default = null ) {
$value = \Jetpack_Options::get_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $default );
return is_numeric( $value ) ? intval( $value ) : $value;
}
/**
* Update the value of a full sync status option.
*
* @access private
*
* @param string $name Name of the option.
* @param mixed $value Value of the option.
* @param boolean $autoload Whether the option should be autoloaded at the beginning of the request.
*/
private function update_status_option( $name, $value, $autoload = false ) {
\Jetpack_Options::update_raw_option( self::STATUS_OPTION_PREFIX . "_$name", $value, $autoload );
}
/**
* Set the full sync enqueue status.
*
* @access private
*
* @param array $new_status The new full sync enqueue status.
*/
private function set_enqueue_status( $new_status ) {
\Jetpack_Options::update_raw_option( 'jetpack_sync_full_enqueue_status', $new_status );
}
/**
* Delete full sync enqueue status.
*
* @access private
*
* @return boolean Whether the status was deleted.
*/
private function delete_enqueue_status() {
return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_enqueue_status' );
}
/**
* Retrieve the current full sync enqueue status.
*
* @access private
*
* @return array Full sync enqueue status.
*/
private function get_enqueue_status() {
return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_enqueue_status' );
}
/**
* Set the full sync enqueue configuration.
*
* @access private
*
* @param array $config The new full sync enqueue configuration.
*/
private function set_config( $config ) {
\Jetpack_Options::update_raw_option( 'jetpack_sync_full_config', $config );
}
/**
* Delete full sync configuration.
*
* @access private
*
* @return boolean Whether the configuration was deleted.
*/
private function delete_config() {
return \Jetpack_Options::delete_raw_option( 'jetpack_sync_full_config' );
}
/**
* Retrieve the current full sync enqueue config.
*
* @access private
*
* @return array Full sync enqueue config.
*/
private function get_config() {
return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
}
/**
* Update an option manually to bypass filters and caching.
*
* @access private
*
* @param string $name Option name.
* @param mixed $value Option value.
* @return int The number of updated rows in the database.
*/
private function write_option( $name, $value ) {
// We write our own option updating code to bypass filters/caching/etc on set_option/get_option.
global $wpdb;
$serialized_value = maybe_serialize( $value );
/**
* Try updating, if no update then insert
* TODO: try to deal with the fact that unchanged values can return updated_num = 0
* below we used "insert ignore" to at least suppress the resulting error.
*/
$updated_num = $wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
$serialized_value,
$name
)
);
if ( ! $updated_num ) {
$updated_num = $wpdb->query(
$wpdb->prepare(
"INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, 'no' )",
$name,
$serialized_value
)
);
}
return $updated_num;
}
/**
* Update an option manually to bypass filters and caching.
*
* @access private
*
* @param string $name Option name.
* @param mixed $default Default option value.
* @return mixed Option value.
*/
private function read_option( $name, $default = null ) {
global $wpdb;
$value = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
$name
)
);
$value = maybe_unserialize( $value );
if ( null === $value && null !== $default ) {
return $default;
}
return $value;
}
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* Import sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Settings;
/**
* Class to handle sync for imports.
*/
class Import extends Module {
/**
* Tracks which actions have already been synced for the import
* to prevent the same event from being triggered a second time.
*
* @var array
*/
private $synced_actions = array();
/**
* A mapping of action types to sync action name.
* Keys are the name of the import action.
* Values are the resulting sync action.
*
* Note: import_done and import_end both intentionally map to
* jetpack_sync_import_end, as they both track the same type of action,
* the successful completion of an import. Different import plugins use
* differently named actions, and this is an attempt to consolidate.
*
* @var array
*/
private static $import_sync_action_map = array(
'import_start' => 'jetpack_sync_import_start',
'import_done' => 'jetpack_sync_import_end',
'import_end' => 'jetpack_sync_import_end',
);
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'import';
}
/**
* Initialize imports action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'export_wp', $callable );
add_action( 'jetpack_sync_import_start', $callable, 10, 2 );
add_action( 'jetpack_sync_import_end', $callable, 10, 2 );
// WordPress.
add_action( 'import_start', array( $this, 'sync_import_action' ) );
// Movable type, RSS, Livejournal.
add_action( 'import_done', array( $this, 'sync_import_action' ) );
// WordPress, Blogger, Livejournal, woo tax rate.
add_action( 'import_end', array( $this, 'sync_import_action' ) );
}
/**
* Set module defaults.
* Define an empty list of synced actions for us to fill later.
*
* @access public
*/
public function set_defaults() {
$this->synced_actions = array();
}
/**
* Generic handler for import actions.
*
* @access public
*
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
*/
public function sync_import_action( $importer ) {
$import_action = current_filter();
// Map action to event name.
$sync_action = self::$import_sync_action_map[ $import_action ];
// Only sync each action once per import.
if ( array_key_exists( $sync_action, $this->synced_actions ) && $this->synced_actions[ $sync_action ] ) {
return;
}
// Mark this action as synced.
$this->synced_actions[ $sync_action ] = true;
// Prefer self-reported $importer value.
if ( ! $importer ) {
// Fall back to inferring by calling class name.
$importer = self::get_calling_importer_class();
}
// Get $importer from known_importers.
$known_importers = Settings::get_setting( 'known_importers' );
if ( isset( $known_importers[ $importer ] ) ) {
$importer = $known_importers[ $importer ];
}
$importer_name = $this->get_importer_name( $importer );
switch ( $sync_action ) {
case 'jetpack_sync_import_start':
/**
* Used for syncing the start of an import
*
* @since 7.3.0
*
* @module sync
*
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
* @param string $importer_name The name reported by the importer, or 'Unknown Importer'.
*/
do_action( 'jetpack_sync_import_start', $importer, $importer_name );
break;
case 'jetpack_sync_import_end':
/**
* Used for syncing the end of an import
*
* @since 7.3.0
*
* @module sync
*
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
* @param string $importer_name The name reported by the importer, or 'Unknown Importer'.
*/
do_action( 'jetpack_sync_import_end', $importer, $importer_name );
break;
}
}
/**
* Retrieve the name of the importer.
*
* @access private
*
* @param string $importer Either a string reported by the importer, the class name of the importer, or 'unknown'.
* @return string Name of the importer, or "Unknown Importer" if importer is unknown.
*/
private function get_importer_name( $importer ) {
$importers = get_importers();
return isset( $importers[ $importer ] ) ? $importers[ $importer ][0] : 'Unknown Importer';
}
/**
* Determine the class that extends `WP_Importer` which is responsible for
* the current action. Designed to be used within an action handler.
*
* @access private
* @static
*
* @return string The name of the calling class, or 'unknown'.
*/
private static function get_calling_importer_class() {
// If WP_Importer doesn't exist, neither will any importer that extends it.
if ( ! class_exists( 'WP_Importer', false ) ) {
return 'unknown';
}
$action = current_filter();
$backtrace = debug_backtrace( false ); //phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.debug_backtrace_optionsFound,WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
$do_action_pos = -1;
$backtrace_len = count( $backtrace );
for ( $i = 0; $i < $backtrace_len; $i++ ) {
// Find the location in the stack of the calling action.
if ( 'do_action' === $backtrace[ $i ]['function'] && $action === $backtrace[ $i ]['args'][0] ) {
$do_action_pos = $i;
break;
}
}
// If the action wasn't called, the calling class is unknown.
if ( -1 === $do_action_pos ) {
return 'unknown';
}
// Continue iterating the stack looking for a caller that extends WP_Importer.
for ( $i = $do_action_pos + 1; $i < $backtrace_len; $i++ ) {
// If there is no class on the trace, continue.
if ( ! isset( $backtrace[ $i ]['class'] ) ) {
continue;
}
$class_name = $backtrace[ $i ]['class'];
// Check if the class extends WP_Importer.
if ( class_exists( $class_name, false ) ) {
$parents = class_parents( $class_name, false );
if ( $parents && in_array( 'WP_Importer', $parents, true ) ) {
return $class_name;
}
}
}
// If we've exhausted the stack without a match, the calling class is unknown.
return 'unknown';
}
}

View File

@@ -0,0 +1,143 @@
<?php
/**
* Menus sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
/**
* Class to handle sync for menus.
*/
class Menus extends Module {
/**
* Navigation menu items that were added but not synced yet.
*
* @access private
*
* @var array
*/
private $nav_items_just_added = array();
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'menus';
}
/**
* Initialize menus action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'wp_create_nav_menu', $callable, 10, 2 );
add_action( 'wp_update_nav_menu', array( $this, 'update_nav_menu' ), 10, 2 );
add_action( 'wp_add_nav_menu_item', array( $this, 'update_nav_menu_add_item' ), 10, 3 );
add_action( 'wp_update_nav_menu_item', array( $this, 'update_nav_menu_update_item' ), 10, 3 );
add_action( 'post_updated', array( $this, 'remove_just_added_menu_item' ), 10, 2 );
add_action( 'jetpack_sync_updated_nav_menu', $callable, 10, 2 );
add_action( 'jetpack_sync_updated_nav_menu_add_item', $callable, 10, 4 );
add_action( 'jetpack_sync_updated_nav_menu_update_item', $callable, 10, 4 );
add_action( 'delete_nav_menu', $callable, 10, 3 );
}
/**
* Nav menu update handler.
*
* @access public
*
* @param int $menu_id ID of the menu.
* @param array $menu_data An array of menu data.
*/
public function update_nav_menu( $menu_id, $menu_data = array() ) {
if ( empty( $menu_data ) ) {
return;
}
/**
* Helps sync log that a nav menu was updated.
*
* @since 5.0.0
*
* @param int $menu_id ID of the menu.
* @param array $menu_data An array of menu data.
*/
do_action( 'jetpack_sync_updated_nav_menu', $menu_id, $menu_data );
}
/**
* Nav menu item addition handler.
*
* @access public
*
* @param int $menu_id ID of the menu.
* @param int $nav_item_id ID of the new menu item.
* @param array $nav_item_args Arguments used to add the menu item.
*/
public function update_nav_menu_add_item( $menu_id, $nav_item_id, $nav_item_args ) {
$menu_data = wp_get_nav_menu_object( $menu_id );
$this->nav_items_just_added[] = $nav_item_id;
/**
* Helps sync log that a new menu item was added.
*
* @since 5.0.0
*
* @param int $menu_id ID of the menu.
* @param array $menu_data An array of menu data.
* @param int $nav_item_id ID of the new menu item.
* @param array $nav_item_args Arguments used to add the menu item.
*/
do_action( 'jetpack_sync_updated_nav_menu_add_item', $menu_id, $menu_data, $nav_item_id, $nav_item_args );
}
/**
* Nav menu item update handler.
*
* @access public
*
* @param int $menu_id ID of the menu.
* @param int $nav_item_id ID of the new menu item.
* @param array $nav_item_args Arguments used to update the menu item.
*/
public function update_nav_menu_update_item( $menu_id, $nav_item_id, $nav_item_args ) {
if ( in_array( $nav_item_id, $this->nav_items_just_added, true ) ) {
return;
}
$menu_data = wp_get_nav_menu_object( $menu_id );
/**
* Helps sync log that an update to the menu item happened.
*
* @since 5.0.0
*
* @param int $menu_id ID of the menu.
* @param array $menu_data An array of menu data.
* @param int $nav_item_id ID of the new menu item.
* @param array $nav_item_args Arguments used to update the menu item.
*/
do_action( 'jetpack_sync_updated_nav_menu_update_item', $menu_id, $menu_data, $nav_item_id, $nav_item_args );
}
/**
* Remove menu items that have already been saved from the "just added" list.
*
* @access public
*
* @param int $nav_item_id ID of the new menu item.
* @param \WP_Post $post_after Nav menu item post object after the update.
*/
public function remove_just_added_menu_item( $nav_item_id, $post_after ) {
if ( 'nav_menu_item' !== $post_after->post_type ) {
return;
}
$this->nav_items_just_added = array_diff( $this->nav_items_just_added, array( $nav_item_id ) );
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Meta sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
/**
* Class to handle sync for meta.
*/
class Meta extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'meta';
}
/**
* This implementation of get_objects_by_id() is a bit hacky since we're not passing in an array of meta IDs,
* but instead an array of post or comment IDs for which to retrieve meta for. On top of that,
* we also pass in an associative array where we expect there to be 'meta_key' and 'ids' keys present.
*
* This seemed to be required since if we have missing meta on WP.com and need to fetch it, we don't know what
* the meta key is, but we do know that we have missing meta for a given post or comment.
*
* @todo Refactor the $wpdb->prepare call to use placeholders.
*
* @param string $object_type The type of object for which we retrieve meta. Either 'post' or 'comment'.
* @param array $config Must include 'meta_key' and 'ids' keys.
*
* @return array
*/
public function get_objects_by_id( $object_type, $config ) {
global $wpdb;
$table = _get_meta_table( $object_type );
if ( ! $table ) {
return array();
}
if ( ! isset( $config['meta_key'] ) || ! isset( $config['ids'] ) || ! is_array( $config['ids'] ) ) {
return array();
}
$meta_key = $config['meta_key'];
$ids = $config['ids'];
$object_id_column = $object_type . '_id';
// Sanitize so that the array only has integer values.
$ids_string = implode( ', ', array_map( 'intval', $ids ) );
$metas = $wpdb->get_results(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM {$table} WHERE {$object_id_column} IN ( {$ids_string} ) AND meta_key = %s",
$meta_key
)
);
$meta_objects = array();
foreach ( (array) $metas as $meta_object ) {
$meta_object = (array) $meta_object;
$meta_objects[ $meta_object[ $object_id_column ] ] = array(
'meta_type' => $object_type,
'meta_id' => $meta_object['meta_id'],
'meta_key' => $meta_key,
'meta_value' => $meta_object['meta_value'],
'object_id' => $meta_object[ $object_id_column ],
);
}
return $meta_objects;
}
}

View File

@@ -0,0 +1,376 @@
<?php
/**
* A base abstraction of a sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Listener;
/**
* Basic methods implemented by Jetpack Sync extensions.
*
* @abstract
*/
abstract class Module {
/**
* Number of items per chunk when grouping objects for performance reasons.
*
* @access public
*
* @var int
*/
const ARRAY_CHUNK_SIZE = 10;
/**
* Sync module name.
*
* @access public
*
* @return string
*/
abstract public function name();
// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Retrieve a sync object by its ID.
*
* @access public
*
* @param string $object_type Type of the sync object.
* @param int $id ID of the sync object.
* @return mixed Object, or false if the object is invalid.
*/
public function get_object_by_id( $object_type, $id ) {
return false;
}
/**
* Initialize callables action listeners.
* Override these to set up listeners and set/reset data/defaults.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
}
/**
* Initialize module action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
}
/**
* Set module defaults.
*
* @access public
*/
public function set_defaults() {
}
/**
* Perform module cleanup.
* Usually triggered when uninstalling the plugin.
*
* @access public
*/
public function reset_data() {
}
/**
* Enqueue the module actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
// In subclasses, return the number of actions enqueued, and next module state (true == done).
return array( null, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
// In subclasses, return the number of items yet to be enqueued.
return null;
}
// phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array();
}
/**
* Get the number of actions that we care about.
*
* @access protected
*
* @param array $action_names Action names we're interested in.
* @param array $actions_to_count Unfiltered list of actions we want to count.
* @return array Number of actions that we're interested in.
*/
protected function count_actions( $action_names, $actions_to_count ) {
return count( array_intersect( $action_names, $actions_to_count ) );
}
/**
* Calculate the checksum of one or more values.
*
* @access protected
*
* @param mixed $values Values to calculate checksum for.
* @return int The checksum.
*/
protected function get_check_sum( $values ) {
return crc32( wp_json_encode( jetpack_json_wrap( $values ) ) );
}
/**
* Whether a particular checksum in a set of checksums is valid.
*
* @access protected
*
* @param array $sums_to_check Array of checksums.
* @param string $name Name of the checksum.
* @param int $new_sum Checksum to compare against.
* @return boolean Whether the checksum is valid.
*/
protected function still_valid_checksum( $sums_to_check, $name, $new_sum ) {
if ( isset( $sums_to_check[ $name ] ) && $sums_to_check[ $name ] === $new_sum ) {
return true;
}
return false;
}
/**
* Enqueue all items of a sync type as an action.
*
* @access protected
*
* @param string $action_name Name of the action.
* @param string $table_name Name of the database table.
* @param string $id_field Name of the ID field in the database.
* @param string $where_sql The SQL WHERE clause to filter to the desired items.
* @param int $max_items_to_enqueue Maximum number of items to enqueue in the same time.
* @param boolean $state Whether enqueueing has finished.
* @return array Array, containing the number of chunks and TRUE, indicating enqueueing has finished.
*/
protected function enqueue_all_ids_as_action( $action_name, $table_name, $id_field, $where_sql, $max_items_to_enqueue, $state ) {
global $wpdb;
if ( ! $where_sql ) {
$where_sql = '1 = 1';
}
$items_per_page = 1000;
$page = 1;
$chunk_count = 0;
$previous_interval_end = $state ? $state : '~0';
$listener = Listener::get_instance();
// Count down from max_id to min_id so we get newest posts/comments/etc first.
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
while ( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} AND {$id_field} < {$previous_interval_end} ORDER BY {$id_field} DESC LIMIT {$items_per_page}" ) ) {
// Request posts in groups of N for efficiency.
$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
// If we hit our row limit, process and return.
if ( $chunk_count + count( $chunked_ids ) >= $max_items_to_enqueue ) {
$remaining_items_count = $max_items_to_enqueue - $chunk_count;
$remaining_items = array_slice( $chunked_ids, 0, $remaining_items_count );
$remaining_items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $remaining_items, $previous_interval_end );
$listener->bulk_enqueue_full_sync_actions( $action_name, $remaining_items_with_previous_interval_end );
$last_chunk = end( $remaining_items );
return array( $remaining_items_count + $chunk_count, end( $last_chunk ) );
}
$chunked_ids_with_previous_end = $this->get_chunks_with_preceding_end( $chunked_ids, $previous_interval_end );
$listener->bulk_enqueue_full_sync_actions( $action_name, $chunked_ids_with_previous_end );
$chunk_count += count( $chunked_ids );
$page++;
// The $ids are ordered in descending order.
$previous_interval_end = end( $ids );
}
return array( $chunk_count, true );
}
/**
* Retrieve chunk IDs with previous interval end.
*
* @access protected
*
* @param array $chunks All remaining items.
* @param int $previous_interval_end The last item from the previous interval.
* @return array Chunk IDs with the previous interval end.
*/
protected function get_chunks_with_preceding_end( $chunks, $previous_interval_end ) {
$chunks_with_ends = array();
foreach ( $chunks as $chunk ) {
$chunks_with_ends[] = array(
'ids' => $chunk,
'previous_end' => $previous_interval_end,
);
// Chunks are ordered in descending order.
$previous_interval_end = end( $chunk );
}
return $chunks_with_ends;
}
/**
* Get metadata of a particular object type within the designated meta key whitelist.
*
* @access protected
*
* @todo Refactor to use $wpdb->prepare() on the SQL query.
*
* @param array $ids Object IDs.
* @param string $meta_type Meta type.
* @param array $meta_key_whitelist Meta key whitelist.
* @return array Unserialized meta values.
*/
protected function get_metadata( $ids, $meta_type, $meta_key_whitelist ) {
global $wpdb;
$table = _get_meta_table( $meta_type );
$id = $meta_type . '_id';
if ( ! $table ) {
return array();
}
$private_meta_whitelist_sql = "'" . implode( "','", array_map( 'esc_sql', $meta_key_whitelist ) ) . "'";
return array_map(
array( $this, 'unserialize_meta' ),
$wpdb->get_results(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
"SELECT $id, meta_key, meta_value, meta_id FROM $table WHERE $id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )' .
" AND meta_key IN ( $private_meta_whitelist_sql ) ",
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
OBJECT
)
);
}
/**
* Initialize listeners for the particular meta type.
*
* @access public
*
* @param string $meta_type Meta type.
* @param callable $callable Action handler callable.
*/
public function init_listeners_for_meta_type( $meta_type, $callable ) {
add_action( "added_{$meta_type}_meta", $callable, 10, 4 );
add_action( "updated_{$meta_type}_meta", $callable, 10, 4 );
add_action( "deleted_{$meta_type}_meta", $callable, 10, 4 );
}
/**
* Initialize meta whitelist handler for the particular meta type.
*
* @access public
*
* @param string $meta_type Meta type.
* @param callable $whitelist_handler Action handler callable.
*/
public function init_meta_whitelist_handler( $meta_type, $whitelist_handler ) {
add_filter( "jetpack_sync_before_enqueue_added_{$meta_type}_meta", $whitelist_handler );
add_filter( "jetpack_sync_before_enqueue_updated_{$meta_type}_meta", $whitelist_handler );
add_filter( "jetpack_sync_before_enqueue_deleted_{$meta_type}_meta", $whitelist_handler );
}
/**
* Retrieve the term relationships for the specified object IDs.
*
* @access protected
*
* @todo This feels too specific to be in the abstract sync Module class. Move it?
*
* @param array $ids Object IDs.
* @return array Term relationships - object ID and term taxonomy ID pairs.
*/
protected function get_term_relationships( $ids ) {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id IN ( " . implode( ',', wp_parse_id_list( $ids ) ) . ' )', OBJECT );
}
/**
* Unserialize the value of a meta object, if necessary.
*
* @access public
*
* @param object $meta Meta object.
* @return object Meta object with possibly unserialized value.
*/
public function unserialize_meta( $meta ) {
$meta->meta_value = maybe_unserialize( $meta->meta_value );
return $meta;
}
/**
* Retrieve a set of objects by their IDs.
*
* @access public
*
* @param string $object_type Object type.
* @param array $ids Object IDs.
* @return array Array of objects.
*/
public function get_objects_by_id( $object_type, $ids ) {
if ( empty( $ids ) || empty( $object_type ) ) {
return array();
}
$objects = array();
foreach ( (array) $ids as $id ) {
$object = $this->get_object_by_id( $object_type, $id );
// Only add object if we have the object.
if ( $object ) {
$objects[ $id ] = $object;
}
}
return $objects;
}
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* Network Options sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
/**
* Class to handle sync for network options.
*/
class Network_Options extends Module {
/**
* Whitelist for network options we want to sync.
*
* @access private
*
* @var array
*/
private $network_options_whitelist;
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'network_options';
}
/**
* Initialize network options action listeners when on multisite.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
if ( ! is_multisite() ) {
return;
}
// Multi site network options.
add_action( 'add_site_option', $callable, 10, 2 );
add_action( 'update_site_option', $callable, 10, 3 );
add_action( 'delete_site_option', $callable, 10, 1 );
$whitelist_network_option_handler = array( $this, 'whitelist_network_options' );
add_filter( 'jetpack_sync_before_enqueue_delete_site_option', $whitelist_network_option_handler );
add_filter( 'jetpack_sync_before_enqueue_add_site_option', $whitelist_network_option_handler );
add_filter( 'jetpack_sync_before_enqueue_update_site_option', $whitelist_network_option_handler );
}
/**
* Initialize network options action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_network_options', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
if ( ! is_multisite() ) {
return;
}
// Full sync.
add_filter(
'jetpack_sync_before_send_jetpack_full_sync_network_options',
array(
$this,
'expand_network_options',
)
);
}
/**
* Set module defaults.
* Define the network options whitelist based on the default one.
*
* @access public
*/
public function set_defaults() {
$this->network_options_whitelist = Defaults::$default_network_options_whitelist;
}
/**
* Enqueue the network options actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! is_multisite() ) {
return array( null, true );
}
/**
* Tells the client to sync all options to the server
*
* @since 4.2.0
*
* @param boolean Whether to expand options (should always be true)
*/
do_action( 'jetpack_full_sync_network_options', true );
// The number of actions enqueued, and next module state (true == done).
return array( 1, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! is_multisite() ) {
return null;
}
return 1;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_network_options' );
}
/**
* Retrieve all network options as per the current network options whitelist.
*
* @access public
*
* @return array All network options.
*/
public function get_all_network_options() {
$options = array();
foreach ( $this->network_options_whitelist as $option ) {
$options[ $option ] = get_site_option( $option );
}
return $options;
}
/**
* Set the network options whitelist.
*
* @access public
*
* @param array $options The new network options whitelist.
*/
public function set_network_options_whitelist( $options ) {
$this->network_options_whitelist = $options;
}
/**
* Get the network options whitelist.
*
* @access public
*
* @return array The network options whitelist.
*/
public function get_network_options_whitelist() {
return $this->network_options_whitelist;
}
/**
* Reject non-whitelisted network options.
*
* @access public
*
* @param array $args The hook parameters.
* @return array|false $args The hook parameters, false if not a whitelisted network option.
*/
public function whitelist_network_options( $args ) {
if ( ! $this->is_whitelisted_network_option( $args[0] ) ) {
return false;
}
return $args;
}
/**
* Whether the option is a whitelisted network option in a multisite system.
*
* @access public
*
* @param string $option Option name.
* @return boolean True if this is a whitelisted network option.
*/
public function is_whitelisted_network_option( $option ) {
return is_multisite() && in_array( $option, $this->network_options_whitelist, true );
}
/**
* Expand the network options within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function expand_network_options( $args ) {
if ( $args[0] ) {
return $this->get_all_network_options();
}
return $args;
}
}

View File

@@ -0,0 +1,344 @@
<?php
/**
* Options sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
/**
* Class to handle sync for options.
*/
class Options extends Module {
/**
* Whitelist for options we want to sync.
*
* @access private
*
* @var array
*/
private $options_whitelist;
/**
* Contentless options we want to sync.
*
* @access private
*
* @var array
*/
private $options_contentless;
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'options';
}
/**
* Initialize options action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
// Options.
add_action( 'added_option', $callable, 10, 2 );
add_action( 'updated_option', $callable, 10, 3 );
add_action( 'deleted_option', $callable, 10, 1 );
// Sync Core Icon: Detect changes in Core's Site Icon and make it syncable.
add_action( 'add_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) );
$whitelist_option_handler = array( $this, 'whitelist_options' );
add_filter( 'jetpack_sync_before_enqueue_deleted_option', $whitelist_option_handler );
add_filter( 'jetpack_sync_before_enqueue_added_option', $whitelist_option_handler );
add_filter( 'jetpack_sync_before_enqueue_updated_option', $whitelist_option_handler );
}
/**
* Initialize options action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_options', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_options', array( $this, 'expand_options' ) );
}
/**
* Set module defaults.
* Define the options whitelist and contentless options.
*
* @access public
*/
public function set_defaults() {
$this->update_options_whitelist();
$this->update_options_contentless();
}
/**
* Set module defaults at a later time.
*
* @access public
*/
public function set_late_default() {
/** This filter is already documented in json-endpoints/jetpack/class.wpcom-json-api-get-option-endpoint.php */
$late_options = apply_filters( 'jetpack_options_whitelist', array() );
if ( ! empty( $late_options ) && is_array( $late_options ) ) {
$this->options_whitelist = array_merge( $this->options_whitelist, $late_options );
}
}
/**
* Enqueue the options actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Tells the client to sync all options to the server
*
* @since 4.2.0
*
* @param boolean Whether to expand options (should always be true)
*/
do_action( 'jetpack_full_sync_options', true );
// The number of actions enqueued, and next module state (true == done).
return array( 1, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return int Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return 1;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_options' );
}
/**
* Retrieve all options as per the current options whitelist.
* Public so that we don't have to store so much data all the options twice.
*
* @access public
*
* @return array All options.
*/
public function get_all_options() {
$options = array();
$random_string = wp_generate_password();
foreach ( $this->options_whitelist as $option ) {
$option_value = get_option( $option, $random_string );
if ( $option_value !== $random_string ) {
$options[ $option ] = $option_value;
}
}
// Add theme mods.
$theme_mods_option = 'theme_mods_' . get_option( 'stylesheet' );
$theme_mods_value = get_option( $theme_mods_option, $random_string );
if ( $theme_mods_value === $random_string ) {
return $options;
}
$this->filter_theme_mods( $theme_mods_value );
$options[ $theme_mods_option ] = $theme_mods_value;
return $options;
}
/**
* Update the options whitelist to the default one.
*
* @access public
*/
public function update_options_whitelist() {
$this->options_whitelist = Defaults::get_options_whitelist();
}
/**
* Set the options whitelist.
*
* @access public
*
* @param array $options The new options whitelist.
*/
public function set_options_whitelist( $options ) {
$this->options_whitelist = $options;
}
/**
* Get the options whitelist.
*
* @access public
*
* @return array The options whitelist.
*/
public function get_options_whitelist() {
return $this->options_whitelist;
}
/**
* Update the contentless options to the defaults.
*
* @access public
*/
public function update_options_contentless() {
$this->options_contentless = Defaults::get_options_contentless();
}
/**
* Get the contentless options.
*
* @access public
*
* @return array Array of the contentless options.
*/
public function get_options_contentless() {
return $this->options_contentless;
}
/**
* Reject any options that aren't whitelisted or contentless.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function whitelist_options( $args ) {
// Reject non-whitelisted options.
if ( ! $this->is_whitelisted_option( $args[0] ) ) {
return false;
}
// Filter our weird array( false ) value for theme_mods_*.
if ( 'theme_mods_' === substr( $args[0], 0, 11 ) ) {
$this->filter_theme_mods( $args[1] );
if ( isset( $args[2] ) ) {
$this->filter_theme_mods( $args[2] );
}
}
// Set value(s) of contentless option to empty string(s).
if ( $this->is_contentless_option( $args[0] ) ) {
// Create a new array matching length of $args, containing empty strings.
$empty = array_fill( 0, count( $args ), '' );
$empty[0] = $args[0];
return $empty;
}
return $args;
}
/**
* Whether a certain option is whitelisted for sync.
*
* @access public
*
* @param string $option Option name.
* @return boolean Whether the option is whitelisted.
*/
public function is_whitelisted_option( $option ) {
return in_array( $option, $this->options_whitelist, true ) || 'theme_mods_' === substr( $option, 0, 11 );
}
/**
* Whether a certain option is a contentless one.
*
* @access private
*
* @param string $option Option name.
* @return boolean Whether the option is contentless.
*/
private function is_contentless_option( $option ) {
return in_array( $option, $this->options_contentless, true );
}
/**
* Filters out falsy values from theme mod options.
*
* @access private
*
* @param array $value Option value.
*/
private function filter_theme_mods( &$value ) {
if ( is_array( $value ) && isset( $value[0] ) ) {
unset( $value[0] );
}
}
/**
* Handle changes in the core site icon and sync them.
*
* @access public
*/
public function jetpack_sync_core_icon() {
$url = get_site_icon_url();
require_once JETPACK__PLUGIN_DIR . 'modules/site-icon/site-icon-functions.php';
// If there's a core icon, maybe update the option. If not, fall back to Jetpack's.
if ( ! empty( $url ) && jetpack_site_icon_url() !== $url ) {
// This is the option that is synced with dotcom.
\Jetpack_Options::update_option( 'site_icon_url', $url );
} elseif ( empty( $url ) ) {
\Jetpack_Options::delete_option( 'site_icon_url' );
}
}
/**
* Expand all options within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function expand_options( $args ) {
if ( $args[0] ) {
return $this->get_all_options();
}
return $args;
}
}

View File

@@ -0,0 +1,413 @@
<?php
/**
* Plugins sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
/**
* Class to handle sync for plugins.
*/
class Plugins extends Module {
/**
* Action handler callable.
*
* @access private
*
* @var callable
*/
private $action_handler;
/**
* Information about plugins we store temporarily.
*
* @access private
*
* @var array
*/
private $plugin_info = array();
/**
* List of all plugins in the installation.
*
* @access private
*
* @var array
*/
private $plugins = array();
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'plugins';
}
/**
* Initialize plugins action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
$this->action_handler = $callable;
add_action( 'deleted_plugin', array( $this, 'deleted_plugin' ), 10, 2 );
add_action( 'activated_plugin', $callable, 10, 2 );
add_action( 'deactivated_plugin', $callable, 10, 2 );
add_action( 'delete_plugin', array( $this, 'delete_plugin' ) );
add_filter( 'upgrader_pre_install', array( $this, 'populate_plugins' ), 10, 1 );
add_action( 'upgrader_process_complete', array( $this, 'on_upgrader_completion' ), 10, 2 );
add_action( 'jetpack_plugin_installed', $callable, 10, 1 );
add_action( 'jetpack_plugin_update_failed', $callable, 10, 4 );
add_action( 'jetpack_plugins_updated', $callable, 10, 2 );
add_action( 'admin_action_update', array( $this, 'check_plugin_edit' ) );
add_action( 'jetpack_edited_plugin', $callable, 10, 2 );
add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'plugin_edit_ajax' ), 0 );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_activated_plugin', array( $this, 'expand_plugin_data' ) );
add_filter( 'jetpack_sync_before_send_deactivated_plugin', array( $this, 'expand_plugin_data' ) );
// Note that we don't simply 'expand_plugin_data' on the 'delete_plugin' action here because the plugin file is deleted when that action finishes.
}
/**
* Fetch and populate all current plugins before upgrader installation.
*
* @access public
*
* @param bool|WP_Error $response Install response, true if successful, WP_Error if not.
*/
public function populate_plugins( $response ) {
$this->plugins = get_plugins();
return $response;
}
/**
* Handler for the upgrader success finishes.
*
* @access public
*
* @param \WP_Upgrader $upgrader Upgrader instance.
* @param array $details Array of bulk item update data.
*/
public function on_upgrader_completion( $upgrader, $details ) {
if ( ! isset( $details['type'] ) ) {
return;
}
if ( 'plugin' !== $details['type'] ) {
return;
}
if ( ! isset( $details['action'] ) ) {
return;
}
$plugins = ( isset( $details['plugins'] ) ? $details['plugins'] : null );
if ( empty( $plugins ) ) {
$plugins = ( isset( $details['plugin'] ) ? array( $details['plugin'] ) : null );
}
// For plugin installer.
if ( empty( $plugins ) && method_exists( $upgrader, 'plugin_info' ) ) {
$plugins = array( $upgrader->plugin_info() );
}
if ( empty( $plugins ) ) {
return; // We shouldn't be here.
}
switch ( $details['action'] ) {
case 'update':
$state = array(
'is_autoupdate' => Jetpack_Constants::is_true( 'JETPACK_PLUGIN_AUTOUPDATE' ),
);
$errors = $this->get_errors( $upgrader->skin );
if ( $errors ) {
foreach ( $plugins as $slug ) {
/**
* Sync that a plugin update failed
*
* @since 5.8.0
*
* @module sync
*
* @param string $plugin , Plugin slug
* @param string Error code
* @param string Error message
*/
do_action( 'jetpack_plugin_update_failed', $this->get_plugin_info( $slug ), $errors['code'], $errors['message'], $state );
}
return;
}
/**
* Sync that a plugin update
*
* @since 5.8.0
*
* @module sync
*
* @param array () $plugin, Plugin Data
*/
do_action( 'jetpack_plugins_updated', array_map( array( $this, 'get_plugin_info' ), $plugins ), $state );
break;
case 'install':
}
if ( 'install' === $details['action'] ) {
/**
* Signals to the sync listener that a plugin was installed and a sync action
* reflecting the installation and the plugin info should be sent
*
* @since 5.8.0
*
* @module sync
*
* @param array () $plugin, Plugin Data
*/
do_action( 'jetpack_plugin_installed', array_map( array( $this, 'get_plugin_info' ), $plugins ) );
return;
}
}
/**
* Retrieve the plugin information by a plugin slug.
*
* @access private
*
* @param string $slug Plugin slug.
* @return array Plugin information.
*/
private function get_plugin_info( $slug ) {
$plugins = get_plugins(); // Get the most up to date info.
if ( isset( $plugins[ $slug ] ) ) {
return array_merge( array( 'slug' => $slug ), $plugins[ $slug ] );
};
// Try grabbing the info from before the update.
return isset( $this->plugins[ $slug ] ) ? array_merge( array( 'slug' => $slug ), $this->plugins[ $slug ] ) : array( 'slug' => $slug );
}
/**
* Retrieve upgrade errors.
*
* @access private
*
* @param \Automatic_Upgrader_Skin|\WP_Upgrader_Skin $skin The upgrader skin being used.
* @return array|boolean Error on error, false otherwise.
*/
private function get_errors( $skin ) {
$errors = method_exists( $skin, 'get_errors' ) ? $skin->get_errors() : null;
if ( is_wp_error( $errors ) ) {
$error_code = $errors->get_error_code();
if ( ! empty( $error_code ) ) {
return array(
'code' => $error_code,
'message' => $errors->get_error_message(),
);
}
}
if ( isset( $skin->result ) ) {
$errors = $skin->result;
if ( is_wp_error( $errors ) ) {
return array(
'code' => $errors->get_error_code(),
'message' => $errors->get_error_message(),
);
}
if ( empty( $skin->result ) ) {
return array(
'code' => 'unknown',
'message' => __( 'Unknown Plugin Update Failure', 'jetpack' ),
);
}
}
return false;
}
/**
* Handle plugin edit in the administration.
*
* @access public
*
* @todo The `admin_action_update` hook is called only for logged in users, but maybe implement nonce verification?
*/
public function check_plugin_edit() {
$screen = get_current_screen();
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( 'plugin-editor' !== $screen->base || ! isset( $_POST['newcontent'] ) || ! isset( $_POST['plugin'] ) ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$plugin = $_POST['plugin'];
$plugins = get_plugins();
if ( ! isset( $plugins[ $plugin ] ) ) {
return;
}
/**
* Helps Sync log that a plugin was edited
*
* @since 4.9.0
*
* @param string $plugin, Plugin slug
* @param mixed $plugins[ $plugin ], Array of plugin data
*/
do_action( 'jetpack_edited_plugin', $plugin, $plugins[ $plugin ] );
}
/**
* Handle plugin ajax edit in the administration.
*
* @access public
*
* @todo Update this method to use WP_Filesystem instead of fopen/fclose.
*/
public function plugin_edit_ajax() {
// This validation is based on wp_edit_theme_plugin_file().
$args = wp_unslash( $_POST );
if ( empty( $args['file'] ) ) {
return;
}
$file = $args['file'];
if ( 0 !== validate_file( $file ) ) {
return;
}
if ( ! isset( $args['newcontent'] ) ) {
return;
}
if ( ! isset( $args['nonce'] ) ) {
return;
}
if ( empty( $args['plugin'] ) ) {
return;
}
$plugin = $args['plugin'];
if ( ! current_user_can( 'edit_plugins' ) ) {
return;
}
if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) {
return;
}
$plugins = get_plugins();
if ( ! array_key_exists( $plugin, $plugins ) ) {
return;
}
if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) {
return;
}
$real_file = WP_PLUGIN_DIR . '/' . $file;
if ( ! is_writeable( $real_file ) ) {
return;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
$file_pointer = fopen( $real_file, 'w+' );
if ( false === $file_pointer ) {
return;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
fclose( $file_pointer );
/**
* This action is documented already in this file
*/
do_action( 'jetpack_edited_plugin', $plugin, $plugins[ $plugin ] );
}
/**
* Handle plugin deletion.
*
* @access public
*
* @param string $plugin_path Path to the plugin main file.
*/
public function delete_plugin( $plugin_path ) {
$full_plugin_path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_path;
// Checking for file existence because some sync plugin module tests simulate plugin installation and deletion without putting file on disk.
if ( file_exists( $full_plugin_path ) ) {
$all_plugin_data = get_plugin_data( $full_plugin_path );
$data = array(
'name' => $all_plugin_data['Name'],
'version' => $all_plugin_data['Version'],
);
} else {
$data = array(
'name' => $plugin_path,
'version' => 'unknown',
);
}
$this->plugin_info[ $plugin_path ] = $data;
}
/**
* Invoked after plugin deletion.
*
* @access public
*
* @param string $plugin_path Path to the plugin main file.
* @param boolean $is_deleted Whether the plugin was deleted successfully.
*/
public function deleted_plugin( $plugin_path, $is_deleted ) {
call_user_func( $this->action_handler, $plugin_path, $is_deleted, $this->plugin_info[ $plugin_path ] );
unset( $this->plugin_info[ $plugin_path ] );
}
/**
* Expand the plugins within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The expanded hook parameters.
*/
public function expand_plugin_data( $args ) {
$plugin_path = $args[0];
$plugin_data = array();
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$all_plugins = get_plugins();
if ( isset( $all_plugins[ $plugin_path ] ) ) {
$all_plugin_data = $all_plugins[ $plugin_path ];
$plugin_data['name'] = $all_plugin_data['Name'];
$plugin_data['version'] = $all_plugin_data['Version'];
}
return array(
$args[0],
$args[1],
$plugin_data,
);
}
}

View File

@@ -0,0 +1,645 @@
<?php
/**
* Posts sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use Automattic\Jetpack\Roles;
use Automattic\Jetpack\Sync\Settings;
/**
* Class to handle sync for posts.
*/
class Posts extends Module {
/**
* The post IDs of posts that were just published but not synced yet.
*
* @access private
*
* @var array
*/
private $just_published = array();
/**
* The previous status of posts that we use for calculating post status transitions.
*
* @access private
*
* @var array
*/
private $previous_status = array();
/**
* Action handler callable.
*
* @access private
*
* @var callable
*/
private $action_handler;
/**
* Import end.
*
* @access private
*
* @todo This appears to be unused - let's remove it.
*
* @var boolean
*/
private $import_end = false;
/**
* Default previous post state.
* Used for default previous post status.
*
* @access public
*
* @var string
*/
const DEFAULT_PREVIOUS_STATE = 'new';
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'posts';
}
/**
* Retrieve a post by its ID.
*
* @access public
*
* @param string $object_type Type of the sync object.
* @param int $id ID of the sync object.
* @return \WP_Post|bool Filtered \WP_Post object, or false if the object is not a post.
*/
public function get_object_by_id( $object_type, $id ) {
if ( 'post' === $object_type ) {
$post = get_post( intval( $id ) );
if ( $post ) {
return $this->filter_post_content_and_add_links( $post );
}
}
return false;
}
/**
* Initialize posts action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
$this->action_handler = $callable;
add_action( 'wp_insert_post', array( $this, 'wp_insert_post' ), 11, 3 );
add_action( 'jetpack_sync_save_post', $callable, 10, 4 );
add_action( 'deleted_post', $callable, 10 );
add_action( 'jetpack_published_post', $callable, 10, 2 );
add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_post', array( $this, 'filter_blacklisted_post_types' ) );
// Listen for meta changes.
$this->init_listeners_for_meta_type( 'post', $callable );
$this->init_meta_whitelist_handler( 'post', array( $this, 'filter_meta' ) );
add_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
add_action( 'jetpack_post_meta_batch_delete', $callable, 10, 2 );
}
/**
* Before Akismet's daily cleanup of spam detection metadata.
*
* @access public
*
* @param array $feedback_ids IDs of feedback posts.
*/
public function daily_akismet_meta_cleanup_before( $feedback_ids ) {
remove_action( 'deleted_post_meta', $this->action_handler );
/**
* Used for syncing deletion of batch post meta
*
* @since 6.1.0
*
* @module sync
*
* @param array $feedback_ids feedback post IDs
* @param string $meta_key to be deleted
*/
do_action( 'jetpack_post_meta_batch_delete', $feedback_ids, '_feedback_akismet_values' );
}
/**
* After Akismet's daily cleanup of spam detection metadata.
*
* @access public
*
* @param array $feedback_ids IDs of feedback posts.
*/
public function daily_akismet_meta_cleanup_after( $feedback_ids ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
add_action( 'deleted_post_meta', $this->action_handler );
}
/**
* Initialize posts action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_posts', $callable ); // Also sends post meta.
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_sync_save_post', array( $this, 'expand_jetpack_sync_save_post' ) );
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_post_ids' ) );
}
/**
* Enqueue the posts actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
global $wpdb;
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @todo Use $wpdb->prepare for the SQL query.
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
global $wpdb;
$query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Retrieve the WHERE SQL clause based on the module config.
*
* @access private
*
* @param array $config Full sync configuration for this sync module.
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
*/
private function get_where_sql( $config ) {
$where_sql = Settings::get_blacklisted_post_types_sql();
// Config is a list of post IDs to sync.
if ( is_array( $config ) ) {
$where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
}
return $where_sql;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_posts' );
}
/**
* Process content before send.
*
* @param array $args Arguments of the `wp_insert_post` hook.
*
* @return array
*/
public function expand_jetpack_sync_save_post( $args ) {
list( $post_id, $post, $update, $previous_state ) = $args;
return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
}
/**
* Filter all blacklisted post types.
*
* @param array $args Hook arguments.
* @return array|false Hook arguments, or false if the post type is a blacklisted one.
*/
public function filter_blacklisted_post_types( $args ) {
$post = $args[1];
if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
return false;
}
return $args;
}
/**
* Filter all meta that is not blacklisted, or is stored for a disallowed post type.
*
* @param array $args Hook arguments.
* @return array|false Hook arguments, or false if meta was filtered.
*/
public function filter_meta( $args ) {
if ( $this->is_post_type_allowed( $args[1] ) && $this->is_whitelisted_post_meta( $args[2] ) ) {
return $args;
}
return false;
}
/**
* Whether a post meta key is whitelisted.
*
* @param string $meta_key Meta key.
* @return boolean Whether the post meta key is whitelisted.
*/
public function is_whitelisted_post_meta( $meta_key ) {
// The _wpas_skip_ meta key is used by Publicize.
return in_array( $meta_key, Settings::get_setting( 'post_meta_whitelist' ), true ) || wp_startswith( $meta_key, '_wpas_skip_' );
}
/**
* Whether a post type is allowed.
* A post type will be disallowed if it's present in the post type blacklist.
*
* @param int $post_id ID of the post.
* @return boolean Whether the post type is allowed.
*/
public function is_post_type_allowed( $post_id ) {
$post = get_post( intval( $post_id ) );
if ( $post->post_type ) {
return ! in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true );
}
return false;
}
/**
* Remove the embed shortcode.
*
* @global $wp_embed
*/
public function remove_embed() {
global $wp_embed;
remove_filter( 'the_content', array( $wp_embed, 'run_shortcode' ), 8 );
// remove the embed shortcode since we would do the part later.
remove_shortcode( 'embed' );
// Attempts to embed all URLs in a post.
remove_filter( 'the_content', array( $wp_embed, 'autoembed' ), 8 );
}
/**
* Add the embed shortcode.
*
* @global $wp_embed
*/
public function add_embed() {
global $wp_embed;
add_filter( 'the_content', array( $wp_embed, 'run_shortcode' ), 8 );
// Shortcode placeholder for strip_shortcodes().
add_shortcode( 'embed', '__return_false' );
// Attempts to embed all URLs in a post.
add_filter( 'the_content', array( $wp_embed, 'autoembed' ), 8 );
}
/**
* Expands wp_insert_post to include filtered content
*
* @param \WP_Post $post_object Post object.
*/
public function filter_post_content_and_add_links( $post_object ) {
global $post;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = $post_object;
// Return non existant post.
$post_type = get_post_type_object( $post->post_type );
if ( empty( $post_type ) || ! is_object( $post_type ) ) {
$non_existant_post = new \stdClass();
$non_existant_post->ID = $post->ID;
$non_existant_post->post_modified = $post->post_modified;
$non_existant_post->post_modified_gmt = $post->post_modified_gmt;
$non_existant_post->post_status = 'jetpack_sync_non_registered_post_type';
$non_existant_post->post_type = $post->post_type;
return $non_existant_post;
}
/**
* Filters whether to prevent sending post data to .com
*
* Passing true to the filter will prevent the post data from being sent
* to the WordPress.com.
* Instead we pass data that will still enable us to do a checksum against the
* Jetpacks data but will prevent us from displaying the data on in the API as well as
* other services.
*
* @since 4.2.0
*
* @param boolean false prevent post data from being synced to WordPress.com
* @param mixed $post \WP_Post object
*/
if ( apply_filters( 'jetpack_sync_prevent_sending_post_data', false, $post ) ) {
// We only send the bare necessary object to be able to create a checksum.
$blocked_post = new \stdClass();
$blocked_post->ID = $post->ID;
$blocked_post->post_modified = $post->post_modified;
$blocked_post->post_modified_gmt = $post->post_modified_gmt;
$blocked_post->post_status = 'jetpack_sync_blocked';
$blocked_post->post_type = $post->post_type;
return $blocked_post;
}
// lets not do oembed just yet.
$this->remove_embed();
if ( 0 < strlen( $post->post_password ) ) {
$post->post_password = 'auto-' . wp_generate_password( 10, false );
}
/** This filter is already documented in core. wp-includes/post-template.php */
if ( Settings::get_setting( 'render_filtered_content' ) && $post_type->public ) {
global $shortcode_tags;
/**
* Filter prevents some shortcodes from expanding.
*
* Since we can can expand some type of shortcode better on the .com side and make the
* expansion more relevant to contexts. For example [galleries] and subscription emails
*
* @since 4.5.0
*
* @param array of shortcode tags to remove.
*/
$shortcodes_to_remove = apply_filters(
'jetpack_sync_do_not_expand_shortcodes',
array(
'gallery',
'slideshow',
)
);
$removed_shortcode_callbacks = array();
foreach ( $shortcodes_to_remove as $shortcode ) {
if ( isset( $shortcode_tags[ $shortcode ] ) ) {
$removed_shortcode_callbacks[ $shortcode ] = $shortcode_tags[ $shortcode ];
}
}
array_map( 'remove_shortcode', array_keys( $removed_shortcode_callbacks ) );
$post->post_content_filtered = apply_filters( 'the_content', $post->post_content );
$post->post_excerpt_filtered = apply_filters( 'the_excerpt', $post->post_excerpt );
foreach ( $removed_shortcode_callbacks as $shortcode => $callback ) {
add_shortcode( $shortcode, $callback );
}
}
$this->add_embed();
if ( has_post_thumbnail( $post->ID ) ) {
$image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
$post->featured_image = $image_attributes[0];
}
}
$post->permalink = get_permalink( $post->ID );
$post->shortlink = wp_get_shortlink( $post->ID );
if ( function_exists( 'amp_get_permalink' ) ) {
$post->amp_permalink = amp_get_permalink( $post->ID );
}
return $post;
}
/**
* Handle transition from another post status to a published one.
*
* @param string $new_status New post status.
* @param string $old_status Old post status.
* @param \WP_Post $post Post object.
*/
public function save_published( $new_status, $old_status, $post ) {
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
$this->just_published[ $post->ID ] = true;
}
$this->previous_status[ $post->ID ] = $old_status;
}
/**
* When publishing or updating a post, the Gutenberg editor sends two requests:
* 1. sent to WP REST API endpoint `wp-json/wp/v2/posts/$id`
* 2. sent to wp-admin/post.php `?post=$id&action=edit&classic-editor=1&meta_box=1`
*
* The 2nd request is to update post meta, which is not supported on WP REST API.
* When syncing post data, we will include if this was a meta box update.
*
* @todo Implement nonce verification.
*
* @return boolean Whether this is a Gutenberg meta box update.
*/
public function is_gutenberg_meta_box_update() {
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
return (
isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
'editpost' === $_POST['action'] &&
'1' === $_GET['classic-editor'] &&
'1' === $_GET['meta_box']
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
);
}
/**
* Handler for the wp_insert_post hook.
* Called upon creation of a new post.
*
* @param int $post_ID Post ID.
* @param \WP_Post $post Post object.
* @param boolean $update Whether this is an existing post being updated or not.
*/
public function wp_insert_post( $post_ID, $post = null, $update = null ) {
if ( ! is_numeric( $post_ID ) || is_null( $post ) ) {
return;
}
// Workaround for https://github.com/woocommerce/woocommerce/issues/18007.
if ( $post && 'shop_order' === $post->post_type ) {
$post = get_post( $post_ID );
}
$previous_status = isset( $this->previous_status[ $post_ID ] ) ?
$this->previous_status[ $post_ID ] :
self::DEFAULT_PREVIOUS_STATE;
$just_published = isset( $this->just_published[ $post_ID ] ) ?
$this->just_published[ $post_ID ] :
false;
$state = array(
'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
'previous_status' => $previous_status,
'just_published' => $just_published,
'is_gutenberg_meta_box_update' => $this->is_gutenberg_meta_box_update(),
);
/**
* Filter that is used to add to the post flags ( meta data ) when a post gets published
*
* @since 5.8.0
*
* @param int $post_ID the post ID
* @param mixed $post \WP_Post object
* @param bool $update Whether this is an existing post being updated or not.
* @param mixed $state state
*
* @module sync
*/
do_action( 'jetpack_sync_save_post', $post_ID, $post, $update, $state );
unset( $this->previous_status[ $post_ID ] );
$this->send_published( $post_ID, $post );
}
/**
* Send a published post for sync.
*
* @param int $post_ID Post ID.
* @param \WP_Post $post Post object.
*/
public function send_published( $post_ID, $post ) {
if ( ! isset( $this->just_published[ $post_ID ] ) ) {
return;
}
// Post revisions cause race conditions where this send_published add the action before the actual post gets synced.
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
return;
}
$post_flags = array(
'post_type' => $post->post_type,
);
$author_user_object = get_user_by( 'id', $post->post_author );
if ( $author_user_object ) {
$roles = new Roles();
$post_flags['author'] = array(
'id' => $post->post_author,
'wpcom_user_id' => get_user_meta( $post->post_author, 'wpcom_user_id', true ),
'display_name' => $author_user_object->display_name,
'email' => $author_user_object->user_email,
'translated_role' => $roles->translate_user_to_role( $author_user_object ),
);
}
/**
* Filter that is used to add to the post flags ( meta data ) when a post gets published
*
* @since 4.4.0
*
* @param mixed array post flags that are added to the post
* @param mixed $post \WP_Post object
*/
$flags = apply_filters( 'jetpack_published_post_flags', $post_flags, $post );
/**
* Action that gets synced when a post type gets published.
*
* @since 4.4.0
*
* @param int $post_ID
* @param mixed array $flags post flags that are added to the post
*/
do_action( 'jetpack_published_post', $post_ID, $flags );
unset( $this->just_published[ $post_ID ] );
/**
* Send additional sync action for Activity Log when post is a Customizer publish
*/
if ( 'customize_changeset' === $post->post_type ) {
$post_content = json_decode( $post->post_content, true );
foreach ( $post_content as $key => $value ) {
// Skip if it isn't a widget.
if ( 'widget_' !== substr( $key, 0, strlen( 'widget_' ) ) ) {
continue;
}
// Change key from "widget_archives[2]" to "archives-2".
$key = str_replace( 'widget_', '', $key );
$key = str_replace( '[', '-', $key );
$key = str_replace( ']', '', $key );
global $wp_registered_widgets;
if ( isset( $wp_registered_widgets[ $key ] ) ) {
$widget_data = array(
'name' => $wp_registered_widgets[ $key ]['name'],
'id' => $key,
'title' => $value['value']['title'],
);
do_action( 'jetpack_widget_edited', $widget_data );
}
}
}
}
/**
* Expand post IDs to post objects within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The expanded hook parameters.
*/
public function expand_post_ids( $args ) {
list( $post_ids, $previous_interval_end) = $args;
$posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
$posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
$posts = array_values( $posts ); // Reindex in case posts were deleted.
return array(
$posts,
$this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) ),
$this->get_term_relationships( $post_ids ),
$previous_interval_end,
);
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Protect sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
/**
* Class to handle sync for Protect.
* Logs BruteProtect failed logins via sync.
*/
class Protect extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'protect';
}
/**
* Initialize Protect action listeners.
*
* @access public
*
* @param callable $callback Action handler callable.
*/
public function init_listeners( $callback ) {
add_action( 'jpp_log_failed_attempt', array( $this, 'maybe_log_failed_login_attempt' ) );
add_action( 'jetpack_valid_failed_login_attempt', $callback );
}
/**
* Maybe log a failed login attempt.
*
* @access public
*
* @param array $failed_attempt Failed attempt data.
*/
public function maybe_log_failed_login_attempt( $failed_attempt ) {
$protect = \Jetpack_Protect_Module::instance();
if ( $protect->has_login_ability() && ! Jetpack_Constants::is_true( 'XMLRPC_REQUEST' ) ) {
do_action( 'jetpack_valid_failed_login_attempt', $failed_attempt );
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Stats sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
/**
* Class to handle sync for stats.
*/
class Stats extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'stats';
}
/**
* Initialize stats action listeners.
*
* @access public
*
* @param callable $callback Action handler callable.
*/
public function init_listeners( $callback ) {
add_action( 'jetpack_heartbeat', array( $this, 'sync_site_stats' ), 20 );
add_action( 'jetpack_sync_heartbeat_stats', $callback );
}
/**
* This namespaces the action that we sync.
* So that we can differentiate it from future actions.
*
* @access public
*/
public function sync_site_stats() {
do_action( 'jetpack_sync_heartbeat_stats' );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_sync_heartbeat_stats', array( $this, 'add_stats' ) );
}
/**
* Retrieve the stats data for the site.
*
* @access public
*
* @return array Stats data.
*/
public function add_stats() {
return array( \Jetpack::get_stat_data( false, false ) );
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* Term relationships sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
use Automattic\Jetpack\Sync\Listener;
use Automattic\Jetpack\Sync\Settings;
/**
* Class to handle sync for term relationships.
*/
class Term_Relationships extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'term_relationships';
}
/**
* Initialize term relationships action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_term_relationships', $callable, 10, 2 );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_term_relationships', array( $this, 'expand_term_relationships' ) );
}
/**
* Enqueue the term relationships actions for full sync.
*
* @access public
*
* @todo This method has similarities with Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action. Refactor to keep DRY.
* @see Automattic\Jetpack\Sync\Modules\Module::enqueue_all_ids_as_action
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
global $wpdb;
$items_per_page = 1000;
$chunk_count = 0;
$previous_interval_end = $state ? $state : array(
'object_id' => '~0',
'term_taxonomy_id' => '~0',
);
$listener = Listener::get_instance();
$action_name = 'jetpack_full_sync_term_relationships';
// Count down from max_id to min_id so we get term relationships for the newest posts and terms first.
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
while ( $ids = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id <= {$previous_interval_end['object_id']} AND term_taxonomy_id < {$previous_interval_end['term_taxonomy_id']} ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT {$items_per_page}", ARRAY_A ) ) {
// Request term relationships in groups of N for efficiency.
$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
// If we hit our row limit, process and return.
if ( $chunk_count + count( $chunked_ids ) >= $max_items_to_enqueue ) {
$remaining_items_count = $max_items_to_enqueue - $chunk_count;
$remaining_items = array_slice( $chunked_ids, 0, $remaining_items_count );
$remaining_items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $remaining_items, $previous_interval_end );
$listener->bulk_enqueue_full_sync_actions( $action_name, $remaining_items_with_previous_interval_end );
$last_chunk = end( $remaining_items );
return array( $remaining_items_count + $chunk_count, end( $last_chunk ) );
}
$chunked_ids_with_previous_end = $this->get_chunks_with_preceding_end( $chunked_ids, $previous_interval_end );
$listener->bulk_enqueue_full_sync_actions( $action_name, $chunked_ids_with_previous_end );
$chunk_count += count( $chunked_ids );
// The $ids are ordered in descending order.
$previous_interval_end = end( $ids );
}
return array( $chunk_count, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return int Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
global $wpdb;
$query = "SELECT COUNT(*) FROM $wpdb->term_relationships";
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_term_relationships' );
}
/**
* Expand the term relationships within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The expanded hook parameters.
*/
public function expand_term_relationships( $args ) {
list( $term_relationships, $previous_end ) = $args;
return array(
'term_relationships' => $term_relationships,
'previous_end' => $previous_end,
);
}
}

View File

@@ -0,0 +1,300 @@
<?php
/**
* Terms sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
use Automattic\Jetpack\Sync\Settings;
/**
* Class to handle sync for terms.
*/
class Terms extends Module {
/**
* Whitelist for taxonomies we want to sync.
*
* @access private
*
* @var array
*/
private $taxonomy_whitelist;
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'terms';
}
/**
* Allows WordPress.com servers to retrieve term-related objects via the sync API.
*
* @param string $object_type The type of object.
* @param int $id The id of the object.
*
* @return bool|object A WP_Term object, or a row from term_taxonomy table depending on object type.
*/
public function get_object_by_id( $object_type, $id ) {
global $wpdb;
$object = false;
if ( 'term' === $object_type ) {
$object = get_term( intval( $id ) );
if ( is_wp_error( $object ) && $object->get_error_code() === 'invalid_taxonomy' ) {
// Fetch raw term.
$columns = implode( ', ', array_unique( array_merge( Defaults::$default_term_checksum_columns, array( 'term_group' ) ) ) );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$object = $wpdb->get_row( $wpdb->prepare( "SELECT $columns FROM $wpdb->terms WHERE term_id = %d", $id ) );
}
}
if ( 'term_taxonomy' === $object_type ) {
$columns = implode( ', ', array_unique( array_merge( Defaults::$default_term_taxonomy_checksum_columns, array( 'description' ) ) ) );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$object = $wpdb->get_row( $wpdb->prepare( "SELECT $columns FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $id ) );
}
if ( 'term_relationships' === $object_type ) {
$columns = implode( ', ', Defaults::$default_term_relationships_checksum_columns );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$objects = $wpdb->get_results( $wpdb->prepare( "SELECT $columns FROM $wpdb->term_relationships WHERE object_id = %d", $id ) );
$object = (object) array(
'object_id' => $id,
'relationships' => array_map( array( $this, 'expand_terms_for_relationship' ), $objects ),
);
}
return $object ? $object : false;
}
/**
* Initialize terms action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'created_term', array( $this, 'save_term_handler' ), 10, 3 );
add_action( 'edited_term', array( $this, 'save_term_handler' ), 10, 3 );
add_action( 'jetpack_sync_save_term', $callable );
add_action( 'jetpack_sync_add_term', $callable );
add_action( 'delete_term', $callable, 10, 4 );
add_action( 'set_object_terms', $callable, 10, 6 );
add_action( 'deleted_term_relationships', $callable, 10, 2 );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_term', array( $this, 'filter_blacklisted_taxonomies' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_term', array( $this, 'filter_blacklisted_taxonomies' ) );
}
/**
* Initialize terms action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_terms', $callable, 10, 2 );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_terms', array( $this, 'expand_term_taxonomy_id' ) );
}
/**
* Enqueue the terms actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
global $wpdb;
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_terms', $wpdb->term_taxonomy, 'term_taxonomy_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
* Retrieve the WHERE SQL clause based on the module config.
*
* @access private
*
* @param array $config Full sync configuration for this sync module.
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
*/
private function get_where_sql( $config ) {
$where_sql = Settings::get_blacklisted_taxonomies_sql();
if ( is_array( $config ) ) {
$where_sql .= ' AND term_taxonomy_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
}
return $where_sql;
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return int Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
global $wpdb;
$query = "SELECT count(*) FROM $wpdb->term_taxonomy";
$where_sql = $this->get_where_sql( $config );
if ( $where_sql ) {
$query .= ' WHERE ' . $where_sql;
}
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_terms' );
}
/**
* Handler for creating and updating terms.
*
* @access public
*
* @param int $term_id Term ID.
* @param int $tt_id Term taxonomy ID.
* @param string $taxonomy Taxonomy slug.
*/
public function save_term_handler( $term_id, $tt_id, $taxonomy ) {
if ( class_exists( '\\WP_Term' ) ) {
$term_object = \WP_Term::get_instance( $term_id, $taxonomy );
} else {
$term_object = get_term_by( 'id', $term_id, $taxonomy );
}
$current_filter = current_filter();
if ( 'created_term' === $current_filter ) {
/**
* Fires when the client needs to add a new term
*
* @since 5.0.0
*
* @param object the Term object
*/
do_action( 'jetpack_sync_add_term', $term_object );
return;
}
/**
* Fires when the client needs to update a term
*
* @since 4.2.0
*
* @param object the Term object
*/
do_action( 'jetpack_sync_save_term', $term_object );
}
/**
* Filter blacklisted taxonomies.
*
* @access public
*
* @param array $args Hook args.
* @return array|boolean False if not whitelisted, the original hook args otherwise.
*/
public function filter_blacklisted_taxonomies( $args ) {
$term = $args[0];
if ( in_array( $term->taxonomy, Settings::get_setting( 'taxonomies_blacklist' ), true ) ) {
return false;
}
return $args;
}
/**
* Set the taxonomy whitelist.
*
* @access public
*
* @param array $taxonomies The new taxonomyy whitelist.
*/
public function set_taxonomy_whitelist( $taxonomies ) {
$this->taxonomy_whitelist = $taxonomies;
}
/**
* Set module defaults.
* Define the taxonomy whitelist to be the default one.
*
* @access public
*/
public function set_defaults() {
$this->taxonomy_whitelist = Defaults::$default_taxonomy_whitelist;
}
/**
* Expand the term taxonomy IDs to terms within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The expanded hook parameters.
*/
public function expand_term_taxonomy_id( $args ) {
list( $term_taxonomy_ids, $previous_end ) = $args;
return array(
'terms' => get_terms(
array(
'hide_empty' => false,
'term_taxonomy_id' => $term_taxonomy_ids,
'orderby' => 'term_taxonomy_id',
'order' => 'DESC',
)
),
'previous_end' => $previous_end,
);
}
/**
* Gets a term object based on a given row from the term_relationships database table.
*
* @access public
*
* @param object $relationship A row object from the term_relationships table.
* @return object|bool A term object, or false if term taxonomy doesn't exist.
*/
public function expand_terms_for_relationship( $relationship ) {
return get_term_by( 'term_taxonomy_id', $relationship->term_taxonomy_id );
}
}

View File

@@ -0,0 +1,825 @@
<?php
/**
* Themes sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
/**
* Class to handle sync for themes.
*/
class Themes extends Module {
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'themes';
}
/**
* Initialize themes action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
add_action( 'switch_theme', array( $this, 'sync_theme_support' ), 10, 3 );
add_action( 'jetpack_sync_current_theme_support', $callable, 10, 2 );
add_action( 'upgrader_process_complete', array( $this, 'check_upgrader' ), 10, 2 );
add_action( 'jetpack_installed_theme', $callable, 10, 2 );
add_action( 'jetpack_updated_themes', $callable, 10, 2 );
add_action( 'delete_site_transient_update_themes', array( $this, 'detect_theme_deletion' ) );
add_action( 'jetpack_deleted_theme', $callable, 10, 2 );
add_filter( 'wp_redirect', array( $this, 'detect_theme_edit' ) );
add_action( 'jetpack_edited_theme', $callable, 10, 2 );
add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'theme_edit_ajax' ), 0 );
add_action( 'update_site_option_allowedthemes', array( $this, 'sync_network_allowed_themes_change' ), 10, 4 );
add_action( 'jetpack_network_disabled_themes', $callable, 10, 2 );
add_action( 'jetpack_network_enabled_themes', $callable, 10, 2 );
// Sidebar updates.
add_action( 'update_option_sidebars_widgets', array( $this, 'sync_sidebar_widgets_actions' ), 10, 2 );
add_action( 'jetpack_widget_added', $callable, 10, 4 );
add_action( 'jetpack_widget_removed', $callable, 10, 4 );
add_action( 'jetpack_widget_moved_to_inactive', $callable, 10, 2 );
add_action( 'jetpack_cleared_inactive_widgets', $callable );
add_action( 'jetpack_widget_reordered', $callable, 10, 2 );
add_filter( 'widget_update_callback', array( $this, 'sync_widget_edit' ), 10, 4 );
add_action( 'jetpack_widget_edited', $callable );
}
/**
* Sync handler for a widget edit.
*
* @access public
*
* @todo Implement nonce verification
*
* @param array $instance The current widget instance's settings.
* @param array $new_instance Array of new widget settings.
* @param array $old_instance Array of old widget settings.
* @param \WP_Widget $widget_object The current widget instance.
* @return array The current widget instance's settings.
*/
public function sync_widget_edit( $instance, $new_instance, $old_instance, $widget_object ) {
if ( empty( $old_instance ) ) {
return $instance;
}
// Don't trigger sync action if this is an ajax request, because Customizer makes them during preview before saving changes.
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['customized'] ) ) {
return $instance;
}
$widget = array(
'name' => $widget_object->name,
'id' => $widget_object->id,
'title' => isset( $new_instance['title'] ) ? $new_instance['title'] : '',
);
/**
* Trigger action to alert $callable sync listener that a widget was edited.
*
* @since 5.0.0
*
* @param string $widget_name , Name of edited widget
*/
do_action( 'jetpack_widget_edited', $widget );
return $instance;
}
/**
* Sync handler for network allowed themes change.
*
* @access public
*
* @param string $option Name of the network option.
* @param mixed $value Current value of the network option.
* @param mixed $old_value Old value of the network option.
* @param int $network_id ID of the network.
*/
public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) {
$all_enabled_theme_slugs = array_keys( $value );
if ( count( $old_value ) > count( $value ) ) {
// Suppress jetpack_network_disabled_themes sync action when theme is deleted.
$delete_theme_call = $this->get_delete_theme_call();
if ( ! empty( $delete_theme_call ) ) {
return;
}
$newly_disabled_theme_names = array_keys( array_diff_key( $old_value, $value ) );
$newly_disabled_themes = $this->get_theme_details_for_slugs( $newly_disabled_theme_names );
/**
* Trigger action to alert $callable sync listener that network themes were disabled.
*
* @since 5.0.0
*
* @param mixed $newly_disabled_themes, Array of info about network disabled themes
* @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
*/
do_action( 'jetpack_network_disabled_themes', $newly_disabled_themes, $all_enabled_theme_slugs );
return;
}
$newly_enabled_theme_names = array_keys( array_diff_key( $value, $old_value ) );
$newly_enabled_themes = $this->get_theme_details_for_slugs( $newly_enabled_theme_names );
/**
* Trigger action to alert $callable sync listener that network themes were enabled
*
* @since 5.0.0
*
* @param mixed $newly_enabled_themes , Array of info about network enabled themes
* @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
*/
do_action( 'jetpack_network_enabled_themes', $newly_enabled_themes, $all_enabled_theme_slugs );
}
/**
* Retrieve details for one or more themes by their slugs.
*
* @access private
*
* @param array $theme_slugs Theme slugs.
* @return array Details for the themes.
*/
private function get_theme_details_for_slugs( $theme_slugs ) {
$theme_data = array();
foreach ( $theme_slugs as $slug ) {
$theme = wp_get_theme( $slug );
$theme_data[ $slug ] = array(
'name' => $theme->get( 'Name' ),
'version' => $theme->get( 'Version' ),
'uri' => $theme->get( 'ThemeURI' ),
'slug' => $slug,
);
}
return $theme_data;
}
/**
* Detect a theme edit during a redirect.
*
* @access public
*
* @param string $redirect_url Redirect URL.
* @return string Redirect URL.
*/
public function detect_theme_edit( $redirect_url ) {
$url = wp_parse_url( admin_url( $redirect_url ) );
$theme_editor_url = wp_parse_url( admin_url( 'theme-editor.php' ) );
if ( $theme_editor_url['path'] !== $url['path'] ) {
return $redirect_url;
}
$query_params = array();
wp_parse_str( $url['query'], $query_params );
if (
! isset( $_POST['newcontent'] ) ||
! isset( $query_params['file'] ) ||
! isset( $query_params['theme'] ) ||
! isset( $query_params['updated'] )
) {
return $redirect_url;
}
$theme = wp_get_theme( $query_params['theme'] );
$theme_data = array(
'name' => $theme->get( 'Name' ),
'version' => $theme->get( 'Version' ),
'uri' => $theme->get( 'ThemeURI' ),
);
/**
* Trigger action to alert $callable sync listener that a theme was edited.
*
* @since 5.0.0
*
* @param string $query_params['theme'], Slug of edited theme
* @param string $theme_data, Information about edited them
*/
do_action( 'jetpack_edited_theme', $query_params['theme'], $theme_data );
return $redirect_url;
}
/**
* Handler for AJAX theme editing.
*
* @todo Refactor to use WP_Filesystem instead of fopen()/fclose().
*/
public function theme_edit_ajax() {
$args = wp_unslash( $_POST );
if ( empty( $args['theme'] ) ) {
return;
}
if ( empty( $args['file'] ) ) {
return;
}
$file = $args['file'];
if ( 0 !== validate_file( $file ) ) {
return;
}
if ( ! isset( $args['newcontent'] ) ) {
return;
}
if ( ! isset( $args['nonce'] ) ) {
return;
}
$stylesheet = $args['theme'];
if ( 0 !== validate_file( $stylesheet ) ) {
return;
}
if ( ! current_user_can( 'edit_themes' ) ) {
return;
}
$theme = wp_get_theme( $stylesheet );
if ( ! $theme->exists() ) {
return;
}
$real_file = $theme->get_stylesheet_directory() . '/' . $file;
if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) {
return;
}
if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
return;
}
$editable_extensions = wp_get_theme_file_editable_extensions( $theme );
$allowed_files = array();
foreach ( $editable_extensions as $type ) {
switch ( $type ) {
case 'php':
$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
break;
case 'css':
$style_files = $theme->get_files( 'css', -1 );
$allowed_files['style.css'] = $style_files['style.css'];
$allowed_files = array_merge( $allowed_files, $style_files );
break;
default:
$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
break;
}
}
if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
return;
}
// Ensure file is real.
if ( ! is_file( $real_file ) ) {
return;
}
// Ensure file extension is allowed.
$extension = null;
if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
$extension = strtolower( $matches[1] );
if ( ! in_array( $extension, $editable_extensions, true ) ) {
return;
}
}
if ( ! is_writeable( $real_file ) ) {
return;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
$file_pointer = fopen( $real_file, 'w+' );
if ( false === $file_pointer ) {
return;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
fclose( $file_pointer );
$theme_data = array(
'name' => $theme->get( 'Name' ),
'version' => $theme->get( 'Version' ),
'uri' => $theme->get( 'ThemeURI' ),
);
/**
* This action is documented already in this file.
*/
do_action( 'jetpack_edited_theme', $stylesheet, $theme_data );
}
/**
* Detect a theme deletion.
*
* @access public
*/
public function detect_theme_deletion() {
$delete_theme_call = $this->get_delete_theme_call();
if ( empty( $delete_theme_call ) ) {
return;
}
$slug = $delete_theme_call['args'][0];
$theme = wp_get_theme( $slug );
$theme_data = array(
'name' => $theme->get( 'Name' ),
'version' => $theme->get( 'Version' ),
'uri' => $theme->get( 'ThemeURI' ),
'slug' => $slug,
);
/**
* Signals to the sync listener that a theme was deleted and a sync action
* reflecting the deletion and theme slug should be sent
*
* @since 5.0.0
*
* @param string $slug Theme slug
* @param array $theme_data Theme info Since 5.3
*/
do_action( 'jetpack_deleted_theme', $slug, $theme_data );
}
/**
* Handle an upgrader completion action.
*
* @access public
*
* @param \WP_Upgrader $upgrader The upgrader instance.
* @param array $details Array of bulk item update data.
*/
public function check_upgrader( $upgrader, $details ) {
if ( ! isset( $details['type'] ) ||
'theme' !== $details['type'] ||
is_wp_error( $upgrader->skin->result ) ||
! method_exists( $upgrader, 'theme_info' )
) {
return;
}
if ( 'install' === $details['action'] ) {
$theme = $upgrader->theme_info();
if ( ! $theme instanceof \WP_Theme ) {
return;
}
$theme_info = array(
'name' => $theme->get( 'Name' ),
'version' => $theme->get( 'Version' ),
'uri' => $theme->get( 'ThemeURI' ),
);
/**
* Signals to the sync listener that a theme was installed and a sync action
* reflecting the installation and the theme info should be sent
*
* @since 4.9.0
*
* @param string $theme->theme_root Text domain of the theme
* @param mixed $theme_info Array of abbreviated theme info
*/
do_action( 'jetpack_installed_theme', $theme->stylesheet, $theme_info );
}
if ( 'update' === $details['action'] ) {
$themes = array();
if ( empty( $details['themes'] ) && isset( $details['theme'] ) ) {
$details['themes'] = array( $details['theme'] );
}
foreach ( $details['themes'] as $theme_slug ) {
$theme = wp_get_theme( $theme_slug );
if ( ! $theme instanceof \WP_Theme ) {
continue;
}
$themes[ $theme_slug ] = array(
'name' => $theme->get( 'Name' ),
'version' => $theme->get( 'Version' ),
'uri' => $theme->get( 'ThemeURI' ),
'stylesheet' => $theme->stylesheet,
);
}
if ( empty( $themes ) ) {
return;
}
/**
* Signals to the sync listener that one or more themes was updated and a sync action
* reflecting the update and the theme info should be sent
*
* @since 6.2.0
*
* @param mixed $themes Array of abbreviated theme info
*/
do_action( 'jetpack_updated_themes', $themes );
}
}
/**
* Initialize themes action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_theme_data', $callable );
}
/**
* Handle a theme switch.
*
* @access public
*
* @param string $new_name Name of the new theme.
* @param \WP_Theme $new_theme The new theme.
* @param \WP_Theme $old_theme The previous theme.
*/
public function sync_theme_support( $new_name, $new_theme = null, $old_theme = null ) {
$previous_theme = $this->get_theme_support_info( $old_theme );
/**
* Fires when the client needs to sync theme support info
* Only sends theme support attributes whitelisted in Defaults::$default_theme_support_whitelist
*
* @since 4.2.0
*
* @param array the theme support array
* @param array the previous theme since Jetpack 6.5.0
*/
do_action( 'jetpack_sync_current_theme_support', $this->get_theme_support_info(), $previous_theme );
}
/**
* Enqueue the themes actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Tells the client to sync all theme data to the server
*
* @since 4.2.0
*
* @param boolean Whether to expand theme data (should always be true)
*/
do_action( 'jetpack_full_sync_theme_data', true );
// The number of actions enqueued, and next module state (true == done).
return array( 1, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return 1;
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_theme_data', array( $this, 'expand_theme_data' ) );
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_theme_data' );
}
/**
* Expand the theme within a hook before it is serialized and sent to the server.
*
* @access public
*
* @return array Theme data.
*/
public function expand_theme_data() {
return array( $this->get_theme_support_info() );
}
/**
* Retrieve the name of the widget by the widget ID.
*
* @access public
* @global $wp_registered_widgets
*
* @param string $widget_id Widget ID.
* @return string Name of the widget, or null if not found.
*/
public function get_widget_name( $widget_id ) {
global $wp_registered_widgets;
return ( isset( $wp_registered_widgets[ $widget_id ] ) ? $wp_registered_widgets[ $widget_id ]['name'] : null );
}
/**
* Retrieve the name of the sidebar by the sidebar ID.
*
* @access public
* @global $wp_registered_sidebars
*
* @param string $sidebar_id Sidebar ID.
* @return string Name of the sidebar, or null if not found.
*/
public function get_sidebar_name( $sidebar_id ) {
global $wp_registered_sidebars;
return ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : null );
}
/**
* Sync addition of widgets to a sidebar.
*
* @access public
*
* @param array $new_widgets New widgets.
* @param array $old_widgets Old widgets.
* @param string $sidebar Sidebar ID.
* @return array All widgets that have been moved to the sidebar.
*/
public function sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar ) {
$added_widgets = array_diff( $new_widgets, $old_widgets );
if ( empty( $added_widgets ) ) {
return array();
}
$moved_to_sidebar = array();
$sidebar_name = $this->get_sidebar_name( $sidebar );
// Don't sync jetpack_widget_added if theme was switched.
if ( $this->is_theme_switch() ) {
return array();
}
foreach ( $added_widgets as $added_widget ) {
$moved_to_sidebar[] = $added_widget;
$added_widget_name = $this->get_widget_name( $added_widget );
/**
* Helps Sync log that a widget got added
*
* @since 4.9.0
*
* @param string $sidebar, Sidebar id got changed
* @param string $added_widget, Widget id got added
* @param string $sidebar_name, Sidebar id got changed Since 5.0.0
* @param string $added_widget_name, Widget id got added Since 5.0.0
*/
do_action( 'jetpack_widget_added', $sidebar, $added_widget, $sidebar_name, $added_widget_name );
}
return $moved_to_sidebar;
}
/**
* Sync removal of widgets from a sidebar.
*
* @access public
*
* @param array $new_widgets New widgets.
* @param array $old_widgets Old widgets.
* @param string $sidebar Sidebar ID.
* @param array $inactive_widgets Current inactive widgets.
* @return array All widgets that have been moved to inactive.
*/
public function sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $inactive_widgets ) {
$removed_widgets = array_diff( $old_widgets, $new_widgets );
if ( empty( $removed_widgets ) ) {
return array();
}
$moved_to_inactive = array();
$sidebar_name = $this->get_sidebar_name( $sidebar );
foreach ( $removed_widgets as $removed_widget ) {
// Lets check if we didn't move the widget to in_active_widgets.
if ( isset( $inactive_widgets ) && ! in_array( $removed_widget, $inactive_widgets, true ) ) {
$removed_widget_name = $this->get_widget_name( $removed_widget );
/**
* Helps Sync log that a widgte got removed
*
* @since 4.9.0
*
* @param string $sidebar, Sidebar id got changed
* @param string $removed_widget, Widget id got removed
* @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
* @param string $removed_widget_name, Name of the widget that got removed Since 5.0.0
*/
do_action( 'jetpack_widget_removed', $sidebar, $removed_widget, $sidebar_name, $removed_widget_name );
} else {
$moved_to_inactive[] = $removed_widget;
}
}
return $moved_to_inactive;
}
/**
* Sync a reorder of widgets within a sidebar.
*
* @access public
*
* @todo Refactor serialize() to a json_encode().
*
* @param array $new_widgets New widgets.
* @param array $old_widgets Old widgets.
* @param string $sidebar Sidebar ID.
*/
public function sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar ) {
$added_widgets = array_diff( $new_widgets, $old_widgets );
if ( ! empty( $added_widgets ) ) {
return;
}
$removed_widgets = array_diff( $old_widgets, $new_widgets );
if ( ! empty( $removed_widgets ) ) {
return;
}
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
if ( serialize( $old_widgets ) !== serialize( $new_widgets ) ) {
$sidebar_name = $this->get_sidebar_name( $sidebar );
/**
* Helps Sync log that a sidebar id got reordered
*
* @since 4.9.0
*
* @param string $sidebar, Sidebar id got changed
* @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
*/
do_action( 'jetpack_widget_reordered', $sidebar, $sidebar_name );
}
}
/**
* Handle the update of the sidebars and widgets mapping option.
*
* @access public
*
* @param mixed $old_value The old option value.
* @param mixed $new_value The new option value.
*/
public function sync_sidebar_widgets_actions( $old_value, $new_value ) {
// Don't really know how to deal with different array_values yet.
if (
( isset( $old_value['array_version'] ) && 3 !== $old_value['array_version'] ) ||
( isset( $new_value['array_version'] ) && 3 !== $new_value['array_version'] )
) {
return;
}
$moved_to_inactive_ids = array();
$moved_to_sidebar = array();
foreach ( $new_value as $sidebar => $new_widgets ) {
if ( in_array( $sidebar, array( 'array_version', 'wp_inactive_widgets' ), true ) ) {
continue;
}
$old_widgets = isset( $old_value[ $sidebar ] )
? $old_value[ $sidebar ]
: array();
if ( ! is_array( $new_widgets ) ) {
$new_widgets = array();
}
$moved_to_inactive_recently = $this->sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $new_value['wp_inactive_widgets'] );
$moved_to_inactive_ids = array_merge( $moved_to_inactive_ids, $moved_to_inactive_recently );
$moved_to_sidebar_recently = $this->sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar );
$moved_to_sidebar = array_merge( $moved_to_sidebar, $moved_to_sidebar_recently );
$this->sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar );
}
// Don't sync either jetpack_widget_moved_to_inactive or jetpack_cleared_inactive_widgets if theme was switched.
if ( $this->is_theme_switch() ) {
return;
}
// Treat inactive sidebar a bit differently.
if ( ! empty( $moved_to_inactive_ids ) ) {
$moved_to_inactive_name = array_map( array( $this, 'get_widget_name' ), $moved_to_inactive_ids );
/**
* Helps Sync log that a widgets IDs got moved to in active
*
* @since 4.9.0
*
* @param array $moved_to_inactive_ids, Array of widgets id that moved to inactive id got changed
* @param array $moved_to_inactive_names, Array of widgets names that moved to inactive id got changed Since 5.0.0
*/
do_action( 'jetpack_widget_moved_to_inactive', $moved_to_inactive_ids, $moved_to_inactive_name );
} elseif ( empty( $moved_to_sidebar ) && empty( $new_value['wp_inactive_widgets'] ) && ! empty( $old_value['wp_inactive_widgets'] ) ) {
/**
* Helps Sync log that a got cleared from inactive.
*
* @since 4.9.0
*/
do_action( 'jetpack_cleared_inactive_widgets' );
}
}
/**
* Retrieve the theme data for the current or a specific theme.
*
* @access private
*
* @param \WP_Theme $theme Theme object. Optional, will default to the current theme.
* @return array Theme data.
*/
private function get_theme_support_info( $theme = null ) {
global $_wp_theme_features;
$theme_support = array();
// We are trying to get the current theme info.
if ( null === $theme ) {
$theme = wp_get_theme();
foreach ( Defaults::$default_theme_support_whitelist as $theme_feature ) {
$has_support = current_theme_supports( $theme_feature );
if ( $has_support ) {
$theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ];
}
}
}
$theme_support['name'] = $theme->get( 'Name' );
$theme_support['version'] = $theme->get( 'Version' );
$theme_support['slug'] = $theme->get_stylesheet();
$theme_support['uri'] = $theme->get( 'ThemeURI' );
return $theme_support;
}
/**
* Whether we've deleted a theme in the current request.
*
* @access private
*
* @return boolean True if this is a theme deletion request, false otherwise.
*/
private function get_delete_theme_call() {
// Intentional usage of `debug_backtrace()` for production needs.
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
$backtrace = debug_backtrace();
$delete_theme_call = null;
foreach ( $backtrace as $call ) {
if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
$delete_theme_call = $call;
break;
}
}
return $delete_theme_call;
}
/**
* Whether we've switched to another theme in the current request.
*
* @access private
*
* @return boolean True if this is a theme switch request, false otherwise.
*/
private function is_theme_switch() {
return did_action( 'after_switch_theme' );
}
}

View File

@@ -0,0 +1,496 @@
<?php
/**
* Updates sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
/**
* Class to handle sync for updates.
*/
class Updates extends Module {
/**
* Name of the updates checksum option.
*
* @var string
*/
const UPDATES_CHECKSUM_OPTION_NAME = 'jetpack_updates_sync_checksum';
/**
* WordPress Version.
*
* @access private
*
* @var string
*/
private $old_wp_version = null;
/**
* The current updates.
*
* @access private
*
* @var array
*/
private $updates = array();
/**
* Set module defaults.
*
* @access public
*/
public function set_defaults() {
$this->updates = array();
}
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'updates';
}
/**
* Initialize updates action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
global $wp_version;
$this->old_wp_version = $wp_version;
add_action( 'set_site_transient_update_plugins', array( $this, 'validate_update_change' ), 10, 3 );
add_action( 'set_site_transient_update_themes', array( $this, 'validate_update_change' ), 10, 3 );
add_action( 'set_site_transient_update_core', array( $this, 'validate_update_change' ), 10, 3 );
add_action( 'jetpack_update_plugins_change', $callable );
add_action( 'jetpack_update_themes_change', $callable );
add_action( 'jetpack_update_core_change', $callable );
add_filter(
'jetpack_sync_before_enqueue_jetpack_update_plugins_change',
array(
$this,
'filter_update_keys',
),
10,
2
);
add_filter(
'jetpack_sync_before_enqueue_upgrader_process_complete',
array(
$this,
'filter_upgrader_process_complete',
),
10,
2
);
add_action( 'automatic_updates_complete', $callable );
if ( is_multisite() ) {
add_filter( 'pre_update_site_option_wpmu_upgrade_site', array( $this, 'update_core_network_event' ), 10, 2 );
add_action( 'jetpack_sync_core_update_network', $callable, 10, 3 );
}
// Send data when update completes.
add_action( '_core_updated_successfully', array( $this, 'update_core' ) );
add_action( 'jetpack_sync_core_reinstalled_successfully', $callable );
add_action( 'jetpack_sync_core_autoupdated_successfully', $callable, 10, 2 );
add_action( 'jetpack_sync_core_updated_successfully', $callable, 10, 2 );
}
/**
* Initialize updates action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_updates', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_updates', array( $this, 'expand_updates' ) );
add_filter( 'jetpack_sync_before_send_jetpack_update_themes_change', array( $this, 'expand_themes' ) );
}
/**
* Handle a core network update.
*
* @access public
*
* @param int $wp_db_version Current version of the WordPress database.
* @param int $old_wp_db_version Old version of the WordPress database.
* @return int Current version of the WordPress database.
*/
public function update_core_network_event( $wp_db_version, $old_wp_db_version ) {
global $wp_version;
/**
* Sync event for when core wp network updates to a new db version
*
* @since 5.0.0
*
* @param int $wp_db_version the latest wp_db_version
* @param int $old_wp_db_version previous wp_db_version
* @param string $wp_version the latest wp_version
*/
do_action( 'jetpack_sync_core_update_network', $wp_db_version, $old_wp_db_version, $wp_version );
return $wp_db_version;
}
/**
* Handle a core update.
*
* @access public
*
* @todo Implement nonce or refactor to use `admin_post_{$action}` hooks instead.
*
* @param string $new_wp_version The new WP core version.
*/
public function update_core( $new_wp_version ) {
global $pagenow;
// // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['action'] ) && 'do-core-reinstall' === $_GET['action'] ) {
/**
* Sync event that fires when core reinstall was successful
*
* @since 5.0.0
*
* @param string $new_wp_version the updated WordPress version
*/
do_action( 'jetpack_sync_core_reinstalled_successfully', $new_wp_version );
return;
}
// Core was autoupdated.
if (
'update-core.php' !== $pagenow &&
! Jetpack_Constants::is_true( 'REST_API_REQUEST' ) // WP.com rest api calls should never be marked as a core autoupdate.
) {
/**
* Sync event that fires when core autoupdate was successful
*
* @since 5.0.0
*
* @param string $new_wp_version the updated WordPress version
* @param string $old_wp_version the previous WordPress version
*/
do_action( 'jetpack_sync_core_autoupdated_successfully', $new_wp_version, $this->old_wp_version );
return;
}
/**
* Sync event that fires when core update was successful
*
* @since 5.0.0
*
* @param string $new_wp_version the updated WordPress version
* @param string $old_wp_version the previous WordPress version
*/
do_action( 'jetpack_sync_core_updated_successfully', $new_wp_version, $this->old_wp_version );
}
/**
* Retrieve the checksum for an update.
*
* @access public
*
* @param object $update The update object.
* @param string $transient The transient we're retrieving a checksum for.
* @return int The checksum.
*/
public function get_update_checksum( $update, $transient ) {
$updates = array();
$no_updated = array();
switch ( $transient ) {
case 'update_plugins':
if ( ! empty( $update->response ) && is_array( $update->response ) ) {
foreach ( $update->response as $plugin_slug => $response ) {
if ( ! empty( $plugin_slug ) && isset( $response->new_version ) ) {
$updates[] = array( $plugin_slug => $response->new_version );
}
}
}
if ( ! empty( $update->no_update ) ) {
$no_updated = array_keys( $update->no_update );
}
if ( ! isset( $no_updated['jetpack/jetpack.php'] ) && isset( $updates['jetpack/jetpack.php'] ) ) {
return false;
}
break;
case 'update_themes':
if ( ! empty( $update->response ) && is_array( $update->response ) ) {
foreach ( $update->response as $theme_slug => $response ) {
if ( ! empty( $theme_slug ) && isset( $response['new_version'] ) ) {
$updates[] = array( $theme_slug => $response['new_version'] );
}
}
}
if ( ! empty( $update->checked ) ) {
$no_updated = $update->checked;
}
break;
case 'update_core':
if ( ! empty( $update->updates ) && is_array( $update->updates ) ) {
foreach ( $update->updates as $response ) {
if ( ! empty( $response->response ) && 'latest' === $response->response ) {
continue;
}
if ( ! empty( $response->response ) && isset( $response->packages->full ) ) {
$updates[] = array( $response->response => $response->packages->full );
}
}
}
if ( ! empty( $update->version_checked ) ) {
$no_updated = $update->version_checked;
}
if ( empty( $updates ) ) {
return false;
}
break;
}
if ( empty( $updates ) && empty( $no_updated ) ) {
return false;
}
return $this->get_check_sum( array( $no_updated, $updates ) );
}
/**
* Validate a change coming from an update before sending for sync.
*
* @access public
*
* @param mixed $value Site transient value.
* @param int $expiration Time until transient expiration in seconds.
* @param string $transient Transient name.
*/
public function validate_update_change( $value, $expiration, $transient ) {
$new_checksum = $this->get_update_checksum( $value, $transient );
if ( false === $new_checksum ) {
return;
}
$checksums = get_option( self::UPDATES_CHECKSUM_OPTION_NAME, array() );
if ( isset( $checksums[ $transient ] ) && $checksums[ $transient ] === $new_checksum ) {
return;
}
$checksums[ $transient ] = $new_checksum;
update_option( self::UPDATES_CHECKSUM_OPTION_NAME, $checksums );
if ( 'update_core' === $transient ) {
/**
* Trigger a change to core update that we want to sync.
*
* @since 5.1.0
*
* @param array $value Contains info that tells us what needs updating.
*/
do_action( 'jetpack_update_core_change', $value );
return;
}
if ( empty( $this->updates ) ) {
// Lets add the shutdown method once and only when the updates move from empty to filled with something.
add_action( 'shutdown', array( $this, 'sync_last_event' ), 9 );
}
if ( ! isset( $this->updates[ $transient ] ) ) {
$this->updates[ $transient ] = array();
}
$this->updates[ $transient ][] = $value;
}
/**
* Sync the last update only.
*
* @access public
*/
public function sync_last_event() {
foreach ( $this->updates as $transient => $values ) {
$value = end( $values ); // Only send over the last value.
/**
* Trigger a change to a specific update that we want to sync.
* Triggers one of the following actions:
* - jetpack_{$transient}_change
* - jetpack_update_plugins_change
* - jetpack_update_themes_change
*
* @since 5.1.0
*
* @param array $value Contains info that tells us what needs updating.
*/
do_action( "jetpack_{$transient}_change", $value );
}
}
/**
* Enqueue the updates actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
* Tells the client to sync all updates to the server
*
* @since 4.2.0
*
* @param boolean Whether to expand updates (should always be true)
*/
do_action( 'jetpack_full_sync_updates', true );
// The number of actions enqueued, and next module state (true == done).
return array( 1, true );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return 1;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_updates' );
}
/**
* Retrieve all updates that we're interested in.
*
* @access public
*
* @return array All updates.
*/
public function get_all_updates() {
return array(
'core' => get_site_transient( 'update_core' ),
'plugins' => get_site_transient( 'update_plugins' ),
'themes' => get_site_transient( 'update_themes' ),
);
}
/**
* Remove unnecessary keys from synced updates data.
*
* @access public
*
* @param array $args Hook arguments.
* @return array $args Hook arguments.
*/
public function filter_update_keys( $args ) {
$updates = $args[0];
if ( isset( $updates->no_update ) ) {
unset( $updates->no_update );
}
return $args;
}
/**
* Filter out upgrader object from the completed upgrader action args.
*
* @access public
*
* @param array $args Hook arguments.
* @return array $args Filtered hook arguments.
*/
public function filter_upgrader_process_complete( $args ) {
array_shift( $args );
return $args;
}
/**
* Expand the updates within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function expand_updates( $args ) {
if ( $args[0] ) {
return $this->get_all_updates();
}
return $args;
}
/**
* Expand the themes within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The hook parameters.
*/
public function expand_themes( $args ) {
if ( ! isset( $args[0], $args[0]->response ) ) {
return $args;
}
if ( ! is_array( $args[0]->response ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
trigger_error( 'Warning: Not an Array as expected but -> ' . wp_json_encode( $args[0]->response ) . ' instead', E_USER_WARNING );
return $args;
}
foreach ( $args[0]->response as $stylesheet => &$theme_data ) {
$theme = wp_get_theme( $stylesheet );
$theme_data['name'] = $theme->name;
}
return $args;
}
/**
* Perform module cleanup.
* Deletes any transients and options that this module uses.
* Usually triggered when uninstalling the plugin.
*
* @access public
*/
public function reset_data() {
delete_option( self::UPDATES_CHECKSUM_OPTION_NAME );
}
}

View File

@@ -0,0 +1,843 @@
<?php
/**
* Users sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use Automattic\Jetpack\Sync\Defaults;
/**
* Class to handle sync for users.
*/
class Users extends Module {
/**
* Maximum number of users to sync initially.
*
* @var int
*/
const MAX_INITIAL_SYNC_USERS = 100;
/**
* User flags we care about.
*
* @access protected
*
* @var array
*/
protected $flags = array();
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'users';
}
/**
* Retrieve a user by its ID.
* This is here to support the backfill API.
*
* @access public
*
* @param string $object_type Type of the sync object.
* @param int $id ID of the sync object.
* @return \WP_User|bool Filtered \WP_User object, or false if the object is not a user.
*/
public function get_object_by_id( $object_type, $id ) {
if ( 'user' === $object_type ) {
$user = get_user_by( 'id', intval( $id ) );
if ( $user ) {
return $this->sanitize_user_and_expand( $user );
}
}
return false;
}
/**
* Initialize users action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
// Users.
add_action( 'user_register', array( $this, 'user_register_handler' ) );
add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 );
add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) );
add_action( 'jetpack_sync_add_user', $callable, 10, 2 );
add_action( 'jetpack_sync_register_user', $callable, 10, 2 );
add_action( 'jetpack_sync_save_user', $callable, 10, 2 );
add_action( 'jetpack_sync_user_locale', $callable, 10, 2 );
add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 );
add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 );
add_action( 'jetpack_deleted_user', $callable, 10, 3 );
add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 );
add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 );
// User roles.
add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 );
add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 );
// User capabilities.
add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 );
// User authentication.
add_filter( 'authenticate', array( $this, 'authenticate_handler' ), 1000, 3 );
add_action( 'wp_login', array( $this, 'wp_login_handler' ), 10, 2 );
add_action( 'jetpack_wp_login', $callable, 10, 3 );
add_action( 'wp_logout', $callable, 10, 0 );
add_action( 'wp_masterbar_logout', $callable, 10, 0 );
// Add on init.
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
}
/**
* Initialize users action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_users', $callable );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
}
/**
* Retrieve a user by a user ID or object.
*
* @access private
*
* @param mixed $user User object or ID.
* @return \WP_User User object, or `null` if user invalid/not found.
*/
private function get_user( $user ) {
if ( is_numeric( $user ) ) {
$user = get_user_by( 'id', $user );
}
if ( $user instanceof \WP_User ) {
return $user;
}
return null;
}
/**
* Sanitize a user object.
* Removes the password from the user object because we don't want to sync it.
*
* @access public
*
* @todo Refactor `serialize`/`unserialize` to `wp_json_encode`/`wp_json_decode`.
*
* @param \WP_User $user User object.
* @return \WP_User Sanitized user object.
*/
public function sanitize_user( $user ) {
$user = $this->get_user( $user );
// This creates a new user object and stops the passing of the object by reference.
// // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
$user = unserialize( serialize( $user ) );
if ( is_object( $user ) && is_object( $user->data ) ) {
unset( $user->data->user_pass );
}
return $user;
}
/**
* Expand a particular user.
*
* @access public
*
* @param \WP_User $user User object.
* @return \WP_User Expanded user object.
*/
public function expand_user( $user ) {
if ( ! is_object( $user ) ) {
return null;
}
$user->allowed_mime_types = get_allowed_mime_types( $user );
$user->allcaps = $this->get_real_user_capabilities( $user );
// Only set the user locale if it is different from the site locale.
if ( get_locale() !== get_user_locale( $user->ID ) ) {
$user->locale = get_user_locale( $user->ID );
}
return $user;
}
/**
* Retrieve capabilities we care about for a particular user.
*
* @access public
*
* @param \WP_User $user User object.
* @return array User capabilities.
*/
public function get_real_user_capabilities( $user ) {
$user_capabilities = array();
if ( is_wp_error( $user ) ) {
return $user_capabilities;
}
foreach ( Defaults::get_capabilities_whitelist() as $capability ) {
if ( user_can( $user, $capability ) ) {
$user_capabilities[ $capability ] = true;
}
}
return $user_capabilities;
}
/**
* Retrieve, expand and sanitize a user.
* Can be directly used in the sync user action handlers.
*
* @access public
*
* @param mixed $user User ID or user object.
* @return \WP_User Expanded and sanitized user object.
*/
public function sanitize_user_and_expand( $user ) {
$user = $this->get_user( $user );
$user = $this->expand_user( $user );
return $this->sanitize_user( $user );
}
/**
* Expand the user within a hook before it is serialized and sent to the server.
*
* @access public
*
* @param array $args The hook arguments.
* @return array $args The hook arguments.
*/
public function expand_action( $args ) {
// The first argument is always the user.
list( $user ) = $args;
if ( $user ) {
$args[0] = $this->sanitize_user_and_expand( $user );
return $args;
}
return false;
}
/**
* Expand the user username at login before being sent to the server.
*
* @access public
*
* @param array $args The hook arguments.
* @return array $args Expanded hook arguments.
*/
public function expand_login_username( $args ) {
list( $login, $user, $flags ) = $args;
$user = $this->sanitize_user( $user );
return array( $login, $user, $flags );
}
/**
* Expand the user username at logout before being sent to the server.
*
* @access public
*
* @param array $args The hook arguments.
* @param int $user_id ID of the user.
* @return array $args Expanded hook arguments.
*/
public function expand_logout_username( $args, $user_id ) {
$user = get_userdata( $user_id );
$user = $this->sanitize_user( $user );
$login = '';
if ( is_object( $user ) && is_object( $user->data ) ) {
$login = $user->data->user_login;
}
// If we don't have a user here lets not send anything.
if ( empty( $login ) ) {
return false;
}
return array( $login, $user );
}
/**
* Additional processing is needed for wp_login so we introduce this wrapper handler.
*
* @access public
*
* @param string $user_login The user login.
* @param \WP_User $user The user object.
*/
public function wp_login_handler( $user_login, $user ) {
/**
* Fires when a user is logged into a site.
*
* @since 7.2.0
*
* @param int $user_id The user ID.
* @param \WP_User $user The User Object of the user that currently logged in.
* @param array $params Any Flags that have been added during login.
*/
do_action( 'jetpack_wp_login', $user->ID, $user, $this->get_flags( $user->ID ) );
$this->clear_flags( $user->ID );
}
/**
* A hook for the authenticate event that checks the password strength.
*
* @access public
*
* @param \WP_Error|\WP_User $user The user object, or an error.
* @param string $username The username.
* @param string $password The password used to authenticate.
* @return \WP_Error|\WP_User the same object that was passed into the function.
*/
public function authenticate_handler( $user, $username, $password ) {
// In case of cookie authentication we don't do anything here.
if ( empty( $password ) ) {
return $user;
}
// We are only interested in successful authentication events.
if ( is_wp_error( $user ) || ! ( $user instanceof \WP_User ) ) {
return $user;
}
jetpack_require_lib( 'class.jetpack-password-checker' );
$password_checker = new \Jetpack_Password_Checker( $user->ID );
$test_results = $password_checker->test( $password, true );
// If the password passes tests, we don't do anything.
if ( empty( $test_results['test_results']['failed'] ) ) {
return $user;
}
$this->add_flags(
$user->ID,
array(
'warning' => 'The password failed at least one strength test.',
'failures' => $test_results['test_results']['failed'],
)
);
return $user;
}
/**
* Handler for after the user is deleted.
*
* @access public
*
* @param int $deleted_user_id ID of the deleted user.
* @param int $reassigned_user_id ID of the user the deleted user's posts are reassigned to (if any).
*/
public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) {
$is_multisite = is_multisite();
/**
* Fires when a user is deleted on a site
*
* @since 5.4.0
*
* @param int $deleted_user_id - ID of the deleted user.
* @param int $reassigned_user_id - ID of the user the deleted user's posts are reassigned to (if any).
* @param bool $is_multisite - Whether this site is a multisite installation.
*/
do_action( 'jetpack_deleted_user', $deleted_user_id, $reassigned_user_id, $is_multisite );
}
/**
* Handler for user registration.
*
* @access public
*
* @param int $user_id ID of the deleted user.
*/
public function user_register_handler( $user_id ) {
// Ensure we only sync users who are members of the current blog.
if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
return;
}
if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
}
/**
* Fires when a new user is registered on a site
*
* @since 4.9.0
*
* @param object The WP_User object
*/
do_action( 'jetpack_sync_register_user', $user_id, $this->get_flags( $user_id ) );
$this->clear_flags( $user_id );
}
/**
* Handler for user addition to the current blog.
*
* @access public
*
* @param int $user_id ID of the user.
*/
public function add_user_to_blog_handler( $user_id ) {
// Ensure we only sync users who are members of the current blog.
if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
return;
}
if ( Jetpack_Constants::is_true( 'JETPACK_INVITE_ACCEPTED' ) ) {
$this->add_flags( $user_id, array( 'invitation_accepted' => true ) );
}
/**
* Fires when a user is added on a site
*
* @since 4.9.0
*
* @param object The WP_User object
*/
do_action( 'jetpack_sync_add_user', $user_id, $this->get_flags( $user_id ) );
$this->clear_flags( $user_id );
}
/**
* Handler for user save.
*
* @access public
*
* @param int $user_id ID of the user.
* @param \WP_User $old_user_data User object before the changes.
*/
public function save_user_handler( $user_id, $old_user_data = null ) {
// Ensure we only sync users who are members of the current blog.
if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) {
return;
}
$user = get_user_by( 'id', $user_id );
// Older versions of WP don't pass the old_user_data in ->data.
if ( isset( $old_user_data->data ) ) {
$old_user = $old_user_data->data;
} else {
$old_user = $old_user_data;
}
if ( null !== $old_user && $user->user_pass !== $old_user->user_pass ) {
$this->flags[ $user_id ]['password_changed'] = true;
}
if ( null !== $old_user && $user->data->user_email !== $old_user->user_email ) {
/**
* The '_new_email' user meta is deleted right after the call to wp_update_user
* that got us to this point so if it's still set then this was a user confirming
* their new email address.
*/
if ( 1 === intval( get_user_meta( $user->ID, '_new_email', true ) ) ) {
$this->flags[ $user_id ]['email_changed'] = true;
}
}
/**
* Fires when the client needs to sync an updated user.
*
* @since 4.2.0
*
* @param \WP_User The WP_User object
* @param array State - New since 5.8.0
*/
do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
$this->clear_flags( $user_id );
}
/**
* Handler for user role change.
*
* @access public
*
* @param int $user_id ID of the user.
* @param string $role New user role.
* @param array $old_roles Previous user roles.
*/
public function save_user_role_handler( $user_id, $role, $old_roles = null ) {
$this->add_flags(
$user_id,
array(
'role_changed' => true,
'previous_role' => $old_roles,
)
);
// The jetpack_sync_register_user payload is identical to jetpack_sync_save_user, don't send both.
if ( $this->is_create_user() || $this->is_add_user_to_blog() ) {
return;
}
/**
* This action is documented already in this file
*/
do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
$this->clear_flags( $user_id );
}
/**
* Retrieve current flags for a particular user.
*
* @access public
*
* @param int $user_id ID of the user.
* @return array Current flags of the user.
*/
public function get_flags( $user_id ) {
if ( isset( $this->flags[ $user_id ] ) ) {
return $this->flags[ $user_id ];
}
return array();
}
/**
* Clear the flags of a particular user.
*
* @access public
*
* @param int $user_id ID of the user.
*/
public function clear_flags( $user_id ) {
if ( isset( $this->flags[ $user_id ] ) ) {
unset( $this->flags[ $user_id ] );
}
}
/**
* Add flags to a particular user.
*
* @access public
*
* @param int $user_id ID of the user.
* @param array $flags New flags to add for the user.
*/
public function add_flags( $user_id, $flags ) {
$this->flags[ $user_id ] = wp_parse_args( $flags, $this->get_flags( $user_id ) );
}
/**
* Save the user meta, if we're interested in it.
* Also uses the time to add flags for the user.
*
* @access public
*
* @param int $meta_id ID of the meta object.
* @param int $user_id ID of the user.
* @param string $meta_key Meta key.
* @param mixed $value Meta value.
*/
public function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) {
if ( 'locale' === $meta_key ) {
$this->add_flags( $user_id, array( 'locale_changed' => true ) );
}
$user = get_user_by( 'id', $user_id );
if ( isset( $user->cap_key ) && $meta_key === $user->cap_key ) {
$this->add_flags( $user_id, array( 'capabilities_changed' => true ) );
}
if ( $this->is_create_user() || $this->is_add_user_to_blog() || $this->is_delete_user() ) {
return;
}
if ( isset( $this->flags[ $user_id ] ) ) {
/**
* This action is documented already in this file
*/
do_action( 'jetpack_sync_save_user', $user_id, $this->get_flags( $user_id ) );
}
}
/**
* Enqueue the users actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
global $wpdb;
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_users', $wpdb->usermeta, 'user_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @todo Refactor to prepare the SQL query before executing it.
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
global $wpdb;
$query = "SELECT count(*) FROM $wpdb->usermeta";
$where_sql = $this->get_where_sql( $config );
if ( $where_sql ) {
$query .= ' WHERE ' . $where_sql;
}
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Retrieve the WHERE SQL clause based on the module config.
*
* @access private
*
* @param array $config Full sync configuration for this sync module.
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
*/
private function get_where_sql( $config ) {
global $wpdb;
$query = "meta_key = '{$wpdb->prefix}capabilities'";
// The $config variable is a list of user IDs to sync.
if ( is_array( $config ) ) {
$query .= ' AND user_id IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
}
return $query;
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_users' );
}
/**
* Retrieve initial sync user config.
*
* @access public
*
* @todo Refactor the SQL query to call $wpdb->prepare() before execution.
*
* @return array|boolean IDs of users to initially sync, or false if tbe number of users exceed the maximum.
*/
public function get_initial_sync_user_config() {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$user_ids = $wpdb->get_col( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}user_level' AND meta_value > 0 LIMIT " . ( self::MAX_INITIAL_SYNC_USERS + 1 ) );
if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
return $user_ids;
} else {
return false;
}
}
/**
* Expand the users within a hook before they are serialized and sent to the server.
*
* @access public
*
* @param array $args The hook arguments.
* @return array $args The hook arguments.
*/
public function expand_users( $args ) {
list( $user_ids, $previous_end ) = $args;
return array(
'users' => array_map(
array( $this, 'sanitize_user_and_expand' ),
get_users(
array(
'include' => $user_ids,
'orderby' => 'ID',
'order' => 'DESC',
)
)
),
'previous_end' => $previous_end,
);
}
/**
* Handler for user removal from a particular blog.
*
* @access public
*
* @param int $user_id ID of the user.
* @param int $blog_id ID of the blog.
*/
public function remove_user_from_blog_handler( $user_id, $blog_id ) {
// User is removed on add, see https://github.com/WordPress/WordPress/blob/0401cee8b36df3def8e807dd766adc02b359dfaf/wp-includes/ms-functions.php#L2114.
if ( $this->is_add_new_user_to_blog() ) {
return;
}
$reassigned_user_id = $this->get_reassigned_network_user_id();
// Note that we are in the context of the blog the user is removed from, see https://github.com/WordPress/WordPress/blob/473e1ba73bc5c18c72d7f288447503713d518790/wp-includes/ms-functions.php#L233.
/**
* Fires when a user is removed from a blog on a multisite installation
*
* @since 5.4.0
*
* @param int $user_id - ID of the removed user
* @param int $reassigned_user_id - ID of the user the removed user's posts are reassigned to (if any).
*/
do_action( 'jetpack_removed_user_from_blog', $user_id, $reassigned_user_id );
}
/**
* Whether we're adding a new user to a blog in this request.
*
* @access protected
*
* @return boolean
*/
protected function is_add_new_user_to_blog() {
return $this->is_function_in_backtrace( 'add_new_user_to_blog' );
}
/**
* Whether we're adding an existing user to a blog in this request.
*
* @access protected
*
* @return boolean
*/
protected function is_add_user_to_blog() {
return $this->is_function_in_backtrace( 'add_user_to_blog' );
}
/**
* Whether we're removing a user from a blog in this request.
*
* @access protected
*
* @return boolean
*/
protected function is_delete_user() {
return $this->is_function_in_backtrace( array( 'wp_delete_user', 'remove_user_from_blog' ) );
}
/**
* Whether we're creating a user or adding a new user to a blog.
*
* @access protected
*
* @return boolean
*/
protected function is_create_user() {
$functions = array(
'add_new_user_to_blog', // Used to suppress jetpack_sync_save_user in save_user_cap_handler when user registered on multi site.
'wp_create_user', // Used to suppress jetpack_sync_save_user in save_user_role_handler when user registered on multi site.
'wp_insert_user', // Used to suppress jetpack_sync_save_user in save_user_cap_handler and save_user_role_handler when user registered on single site.
);
return $this->is_function_in_backtrace( $functions );
}
/**
* Retrieve the ID of the user the removed user's posts are reassigned to (if any).
*
* @return int ID of the user that got reassigned as the author of the posts.
*/
protected function get_reassigned_network_user_id() {
$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
foreach ( $backtrace as $call ) {
if (
'remove_user_from_blog' === $call['function'] &&
3 === count( $call['args'] )
) {
return $call['args'][2];
}
}
return false;
}
/**
* Checks if one or more function names is in debug_backtrace.
*
* @access protected
*
* @param array|string $names Mixed string name of function or array of string names of functions.
* @return bool
*/
protected function is_function_in_backtrace( $names ) {
$backtrace = debug_backtrace( false ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
if ( ! is_array( $names ) ) {
$names = array( $names );
}
$names_as_keys = array_flip( $names );
// Do check in constant O(1) time for PHP5.5+.
if ( function_exists( 'array_column' ) ) {
$backtrace_functions = array_column( $backtrace, 'function' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_columnFound
$backtrace_functions_as_keys = array_flip( $backtrace_functions );
$intersection = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
return ! empty( $intersection );
}
// Do check in linear O(n) time for < PHP5.5 ( using isset at least prevents O(n^2) ).
foreach ( $backtrace as $call ) {
if ( isset( $names_as_keys[ $call['function'] ] ) ) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,156 @@
<?php
/**
* WP_Super_Cache sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
/**
* Class to handle sync for WP_Super_Cache.
*/
class WP_Super_Cache extends Module {
/**
* Constructor.
*
* @todo Should we refactor this to use $this->set_defaults() instead?
*/
public function __construct() {
add_filter( 'jetpack_sync_constants_whitelist', array( $this, 'add_wp_super_cache_constants_whitelist' ), 10 );
add_filter( 'jetpack_sync_callable_whitelist', array( $this, 'add_wp_super_cache_callable_whitelist' ), 10 );
}
/**
* Whitelist for constants we are interested to sync.
*
* @access public
* @static
*
* @var array
*/
public static $wp_super_cache_constants = array(
'WPLOCKDOWN',
'WPSC_DISABLE_COMPRESSION',
'WPSC_DISABLE_LOCKING',
'WPSC_DISABLE_HTACCESS_UPDATE',
'ADVANCEDCACHEPROBLEM',
);
/**
* Container for the whitelist for WP_Super_Cache callables we are interested to sync.
*
* @access public
* @static
*
* @var array
*/
public static $wp_super_cache_callables = array(
'wp_super_cache_globals' => array( __CLASS__, 'get_wp_super_cache_globals' ),
);
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'wp-super-cache';
}
/**
* Retrieve all WP_Super_Cache callables we are interested to sync.
*
* @access public
*
* @global $wp_cache_mod_rewrite;
* @global $cache_enabled;
* @global $super_cache_enabled;
* @global $ossdlcdn;
* @global $cache_rebuild_files;
* @global $wp_cache_mobile;
* @global $wp_super_cache_late_init;
* @global $wp_cache_anon_only;
* @global $wp_cache_not_logged_in;
* @global $wp_cache_clear_on_post_edit;
* @global $wp_cache_mobile_enabled;
* @global $wp_super_cache_debug;
* @global $cache_max_time;
* @global $wp_cache_refresh_single_only;
* @global $wp_cache_mfunc_enabled;
* @global $wp_supercache_304;
* @global $wp_cache_no_cache_for_get;
* @global $wp_cache_mutex_disabled;
* @global $cache_jetpack;
* @global $cache_domain_mapping;
*
* @return array All WP_Super_Cache callables.
*/
public static function get_wp_super_cache_globals() {
global $wp_cache_mod_rewrite;
global $cache_enabled;
global $super_cache_enabled;
global $ossdlcdn;
global $cache_rebuild_files;
global $wp_cache_mobile;
global $wp_super_cache_late_init;
global $wp_cache_anon_only;
global $wp_cache_not_logged_in;
global $wp_cache_clear_on_post_edit;
global $wp_cache_mobile_enabled;
global $wp_super_cache_debug;
global $cache_max_time;
global $wp_cache_refresh_single_only;
global $wp_cache_mfunc_enabled;
global $wp_supercache_304;
global $wp_cache_no_cache_for_get;
global $wp_cache_mutex_disabled;
global $cache_jetpack;
global $cache_domain_mapping;
return array(
'wp_cache_mod_rewrite' => $wp_cache_mod_rewrite,
'cache_enabled' => $cache_enabled,
'super_cache_enabled' => $super_cache_enabled,
'ossdlcdn' => $ossdlcdn,
'cache_rebuild_files' => $cache_rebuild_files,
'wp_cache_mobile' => $wp_cache_mobile,
'wp_super_cache_late_init' => $wp_super_cache_late_init,
'wp_cache_anon_only' => $wp_cache_anon_only,
'wp_cache_not_logged_in' => $wp_cache_not_logged_in,
'wp_cache_clear_on_post_edit' => $wp_cache_clear_on_post_edit,
'wp_cache_mobile_enabled' => $wp_cache_mobile_enabled,
'wp_super_cache_debug' => $wp_super_cache_debug,
'cache_max_time' => $cache_max_time,
'wp_cache_refresh_single_only' => $wp_cache_refresh_single_only,
'wp_cache_mfunc_enabled' => $wp_cache_mfunc_enabled,
'wp_supercache_304' => $wp_supercache_304,
'wp_cache_no_cache_for_get' => $wp_cache_no_cache_for_get,
'wp_cache_mutex_disabled' => $wp_cache_mutex_disabled,
'cache_jetpack' => $cache_jetpack,
'cache_domain_mapping' => $cache_domain_mapping,
);
}
/**
* Add WP_Super_Cache constants to the constants whitelist.
*
* @param array $list Existing constants whitelist.
* @return array Updated constants whitelist.
*/
public function add_wp_super_cache_constants_whitelist( $list ) {
return array_merge( $list, self::$wp_super_cache_constants );
}
/**
* Add WP_Super_Cache callables to the callables whitelist.
*
* @param array $list Existing callables whitelist.
* @return array Updated callables whitelist.
*/
public function add_wp_super_cache_callable_whitelist( $list ) {
return array_merge( $list, self::$wp_super_cache_callables );
}
}

View File

@@ -0,0 +1,525 @@
<?php
/**
* WooCommerce sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
/**
* Class to handle sync for WooCommerce.
*/
class WooCommerce extends Module {
/**
* Whitelist for order item meta we are interested to sync.
*
* @access private
*
* @var array
*/
private $order_item_meta_whitelist = array(
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-product-store.php#L20 .
'_product_id',
'_variation_id',
'_qty',
// Tax ones also included in below class
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-fee-data-store.php#L20 .
'_tax_class',
'_tax_status',
'_line_subtotal',
'_line_subtotal_tax',
'_line_total',
'_line_tax',
'_line_tax_data',
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-shipping-data-store.php#L20 .
'method_id',
'cost',
'total_tax',
'taxes',
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-tax-data-store.php#L20 .
'rate_id',
'label',
'compound',
'tax_amount',
'shipping_tax_amount',
// See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-coupon-data-store.php .
'discount_amount',
'discount_amount_tax',
);
/**
* Name of the order item database table.
*
* @access private
*
* @var string
*/
private $order_item_table_name;
/**
* Constructor.
*
* @global $wpdb
*
* @todo Should we refactor this to use $this->set_defaults() instead?
*/
public function __construct() {
global $wpdb;
$this->order_item_table_name = $wpdb->prefix . 'woocommerce_order_items';
// Options, constants and post meta whitelists.
add_filter( 'jetpack_sync_options_whitelist', array( $this, 'add_woocommerce_options_whitelist' ), 10 );
add_filter( 'jetpack_sync_constants_whitelist', array( $this, 'add_woocommerce_constants_whitelist' ), 10 );
add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'add_woocommerce_post_meta_whitelist' ), 10 );
add_filter( 'jetpack_sync_comment_meta_whitelist', array( $this, 'add_woocommerce_comment_meta_whitelist' ), 10 );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_new_order_item', array( $this, 'filter_order_item' ) );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_update_order_item', array( $this, 'filter_order_item' ) );
add_filter( 'jetpack_sync_whitelisted_comment_types', array( $this, 'add_review_comment_types' ) );
}
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'woocommerce';
}
/**
* Initialize WooCommerce action listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
// Attributes.
add_action( 'woocommerce_attribute_added', $callable, 10, 2 );
add_action( 'woocommerce_attribute_updated', $callable, 10, 3 );
add_action( 'woocommerce_attribute_deleted', $callable, 10, 3 );
// Orders.
add_action( 'woocommerce_new_order', $callable, 10, 1 );
add_action( 'woocommerce_order_status_changed', $callable, 10, 3 );
add_action( 'woocommerce_payment_complete', $callable, 10, 1 );
// Order items.
add_action( 'woocommerce_new_order_item', $callable, 10, 4 );
add_action( 'woocommerce_update_order_item', $callable, 10, 4 );
add_action( 'woocommerce_delete_order_item', $callable, 10, 1 );
$this->init_listeners_for_meta_type( 'order_item', $callable );
// Payment tokens.
add_action( 'woocommerce_new_payment_token', $callable, 10, 1 );
add_action( 'woocommerce_payment_token_deleted', $callable, 10, 2 );
add_action( 'woocommerce_payment_token_updated', $callable, 10, 1 );
$this->init_listeners_for_meta_type( 'payment_token', $callable );
// Product downloads.
add_action( 'woocommerce_downloadable_product_download_log_insert', $callable, 10, 1 );
add_action( 'woocommerce_grant_product_download_access', $callable, 10, 1 );
// Tax rates.
add_action( 'woocommerce_tax_rate_added', $callable, 10, 2 );
add_action( 'woocommerce_tax_rate_updated', $callable, 10, 2 );
add_action( 'woocommerce_tax_rate_deleted', $callable, 10, 1 );
// Webhooks.
add_action( 'woocommerce_new_webhook', $callable, 10, 1 );
add_action( 'woocommerce_webhook_deleted', $callable, 10, 2 );
add_action( 'woocommerce_webhook_updated', $callable, 10, 1 );
}
/**
* Initialize WooCommerce action listeners for full sync.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_woocommerce_order_items', $callable ); // Also sends post meta.
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_woocommerce_order_items' );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_woocommerce_order_items', array( $this, 'expand_order_item_ids' ) );
}
/**
* Expand the order items properly.
*
* @access public
*
* @param array $args The hook arguments.
* @return array $args The hook arguments.
*/
public function filter_order_item( $args ) {
// Make sure we always have all the data - prior to WooCommerce 3.0 we only have the user supplied data in the second argument and not the full details.
$args[1] = $this->build_order_item( $args[0] );
return $args;
}
/**
* Expand order item IDs to order items and their meta.
*
* @access public
*
* @todo Refactor table name to use a $wpdb->prepare placeholder.
*
* @param array $args The hook arguments.
* @return array $args Expanded order items with meta.
*/
public function expand_order_item_ids( $args ) {
$order_item_ids = $args[0];
global $wpdb;
$order_item_ids_sql = implode( ', ', array_map( 'intval', $order_item_ids ) );
$order_items = $wpdb->get_results(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM $this->order_item_table_name WHERE order_item_id IN ( $order_item_ids_sql )"
);
return array(
$order_items,
$this->get_metadata( $order_item_ids, 'order_item', $this->order_item_meta_whitelist ),
);
}
/**
* Extract the full order item from the database by its ID.
*
* @access public
*
* @todo Refactor table name to use a $wpdb->prepare placeholder.
*
* @param int $order_item_id Order item ID.
* @return object Order item.
*/
public function build_order_item( $order_item_id ) {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->order_item_table_name WHERE order_item_id = %d", $order_item_id ) );
}
/**
* Enqueue the WooCommerce actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_woocommerce_order_items', $this->order_item_table_name, 'order_item_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @todo Refactor the SQL query to use $wpdb->prepare().
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) {
global $wpdb;
$query = "SELECT count(*) FROM $this->order_item_table_name WHERE " . $this->get_where_sql( $config );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Retrieve the WHERE SQL clause based on the module config.
*
* @access private
*
* @param array $config Full sync configuration for this sync module.
* @return string WHERE SQL clause.
*/
private function get_where_sql( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return '1=1';
}
/**
* Add WooCommerce options to the options whitelist.
*
* @param array $list Existing options whitelist.
* @return array Updated options whitelist.
*/
public function add_woocommerce_options_whitelist( $list ) {
return array_merge( $list, self::$wc_options_whitelist );
}
/**
* Add WooCommerce constants to the constants whitelist.
*
* @param array $list Existing constants whitelist.
* @return array Updated constants whitelist.
*/
public function add_woocommerce_constants_whitelist( $list ) {
return array_merge( $list, self::$wc_constants_whitelist );
}
/**
* Add WooCommerce post meta to the post meta whitelist.
*
* @param array $list Existing post meta whitelist.
* @return array Updated post meta whitelist.
*/
public function add_woocommerce_post_meta_whitelist( $list ) {
return array_merge( $list, self::$wc_post_meta_whitelist );
}
/**
* Add WooCommerce comment meta to the comment meta whitelist.
*
* @param array $list Existing comment meta whitelist.
* @return array Updated comment meta whitelist.
*/
public function add_woocommerce_comment_meta_whitelist( $list ) {
return array_merge( $list, self::$wc_comment_meta_whitelist );
}
/**
* Adds 'revew' to the list of comment types so Sync will listen for status changes on 'reviews'.
*
* @access public
*
* @param array $comment_types The list of comment types prior to this filter.
* return array The list of comment types with 'review' added.
*/
public function add_review_comment_types( $comment_types ) {
if ( is_array( $comment_types ) ) {
$comment_types[] = 'review';
}
return $comment_types;
}
/**
* Whitelist for options we are interested to sync.
*
* @access private
* @static
*
* @var array
*/
private static $wc_options_whitelist = array(
'woocommerce_currency',
'woocommerce_db_version',
'woocommerce_weight_unit',
'woocommerce_version',
'woocommerce_unforce_ssl_checkout',
'woocommerce_tax_total_display',
'woocommerce_tax_round_at_subtotal',
'woocommerce_tax_display_shop',
'woocommerce_tax_display_cart',
'woocommerce_prices_include_tax',
'woocommerce_price_thousand_sep',
'woocommerce_price_num_decimals',
'woocommerce_price_decimal_sep',
'woocommerce_notify_low_stock',
'woocommerce_notify_low_stock_amount',
'woocommerce_notify_no_stock',
'woocommerce_notify_no_stock_amount',
'woocommerce_manage_stock',
'woocommerce_force_ssl_checkout',
'woocommerce_hide_out_of_stock_items',
'woocommerce_file_download_method',
'woocommerce_enable_signup_and_login_from_checkout',
'woocommerce_enable_shipping_calc',
'woocommerce_enable_review_rating',
'woocommerce_enable_guest_checkout',
'woocommerce_enable_coupons',
'woocommerce_enable_checkout_login_reminder',
'woocommerce_enable_ajax_add_to_cart',
'woocommerce_dimension_unit',
'woocommerce_default_country',
'woocommerce_default_customer_address',
'woocommerce_currency_pos',
'woocommerce_api_enabled',
'woocommerce_allow_tracking',
);
/**
* Whitelist for constants we are interested to sync.
*
* @access private
* @static
*
* @var array
*/
private static $wc_constants_whitelist = array(
// WooCommerce constants.
'WC_PLUGIN_FILE',
'WC_ABSPATH',
'WC_PLUGIN_BASENAME',
'WC_VERSION',
'WOOCOMMERCE_VERSION',
'WC_ROUNDING_PRECISION',
'WC_DISCOUNT_ROUNDING_MODE',
'WC_TAX_ROUNDING_MODE',
'WC_DELIMITER',
'WC_LOG_DIR',
'WC_SESSION_CACHE_GROUP',
'WC_TEMPLATE_DEBUG_MODE',
);
/**
* Whitelist for post meta we are interested to sync.
*
* @access private
* @static
*
* @var array
*/
private static $wc_post_meta_whitelist = array(
// WooCommerce products.
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-product-data-store-cpt.php#L21 .
'_visibility',
'_sku',
'_price',
'_regular_price',
'_sale_price',
'_sale_price_dates_from',
'_sale_price_dates_to',
'total_sales',
'_tax_status',
'_tax_class',
'_manage_stock',
'_backorders',
'_sold_individually',
'_weight',
'_length',
'_width',
'_height',
'_upsell_ids',
'_crosssell_ids',
'_purchase_note',
'_default_attributes',
'_product_attributes',
'_virtual',
'_downloadable',
'_download_limit',
'_download_expiry',
'_featured',
'_downloadable_files',
'_wc_rating_count',
'_wc_average_rating',
'_wc_review_count',
'_variation_description',
'_thumbnail_id',
'_file_paths',
'_product_image_gallery',
'_product_version',
'_wp_old_slug',
// Woocommerce orders.
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L27 .
'_order_key',
'_order_currency',
// '_billing_first_name', do not sync these as they contain personal data
// '_billing_last_name',
// '_billing_company',
// '_billing_address_1',
// '_billing_address_2',
'_billing_city',
'_billing_state',
'_billing_postcode',
'_billing_country',
// '_billing_email', do not sync these as they contain personal data.
// '_billing_phone',
// '_shipping_first_name',
// '_shipping_last_name',
// '_shipping_company',
// '_shipping_address_1',
// '_shipping_address_2',
'_shipping_city',
'_shipping_state',
'_shipping_postcode',
'_shipping_country',
'_completed_date',
'_paid_date',
'_cart_discount',
'_cart_discount_tax',
'_order_shipping',
'_order_shipping_tax',
'_order_tax',
'_order_total',
'_payment_method',
'_payment_method_title',
// '_transaction_id', do not sync these as they contain personal data.
// '_customer_ip_address',
// '_customer_user_agent',
'_created_via',
'_order_version',
'_prices_include_tax',
'_date_completed',
'_date_paid',
'_payment_tokens',
'_billing_address_index',
'_shipping_address_index',
'_recorded_sales',
'_recorded_coupon_usage_counts',
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L539 .
'_download_permissions_granted',
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L594 .
'_order_stock_reduced',
// Woocommerce order refunds.
// See https://github.com/woocommerce/woocommerce/blob/b8a2815ae546c836467008739e7ff5150cb08e93/includes/data-stores/class-wc-order-refund-data-store-cpt.php#L20 .
'_order_currency',
'_refund_amount',
'_refunded_by',
'_refund_reason',
'_order_shipping',
'_order_shipping_tax',
'_order_tax',
'_order_total',
'_order_version',
'_prices_include_tax',
'_payment_tokens',
);
/**
* Whitelist for comment meta we are interested to sync.
*
* @access private
* @static
*
* @var array
*/
private static $wc_comment_meta_whitelist = array(
'rating',
);
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* Jetpack_Tracks_Client
* @autounit nosara tracks-client
*
* Send Tracks events on behalf of a user
*
* Example Usage:
```php
require( dirname(__FILE__).'path/to/tracks/class.tracks-client' );
$result = Jetpack_Tracks_Client::record_event( array(
'_en' => $event_name, // required
'_ui' => $user_id, // required unless _ul is provided
'_ul' => $user_login, // required unless _ui is provided
// Optional, but recommended
'_ts' => $ts_in_ms, // Default: now
'_via_ip' => $client_ip, // we use it for geo, etc.
// Possibly useful to set some context for the event
'_via_ua' => $client_user_agent,
'_via_url' => $client_url,
'_via_ref' => $client_referrer,
// For user-targeted tests
'abtest_name' => $abtest_name,
'abtest_variation' => $abtest_variation,
// Your application-specific properties
'custom_property' => $some_value,
) );
if ( is_wp_error( $result ) ) {
// Handle the error in your app
}
```
*/
class Jetpack_Tracks_Client {
const PIXEL = 'https://pixel.wp.com/t.gif';
const BROWSER_TYPE = 'php-agent';
const USER_AGENT_SLUG = 'tracks-client';
const VERSION = '0.3';
/**
* record_event
* @param mixed $event Event object to send to Tracks. An array will be cast to object. Required.
* Properties are included directly in the pixel query string after light validation.
* @return mixed True on success, WP_Error on failure
*/
static function record_event( $event ) {
if ( ! Jetpack::jetpack_tos_agreed() || ! empty( $_COOKIE['tk_opt-out'] ) ) {
return false;
}
if ( ! $event instanceof Jetpack_Tracks_Event ) {
$event = new Jetpack_Tracks_Event( $event );
}
if ( is_wp_error( $event ) ) {
return $event;
}
$pixel = $event->build_pixel_url( $event );
if ( ! $pixel ) {
return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 );
}
return self::record_pixel( $pixel );
}
/**
* Synchronously request the pixel
*/
static function record_pixel( $pixel ) {
// Add the Request Timestamp and URL terminator just before the HTTP request.
$pixel .= '&_rt=' . self::build_timestamp() . '&_=_';
$response = wp_remote_get( $pixel, array(
'blocking' => true, // The default, but being explicit here :)
'timeout' => 1,
'redirection' => 2,
'httpversion' => '1.1',
'user-agent' => self::get_user_agent(),
) );
if ( is_wp_error( $response ) ) {
return $response;
}
$code = isset( $response['response']['code'] ) ? $response['response']['code'] : 0;
if ( $code !== 200 ) {
return new WP_Error( 'request_failed', 'Tracks pixel request failed', $code );
}
return true;
}
static function get_user_agent() {
return Jetpack_Tracks_Client::USER_AGENT_SLUG . '-v' . Jetpack_Tracks_Client::VERSION;
}
/**
* Build an event and return its tracking URL
* @deprecated Call the `build_pixel_url` method on a Jetpack_Tracks_Event object instead.
* @param array $event Event keys and values
* @return string URL of a tracking pixel
*/
static function build_pixel_url( $event ) {
$_event = new Jetpack_Tracks_Event( $event );
return $_event->build_pixel_url();
}
/**
* Validate input for a tracks event.
* @deprecated Instantiate a Jetpack_Tracks_Event object instead
* @param array $event Event keys and values
* @return mixed Validated keys and values or WP_Error on failure
*/
private static function validate_and_sanitize( $event ) {
$_event = new Jetpack_Tracks_Event( $event );
if ( is_wp_error( $_event ) ) {
return $_event;
}
return get_object_vars( $_event );
}
// Milliseconds since 1970-01-01
static function build_timestamp() {
$ts = round( microtime( true ) * 1000 );
return number_format( $ts, 0, '', '' );
}
/**
* Grabs the user's anon id from cookies, or generates and sets a new one
*
* @return string An anon id for the user
*/
static function get_anon_id() {
static $anon_id = null;
if ( ! isset( $anon_id ) ) {
// Did the browser send us a cookie?
if ( isset( $_COOKIE[ 'tk_ai' ] ) && preg_match( '#^[A-Za-z0-9+/=]{24}$#', $_COOKIE[ 'tk_ai' ] ) ) {
$anon_id = $_COOKIE[ 'tk_ai' ];
} else {
$binary = '';
// Generate a new anonId and try to save it in the browser's cookies
// Note that base64-encoding an 18 character string generates a 24-character anon id
for ( $i = 0; $i < 18; ++$i ) {
$binary .= chr( mt_rand( 0, 255 ) );
}
$anon_id = 'jetpack:' . base64_encode( $binary );
if ( ! headers_sent()
&& ! ( defined( 'REST_REQUEST' ) && REST_REQUEST )
&& ! ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
) {
setcookie( 'tk_ai', $anon_id );
}
}
}
return $anon_id;
}
/**
* Gets the WordPress.com user's Tracks identity, if connected.
*
* @return array|bool
*/
static function get_connected_user_tracks_identity() {
if ( ! $user_data = Jetpack::get_connected_user_data() ) {
return false;
}
return array(
'blogid' => Jetpack_Options::get_option( 'id', 0 ),
'userid' => $user_data['ID'],
'username' => $user_data['login'],
);
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
* @autounit nosara tracks-client
*
* Example Usage:
```php
require_once( dirname(__FILE__) . 'path/to/tracks/class.tracks-event' );
$event = new Jetpack_Tracks_Event( array(
'_en' => $event_name, // required
'_ui' => $user_id, // required unless _ul is provided
'_ul' => $user_login, // required unless _ui is provided
// Optional, but recommended
'_via_ip' => $client_ip, // for geo, etc.
// Possibly useful to set some context for the event
'_via_ua' => $client_user_agent,
'_via_url' => $client_url,
'_via_ref' => $client_referrer,
// For user-targeted tests
'abtest_name' => $abtest_name,
'abtest_variation' => $abtest_variation,
// Your application-specific properties
'custom_property' => $some_value,
) );
if ( is_wp_error( $event->error ) ) {
// Handle the error in your app
}
$bump_and_redirect_pixel = $event->build_signed_pixel_url();
```
*/
class Jetpack_Tracks_Event {
const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/';
const PROP_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/';
public $error;
function __construct( $event ) {
$_event = self::validate_and_sanitize( $event );
if ( is_wp_error( $_event ) ) {
$this->error = $_event;
return;
}
foreach( $_event as $key => $value ) {
$this->{$key} = $value;
}
}
function record() {
return Jetpack_Tracks_Client::record_event( $this );
}
/**
* Annotate the event with all relevant info.
* @param mixed $event Object or (flat) array
* @return mixed The transformed event array or WP_Error on failure.
*/
static function validate_and_sanitize( $event ) {
$event = (object) $event;
// Required
if ( ! $event->_en ) {
return new WP_Error( 'invalid_event', 'A valid event must be specified via `_en`', 400 );
}
// delete non-routable addresses otherwise geoip will discard the record entirely
if ( property_exists( $event, '_via_ip' ) && preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) {
unset($event->_via_ip);
}
$validated = array(
'browser_type' => Jetpack_Tracks_Client::BROWSER_TYPE,
'_aua' => Jetpack_Tracks_Client::get_user_agent(),
);
$_event = (object) array_merge( (array) $event, $validated );
// If you want to blacklist property names, do it here.
// Make sure we have an event timestamp.
if ( ! isset( $_event->_ts ) ) {
$_event->_ts = Jetpack_Tracks_Client::build_timestamp();
}
return $_event;
}
/**
* Build a pixel URL that will send a Tracks event when fired.
* On error, returns an empty string ('').
*
* @return string A pixel URL or empty string ('') if there were invalid args.
*/
function build_pixel_url() {
if ( $this->error ) {
return '';
}
$args = get_object_vars( $this );
// Request Timestamp and URL Terminator must be added just before the HTTP request or not at all.
unset( $args['_rt'] );
unset( $args['_'] );
$validated = self::validate_and_sanitize( $args );
if ( is_wp_error( $validated ) )
return '';
return Jetpack_Tracks_Client::PIXEL . '?' . http_build_query( $validated );
}
static function event_name_is_valid( $name ) {
return preg_match( Jetpack_Tracks_Event::EVENT_NAME_REGEX, $name );
}
static function prop_name_is_valid( $name ) {
return preg_match( Jetpack_Tracks_Event::PROP_NAME_REGEX, $name );
}
static function scrutinize_event_names( $event ) {
if ( ! Jetpack_Tracks_Event::event_name_is_valid( $event->_en ) ) {
return;
}
$whitelisted_key_names = array(
'anonId',
'Browser_Type',
);
foreach ( array_keys( (array) $event ) as $key ) {
if ( in_array( $key, $whitelisted_key_names ) ) {
continue;
}
if ( ! Jetpack_Tracks_Event::prop_name_is_valid( $key ) ) {
return;
}
}
}
}

View File

@@ -0,0 +1,168 @@
<?php
/**
* Nosara Tracks for Jetpack
*
* @package jetpack-tracking
*/
namespace Automattic\Jetpack;
/**
* The Tracking class, used to record events in wpcom
*/
class Tracking {
private $product_name;
private $connection;
function __construct( $product_name = 'jetpack', $connection = null ) {
$this->product_name = $product_name;
$this->connection = $connection;
if ( is_null( $this->connection ) ) {
// TODO We should always pass a Connection.
$this->connection = new Connection\Manager();
}
}
function enqueue_tracks_scripts() {
wp_enqueue_script( 'jptracks', plugins_url( '_inc/lib/tracks/tracks-ajax.js', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION, true );
wp_localize_script(
'jptracks',
'jpTracksAJAX',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'jpTracksAJAX_nonce' => wp_create_nonce( 'jp-tracks-ajax-nonce' ),
)
);
}
function record_user_event( $event_type, $data = array(), $user = null ) {
if ( ! $user ) {
$user = wp_get_current_user();
}
$site_url = get_option( 'siteurl' );
$data['_via_ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
$data['_via_ip'] = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
$data['_lg'] = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
$data['blog_url'] = $site_url;
$data['blog_id'] = \Jetpack_Options::get_option( 'id' );
// Top level events should not be namespaced
if ( '_aliasUser' != $event_type ) {
$event_type = $this->product_name . '_' . $event_type;
}
$data['jetpack_version'] = defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '0';
return $this->tracks_record_event( $user, $event_type, $data );
}
/**
* Record an event in Tracks - this is the preferred way to record events from PHP.
*
* @param mixed $identity username, user_id, or WP_user object
* @param string $event_name The name of the event
* @param array $properties Custom properties to send with the event
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred
*
* @return bool true for success | \WP_Error if the event pixel could not be fired
*/
function tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
// We don't want to track user events during unit tests/CI runs.
if ( $user instanceof \WP_User && 'wptests_capabilities' === $user->cap_key ) {
return false;
}
$event_obj = $this->tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis );
if ( is_wp_error( $event_obj->error ) ) {
return $event_obj->error;
}
return $event_obj->record();
}
/**
* Procedurally build a Tracks Event Object.
* NOTE: Use this only when the simpler Automattic\Jetpack\Tracking->jetpack_tracks_record_event() function won't work for you.
*
* @param $identity WP_user object
* @param string $event_name The name of the event
* @param array $properties Custom properties to send with the event
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred
*
* @return \Jetpack_Tracks_Event|\WP_Error
*/
function tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
$identity = $this->tracks_get_identity( $user->ID );
$properties['user_lang'] = $user->get( 'WPLANG' );
$blog_details = array(
'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ),
);
$timestamp = ( $event_timestamp_millis !== false ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
$timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' );
return new \Jetpack_Tracks_Event(
array_merge(
$blog_details,
(array) $properties,
$identity,
array(
'_en' => $event_name,
'_ts' => $timestamp_string,
)
)
);
}
/**
* Get the identity to send to tracks.
*
* @param int $user_id The user id of the local user
*
* @return array $identity
*/
function tracks_get_identity( $user_id ) {
// Meta is set, and user is still connected. Use WPCOM ID
$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
if ( $wpcom_id && $this->connection->is_user_connected( $user_id ) ) {
return array(
'_ut' => 'wpcom:user_id',
'_ui' => $wpcom_id,
);
}
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
if ( $this->connection->is_user_connected( $user_id ) ) {
$wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
return array(
'_ut' => 'wpcom:user_id',
'_ui' => $wpcom_user_data['ID'],
);
}
// User isn't linked at all. Fall back to anonymous ID.
$anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true );
if ( ! $anon_id ) {
$anon_id = \Jetpack_Tracks_Client::get_anon_id();
add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false );
}
if ( ! isset( $_COOKIE['tk_ai'] ) && ! headers_sent() ) {
setcookie( 'tk_ai', $anon_id );
}
return array(
'_ut' => 'anon',
'_ui' => $anon_id,
);
}
}

View File

@@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,73 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Automattic\\Jetpack\\Assets' => $vendorDir . '/automattic/jetpack-assets/src/Assets.php',
'Automattic\\Jetpack\\Assets\\Logo' => $vendorDir . '/automattic/jetpack-logo/src/Logo.php',
'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => $vendorDir . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php',
'Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin' => $vendorDir . '/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php',
'Automattic\\Jetpack\\Connection\\Client' => $vendorDir . '/automattic/jetpack-connection/src/Client.php',
'Automattic\\Jetpack\\Connection\\Manager' => $vendorDir . '/automattic/jetpack-connection/src/Manager.php',
'Automattic\\Jetpack\\Connection\\Manager_Interface' => $vendorDir . '/automattic/jetpack-connection/src/Manager_Interface.php',
'Automattic\\Jetpack\\Connection\\REST_Connector' => $vendorDir . '/automattic/jetpack-connection/src/REST_Connector.php',
'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => $vendorDir . '/automattic/jetpack-connection/src/XMLRPC_Connector.php',
'Automattic\\Jetpack\\Constants' => $vendorDir . '/automattic/jetpack-constants/src/Constants.php',
'Automattic\\Jetpack\\JITM' => $vendorDir . '/automattic/jetpack-jitm/src/JITM.php',
'Automattic\\Jetpack\\Plugin\\Tracking' => $baseDir . '/src/Tracking.php',
'Automattic\\Jetpack\\Roles' => $vendorDir . '/automattic/jetpack-roles/src/Roles.php',
'Automattic\\Jetpack\\Status' => $vendorDir . '/automattic/jetpack-status/src/Status.php',
'Automattic\\Jetpack\\Sync\\Actions' => $vendorDir . '/automattic/jetpack-sync/src/Actions.php',
'Automattic\\Jetpack\\Sync\\Codec_Interface' => $vendorDir . '/automattic/jetpack-sync/src/Codec_Interface.php',
'Automattic\\Jetpack\\Sync\\Defaults' => $vendorDir . '/automattic/jetpack-sync/src/Defaults.php',
'Automattic\\Jetpack\\Sync\\Functions' => $vendorDir . '/automattic/jetpack-sync/src/Functions.php',
'Automattic\\Jetpack\\Sync\\JSON_Deflate_Array_Codec' => $vendorDir . '/automattic/jetpack-sync/src/Json_Deflate_Array_Codec.php',
'Automattic\\Jetpack\\Sync\\Listener' => $vendorDir . '/automattic/jetpack-sync/src/Listener.php',
'Automattic\\Jetpack\\Sync\\Main' => $vendorDir . '/automattic/jetpack-sync/src/Main.php',
'Automattic\\Jetpack\\Sync\\Modules' => $vendorDir . '/automattic/jetpack-sync/src/Modules.php',
'Automattic\\Jetpack\\Sync\\Modules\\Attachments' => $vendorDir . '/automattic/jetpack-sync/src/modules/Attachments.php',
'Automattic\\Jetpack\\Sync\\Modules\\Callables' => $vendorDir . '/automattic/jetpack-sync/src/modules/Callables.php',
'Automattic\\Jetpack\\Sync\\Modules\\Comments' => $vendorDir . '/automattic/jetpack-sync/src/modules/Comments.php',
'Automattic\\Jetpack\\Sync\\Modules\\Constants' => $vendorDir . '/automattic/jetpack-sync/src/modules/Constants.php',
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync' => $vendorDir . '/automattic/jetpack-sync/src/modules/Full_Sync.php',
'Automattic\\Jetpack\\Sync\\Modules\\Import' => $vendorDir . '/automattic/jetpack-sync/src/modules/Import.php',
'Automattic\\Jetpack\\Sync\\Modules\\Menus' => $vendorDir . '/automattic/jetpack-sync/src/modules/Menus.php',
'Automattic\\Jetpack\\Sync\\Modules\\Meta' => $vendorDir . '/automattic/jetpack-sync/src/modules/Meta.php',
'Automattic\\Jetpack\\Sync\\Modules\\Module' => $vendorDir . '/automattic/jetpack-sync/src/modules/Module.php',
'Automattic\\Jetpack\\Sync\\Modules\\Network_Options' => $vendorDir . '/automattic/jetpack-sync/src/modules/Network_Options.php',
'Automattic\\Jetpack\\Sync\\Modules\\Options' => $vendorDir . '/automattic/jetpack-sync/src/modules/Options.php',
'Automattic\\Jetpack\\Sync\\Modules\\Plugins' => $vendorDir . '/automattic/jetpack-sync/src/modules/Plugins.php',
'Automattic\\Jetpack\\Sync\\Modules\\Posts' => $vendorDir . '/automattic/jetpack-sync/src/modules/Posts.php',
'Automattic\\Jetpack\\Sync\\Modules\\Protect' => $vendorDir . '/automattic/jetpack-sync/src/modules/Protect.php',
'Automattic\\Jetpack\\Sync\\Modules\\Stats' => $vendorDir . '/automattic/jetpack-sync/src/modules/Stats.php',
'Automattic\\Jetpack\\Sync\\Modules\\Term_Relationships' => $vendorDir . '/automattic/jetpack-sync/src/modules/Term_Relationships.php',
'Automattic\\Jetpack\\Sync\\Modules\\Terms' => $vendorDir . '/automattic/jetpack-sync/src/modules/Terms.php',
'Automattic\\Jetpack\\Sync\\Modules\\Themes' => $vendorDir . '/automattic/jetpack-sync/src/modules/Themes.php',
'Automattic\\Jetpack\\Sync\\Modules\\Updates' => $vendorDir . '/automattic/jetpack-sync/src/modules/Updates.php',
'Automattic\\Jetpack\\Sync\\Modules\\Users' => $vendorDir . '/automattic/jetpack-sync/src/modules/Users.php',
'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache' => $vendorDir . '/automattic/jetpack-sync/src/modules/WP_Super_Cache.php',
'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce' => $vendorDir . '/automattic/jetpack-sync/src/modules/WooCommerce.php',
'Automattic\\Jetpack\\Sync\\Queue' => $vendorDir . '/automattic/jetpack-sync/src/Queue.php',
'Automattic\\Jetpack\\Sync\\Queue_Buffer' => $vendorDir . '/automattic/jetpack-sync/src/Queue_Buffer.php',
'Automattic\\Jetpack\\Sync\\Replicastore' => $vendorDir . '/automattic/jetpack-sync/src/Replicastore.php',
'Automattic\\Jetpack\\Sync\\Replicastore_Interface' => $vendorDir . '/automattic/jetpack-sync/src/Replicastore_Interface.php',
'Automattic\\Jetpack\\Sync\\Sender' => $vendorDir . '/automattic/jetpack-sync/src/Sender.php',
'Automattic\\Jetpack\\Sync\\Server' => $vendorDir . '/automattic/jetpack-sync/src/Server.php',
'Automattic\\Jetpack\\Sync\\Settings' => $vendorDir . '/automattic/jetpack-sync/src/Settings.php',
'Automattic\\Jetpack\\Sync\\Simple_Codec' => $vendorDir . '/automattic/jetpack-sync/src/Simple_Codec.php',
'Automattic\\Jetpack\\Sync\\Users' => $vendorDir . '/automattic/jetpack-sync/src/Users.php',
'Automattic\\Jetpack\\Sync\\Utils' => $vendorDir . '/automattic/jetpack-sync/src/Utils.php',
'Automattic\\Jetpack\\Tracking' => $vendorDir . '/automattic/jetpack-tracking/src/Tracking.php',
'JetpackTracking' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-tracks.php',
'Jetpack_Client' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-client.php',
'Jetpack_Options' => $vendorDir . '/automattic/jetpack-options/legacy/class.jetpack-options.php',
'Jetpack_Signature' => $vendorDir . '/automattic/jetpack-connection/legacy/class.jetpack-signature.php',
'Jetpack_Sync_Actions' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-sync-actions.php',
'Jetpack_Sync_Modules' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-sync-modules.php',
'Jetpack_Sync_Settings' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-sync-settings.php',
'Jetpack_Tracks_Client' => $vendorDir . '/automattic/jetpack-tracking/legacy/class.tracks-client.php',
'Jetpack_Tracks_Event' => $vendorDir . '/automattic/jetpack-tracking/legacy/class.tracks-event.php',
);

View File

@@ -0,0 +1,354 @@
<?php
// This file `autoload_classmap_packages.php` was auto generated by automattic/jetpack-autoloader.
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Automattic\\Jetpack\\Sync\\Modules\\Network_Options' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Network_Options.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Import' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Import.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Terms' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Terms.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Themes' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Themes.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Comments' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Comments.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/WooCommerce.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Attachments' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Attachments.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Term_Relationships' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Term_Relationships.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Meta' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Meta.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Plugins' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Plugins.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Full_Sync.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Constants' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Constants.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Stats' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Stats.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Menus' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Menus.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Users' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Users.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Module' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Module.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Options' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Options.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Updates' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Updates.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/WP_Super_Cache.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Protect' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Protect.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Callables' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Callables.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Posts' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Posts.php'
),
'Automattic\\Jetpack\\Sync\\Functions' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Functions.php'
),
'Automattic\\Jetpack\\Sync\\JSON_Deflate_Array_Codec' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Json_Deflate_Array_Codec.php'
),
'Automattic\\Jetpack\\Sync\\Replicastore_Interface' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Replicastore_Interface.php'
),
'Automattic\\Jetpack\\Sync\\Actions' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Actions.php'
),
'Automattic\\Jetpack\\Sync\\Server' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Server.php'
),
'Automattic\\Jetpack\\Sync\\Codec_Interface' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Codec_Interface.php'
),
'Automattic\\Jetpack\\Sync\\Settings' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Settings.php'
),
'Automattic\\Jetpack\\Sync\\Main' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Main.php'
),
'Automattic\\Jetpack\\Sync\\Listener' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Listener.php'
),
'Automattic\\Jetpack\\Sync\\Queue' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Queue.php'
),
'Automattic\\Jetpack\\Sync\\Users' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Users.php'
),
'Automattic\\Jetpack\\Sync\\Simple_Codec' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Simple_Codec.php'
),
'Automattic\\Jetpack\\Sync\\Modules' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Modules.php'
),
'Automattic\\Jetpack\\Sync\\Queue_Buffer' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Queue_Buffer.php'
),
'Automattic\\Jetpack\\Sync\\Utils' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Utils.php'
),
'Automattic\\Jetpack\\Sync\\Defaults' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Defaults.php'
),
'Automattic\\Jetpack\\Sync\\Sender' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Sender.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Network_Options' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Network_Options.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Import' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Import.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Terms' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Terms.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Themes' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Themes.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Comments' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Comments.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/WooCommerce.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Attachments' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Attachments.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Term_Relationships' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Term_Relationships.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Meta' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Meta.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Plugins' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Plugins.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Full_Sync.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Constants' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Constants.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Stats' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Stats.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Menus' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Menus.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Users' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Users.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Module' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Module.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Options' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Options.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Updates' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Updates.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/WP_Super_Cache.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Protect' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Protect.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Callables' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Callables.php'
),
'Automattic\\Jetpack\\Sync\\Modules\\Posts' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/modules/Posts.php'
),
'Automattic\\Jetpack\\Sync\\Replicastore' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-sync/src/Replicastore.php'
),
'Automattic\\Jetpack\\Connection\\REST_Connector' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-connection/src/REST_Connector.php'
),
'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-connection/src/XMLRPC_Connector.php'
),
'Automattic\\Jetpack\\Connection\\Manager' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-connection/src/Manager.php'
),
'Automattic\\Jetpack\\Connection\\Manager_Interface' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-connection/src/Manager_Interface.php'
),
'Automattic\\Jetpack\\Connection\\Client' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-connection/src/Client.php'
),
'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php'
),
'Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php'
),
'Automattic\\Jetpack\\Assets\\Logo' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-logo/src/Logo.php'
),
'Automattic\\Jetpack\\Constants' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-constants/src/Constants.php'
),
'Automattic\\Jetpack\\Tracking' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-tracking/src/Tracking.php'
),
'Automattic\\Jetpack\\Assets' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-assets/src/Assets.php'
),
'Automattic\\Jetpack\\JITM' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-jitm/src/JITM.php'
),
'Automattic\\Jetpack\\Status' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-status/src/Status.php'
),
'Automattic\\Jetpack\\Roles' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-roles/src/Roles.php'
),
'Automattic\\Jetpack\\Plugin\\Tracking' => array(
'version' => 'dev-branch-7.6',
'path' => $baseDir . '/src/Tracking.php'
),
'JetpackTracking' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-tracks.php'
),
'Jetpack_Sync_Modules' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-sync-modules.php'
),
'Jetpack_Client' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-client.php'
),
'Jetpack_Sync_Actions' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-sync-actions.php'
),
'Jetpack_Sync_Settings' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-compat/legacy/class.jetpack-sync-settings.php'
),
'Jetpack_Tracks_Client' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-tracking/legacy/class.tracks-client.php'
),
'Jetpack_Tracks_Event' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-tracking/legacy/class.tracks-event.php'
),
'Jetpack_Signature' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-connection/legacy/class.jetpack-signature.php'
),
'Jetpack_Options' => array(
'version' => 'dev-update/sync-use-roles-package',
'path' => $vendorDir . '/automattic/jetpack-options/legacy/class.jetpack-options.php'
),
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'009de6aaa0d497eacea41fab13fc05f1' => $vendorDir . '/automattic/jetpack-compat/functions.php',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,15 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Automattic\\Jetpack\\Sync\\Modules\\' => array($vendorDir . '/automattic/jetpack-sync/src/modules'),
'Automattic\\Jetpack\\Sync\\' => array($vendorDir . '/automattic/jetpack-sync/src'),
'Automattic\\Jetpack\\Connection\\' => array($vendorDir . '/automattic/jetpack-connection/src'),
'Automattic\\Jetpack\\Autoloader\\' => array($vendorDir . '/automattic/jetpack-autoloader/src'),
'Automattic\\Jetpack\\Assets\\' => array($vendorDir . '/automattic/jetpack-logo/src'),
'Automattic\\Jetpack\\' => array($vendorDir . '/automattic/jetpack-constants/src', $vendorDir . '/automattic/jetpack-tracking/src', $vendorDir . '/automattic/jetpack-assets/src', $vendorDir . '/automattic/jetpack-jitm/src', $vendorDir . '/automattic/jetpack-status/src', $vendorDir . '/automattic/jetpack-roles/src'),
);

View File

@@ -0,0 +1,61 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit73780ab222d7fdeca4785a6d0fe43054
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit73780ab222d7fdeca4785a6d0fe43054', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit73780ab222d7fdeca4785a6d0fe43054', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit73780ab222d7fdeca4785a6d0fe43054::getInitializer($loader));
} else {
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->setClassMapAuthoritative(true);
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit73780ab222d7fdeca4785a6d0fe43054::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire73780ab222d7fdeca4785a6d0fe43054($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire73780ab222d7fdeca4785a6d0fe43054($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}

View File

@@ -0,0 +1,133 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit73780ab222d7fdeca4785a6d0fe43054
{
public static $files = array (
'009de6aaa0d497eacea41fab13fc05f1' => __DIR__ . '/..' . '/automattic/jetpack-compat/functions.php',
);
public static $prefixLengthsPsr4 = array (
'A' =>
array (
'Automattic\\Jetpack\\Sync\\Modules\\' => 32,
'Automattic\\Jetpack\\Sync\\' => 24,
'Automattic\\Jetpack\\Connection\\' => 30,
'Automattic\\Jetpack\\Autoloader\\' => 30,
'Automattic\\Jetpack\\Assets\\' => 26,
'Automattic\\Jetpack\\' => 19,
),
);
public static $prefixDirsPsr4 = array (
'Automattic\\Jetpack\\Sync\\Modules\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules',
),
'Automattic\\Jetpack\\Sync\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/jetpack-sync/src',
),
'Automattic\\Jetpack\\Connection\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/jetpack-connection/src',
),
'Automattic\\Jetpack\\Autoloader\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src',
),
'Automattic\\Jetpack\\Assets\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/jetpack-logo/src',
),
'Automattic\\Jetpack\\' =>
array (
0 => __DIR__ . '/..' . '/automattic/jetpack-constants/src',
1 => __DIR__ . '/..' . '/automattic/jetpack-tracking/src',
2 => __DIR__ . '/..' . '/automattic/jetpack-assets/src',
3 => __DIR__ . '/..' . '/automattic/jetpack-jitm/src',
4 => __DIR__ . '/..' . '/automattic/jetpack-status/src',
5 => __DIR__ . '/..' . '/automattic/jetpack-roles/src',
),
);
public static $classMap = array (
'Automattic\\Jetpack\\Assets' => __DIR__ . '/..' . '/automattic/jetpack-assets/src/Assets.php',
'Automattic\\Jetpack\\Assets\\Logo' => __DIR__ . '/..' . '/automattic/jetpack-logo/src/Logo.php',
'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php',
'Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin' => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php',
'Automattic\\Jetpack\\Connection\\Client' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/Client.php',
'Automattic\\Jetpack\\Connection\\Manager' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/Manager.php',
'Automattic\\Jetpack\\Connection\\Manager_Interface' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/Manager_Interface.php',
'Automattic\\Jetpack\\Connection\\REST_Connector' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/REST_Connector.php',
'Automattic\\Jetpack\\Connection\\XMLRPC_Connector' => __DIR__ . '/..' . '/automattic/jetpack-connection/src/XMLRPC_Connector.php',
'Automattic\\Jetpack\\Constants' => __DIR__ . '/..' . '/automattic/jetpack-constants/src/Constants.php',
'Automattic\\Jetpack\\JITM' => __DIR__ . '/..' . '/automattic/jetpack-jitm/src/JITM.php',
'Automattic\\Jetpack\\Plugin\\Tracking' => __DIR__ . '/../..' . '/src/Tracking.php',
'Automattic\\Jetpack\\Roles' => __DIR__ . '/..' . '/automattic/jetpack-roles/src/Roles.php',
'Automattic\\Jetpack\\Status' => __DIR__ . '/..' . '/automattic/jetpack-status/src/Status.php',
'Automattic\\Jetpack\\Sync\\Actions' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Actions.php',
'Automattic\\Jetpack\\Sync\\Codec_Interface' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Codec_Interface.php',
'Automattic\\Jetpack\\Sync\\Defaults' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Defaults.php',
'Automattic\\Jetpack\\Sync\\Functions' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Functions.php',
'Automattic\\Jetpack\\Sync\\JSON_Deflate_Array_Codec' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Json_Deflate_Array_Codec.php',
'Automattic\\Jetpack\\Sync\\Listener' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Listener.php',
'Automattic\\Jetpack\\Sync\\Main' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Main.php',
'Automattic\\Jetpack\\Sync\\Modules' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Modules.php',
'Automattic\\Jetpack\\Sync\\Modules\\Attachments' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Attachments.php',
'Automattic\\Jetpack\\Sync\\Modules\\Callables' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Callables.php',
'Automattic\\Jetpack\\Sync\\Modules\\Comments' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Comments.php',
'Automattic\\Jetpack\\Sync\\Modules\\Constants' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Constants.php',
'Automattic\\Jetpack\\Sync\\Modules\\Full_Sync' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Full_Sync.php',
'Automattic\\Jetpack\\Sync\\Modules\\Import' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Import.php',
'Automattic\\Jetpack\\Sync\\Modules\\Menus' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Menus.php',
'Automattic\\Jetpack\\Sync\\Modules\\Meta' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Meta.php',
'Automattic\\Jetpack\\Sync\\Modules\\Module' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Module.php',
'Automattic\\Jetpack\\Sync\\Modules\\Network_Options' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Network_Options.php',
'Automattic\\Jetpack\\Sync\\Modules\\Options' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Options.php',
'Automattic\\Jetpack\\Sync\\Modules\\Plugins' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Plugins.php',
'Automattic\\Jetpack\\Sync\\Modules\\Posts' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Posts.php',
'Automattic\\Jetpack\\Sync\\Modules\\Protect' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Protect.php',
'Automattic\\Jetpack\\Sync\\Modules\\Stats' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Stats.php',
'Automattic\\Jetpack\\Sync\\Modules\\Term_Relationships' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Term_Relationships.php',
'Automattic\\Jetpack\\Sync\\Modules\\Terms' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Terms.php',
'Automattic\\Jetpack\\Sync\\Modules\\Themes' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Themes.php',
'Automattic\\Jetpack\\Sync\\Modules\\Updates' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Updates.php',
'Automattic\\Jetpack\\Sync\\Modules\\Users' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/Users.php',
'Automattic\\Jetpack\\Sync\\Modules\\WP_Super_Cache' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/WP_Super_Cache.php',
'Automattic\\Jetpack\\Sync\\Modules\\WooCommerce' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/modules/WooCommerce.php',
'Automattic\\Jetpack\\Sync\\Queue' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Queue.php',
'Automattic\\Jetpack\\Sync\\Queue_Buffer' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Queue_Buffer.php',
'Automattic\\Jetpack\\Sync\\Replicastore' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Replicastore.php',
'Automattic\\Jetpack\\Sync\\Replicastore_Interface' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Replicastore_Interface.php',
'Automattic\\Jetpack\\Sync\\Sender' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Sender.php',
'Automattic\\Jetpack\\Sync\\Server' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Server.php',
'Automattic\\Jetpack\\Sync\\Settings' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Settings.php',
'Automattic\\Jetpack\\Sync\\Simple_Codec' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Simple_Codec.php',
'Automattic\\Jetpack\\Sync\\Users' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Users.php',
'Automattic\\Jetpack\\Sync\\Utils' => __DIR__ . '/..' . '/automattic/jetpack-sync/src/Utils.php',
'Automattic\\Jetpack\\Tracking' => __DIR__ . '/..' . '/automattic/jetpack-tracking/src/Tracking.php',
'JetpackTracking' => __DIR__ . '/..' . '/automattic/jetpack-compat/legacy/class.jetpack-tracks.php',
'Jetpack_Client' => __DIR__ . '/..' . '/automattic/jetpack-compat/legacy/class.jetpack-client.php',
'Jetpack_Options' => __DIR__ . '/..' . '/automattic/jetpack-options/legacy/class.jetpack-options.php',
'Jetpack_Signature' => __DIR__ . '/..' . '/automattic/jetpack-connection/legacy/class.jetpack-signature.php',
'Jetpack_Sync_Actions' => __DIR__ . '/..' . '/automattic/jetpack-compat/legacy/class.jetpack-sync-actions.php',
'Jetpack_Sync_Modules' => __DIR__ . '/..' . '/automattic/jetpack-compat/legacy/class.jetpack-sync-modules.php',
'Jetpack_Sync_Settings' => __DIR__ . '/..' . '/automattic/jetpack-compat/legacy/class.jetpack-sync-settings.php',
'Jetpack_Tracks_Client' => __DIR__ . '/..' . '/automattic/jetpack-tracking/legacy/class.tracks-client.php',
'Jetpack_Tracks_Event' => __DIR__ . '/..' . '/automattic/jetpack-tracking/legacy/class.tracks-event.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit73780ab222d7fdeca4785a6d0fe43054::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit73780ab222d7fdeca4785a6d0fe43054::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit73780ab222d7fdeca4785a6d0fe43054::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,401 @@
[
{
"name": "automattic/jetpack-assets",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/assets",
"reference": "e93b5911e77ff0abfad498e99edbb5f6a8a124a9"
},
"require": {
"automattic/jetpack-constants": "@dev"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\": "src/"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Asset management utilities for Jetpack ecosystem packages"
},
{
"name": "automattic/jetpack-autoloader",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/autoloader",
"reference": "43bb413915e6aad7e4a088490cb76d72df22a8fb"
},
"require": {
"composer-plugin-api": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "composer-plugin",
"extra": {
"class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin"
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\Autoloader\\": "src"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Creates a custom autoloader for a plugin or theme."
},
{
"name": "automattic/jetpack-compat",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/compat",
"reference": "cd47566548267b29b0df1574a7c6be67de9c5cc9"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"functions.php"
],
"classmap": [
"legacy"
]
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Compatibility layer with previous versions of Jetpack"
},
{
"name": "automattic/jetpack-connection",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/connection",
"reference": "0a0e293d73c17d59376590d56e5667b169d26877"
},
"require": {
"automattic/jetpack-constants": "@dev",
"automattic/jetpack-options": "@dev"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\Connection\\": "src"
},
"classmap": [
"legacy"
]
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Everything needed to connect to the Jetpack infrastructure"
},
{
"name": "automattic/jetpack-constants",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/constants",
"reference": "a6ab6360f4b48962ec7d62b06b39d1470b1dbe95"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\": "src/"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "A wrapper for defining constants in a more testable way."
},
{
"name": "automattic/jetpack-jitm",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/jitm",
"reference": "b0c2da6ce6a0137f3a1895ab82a93ad7769fddca"
},
"require": {
"automattic/jetpack-assets": "@dev",
"automattic/jetpack-connection": "@dev",
"automattic/jetpack-constants": "@dev",
"automattic/jetpack-logo": "@dev",
"automattic/jetpack-options": "@dev",
"automattic/jetpack-tracking": "@dev"
},
"require-dev": {
"mockery/mockery": "^1.2",
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\": "src/"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Just in time messages for Jetpack"
},
{
"name": "automattic/jetpack-logo",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/logo",
"reference": "d8a31dfd40166c4867fa2c526a03d9df481d5610"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\Assets\\": "src/"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "A logo for Jetpack"
},
{
"name": "automattic/jetpack-options",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/options",
"reference": "78220bf7d3c1a3a5ed4edb77462e84982b3c408f"
},
"require": {
"automattic/jetpack-constants": "@dev"
},
"require-dev": {
"10up/wp_mock": "0.4.2",
"phpunit/phpunit": "7.*.*"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"classmap": [
"legacy"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "A wrapper for wp-options to manage specific Jetpack options."
},
{
"name": "automattic/jetpack-roles",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/roles",
"reference": "f38b3379c11a05e4711b4fb29b390c8107daccd7"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\": "src/"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Utilities, related with user roles and capabilities."
},
{
"name": "automattic/jetpack-status",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/status",
"reference": "99ecd79ed31dc3432892df709ba745ebc6f747e9"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\": "src/"
}
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Used to retrieve information about the current status of Jetpack and the site overall."
},
{
"name": "automattic/jetpack-sync",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/sync",
"reference": "1cad05fcfd38ad123af0bbf08b5a1224bd95312a"
},
"require": {
"automattic/jetpack-connection": "@dev",
"automattic/jetpack-constants": "@dev",
"automattic/jetpack-options": "@dev",
"automattic/jetpack-roles": "@dev",
"automattic/jetpack-status": "@dev"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\Sync\\": "src/",
"Automattic\\Jetpack\\Sync\\Modules\\": "src/modules/"
}
},
"license": [
"GPL-2.0-or-later"
],
"description": "Everything needed to allow syncing to the WP.com infrastructure."
},
{
"name": "automattic/jetpack-tracking",
"version": "dev-update/sync-use-roles-package",
"version_normalized": "dev-update/sync-use-roles-package",
"dist": {
"type": "path",
"url": "./packages/tracking",
"reference": "b2c79c42a8ccd728db9450818aa95fd6d51afc85"
},
"require": {
"automattic/jetpack-options": "@dev"
},
"require-dev": {
"php-mock/php-mock": "^2.1",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
},
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Automattic\\Jetpack\\": "src/"
},
"classmap": [
"legacy"
]
},
"scripts": {
"phpunit": [
"@composer install",
"./vendor/phpunit/phpunit/phpunit --colors=always"
]
},
"license": [
"GPL-2.0-or-later"
],
"description": "Tracking for Jetpack"
}
]