updated plugin Jetpack Protect version 4.0.0

This commit is contained in:
2025-04-29 21:19:56 +00:00
committed by Gitium
parent eb9181b250
commit ebd40ef928
265 changed files with 11864 additions and 3987 deletions

View File

@ -5,6 +5,169 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.9.2] - 2025-03-21
### Added
- Sync: Consider blacklisted taxonomies when doing full sync for term relationships [#42618]
## [4.9.1] - 2025-03-17
### Changed
- Internal updates.
## [4.9.0] - 2025-03-17
### Added
- Add `_wp_old_date` to meta sync. [#42405]
### Changed
- Sync: Don't send any old full sync actions if full sync has been restarted. [#42433]
## [4.8.4] - 2025-03-12
### Changed
- Internal updates.
## [4.8.3] - 2025-03-05
### Changed
- Internal updates.
## [4.8.2] - 2025-02-24
### Added
- Modules: Move custom capabilities from the Jetpack plugin. [#41859]
### Fixed
- Code: Prevent dynamic class properties. [#41857]
## [4.8.1] - 2025-02-17
### Changed
- Jetpack Sync: Optimize performance for the 'terms' module. [#41809]
## [4.8.0] - 2025-02-12
### Removed
- Remove unused setting. [#41658]
## [4.7.0] - 2025-02-10
### Added
- Add setting to hide newsletter category modal. [#41552]
### Changed
- Sync: Use dynamic chunk size for Woo modules in Full Sync if default is too large. [#41433]
### Deprecated
- Sync: Full sync for posts not sending term relationships. [#41597]
## [4.6.0] - 2025-02-03
### Added
- Sync: Use dynamic chunk size for Full Sync comments if default is too large. [#41350]
### Changed
- Jetpack Sync: Checksum performance optimizations for meta sync module. [#41390]
### Fixed
- Code: Remove extra params on function calls. [#41263]
## [4.5.0] - 2025-01-23
### Changed
- Sync: Full sync doesn't send actions for posts and comments with no items. [#41183]
## [4.4.0] - 2025-01-20
### Added
- Add context for full sync. [#40930]
### Changed
- Code: Use function-style exit() and die() with a default status code of 0. [#41167]
## [4.3.0] - 2025-01-09
### Added
- Instant Search: add taxonomies for a8c-support-theme kb_article CPT. [#38660]
## [4.2.0] - 2024-12-23
### Added
- Add specific key for full sync actions. [#40566]
## [4.1.1] - 2024-12-16
### Changed
- Internal updates.
## [4.1.0] - 2024-12-09
### Added
- WordPress.com Features: add Holiday Snow functionality. [#40478]
## [4.0.2] - 2024-11-28
### Fixed
- Sync: Fixed bug in checksum histogram when max_range is PHP_INT_MAX [#40309]
## [4.0.1] - 2024-11-25
### Changed
- Updated dependencies. [#40286]
## [4.0.0] - 2024-11-14
### Added
- Added UTM option to sync [#40144]
### Changed
- Jetpack Sync: Add 'od_url_metrics' in blacklisted post types [#40158]
### Removed
- General: Update minimum PHP version to 7.2. [#40147]
## [3.15.0] - 2024-11-11
### Changed
- Sync: Modules in Full Sync are now sent in the order the config is set. [#40100]
### Fixed
- Jetpack Sync: Add missing handlers for removing or trashing shop_subscription orders [#40047]
## [3.14.4] - 2024-11-04
### Added
- Enable test coverage. [#39961]
## [3.14.3] - 2024-10-25
### Fixed
- Hooks: Hook init_sync_cron_jobs into init to ensure translation loading within the function is not triggered too early. [#39841]
## [3.14.2] - 2024-10-15
### Changed
- Jetpack Sync: Update default Post Type Blacklist [#39770]
### Fixed
- Jetpack Sync: Ensure Full Sync is only triggered on backend admin POST requests [#39747]
- Update plugin action links filter parameter to avoid conflicts with other plugins. [#39681]
## [3.14.1] - 2024-10-14
### Changed
- Internal updates.
## [3.14.0] - 2024-10-10
### Added
- Jetpack Sync: Add 'woocommerce_analytics_first_activation' in options' whitelist
### Changed
- Sync: Ensure we don't sync set_object_terms action for terms with blacklisted taxonomies
## [3.13.2] - 2024-09-30
### Fixed
- Jetpack Sync: Fix a bug in syncing HPOS 'woocommerce_delete_order' actions [#39562]
## [3.13.1] - 2024-09-23
### Changed
- Update dependencies.
## [3.13.0] - 2024-09-16
### Removed
- Social: Cleaned up media auto-conversion backend logic [#38587]
### Fixed
- Sync: Ensure is_plugin_active exists when loading Table Checksums [#39369]
## [3.12.0] - 2024-09-10
### Added
- Sync: Enable Full Sync for woocommerce_hpos_orders module [#39297]
## [3.11.0] - 2024-09-09
### Added
- Sync: Enable Full Sync Immediately for woocommerce module [#39254]
### Removed
- Jetpack Sync: Stop syncing 'automatic_updates_complete' actions [#39296]
## [3.10.0] - 2024-09-05
### Added
- Sync: Add a filter that allows modification of the default modules list used for full sync procedure. [#39117]
@ -1273,6 +1436,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Move sync to a classmapped package
[4.9.2]: https://github.com/Automattic/jetpack-sync/compare/v4.9.1...v4.9.2
[4.9.1]: https://github.com/Automattic/jetpack-sync/compare/v4.9.0...v4.9.1
[4.9.0]: https://github.com/Automattic/jetpack-sync/compare/v4.8.4...v4.9.0
[4.8.4]: https://github.com/Automattic/jetpack-sync/compare/v4.8.3...v4.8.4
[4.8.3]: https://github.com/Automattic/jetpack-sync/compare/v4.8.2...v4.8.3
[4.8.2]: https://github.com/Automattic/jetpack-sync/compare/v4.8.1...v4.8.2
[4.8.1]: https://github.com/Automattic/jetpack-sync/compare/v4.8.0...v4.8.1
[4.8.0]: https://github.com/Automattic/jetpack-sync/compare/v4.7.0...v4.8.0
[4.7.0]: https://github.com/Automattic/jetpack-sync/compare/v4.6.0...v4.7.0
[4.6.0]: https://github.com/Automattic/jetpack-sync/compare/v4.5.0...v4.6.0
[4.5.0]: https://github.com/Automattic/jetpack-sync/compare/v4.4.0...v4.5.0
[4.4.0]: https://github.com/Automattic/jetpack-sync/compare/v4.3.0...v4.4.0
[4.3.0]: https://github.com/Automattic/jetpack-sync/compare/v4.2.0...v4.3.0
[4.2.0]: https://github.com/Automattic/jetpack-sync/compare/v4.1.1...v4.2.0
[4.1.1]: https://github.com/Automattic/jetpack-sync/compare/v4.1.0...v4.1.1
[4.1.0]: https://github.com/Automattic/jetpack-sync/compare/v4.0.2...v4.1.0
[4.0.2]: https://github.com/Automattic/jetpack-sync/compare/v4.0.1...v4.0.2
[4.0.1]: https://github.com/Automattic/jetpack-sync/compare/v4.0.0...v4.0.1
[4.0.0]: https://github.com/Automattic/jetpack-sync/compare/v3.15.0...v4.0.0
[3.15.0]: https://github.com/Automattic/jetpack-sync/compare/v3.14.4...v3.15.0
[3.14.4]: https://github.com/Automattic/jetpack-sync/compare/v3.14.3...v3.14.4
[3.14.3]: https://github.com/Automattic/jetpack-sync/compare/v3.14.2...v3.14.3
[3.14.2]: https://github.com/Automattic/jetpack-sync/compare/v3.14.1...v3.14.2
[3.14.1]: https://github.com/Automattic/jetpack-sync/compare/v3.14.0...v3.14.1
[3.14.0]: https://github.com/Automattic/jetpack-sync/compare/v3.13.2...v3.14.0
[3.13.2]: https://github.com/Automattic/jetpack-sync/compare/v3.13.1...v3.13.2
[3.13.1]: https://github.com/Automattic/jetpack-sync/compare/v3.13.0...v3.13.1
[3.13.0]: https://github.com/Automattic/jetpack-sync/compare/v3.12.0...v3.13.0
[3.12.0]: https://github.com/Automattic/jetpack-sync/compare/v3.11.0...v3.12.0
[3.11.0]: https://github.com/Automattic/jetpack-sync/compare/v3.10.0...v3.11.0
[3.10.0]: https://github.com/Automattic/jetpack-sync/compare/v3.9.1...v3.10.0
[3.9.1]: https://github.com/Automattic/jetpack-sync/compare/v3.9.0...v3.9.1
[3.9.0]: https://github.com/Automattic/jetpack-sync/compare/v3.8.1...v3.9.0

View File

@ -4,20 +4,21 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=7.0",
"automattic/jetpack-connection": "^4.0.0",
"automattic/jetpack-constants": "^2.0.4",
"automattic/jetpack-password-checker": "^0.3.2",
"automattic/jetpack-ip": "^0.2.3",
"automattic/jetpack-roles": "^2.0.3",
"automattic/jetpack-status": "^4.0.0"
"php": ">=7.2",
"automattic/jetpack-connection": "^6.7.7",
"automattic/jetpack-constants": "^3.0.5",
"automattic/jetpack-password-checker": "^0.4.7",
"automattic/jetpack-ip": "^0.4.6",
"automattic/jetpack-roles": "^3.0.5",
"automattic/jetpack-status": "^5.0.10"
},
"require-dev": {
"automattic/jetpack-changelogger": "^4.2.6",
"yoast/phpunit-polyfills": "^1.1.1",
"automattic/jetpack-changelogger": "^6.0.2",
"yoast/phpunit-polyfills": "^3.0.0",
"automattic/jetpack-search": "@dev",
"automattic/jetpack-waf": "@dev",
"automattic/wordbless": "@dev"
"automattic/jetpack-test-environment": "@dev",
"automattic/phpunit-select-config": "^1.0.1"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
@ -29,13 +30,14 @@
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
"phpunit-select-config phpunit.#.xml.dist --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\""
],
"test-php": [
"@composer phpunit"
],
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
@ -50,7 +52,7 @@
"link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "3.10.x-dev"
"dev-trunk": "4.9.x-dev"
},
"dependencies": {
"test-only": [

View File

@ -116,7 +116,7 @@ class Actions {
}
if ( self::sync_via_cron_allowed() ) {
self::init_sync_cron_jobs();
add_action( 'init', array( __CLASS__, 'init_sync_cron_jobs' ), 1 );
} elseif ( wp_next_scheduled( 'jetpack_sync_cron' ) ) {
self::clear_sync_cron_jobs();
}
@ -175,7 +175,9 @@ class Actions {
) ) {
self::initialize_sender();
add_action( 'shutdown', array( self::$sender, 'do_sync' ), 9998 );
add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
if ( self::should_initialize_sender( true ) ) {
add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
}
}
}
@ -212,9 +214,11 @@ class Actions {
* @access public
* @static
*
* @param bool $full_sync Whether the Full Sync sender should run on shutdown for this request.
*
* @return bool
*/
public static function should_initialize_sender() {
public static function should_initialize_sender( $full_sync = false ) {
// Allow for explicit disable of Sync from request param jetpack_sync_read_only.
if ( isset( $_REQUEST['jetpack_sync_read_only'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
@ -227,9 +231,10 @@ class Actions {
}
/**
* For now, if dedicated Sync is enabled we will always initialize send, even for GET and unauthenticated requests.
* For now, if dedicated Sync is enabled we will always initialize send, even for GET and unauthenticated requests
* but not for Full Sync, since it will still happen on shutdown.
*/
if ( Settings::is_dedicated_sync_enabled() ) {
if ( false === $full_sync && Settings::is_dedicated_sync_enabled() ) {
return true;
}
@ -603,7 +608,7 @@ class Actions {
'network_options' => true,
);
self::do_full_sync( $initial_sync_config );
self::do_full_sync( $initial_sync_config, 'initial_sync' );
}
/**
@ -628,9 +633,10 @@ class Actions {
* @static
*
* @param array $modules The sync modules should be included in this full sync. All will be included if null.
* @param mixed $context The context where the full sync was initiated from.
* @return bool True if full sync was successfully started.
*/
public static function do_full_sync( $modules = null ) {
public static function do_full_sync( $modules = null, $context = null ) {
if ( ! self::sync_allowed() ) {
return false;
}
@ -644,7 +650,7 @@ class Actions {
self::initialize_listener();
$full_sync_module->start( $modules );
$full_sync_module->start( $modules, $context );
return true;
}

View File

@ -79,6 +79,7 @@ class Defaults {
'jetpack_comment_form_color_scheme',
'jetpack_comment_likes_enabled',
'jetpack_excluded_extensions',
'jetpack_holiday_snow_enabled',
'jetpack_mailchimp',
'jetpack_options',
'jetpack_portfolio',
@ -89,7 +90,7 @@ class Defaults {
'jetpack_relatedposts',
'jetpack_social_notes_config',
'jetpack_social_settings',
'jetpack_social_autoconvert_images',
'jetpack_social_utm_settings',
'jetpack_sso_match_by_email',
'jetpack_sso_require_two_step',
'jetpack_sync_non_blocking', // is non-blocking Jetpack Sync flow enabled.
@ -460,8 +461,12 @@ class Defaults {
'wp_log', // WP Logging Plugin.
'wpephpcompat_jobs',
'wprss_feed_item',
'memberships_coupon',
'memberships_gift',
'tribe-ea-record', // The Events Calendar Plugin - Store Event Aggregator record information.
'wphb_minify_group', // Hummingbird Plugin - Used internally to keep data about assets minification.
'bigcommerce_task', // BigCommerce Plugin - Store import queue.
'secupress_log_err404', // SecuPress Plugin - Log 404 pages
'iw_omnibus_price_log', // Omnibus Plugin - Log price changes.
'od_url_metrics', // Optimization Detective - Log URL metrics.
);
/**
@ -764,6 +769,7 @@ class Defaults {
'_wp_attachment_is_custom_background',
'_wp_attachment_is_custom_header',
'_wp_attachment_metadata',
'_wp_old_date',
'_wp_page_template',
'_wp_trash_meta_comments_status',
'_wpas_feature_enabled',
@ -1291,41 +1297,58 @@ class Defaults {
* @var array list of module names.
*/
public static $default_full_sync_config = array(
'comments' => 1,
'constants' => 1,
'functions' => 1,
'options' => 1,
'posts' => 1,
'term_relationships' => 1,
'terms' => 1,
'themes' => 1,
'updates' => 1,
'users' => 1,
'posts' => 1,
'comments' => 1,
'updates' => 1,
'term_relationships' => 1,
);
/**
* Default Full Sync limits for one module.
*
* @var array list of limits.
*/
public static $default_full_sync_limits_per_module = array(
'chunk_size' => 100,
'max_chunks' => 10,
);
/**
* Default Full Sync max objects to send on a single request.
*
* @var array list of module => max.
*/
public static $default_full_sync_limits = array(
'comments' => array(
'comments' => array(
'chunk_size' => 100,
'max_chunks' => 10,
),
'posts' => array(
'posts' => array(
'chunk_size' => 100,
'max_chunks' => 1,
),
'term_relationships' => array(
'term_relationships' => array(
'chunk_size' => 1000,
'max_chunks' => 10,
),
'terms' => array(
'terms' => array(
'chunk_size' => 1000,
'max_chunks' => 10,
),
'users' => array(
'users' => array(
'chunk_size' => 100,
'max_chunks' => 10,
),
'woocommerce' => array(
'chunk_size' => 100,
'max_chunks' => 10,
),
'woocommerce_hpos_orders' => array(
'chunk_size' => 100,
'max_chunks' => 10,
),

View File

@ -40,6 +40,30 @@ class Main {
// Set up package version hook.
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
// Add the custom capabilities for managing modules
add_filter( 'map_meta_cap', array( __CLASS__, 'module_custom_caps' ), 10, 2 );
}
/**
* Sets the Module custom capabilities.
*
* @param string[] $caps Array of the user's capabilities.
* @param string $cap Capability name.
* @return string[] The user's capabilities, adjusted as necessary.
*/
public static function module_custom_caps( $caps, $cap ) {
switch ( $cap ) {
case 'jetpack_manage_modules':
case 'jetpack_activate_modules':
case 'jetpack_deactivate_modules':
$caps = array( 'manage_options' );
break;
case 'jetpack_configure_modules':
$caps = array( 'manage_options' );
break;
}
return $caps;
}
/**

View File

@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Sync;
*/
class Package_Version {
const PACKAGE_VERSION = '3.10.0';
const PACKAGE_VERSION = '4.9.2';
const PACKAGE_SLUG = 'sync';

View File

@ -1183,82 +1183,25 @@ class Replicastore implements Replicastore_Interface {
* @return array Checksums.
*/
public function checksum_all( $perform_text_conversion = false ) {
$post_checksum = $this->checksum_histogram( 'posts', null, null, null, null, true, '', false, false, $perform_text_conversion );
$comments_checksum = $this->checksum_histogram( 'comments', null, null, null, null, true, '', false, false, $perform_text_conversion );
$post_meta_checksum = $this->checksum_histogram( 'postmeta', null, null, null, null, true, '', false, false, $perform_text_conversion );
$comment_meta_checksum = $this->checksum_histogram( 'commentmeta', null, null, null, null, true, '', false, false, $perform_text_conversion );
$terms_checksum = $this->checksum_histogram( 'terms', null, null, null, null, true, '', false, false, $perform_text_conversion );
$term_relationships_checksum = $this->checksum_histogram( 'term_relationships', null, null, null, null, true, '', false, false, $perform_text_conversion );
$term_taxonomy_checksum = $this->checksum_histogram( 'term_taxonomy', null, null, null, null, true, '', false, false, $perform_text_conversion );
$all_checksum_tables = Table_Checksum::get_allowed_tables();
$result = array(
'posts' => $this->summarize_checksum_histogram( $post_checksum ),
'comments' => $this->summarize_checksum_histogram( $comments_checksum ),
'post_meta' => $this->summarize_checksum_histogram( $post_meta_checksum ),
'comment_meta' => $this->summarize_checksum_histogram( $comment_meta_checksum ),
'terms' => $this->summarize_checksum_histogram( $terms_checksum ),
'term_relationships' => $this->summarize_checksum_histogram( $term_relationships_checksum ),
'term_taxonomy' => $this->summarize_checksum_histogram( $term_taxonomy_checksum ),
);
unset( $all_checksum_tables['users'] ); // Handled separately - TODO.
unset( $all_checksum_tables['usermeta'] ); // Handled separately - TODO.
unset( $all_checksum_tables['termmeta'] ); // Handled separately - TODO.
unset( $all_checksum_tables['links'] ); // Not supported yet. Consider removing from default config.
unset( $all_checksum_tables['options'] ); // Not supported yet. Consider removing from default config.
/**
* WooCommerce tables
*/
$all_checksum_tables = array_unique( array_keys( $all_checksum_tables ) );
/**
* On WordPress.com, we can't directly check if the site has support for WooCommerce.
* Having the option to override the functionality here helps with syncing WooCommerce tables.
*
* @since 10.1
*
* @param bool If we should we force-enable WooCommerce tables support.
*/
$force_woocommerce_support = apply_filters( 'jetpack_table_checksum_force_enable_woocommerce', false );
if ( $force_woocommerce_support || class_exists( 'WooCommerce' ) ) {
/**
* Guard in Try/Catch as it's possible for the WooCommerce class to exist, but
* the tables to not. If we don't do this, the response will be just the exception, without
* returning any valid data. This will prevent us from ever performing a checksum/fix
* for sites like this.
* It's better to just skip the tables in the response, instead of completely failing.
*/
$result = array();
foreach ( $all_checksum_tables as $table ) {
$result_key = in_array( $table, array( 'postmeta', 'commentmeta' ), true ) ? str_replace( 'meta', '_meta', $table ) : $table;
try {
$woocommerce_order_items_checksum = $this->checksum_histogram( 'woocommerce_order_items' );
$result['woocommerce_order_items'] = $this->summarize_checksum_histogram( $woocommerce_order_items_checksum );
$checksum = $this->checksum_histogram( $table, null, null, null, null, true, '', false, false, $perform_text_conversion );
$result[ $result_key ] = $this->summarize_checksum_histogram( $checksum );
} catch ( Exception $ex ) {
$result['woocommerce_order_items'] = null;
}
try {
$woocommerce_order_itemmeta_checksum = $this->checksum_histogram( 'woocommerce_order_itemmeta' );
$result['woocommerce_order_itemmeta'] = $this->summarize_checksum_histogram( $woocommerce_order_itemmeta_checksum );
} catch ( Exception $ex ) {
$result['woocommerce_order_itemmeta'] = null;
}
if ( Table_Checksum::enable_woocommerce_hpos_tables() ) {
try {
$woocommerce_hpos_orders_checksum = $this->checksum_histogram( 'wc_orders' );
$result['wc_orders'] = $this->summarize_checksum_histogram( $woocommerce_hpos_orders_checksum );
} catch ( Exception $ex ) {
$result['wc_orders'] = null;
}
try {
$woocommerce_hpos_order_addresses_checksum = $this->checksum_histogram( 'wc_order_addresses' );
$result['wc_order_addresses'] = $this->summarize_checksum_histogram( $woocommerce_hpos_order_addresses_checksum );
} catch ( Exception $ex ) {
$result['wc_order_addresses'] = null;
}
try {
$woocommerce_hpos_order_operational_data_checksum = $this->checksum_histogram( 'wc_order_operational_data' );
$result['wc_order_operational_data'] = $this->summarize_checksum_histogram( $woocommerce_hpos_order_operational_data_checksum );
} catch ( Exception $ex ) {
$result['wc_order_operational_data'] = null;
}
$result[ $result_key ] = null;
}
}
@ -1392,7 +1335,10 @@ class Replicastore implements Replicastore_Interface {
} else {
$histogram[ "{$ids_range[ 'min_range' ]}-{$ids_range[ 'max_range' ]}" ] = $batch_checksum;
}
// If ids_range['max_range'] is PHP_INT_MAX, we've reached the end of the table. Edge case causing the loop to never end.
if ( PHP_INT_MAX === (int) $ids_range['max_range'] ) {
break;
}
$previous_max_id = $ids_range['max_range'] + 1;
// If we've reached the max_range lets bail out.
if ( $previous_max_id > $range_edges['max_range'] ) {

View File

@ -58,6 +58,11 @@ class REST_Endpoints {
'type' => 'array',
'required' => false,
),
'context' => array(
'description' => __( 'Context for the Full Sync', 'jetpack-sync' ),
'type' => 'string',
'required' => false,
),
),
)
);
@ -363,9 +368,11 @@ class REST_Endpoints {
$modules = null;
}
$context = $request->get_param( 'context' );
return rest_ensure_response(
array(
'scheduled' => Actions::do_full_sync( $modules ),
'scheduled' => Actions::do_full_sync( $modules, $context ),
)
);
}
@ -619,7 +626,7 @@ class REST_Endpoints {
$sender = new REST_Sender();
if ( 'immediate' === $queue_name ) {
return rest_ensure_response( $sender->immediate_full_sync_pull( $number_of_items ) );
return rest_ensure_response( $sender->immediate_full_sync_pull() );
}
$response = $sender->queue_pull( $queue_name, $number_of_items, $args );

View File

@ -430,7 +430,7 @@ class Sender {
}
if ( $do_real_exit ) {
exit;
exit( 0 );
}
}
@ -580,7 +580,7 @@ class Sender {
* @access private
*/
private function fastcgi_finish_request() {
if ( function_exists( 'fastcgi_finish_request' ) && version_compare( phpversion(), '7.0.16', '>=' ) ) {
if ( function_exists( 'fastcgi_finish_request' ) ) {
fastcgi_finish_request();
}
}
@ -704,16 +704,17 @@ class Sender {
*
* @param string $action_name The action.
* @param array $data The data associated with the action.
* @param string $key The key to use for the action.
*
* @return array Items processed. TODO: this doesn't make much sense anymore, it should probably be just a bool.
*/
public function send_action( $action_name, $data = null ) {
public function send_action( $action_name, $data = null, $key = null ) {
if ( ! Settings::is_sender_enabled( 'full_sync' ) ) {
return array();
}
// Compose the data to be sent.
$action_to_send = $this->create_action_to_send( $action_name, $data );
$action_to_send = $this->create_action_to_send( $action_name, $data, $key );
list( $items_to_send, $skipped_items_ids, $items, $preprocess_duration ) = $this->get_items_to_send( $action_to_send, true ); // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
Settings::set_is_sending( true );
@ -741,11 +742,12 @@ class Sender {
*
* @param string $action_name The action.
* @param array $data The data associated with the action.
* @param string $key The key to use for the action.
* @return array An array of synthetic sync actions keyed by current microtime(true)
*/
private function create_action_to_send( $action_name, $data ) {
private function create_action_to_send( $action_name, $data, $key = null ) {
return array(
(string) microtime( true ) => array(
$key ?? (string) microtime( true ) => array(
$action_name,
$data,
get_current_user_id(),

View File

@ -115,7 +115,7 @@ class Settings {
public static function get_settings() {
$settings = array();
foreach ( array_keys( self::$valid_settings ) as $setting ) {
$settings[ $setting ] = self::get_setting( $setting );
$settings[ $setting ] = static::get_setting( $setting );
}
return $settings;
@ -350,7 +350,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_blacklisted_post_types_sql() {
return 'post_type NOT IN (\'' . implode( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ) ) . '\')';
return 'post_type NOT IN (\'' . implode( '\', \'', array_map( 'esc_sql', static::get_setting( 'post_types_blacklist' ) ) ) . '\')';
}
/**
@ -365,7 +365,7 @@ class Settings {
return array(
'post_type' => array(
'operator' => 'NOT IN',
'values' => array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ),
'values' => array_map( 'esc_sql', static::get_setting( 'post_types_blacklist' ) ),
),
);
}
@ -380,7 +380,25 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_blacklisted_taxonomies_sql() {
return "taxonomy NOT IN ('" . implode( "', '", array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ) ) . "')";
return "taxonomy NOT IN ('" . implode( "', '", array_map( 'esc_sql', static::get_setting( 'taxonomies_blacklist' ) ) ) . "')";
}
/**
* Returns escaped SQL for whitelisted taxonomies.
* Can be injected directly into a WHERE clause.
*
* @access public
* @static
*
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_taxonomies_sql() {
global $wp_taxonomies;
$allowed_taxonomies = array_keys( $wp_taxonomies );
$allowed_taxonomies = array_diff( $allowed_taxonomies, static::get_setting( 'taxonomies_blacklist' ) );
return "taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $allowed_taxonomies ) ) . "')";
}
/**
@ -393,7 +411,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_post_meta_sql() {
return 'meta_key IN (\'' . implode( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_meta_whitelist' ) ) ) . '\')';
return 'meta_key IN (\'' . implode( '\', \'', array_map( 'esc_sql', static::get_setting( 'post_meta_whitelist' ) ) ) . '\')';
}
/**
@ -408,7 +426,7 @@ class Settings {
return array(
'meta_key' => array(
'operator' => 'IN',
'values' => array_map( 'esc_sql', self::get_setting( 'post_meta_whitelist' ) ),
'values' => array_map( 'esc_sql', static::get_setting( 'post_meta_whitelist' ) ),
),
);
}
@ -425,7 +443,7 @@ class Settings {
return array(
'taxonomy' => array(
'operator' => 'NOT IN',
'values' => array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ),
'values' => array_map( 'esc_sql', static::get_setting( 'taxonomies_blacklist' ) ),
),
);
}
@ -442,7 +460,7 @@ class Settings {
global $wp_taxonomies;
$allowed_taxonomies = array_keys( $wp_taxonomies );
$allowed_taxonomies = array_diff( $allowed_taxonomies, self::get_setting( 'taxonomies_blacklist' ) );
$allowed_taxonomies = array_diff( $allowed_taxonomies, static::get_setting( 'taxonomies_blacklist' ) );
return array(
'taxonomy' => array(
'operator' => 'IN',
@ -461,7 +479,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_comment_meta_sql() {
return 'meta_key IN (\'' . implode( '\', \'', array_map( 'esc_sql', self::get_setting( 'comment_meta_whitelist' ) ) ) . '\')';
return 'meta_key IN (\'' . implode( '\', \'', array_map( 'esc_sql', static::get_setting( 'comment_meta_whitelist' ) ) ) . '\')';
}
/**
@ -476,7 +494,7 @@ class Settings {
return array(
'meta_key' => array(
'operator' => 'IN',
'values' => array_map( 'esc_sql', self::get_setting( 'comment_meta_whitelist' ) ),
'values' => array_map( 'esc_sql', static::get_setting( 'comment_meta_whitelist' ) ),
),
);
}
@ -573,7 +591,7 @@ class Settings {
* @return boolean Whether sync is enabled.
*/
public static function is_sync_enabled() {
return ! ( self::get_setting( 'disable' ) || self::get_setting( 'network_disable' ) );
return ! ( static::get_setting( 'disable' ) || static::get_setting( 'network_disable' ) );
}
/**
@ -664,7 +682,7 @@ class Settings {
* @return boolean Whether sync is enabled.
*/
public static function is_sender_enabled( $queue_id ) {
return (bool) self::get_setting( $queue_id . '_sender_enabled' );
return (bool) static::get_setting( $queue_id . '_sender_enabled' );
}
/**
@ -676,7 +694,7 @@ class Settings {
* @return boolean Whether sync is enabled.
*/
public static function is_checksum_enabled() {
return ! (bool) self::get_setting( 'checksum_disable' );
return ! (bool) static::get_setting( 'checksum_disable' );
}
/**
@ -688,7 +706,7 @@ class Settings {
* @return boolean Whether dedicated Sync flow is enabled.
*/
public static function is_dedicated_sync_enabled() {
return (bool) self::get_setting( 'dedicated_sync_enabled' );
return (bool) static::get_setting( 'dedicated_sync_enabled' );
}
/**
@ -700,7 +718,7 @@ class Settings {
* @return boolean Whether custom queue table is enabled.
*/
public static function is_custom_queue_table_enabled() {
return (bool) self::get_setting( 'custom_queue_table_enabled' );
return (bool) static::get_setting( 'custom_queue_table_enabled' );
}
/**
@ -712,6 +730,6 @@ class Settings {
* @return boolean Whether wpcom rest api is enabled.
*/
public static function is_wpcom_rest_api_enabled() {
return (bool) self::get_setting( 'wpcom_rest_api_enabled' );
return (bool) static::get_setting( 'wpcom_rest_api_enabled' );
}
}

View File

@ -379,12 +379,12 @@ class Callables extends Module {
$plugins_action_links = array();
// Is the transient lock in place?
$plugins_lock = get_transient( 'jetpack_plugin_api_action_links_refresh', false );
$plugins_lock = get_transient( 'jetpack_plugin_api_action_links_refresh' );
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 = Functions::get_plugins();
foreach ( $plugins as $plugin_file => $plugin_data ) {
/**
* Plugins often like to unset things but things break if they are not able to.
*/
@ -396,13 +396,13 @@ class Callables extends Module {
'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' );
$action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, $plugin_data, 'all' );
// Verify $action_links is still an array.
if ( ! is_array( $action_links ) ) {
$action_links = array();
}
/** 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 = apply_filters( "plugin_action_links_{$plugin_file}", $action_links, $plugin_file, $plugin_data, 'all' );
// Verify $action_links is still an array to resolve warnings from filters not returning an array.
if ( is_array( $action_links ) ) {
$action_links = array_filter( $action_links );

View File

@ -14,6 +14,7 @@ use Automattic\Jetpack\Sync\Settings;
* Class to handle sync for comments.
*/
class Comments extends Module {
/**
* Sync module name.
*
@ -37,16 +38,30 @@ class Comments extends Module {
}
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Comments->table' );
return 'comments';
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
global $wpdb;
return $wpdb->comments;
}
/**
* Retrieve a comment by its ID.
*
@ -304,8 +319,14 @@ class Comments extends Module {
* @access public
*/
public function init_before_send() {
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
$sync_module = Modules::get_module( 'full-sync' );
if ( $sync_module instanceof Full_Sync_Immediately ) {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'extract_comments_and_meta' ) );
} else {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
}
}
/**
@ -501,4 +522,73 @@ class Comments extends Module {
$previous_interval_end,
);
}
/**
* 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 extract_comments_and_meta( $args ) {
list( $filtered_comments, $previous_end ) = $args;
return array(
$filtered_comments['objects'],
$filtered_comments['meta'],
$previous_end,
);
}
/**
* Given the Module Configuration and Status return the next chunk of items to send.
* This function also expands the posts and metadata and filters them based on the maximum size constraints.
*
* @param array $config This module Full Sync configuration.
* @param array $status This module Full Sync status.
* @param int $chunk_size Chunk size.
*
* @return array
*/
public function get_next_chunk( $config, $status, $chunk_size ) {
$comment_ids = parent::get_next_chunk( $config, $status, $chunk_size );
// If no comment IDs were fetched, return an empty array.
if ( empty( $comment_ids ) ) {
return array();
}
$comments = get_comments(
array(
'comment__in' => $comment_ids,
'orderby' => 'comment_ID',
'order' => 'DESC',
)
);
// If no comments were fetched, make sure to return the expected structure so that status is updated correctly.
if ( empty( $comments ) ) {
return array(
'object_ids' => $comment_ids,
'objects' => array(),
'meta' => array(),
);
}
// Get the comment IDs from the comments that were fetched.
$fetched_comment_ids = wp_list_pluck( $comments, 'comment_ID' );
$metadata = $this->get_metadata( $fetched_comment_ids, 'comment', Settings::get_setting( 'comment_meta_whitelist' ) );
// Filter the comments and metadata based on the maximum size constraints.
list( $filtered_comment_ids, $filtered_comments, $filtered_comments_metadata ) = $this->filter_objects_and_metadata_by_size(
'comment',
$comments,
$metadata,
self::MAX_META_LENGTH, // Replace with appropriate comment meta length constant.
self::MAX_SIZE_FULL_SYNC
);
return array(
'object_ids' => $filtered_comment_ids,
'objects' => $filtered_comments,
'meta' => $filtered_comments_metadata,
);
}
}

View File

@ -27,6 +27,13 @@ class Constants extends Module {
*/
const CONSTANTS_AWAIT_TRANSIENT_NAME = 'jetpack_sync_constants_await';
/**
* Constants whitelist array.
*
* @var array
*/
public $constants_whitelist;
/**
* Sync module name.
*

View File

@ -60,10 +60,11 @@ class Full_Sync_Immediately extends Module {
* @access public
*
* @param array $full_sync_config Full sync configuration.
* @param mixed $context The context where the full sync was initiated from.
*
* @return bool Always returns true at success.
*/
public function start( $full_sync_config = null ) {
public function start( $full_sync_config = null, $context = null ) {
// There was a full sync in progress.
if ( $this->is_started() && ! $this->is_finished() ) {
/**
@ -114,15 +115,15 @@ class Full_Sync_Immediately extends Module {
*
* @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.
* @param mixed $context The context where the full sync was initiated from.
*
* @since 1.6.3
* @since-jetpack 4.2.0
* @since-jetpack 7.3.0 Added $range arg.
* @since-jetpack 7.4.0 Added $empty arg.
* @since 4.4.0 Added $context arg.
*/
do_action( 'jetpack_full_sync_start', $full_sync_config, $range );
$this->send_action( 'jetpack_full_sync_start', array( $full_sync_config, $range ) );
$this->send_action( 'jetpack_full_sync_start', array( $full_sync_config, $range, $context ) );
return true;
}
@ -391,6 +392,8 @@ class Full_Sync_Immediately extends Module {
$progress = $this->get_status()['progress'];
$started = $this->get_status()['started'];
foreach ( $this->get_remaining_modules_to_send() as $module ) {
$progress[ $module->name() ] = $module->send_full_sync_actions( $config[ $module->name() ], $progress[ $module->name() ], $send_until );
if ( isset( $progress[ $module->name() ]['error'] ) ) {
@ -401,6 +404,10 @@ class Full_Sync_Immediately extends Module {
$this->update_status( array( 'progress' => $progress ) );
return true;
}
if ( $this->get_status()['started'] !== $started ) {
// Full sync was restarted, stop sending.
return false;
}
}
$this->send_full_sync_end();
@ -414,33 +421,25 @@ class Full_Sync_Immediately extends Module {
* @return array
*/
public function get_remaining_modules_to_send() {
$status = $this->get_status();
return array_filter(
Modules::get_modules(),
/**
* Select configured and not finished modules.
*
* @param Module $module
* @return bool
*/
function ( $module ) use ( $status ) {
// Skip module if not configured for this sync or module is done.
if ( ! isset( $status['config'][ $module->name() ] ) ) {
return false;
}
if ( ! $status['config'][ $module->name() ] ) {
return false;
}
if ( isset( $status['progress'][ $module->name() ]['finished'] ) ) {
if ( true === $status['progress'][ $module->name() ]['finished'] ) {
return false;
}
}
return true;
$status = $this->get_status();
$remaining_modules = array();
foreach ( array_keys( $status['config'] ) as $module_name ) {
$module = Modules::get_module( $module_name );
if ( ! $module ) {
continue;
}
);
if ( isset( $status['progress'][ $module_name ]['finished'] ) &&
true === $status['progress'][ $module_name ]['finished'] ) {
continue;
}
// Ensure that 'constants', 'options', and 'callables' are sent first.
if ( in_array( $module_name, array( 'network_options', 'options', 'functions', 'constants' ), true ) ) {
array_unshift( $remaining_modules, $module );
} else {
$remaining_modules[] = $module;
}
}
return $remaining_modules;
}
/**

View File

@ -80,9 +80,10 @@ class Full_Sync extends Module {
* @access public
*
* @param array $module_configs Full sync configuration for all sync modules.
* @param mixed $context Context for the full sync.
* @return bool Always returns true at success.
*/
public function start( $module_configs = null ) {
public function start( $module_configs = null, $context = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$was_already_running = $this->is_started() && ! $this->is_finished();
// Remove all evidence of previous full sync items and status.

View File

@ -38,6 +38,8 @@ class Meta extends Module {
* @return array
*/
public function get_objects_by_id( $object_type, $config ) {
global $wpdb;
$table = _get_meta_table( $object_type );
if ( ! $table ) {
@ -48,13 +50,42 @@ class Meta extends Module {
return array();
}
$meta_objects = array();
$object_id_column = $object_type . '_id';
$object_key_pairs = array();
foreach ( $config as $item ) {
$meta = null;
if ( isset( $item['id'] ) && isset( $item['meta_key'] ) ) {
$meta = $this->get_object_by_id( $object_type, (int) $item['id'], (string) $item['meta_key'] );
$object_key_pairs[ (int) $item['id'] ][] = (string) $item['meta_key'];
}
$meta_objects[ $item['id'] . '-' . $item['meta_key'] ] = $meta;
}
$meta_objects = array();
$where_sql = '';
$current_query_length = 0;
foreach ( $object_key_pairs as $object_id => $keys ) {
$keys_placeholders = implode( ',', array_fill( 0, count( $keys ), '%s' ) );
$where_condition = trim(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"( `$object_id_column` = %d AND meta_key IN ( $keys_placeholders ) )",
array_merge( array( $object_id ), $keys )
)
);
$where_sql = empty( $where_sql ) ? $where_condition : $where_sql . ' OR ' . $where_condition;
$current_query_length += strlen( $where_sql );
if ( $current_query_length > self::MAX_DB_QUERY_LENGTH ) {
$meta_objects = $this->fetch_prepared_meta_from_db( $object_type, $where_sql, $meta_objects );
$where_sql = '';
$current_query_length = 0;
}
}
if ( ! empty( $where_sql ) ) {
$meta_objects = $this->fetch_prepared_meta_from_db( $object_type, $where_sql, $meta_objects );
}
return $meta_objects;
@ -63,9 +94,9 @@ class Meta extends Module {
/**
* Get a single Meta Result.
*
* @param string $object_type post, comment, term, user.
* @param null $id Object ID.
* @param null $meta_key Meta Key.
* @param string $object_type post, comment, term, user.
* @param int|null $id Object ID.
* @param string|null $meta_key Meta Key.
*
* @return mixed|null
*/
@ -76,37 +107,86 @@ class Meta extends Module {
return null;
}
$table = _get_meta_table( $object_type );
$table = _get_meta_table( $object_type );
if ( ! $table ) {
return null;
}
$object_id_column = $object_type . '_id';
// Sanitize so that the array only has integer values.
$meta = $wpdb->get_results(
$wpdb->prepare(
$where_condition = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM {$table} WHERE {$object_id_column} = %d AND meta_key = %s",
$id,
$meta_key
),
"{$object_id_column} = %d AND meta_key = %s",
$id,
$meta_key
);
$meta_objects = $this->fetch_prepared_meta_from_db( $object_type, $where_condition );
$key = $id . '-' . $meta_key;
return $meta_objects[ $key ] ?? null;
}
/**
* Fetch meta from DB and return them in a standard format.
*
* @param string $object_type The meta object type, eg 'post', 'user' etc.
* @param string $where Prepared SQL 'where' statement.
* @param array $meta_objects An existing array of meta to populate. Defaults to an empty array.
* @return array
*/
private function fetch_prepared_meta_from_db( $object_type, $where, $meta_objects = array() ) {
global $wpdb;
$table = _get_meta_table( $object_type );
$object_id_column = $object_type . '_id';
$meta = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT * FROM {$table} WHERE {$where}",
ARRAY_A
);
$meta_objects = null;
if ( ! is_wp_error( $meta ) && ! empty( $meta ) ) {
foreach ( $meta as $meta_entry ) {
if ( 'post' === $object_type && strlen( $meta_entry['meta_value'] ) >= Posts::MAX_POST_META_LENGTH ) {
$meta_entry['meta_value'] = '';
$object_id = $meta_entry[ $object_id_column ];
$meta_key = $meta_entry['meta_key'];
$key = $object_id . '-' . $meta_key;
if ( ! isset( $meta_objects[ $key ] ) ) {
$meta_objects[ $key ] = array();
}
$meta_objects[] = array(
'meta_type' => $object_type,
'meta_id' => $meta_entry['meta_id'],
'meta_key' => $meta_key,
'meta_value' => $meta_entry['meta_value'],
'object_id' => $meta_entry[ $object_id_column ],
);
$meta_objects[ $key ][] = $this->get_prepared_meta_object( $object_type, $meta_entry );
}
}
return $meta_objects;
}
/**
* Accepts a DB meta entry and returns it in a standard format.
*
* @param string $object_type The meta object type, eg 'post', 'user' etc.
* @param array $meta_entry A meta array.
* @return array
*/
private function get_prepared_meta_object( $object_type, $meta_entry ) {
$object_id_column = $object_type . '_id';
if ( 'post' === $object_type && strlen( $meta_entry['meta_value'] ) >= Posts::MAX_META_LENGTH ) {
$meta_entry['meta_value'] = '';
}
return array(
'meta_type' => $object_type,
'meta_id' => $meta_entry['meta_id'],
'meta_key' => $meta_entry['meta_key'],
'meta_value' => $meta_entry['meta_value'],
'object_id' => $meta_entry[ $object_id_column ],
);
}
}

View File

@ -7,6 +7,7 @@
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
use Automattic\Jetpack\Sync\Functions;
use Automattic\Jetpack\Sync\Listener;
use Automattic\Jetpack\Sync\Replicastore;
@ -28,6 +29,35 @@ abstract class Module {
*/
const ARRAY_CHUNK_SIZE = 10;
/**
* Max query length for DB queries.
*
* @access public
*
* @var int
*/
const MAX_DB_QUERY_LENGTH = 15 * 1024;
/**
* Max bytes allowed for full sync upload for the module.
* Default Setting : 7MB.
*
* @access public
*
* @var int
*/
const MAX_SIZE_FULL_SYNC = 7000000;
/**
* Max bytes allowed for post meta_value => length.
* Default Setting : 2MB.
*
* @access public
*
* @var int
*/
const MAX_META_LENGTH = 2000000;
/**
* Sync module name.
*
@ -49,16 +79,40 @@ abstract class Module {
}
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string|bool
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Module->table' );
return false;
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table_name() {
public function table() {
return false;
}
/**
* The full sync action name for this module.
*
* @access public
*
* @return string
*/
public function full_sync_action_name() {
return 'jetpack_full_sync_' . $this->name();
}
// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
/**
@ -298,7 +352,7 @@ abstract class Module {
return $wpdb->get_col(
"
SELECT {$this->id_field()}
FROM {$wpdb->{$this->table_name()}}
FROM {$this->table()}
WHERE {$this->get_where_sql( $config )}
AND {$this->id_field()} < {$status['last_sent']}
ORDER BY {$this->id_field()}
@ -321,7 +375,7 @@ abstract class Module {
return $wpdb->get_var(
"
SELECT {$this->id_field()}
FROM {$wpdb->{$this->table_name()}}
FROM {$this->table()}
WHERE {$this->get_where_sql( $config )}
ORDER BY {$this->id_field()}
LIMIT 1
@ -357,7 +411,12 @@ abstract class Module {
$status['last_sent'] = $this->get_initial_last_sent();
}
$limits = Settings::get_setting( 'full_sync_limits' )[ $this->name() ];
$limits = Settings::get_setting( 'full_sync_limits' )[ $this->name() ] ??
Defaults::get_default_setting( 'full_sync_limits' )[ $this->name() ] ??
array(
'max_chunks' => 10,
'chunk_size' => 100,
);
$chunks_sent = 0;
@ -375,18 +434,24 @@ abstract class Module {
$status['finished'] = true;
return $status;
}
$result = $this->send_action( 'jetpack_full_sync_' . $this->name(), array( $objects, $status['last_sent'] ) );
if ( is_wp_error( $result ) || $wpdb->last_error ) {
$status['error'] = true;
return $status;
// If we have objects as a key it means get_next_chunk is being overridden, we need to check for it being an empty array.
// In case it is an empty array, we should not send the action or increase the chunks_sent, we just need to update the status.
if ( ! isset( $objects['objects'] ) || array() !== $objects['objects'] ) {
$key = $this->full_sync_action_name() . '_' . crc32( wp_json_encode( $status['last_sent'] ) );
$result = $this->send_action( $this->full_sync_action_name(), array( $objects, $status['last_sent'] ), $key );
if ( is_wp_error( $result ) || $wpdb->last_error ) {
$status['error'] = true;
return $status;
}
++$chunks_sent;
}
// Updated the sent and last_sent status.
$status = $this->set_send_full_sync_actions_status( $status, $objects );
if ( $last_item === $status['last_sent'] ) {
$status['finished'] = true;
return $status;
}
++$chunks_sent;
}
return $status;
@ -394,6 +459,9 @@ abstract class Module {
/**
* Set the status of the full sync action based on the objects that were sent.
* Used to update the status of the module after sending a chunk of objects.
* Since Full Sync logic chunking relies on order of items being processed in descending order, we need to sort
* due to some modules (e.g. WooCommerce) changing the order while getting the objects.
*
* @access protected
*
@ -403,8 +471,10 @@ abstract class Module {
* @return array The updated status.
*/
protected function set_send_full_sync_actions_status( $status, $objects ) {
$status['last_sent'] = end( $objects );
$status['sent'] += count( $objects );
$object_ids = $objects['object_ids'] ?? $objects;
$status['last_sent'] = end( $object_ids );
$status['sent'] += count( $object_ids );
return $status;
}
@ -413,10 +483,11 @@ abstract class Module {
*
* @param string $action_name The action.
* @param array $data The data associated with the action.
* @param string $key The key to use for the action.
*/
public function send_action( $action_name, $data = null ) {
public function send_action( $action_name, $data = null, $key = null ) {
$sender = Sender::get_instance();
return $sender->send_action( $action_name, $data );
return $sender->send_action( $action_name, $data, $key );
}
/**
@ -571,14 +642,13 @@ abstract class Module {
* @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() ) {
if ( ! $this->table() ) {
return false;
}
$results = array();
$table = $wpdb->{$this->table_name()};
$table = $this->table();
$current_max = 0;
$current_min = 1;
$id_field = $this->id_field();
@ -628,7 +698,7 @@ abstract class Module {
*/
public function total( $config ) {
global $wpdb;
$table = $wpdb->{$this->table_name()};
$table = $this->table();
$where = $this->get_where_sql( $config );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
@ -646,4 +716,64 @@ abstract class Module {
public function get_where_sql( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return '1=1';
}
/**
* Filters objects and metadata based on maximum size constraints.
* It always allows the first object with its metadata, even if they exceed the limit.
*
* @access public
*
* @param string $type The type of objects to filter (e.g., 'post' or 'comment').
* @param array $objects The array of objects to filter (e.g., posts or comments).
* @param array $metadata The array of metadata to filter.
* @param int $max_meta_size Maximum size for individual objects.
* @param int $max_total_size Maximum combined size for objects and metadata.
* @return array An array containing the filtered object IDs, filtered objects, and filtered metadata.
*/
public function filter_objects_and_metadata_by_size( $type, $objects, $metadata, $max_meta_size, $max_total_size ) {
$filtered_objects = array();
$filtered_metadata = array();
$filtered_object_ids = array();
$current_size = 0;
foreach ( $objects as $object ) {
$object_size = strlen( maybe_serialize( $object ) );
$current_metadata = array();
$metadata_size = 0;
$id_field = $this->id_field();
$object_id = (int) ( is_object( $object ) ? $object->{$id_field} : $object[ $id_field ] );
foreach ( $metadata as $key => $metadata_item ) {
if ( (int) $metadata_item->{$type . '_id'} === $object_id ) {
$metadata_item_size = strlen( maybe_serialize( $metadata_item->meta_value ) );
if ( $metadata_item_size >= $max_meta_size ) {
$metadata_item->meta_value = ''; // Trim metadata if too large.
}
$current_metadata[] = $metadata_item;
$metadata_size += $metadata_item_size >= $max_meta_size ? 0 : $metadata_item_size;
if ( ! empty( $filtered_object_ids ) && ( $current_size + $object_size + $metadata_size ) > $max_total_size ) {
break 2; // Exit both loops.
}
unset( $metadata[ $key ] );
}
}
// Always allow the first object with metadata.
if ( empty( $filtered_object_ids ) || ( $current_size + $object_size + $metadata_size ) <= $max_total_size ) {
$filtered_object_ids[] = strval( is_object( $object ) ? $object->{$id_field} : $object[ $id_field ] );
$filtered_objects[] = $object;
$filtered_metadata = array_merge( $filtered_metadata, $current_metadata );
$current_size += $object_size + $metadata_size;
} else {
break;
}
}
return array(
$filtered_object_ids,
$filtered_objects,
$filtered_metadata,
);
}
}

View File

@ -41,6 +41,24 @@ class Plugins extends Module {
*/
private $plugins = array();
/**
* List of all updated plugins.
*
* @access private
*
* @var array
*/
private $plugins_updated = array();
/**
* State
*
* @access private
*
* @var array
*/
private $state = array();
/**
* Sync module name.
*
@ -131,10 +149,10 @@ class Plugins extends Module {
switch ( $details['action'] ) {
case 'update':
$state = array(
$this->state = array(
'is_autoupdate' => Jetpack_Constants::is_true( 'JETPACK_PLUGIN_AUTOUPDATE' ),
);
$errors = $this->get_errors( $upgrader->skin );
$errors = $this->get_errors( $upgrader->skin );
if ( $errors ) {
foreach ( $plugins as $slug ) {
/**
@ -149,13 +167,20 @@ class Plugins extends Module {
* @param string Error code
* @param string Error message
*/
do_action( 'jetpack_plugin_update_failed', $this->get_plugin_info( $slug ), $errors['code'], $errors['message'], $state );
do_action( 'jetpack_plugin_update_failed', $this->get_plugin_info( $slug ), $errors['code'], $errors['message'], $this->state );
}
return;
}
$this->plugins_updated = array_map( array( $this, 'get_plugin_info' ), $plugins );
add_action( 'shutdown', array( $this, 'sync_plugins_updated' ), 9 );
break;
case 'install':
/**
* Sync that a plugin update
* 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 1.6.3
* @since-jetpack 5.8.0
@ -164,26 +189,7 @@ class Plugins extends Module {
*
* @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 1.6.3
* @since-jetpack 5.8.0
*
* @module sync
*
* @param array () $plugin, Plugin Data
*/
do_action( 'jetpack_plugin_installed', array_map( array( $this, 'get_plugin_info' ), $plugins ) );
return;
do_action( 'jetpack_plugin_installed', array_map( array( $this, 'get_plugin_info' ), $plugins ) );
}
}
@ -379,4 +385,23 @@ class Plugins extends Module {
$plugin_data,
);
}
/**
* Helper method for firing the 'jetpack_plugins_updated' action on shutdown.
*
* @access public
*/
public function sync_plugins_updated() {
/**
* Sync that a plugin update
*
* @since 1.6.3
* @since-jetpack 5.8.0
*
* @module sync
*
* @param array () $plugin, Plugin Data
*/
do_action( 'jetpack_plugins_updated', $this->plugins_updated, $this->state );
}
}

View File

@ -64,26 +64,6 @@ class Posts extends Module {
*/
const MAX_POST_CONTENT_LENGTH = 5000000;
/**
* Max bytes allowed for post meta_value => length.
* Current Setting : 2MB.
*
* @access public
*
* @var int
*/
const MAX_POST_META_LENGTH = 2000000;
/**
* Max bytes allowed for full sync upload.
* Current Setting : 7MB.
*
* @access public
*
* @var int
*/
const MAX_SIZE_FULL_SYNC = 7000000;
/**
* Default previous post state.
* Used for default previous post status.
@ -106,16 +86,30 @@ class Posts extends Module {
}
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Posts->table' );
return 'posts';
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
global $wpdb;
return $wpdb->posts;
}
/**
* Retrieve a post by its ID.
*
@ -234,7 +228,7 @@ class Posts extends Module {
// Full sync.
$sync_module = Modules::get_module( 'full-sync' );
if ( $sync_module instanceof Full_Sync_Immediately ) {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'add_term_relationships' ) );
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'build_full_sync_action_array' ) );
} else {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_posts_with_metadata_and_terms' ) );
}
@ -307,7 +301,7 @@ class Posts extends Module {
}
/**
* Filter meta arguments so that we don't sync meta_values over MAX_POST_META_LENGTH.
* Filter meta arguments so that we don't sync meta_values over MAX_META_LENGTH.
*
* @param array $args action arguments.
*
@ -318,7 +312,7 @@ class Posts extends Module {
// Explicitly truncate meta_value when it exceeds limit.
// Large content will cause OOM issues and break Sync.
$serialized_value = maybe_serialize( $meta_value );
if ( strlen( $serialized_value ) >= self::MAX_POST_META_LENGTH ) {
if ( strlen( $serialized_value ) >= self::MAX_META_LENGTH ) {
$meta_value = '';
}
return array( $meta_id, $object_id, $meta_key, $meta_value );
@ -785,6 +779,25 @@ class Posts extends Module {
}
}
/**
* Build the full sync action object for Posts.
*
* @access public
*
* @param array $args An array with the posts and the previous end.
*
* @return array An array with the posts, postmeta and the previous end.
*/
public function build_full_sync_action_array( $args ) {
list( $filtered_posts, $previous_end ) = $args;
return array(
$filtered_posts['objects'],
$filtered_posts['meta'],
array(), // WPCOM does not process term relationships in full sync posts actions for a while now, let's skip them.
$previous_end,
);
}
/**
* Add term relationships to post objects within a hook before they are serialized and sent to the server.
* This is used in Full Sync Immediately
@ -793,15 +806,16 @@ class Posts extends Module {
*
* @param array $args The hook parameters.
* @return array $args The expanded hook parameters.
* @deprecated since 4.7.0
*/
public function add_term_relationships( $args ) {
list( $filtered_posts, $previous_interval_end ) = $args;
list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $filtered_posts;
_deprecated_function( __METHOD__, '4.7.0' );
list( $filtered_posts, $previous_interval_end ) = $args;
return array(
$filtered_posts,
$filtered_posts_metadata,
$this->get_term_relationships( $filtered_post_ids ),
$filtered_posts['objects'],
$filtered_posts['meta'],
$this->get_term_relationships( $filtered_posts['object_ids'] ),
$previous_interval_end,
);
}
@ -862,15 +876,33 @@ class Posts extends Module {
return array();
}
$posts = $this->expand_posts( $post_ids );
$posts_metadata = $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
$posts = $this->expand_posts( $post_ids );
// If no posts were fetched, make sure to return the expected structure so that status is updated correctly.
if ( empty( $posts ) ) {
return array(
'object_ids' => $post_ids,
'objects' => array(),
'meta' => array(),
);
}
// Get the post IDs from the posts that were fetched.
$fetched_post_ids = wp_list_pluck( $posts, 'ID' );
$metadata = $this->get_metadata( $fetched_post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
// Filter the posts and metadata based on the maximum size constraints.
list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $this->filter_objects_and_metadata_by_size(
'post',
$posts,
$metadata,
self::MAX_META_LENGTH,
self::MAX_SIZE_FULL_SYNC
);
// Filter posts and metadata based on maximum size constraints.
list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $this->filter_posts_and_metadata_max_size( $posts, $posts_metadata );
return array(
$filtered_post_ids,
$filtered_posts,
$filtered_posts_metadata,
'object_ids' => $filtered_post_ids,
'objects' => $filtered_posts,
'meta' => $filtered_posts_metadata,
);
}
@ -887,71 +919,4 @@ class Posts extends Module {
$posts = array_values( $posts ); // Reindex in case posts were deleted.
return $posts;
}
/**
* Filters posts and metadata based on maximum size constraints.
* It always allows the first post with its metadata even if they exceed the limit, otherwise they will never be synced.
*
* @access public
*
* @param array $posts The array of posts to filter.
* @param array $metadata The array of metadata to filter.
* @return array An array containing the filtered post IDs, filtered posts, and filtered metadata.
*/
public function filter_posts_and_metadata_max_size( $posts, $metadata ) {
$filtered_posts = array();
$filtered_metadata = array();
$filtered_post_ids = array();
$current_size = 0;
foreach ( $posts as $post ) {
$post_content_size = isset( $post->post_content ) ? strlen( $post->post_content ) : 0;
$current_metadata = array();
$metadata_size = 0;
foreach ( $metadata as $key => $metadata_item ) {
if ( (int) $metadata_item->post_id === $post->ID ) {
// Trimming metadata if it exceeds limit. Similar to trim_post_meta.
$metadata_item_size = strlen( maybe_serialize( $metadata_item->meta_value ) );
if ( $metadata_item_size >= self::MAX_POST_META_LENGTH ) {
$metadata_item->meta_value = '';
}
$current_metadata[] = $metadata_item;
$metadata_size += $metadata_item_size >= self::MAX_POST_META_LENGTH ? 0 : $metadata_item_size;
if ( ! empty( $filtered_post_ids ) && ( $current_size + $post_content_size + $metadata_size ) > ( self::MAX_SIZE_FULL_SYNC ) ) {
break 2; // Break both foreach loops.
}
unset( $metadata[ $key ] );
}
}
// Always allow the first post with its metadata.
if ( empty( $filtered_post_ids ) || ( $current_size + $post_content_size + $metadata_size ) <= ( self::MAX_SIZE_FULL_SYNC ) ) {
$filtered_post_ids[] = strval( $post->ID );
$filtered_posts[] = $post;
$filtered_metadata = array_merge( $filtered_metadata, $current_metadata );
$current_size += $post_content_size + $metadata_size;
} else {
break;
}
}
return array(
$filtered_post_ids,
$filtered_posts,
$filtered_metadata,
);
}
/**
* Set the status of the full sync action based on the objects that were sent.
*
* @access public
*
* @param array $status This module Full Sync status.
* @param array $objects This module Full Sync objects.
*
* @return array The updated status.
*/
public function set_send_full_sync_actions_status( $status, $objects ) {
$status['last_sent'] = end( $objects[0] );
$status['sent'] += count( $objects[0] );
return $status;
}
}

View File

@ -1729,6 +1729,10 @@ class Search extends Module {
// wp.com martketplace search - @see https://wp.me/pdh6GB-Ax#comment-2104
'wpcom_marketplace_categories',
// wp.com a8c-support-theme taxonomies.
'kb_category',
'kb_tag',
); // end taxonomies.
/**

View File

@ -56,16 +56,30 @@ class Term_Relationships extends Module {
}
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Term_Relationships->table' );
return 'term_relationships';
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
global $wpdb;
return $wpdb->term_relationships;
}
/**
* Initialize term relationships action listeners for full sync.
*
@ -164,11 +178,14 @@ class Term_Relationships extends Module {
return $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",
"SELECT tr.object_id, tr.term_taxonomy_id
FROM $wpdb->term_relationships tr INNER JOIN $wpdb->term_taxonomy tt
ON tr.term_taxonomy_id=tt.term_taxonomy_id
WHERE " .
Settings::get_whitelisted_taxonomies_sql() // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
. ' AND ( ( tr.object_id = %d AND tr.term_taxonomy_id < %d ) OR ( tr.object_id < %d ) )
ORDER BY tr.object_id DESC, tr.term_taxonomy_id
DESC LIMIT %d',
$status['last_sent']['object_id'],
$status['last_sent']['term_taxonomy_id'],
$status['last_sent']['object_id'],

View File

@ -7,7 +7,6 @@
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Defaults;
use Automattic\Jetpack\Sync\Settings;
/**
@ -38,55 +37,104 @@ class Terms extends Module {
}
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Terms->table' );
return 'term_taxonomy';
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
global $wpdb;
return $wpdb->term_taxonomy;
}
/**
* Allows WordPress.com servers to retrieve term-related objects via the sync API.
*
* @param string $object_type The type of object.
* @param string $object_type The type of object. Accepts: 'term', 'term_taxonomy', 'term_relationships'.
* @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.
* @return false|object A term or term_taxonomy object, depending on object type.
*/
public function get_object_by_id( $object_type, $id ) {
$id = (int) $id;
if ( empty( $id ) ) {
return false;
}
$objects = $this->get_objects_by_id( $object_type, array( $id ) );
return $objects[ $id ] ?? false;
}
/**
* Retrieve a set of objects by their IDs.
*
* @access public
*
* @param string $object_type Object type. Accepts: 'term', 'term_taxonomy', 'term_relationships'.
* @param array $ids Object IDs.
*
* @return array Array of objects.
*/
public function get_objects_by_id( $object_type, $ids ) {
global $wpdb;
$object = false;
if ( 'term' === $object_type ) {
$object = get_term( (int) $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 ) );
}
$objects = array();
if ( ! is_array( $ids ) || empty( $ids ) || empty( $object_type ) ) {
return $objects;
}
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 ) );
// Sanitize.
$ids = array_map( 'intval', $ids );
$ids_str = implode( ',', $ids );
$where_sql = Settings::get_whitelisted_taxonomies_sql();
switch ( $object_type ) {
case 'term':
$query = "SELECT * FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON t.term_id=tt.term_id WHERE t.term_id IN ( $ids_str ) AND ";
$callback = 'expand_raw_terms';
break;
case 'term_taxonomy':
$query = "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ( $ids_str ) AND ";
$callback = 'expand_raw_term_taxonomies';
break;
case 'term_relationships':
$query = "SELECT * FROM $wpdb->term_relationships tr INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id=tt.term_taxonomy_id WHERE object_id IN ( $ids_str ) AND ";
$callback = 'expand_raw_term_relationships';
break;
default:
return array();
}
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 ),
);
$query .= $where_sql;
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Already sanitized above.
$results = $wpdb->get_results( $query );
if ( ! is_array( $results ) ) {
return array();
}
return $object ? $object : false;
$objects = $this->$callback( $results );
return $objects ?? array();
}
/**
@ -259,12 +307,16 @@ class Terms extends Module {
}
/**
* Filter out set_object_terms actions where the terms have not changed.
* Filter out set_object_terms actions with blacklisted taxonomies or where the terms have not changed.
*
* @param array $args Hook args.
* @return array|boolean False if no change in terms, the original hook args otherwise.
* @return array|boolean False if blacklisted taxonomy or no change in terms, the original hook args otherwise.
*/
public function filter_set_object_terms_no_update( $args ) {
// Check if the taxonomy is blacklisted. $args[3] is the taxonomy.
if ( isset( $args[3] ) && in_array( $args[3], Settings::get_setting( 'taxonomies_blacklist' ), true ) ) {
return false;
}
// There is potential for other plugins to modify args, therefore lets validate # of and types.
// $args[2] is $tt_ids, $args[5] is $old_tt_ids see wp-includes/taxonomy.php L2740.
if ( 6 === count( $args ) && is_array( $args[2] ) && is_array( $args[5] ) ) {
@ -284,7 +336,7 @@ class Terms extends Module {
* @return array $args The expanded hook parameters.
*/
public function expand_term_taxonomy_id( $args ) {
list( $term_taxonomy_ids, $previous_end ) = $args;
list( $term_taxonomy_ids, $previous_end ) = $args;
return array(
'terms' => get_terms(
@ -304,10 +356,127 @@ class Terms extends Module {
*
* @access public
*
* @deprecated since 4.8.1
*
* @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 ) {
_deprecated_function( __METHOD__, '4.8.1' );
return get_term_by( 'term_taxonomy_id', $relationship->term_taxonomy_id );
}
/**
* Prepare raw terms and return them in a standard format.
*
* @param array $terms An array of raw term objects.
* @return array An array of term objects.
*/
private function expand_raw_terms( array $terms ) {
$objects = array();
$columns = array(
'term_id' => 'int',
'name' => 'string',
'slug' => 'string',
'taxonomy' => 'string',
'description' => 'string',
'term_group' => 'int',
'term_taxonomy_id' => 'int',
'parent' => 'int',
'count' => 'int',
);
foreach ( $terms as $term ) {
if ( ! array_key_exists( $term->term_id, $objects ) ) {
$t_array = array();
foreach ( $columns as $field => $type ) {
$value = $term->$field ?? '';
$t_array[ $field ] = 'int' === $type ? (int) $value : $value;
}
// This will allow us to know on WPCOM that the term name is the raw one, coming from the DB. Useful with backwards compatibility in mind.
$t_array['raw_name'] = $t_array['name'];
$objects[ $term->term_id ] = (object) $t_array;
}
}
return $objects;
}
/**
* Prepare raw term taxonomies and return them in a standard format.
*
* @param array $term_taxonomies An array of raw term_taxonomy objects.
* @return array An array of term_taxonomy objects.
*/
private function expand_raw_term_taxonomies( array $term_taxonomies ) {
$objects = array();
$columns = array(
'term_id' => 'int',
'taxonomy' => 'string',
'description' => 'string',
'term_taxonomy_id' => 'int',
'parent' => 'int',
'count' => 'int',
);
foreach ( $term_taxonomies as $tt ) {
if ( ! array_key_exists( $tt->term_taxonomy_id, $objects ) ) {
$t_array = array();
foreach ( $columns as $field => $type ) {
$value = $tt->$field ?? '';
$t_array[ $field ] = 'int' === $type ? (int) $value : $value;
}
$objects[ $tt->term_taxonomy_id ] = (object) $t_array;
}
}
return $objects;
}
/**
* Prepare raw term taxonomies and return them in a standard format.
*
* @param array $term_relationships An array of raw term_taxonomy objects.
* @return array An array of term_taxonomy objects or false.
*/
private function expand_raw_term_relationships( array $term_relationships ) {
$objects = array();
$columns = array(
'object_id' => 'int',
'term_id' => 'int',
'taxonomy' => 'string',
'term_taxonomy_id' => 'int',
'term_order' => 'int',
'parent' => 'int',
'count' => 'int',
);
foreach ( $term_relationships as $tt ) {
$object_id = (int) $tt->object_id;
$relationship = array();
foreach ( $columns as $field => $type ) {
$value = $tt->$field ?? '';
$relationship[ $field ] = 'int' === $type ? (int) $value : $value;
}
$relationship = (object) $relationship;
if ( ! array_key_exists( $object_id, $objects ) ) {
$objects[ $object_id ] = (object) array(
'object_id' => $object_id,
'relationships' => array( $relationship ),
);
} else {
$objects[ $object_id ]->relationships[] = $relationship;
}
}
return $objects;
}
}

View File

@ -103,8 +103,6 @@ class Updates extends Module {
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 );

View File

@ -58,16 +58,30 @@ class Users extends Module {
}
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\Users->table' );
return 'usermeta';
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
global $wpdb;
return $wpdb->usermeta;
}
/**
* The id field in the database.
*

View File

@ -45,8 +45,21 @@ class WooCommerce_HPOS_Orders extends Module {
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\WooCommerce_HPOS_Orders->table' );
return $this->order_table_name;
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
return $this->order_table_name;
}
@ -72,6 +85,11 @@ class WooCommerce_HPOS_Orders extends Module {
public static function get_order_types_to_sync( $prefixed = false ) {
$types = array( 'order', 'order_refund' );
// Ensure this is available.
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( is_plugin_active( self::WOOCOMMERCE_SUBSCRIPTIONS_PATH ) ) {
$types[] = 'subscription';
}
@ -100,9 +118,13 @@ class WooCommerce_HPOS_Orders extends Module {
add_filter( "jetpack_sync_before_enqueue_woocommerce_after_{$type}_object_save", array( $this, 'expand_order_object' ) );
}
add_action( 'woocommerce_delete_order', $callable );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_delete_order', array( $this, 'expand_order_object' ) );
add_action( 'woocommerce_delete_subscription', $callable );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_delete_order', array( $this, 'on_before_enqueue_order_trash_delete' ) );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_delete_subscription', array( $this, 'on_before_enqueue_order_trash_delete' ) );
add_action( 'woocommerce_trash_order', $callable );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_trash_order', array( $this, 'expand_order_object' ) );
add_action( 'woocommerce_trash_subscription', $callable );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_trash_order', array( $this, 'on_before_enqueue_order_trash_delete' ) );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_trash_subscription', array( $this, 'on_before_enqueue_order_trash_delete' ) );
}
/**
@ -114,7 +136,16 @@ class WooCommerce_HPOS_Orders extends Module {
*/
public function init_full_sync_listeners( $callable ) {
add_action( 'jetpack_full_sync_orders', $callable );
add_filter( 'jetpack_sync_before_enqueue_full_sync_orders', array( $this, 'expand_order_objects' ) );
}
/**
* 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_hpos_orders', array( $this, 'build_full_sync_action_array' ) );
}
/**
@ -197,11 +228,32 @@ class WooCommerce_HPOS_Orders extends Module {
* @param array $args List of order IDs.
*
* @return array
* @deprecated since 4.7.0
*/
public function expand_order_objects( $args ) {
$order_ids = $args;
_deprecated_function( __METHOD__, '4.7.0' );
list( $order_ids, $previous_end ) = $args;
return array(
'orders' => $this->get_objects_by_id( 'order', $order_ids ),
'previous_end' => $previous_end,
);
}
return $this->get_objects_by_id( 'order', $order_ids );
/**
* Build the full sync action object.
*
* @access public
*
* @param array $args An array with filtered objects and previous end.
*
* @return array An array with orders and previous end.
*/
public function build_full_sync_action_array( $args ) {
list( $filtered_orders, $previous_end ) = $args;
return array(
'orders' => $filtered_orders['objects'],
'previous_end' => $previous_end,
);
}
/**
@ -230,6 +282,28 @@ class WooCommerce_HPOS_Orders extends Module {
return $this->filter_order_data( $order_object );
}
/**
* Convert order ID to array.
*
* @access public
*
* @param array $args Order ID.
*
* @return array
*/
public function on_before_enqueue_order_trash_delete( $args ) {
if ( ! is_array( $args ) || ! isset( $args[0] ) ) {
return false;
}
$order_id = $args[0];
if ( ! is_int( $order_id ) ) {
return false;
}
return array( 'id' => $order_id );
}
/**
* Filters only allowed keys from order data. No PII etc information is allowed to be synced.
*
@ -374,6 +448,12 @@ class WooCommerce_HPOS_Orders extends Module {
);
}
if ( function_exists( 'wcs_get_subscription_statuses' ) ) {
// @phan-suppress-next-line PhanUndeclaredFunction -- Checked above. See also https://github.com/phan/phan/issues/1204.
$wc_subscription_statuses = array_keys( wcs_get_subscription_statuses() );
$wc_order_statuses = array_merge( $wc_order_statuses, $wc_subscription_statuses );
}
return array_unique( $wc_order_statuses );
}
@ -403,7 +483,7 @@ class WooCommerce_HPOS_Orders extends Module {
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- We return all order count for full sync, so confit is not required.
global $wpdb;
$query = "SELECT count(*) FROM {$this->table_name()} WHERE {$this->get_where_sql( $config ) }";
$query = "SELECT count(*) FROM {$this->table()} WHERE {$this->get_where_sql( $config ) }";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Hardcoded query, no user variable
$count = (int) $wpdb->get_var( $query );
@ -421,7 +501,7 @@ class WooCommerce_HPOS_Orders extends Module {
* @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( 'full_sync_orders', $this->table_name(), 'id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
return $this->enqueue_all_ids_as_action( 'full_sync_orders', $this->table(), 'id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
@ -442,4 +522,47 @@ class WooCommerce_HPOS_Orders extends Module {
$where_sql = $wpdb->prepare( "type IN ( $order_type_placeholder )", $order_types );
return "{$parent_where} AND {$where_sql}";
}
/**
* Given the Module Configuration and Status return the next chunk of items to send.
* This function also expands the posts and metadata and filters them based on the maximum size constraints.
*
* @param array $config This module Full Sync configuration.
* @param array $status This module Full Sync status.
* @param int $chunk_size Chunk size.
*
* @return array
*/
public function get_next_chunk( $config, $status, $chunk_size ) {
$order_ids = parent::get_next_chunk( $config, $status, $chunk_size );
if ( empty( $order_ids ) ) {
return array();
}
$orders = $this->get_objects_by_id( 'order', $order_ids );
// If no orders were fetched, make sure to return the expected structure so that status is updated correctly.
if ( empty( $orders ) ) {
return array(
'object_ids' => $order_ids,
'objects' => array(),
);
}
// Filter the orders based on the maximum size constraints. We don't need to filter metadata here since we don't sync it for hpos.
list( $filtered_order_ids, $filtered_orders, ) = $this->filter_objects_and_metadata_by_size(
'order',
$orders,
array(),
0,
self::MAX_SIZE_FULL_SYNC
);
return array(
'object_ids' => $filtered_order_ids,
'objects' => $filtered_orders,
);
}
}

View File

@ -61,14 +61,50 @@ class WooCommerce extends Module {
private $order_item_table_name;
/**
* The table in the database.
* The table name.
*
* @access public
*
* @return string
* @deprecated since 3.11.0 Use table() instead.
*/
public function table_name() {
_deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\WooCommerce->table' );
return $this->order_item_table_name;
}
/**
* The table in the database with the prefix.
*
* @access public
*
* @return string|bool
*/
public function table() {
global $wpdb;
return $wpdb->prefix . 'woocommerce_order_items';
}
/**
* The id field in the database.
*
* @access public
*
* @return string
*/
public function table_name() {
return $this->order_item_table_name;
public function id_field() {
return 'order_item_id';
}
/**
* The full sync action name for this module.
*
* @access public
*
* @return string
*/
public function full_sync_action_name() {
return 'jetpack_full_sync_woocommerce_order_items';
}
/**
@ -185,7 +221,7 @@ class WooCommerce extends Module {
*/
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' ) );
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_woocommerce_order_items', array( $this, 'build_full_sync_action_array' ) );
}
/**
@ -230,14 +266,17 @@ class WooCommerce extends Module {
*
* @param array $args The hook arguments.
* @return array $args Expanded order items with meta.
* @deprecated since 4.7.0
*/
public function expand_order_item_ids( $args ) {
_deprecated_function( __METHOD__, '4.7.0' );
$order_item_ids = $args[0];
global $wpdb;
$order_item_ids_sql = implode( ', ', array_map( 'intval', $order_item_ids ) );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$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 )"
@ -248,7 +287,6 @@ class WooCommerce extends Module {
$this->get_metadata( $order_item_ids, 'order_item', static::$order_item_meta_whitelist ),
);
}
/**
* Extract the full order item from the database by its ID.
*
@ -608,13 +646,14 @@ class WooCommerce extends Module {
/**
* Returns a list of order_item objects by their IDs.
*
* @param array $ids List of order_item IDs to fetch.
* @param array $ids List of order_item IDs to fetch.
* @param string $order Either 'ASC' or 'DESC'.
*
* @access public
*
* @return array|object|null
*/
public function get_order_item_by_ids( $ids ) {
public function get_order_item_by_ids( $ids, $order = '' ) {
global $wpdb;
if ( ! is_array( $ids ) ) {
@ -632,8 +671,77 @@ class WooCommerce extends Module {
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
$query = "SELECT * FROM {$this->order_item_table_name} WHERE order_item_id IN ( $placeholders )";
if ( ! empty( $order ) && in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
$query .= " ORDER BY order_item_id $order";
}
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_results( $wpdb->prepare( $query, $ids ), ARRAY_A );
}
/**
* Build the full sync action object for WooCommerce order items.
*
* @access public
*
* @param array $args An array with the order items and the previous end.
*
* @return array An array with the order items, order item meta and the previous end.
*/
public function build_full_sync_action_array( $args ) {
list( $filtered_order_items, $previous_end ) = $args;
return array(
'order_items' => $filtered_order_items['objects'],
'order_item_meta' => $filtered_order_items['meta'],
'previous_end' => $previous_end,
);
}
/**
* Given the Module Configuration and Status return the next chunk of items to send.
* This function also expands the posts and metadata and filters them based on the maximum size constraints.
*
* @param array $config This module Full Sync configuration.
* @param array $status This module Full Sync status.
* @param int $chunk_size Chunk size.
*
* @return array
*/
public function get_next_chunk( $config, $status, $chunk_size ) {
$order_item_ids = parent::get_next_chunk( $config, $status, $chunk_size );
if ( empty( $order_item_ids ) ) {
return array();
}
// Fetch the order items in DESC order for the next chunk logic to work.
$order_items = $this->get_order_item_by_ids( $order_item_ids, 'DESC' );
// If no orders were fetched, make sure to return the expected structure so that status is updated correctly.
if ( empty( $order_items ) ) {
return array(
'object_ids' => $order_item_ids,
'objects' => array(),
);
}
// Get the order IDs from the orders that were fetched.
$fetched_order_item_ids = wp_list_pluck( $order_items, 'order_item_id' );
$metadata = $this->get_metadata( $fetched_order_item_ids, 'order_item', static::$order_item_meta_whitelist );
// Filter the orders and metadata based on the maximum size constraints.
list( $filtered_order_item_ids, $filtered_order_items, $filtered_order_items_metadata ) = $this->filter_objects_and_metadata_by_size(
'order_item',
$order_items,
$metadata,
self::MAX_META_LENGTH,
self::MAX_SIZE_FULL_SYNC
);
return array(
'object_ids' => $filtered_order_item_ids,
'objects' => $filtered_order_items,
'meta' => $filtered_order_items_metadata,
);
}
}

View File

@ -148,7 +148,7 @@ class Table_Checksum {
$this->salt = $salt;
$this->default_tables = $this->get_default_tables();
$this->default_tables = static::get_default_tables();
$this->perform_text_conversion = $perform_text_conversion;
@ -181,7 +181,7 @@ class Table_Checksum {
*
* @return array
*/
protected function get_default_tables() {
protected static function get_default_tables() {
global $wpdb;
return array(
@ -295,7 +295,7 @@ class Table_Checksum {
'key_fields' => array( 'order_item_id' ),
'checksum_fields' => array( 'order_id' ),
'checksum_text_fields' => array( 'order_item_name', 'order_item_type' ),
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
'is_table_enabled_callback' => 'Automattic\Jetpack\Sync\Replicastore\Table_Checksum::enable_woocommerce_tables',
),
'woocommerce_order_itemmeta' => array(
'table' => "{$wpdb->prefix}woocommerce_order_itemmeta",
@ -306,7 +306,7 @@ class Table_Checksum {
'parent_table' => 'woocommerce_order_items',
'parent_join_field' => 'order_item_id',
'table_join_field' => 'order_item_id',
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
'is_table_enabled_callback' => 'Automattic\Jetpack\Sync\Replicastore\Table_Checksum::enable_woocommerce_tables',
),
'wc_orders' => array(
'table' => "{$wpdb->prefix}wc_orders",
@ -381,6 +381,15 @@ class Table_Checksum {
);
}
/**
* Get allowed table configurations.
*
* @return array
*/
public static function get_allowed_tables() {
return apply_filters( 'jetpack_sync_checksum_allowed_tables', static::get_default_tables() );
}
/**
* Prepare field params based off provided configuration.
*
@ -872,7 +881,7 @@ class Table_Checksum {
*
* @return bool
*/
protected function enable_woocommerce_tables() {
public static function enable_woocommerce_tables() {
/**
* On WordPress.com, we can't directly check if the site has support for WooCommerce.
* Having the option to override the functionality here helps with syncing WooCommerce tables.
@ -889,14 +898,8 @@ class Table_Checksum {
return true;
}
// No need to proceed if WooCommerce is not available.
if ( ! class_exists( 'WooCommerce' ) ) {
return false;
}
// TODO more checks if needed. Probably query the DB to make sure the tables exist.
return true;
// If the 'woocommerce' module is enabled, this means that WooCommerce class exists.
return false !== Sync\Modules::get_module( 'woocommerce' );
}
/**