summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony G. Basile <blueness@gentoo.org>2020-01-06 14:32:30 -0500
committerAnthony G. Basile <blueness@gentoo.org>2020-01-06 14:32:30 -0500
commit10ef81bf85ad0a4bad0d204838e14c99ca2526f7 (patch)
treeb4bb36a326d41de12d1a6181d2a2baf34696ac24 /plugins/jetpack/vendor/automattic/jetpack-sync/src/modules
parentUpdating script for Update (diff)
downloadblogs-gentoo-10ef81bf85ad0a4bad0d204838e14c99ca2526f7.tar.gz
blogs-gentoo-10ef81bf85ad0a4bad0d204838e14c99ca2526f7.tar.bz2
blogs-gentoo-10ef81bf85ad0a4bad0d204838e14c99ca2526f7.zip
Update jetpack 8.0
Signed-off-by: Anthony G. Basile <blueness@gentoo.org>
Diffstat (limited to 'plugins/jetpack/vendor/automattic/jetpack-sync/src/modules')
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-attachments.php95
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-callables.php491
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-comments.php411
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-constants.php248
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-full-sync.php673
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-import.php218
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-menus.php143
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-meta.php81
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-module.php463
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-network-options.php236
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-options.php344
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-plugins.php413
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-posts.php671
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-protect.php53
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-stats.php66
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-term-relationships.php204
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-terms.php322
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-themes.php825
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-updates.php496
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-users.php854
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-woocommerce.php546
-rw-r--r--plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-wp-super-cache.php156
22 files changed, 8009 insertions, 0 deletions
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-attachments.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-attachments.php
new file mode 100644
index 00000000..bf716be3
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-attachments.php
@@ -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 );
+ }
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-callables.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-callables.php
new file mode 100644
index 00000000..d8ac3e9e
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-callables.php
@@ -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;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-comments.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-comments.php
new file mode 100644
index 00000000..e956748c
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-comments.php
@@ -0,0 +1,411 @@
+<?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';
+ }
+
+ /**
+ * The id field in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function id_field() {
+ return 'comment_ID';
+ }
+
+ /**
+ * The table in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function table_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 );
+ add_filter( 'jetpack_sync_before_enqueue_wp_insert_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
+
+ /**
+ * 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' )
+ );
+ }
+
+ /**
+ * Prevents any comment types that are not in the whitelist from being enqueued and sent to WordPress.com.
+ *
+ * @param array $args Arguments passed to wp_insert_comment.
+ *
+ * @return bool or array $args Arguments passed to wp_insert_comment
+ */
+ public function only_allow_white_listed_comment_types( $args ) {
+ $comment = $args[1];
+
+ if ( ! in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true ) ) {
+ return false;
+ }
+
+ return $args;
+ }
+
+ /**
+ * 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 public
+ *
+ * @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.
+ */
+ public 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,
+ );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-constants.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-constants.php
new file mode 100644
index 00000000..d4fecb3b
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-constants.php
@@ -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;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-full-sync.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-full-sync.php
new file mode 100644
index 00000000..325b35f4
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-full-sync.php
@@ -0,0 +1,673 @@
+<?php
+/**
+ * Full sync module.
+ *
+ * @package automattic/jetpack-sync
+ */
+
+namespace Automattic\Jetpack\Sync\Modules;
+
+use Automattic\Jetpack\Sync\Listener;
+use Automattic\Jetpack\Sync\Lock;
+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_';
+
+
+ /**
+ * Enqueue Lock name.
+ *
+ * @var string
+ */
+ const ENQUEUE_LOCK_NAME = 'full_sync_enqueue';
+
+ /**
+ * 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 );
+
+ return true;
+ }
+
+ /**
+ * Enqueue the next items to sync.
+ *
+ * @access public
+ *
+ * @param array $configs Full sync configuration for all sync modules.
+ */
+ public function continue_enqueuing( $configs = null ) {
+ if ( ! $this->is_started() || ! ( new Lock() )->attempt( self::ENQUEUE_LOCK_NAME ) || $this->get_status_option( 'queue_finished' ) ) {
+ return;
+ }
+
+ $this->enqueue( $configs );
+
+ ( new Lock() )->remove( self::ENQUEUE_LOCK_NAME );
+ }
+
+ /**
+ * Get Modules that are configured to Full Sync and haven't finished enqueuing
+ *
+ * @param array $configs Full sync configuration for all sync modules.
+ *
+ * @return array
+ */
+ public function get_remaining_modules_to_enqueue( $configs ) {
+ $enqueue_status = $this->get_enqueue_status();
+ return array_filter(
+ Modules::get_modules(),
+ /**
+ * Select configured and not finished modules.
+ *
+ * @var $module Module
+ * @return bool
+ */
+ function ( $module ) use ( $configs, $enqueue_status ) {
+ // Skip module if not configured for this sync or module is done.
+ if ( ! isset( $configs[ $module->name() ] ) ) {
+ return false;
+ }
+ if ( ! $configs[ $module->name() ] ) {
+ return false;
+ }
+ if ( isset( $enqueue_status[ $module->name() ][2] ) ) {
+ if ( true === $enqueue_status[ $module->name() ][2] ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ );
+ }
+
+ /**
+ * Enqueue the next items to sync.
+ *
+ * @access public
+ *
+ * @param array $configs Full sync configuration for all sync modules.
+ */
+ public function enqueue( $configs = null ) {
+ if ( ! $configs ) {
+ $configs = $this->get_config();
+ }
+
+ $enqueue_status = $this->get_enqueue_status();
+ $full_sync_queue = new Queue( 'full_sync' );
+ $available_queue_slots = Settings::get_setting( 'max_queue_size_full_sync' ) - $full_sync_queue->size();
+
+ if ( $available_queue_slots <= 0 ) {
+ return;
+ }
+
+ $remaining_items_to_enqueue = min( Settings::get_setting( 'max_enqueue_full_sync' ), $available_queue_slots );
+
+ /**
+ * If a module exits early (e.g. because it ran out of full sync queue slots, or we ran out of request time)
+ * then it should exit early
+ */
+ foreach ( $this->get_remaining_modules_to_enqueue( $configs ) as $module ) {
+ 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;
+ }
+
+ if ( 0 >= $remaining_items_to_enqueue || true !== $next_enqueue_state ) {
+ $this->set_enqueue_status( $enqueue_status );
+ return;
+ }
+ }
+
+ $this->queue_full_sync_end( $configs );
+ $this->set_enqueue_status( $enqueue_status );
+ }
+
+ /**
+ * Enqueue 'jetpack_full_sync_end' and update 'queue_finished' status.
+ *
+ * @access public
+ *
+ * @param array $configs Full sync configuration for all sync modules.
+ */
+ public function queue_full_sync_end( $configs ) {
+ $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 );
+
+ // Setting autoload to true means that it's faster to check whether we should continue enqueuing.
+ $this->update_status_option( 'queue_finished', time(), true );
+ }
+
+ /**
+ * 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();
+ ( new Lock() )->remove( self::ENQUEUE_LOCK_NAME );
+
+ $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.
+ */
+ public 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' );
+ }
+
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-import.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-import.php
new file mode 100644
index 00000000..99afd74b
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-import.php
@@ -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';
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-menus.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-menus.php
new file mode 100644
index 00000000..69faa9b5
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-menus.php
@@ -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 ) );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-meta.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-meta.php
new file mode 100644
index 00000000..1d30c72e
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-meta.php
@@ -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;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-module.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-module.php
new file mode 100644
index 00000000..b8b57d87
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-module.php
@@ -0,0 +1,463 @@
+<?php
+/**
+ * A base abstraction of a sync module.
+ *
+ * @package automattic/jetpack-sync
+ */
+
+namespace Automattic\Jetpack\Sync\Modules;
+
+use Automattic\Jetpack\Sync\Listener;
+use Automattic\Jetpack\Sync\Replicastore;
+
+/**
+ * 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();
+
+ /**
+ * The id field in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function id_field() {
+ return 'ID';
+ }
+
+ /**
+ * The table in the database.
+ *
+ * @access public
+ *
+ * @return string|bool
+ */
+ public function table_name() {
+ return false;
+ }
+
+ // 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 );
+ }
+
+ if ( $wpdb->last_error ) {
+ // return the values that were passed in so all these chunks get retried.
+ return array( $max_items_to_enqueue, $state );
+ }
+
+ 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;
+ }
+
+ /**
+ * Gets a list of minimum and maximum object ids for each batch based on the given batch size.
+ *
+ * @access public
+ *
+ * @param int $batch_size The batch size for objects.
+ * @param string|bool $where_sql The sql where clause minus 'WHERE', or false if no where clause is needed.
+ *
+ * @return array|bool An array of min and max ids for each batch. FALSE if no table can be found.
+ */
+ public function get_min_max_object_ids_for_batches( $batch_size, $where_sql = false ) {
+ global $wpdb;
+
+ if ( ! $this->table_name() ) {
+ return false;
+ }
+
+ $results = array();
+ $table = $wpdb->{$this->table_name()};
+ $current_max = 0;
+ $current_min = 1;
+ $id_field = $this->id_field();
+ $replicastore = new Replicastore();
+
+ $total = $replicastore->get_min_max_object_id(
+ $id_field,
+ $table,
+ $where_sql,
+ false
+ );
+
+ while ( $total->max > $current_max ) {
+ $where = $where_sql ?
+ $where_sql . " AND $id_field > $current_max" :
+ "$id_field > $current_max";
+ $result = $replicastore->get_min_max_object_id(
+ $id_field,
+ $table,
+ $where,
+ $batch_size
+ );
+ if ( empty( $result->min ) && empty( $result->max ) ) {
+ // Our query produced no min and max. We can assume the min from the previous query,
+ // and the total max we found in the initial query.
+ $current_max = (int) $total->max;
+ $result = (object) array(
+ 'min' => $current_min,
+ 'max' => $current_max,
+ );
+ } else {
+ $current_min = (int) $result->min;
+ $current_max = (int) $result->max;
+ }
+ $results[] = $result;
+ }
+
+ return $results;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-network-options.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-network-options.php
new file mode 100644
index 00000000..c30ae8c7
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-network-options.php
@@ -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;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-options.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-options.php
new file mode 100644
index 00000000..2c323a2b
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-options.php
@@ -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;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-plugins.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-plugins.php
new file mode 100644
index 00000000..9f257557
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-plugins.php
@@ -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,
+ );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-posts.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-posts.php
new file mode 100644
index 00000000..14d1c0b7
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-posts.php
@@ -0,0 +1,671 @@
+<?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';
+ }
+
+ /**
+ * The table in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function table_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 public
+ *
+ * @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.
+ */
+ public 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 ( isset( $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,
+ );
+ }
+
+ /**
+ * Gets a list of minimum and maximum object ids for each batch based on the given batch size.
+ *
+ * @access public
+ *
+ * @param int $batch_size The batch size for objects.
+ * @param string|bool $where_sql The sql where clause minus 'WHERE', or false if no where clause is needed.
+ *
+ * @return array|bool An array of min and max ids for each batch. FALSE if no table can be found.
+ */
+ public function get_min_max_object_ids_for_batches( $batch_size, $where_sql = false ) {
+ return parent::get_min_max_object_ids_for_batches( $batch_size, $this->get_where_sql( $where_sql ) );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-protect.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-protect.php
new file mode 100644
index 00000000..ebd62ff8
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-protect.php
@@ -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 );
+ }
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-stats.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-stats.php
new file mode 100644
index 00000000..bbd4cae6
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-stats.php
@@ -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 ) );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-term-relationships.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-term-relationships.php
new file mode 100644
index 00000000..3cad885d
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-term-relationships.php
@@ -0,0 +1,204 @@
+<?php
+/**
+ * Term relationships sync module.
+ *
+ * @package automattic/jetpack-sync
+ */
+
+namespace Automattic\Jetpack\Sync\Modules;
+
+use Automattic\Jetpack\Sync\Listener;
+use Automattic\Jetpack\Sync\Settings;
+
+/**
+ * Class to handle sync for term relationships.
+ */
+class Term_Relationships extends Module {
+
+ /**
+ * Max terms to return in one single query
+ *
+ * @access public
+ *
+ * @const int
+ */
+ const QUERY_LIMIT = 1000;
+
+ /**
+ * Max value for a signed INT in MySQL - https://dev.mysql.com/doc/refman/8.0/en/integer-types.html
+ *
+ * @access public
+ *
+ * @const int
+ */
+ const MAX_INT = 2147483647;
+
+ /**
+ * Sync module name.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function name() {
+ return 'term_relationships';
+ }
+
+ /**
+ * The id field in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function id_field() {
+ return 'object_id';
+ }
+
+ /**
+ * The table in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function table_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
+ *
+ * @param array $config Full sync configuration for this sync module.
+ * @param int $max_items_to_enqueue Maximum number of items to enqueue.
+ * @param object $last_object_enqueued Last object enqueued.
+ *
+ * @return array Number of actions enqueued, and next module state.
+ * @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
+ */
+ public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $last_object_enqueued ) {
+ global $wpdb;
+ $term_relationships_full_sync_item_size = Settings::get_setting( 'term_relationships_full_sync_item_size' );
+ $limit = min( $max_items_to_enqueue * $term_relationships_full_sync_item_size, self::QUERY_LIMIT );
+ $items_enqueued_count = 0;
+ $last_object_enqueued = $last_object_enqueued ? $last_object_enqueued : array(
+ 'object_id' => self::MAX_INT,
+ 'term_taxonomy_id' => self::MAX_INT,
+ );
+
+ while ( $limit > 0 ) {
+ /*
+ * SELECT object_id, term_taxonomy_id
+ * FROM $wpdb->term_relationships
+ * WHERE ( object_id = 11 AND term_taxonomy_id < 14 ) OR ( object_id < 11 )
+ * ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT 1000
+ */
+ $objects = $wpdb->get_results( $wpdb->prepare( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], $limit ), ARRAY_A );
+ // Request term relationships in groups of N for efficiency.
+ $objects_count = count( $objects );
+ if ( ! count( $objects ) ) {
+ return array( $items_enqueued_count, true );
+ }
+ $items = array_chunk( $objects, $term_relationships_full_sync_item_size );
+ $last_object_enqueued = $this->bulk_enqueue_full_sync_term_relationships( $items, $last_object_enqueued );
+ $items_enqueued_count += count( $items );
+ $limit = min( $limit - $objects_count, self::QUERY_LIMIT );
+ }
+
+ // We need to do this extra check in case $max_items_to_enqueue * $term_relationships_full_sync_item_size == relationships objects left.
+ $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], 1 ) );
+ if ( intval( $count ) === 0 ) {
+ return array( $items_enqueued_count, true );
+ }
+
+ return array( $items_enqueued_count, $last_object_enqueued );
+ }
+
+ /**
+ *
+ * Enqueue all $items within `jetpack_full_sync_term_relationships` actions.
+ *
+ * @param array $items Groups of objects to sync.
+ * @param array $previous_interval_end Last item enqueued.
+ *
+ * @return array Last enqueued object.
+ */
+ public function bulk_enqueue_full_sync_term_relationships( $items, $previous_interval_end ) {
+ $listener = Listener::get_instance();
+ $items_with_previous_interval_end = $this->get_chunks_with_preceding_end( $items, $previous_interval_end );
+ $listener->bulk_enqueue_full_sync_actions( 'jetpack_full_sync_term_relationships', $items_with_previous_interval_end );
+ $last_item = end( $items );
+ return end( $last_item );
+ }
+
+ /**
+ * 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 / Settings::get_setting( 'term_relationships_full_sync_item_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,
+ );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-terms.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-terms.php
new file mode 100644
index 00000000..36afc5d7
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-terms.php
@@ -0,0 +1,322 @@
+<?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';
+ }
+
+ /**
+ * The id field in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function id_field() {
+ return 'term_id';
+ }
+
+ /**
+ * The table in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function table_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 public
+ *
+ * @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.
+ */
+ public 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 );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-themes.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-themes.php
new file mode 100644
index 00000000..57535527
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-themes.php
@@ -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' );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-updates.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-updates.php
new file mode 100644
index 00000000..d99c9c57
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-updates.php
@@ -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 );
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-users.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-users.php
new file mode 100644
index 00000000..21974a5b
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-users.php
@@ -0,0 +1,854 @@
+<?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';
+ }
+
+ /**
+ * The table in the database.
+ *
+ * @access public
+ *
+ * @return string
+ */
+ public function table_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 public
+ *
+ * @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.
+ */
+ public 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;
+ }
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-woocommerce.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-woocommerce.php
new file mode 100644
index 00000000..1c336342
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-woocommerce.php
@@ -0,0 +1,546 @@
+<?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' ) );
+
+ // Blacklist Action Scheduler comment types.
+ add_filter( 'jetpack_sync_prevent_sending_comment_data', array( $this, 'filter_action_scheduler_comments' ), 10, 2 );
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Stop comments from the Action Scheduler from being synced.
+ * https://github.com/woocommerce/woocommerce/tree/e7762627c37ec1f7590e6cac4218ba0c6a20024d/includes/libraries/action-scheduler
+ *
+ * @since 7.7.0
+ *
+ * @param boolean $can_sync Should we prevent comment data from bing synced to WordPress.com.
+ * @param mixed $comment WP_COMMENT object.
+ *
+ * @return bool
+ */
+ public function filter_action_scheduler_comments( $can_sync, $comment ) {
+ if ( isset( $comment->comment_agent ) && 'ActionScheduler' === $comment->comment_agent ) {
+ return true;
+ }
+ return $can_sync;
+ }
+
+ /**
+ * 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',
+ );
+}
diff --git a/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-wp-super-cache.php b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-wp-super-cache.php
new file mode 100644
index 00000000..af4aec41
--- /dev/null
+++ b/plugins/jetpack/vendor/automattic/jetpack-sync/src/modules/class-wp-super-cache.php
@@ -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 );
+ }
+}