updated plugin Jetpack Protect
version 1.4.2
This commit is contained in:
@ -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
|
||||
|
@ -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.**
|
||||
|
||||
|
@ -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": {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -185,5 +185,4 @@ class Health {
|
||||
self::update_status( self::STATUS_IN_SYNC );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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']}" ) );
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -140,5 +140,4 @@ class REST_Sender {
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 ) );
|
||||
|
@ -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' );
|
||||
}
|
||||
}
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -335,5 +335,4 @@ class Constants extends Module {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
@ -726,5 +726,4 @@ class Full_Sync extends Module {
|
||||
private function get_config() {
|
||||
return \Jetpack_Options::get_raw_option( 'jetpack_sync_full_config' );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 );
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -477,5 +477,4 @@ class Options extends Module {
|
||||
|
||||
return 'OPTION-DOES-NOT-EXIST';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 );
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 );
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -180,5 +180,4 @@ class Table_Checksum_Users extends Table_Checksum {
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -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?
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user