updated plugin Jetpack Protect version 2.0.0

This commit is contained in:
2024-02-08 12:31:43 +00:00
committed by Gitium
parent ce653dd56c
commit 8d5e7cc070
192 changed files with 5244 additions and 2003 deletions

View File

@ -5,6 +5,90 @@ 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).
## [2.4.2] - 2024-01-18
### Changed
- Update dependencies.
## [2.4.1] - 2024-01-15
### Added
- Options: synchronize block status option. [#34989]
### Changed
- Sync: Dedicated sync now disabled for high queue lags only if test request fails. [#34888]
### Fixed
- Added `is_array` check to `get_items_to_send` to make sure no fatals are thrown on non-array values. [#31552]
- Jetpack Sync: Fixed buffer sanitization in Sync close endpoint [#34961]
- Jetpack Sync: Fix restoring post global before enqueuing a post action. [#34990]
## [2.4.0] - 2024-01-04
### Removed
- Social: Removed sync option for tweetstorm. [#34330]
## [2.3.0] - 2023-12-20
### Added
- Add wpcom_ai_site_prompt option to the site settings endpoint. [#34709]
### Fixed
- Added preemptive check to break expanding metadata for posts loop in Full Sync. [#34661]
## [2.2.1] - 2023-12-13
### Changed
- Refactored loop to improve efficiency and code readability [#34565]
## [2.2.0] - 2023-12-11
### Added
- Social: Add auto-conversion option to sync to WPCOM. [#34113]
### Fixed
- Fixed a missing sanity check in Sync Posts handler logic that created failed builds. [#34548]
- Sync: Update Full Sync to limit max amount of data sent in one request. [#34390]
## [2.1.2] - 2023-12-06
### Changed
- Update dependencies.
## [2.1.1] - 2023-12-03
### Changed
- Internal updates.
## [2.1.0] - 2023-11-24
### Added
- Added jetpack_verbum_subscription_modal setting to manage subscription modal show/hide on Verbum. [#34258]
### Fixed
- Silenced the call to `gzinflate` to avoid a few PHP warnings. [#34186]
## [2.0.2] - 2023-11-21
### Changed
- Replaced usage of strpos() with str_contains(). [#34137]
- Replaced usage of substr() with str_starts_with() and str_ends_with(). [#34207]
## [2.0.1] - 2023-11-21
## [2.0.0] - 2023-11-20
### Changed
- Replaced usage of strpos() with str_starts_with(). [#34135]
- Updated required PHP version to >= 7.0. [#34192]
## [1.60.1] - 2023-10-31
## [1.60.0] - 2023-10-26
### Removed
- Remove Jetpack option jetpack-memberships-connected-account-id. [#32354]
## [1.59.2] - 2023-10-24
### Changed
- Update sync version.
## [1.59.1] - 2023-10-24
### Added
- Sync: Add missing support for supplying additional columns to do checksum on. [#33440]
## [1.59.0] - 2023-10-23
### Changed
- Dedicated Sync: Update 'init' hook priority on Dedicated Sync requests to 0, in order to start sending Sync actions to WPCOM and exit as early as possible. [#33594]
## [1.58.1] - 2023-10-18
### Fixed
- Update dependencies.
@ -946,6 +1030,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Move sync to a classmapped package
[2.4.2]: https://github.com/Automattic/jetpack-sync/compare/v2.4.1...v2.4.2
[2.4.1]: https://github.com/Automattic/jetpack-sync/compare/v2.4.0...v2.4.1
[2.4.0]: https://github.com/Automattic/jetpack-sync/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/Automattic/jetpack-sync/compare/v2.2.1...v2.3.0
[2.2.1]: https://github.com/Automattic/jetpack-sync/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/Automattic/jetpack-sync/compare/v2.1.2...v2.2.0
[2.1.2]: https://github.com/Automattic/jetpack-sync/compare/v2.1.1...v2.1.2
[2.1.1]: https://github.com/Automattic/jetpack-sync/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/Automattic/jetpack-sync/compare/v2.0.2...v2.1.0
[2.0.2]: https://github.com/Automattic/jetpack-sync/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/Automattic/jetpack-sync/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Automattic/jetpack-sync/compare/v1.60.1...v2.0.0
[1.60.1]: https://github.com/Automattic/jetpack-sync/compare/v1.60.0...v1.60.1
[1.60.0]: https://github.com/Automattic/jetpack-sync/compare/v1.59.2...v1.60.0
[1.59.2]: https://github.com/Automattic/jetpack-sync/compare/v1.59.1...v1.59.2
[1.59.1]: https://github.com/Automattic/jetpack-sync/compare/v1.59.0...v1.59.1
[1.59.0]: https://github.com/Automattic/jetpack-sync/compare/v1.58.1...v1.59.0
[1.58.1]: https://github.com/Automattic/jetpack-sync/compare/v1.58.0...v1.58.1
[1.58.0]: https://github.com/Automattic/jetpack-sync/compare/v1.57.4...v1.58.0
[1.57.4]: https://github.com/Automattic/jetpack-sync/compare/v1.57.3...v1.57.4

View File

@ -4,16 +4,17 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.58.1",
"automattic/jetpack-constants": "^1.6.23",
"automattic/jetpack-identity-crisis": "^0.11.0",
"automattic/jetpack-password-checker": "^0.2.14",
"automattic/jetpack-ip": "^0.1.6",
"automattic/jetpack-roles": "^1.4.25",
"automattic/jetpack-status": "^1.18.5"
"php": ">=7.0",
"automattic/jetpack-connection": "^2.2.0",
"automattic/jetpack-constants": "^2.0.0",
"automattic/jetpack-identity-crisis": "^0.15.0",
"automattic/jetpack-password-checker": "^0.3.0",
"automattic/jetpack-ip": "^0.2.1",
"automattic/jetpack-roles": "^2.0.0",
"automattic/jetpack-status": "^2.0.2"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.3.11",
"automattic/jetpack-changelogger": "^4.0.5",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/wordbless": "@dev"
},
@ -48,7 +49,7 @@
"link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.58.x-dev"
"dev-trunk": "2.4.x-dev"
}
},
"config": {

View File

@ -110,7 +110,7 @@ class Actions {
// rely on 'jetpack_sync_before_send_queue_sync' are picked up and added to the queue if needed.
if ( Settings::is_dedicated_sync_enabled() && Dedicated_Sender::is_dedicated_sync_request() ) {
self::initialize_listener();
add_action( 'init', array( __CLASS__, 'add_dedicated_sync_sender_init' ), 90 );
add_action( 'init', array( __CLASS__, 'add_dedicated_sync_sender_init' ), 200 );
return;
}
@ -1059,7 +1059,7 @@ class Actions {
);
// Verify $sync_module is not false.
if ( ( $sync_module ) && false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
if ( ( $sync_module ) && ! str_contains( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
$result['full_queue_size'] = $full_queue->size();
$result['full_queue_lag'] = $full_queue->lag();
}

View File

@ -151,35 +151,6 @@ class Dedicated_Sender {
$queue_lag = $queue->lag();
// Only check if we're failing to send events if the queue lag is longer than the threshold.
if ( $queue_lag > $queue_send_time_threshold ) {
/**
* Check if Dedicated Sync is healthy and revert to Default Sync if such case is detected.
*/
$last_successful_queue_send_time = get_option( Actions::LAST_SUCCESS_PREFIX . $queue->id, null );
if ( $last_successful_queue_send_time === null ) {
/**
* No successful sync sending completed. This might be either a "new" sync site or a site that's totally stuck.
*/
self::on_dedicated_sync_lag_not_sending_threshold_reached();
return new WP_Error( 'dedicated_sync_not_sending', 'Dedicated Sync is not successfully sending events' );
} else {
/**
* We have recorded a successful sending of events. Let's see if that is not too long ago in the past.
*/
$time_since_last_succesful_send = time() - $last_successful_queue_send_time;
if ( $time_since_last_succesful_send > $queue_send_time_threshold ) {
// We haven't successfully sent stuff in more than 30 minutes. Revert to Default Sync
self::on_dedicated_sync_lag_not_sending_threshold_reached();
return new WP_Error( 'dedicated_sync_not_sending', 'Dedicated Sync is not successfully sending events' );
}
}
}
/**
* Try to acquire a request lock, so we don't spawn multiple requests at the same time.
* This should prevent cases where sites might have limits on the amount of simultaneous requests.
@ -189,6 +160,21 @@ class Dedicated_Sender {
return new WP_Error( 'dedicated_request_lock', 'Unable to acquire request lock' );
}
/**
* If the queue lag is bigger than the threshold, we want to check if Dedicated Sync is working correctly.
* We will do by sending a test request and disabling Dedicated Sync if it's not working. We will also exit early
* in case we send the test request since it is a blocking request.
*/
if ( $queue_lag > $queue_send_time_threshold ) {
if ( false === get_transient( self::DEDICATED_SYNC_CHECK_TRANSIENT ) ) {
if ( ! self::can_spawn_dedicated_sync_request() ) {
self::on_dedicated_sync_lag_not_sending_threshold_reached();
return new WP_Error( 'dedicated_sync_not_sending', 'Dedicated Sync is not successfully sending events' );
}
return true;
}
}
$url = rest_url( 'jetpack/v4/sync/spawn-sync' );
$url = add_query_arg( 'time', time(), $url ); // Enforce Cache busting.
$url = add_query_arg( self::DEDICATED_SYNC_REQUEST_LOCK_QUERY_PARAM_NAME, $request_lock, $url );
@ -361,7 +347,6 @@ class Dedicated_Sender {
$sender->send_action( 'jetpack_sync_flow_error_enable', $data );
}
}
return self::DEDICATED_SYNC_VALIDATION_STRING === $dedicated_sync_response_body;
}

View File

@ -63,7 +63,6 @@ class Defaults {
'image_default_link_type',
'infinite_scroll',
'infinite_scroll_google_analytics',
'jetpack-memberships-connected-account-id',
'jetpack-memberships-has-connected-account',
'jetpack-twitter-cards-site-tag',
'jetpack_activated',
@ -89,6 +88,7 @@ class Defaults {
'jetpack_publicize_options',
'jetpack_relatedposts',
'jetpack_social_settings',
'jetpack_social_autoconvert_images',
'jetpack_sso_match_by_email',
'jetpack_sso_require_two_step',
'jetpack_sync_non_blocking', // is non-blocking Jetpack Sync flow enabled.
@ -178,6 +178,7 @@ class Defaults {
'wp_mobile_excerpt',
'wp_mobile_featured_images',
'wp_page_for_privacy_policy',
'wpcom_ai_site_prompt',
'wpcom_featured_image_in_email',
'wpcom_gifting_subscription',
'wpcom_is_fse_activated',
@ -190,6 +191,8 @@ class Defaults {
'wpcom_reader_views_enabled',
'wpcom_site_setup',
'wpcom_subscription_emails_use_excerpt',
'jetpack_verbum_subscription_modal',
'jetpack_blocks_disabled',
);
/**
@ -739,7 +742,6 @@ class Defaults {
'_wp_page_template',
'_wp_trash_meta_comments_status',
'_wpas_feature_enabled',
'_wpas_is_tweetstorm',
'_wpas_mess',
'_wpas_options',
'advanced_seo_description', // Jetpack_SEO_Posts::DESCRIPTION_META_KEY.

View File

@ -41,7 +41,10 @@ class JSON_Deflate_Array_Codec implements Codec_Interface {
* @return array|mixed|object
*/
public function decode( $input ) {
return $this->json_unserialize( gzinflate( base64_decode( $input ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$decoded = base64_decode( $input ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$inflated = @gzinflate( $decoded ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
return is_string( $inflated ) ? $this->json_unserialize( $inflated ) : null;
}
/**

View File

@ -216,6 +216,7 @@ class Listener {
* If we add any items to the queue, we should try to ensure that our script
* can't be killed before they are sent.
*/
// https://plugins.trac.wordpress.org/ticket/2041
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}
@ -319,6 +320,7 @@ class Listener {
* If we add any items to the queue, we should try to ensure that our script
* can't be killed before they are sent.
*/
// https://plugins.trac.wordpress.org/ticket/2041
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}

View File

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

View File

@ -1306,27 +1306,22 @@ class Replicastore implements Replicastore_Interface {
* @param bool $perform_text_conversion If text fields should be converted to latin1 during the checksum calculation.
*
* @return array|WP_Error The checksum histogram.
* @throws Exception Throws an exception if data validation fails inside `Table_Checksum` calls.
*/
public function checksum_histogram( $table, $buckets = null, $start_id = null, $end_id = null, $columns = null, $strip_non_ascii = true, $salt = '', $only_range_edges = false, $detailed_drilldown = false, $perform_text_conversion = false ) {
global $wpdb;
$wpdb->queries = array();
try {
$checksum_table = $this->get_table_checksum_instance( $table, $salt, $perform_text_conversion );
$checksum_table = $this->get_table_checksum_instance( $table, $salt, $perform_text_conversion, $columns );
} catch ( Exception $ex ) {
return new WP_Error( 'checksum_disabled', $ex->getMessage() );
}
// Validate / Determine Buckets.
if ( $buckets === null || $buckets < 1 ) {
$buckets = $this->calculate_buckets( $table, $start_id, $end_id );
try {
$range_edges = $checksum_table->get_range_edges( $start_id, $end_id );
} catch ( Exception $ex ) {
return new WP_Error( 'invalid_range_edges', '[' . $start_id . '-' . $end_id . ']: ' . $ex->getMessage() );
}
if ( is_wp_error( $buckets ) ) {
return $buckets;
}
$range_edges = $checksum_table->get_range_edges( $start_id, $end_id );
if ( $only_range_edges ) {
return $range_edges;
@ -1338,12 +1333,21 @@ class Replicastore implements Replicastore_Interface {
return array();
}
// Validate / Determine Buckets.
if ( $buckets === null || $buckets < 1 ) {
$buckets = $this->calculate_buckets( $table, $object_count );
}
$bucket_size = (int) ceil( $object_count / $buckets );
$previous_max_id = max( 0, $range_edges['min_range'] );
$histogram = array();
do {
$ids_range = $checksum_table->get_range_edges( $previous_max_id, null, $bucket_size );
try {
$ids_range = $checksum_table->get_range_edges( $previous_max_id, null, $bucket_size );
} catch ( Exception $ex ) {
return new WP_Error( 'invalid_range_edges', '[' . $previous_max_id . '- ]: ' . $ex->getMessage() );
}
if ( empty( $ids_range['min_range'] ) || empty( $ids_range['max_range'] ) ) {
// Nothing to checksum here...
@ -1401,20 +1405,10 @@ class Replicastore implements Replicastore_Interface {
* Determine number of buckets to use in full table checksum.
*
* @param string $table Object Type.
* @param int $start_id Min Object ID.
* @param int $end_id Max Object ID.
* @return int|WP_Error Number of Buckets to use.
* @param int $object_count Object count.
* @return int Number of Buckets to use.
*/
private function calculate_buckets( $table, $start_id = null, $end_id = null ) {
// Get # of objects.
try {
$checksum_table = $this->get_table_checksum_instance( $table );
} catch ( Exception $ex ) {
return new WP_Error( 'checksum_disabled', $ex->getMessage() );
}
$range_edges = $checksum_table->get_range_edges( $start_id, $end_id );
$object_count = $range_edges['item_count'];
private function calculate_buckets( $table, $object_count ) {
// Ensure no division by 0.
if ( 0 === (int) $object_count ) {
return 1;
@ -1437,21 +1431,22 @@ class Replicastore implements Replicastore_Interface {
*
* Some tables require custom instances, due to different checksum logic.
*
* @param string $table The table that we want to get the instance for.
* @param null $salt Salt to be used when generating the checksums.
* @param false $perform_text_conversion Should we perform text encoding conversion when calculating the checksum.
* @param string $table The table that we want to get the instance for.
* @param string $salt Salt to be used when generating the checksums.
* @param bool $perform_text_conversion Should we perform text encoding conversion when calculating the checksum.
* @param array $additional_columns Additional columns to add to the checksum calculation.
*
* @return Table_Checksum|Table_Checksum_Usermeta
* @throws Exception Might throw an exception if any of the input parameters were invalid.
*/
public function get_table_checksum_instance( $table, $salt = null, $perform_text_conversion = false ) {
public function get_table_checksum_instance( $table, $salt = null, $perform_text_conversion = false, $additional_columns = null ) {
if ( 'users' === $table ) {
return new Table_Checksum_Users( $table, $salt, $perform_text_conversion );
return new Table_Checksum_Users( $table, $salt, $perform_text_conversion, $additional_columns );
}
if ( 'usermeta' === $table ) {
return new Table_Checksum_Usermeta( $table, $salt, $perform_text_conversion );
return new Table_Checksum_Usermeta( $table, $salt, $perform_text_conversion, $additional_columns );
}
return new Table_Checksum( $table, $salt, $perform_text_conversion );
return new Table_Checksum( $table, $salt, $perform_text_conversion, $additional_columns );
}
}

View File

@ -679,7 +679,7 @@ class REST_Endpoints {
}
// Limit to A-Z,a-z,0-9,_,- .
$request_body['buffer_id'] = preg_replace( '/[^A-Za-z0-9]/', '', $request_body['buffer_id'] );
$request_body['buffer_id'] = preg_replace( '/[^A-Za-z0-9\-_\.]/', '', $request_body['buffer_id'] );
$request_body['item_ids'] = array_filter( array_map( array( 'Automattic\Jetpack\Sync\REST_Endpoints', 'sanitize_item_ids' ), $request_body['item_ids'] ) );
$queue = new Queue( $queue_name );
@ -862,7 +862,7 @@ class REST_Endpoints {
*/
protected static function sanitize_item_ids( $item ) {
// lets not delete any options that don't start with jpsq_sync- .
if ( ! is_string( $item ) || substr( $item, 0, 5 ) !== 'jpsq_' ) {
if ( ! is_string( $item ) || ! str_starts_with( $item, 'jpsq_' ) ) {
return null;
}
// Limit to A-Z,a-z,0-9,_,-,. .

View File

@ -295,7 +295,7 @@ class Sender {
$this->continue_full_sync_enqueue();
// immediate full sync sends data in continue_full_sync_enqueue.
if ( false === strpos( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
if ( ! str_contains( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
return $this->do_sync_and_set_delays( $this->full_sync_queue );
} else {
$status = $sync_module->get_status();
@ -509,6 +509,11 @@ class Sender {
* This is expensive, but the only way to really know :/
*/
foreach ( $items as $key => $item ) {
if ( ! is_array( $item ) ) {
$skipped_items_ids[] = $key;
continue;
}
// Suspending cache addition help prevent overloading in memory cache of large sites.
wp_suspend_cache_addition( true );
/**
@ -574,6 +579,7 @@ class Sender {
* Now that we're sure we are about to sync, try to ignore user abort
* so we can avoid getting into a bad state.
*/
// https://plugins.trac.wordpress.org/ticket/2041
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}

View File

@ -313,7 +313,7 @@ class Settings {
* @return boolean Whether the setting is a network setting.
*/
public static function is_network_setting( $setting ) {
return strpos( $setting, 'network_' ) === 0;
return str_starts_with( $setting, 'network_' );
}
/**

View File

@ -348,7 +348,6 @@ class Callables extends Module {
public function set_plugin_action_links() {
if (
! class_exists( '\DOMDocument' ) ||
! function_exists( 'libxml_use_internal_errors' ) ||
! function_exists( 'mb_convert_encoding' )
) {
return;

View File

@ -308,6 +308,28 @@ SQL
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
/**
* Return last_item to send for Module Full Sync Configuration.
*
* @param array $config This module Full Sync configuration.
*
* @return array|object|null
*/
public function get_last_item( $config ) {
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
return $wpdb->get_var(
<<<SQL
SELECT {$this->id_field()}
FROM {$wpdb->{$this->table_name()}}
WHERE {$this->get_where_sql( $config )}
ORDER BY {$this->id_field()}
LIMIT 1
SQL
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
}
/**
* Return the initial last sent object.
*
@ -338,27 +360,51 @@ SQL
$limits = Settings::get_setting( 'full_sync_limits' )[ $this->name() ];
$chunks_sent = 0;
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
while ( $objects = $this->get_next_chunk( $config, $status, $limits['chunk_size'] ) ) {
if ( $chunks_sent++ === $limits['max_chunks'] || microtime( true ) >= $send_until ) {
$last_item = $this->get_last_item( $config );
while ( $chunks_sent < $limits['max_chunks'] && microtime( true ) < $send_until ) {
$objects = $this->get_next_chunk( $config, $status, $limits['chunk_size'] );
if ( $wpdb->last_error ) {
$status['error'] = true;
return $status;
}
if ( empty( $objects ) ) {
$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;
}
// The $ids are ordered in descending order.
$status['last_sent'] = end( $objects );
$status['sent'] += count( $objects );
// 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;
}
if ( ! $wpdb->last_error ) {
$status['finished'] = true;
}
return $status;
}
/**
* Set the status of the full sync action based on the objects that were sent.
*
* @access protected
*
* @param array $status This module Full Sync status.
* @param array $objects This module Full Sync objects.
*
* @return array The updated status.
*/
protected function set_send_full_sync_actions_status( $status, $objects ) {
$status['last_sent'] = end( $objects );
$status['sent'] += count( $objects );
return $status;
}

View File

@ -220,7 +220,7 @@ class Options extends Module {
$options = array();
$random_string = wp_generate_password();
foreach ( $this->options_whitelist as $option ) {
if ( 0 === strpos( $option, Settings::SETTINGS_OPTION_PREFIX ) ) {
if ( str_starts_with( $option, Settings::SETTINGS_OPTION_PREFIX ) ) {
$option_value = Settings::get_setting( str_replace( Settings::SETTINGS_OPTION_PREFIX, '', $option ) );
$options[ $option ] = $option_value;
} else {
@ -308,7 +308,7 @@ class Options extends Module {
}
// Filter our weird array( false ) value for theme_mods_*.
if ( 'theme_mods_' === substr( $args[0], 0, 11 ) ) {
if ( str_starts_with( $args[0], 'theme_mods_' ) ) {
$this->filter_theme_mods( $args[1] );
if ( isset( $args[2] ) ) {
$this->filter_theme_mods( $args[2] );
@ -335,7 +335,7 @@ class Options extends Module {
* @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 );
return in_array( $option, $this->options_whitelist, true ) || str_starts_with( $option, 'theme_mods_' );
}
/**
@ -461,7 +461,7 @@ class Options extends Module {
$random_string = wp_generate_password();
// Only whitelisted options can be returned.
if ( in_array( $id, $this->options_whitelist, true ) ) {
if ( 0 === strpos( $id, Settings::SETTINGS_OPTION_PREFIX ) ) {
if ( str_starts_with( $id, Settings::SETTINGS_OPTION_PREFIX ) ) {
$option_value = Settings::get_setting( str_replace( Settings::SETTINGS_OPTION_PREFIX, '', $id ) );
return $option_value;
} else {

View File

@ -9,6 +9,7 @@ namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use Automattic\Jetpack\Roles;
use Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Sync\Settings;
/**
@ -73,6 +74,16 @@ class Posts extends Module {
*/
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.
@ -220,7 +231,12 @@ class Posts extends Module {
add_filter( 'jetpack_sync_before_send_updated_post_meta', array( $this, 'trim_post_meta' ) );
add_filter( 'jetpack_sync_before_send_deleted_post_meta', array( $this, 'trim_post_meta' ) );
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_post_ids' ) );
$sync_module = Modules::get_module( 'full-sync' );
if ( $sync_module && str_contains( get_class( $sync_module ), 'Full_Sync_Immediately' ) ) {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'add_term_relationships' ) );
} else {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_posts_with_metadata_and_terms' ) );
}
}
/**
@ -374,7 +390,7 @@ class Posts extends Module {
*/
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 ) || ( 0 === strpos( $meta_key, '_wpas_skip_' ) );
return in_array( $meta_key, Settings::get_setting( 'post_meta_whitelist' ), true ) || str_starts_with( $meta_key, '_wpas_skip_' );
}
/**
@ -428,6 +444,10 @@ class Posts extends Module {
*/
public function filter_post_content_and_add_links( $post_object ) {
global $post;
// Used to restore the post global.
$current_post = $post;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = $post_object;
@ -440,6 +460,9 @@ class Posts extends Module {
$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;
// Restore global post.
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = $current_post;
return $non_existant_post;
}
@ -467,6 +490,10 @@ class Posts extends Module {
$blocked_post->post_status = 'jetpack_sync_blocked';
$blocked_post->post_type = $post->post_type;
// Restore global post.
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = $current_post;
return $blocked_post;
}
@ -546,7 +573,13 @@ class Posts extends Module {
$post->amp_permalink = amp_get_permalink( $post->ID );
}
return $post;
$filtered_post = $post;
// Restore global post.
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$post = $current_post;
return $filtered_post;
}
/**
@ -745,24 +778,46 @@ class Posts extends Module {
}
/**
* Expand post IDs to post objects within a hook before they are serialized and sent to the server.
* 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
*
* @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;
public function add_term_relationships( $args ) {
list( $filtered_posts, $previous_interval_end ) = $args;
list( $filtered_post_ids, $filtered_posts, $filtered_posts_metadata ) = $filtered_posts;
$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(
$filtered_posts,
$filtered_posts_metadata,
$this->get_term_relationships( $filtered_post_ids ),
$previous_interval_end,
);
}
/**
* Expand post IDs to post objects within a hook before they are serialized and sent to the server.
* This is used in Legacy Full Sync
*
* @access public
*
* @param array $args The hook parameters.
* @return array $args The expanded hook parameters.
*/
public function expand_posts_with_metadata_and_terms( $args ) {
list( $post_ids, $previous_interval_end ) = $args;
$posts = $this->expand_posts( $post_ids );
$posts_metadata = $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
$term_relationships = $this->get_term_relationships( $post_ids );
return array(
$posts,
$this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) ),
$this->get_term_relationships( $post_ids ),
$posts_metadata,
$term_relationships,
$previous_interval_end,
);
}
@ -780,4 +835,115 @@ class Posts extends Module {
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 ) );
}
/**
* 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 ) {
$post_ids = parent::get_next_chunk( $config, $status, $chunk_size );
if ( empty( $post_ids ) ) {
return array();
}
$posts = $this->expand_posts( $post_ids );
$posts_metadata = $this->get_metadata( $post_ids, 'post', Settings::get_setting( 'post_meta_whitelist' ) );
// 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,
);
}
/**
* Expand posts.
*
* @param array $post_ids Post IDs.
*
* @return array Expanded posts.
*/
private function expand_posts( $post_ids ) {
$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 $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

@ -178,6 +178,25 @@ class Term_Relationships extends Module {
);
}
/**
* Return last_item to send for Module Full Sync Configuration.
*
* @param array $config This module Full Sync configuration.
*
* @return array|object|null
*/
public function get_last_item( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
return $wpdb->get_results(
"SELECT object_id, term_taxonomy_id
FROM $wpdb->term_relationships
ORDER BY object_id , term_taxonomy_id
LIMIT 1",
ARRAY_A
);
}
/**
*
* Enqueue all $items within `jetpack_full_sync_term_relationships` actions.

View File

@ -7,6 +7,8 @@
namespace Automattic\Jetpack\Sync\Replicastore;
use Exception;
/**
* Class to handle Table Checksums for the Users table.
*/

View File

@ -132,13 +132,14 @@ class Table_Checksum {
/**
* Table_Checksum constructor.
*
* @param string $table The table to calculate checksums for.
* @param string $salt Optional salt to add to the checksum.
* @param string $table The table to calculate checksums for.
* @param string $salt Optional salt to add to the checksum.
* @param boolean $perform_text_conversion If text fields should be latin1 converted.
* @param array $additional_columns Additional columns to add to the checksum calculation.
*
* @throws Exception Throws exception from inner functions.
*/
public function __construct( $table, $salt = null, $perform_text_conversion = false ) {
public function __construct( $table, $salt = null, $perform_text_conversion = false, $additional_columns = null ) {
if ( ! Sync\Settings::is_checksum_enabled() ) {
throw new Exception( 'Checksums are currently disabled.' );
@ -163,6 +164,8 @@ class Table_Checksum {
$this->prepare_fields( $this->table_configuration );
$this->prepare_additional_columns( $additional_columns );
// Run any callbacks to check if a table is enabled or not.
if (
is_callable( $this->is_table_enabled_callback )
@ -877,4 +880,48 @@ class Table_Checksum {
return true;
}
/**
* Prepare and append custom columns to the list of columns that we run the checksum on.
*
* @param string|array $additional_columns List of additional columns.
*
* @return void
* @throws Exception When field validation fails.
*/
protected function prepare_additional_columns( $additional_columns ) {
/**
* No need to do anything if the parameter is not provided or empty.
*/
if ( empty( $additional_columns ) ) {
return;
}
if ( ! is_array( $additional_columns ) ) {
if ( ! is_string( $additional_columns ) ) {
throw new Exception( 'Invalid value for additional fields' );
}
$additional_columns = explode( ',', $additional_columns );
}
/**
* Validate the fields. If any don't conform to the required norms, we will throw an exception and
* halt code here.
*/
$this->validate_fields( $additional_columns );
/**
* Assign the fields to the checksum_fields to be used in the checksum later.
*
* We're adding the fields to the rest of the `checksum_fields`, so we don't need
* to implement extra logic just for the additional fields.
*/
$this->checksum_fields = array_unique(
array_merge(
$this->checksum_fields,
$additional_columns
)
);
}
}