updated plugin Jetpack Protect version 1.4.2

This commit is contained in:
2023-10-22 22:21:06 +00:00
committed by Gitium
parent f512d25847
commit f07dfae114
242 changed files with 6494 additions and 1502 deletions

View File

@ -5,6 +5,126 @@ 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).
## [1.58.1] - 2023-10-18
### Fixed
- Update dependencies.
## [1.58.0] - 2023-10-16
### Changed
- Migrated 'jetpack_sync_before_send*' actions for Sync queue to 'jetpack_sync_before_enqueue' instead. [#33384]
## [1.57.4] - 2023-10-10
- Minor internal updates.
## [1.57.3] - 2023-09-28
### Removed
- Remove compatibility code for PHP <5.5. [#33288]
## [1.57.2] - 2023-09-25
### Added
- Site Settings Endpoint: Allow for updating and retrieving of the wpcom_newsletter_categories site option. [#33234]
## [1.57.1] - 2023-09-20
### Added
- Adds legacy contact and locked mode options for 100-year plan [#33081]
## [1.57.0] - 2023-09-19
### Added
- Added a definition of a WooCommerce HPOS table to Jetpack Sync. [#32774]
- Jetpack Sync: Drop Sync custom queue table when Jetpack is disconnected [#32940]
- Woo: add HPOS (custom order tables) events to sync module. [#32530]
## [1.56.0] - 2023-09-04
### Added
- Add wpcom_newsletter_categories_enabled site option [#32569]
- Whitelist the blog option for auto conversion settings. [#32693]
## [1.55.2] - 2023-08-28
### Fixed
- Re-adds the jetpack-memberships-connected-account-id option to whitelist. [#32632]
## [1.55.1] - 2023-08-23
### Changed
- Updated package dependencies. [#32605]
## [1.55.0] - 2023-08-21
### Removed
- Remove Jetpack option jetpack-memberships-connected-account-id [#32125]
## [1.54.0] - 2023-08-15
### Added
- Extract Sync Queue storage handling to an external class to prepare for Custom Table migration [#32275]
## [1.53.0] - 2023-08-09
### Added
- Jetpack Sync: Custom table initialization and migration functionality [#32135]
- Jetpack Sync: Drop custom table on sender uninstall [#32335]
## [1.52.0] - 2023-08-01
### Added
- Add support for a custom database table for Sync Queue. [#32111]
- Extract Sync Queue storage handling to an external class to prepare for Custom Table migration. [#32089]
- Sync: Add feature flag for enabling custom queue table. [#31681]
## [1.51.0] - 2023-07-17
### Added
- Jetpack & Jetpack Sync: Added cache check when trying to spawn dedicated sync or update JETPACK__VERSION to avoid additional requests to the DB if external cache is available. [#31645]
- Newsletters: Add option to enable subscribe modal. [#31393]
- Sync: Add support for additional guest and note meta fields [#31810]
## [1.50.2] - 2023-07-05
### Added
- Sync wpcom_site_setup site option [#31662]
## [1.50.1] - 2023-07-04
### Changed
- Revert dedicated hook endpoint to later in the 'init' hook, as it broke existing code that registers post statuses and such during 'init'. [#31685]
## [1.50.0] - 2023-06-26
### Added
- Added a new callable to the whitelist for get_loaded_extensions(). [#31333]
- CPT Exclusion: do not sync Jetpack Inspect Log entries. [#31535]
### Fixed
- Initialize dedicated hook endpoint earlier in the 'init' hook to avoid cookie conflicts. [#31423]
## [1.49.0] - 2023-05-29
### Added
- Added `wpcom_reader_views_enabled` option to default sync list [#30800]
### Changed
- FSE: remove usage of `gutenberg_is_fse_theme` for modern `wp_is_block_theme` [#30806]
## [1.48.1] - 2023-05-15
### Changed
- PHP 8 compatibility updates. [#30599]
- PHP 8.1 compatibility updates [#30523]
## [1.48.0] - 2023-05-08
### Changed
- Use Jetpack Constants to check the value of REST_API_REQUEST in Settings:is_syncing function so we're able to overwrite the value and render some jetpack blocks via the rest api endpoint [#30400]
## [1.47.9] - 2023-05-02
### Changed
- Internal updates.
## [1.47.8] - 2023-05-01
### Changed
- Internal updates.
## [1.47.7] - 2023-04-10
### Added
- Add Jetpack Autoloader package suggestion. [#29988]
## [1.47.6] - 2023-04-04
### Changed
- Sync: Lowered priority to sync so that the hook is run at the end. [#29804]
## [1.47.5] - 2023-04-03
### Changed
- Minor internal updates.
## [1.47.4] - 2023-03-28
### Changed
- Move brute force protection into WAF package. [#28401]
@ -826,6 +946,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Move sync to a classmapped package
[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
[1.57.3]: https://github.com/Automattic/jetpack-sync/compare/v1.57.2...v1.57.3
[1.57.2]: https://github.com/Automattic/jetpack-sync/compare/v1.57.1...v1.57.2
[1.57.1]: https://github.com/Automattic/jetpack-sync/compare/v1.57.0...v1.57.1
[1.57.0]: https://github.com/Automattic/jetpack-sync/compare/v1.56.0...v1.57.0
[1.56.0]: https://github.com/Automattic/jetpack-sync/compare/v1.55.2...v1.56.0
[1.55.2]: https://github.com/Automattic/jetpack-sync/compare/v1.55.1...v1.55.2
[1.55.1]: https://github.com/Automattic/jetpack-sync/compare/v1.55.0...v1.55.1
[1.55.0]: https://github.com/Automattic/jetpack-sync/compare/v1.54.0...v1.55.0
[1.54.0]: https://github.com/Automattic/jetpack-sync/compare/v1.53.0...v1.54.0
[1.53.0]: https://github.com/Automattic/jetpack-sync/compare/v1.52.0...v1.53.0
[1.52.0]: https://github.com/Automattic/jetpack-sync/compare/v1.51.0...v1.52.0
[1.51.0]: https://github.com/Automattic/jetpack-sync/compare/v1.50.2...v1.51.0
[1.50.2]: https://github.com/Automattic/jetpack-sync/compare/v1.50.1...v1.50.2
[1.50.1]: https://github.com/Automattic/jetpack-sync/compare/v1.50.0...v1.50.1
[1.50.0]: https://github.com/Automattic/jetpack-sync/compare/v1.49.0...v1.50.0
[1.49.0]: https://github.com/Automattic/jetpack-sync/compare/v1.48.1...v1.49.0
[1.48.1]: https://github.com/Automattic/jetpack-sync/compare/v1.48.0...v1.48.1
[1.48.0]: https://github.com/Automattic/jetpack-sync/compare/v1.47.9...v1.48.0
[1.47.9]: https://github.com/Automattic/jetpack-sync/compare/v1.47.8...v1.47.9
[1.47.8]: https://github.com/Automattic/jetpack-sync/compare/v1.47.7...v1.47.8
[1.47.7]: https://github.com/Automattic/jetpack-sync/compare/v1.47.6...v1.47.7
[1.47.6]: https://github.com/Automattic/jetpack-sync/compare/v1.47.5...v1.47.6
[1.47.5]: https://github.com/Automattic/jetpack-sync/compare/v1.47.4...v1.47.5
[1.47.4]: https://github.com/Automattic/jetpack-sync/compare/v1.47.3...v1.47.4
[1.47.3]: https://github.com/Automattic/jetpack-sync/compare/v1.47.2...v1.47.3
[1.47.2]: https://github.com/Automattic/jetpack-sync/compare/v1.47.1...v1.47.2

View File

@ -4,11 +4,20 @@ Full details of the Automattic Security Policy can be found on [automattic.com](
## Supported Versions
Generally, only the latest version of Jetpack has continued support. If a critical vulnerability is found in the current version of Jetpack, we may opt to backport any patches to previous versions.
Generally, only the latest version of Jetpack and its associated plugins have continued support. If a critical vulnerability is found in the current version of a plugin, we may opt to backport any patches to previous versions.
## Reporting a Vulnerability
[Jetpack](https://jetpack.com/) is an open-source plugin for WordPress. Our HackerOne program covers the plugin software, as well as a variety of related projects and infrastructure.
Our HackerOne program covers the below plugin software, as well as a variety of related projects and infrastructure:
* [Jetpack](https://jetpack.com/)
* Jetpack Backup
* Jetpack Boost
* Jetpack CRM
* Jetpack Protect
* Jetpack Search
* Jetpack Social
* Jetpack VideoPress
**For responsible disclosure of security issues and to be eligible for our bug bounty program, please submit your report via the [HackerOne](https://hackerone.com/automattic) portal.**

View File

@ -4,19 +4,22 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-connection": "^1.51.3",
"automattic/jetpack-constants": "^1.6.21",
"automattic/jetpack-identity-crisis": "^0.8.40",
"automattic/jetpack-password-checker": "^0.2.12",
"automattic/jetpack-ip": "^0.1.1",
"automattic/jetpack-roles": "^1.4.22",
"automattic/jetpack-status": "^1.16.3"
"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"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.3.2",
"yoast/phpunit-polyfills": "1.0.4",
"automattic/jetpack-changelogger": "^3.3.11",
"yoast/phpunit-polyfills": "1.1.0",
"automattic/wordbless": "@dev"
},
"suggest": {
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
},
"autoload": {
"classmap": [
"src/"
@ -45,7 +48,7 @@
"link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.47.x-dev"
"dev-trunk": "1.58.x-dev"
}
},
"config": {

View File

@ -11,6 +11,8 @@ use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Identity_Crisis;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Sync\Modules\WooCommerce_HPOS_Orders;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use WP_Error;
/**
@ -171,7 +173,7 @@ class Actions {
self::should_initialize_sender()
) ) {
self::initialize_sender();
add_action( 'shutdown', array( self::$sender, 'do_sync' ) );
add_action( 'shutdown', array( self::$sender, 'do_sync' ), 9998 );
add_action( 'shutdown', array( self::$sender, 'do_full_sync' ), 9999 );
}
}
@ -441,6 +443,7 @@ class Actions {
'buffer_id' => $buffer_id,
// TODO this will be extended in the future. Might be good to extract in a separate method to support future entries too.
'sync_flow_type' => Settings::is_dedicated_sync_enabled() ? 'dedicated' : 'default',
'storage_type' => Settings::is_custom_queue_table_enabled() ? 'custom' : 'options',
);
$query_args['timeout'] = Settings::is_doing_cron() ? 30 : 20;
@ -743,6 +746,14 @@ class Actions {
return;
}
add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_sync_module' ) );
if ( ! class_exists( CustomOrdersTableController::class ) ) {
return;
}
$cot_controller = wc_get_container()->get( CustomOrdersTableController::class );
if ( $cot_controller->custom_orders_table_usage_is_enabled() ) {
add_filter( 'jetpack_sync_modules', array( __CLASS__, 'add_woocommerce_hpos_order_sync_module' ) );
}
}
/**
@ -789,6 +800,21 @@ class Actions {
return $sync_modules;
}
/**
* Adds Woo's HPOS sync modules to existing modules for sending.
*
* @param array $sync_modules The list of sync modules declared prior to this filter.
*
* @access public
* @static
*
* @return array A list of sync modules that now includes Woo's HPOS modules.
*/
public static function add_woocommerce_hpos_order_sync_module( $sync_modules ) {
$sync_modules[] = WooCommerce_HPOS_Orders::class;
return $sync_modules;
}
/**
* Initializes sync for WP Super Cache.
*

View File

@ -68,6 +68,7 @@ class Data_Settings {
'jetpack_sync_settings_post_types_blacklist',
'jetpack_sync_settings_taxonomies_blacklist',
'jetpack_sync_settings_dedicated_sync_enabled',
'jetpack_sync_settings_custom_queue_table_enabled',
/**
* Connection related options
*/

View File

@ -220,6 +220,13 @@ class Dedicated_Sender {
public static function try_lock_spawn_request() {
$current_microtime = (string) microtime( true );
if ( wp_using_ext_object_cache() ) {
if ( true !== wp_cache_add( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, $current_microtime, 'jetpack', self::DEDICATED_SYNC_REQUEST_LOCK_TIMEOUT ) ) {
// Cache lock has been claimed already.
return false;
}
}
$current_lock_value = \Jetpack_Options::get_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null );
if ( ! empty( $current_lock_value ) ) {
@ -264,9 +271,14 @@ class Dedicated_Sender {
return new WP_Error( 'dedicated_request_lock_invalid', 'Invalid lock_id supplied for unlock' );
}
$current_lock_value = \Jetpack_Options::get_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null );
if ( wp_using_ext_object_cache() ) {
if ( (string) $lock_id === wp_cache_get( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, 'jetpack', true ) ) {
wp_cache_delete( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, 'jetpack' );
}
}
// If this is the flow that has the lock, let's release it so we can spawn other requests afterwards
$current_lock_value = \Jetpack_Options::get_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME, null );
if ( (string) $lock_id === $current_lock_value ) {
\Jetpack_Options::delete_raw_option( self::DEDICATED_SYNC_REQUEST_LOCK_OPTION_NAME );
return true;

View File

@ -64,6 +64,7 @@ class Defaults {
'infinite_scroll',
'infinite_scroll_google_analytics',
'jetpack-memberships-connected-account-id',
'jetpack-memberships-has-connected-account',
'jetpack-twitter-cards-site-tag',
'jetpack_activated',
'jetpack_allowed_xsite_search_ids',
@ -87,6 +88,7 @@ class Defaults {
'jetpack_protect_key',
'jetpack_publicize_options',
'jetpack_relatedposts',
'jetpack_social_settings',
'jetpack_sso_match_by_email',
'jetpack_sso_require_two_step',
'jetpack_sync_non_blocking', // is non-blocking Jetpack Sync flow enabled.
@ -96,6 +98,7 @@ class Defaults {
'jetpack_sync_settings_post_types_blacklist',
'jetpack_sync_settings_taxonomies_blacklist',
'jetpack_sync_settings_dedicated_sync_enabled', // is Dedicated Sync flow enabled.
'jetpack_sync_settings_custom_queue_table_enabled', // is custom queue table enabled.
'jetpack_testimonial',
'jetpack_testimonial_posts_per_page',
'jetpack_wga',
@ -142,6 +145,7 @@ class Defaults {
'stats_options',
'stb_enabled',
'stc_enabled',
'sm_enabled',
'sticky_posts',
'stylesheet',
'subscription_options',
@ -159,6 +163,7 @@ class Defaults {
'uploads_use_yearmonth_folders',
'users_can_register',
'verification_services_codes',
'videopress_private_enabled_for_site',
'wordads_ccpa_enabled',
'wordads_ccpa_privacy_policy_url',
'wordads_custom_adstxt',
@ -168,17 +173,23 @@ class Defaults {
'wordads_display_page',
'wordads_display_post',
'wordads_second_belowpost',
'woocommerce_custom_orders_table_enabled',
'wp_mobile_app_promos',
'wp_mobile_excerpt',
'wp_mobile_featured_images',
'wp_page_for_privacy_policy',
'wpcom_featured_image_in_email',
'wpcom_gifting_subscription',
'wpcom_is_fse_activated',
'wpcom_legacy_contact',
'wpcom_locked_mode',
'wpcom_newsletter_categories',
'wpcom_newsletter_categories_enabled',
'wpcom_publish_comments_with_markdown',
'wpcom_publish_posts_with_markdown',
'wpcom_reader_views_enabled',
'wpcom_site_setup',
'wpcom_subscription_emails_use_excerpt',
'videopress_private_enabled_for_site',
'wpcom_gifting_subscription',
);
/**
@ -289,6 +300,7 @@ class Defaults {
* @var array Default whitelist of callables.
*/
public static $default_callable_whitelist = array(
'get_loaded_extensions' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_loaded_extensions' ),
'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
'get_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ),
'get_plugins_action_links' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins_action_links' ),
@ -398,6 +410,7 @@ class Defaults {
'flamingo_outbound',
'http',
'idx_page',
'jetpack_inspect_log', // Jetpack Inspect dev tool. p1HpG7-nkd-p2
'jetpack_migration',
'jp_img_sitemap',
'jp_img_sitemap_index',
@ -1296,4 +1309,10 @@ class Defaults {
*/
public static $default_dedicated_sync_enabled = 0;
/**
* Default for enabling custom queue table for Sync.
*
* @var int Bool-ish. Default 0.
*/
public static $default_custom_queue_table_enabled = 0;
}

View File

@ -583,10 +583,12 @@ class Functions {
/**
* Returns if the current theme is a Full Site Editing theme.
*
* @since 1.49.0 Uses wp_is_block_theme() instead of deprecated gutenberg_is_fse_theme().
*
* @return bool Theme is a Full Site Editing theme.
*/
public static function get_is_fse_theme() {
return function_exists( 'gutenberg_is_fse_theme' ) && gutenberg_is_fse_theme();
return wp_is_block_theme();
}
/**
@ -671,4 +673,40 @@ class Functions {
public static function get_active_modules() {
return ( new Jetpack_Modules() )->get_active();
}
/**
* Return a list of PHP modules that we want to track.
*
* @since $$next_version$$
*
* @return array
*/
public static function get_loaded_extensions() {
if ( function_exists( 'get_loaded_extensions' ) ) {
return get_loaded_extensions();
}
// If a hosting provider has blocked get_loaded_extensions for any reason,
// we check extensions manually.
$extensions_to_check = array(
'libxml' => array( 'class' => 'libXMLError' ),
'xml' => array( 'function' => 'xml_parse' ),
'dom' => array( 'class' => 'DOMDocument' ),
'xdebug' => array( 'function' => 'xdebug_break' ),
);
$enabled_extensions = array();
foreach ( $extensions_to_check as $extension_name => $extension ) {
if (
( isset( $extension['function'] )
&& function_exists( $extension['function'] ) )
|| class_exists( $extension['class'] )
) {
$enabled_extensions[] = $extension_name;
}
}
return $enabled_extensions;
}
}

View File

@ -185,5 +185,4 @@ class Health {
self::update_status( self::STATUS_IN_SYNC );
}
}
}

View File

@ -474,7 +474,7 @@ class Listener {
* @return string Request URL, if known. Otherwise, wp-admin or home_url.
*/
public function get_request_url() {
if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
if ( isset( $_SERVER['HTTP_HOST'] ) && isset( $_SERVER['REQUEST_URI'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- False positive, sniff misses the call to esc_url_raw.
return esc_url_raw( 'http' . ( isset( $_SERVER['HTTPS'] ) ? 's' : '' ) . '://' . wp_unslash( "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}" ) );
}

View File

@ -8,6 +8,7 @@
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Sync\Actions as Sync_Actions;
use Automattic\Jetpack\Sync\Queue\Queue_Storage_Table;
/**
* Jetpack Sync main class.
@ -34,6 +35,9 @@ class Main {
// Any hooks below are special cases that need to be declared even if Sync is not allowed.
add_action( 'jetpack_site_registered', array( 'Automattic\\Jetpack\\Sync\\Actions', 'do_initial_sync' ), 10, 0 );
// Sync clean up, when Jetpack is disconnected.
add_action( 'jetpack_site_disconnected', array( __CLASS__, 'on_jetpack_site_disconnected' ), 1000 );
// Set up package version hook.
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
}
@ -45,6 +49,24 @@ class Main {
Sender::get_instance()->uninstall();
}
/**
* Sync cleanup on shutdown.
*/
public static function on_jetpack_site_disconnected() {
add_action( 'shutdown', array( __CLASS__, 'sync_cleanup' ), 10000 );
}
/**
* Delete all sync related data on Site disconnect / clean up custom table.
* Needs to happen on shutdown to prevent fatals.
*/
public static function sync_cleanup() {
Sender::get_instance()->uninstall();
$table_storage = new Queue_Storage_Table( 'test_queue' );
$table_storage->drop_table();
}
/**
* Sets the Sync data settings.
*
@ -110,5 +132,4 @@ class Main {
// Initialize health-related hooks after plugins have loaded.
Health::init();
}
}

View File

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

View File

@ -7,6 +7,8 @@
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Sync\Queue\Queue_Storage_Options;
use Automattic\Jetpack\Sync\Queue\Queue_Storage_Table;
use WP_Error;
/**
@ -37,6 +39,15 @@ class Queue {
*/
public $random_int;
/**
* Queue Storage instance where we'll store the queue items.
*
* For now, it's only the Options table. To be updated to include the Custom table in future updates.
*
* @var Queue_Storage_Options|Queue_Storage_Table|null
*/
public $queue_storage = null;
/**
* Queue constructor.
*
@ -46,42 +57,47 @@ class Queue {
$this->id = str_replace( '-', '_', $id ); // Necessary to ensure we don't have ID collisions in the SQL.
$this->row_iterator = 0;
$this->random_int = wp_rand( 1, 1000000 );
/**
* If the Custom queue table is enabled - let's use it as a backend. Otherwise, fall back to the Options table.
*/
if ( Settings::is_custom_queue_table_enabled() ) {
$this->queue_storage = new Queue_Storage_Table( $this->id );
} else {
// Initialize the storage with the Options table backend. To be changed in subsequent updates to include the logic to switch to Custom Table.
$this->queue_storage = new Queue_Storage_Options( $this->id );
}
}
/**
* Add a single item to the queue.
*
* @param object $item Event object to add to queue.
*
* @return bool|WP_Error
*/
public function add( $item ) {
global $wpdb;
$added = false;
// If empty, don't add.
if ( empty( $item ) ) {
return;
return false;
}
// Attempt to serialize data, if an exception (closures) return early.
try {
$item = serialize( $item ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
} catch ( \Exception $ex ) {
return;
return new WP_Error( 'queue_unable_to_serialize', 'Unable to serialize item' );
}
// This basically tries to add the option until enough time has elapsed that
// it has a unique (microtime-based) option key.
while ( ! $added ) {
$rows_added = $wpdb->query(
$wpdb->prepare(
"INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES (%s, %s,%s)",
$this->get_next_data_row_option_name(),
$item,
'no'
)
);
$added = ( 0 !== $rows_added );
$added = $this->queue_storage->insert_item( $this->get_next_data_row_option_name(), $item );
}
return $added;
}
/**
@ -92,33 +108,15 @@ class Queue {
* @return bool|\WP_Error
*/
public function add_all( $items ) {
global $wpdb;
// TODO check and figure out if it's used at all and if we can optimize it.
$base_option_name = $this->get_next_data_row_option_name();
$query = "INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES ";
$rows = array();
$count_items = count( $items );
for ( $i = 0; $i < $count_items; ++$i ) {
// skip empty items.
if ( empty( $items[ $i ] ) ) {
continue;
}
try {
$option_name = esc_sql( $base_option_name . '-' . $i );
$option_value = esc_sql( serialize( $items[ $i ] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
$rows[] = "('$option_name', '$option_value', 'no')";
} catch ( \Exception $e ) {
// Item cannot be serialized so skip.
continue;
}
}
$rows_added = $wpdb->query( $query . join( ',', $rows ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$rows_added = $this->queue_storage->add_all( $items, $base_option_name );
if ( count( $items ) !== $rows_added ) {
return new WP_Error( 'row_count_mismatch', "The number of rows inserted didn't match the size of the input array" );
}
return true;
}
@ -159,49 +157,21 @@ class Queue {
* Lag is the difference in time between the age of the oldest item
* (aka first or frontmost item) and the current time.
*
* @param microtime $now The current time in microtime.
* @param float $now The current time in microtime.
*
* @return float|int|mixed|null
* @return float
*/
public function lag( $now = null ) {
global $wpdb;
$first_item_name = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 1",
"jpsq_{$this->id}-%"
)
);
if ( ! $first_item_name ) {
return 0;
}
if ( null === $now ) {
$now = microtime( true );
}
// Break apart the item name to get the timestamp.
$matches = null;
if ( preg_match( '/^jpsq_' . $this->id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) {
return $now - (float) $matches[1];
} else {
return 0;
}
return (float) $this->queue_storage->get_lag( $now );
}
/**
* Resets the queue.
*/
public function reset() {
global $wpdb;
$this->delete_checkout_id();
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
"jpsq_{$this->id}-%"
)
);
$this->queue_storage->clear_queue();
}
/**
@ -210,14 +180,7 @@ class Queue {
* @return int
*/
public function size() {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT count(*) FROM $wpdb->options WHERE option_name LIKE %s",
"jpsq_{$this->id}-%"
)
);
return $this->queue_storage->get_item_count();
}
/**
@ -228,15 +191,7 @@ class Queue {
* @return bool
*/
public function has_any_items() {
global $wpdb;
$value = $wpdb->get_var(
$wpdb->prepare(
"SELECT exists( SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s )",
"jpsq_{$this->id}-%"
)
);
return ( '1' === $value );
return $this->size() > 0;
}
/**
@ -251,7 +206,8 @@ class Queue {
return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
}
$buffer_id = uniqid();
// TODO check if adding a prefix is going to be a problem
$buffer_id = uniqid( '', true );
$result = $this->set_checkout_id( $buffer_id );
@ -261,13 +217,15 @@ class Queue {
$items = $this->fetch_items( $buffer_size );
if ( ! is_countable( $items ) ) {
return false;
}
if ( count( $items ) === 0 ) {
return false;
}
$buffer = new Queue_Buffer( $buffer_id, array_slice( $items, 0, $buffer_size ) );
return $buffer;
return new Queue_Buffer( $buffer_id, array_slice( $items, 0, $buffer_size ) );
}
/**
@ -315,14 +273,14 @@ class Queue {
* @param int $max_memory (bytes) Maximum memory threshold.
* @param int $max_buffer_size Maximum buffer size (number of items).
*
* @return Automattic\Jetpack\Sync\Queue_Buffer|bool|int|\WP_Error
* @return \Automattic\Jetpack\Sync\Queue_Buffer|bool|int|\WP_Error
*/
public function checkout_with_memory_limit( $max_memory, $max_buffer_size = 500 ) {
if ( $this->get_checkout_id() ) {
return new WP_Error( 'unclosed_buffer', 'There is an unclosed buffer' );
}
$buffer_id = uniqid();
$buffer_id = uniqid( '', true );
$result = $this->set_checkout_id( $buffer_id );
@ -330,27 +288,22 @@ class Queue {
return $result;
}
// Get the map of buffer_id -> memory_size.
global $wpdb;
// How much memory is currently being used by the items.
$total_memory = 0;
$items_with_size = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, LENGTH(option_value) AS value_size FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
"jpsq_{$this->id}-%",
$max_buffer_size
),
OBJECT
);
// Store the items to return
$items = array();
if ( count( $items_with_size ) === 0 ) {
$current_items_ids = $this->queue_storage->get_items_ids_with_size( $max_buffer_size - count( $items ) );
// If no valid items are returned or no items are returned, continue.
if ( ! is_countable( $current_items_ids ) || count( $current_items_ids ) === 0 ) {
return false;
}
$total_memory = 0;
$max_item_id = $items_with_size[0]->id;
$min_item_id = $max_item_id;
$item_ids_to_fetch = array();
foreach ( $items_with_size as $id => $item_with_size ) {
foreach ( $current_items_ids as $id => $item_with_size ) {
$total_memory += $item_with_size->value_size;
// If this is the first item and it exceeds memory, allow loop to continue
@ -359,20 +312,28 @@ class Queue {
break;
}
$max_item_id = $item_with_size->id;
$item_ids_to_fetch[] = $item_with_size->id;
}
$query = $wpdb->prepare(
"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name >= %s and option_name <= %s ORDER BY option_name ASC",
$min_item_id,
$max_item_id
);
$current_items = $this->queue_storage->fetch_items_by_ids( $item_ids_to_fetch );
$items = $wpdb->get_results( $query, OBJECT ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
foreach ( $items as $item ) {
// @codingStandardsIgnoreStart
$item->value = @unserialize( $item->value );
// @codingStandardsIgnoreEnd
$items_count = is_countable( $current_items ) ? count( $current_items ) : 0;
if ( $items_count > 0 ) {
/**
* Save some memory by moving things one by one to the array of items being returned, instead of
* unserializing all and then merging them with other items.
*
* PHPCS ignore is because this is the expected behavior - we're assigning a variable in the condition part of the loop.
*/
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
while ( ( $current_item = array_shift( $current_items ) ) !== null ) {
// @codingStandardsIgnoreStart
$current_item->value = unserialize( $current_item->value );
// @codingStandardsIgnoreEnd
$items[] = $current_item;
}
}
if ( count( $items ) === 0 ) {
@ -381,9 +342,7 @@ class Queue {
return false;
}
$buffer = new Queue_Buffer( $buffer_id, $items );
return $buffer;
return new Queue_Buffer( $buffer_id, $items );
}
/**
@ -445,14 +404,13 @@ class Queue {
* @return bool|int
*/
private function delete( $ids ) {
if ( 0 === count( $ids ) ) {
if ( array() === $ids ) {
return 0;
}
global $wpdb;
$sql = "DELETE FROM $wpdb->options WHERE option_name IN (" . implode( ', ', array_fill( 0, count( $ids ), '%s' ) ) . ')';
$query = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $ids ) );
return $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$this->queue_storage->delete_items_by_ids( $ids );
return true;
}
/**
@ -656,26 +614,7 @@ class Queue {
* @return array|object|null
*/
private function fetch_items( $limit = null ) {
global $wpdb;
if ( $limit ) {
$items = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
"jpsq_{$this->id}-%",
$limit
),
OBJECT
);
} else {
$items = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC",
"jpsq_{$this->id}-%"
),
OBJECT
);
}
$items = $this->queue_storage->fetch_items( $limit );
return $this->unserialize_values( $items );
}
@ -688,26 +627,7 @@ class Queue {
* @return array|object|null
*/
private function fetch_items_by_id( $items_ids ) {
global $wpdb;
// return early if $items_ids is empty or not an array.
if ( empty( $items_ids ) || ! is_array( $items_ids ) ) {
return null;
}
$ids_placeholders = implode( ', ', array_fill( 0, count( $items_ids ), '%s' ) );
$query_with_placeholders = "SELECT option_name AS id, option_value AS value
FROM $wpdb->options
WHERE option_name IN ( $ids_placeholders )";
$items = $wpdb->get_results(
$wpdb->prepare(
$query_with_placeholders, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$items_ids
),
OBJECT
);
return $this->unserialize_values( $items );
return $this->unserialize_values( $this->queue_storage->fetch_items_by_ids( $items_ids ) );
}
/**

View File

@ -1280,7 +1280,7 @@ class Replicastore implements Replicastore_Interface {
// With a limit present, we'll look at a dataset consisting of object_ids that meet the constructs of the $where clause.
// Without a limit, we can use the actual table as a dataset.
$from = $bucket_size ?
"( SELECT $distinct_sql $id_field FROM $object_table $where_sql ORDER BY $id_field ASC LIMIT $bucket_size ) as ids" :
"( SELECT $distinct_sql $id_field FROM $object_table $where_sql ORDER BY $id_field ASC LIMIT " . ( (int) $bucket_size ) . ' ) as ids' :
"$object_table $where_sql ORDER BY $id_field ASC";
return $wpdb->get_row(

View File

@ -353,7 +353,7 @@ class REST_Endpoints {
$modules['users'] = 'initial';
} elseif ( is_array( $request->get_param( $module_name ) ) ) {
$ids = $request->get_param( $module_name );
if ( count( $ids ) > 0 ) {
if ( array() !== $ids ) {
$modules[ $module_name ] = $ids;
}
}
@ -868,5 +868,4 @@ class REST_Endpoints {
// Limit to A-Z,a-z,0-9,_,-,. .
return preg_replace( '/[^A-Za-z0-9-_.]/', '', $item );
}
}

View File

@ -140,5 +140,4 @@ class REST_Sender {
return $buffer;
}
}

View File

@ -531,7 +531,7 @@ class Sender {
}
$encoded_item = $this->codec->encode( $item );
$upload_size += strlen( $encoded_item );
if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) {
if ( $upload_size > $this->upload_max_bytes && array() !== $items_to_send ) {
break;
}
$items_to_send[ $key ] = $encode ? $encoded_item : $item;
@ -641,11 +641,9 @@ class Sender {
} else {
// Detect if the last item ID was an error.
$had_wp_error = is_wp_error( end( $processed_item_ids ) );
if ( $had_wp_error ) {
$wp_error = array_pop( $processed_item_ids );
}
$wp_error = $had_wp_error ? array_pop( $processed_item_ids ) : null;
// Also checkin any items that were skipped.
if ( count( $skipped_items_ids ) > 0 ) {
if ( array() !== $skipped_items_ids ) {
$processed_item_ids = array_merge( $processed_item_ids, $skipped_items_ids );
}
$processed_items = array_intersect_key( $items, array_flip( $processed_item_ids ) );

View File

@ -7,6 +7,9 @@
namespace Automattic\Jetpack\Sync;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Sync\Queue\Queue_Storage_Table;
/**
* Class to manage the sync settings.
*/
@ -57,6 +60,7 @@ class Settings {
'full_sync_limits' => true,
'checksum_disable' => true,
'dedicated_sync_enabled' => true,
'custom_queue_table_enabled' => true,
);
/**
@ -197,6 +201,84 @@ class Settings {
$validated_settings = array_intersect_key( $new_settings, self::$valid_settings );
foreach ( $validated_settings as $setting => $value ) {
/**
* Custom table migration logic.
*
* This needs to happen before the option is updated, to avoid race conditions where we update the option,
* but haven't yet created the table or can't create it.
*
* On high-traffic sites this can lead to Sync trying to write in a non-existent table.
*
* So to avoid this, we're going to first try to initialize everything and then update the option.
*/
if ( 'custom_queue_table_enabled' === $setting ) {
// Need to check the current value in the database to make sure we're not doing anything weird.
$old_value = get_option( self::SETTINGS_OPTION_PREFIX . $setting, null );
if ( ! $old_value && $value ) {
/**
* The custom table has been enabled.
*
* - Initialize the custom table
* - Migrate the data
*
* If something fails, migrate back to the options table and clean up everything about the custom table.
*/
$init_result = Queue_Storage_Table::initialize_custom_sync_table();
/**
* Check if there was a problem when initializing the table.
*/
if ( is_wp_error( $init_result ) ) {
/**
* Unable to initialize the table properly. Set the value to `false` as we can't enable it.
*/
$value = false;
/**
* Send error to WPCOM, so we can track and take an appropriate action.
*/
$data = array(
'timestamp' => microtime( true ),
'error_code' => $init_result->get_error_code(),
'response_body' => $init_result->get_error_message(),
);
$sender = Sender::get_instance();
$sender->send_action( 'jetpack_sync_storage_error_custom_init', $data );
} elseif ( ! Queue_Storage_Table::migrate_from_options_table_to_custom_table() ) {
/**
* If the migration fails, do a reverse migration and set the value to `false` as we can't
* safely enable the table.
*/
Queue_Storage_Table::migrate_from_custom_table_to_options_table();
// Set $value to `false` as we couldn't do the migration, and we can't continue enabling the table.
$value = false;
/**
* Send error to WPCOM, so we can track and take an appropriate action.
*/
$data = array(
'timestamp' => microtime( true ),
// TODO: Maybe add more details here for the migration, i.e. how many items where in the queue?
);
$sender = Sender::get_instance();
$sender->send_action( 'jetpack_sync_storage_error_custom_migrate', $data );
}
} elseif ( $old_value && ! $value ) {
/**
* The custom table has been disabled, migrate what we can from the custom table to the options table.
*/
Queue_Storage_Table::migrate_from_custom_table_to_options_table();
}
}
/**
* Regular option update and handling
*/
if ( self::is_network_setting( $setting ) ) {
if ( is_multisite() && is_main_site() ) {
$updated = update_site_option( self::SETTINGS_OPTION_PREFIX . $setting, $value );
@ -244,7 +326,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_blacklisted_post_types_sql() {
return 'post_type NOT IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ) ) . '\')';
return 'post_type NOT IN (\'' . implode( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_types_blacklist' ) ) ) . '\')';
}
/**
@ -274,7 +356,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_blacklisted_taxonomies_sql() {
return "taxonomy NOT IN ('" . join( "', '", array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ) ) . "')";
return "taxonomy NOT IN ('" . implode( "', '", array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ) ) . "')";
}
/**
@ -287,7 +369,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_post_meta_sql() {
return 'meta_key IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_meta_whitelist' ) ) ) . '\')';
return 'meta_key IN (\'' . implode( '\', \'', array_map( 'esc_sql', self::get_setting( 'post_meta_whitelist' ) ) ) . '\')';
}
/**
@ -355,7 +437,7 @@ class Settings {
* @return string SQL WHERE clause.
*/
public static function get_whitelisted_comment_meta_sql() {
return 'meta_key IN (\'' . join( '\', \'', array_map( 'esc_sql', self::get_setting( 'comment_meta_whitelist' ) ) ) . '\')';
return 'meta_key IN (\'' . implode( '\', \'', array_map( 'esc_sql', self::get_setting( 'comment_meta_whitelist' ) ) ) . '\')';
}
/**
@ -508,7 +590,7 @@ class Settings {
* @return boolean Whether we are currently syncing.
*/
public static function is_syncing() {
return (bool) self::$is_syncing || ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST );
return (bool) self::$is_syncing || Constants::is_true( 'REST_API_REQUEST' );
}
/**
@ -585,4 +667,15 @@ class Settings {
return (bool) self::get_setting( 'dedicated_sync_enabled' );
}
/**
* Whether custom queue table is enabled.
*
* @access public
* @static
*
* @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' );
}
}

View File

@ -59,5 +59,4 @@ class Simple_Codec extends JSON_Deflate_Array_Codec {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
return $this->json_unserialize( base64_decode( $input ) );
}
}

View File

@ -389,7 +389,7 @@ class Callables extends Module {
$action_links = array();
}
$formatted_action_links = null;
if ( ! empty( $action_links ) && count( $action_links ) > 0 ) {
if ( $action_links ) {
$dom_doc = new \DOMDocument();
foreach ( $action_links as $action_link ) {
// The @ is not enough to suppress errors when dealing with libxml,
@ -642,5 +642,4 @@ class Callables extends Module {
return 'CALLABLE-DOES-NOT-EXIST';
}
}

View File

@ -91,7 +91,7 @@ class Comments extends Module {
add_filter( 'wp_update_comment_data', array( $this, 'handle_comment_contents_modification' ), 10, 3 );
// comment actions.
add_filter( 'jetpack_sync_before_enqueue_wp_insert_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
add_filter( 'jetpack_sync_before_enqueue_wp_insert_comment', array( $this, 'filter_jetpack_sync_before_enqueue_wp_insert_comment' ) );
add_filter( 'jetpack_sync_before_enqueue_deleted_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
add_filter( 'jetpack_sync_before_enqueue_trashed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
add_filter( 'jetpack_sync_before_enqueue_untrashed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
@ -115,6 +115,13 @@ class Comments extends Module {
foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
$comment_action_name = "comment_{$comment_status}_{$comment_type}";
add_action( $comment_action_name, $callable, 10, 2 );
add_filter(
'jetpack_sync_before_enqueue_' . $comment_action_name,
array(
$this,
'expand_wp_insert_comment',
)
);
}
}
@ -258,6 +265,22 @@ class Comments extends Module {
return $args;
}
/**
* Prevents any comment types that are not in the whitelist from being enqueued and sent to WordPress.com.
* Also expands comment data before being enqueued.
*
* @param array $args Arguments passed to wp_insert_comment.
*
* @return false or array $args Arguments passed to wp_insert_comment or false if the comment type is a blacklisted one.
*/
public function filter_jetpack_sync_before_enqueue_wp_insert_comment( $args ) {
if ( false === $this->only_allow_white_listed_comment_types( $args ) ) {
return false;
}
return $this->expand_wp_insert_comment( $args );
}
/**
* Whether a comment type is allowed.
* A comment type is allowed if it's present in the comment type whitelist.
@ -280,21 +303,6 @@ class Comments extends Module {
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_wp_insert_comment', array( $this, 'expand_wp_insert_comment' ) );
foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
$comment_action_name = "comment_{$comment_status}_{$comment_type}";
add_filter(
'jetpack_sync_before_send_' . $comment_action_name,
array(
$this,
'expand_wp_insert_comment',
)
);
}
}
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
}
@ -392,7 +400,7 @@ class Comments extends Module {
}
/**
* Expand the comment creation before the data is serialized and sent to the server.
* Expand the comment creation before the data is added to the Sync queue.
*
* @access public
*

View File

@ -335,5 +335,4 @@ class Constants extends Module {
return false;
}
}

View File

@ -98,7 +98,7 @@ class Full_Sync_Immediately extends Module {
)
);
$range = $this->get_content_range( $full_sync_config );
$range = $this->get_content_range();
/**
* Fires when a full sync begins. This action is serialized
* and sent to the server so that it knows a full sync is coming.
@ -466,5 +466,4 @@ class Full_Sync_Immediately extends Module {
* @param array $actions an array of actions, ignored for queueless sync.
*/
public function update_sent_progress_action( $actions ) { } // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
}

View File

@ -726,5 +726,4 @@ class Full_Sync extends Module {
private function get_config() {
return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
}
}

View File

@ -179,7 +179,7 @@ class Import extends Module {
}
$action = current_filter();
$backtrace = debug_backtrace( false ); //phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.debug_backtrace_optionsFound,WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
$backtrace = debug_backtrace( false ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
$do_action_pos = -1;
$backtrace_len = count( $backtrace );

View File

@ -250,7 +250,7 @@ abstract class Module {
$listener = Listener::get_instance();
// Count down from max_id to min_id so we get newest posts/comments/etc first.
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
while ( $ids = $wpdb->get_col( "SELECT {$id_field} FROM {$table_name} WHERE {$where_sql} AND {$id_field} < {$previous_interval_end} ORDER BY {$id_field} DESC LIMIT {$items_per_page}" ) ) {
// Request posts in groups of N for efficiency.
$chunked_ids = array_chunk( $ids, self::ARRAY_CHUNK_SIZE );
@ -338,7 +338,7 @@ SQL
$limits = Settings::get_setting( 'full_sync_limits' )[ $this->name() ];
$chunks_sent = 0;
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
// 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 ) {
return $status;
@ -600,5 +600,4 @@ SQL
public function get_where_sql( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return '1=1';
}
}

View File

@ -246,7 +246,6 @@ class Network_Options extends Module {
* @return int total
*/
public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return count( $this->network_options_whitelist );
return count( (array) $this->network_options_whitelist );
}
}

View File

@ -477,5 +477,4 @@ class Options extends Module {
return 'OPTION-DOES-NOT-EXIST';
}
}

View File

@ -73,17 +73,10 @@ class Plugins extends Module {
add_action( 'admin_action_update', array( $this, 'check_plugin_edit' ) );
add_action( 'jetpack_edited_plugin', $callable, 10, 2 );
add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'plugin_edit_ajax' ), 0 );
}
/**
* Initialize the module in the sender.
*
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_activated_plugin', array( $this, 'expand_plugin_data' ) );
add_filter( 'jetpack_sync_before_send_deactivated_plugin', array( $this, 'expand_plugin_data' ) );
// Note that we don't simply 'expand_plugin_data' on the 'delete_plugin' action here because the plugin file is deleted when that action finishes.
add_filter( 'jetpack_sync_before_enqueue_activated_plugin', array( $this, 'expand_plugin_data' ) );
add_filter( 'jetpack_sync_before_enqueue_deactivated_plugin', array( $this, 'expand_plugin_data' ) );
}
/**
@ -332,8 +325,8 @@ class Plugins extends Module {
$real_file = WP_PLUGIN_DIR . '/' . $file;
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writeable
if ( ! is_writeable( $real_file ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
if ( ! is_writable( $real_file ) ) {
return;
}

View File

@ -144,12 +144,13 @@ class Posts extends Module {
add_filter( 'jetpack_sync_before_enqueue_deleted_post', array( $this, 'filter_blacklisted_post_types_deleted' ) );
add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_post', array( $this, 'filter_blacklisted_post_types' ) );
// Listen for meta changes.
$this->init_listeners_for_meta_type( 'post', $callable );
$this->init_meta_whitelist_handler( 'post', array( $this, 'filter_meta' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_post', array( $this, 'filter_jetpack_sync_before_enqueue_jetpack_sync_save_post' ) );
add_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
add_action( 'jetpack_post_meta_batch_delete', $callable, 10, 2 );
@ -214,13 +215,10 @@ class Posts extends Module {
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_sync_save_post', array( $this, 'expand_jetpack_sync_save_post' ) );
// meta.
add_filter( 'jetpack_sync_before_send_added_post_meta', array( $this, 'trim_post_meta' ) );
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' ) );
}
@ -321,6 +319,22 @@ class Posts extends Module {
return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
}
/**
* Filter all blacklisted post types and add filtered post content.
*
* @param array $args Hook arguments.
* @return array|false Hook arguments, or false if the post type is a blacklisted one.
*/
public function filter_jetpack_sync_before_enqueue_jetpack_sync_save_post( $args ) {
list( $post_id, $post, $update, $previous_state ) = $args;
if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
return false;
}
return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
}
/**
* Filter all blacklisted post types.
*
@ -338,22 +352,6 @@ class Posts extends Module {
return $args;
}
/**
* Filter all blacklisted post types.
*
* @param array $args Hook arguments.
* @return array|false Hook arguments, or false if the post type is a blacklisted one.
*/
public function filter_blacklisted_post_types( $args ) {
$post = $args[1];
if ( in_array( $post->post_type, Settings::get_setting( 'post_types_blacklist' ), true ) ) {
return false;
}
return $args;
}
/**
* Filter all meta that is not blacklisted, or is stored for a disallowed post type.
*
@ -514,9 +512,18 @@ class Posts extends Module {
}
array_map( 'remove_shortcode', array_keys( $removed_shortcode_callbacks ) );
/**
* Certain modules such as Likes, Related Posts and Sharedaddy are using `Settings::is_syncing`
* in order to NOT get rendered in filtered post content.
* Since the current method runs now before enqueueing instead of before sending,
* we are setting `is_syncing` flag to true in order to preserve the existing functionality.
*/
$is_syncing_current = Settings::is_syncing();
Settings::set_is_syncing( true );
$post->post_content_filtered = apply_filters( 'the_content', $post->post_content );
$post->post_excerpt_filtered = apply_filters( 'the_excerpt', $post->post_excerpt );
Settings::set_is_syncing( $is_syncing_current );
foreach ( $removed_shortcode_callbacks as $shortcode => $callback ) {
add_shortcode( $shortcode, $callback );

View File

@ -8,6 +8,7 @@
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use Automattic\Jetpack\Waf\Brute_Force_Protection\Brute_Force_Protection;
/**
* Class to handle sync for Protect.
@ -45,7 +46,8 @@ class Protect extends Module {
* @param array $failed_attempt Failed attempt data.
*/
public function maybe_log_failed_login_attempt( $failed_attempt ) {
if ( $failed_attempt['has_login_ability'] && ! Jetpack_Constants::is_true( 'XMLRPC_REQUEST' ) ) {
$brute_force_protection = Brute_Force_Protection::instance();
if ( $brute_force_protection->has_login_ability() && ! Jetpack_Constants::is_true( 'XMLRPC_REQUEST' ) ) {
do_action( 'jetpack_valid_failed_login_attempt', $failed_attempt );
}
}

View File

@ -231,6 +231,7 @@ class Search extends Module {
'age' => array(),
'aliases' => array(),
'alternate_title' => array(),
'ama_content' => array(),
'amazon' => array(),
'answer' => array( 'searchable_in_all_content' => true ),
'area' => array(),
@ -288,6 +289,8 @@ class Search extends Module {
'fullscreen_view' => array(),
'gallery' => array(),
'genre' => array( 'searchable_in_all_content' => true ),
'guest_bio' => array(),
'guest_name' => array(),
'guests' => array( 'searchable_in_all_content' => true ),
'has_variations' => array(),
'hashtag' => array(),
@ -344,6 +347,7 @@ class Search extends Module {
'panels_data' => array(),
'parking' => array(),
'pdf_upload' => array(),
'people_mentioned' => array(),
'photo' => array(),
'play_time' => array(),
'position' => array(),
@ -365,12 +369,15 @@ class Search extends Module {
'review_post' => array(),
'rule' => array(),
'section' => array( 'searchable_in_all_content' => true ),
'selected_links' => array(),
'session_transcript' => array(),
'settings' => array(),
'sex' => array(),
'shares_count' => array(),
'show_description' => array( 'searchable_in_all_content' => true ),
'show_page_title' => array(),
'show_notes' => array(),
'show_notes_preview' => array(),
'side' => array(),
'sidebar' => array(),
'site' => array(),
@ -1733,6 +1740,7 @@ class Search extends Module {
* @var array
*/
private static $options_to_sync = array(
'jetpack_search_ai_prompt_override',
'jetpack_search_color_theme',
'jetpack_search_result_format',
'jetpack_search_default_sort',
@ -1766,7 +1774,7 @@ class Search extends Module {
* @return array Updated post meta whitelist.
*/
public function add_search_post_meta_whitelist( $list ) {
return array_merge( $list, $this->get_all_postmeta_keys() );
return array_merge( $list, static::get_all_postmeta_keys() );
}
/**
@ -1776,7 +1784,7 @@ class Search extends Module {
* @return array Updated options whitelist.
*/
public function add_search_options_whitelist( $list ) {
return array_merge( $list, $this->get_all_option_keys() );
return array_merge( $list, static::get_all_option_keys() );
}
//
@ -1864,5 +1872,4 @@ class Search extends Module {
public static function get_all_taxonomies() {
return self::$taxonomies_to_sync;
}
}

View File

@ -119,8 +119,8 @@ class Term_Relationships extends Module {
*/
$objects = $wpdb->get_results( $wpdb->prepare( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships WHERE ( object_id = %d AND term_taxonomy_id < %d ) OR ( object_id < %d ) ORDER BY object_id DESC, term_taxonomy_id DESC LIMIT %d", $last_object_enqueued['object_id'], $last_object_enqueued['term_taxonomy_id'], $last_object_enqueued['object_id'], $limit ), ARRAY_A );
// Request term relationships in groups of N for efficiency.
$objects_count = count( $objects );
if ( ! count( $objects ) ) {
$objects_count = is_countable( $objects ) ? count( $objects ) : 0;
if ( ! $objects_count ) {
return array( $items_enqueued_count, true );
}
$items = array_chunk( $objects, $term_relationships_full_sync_item_size );

View File

@ -310,5 +310,4 @@ class Terms extends Module {
public function expand_terms_for_relationship( $relationship ) {
return get_term_by( 'term_taxonomy_id', $relationship->term_taxonomy_id );
}
}

View File

@ -112,9 +112,10 @@ class Themes extends Module {
*/
public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$all_enabled_theme_slugs = array_keys( $value );
$old_value_count = is_countable( $old_value ) ? count( $old_value ) : 0;
$value_count = is_countable( $value ) ? count( $value ) : 0;
if ( count( $old_value ) > count( $value ) ) {
if ( $old_value_count > $value_count ) {
// Suppress jetpack_network_disabled_themes sync action when theme is deleted.
$delete_theme_call = $this->get_delete_theme_call();
if ( ! empty( $delete_theme_call ) ) {
@ -307,8 +308,8 @@ class Themes extends Module {
}
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writeable
if ( ! is_writeable( $real_file ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
if ( ! is_writable( $real_file ) ) {
return;
}
@ -872,5 +873,4 @@ class Themes extends Module {
return array( $this->get_theme_info() );
}
}

View File

@ -76,6 +76,14 @@ class Updates extends Module {
add_action( 'jetpack_update_themes_change', $callable );
add_action( 'jetpack_update_core_change', $callable );
add_filter(
'jetpack_sync_before_enqueue_jetpack_update_themes_change',
array(
$this,
'expand_themes',
)
);
add_filter(
'jetpack_sync_before_enqueue_jetpack_update_plugins_change',
array(
@ -127,7 +135,6 @@ class Updates extends Module {
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_updates', array( $this, 'expand_updates' ) );
add_filter( 'jetpack_sync_before_send_jetpack_update_themes_change', array( $this, 'expand_themes' ) );
}
/**
@ -492,7 +499,7 @@ class Updates extends Module {
* @return array $args The hook parameters.
*/
public function expand_themes( $args ) {
if ( ! isset( $args[0], $args[0]->response ) ) {
if ( ! isset( $args[0]->response ) ) {
return $args;
}
if ( ! is_array( $args[0]->response ) ) {
@ -579,5 +586,4 @@ class Updates extends Module {
return false;
}
}

View File

@ -142,13 +142,15 @@ class Users extends Module {
add_action( 'jetpack_wp_login', $callable, 10, 3 );
add_action( 'wp_logout', $callable, 10, 0 );
add_action( 'wp_logout', $callable, 10, 1 );
add_action( 'wp_masterbar_logout', $callable, 10, 1 );
// Add on init.
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) );
add_filter( 'jetpack_sync_before_enqueue_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
add_filter( 'jetpack_sync_before_enqueue_wp_logout', array( $this, 'expand_logout_username' ), 10, 1 );
}
/**
@ -168,9 +170,6 @@ class Users extends Module {
* @access public
*/
public function init_before_send() {
add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 );
add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 );
// Full sync.
add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) );
}
@ -295,7 +294,7 @@ class Users extends Module {
}
/**
* Expand the user username at login before being sent to the server.
* Expand the user username at login before enqueuing.
*
* @access public
*
@ -310,15 +309,16 @@ class Users extends Module {
}
/**
* Expand the user username at logout before being sent to the server.
* Expand the user username at logout before enqueuing.
*
* @access public
*
* @param array $args The hook arguments.
* @param int $user_id ID of the user.
* @return array $args Expanded hook arguments.
* @return false|array $args Expanded hook arguments or false if we don't have a user.
*/
public function expand_logout_username( $args, $user_id ) {
public function expand_logout_username( $args ) {
list( $user_id ) = $args;
$user = get_userdata( $user_id );
$user = $this->sanitize_user( $user );
@ -327,7 +327,7 @@ class Users extends Module {
$login = $user->data->user_login;
}
// If we don't have a user here lets not send anything.
// If we don't have a user here lets not enqueue anything.
if ( empty( $login ) ) {
return false;
}
@ -737,8 +737,9 @@ class Users extends Module {
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$user_ids = $wpdb->get_col( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$wpdb->prefix}user_level' AND meta_value > 0 LIMIT " . ( self::MAX_INITIAL_SYNC_USERS + 1 ) );
$user_ids_count = is_countable( $user_ids ) ? count( $user_ids ) : 0;
if ( count( $user_ids ) <= self::MAX_INITIAL_SYNC_USERS ) {
if ( $user_ids_count <= self::MAX_INITIAL_SYNC_USERS ) {
return $user_ids;
} else {
return false;
@ -884,20 +885,9 @@ class Users extends Module {
}
$names_as_keys = array_flip( $names );
// Do check in constant O(1) time for PHP5.5+.
if ( function_exists( 'array_column' ) ) {
$backtrace_functions = array_column( $backtrace, 'function' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_columnFound
$backtrace_functions_as_keys = array_flip( $backtrace_functions );
$intersection = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
return ! empty( $intersection );
}
// Do check in linear O(n) time for < PHP5.5 ( using isset at least prevents O(n^2) ).
foreach ( $backtrace as $call ) {
if ( isset( $names_as_keys[ $call['function'] ] ) ) {
return true;
}
}
return false;
$backtrace_functions = array_column( $backtrace, 'function' );
$backtrace_functions_as_keys = array_flip( $backtrace_functions );
$intersection = array_intersect_key( $backtrace_functions_as_keys, $names_as_keys );
return ! empty( $intersection );
}
}

View File

@ -0,0 +1,355 @@
<?php
/**
* WooCommerce HPOS orders sync module.
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Modules;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
/**
* Adds WooCommerce HPOS specific data to sync when HPOS is enabled on the site.
*/
class WooCommerce_HPOS_Orders extends Module {
/**
* Order table name. There are four order tables (order, addresses, operational_data and meta), but for sync purposes we only care about the main table since it has the order ID.
*
* @access private
*
* @var string
*/
private $order_table_name;
/**
* Sync module name.
*
* @access public
*
* @return string
*/
public function name() {
return 'woocommerce_hpos_orders';
}
/**
* Get the order table name.
*
* @access public
*
* @return string
*/
public function table_name() {
return $this->order_table_name;
}
/**
* Initialize order table data store, returns if the class don't exist (pre WC 6.x)
*
* @access public
*/
public function __construct() {
if ( ! class_exists( OrdersTableDataStore::class ) ) {
return;
}
$this->order_table_name = OrdersTableDataStore::get_orders_table_name();
}
/**
* Get order types that we want to sync. Adding a new type here is not enough, we would also need to add its prop in filter_order_data method.
*
* @access private
*
* @param bool $prefixed Whether to return prefixed types with shop_ or not.
*
* @return array Order types to sync.
*/
private function get_order_types_to_sync( $prefixed = false ) {
$types = array( 'order', 'order_refund' );
if ( $prefixed ) {
$types = array_map(
function ( $type ) {
return "shop_{$type}";
},
$types
);
}
return $types;
}
/**
* Hooks sync listners on order modify events.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
public function init_listeners( $callable ) {
foreach ( $this->get_order_types_to_sync() as $type ) {
add_action( "woocommerce_after_{$type}_object_save", $callable );
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_trash_order', $callable );
add_filter( 'jetpack_sync_before_enqueue_woocommerce_trash_order', array( $this, 'expand_order_object' ) );
}
/**
* Hooks the full sync listeners.
*
* @access public
*
* @param callable $callable Action handler callable.
*/
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' ) );
}
/**
* Returns the ID field from wc_orders table.
*
* @access public
*
* @return string
*/
public function id_field() {
return 'id';
}
/**
* Retrieve the actions that will be sent for this module during a full sync.
*
* @access public
*
* @return array Full sync actions of this module.
*/
public function get_full_sync_actions() {
return array( 'jetpack_full_sync_orders' );
}
/**
* Retrieve order data by its ID.
*
* @access public
*
* @param string $object_type Type of object to retrieve. Should be `order`.
* @param int $id Order ID.
*
* @return array
*/
public function get_object_by_id( $object_type, $id ) {
if ( 'order' !== $object_type ) {
return $id;
}
$order_objects = $this->get_objects_by_id( $object_type, array( $id ) );
return isset( $order_objects[ $id ] ) ? $order_objects[ $id ] : false;
}
/**
* Retrieves multiple orders data by their ID.
*
* @access public
*
* @param string $object_type Type of object to retrieve. Should be `order`.
* @param array $ids List of order IDs.
*
* @return array
*/
public function get_objects_by_id( $object_type, $ids ) {
if ( 'order' !== $object_type ) {
return $ids;
}
$orders = wc_get_orders(
array(
'include' => $ids,
'type' => $this->get_order_types_to_sync( true ),
)
);
$orders_data = array();
foreach ( $orders as $order ) {
$orders_data[ $order->get_id() ] = $this->filter_order_data( $order );
}
return $orders_data;
}
/**
* Retrieves multiple orders data by their ID.
*
* @access public
*
* @param array $args List of order IDs.
*
* @return array
*/
public function expand_order_objects( $args ) {
$order_ids = $args;
return $this->get_objects_by_id( 'order', $order_ids );
}
/**
* Retrieve order data by its ID.
*
* @access public
*
* @param array $args Order ID.
*
* @return array
*/
public function expand_order_object( $args ) {
$order_object = $args[0];
if ( is_int( $order_object ) ) {
$order_object = wc_get_order( $order_object );
}
if ( ! $order_object instanceof \WC_Abstract_Order ) {
return false;
}
return $this->filter_order_data( $order_object );
}
/**
* Filters only allowed keys from order data. No PII etc information is allowed to be synced.
*
* @access private
*
* @param \WC_Abstract_Order $order_object Order object.
*
* @return array Filtered order data.
*/
private function filter_order_data( $order_object ) {
// Filter with allowlist.
$allowed_data_keys = WooCommerce::$wc_post_meta_whitelist;
$core_table_keys = array(
'id',
'status',
'date_created',
'date_modified',
'parent_id',
);
$allowed_data_keys = array_merge( $allowed_data_keys, $core_table_keys );
$filtered_order_data = array( 'type' => $order_object->get_type() );
$order_data = $order_object->get_data();
foreach ( $allowed_data_keys as $key ) {
$key = trim( $key, '_' );
$key_parts = explode( '_', $key );
if ( in_array( $key_parts[0], array( 'order', 'refund' ), true ) ) {
if ( isset( $order_data[ $key_parts[1] ] ) && ! is_array( $order_data[ $key_parts[1] ] ) ) {
$filtered_order_data[ $key ] = $order_data[ $key_parts[1] ];
continue;
}
}
if ( in_array( $key_parts[0], array( 'billing', 'shipping' ), true ) && 2 === count( $key_parts ) ) {
if ( isset( $order_data[ $key_parts[0] ][ $key_parts[1] ] ) ) {
$filtered_order_data[ $key ] = $order_data[ $key_parts[0] ][ $key_parts[1] ];
continue;
}
}
if ( isset( $order_data[ $key ] ) ) {
$filtered_order_data[ $key ] = $order_data[ $key ];
continue;
}
switch ( $key ) {
case 'cart_discount':
$filtered_order_data[ $key ] = isset( $order_data['discount_total'] ) ? $order_data['discount_total'] : '';
break;
case 'cart_discount_tax':
$filtered_order_data[ $key ] = isset( $order_data['discount_tax'] ) ? $order_data['discount_tax'] : '';
break;
case 'order_shipping':
$filtered_order_data[ $key ] = isset( $order_data['shipping_total'] ) ? $order_data['shipping_total'] : '';
break;
case 'order_shipping_tax':
$filtered_order_data[ $key ] = isset( $order_data['shipping_tax'] ) ? $order_data['shipping_tax'] : '';
break;
case 'order_tax':
$filtered_order_data[ $key ] = isset( $order_data['cart_tax'] ) ? $order_data['cart_tax'] : '';
break;
case 'order_total':
$filtered_order_data[ $key ] = isset( $order_data['total'] ) ? $order_data['total'] : '';
break;
}
}
if ( '' === $filtered_order_data['status'] ) {
$filtered_order_data['status'] = 'pending';
}
return $filtered_order_data;
}
/**
* Returns metadata for order object.
*
* @access protected
*
* @param array $ids List of order IDs.
* @param string $meta_type Meta type.
* @param array $meta_key_whitelist List of allowed meta keys.
*
* @return array Filtered order metadata.
*/
protected function get_metadata( $ids, $meta_type, $meta_key_whitelist ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- returning empty meta is intentional.
return array(); // don't sync metadata, all allow-listed core data is available in the order object.
}
/**
* Retrieve an estimated number of actions that will be enqueued.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @return array Number of items yet to be enqueued.
*/
public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- 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 ) }";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Hardcoded query, no user variable
$count = $wpdb->get_var( $query );
return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
}
/**
* Enqueue the WooCommerce HPOS orders actions for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
* @param int $max_items_to_enqueue Maximum number of items to enqueue.
* @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
* @return array Number of actions enqueued, and next module state.
*/
public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
return $this->enqueue_all_ids_as_action( 'full_sync_orders', $this->table_name(), 'id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
}
/**
* Get where SQL for full sync.
*
* @access public
*
* @param array $config Full sync configuration for this sync module.
*
* @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
*/
public function get_where_sql( $config ) {
global $wpdb;
$parent_where = parent::get_where_sql( $config );
$order_types = $this->get_order_types_to_sync( true );
$order_type_placeholder = implode( ', ', array_fill( 0, count( $order_types ), '%s' ) );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Query is prepared.
$where_sql = $wpdb->prepare( "type IN ( $order_type_placeholder )", $order_types );
return "{$parent_where} AND {$where_sql}";
}
}

View File

@ -441,7 +441,7 @@ class WooCommerce extends Module {
*
* @var array
*/
private static $wc_post_meta_whitelist = array(
public static $wc_post_meta_whitelist = array(
// WooCommerce products.
// See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-product-data-store-cpt.php#L21 .
'_visibility',

View File

@ -180,5 +180,4 @@ class Table_Checksum_Users extends Table_Checksum {
return $result;
}
}

View File

@ -304,6 +304,30 @@ class Table_Checksum {
'table_join_field' => 'order_item_id',
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
),
'wc_orders' => array(
'table' => "{$wpdb->prefix}wc_orders",
'range_field' => 'id',
'key_fields' => array( 'id' ),
'checksum_text_fields' => array( 'type', 'status', 'payment_method_title' ),
'filter_values' => array(),
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
),
'wc_order_addresses' => array(
'table' => "{$wpdb->prefix}wc_order_addresses",
'range_field' => 'order_id',
'key_fields' => array( 'order_id', 'address_type' ),
'checksum_text_fields' => array( 'address_type' ),
'filter_values' => array(),
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
),
'wc_order_operational_data' => array(
'table' => "{$wpdb->prefix}wc_order_operational_data",
'range_field' => 'order_id',
'key_fields' => array( 'order_id' ),
'checksum_text_fields' => array( 'order_key', 'cart_hash' ),
'filter_values' => array(),
'is_table_enabled_callback' => array( $this, 'enable_woocommerce_tables' ),
),
'users' => array(
'table' => $wpdb->users,
'range_field' => 'ID',
@ -456,7 +480,8 @@ class Table_Checksum {
switch ( $filter['operator'] ) {
case 'IN':
case 'NOT IN':
$values_placeholders = implode( ',', array_fill( 0, count( $filter['values'] ), '%s' ) );
$filter_values_count = is_countable( $filter['values'] ) ? count( $filter['values'] ) : 0;
$values_placeholders = implode( ',', array_fill( 0, $filter_values_count, '%s' ) );
$statement = "{$key} {$filter['operator']} ( $values_placeholders )";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@ -852,5 +877,4 @@ class Table_Checksum {
return true;
}
}

View File

@ -0,0 +1,291 @@
<?php
/**
* The class responsible for storing Queue events in the `wp_options` table.
*
* Used by class Queue.
*
* @see \Automattic\Jetpack\Sync\Queue
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Queue;
/**
* `wp_options` storage backend for the Queue.
*/
class Queue_Storage_Options {
/**
* What queue is this instance responsible for.
*
* @var string
*/
public $queue_id = '';
/**
* Class constructor.
*
* @param string $queue_id The queue name this instance will be responsible for.
*
* @throws \Exception If queue name was not provided.
*/
public function __construct( $queue_id ) {
if ( empty( $queue_id ) ) {
// TODO what should we return here or throw an exception?
throw new \Exception( 'Invalid queue_id provided' );
}
// TODO validate the value maybe?
$this->queue_id = $queue_id;
}
/**
* Insert an item in the queue.
*
* @param string $item_id The item ID.
* @param string $item Serialized item data.
*
* @return bool If the item was added.
*/
public function insert_item( $item_id, $item ) {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$rows_added = $wpdb->query(
$wpdb->prepare(
"INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES (%s, %s,%s)",
$item_id,
$item,
'no'
)
);
return ( 0 !== $rows_added );
}
/**
* Fetch items from the queue.
*
* @param int|null $item_count How many items to fetch from the queue.
* The parameter is null-able, if no limit on the amount of items.
*
* @return array|object|\stdClass[]|null
*/
public function fetch_items( $item_count ) {
global $wpdb;
// TODO make it more simple for the $item_count
if ( $item_count ) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$items = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
"jpsq_{$this->queue_id}-%",
$item_count
),
OBJECT
);
} else {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$items = $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, option_value AS value FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC",
"jpsq_{$this->queue_id}-%"
),
OBJECT
);
}
return $items;
}
/**
* Fetches items with specific IDs from the Queue.
*
* @param array $items_ids Items IDs to fetch from the queue.
*
* @return \stdClass[]|null
*/
public function fetch_items_by_ids( $items_ids ) {
global $wpdb;
// return early if $items_ids is empty or not an array.
if ( empty( $items_ids ) || ! is_array( $items_ids ) ) {
return array();
}
$ids_placeholders = implode( ', ', array_fill( 0, count( $items_ids ), '%s' ) );
$query_with_placeholders = "SELECT option_name AS id, option_value AS value
FROM $wpdb->options
WHERE option_name IN ( $ids_placeholders )";
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$items = $wpdb->get_results(
$wpdb->prepare(
$query_with_placeholders, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$items_ids
),
OBJECT
);
return $items;
}
/**
* Clear out the queue.
*
* @return bool|int|\mysqli_result|resource|null
*/
public function clear_queue() {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
return $wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
"jpsq_{$this->queue_id}-%"
)
);
}
/**
* Check how many items are in the queue.
*
* @return int
*/
public function get_item_count() {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT count(*) FROM $wpdb->options WHERE option_name LIKE %s",
"jpsq_{$this->queue_id}-%"
)
);
}
/**
* Return the lag amount for the queue.
*
* @param float|int|null $now A timestamp to use as starting point when calculating the lag.
*
* @return float|int The lag amount.
*/
public function get_lag( $now = null ) {
global $wpdb;
// TODO replace with peek and a flag to fetch only the name.
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$first_item_name = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT 1",
"jpsq_{$this->queue_id}-%"
)
);
if ( ! $first_item_name ) {
return 0;
}
if ( null === $now ) {
$now = microtime( true );
}
// Break apart the item name to get the timestamp.
$matches = null;
if ( preg_match( '/^jpsq_' . $this->queue_id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) {
return $now - (float) $matches[1];
} else {
return 0;
}
}
/**
* Add multiple items to the queue at once.
*
* @param array $items Array of items to add.
* @param string $id_prefix Prefix to use for all the items.
*
* @return bool|int|\mysqli_result|resource|null
*/
public function add_all( $items, $id_prefix ) {
global $wpdb;
$query = "INSERT INTO $wpdb->options (option_name, option_value, autoload) VALUES ";
$rows = array();
$count_items = count( $items );
for ( $i = 0; $i < $count_items; ++$i ) {
// skip empty items.
if ( empty( $items[ $i ] ) ) {
continue;
}
try {
$option_name = esc_sql( $id_prefix . '-' . $i );
$option_value = esc_sql( serialize( $items[ $i ] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
$rows[] = "('$option_name', '$option_value', 'no')";
} catch ( \Exception $e ) {
// Item cannot be serialized so skip.
continue;
}
}
$rows_added = $wpdb->query( $query . implode( ',', $rows ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return $rows_added;
}
/**
* Return $max_count items from the queue, including their value string length.
*
* @param int $max_count How many items to fetch from the queue.
*
* @return \stdClass[]|null
*/
public function get_items_ids_with_size( $max_count ) {
global $wpdb;
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
return $wpdb->get_results(
$wpdb->prepare(
"SELECT option_name AS id, LENGTH(option_value) AS value_size FROM $wpdb->options WHERE option_name LIKE %s ORDER BY option_name ASC LIMIT %d",
"jpsq_{$this->queue_id}-%",
$max_count
),
OBJECT
);
}
/**
* Delete items with specific IDs from the queue.
*
* @param array $ids IDs of the items to remove from the queue.
*
* @return bool|int|\mysqli_result|resource|null
*/
public function delete_items_by_ids( $ids ) {
global $wpdb;
if ( ! is_array( $ids ) || empty( $ids ) ) {
return false;
}
// TODO check if it's working properly - no need to delete all options in the table if the params are not right
$ids_placeholders = implode( ', ', array_fill( 0, count( $ids ), '%s' ) );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
return $wpdb->query(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$ids_placeholders`, as we're preparing them above and are a dynamic count.
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
"DELETE FROM {$wpdb->options} WHERE option_name IN ( $ids_placeholders )",
$ids
)
);
}
}

View File

@ -0,0 +1,663 @@
<?php
/**
* The class responsible for storing Queue events in a custom Sync events table.
*
* Used by class Queue.
*
* @see \Automattic\Jetpack\Sync\Queue
*
* @package automattic/jetpack-sync
*/
namespace Automattic\Jetpack\Sync\Queue;
/**
* Custom Sync events table storage backend for the Queue.
*/
class Queue_Storage_Table {
/**
* The custom Sync events table name, without a prefix.
* A prefix will be added when the class is instantiated,
* as we fetch the prefix from `$wpdb` as is configured in
* the WordPress config file.
*
* @var string
*/
public $table_name_no_prefix = 'jetpack_sync_queue';
/**
* The table name with the DB prefix.
*
* @var string
*/
public $table_name = '';
/**
* What queue is this instance responsible for.
*
* @var string
*/
public $queue_id = '';
/**
* Class constructor.
*
* @param string $queue_id The queue name this instance will be responsible for.
*
* @throws \Exception If queue name was not provided.
*/
public function __construct( $queue_id ) {
global $wpdb;
if ( empty( $queue_id ) ) {
// TODO what should we return here or throw an exception?
throw new \Exception( 'Invalid queue_id provided' );
}
// TODO validate the value maybe?
$this->queue_id = $queue_id;
// Initialize the `table_name` property with the correct prefix for easier usage in the class.
$this->table_name = $wpdb->prefix . $this->table_name_no_prefix;
}
/**
* Creates the new table and updates the options to work with
* the new table if it was created successfully.
*
* @return void
*/
protected function create_table() {
global $wpdb;
require_once ABSPATH . '/wp-admin/includes/upgrade.php';
$charset_collate = $wpdb->get_charset_collate();
$table_definition = "CREATE TABLE {$this->table_name} (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`queue_id` varchar(50) NOT NULL,
`event_id` varchar(100) NOT NULL,
`event_payload` longtext NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`ID`),
KEY `event_id` (`event_id`),
KEY `queue_id` (`queue_id`),
KEY `queue_id_event_id` (queue_id, event_id),
KEY `timestamp` (`timestamp`)
) $charset_collate;";
/**
* The function dbDelta will only return the differences. If the table exists, the result will be empty,
* so let's run a check afterward to see if the table exists and is healthy.
*/
\dbDelta( $table_definition );
}
/**
* Check if the Custom table actually exists.
*
* @return bool
*/
protected function custom_table_exists() {
global $wpdb;
// Check if the table exists
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$result = $wpdb->get_row(
$wpdb->prepare( 'SHOW TABLES LIKE %s', $this->table_name ),
ARRAY_N
);
if ( empty( $result ) || count( $result ) !== 1 || $result[0] !== $this->table_name ) {
return false;
}
return true;
}
/**
* Check if the table is healthy, and we can read and write from/to it.
*
* @return true|\WP_Error If the custom table is available, and we can read and write from/to it.
*/
protected function is_custom_table_healthy() {
global $wpdb;
if ( ! $this->custom_table_exists() ) {
return new \WP_Error( 'custom_table_not_exist', 'Jetpack Sync Custom table: Table does not exist' );
}
// Try to read from the table
// Ignore the interpolated table name
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$query = $wpdb->query( "SELECT count(`ID`) FROM {$this->table_name}" );
if ( $query === false ) {
// The query failed to select anything from the table, so there must be an issue reading from it.
return new \WP_Error( 'custom_table_unable_to_read', 'Jetpack Sync Custom table: Unable to read from table' );
}
if ( $wpdb->last_error ) {
// There was an error reading, that's not necessarily failing the query.
// TODO check if we need this error check.
// TODO add more information about the erorr in the return value.
return new \WP_Error( 'custom_table_unable_to_read_sql_error', 'Jetpack Sync Custom table: Unable to read from table - SQL error' );
}
// Check if we can write in the table
if ( ! $this->insert_item( 'test', 'test' ) ) {
return new \WP_Error( 'custom_table_unable_to_writeread', 'Jetpack Sync Custom table: Unable to write into table' );
}
// See if we can read the item back
$items = $this->fetch_items_by_ids( array( 'test' ) );
if ( empty( $items ) || ! is_object( $items[0] ) || $items[0]->value !== 'test' ) {
return new \WP_Error( 'custom_table_unable_to_writeread', 'Jetpack Sync Custom table: Unable to read item after writing' );
}
// Try to insert an item, read it back and then delete it.
$this->delete_items_by_ids( array( 'test' ) );
// Try to fetch the item back. It should not exist.
$items = $this->fetch_items_by_ids( array( 'test' ) );
if ( ! empty( $items ) ) {
return new \WP_Error( 'custom_table_unable_to_writeread', 'Jetpack Sync Custom table: Unable to delete from table' );
}
return true;
}
/**
* Drop the custom table as part of cleanup.
*
* @return bool If the table is cleared.
*/
public function drop_table() {
global $wpdb;
if ( $this->custom_table_exists() ) {
// Ignoring the linting warning, as there's still no placeholder replacement for DB field name.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.SchemaChange
return (bool) $wpdb->query( "DROP TABLE {$this->table_name}" );
}
}
/**
* Queue API implementation
*/
/**
* Insert an item in the queue.
*
* @param string $item_id The item ID.
* @param string $item Serialized item data.
*
* @return bool If the item was added.
*/
public function insert_item( $item_id, $item ) {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$rows_added = $wpdb->query(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"INSERT INTO {$this->table_name} (queue_id, event_id, event_payload) VALUES (%s, %s,%s)",
$this->queue_id,
$item_id,
$item
)
);
return ( 0 !== $rows_added );
}
/**
* Fetch items from the queue.
*
* @param int|null $item_count How many items to fetch from the queue.
* The parameter is null-able, if no limit on the amount of items.
*
* @return array|object|stdClass[]|null
*/
public function fetch_items( $item_count ) {
global $wpdb;
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// TODO make it more simple for the $item_count
if ( $item_count ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$items = $wpdb->get_results(
$wpdb->prepare(
"
SELECT
event_id AS id,
event_payload AS value
FROM {$this->table_name}
WHERE queue_id LIKE %s
ORDER BY event_id ASC
LIMIT %d
",
$this->queue_id,
$item_count
)
);
} else {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$items = $wpdb->get_results(
$wpdb->prepare(
"
SELECT
event_id AS id,
event_payload AS value
FROM {$this->table_name}
WHERE queue_id LIKE %s
ORDER BY event_id ASC
",
$this->queue_id
)
);
}
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $items;
}
/**
* Fetches items with specific IDs from the Queue.
*
* @param array $items_ids Items IDs to fetch from the queue.
*
* @return array|object|stdClass[]|null
*/
public function fetch_items_by_ids( $items_ids ) {
global $wpdb;
// return early if $items_ids is empty or not an array.
if ( empty( $items_ids ) || ! is_array( $items_ids ) ) {
return array();
}
$ids_placeholders = implode( ', ', array_fill( 0, count( $items_ids ), '%s' ) );
$query_with_placeholders = "SELECT event_id AS id, event_payload AS value
FROM {$this->table_name}
WHERE queue_id = %s AND event_id IN ( $ids_placeholders )";
$replacement_values = array_merge( array( $this->queue_id ), $items_ids );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$items = $wpdb->get_results(
$wpdb->prepare(
$query_with_placeholders, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$replacement_values
),
OBJECT
);
return $items;
}
/**
* Check how many items are in the queue.
*
* @return int
*/
public function get_item_count() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (int) $wpdb->get_var(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT count(*) FROM {$this->table_name} WHERE queue_id = %s",
$this->queue_id
)
);
}
/**
* Clear out the queue.
*
* @return bool|int|\mysqli_result|resource|null
*/
public function clear_queue() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->query(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"DELETE FROM {$this->table_name} WHERE queue_id = %s",
$this->queue_id
)
);
}
/**
* Return the lag amount for the queue.
*
* @param float|int|null $now A timestamp to use as starting point when calculating the lag.
*
* @return float|int The lag amount.
*/
public function get_lag( $now = null ) {
global $wpdb;
// TODO replace with peek and a flag to fetch only the name.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$first_item_name = $wpdb->get_var(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT event_id FROM {$this->table_name} WHERE queue_id = %s ORDER BY event_id ASC LIMIT 1",
$this->queue_id
)
);
if ( ! $first_item_name ) {
return 0;
}
if ( null === $now ) {
$now = microtime( true );
}
// Break apart the item name to get the timestamp.
$matches = null;
if ( preg_match( '/^jpsq_' . $this->queue_id . '-(\d+\.\d+)-/', $first_item_name, $matches ) ) {
return $now - (float) $matches[1];
} else {
return 0;
}
}
/**
* Add multiple items to the queue at once.
*
* @param array $items Array of items to add.
* @param string $id_prefix Prefix to use for all the items.
*
* @return bool|int|\mysqli_result|resource|null
*/
public function add_all( $items, $id_prefix ) {
global $wpdb;
$query = "INSERT INTO {$this->table_name} (queue_id, event_id, event_payload ) VALUES ";
$rows = array();
$count_items = count( $items );
for ( $i = 0; $i < $count_items; ++$i ) {
// skip empty items.
if ( empty( $items[ $i ] ) ) {
continue;
}
try {
$event_id = esc_sql( $id_prefix . '-' . $i );
$event_payload = esc_sql( serialize( $items[ $i ] ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
$rows[] = "('{$this->queue_id}', '$event_id','$event_payload')";
} catch ( \Exception $e ) {
// Item cannot be serialized so skip.
continue;
}
}
$rows_added = $wpdb->query( $query . implode( ',', $rows ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return $rows_added;
}
/**
* Return $max_count items from the queue, including their value string length.
*
* @param int $max_count How many items to fetch from the queue.
*
* @return array|object|stdClass[]|null
*/
public function get_items_ids_with_size( $max_count ) {
global $wpdb;
// TODO optimize the fetch to happen by queue name not by the IDs as it can be issue cross-queues.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_results(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT event_id AS id, LENGTH( event_payload ) AS value_size FROM {$this->table_name} WHERE queue_id = %s ORDER BY event_id ASC LIMIT %d",
$this->queue_id,
$max_count
),
OBJECT
);
}
/**
* Delete items with specific IDs from the queue.
*
* @param array $ids IDs of the items to remove from the queue.
*
* @return bool|int|\mysqli_result|resource|null
*/
public function delete_items_by_ids( $ids ) {
global $wpdb;
$ids_placeholders = implode( ', ', array_fill( 0, count( $ids ), '%s' ) );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->query(
$wpdb->prepare(
/**
* Ignoring the linting warning, as there's still no placeholder replacement for DB field name,
* in this case this is `$this->table_name`
*/
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"DELETE FROM {$this->table_name} WHERE queue_id = %s AND event_id IN ( $ids_placeholders )",
array_merge( array( $this->queue_id ), $ids )
)
);
}
/**
* Table initialization
*/
public static function initialize_custom_sync_table() {
/**
* Initialize an instance of the class with a test name, so we can use table prefix and then test if the table is healthy.
*/
$custom_table_instance = new Queue_Storage_Table( 'test_queue' );
// Check if the table exists
if ( ! $custom_table_instance->custom_table_exists() ) {
$custom_table_instance->create_table();
}
return $custom_table_instance->is_custom_table_healthy();
}
/**
* Migrates the existing Sync events from the options table to the Custom table
*
* @return bool
*/
public static function migrate_from_options_table_to_custom_table() {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$count_result = $wpdb->get_row(
"
SELECT
COUNT(*) as item_count
FROM
{$wpdb->options}
WHERE
option_name LIKE 'jpsq_%'
"
);
$item_count = $count_result->item_count;
$limit = 100;
$offset = 0;
do {
// get all the records from the options table
$query = "
SELECT
option_name as event_id,
option_value as event_payload
FROM
{$wpdb->options}
WHERE
option_name LIKE 'jpsq_%'
ORDER BY
option_name ASC
LIMIT $offset, $limit
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( $query );
$insert_rows = array();
foreach ( $rows as $event ) {
$event_id = $event->event_id;
// Parse the event
if (
preg_match(
'!jpsq_(?P<queue_id>[^-]+)-(?P<timestamp>[^-]+)-.+!',
$event_id,
$events_matches
)
) {
$queue_id = $events_matches['queue_id'];
$timestamp = $events_matches['timestamp'];
$insert_rows[] = $wpdb->prepare(
'(%s, %s, %s, %s)',
array(
$queue_id,
$event_id,
$event->event_payload,
(int) $timestamp,
)
);
}
}
// Instantiate table storage, so we can get the table name. Queue ID is just a placeholder here.
$queue_table_storage = new Queue_Storage_Table( 'test_queue' );
if ( ! empty( $insert_rows ) ) {
$insert_query = 'INSERT INTO ' . $queue_table_storage->table_name . ' (queue_id, event_id, event_payload, timestamp) VALUES ';
$insert_query .= implode( ',', $insert_rows );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $insert_query );
}
$offset += $limit;
} while ( $offset < $item_count );
// Clear out the options queue
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
'jpsq_%-%'
)
);
return true;
}
/**
* Migrates the existing Sync events from the Custom table to the Options table
*
* @return void
*/
public static function migrate_from_custom_table_to_options_table() {
global $wpdb;
// Instantiate table storage, so we can get the table name. Queue ID is just a placeholder here.
$queue_table_storage = new Queue_Storage_Table( 'test_queue' );
$custom_table_name = $queue_table_storage->table_name;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$count_result = $wpdb->get_row( "SELECT COUNT(*) as item_count FROM {$custom_table_name}" );
$item_count = $count_result->item_count;
$limit = 100;
$offset = 0;
do {
// get all the records from the options table
$query = "
SELECT
event_id,
event_payload
FROM
{$custom_table_name}
ORDER BY
event_id ASC
LIMIT $offset, $limit
";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( $query );
$insert_rows = array();
foreach ( $rows as $event ) {
$insert_rows[] = $wpdb->prepare(
'(%s, %s, "no")',
array(
$event->event_id,
$event->event_payload,
)
);
}
if ( ! empty( $insert_rows ) ) {
$insert_query = "INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES ";
$insert_query .= implode( ',', $insert_rows );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $insert_query );
}
$offset += $limit;
} while ( $offset < $item_count );
// Clear the custom table
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "DELETE FROM {$custom_table_name}" );
// TODO should we drop the table here instead?
}
}